refactor(frontend): remove i18n fallback strings and drop providers route

- Replace `t(key, defaultValue)` calls with key-only translations across UI pages
- Clean up locale files by pruning unused keys and adding missing shared keys
- Remove the obsolete `/providers` page and update generated route tree
This commit is contained in:
wenjie 2026-03-09 19:12:41 +08:00
parent b06e9e3707
commit a418554441
23 changed files with 149 additions and 495 deletions

View file

@ -87,10 +87,7 @@ export function AppHeader() {
<span className="bg-destructive/50 relative flex size-2 shrink-0 items-center justify-center rounded-full">
<span className="bg-destructive absolute inline-flex size-full animate-ping rounded-full opacity-75"></span>
</span>
{t(
"chat.notConnected",
"Gateway is not running. Start it to chat.",
)}
{t("chat.notConnected")}
</div>
)}
</div>
@ -99,24 +96,19 @@ export function AppHeader() {
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("header.gateway.stopDialog.title", "Stop Gateway Service?")}
{t("header.gateway.stopDialog.title")}
</AlertDialogTitle>
<AlertDialogDescription>
{t(
"header.gateway.stopDialog.description",
"Are you sure you want to stop the gateway? This will disconnect your active chat sessions and halt inference.",
)}
{t("header.gateway.stopDialog.description")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("common.cancel", "Cancel")}
</AlertDialogCancel>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={confirmStop}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{t("header.gateway.stopDialog.confirm", "Stop Gateway")}
{t("header.gateway.stopDialog.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@ -146,10 +138,10 @@ export function AppHeader() {
)}
<span className="text-xs font-semibold">
{isRunning
? t("header.gateway.action.stop", "Stop Gateway")
? t("header.gateway.action.stop")
: isStarting
? t("header.gateway.status.starting", "Starting Gateway...")
: t("header.gateway.action.start", "Start Gateway")}
? t("header.gateway.status.starting")
: t("header.gateway.action.start")}
</span>
</Button>

View file

@ -494,7 +494,7 @@ export function ChannelConfigPage({ channelName }: ChannelConfigPageProps) {
<div className="w-full max-w-250 space-y-5 pt-2">
<div className="flex items-center gap-2 text-sm">
<p className="font-medium">
{t("channels.edit.title", {
{t("channels.edit", {
name: channelDisplayName,
})}
</p>

View file

@ -69,7 +69,7 @@ export function ChatPage() {
return (
<div className="bg-background/95 flex h-full flex-col">
<PageHeader
title={t("navigation.chat", "Chat")}
title={t("navigation.chat")}
titleExtra={
hasConfiguredModels && (
<ModelSelector

View file

@ -40,7 +40,7 @@ export function ModelSelector({
<SelectContent>
{apiKeyModels.length > 0 && (
<SelectGroup>
<SelectLabel>{t("chat.modelGroup.apikey", "API Key")}</SelectLabel>
<SelectLabel>{t("chat.modelGroup.apikey")}</SelectLabel>
{apiKeyModels.map((model) => (
<SelectItem key={model.index} value={model.model_name}>
{model.model_name}
@ -55,7 +55,7 @@ export function ModelSelector({
{oauthModels.length > 0 && (
<SelectGroup>
<SelectLabel>{t("chat.modelGroup.oauth", "OAuth")}</SelectLabel>
<SelectLabel>{t("chat.modelGroup.oauth")}</SelectLabel>
{oauthModels.map((model) => (
<SelectItem key={model.index} value={model.model_name}>
{model.model_name}
@ -70,7 +70,7 @@ export function ModelSelector({
{localModels.length > 0 && (
<SelectGroup>
<SelectLabel>{t("chat.modelGroup.local", "Local")}</SelectLabel>
<SelectLabel>{t("chat.modelGroup.local")}</SelectLabel>
{localModels.map((model) => (
<SelectItem key={model.index} value={model.model_name}>
{model.model_name}

View file

@ -71,7 +71,7 @@ export function SessionHistoryMenu({
<Button
variant="ghost"
size="icon"
aria-label={t("chat.deleteSession", "Delete session")}
aria-label={t("chat.deleteSession")}
className="text-muted-foreground hover:bg-destructive/10 hover:text-destructive absolute top-1/2 right-2 h-6 w-6 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100"
onClick={(e) => {
e.preventDefault()
@ -87,7 +87,7 @@ export function SessionHistoryMenu({
{hasMore && sessions.length > 0 && (
<div ref={observerRef} className="py-2 text-center">
<span className="text-muted-foreground animate-pulse text-xs">
{t("chat.loadingMore", "Loading more...")}
{t("chat.loadingMore")}
</span>
</div>
)}

View file

@ -106,29 +106,14 @@ export function ConfigPage() {
const autoStartSupported = autoStartStatus?.supported !== false
const autoStartHint = autoStartError
? t(
"pages.config.autostart_load_error",
"Failed to load launch-at-login status.",
)
? t("pages.config.autostart_load_error")
: !autoStartSupported
? t(
"pages.config.autostart_unsupported",
"Launch at login is not supported on this platform.",
)
: t(
"pages.config.autostart_hint",
"Start PicoClaw Web automatically when you log in.",
)
? t("pages.config.autostart_unsupported")
: t("pages.config.autostart_hint")
const launcherHint = launcherError
? t(
"pages.config.launcher_load_error",
"Failed to load service parameters.",
)
: t(
"pages.config.launcher_restart_hint",
"Service parameter changes apply after restarting PicoClaw Web.",
)
? t("pages.config.launcher_load_error")
: t("pages.config.launcher_restart_hint")
const updateField = <K extends keyof CoreConfigForm>(
key: K,
@ -148,12 +133,7 @@ export function ConfigPage() {
setForm(baseline)
setLauncherForm(launcherBaseline)
setAutoStartEnabled(autoStartBaseline)
toast.info(
t(
"pages.config.reset_success",
"Changes have been reset to the last saved state.",
),
)
toast.info(t("pages.config.reset_success"))
}
const handleSave = async () => {
@ -251,12 +231,7 @@ export function ConfigPage() {
if (autoStartDirty) {
if (!autoStartSupported) {
throw new Error(
t(
"pages.config.autostart_unsupported",
"Launch at login is not supported on this platform.",
),
)
throw new Error(t("pages.config.autostart_unsupported"))
}
const status = await updateAutoStartEnabled(autoStartEnabled)
setAutoStartEnabled(status.enabled)
@ -264,14 +239,10 @@ export function ConfigPage() {
queryClient.setQueryData(["system", "autostart"], status)
}
toast.success(
t("pages.config.save_success", "Configuration saved successfully."),
)
toast.success(t("pages.config.save_success"))
} catch (err) {
toast.error(
err instanceof Error
? err.message
: t("pages.config.save_error", "Failed to save configuration."),
err instanceof Error ? err.message : t("pages.config.save_error"),
)
} finally {
setSaving(false)
@ -281,12 +252,12 @@ export function ConfigPage() {
return (
<div className="flex h-full flex-col">
<PageHeader
title={t("navigation.config", "Config")}
title={t("navigation.config")}
children={
<Button variant="outline" asChild>
<Link to="/config/raw">
<IconCode className="size-4" />
{t("pages.config.open_raw", "Raw Config")}
{t("pages.config.open_raw")}
</Link>
</Button>
}
@ -295,23 +266,17 @@ export function ConfigPage() {
<div className="mx-auto w-full max-w-[1000px] space-y-6">
{isLoading ? (
<div className="text-muted-foreground py-6 text-sm">
{t("labels.loading", "Loading...")}
{t("labels.loading")}
</div>
) : error ? (
<div className="text-destructive py-6 text-sm">
{t(
"pages.config.load_error",
"Failed to load configuration. Please refresh and try again.",
)}
{t("pages.config.load_error")}
</div>
) : (
<div className="space-y-6">
{isDirty && (
<div className="bg-yellow-50 px-3 py-2 text-sm text-yellow-700">
{t(
"pages.config.unsaved_changes",
"You have unsaved changes.",
)}
{t("pages.config.unsaved_changes")}
</div>
)}
@ -356,13 +321,11 @@ export function ConfigPage() {
onClick={handleReset}
disabled={!isDirty || saving}
>
{t("common.reset", "Reset")}
{t("common.reset")}
</Button>
<Button onClick={handleSave} disabled={!isDirty || saving}>
<IconDeviceFloppy className="size-4" />
{saving
? t("common.saving", "Saving...")
: t("common.save", "Save")}
{saving ? t("common.saving") : t("common.save")}
</Button>
</div>
</div>

View file

@ -44,11 +44,8 @@ export function AgentDefaultsSection({
<section className="space-y-3">
<div className="space-y-4">
<Field
label={t("pages.config.workspace", "Workspace Directory")}
hint={t(
"pages.config.workspace_hint",
"Base directory for agent file operations.",
)}
label={t("pages.config.workspace")}
hint={t("pages.config.workspace_hint")}
>
<Input
value={form.workspace}
@ -58,11 +55,8 @@ export function AgentDefaultsSection({
</Field>
<SwitchCardField
label={t("pages.config.restrict_workspace", "Restrict to Workspace")}
hint={t(
"pages.config.restrict_workspace_hint",
"Only allow file operations inside workspace.",
)}
label={t("pages.config.restrict_workspace")}
hint={t("pages.config.restrict_workspace_hint")}
checked={form.restrictToWorkspace}
onCheckedChange={(checked) =>
onFieldChange("restrictToWorkspace", checked)
@ -70,11 +64,8 @@ export function AgentDefaultsSection({
/>
<Field
label={t("pages.config.max_tokens", "Max Tokens")}
hint={t(
"pages.config.max_tokens_hint",
"Upper token limit per model response.",
)}
label={t("pages.config.max_tokens")}
hint={t("pages.config.max_tokens_hint")}
>
<Input
type="number"
@ -85,11 +76,8 @@ export function AgentDefaultsSection({
</Field>
<Field
label={t("pages.config.max_tool_iterations", "Max Tool Iterations")}
hint={t(
"pages.config.max_tool_iterations_hint",
"Maximum tool-call loops in a single task.",
)}
label={t("pages.config.max_tool_iterations")}
hint={t("pages.config.max_tool_iterations_hint")}
>
<Input
type="number"
@ -100,14 +88,8 @@ export function AgentDefaultsSection({
</Field>
<Field
label={t(
"pages.config.summarize_threshold",
"Summarize Message Threshold",
)}
hint={t(
"pages.config.summarize_threshold_hint",
"Start summarization after this many messages.",
)}
label={t("pages.config.summarize_threshold")}
hint={t("pages.config.summarize_threshold_hint")}
>
<Input
type="number"
@ -120,14 +102,8 @@ export function AgentDefaultsSection({
</Field>
<Field
label={t(
"pages.config.summarize_token_percent",
"Summarize Token Percent",
)}
hint={t(
"pages.config.summarize_token_percent_hint",
"Used when conversation summary is triggered.",
)}
label={t("pages.config.summarize_token_percent")}
hint={t("pages.config.summarize_token_percent_hint")}
>
<Input
type="number"
@ -159,11 +135,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
<section className="space-y-3">
<div className="space-y-4">
<Field
label={t("pages.config.session_scope", "Session Scope")}
hint={t(
"pages.config.session_scope_hint",
"How chat context is isolated across peers/channels.",
)}
label={t("pages.config.session_scope")}
hint={t("pages.config.session_scope_hint")}
>
<Select
value={form.dmScope}
@ -183,11 +156,9 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
{DM_SCOPE_OPTIONS.map((scope) => (
<SelectItem key={scope.value} value={scope.value}>
<div className="flex flex-col gap-0.5">
<span className="font-medium">
{t(scope.labelKey, scope.labelDefault)}
</span>
<span className="font-medium">{t(scope.labelKey)}</span>
<span className="text-muted-foreground text-xs">
{t(scope.descKey, scope.descDefault)}
{t(scope.descKey)}
</span>
</div>
</SelectItem>
@ -197,11 +168,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
</Field>
<SwitchCardField
label={t("pages.config.heartbeat_enabled", "Heartbeat")}
hint={t(
"pages.config.heartbeat_enabled_hint",
"Send periodic heartbeat messages.",
)}
label={t("pages.config.heartbeat_enabled")}
hint={t("pages.config.heartbeat_enabled_hint")}
checked={form.heartbeatEnabled}
onCheckedChange={(checked) =>
onFieldChange("heartbeatEnabled", checked)
@ -210,14 +178,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
{form.heartbeatEnabled && (
<Field
label={t(
"pages.config.heartbeat_interval",
"Heartbeat Interval (minutes)",
)}
hint={t(
"pages.config.heartbeat_interval_hint",
"Interval in minutes between heartbeat signals.",
)}
label={t("pages.config.heartbeat_interval")}
hint={t("pages.config.heartbeat_interval_hint")}
>
<Input
type="number"
@ -253,11 +215,8 @@ export function LauncherSection({
<section className="space-y-3">
<div className="space-y-4">
<Field
label={t("pages.config.server_port", "Service Port")}
hint={t(
"pages.config.server_port_hint",
"HTTP port used by PicoClaw Web.",
)}
label={t("pages.config.server_port")}
hint={t("pages.config.server_port_hint")}
>
<Input
type="number"
@ -270,30 +229,21 @@ export function LauncherSection({
</Field>
<SwitchCardField
label={t("pages.config.lan_access", "Enable LAN Access")}
hint={t(
"pages.config.lan_access_hint",
"Allow access from other devices on your local network.",
)}
label={t("pages.config.lan_access")}
hint={t("pages.config.lan_access_hint")}
checked={launcherForm.publicAccess}
disabled={disabled}
onCheckedChange={(checked) => onFieldChange("publicAccess", checked)}
/>
<Field
label={t("pages.config.allowed_cidrs", "Allowed Network CIDRs")}
hint={t(
"pages.config.allowed_cidrs_hint",
"Only clients from these CIDR ranges can access the service. One per line or comma-separated. Leave empty to allow all.",
)}
label={t("pages.config.allowed_cidrs")}
hint={t("pages.config.allowed_cidrs_hint")}
>
<Textarea
value={launcherForm.allowedCIDRsText}
disabled={disabled}
placeholder={t(
"pages.config.allowed_cidrs_placeholder",
"192.168.1.0/24\n10.0.0.0/8",
)}
placeholder={t("pages.config.allowed_cidrs_placeholder")}
className="min-h-[88px]"
onChange={(e) => onFieldChange("allowedCIDRsText", e.target.value)}
/>
@ -328,11 +278,8 @@ export function DevicesSection({
<section className="space-y-3">
<div className="space-y-4">
<SwitchCardField
label={t("pages.config.devices_enabled", "Enable Devices")}
hint={t(
"pages.config.devices_enabled_hint",
"Enable hardware-device integrations.",
)}
label={t("pages.config.devices_enabled")}
hint={t("pages.config.devices_enabled_hint")}
checked={form.devicesEnabled}
onCheckedChange={(checked) =>
onFieldChange("devicesEnabled", checked)
@ -340,17 +287,14 @@ export function DevicesSection({
/>
<SwitchCardField
label={t("pages.config.monitor_usb", "Monitor USB")}
hint={t(
"pages.config.monitor_usb_hint",
"Watch USB plug/unplug events when devices are enabled.",
)}
label={t("pages.config.monitor_usb")}
hint={t("pages.config.monitor_usb_hint")}
checked={form.monitorUSB}
onCheckedChange={(checked) => onFieldChange("monitorUSB", checked)}
/>
<SwitchCardField
label={t("pages.config.autostart_label", "Launch at Login")}
label={t("pages.config.autostart_label")}
hint={autoStartHint}
checked={autoStartEnabled}
disabled={autoStartDisabled}
@ -367,16 +311,13 @@ export function AdvancedSection() {
return (
<section className="space-y-3">
<p className="text-muted-foreground text-sm">
{t(
"pages.config.advanced_desc",
"Open the raw JSON page to edit every field directly.",
)}
{t("pages.config.advanced_desc")}
</p>
<div>
<Button variant="outline" asChild>
<Link to="/config/raw">
<IconCode className="size-4" />
{t("pages.config.open_raw", "Raw Config")}
{t("pages.config.open_raw")}
</Link>
</Button>
</div>

View file

@ -52,9 +52,7 @@ export function RawJsonPanel() {
}
},
onSuccess: (_, submittedConfig) => {
toast.success(
t("pages.config.save_success", "Configuration saved successfully."),
)
toast.success(t("pages.config.save_success"))
try {
const savedConfig = JSON.parse(submittedConfig)
setLastSavedConfig(savedConfig)
@ -65,7 +63,7 @@ export function RawJsonPanel() {
}
},
onError: () => {
toast.error(t("pages.config.save_error", "Failed to save configuration."))
toast.error(t("pages.config.save_error"))
},
})
@ -101,9 +99,7 @@ export function RawJsonPanel() {
2,
)
setEditorValue(formatted)
toast.success(
t("pages.config.format_success", "JSON formatted successfully."),
)
toast.success(t("pages.config.format_success"))
} catch (error) {
toast.error(
t(
@ -123,38 +119,26 @@ export function RawJsonPanel() {
setEditorValue(JSON.stringify(config, null, 2))
}
setIsDirty(false)
toast.info(
t(
"pages.config.reset_success",
"Changes have been reset to the last saved state.",
),
)
toast.info(t("pages.config.reset_success"))
setShowResetDialog(false)
}
return (
<Card>
<CardHeader>
<CardTitle>
{t("pages.config.raw_json_title", "Raw JSON Configuration")}
</CardTitle>
<CardDescription>
{t(
"pages.config.raw_json_desc",
"Advanced users can directly edit the raw JSON configuration below.",
)}
</CardDescription>
<CardTitle>{t("pages.config.raw_json_title")}</CardTitle>
<CardDescription>{t("pages.config.raw_json_desc")}</CardDescription>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="flex h-64 items-center justify-center">
<p>{t("labels.loading", "Loading...")}</p>
<p>{t("labels.loading")}</p>
</div>
) : (
<div className="space-y-3">
{isDirty && (
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-2 text-sm text-yellow-700">
{t("pages.config.unsaved_changes", "You have unsaved changes.")}
{t("pages.config.unsaved_changes")}
</div>
)}
<div className="bg-muted/30 relative rounded-lg border">
@ -166,10 +150,7 @@ export function RawJsonPanel() {
setIsDirty(true)
}}
className="min-h-[200px] resize-none border-0 bg-transparent px-4 py-3 font-mono text-sm shadow-none focus-visible:ring-0"
placeholder={t(
"pages.config.json_placeholder",
"Enter valid JSON configuration...",
)}
placeholder={t("pages.config.json_placeholder")}
/>
</ScrollArea>
</div>
@ -179,7 +160,7 @@ export function RawJsonPanel() {
onClick={handleFormat}
disabled={mutation.isPending}
>
{t("pages.config.format", "Format")}
{t("pages.config.format")}
</Button>
<AlertDialog
open={showResetDialog}
@ -191,35 +172,28 @@ export function RawJsonPanel() {
disabled={!isDirty}
onClick={() => setShowResetDialog(true)}
>
{t("common.reset", "Reset")}
{t("common.reset")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("pages.config.reset_confirm_title", "Reset Changes")}
{t("pages.config.reset_confirm_title")}
</AlertDialogTitle>
<AlertDialogDescription>
{t(
"pages.config.reset_confirm_desc",
"Are you sure you want to reset your unsaved changes back to the last saved state?",
)}
{t("pages.config.reset_confirm_desc")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("common.cancel", "Cancel")}
</AlertDialogCancel>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={confirmReset}>
{t("common.confirm", "Confirm")}
{t("common.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Button onClick={handleSave} disabled={mutation.isPending}>
{mutation.isPending
? t("common.saving", "Saving...")
: t("common.save", "Save")}
{mutation.isPending ? t("common.saving") : t("common.save")}
</Button>
</div>
</div>

View file

@ -34,7 +34,7 @@ export function AnthropicCredentialCard({
const { t } = useTranslation()
const actionBusy = activeAction !== ""
const tokenLoading = activeAction === "anthropic:token"
const stopLabel = t("credentials.actions.stopLoading", "Stop Loading")
const stopLabel = t("credentials.actions.stopLoading")
return (
<CredentialCard
@ -46,10 +46,7 @@ export function AnthropicCredentialCard({
<span>Anthropic</span>
</span>
}
description={t(
"credentials.providers.anthropic.description",
"Uses token login for Claude access.",
)}
description={t("credentials.providers.anthropic.description")}
status={status?.status ?? "not_logged_in"}
authMethod={status?.auth_method}
actions={
@ -60,10 +57,7 @@ export function AnthropicCredentialCard({
value={token}
onChange={(e) => onTokenChange(e.target.value)}
type="password"
placeholder={t(
"credentials.fields.anthropicToken",
"Anthropic token",
)}
placeholder={t("credentials.fields.anthropicToken")}
/>
<Button
size="sm"
@ -75,7 +69,7 @@ export function AnthropicCredentialCard({
<IconLoader2 className="size-4 animate-spin" />
)}
<IconKey className="size-4" />
{t("credentials.actions.saveToken", "Save")}
{t("credentials.actions.saveToken")}
</Button>
{tokenLoading && (
<Button
@ -105,7 +99,7 @@ export function AnthropicCredentialCard({
{activeAction === "anthropic:logout" && (
<IconLoader2 className="size-4 animate-spin" />
)}
{t("credentials.actions.logout", "Logout")}
{t("credentials.actions.logout")}
</Button>
) : null
}

View file

@ -40,22 +40,19 @@ export function AntigravityCredentialCard({
<span>Google Antigravity</span>
</span>
}
description={t(
"credentials.providers.antigravity.description",
"Uses browser OAuth for Google Cloud Code Assist.",
)}
description={t("credentials.providers.antigravity.description")}
status={status?.status ?? "not_logged_in"}
authMethod={status?.auth_method}
details={
<div className="space-y-1">
{status?.email && (
<p>
{t("credentials.labels.email", "Email")}: {status.email}
{t("credentials.labels.email")}: {status.email}
</p>
)}
{status?.project_id && (
<p>
{t("credentials.labels.project", "Project")}: {status.project_id}
{t("credentials.labels.project")}: {status.project_id}
</p>
)}
</div>
@ -73,7 +70,7 @@ export function AntigravityCredentialCard({
<IconLoader2 className="size-4 animate-spin" />
)}
<IconLockOpen className="size-4" />
{t("credentials.actions.browser", "Browser OAuth")}
{t("credentials.actions.browser")}
</Button>
{browserLoading && (
<Button
@ -100,7 +97,7 @@ export function AntigravityCredentialCard({
{activeAction === "google-antigravity:logout" && (
<IconLoader2 className="size-4 animate-spin" />
)}
{t("credentials.actions.logout", "Logout")}
{t("credentials.actions.logout")}
</Button>
) : null
}

View file

@ -42,15 +42,12 @@ export function CredentialsPage() {
return (
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.credentials", "Credentials")} />
<PageHeader title={t("navigation.credentials")} />
<div className="min-h-0 flex-1 overflow-y-auto px-4 sm:px-6">
<div className="pt-2">
<p className="text-muted-foreground text-sm">
{t(
"credentials.description",
"Manage OAuth and token-based credentials for supported providers.",
)}
{t("credentials.description")}
</p>
</div>
@ -62,9 +59,7 @@ export function CredentialsPage() {
{activeFlow && (
<div className="bg-muted mt-4 rounded-lg border px-4 py-3 text-sm">
<p className="font-medium">
{t("credentials.flow.current", "Current authentication status")}
</p>
<p className="font-medium">{t("credentials.flow.current")}</p>
<p className="text-muted-foreground mt-1">{flowHint}</p>
</div>
)}
@ -72,7 +67,7 @@ export function CredentialsPage() {
{loading ? (
<div className="text-muted-foreground flex items-center gap-2 py-10 text-sm">
<IconLoader2 className="size-4 animate-spin" />
{t("credentials.loading", "Loading credentials...")}
{t("credentials.loading")}
</div>
) : (
<div className="grid grid-cols-1 gap-4 py-5 lg:auto-rows-fr lg:grid-cols-3">

View file

@ -34,21 +34,16 @@ export function DeviceCodeSheet({
className="data-[side=right]:!w-full data-[side=right]:sm:!w-[480px] data-[side=right]:sm:!max-w-[480px]"
>
<SheetHeader className="border-b-muted border-b px-6 py-5">
<SheetTitle>
{t("credentials.device.title", "OpenAI Device Login")}
</SheetTitle>
<SheetTitle>{t("credentials.device.title")}</SheetTitle>
<SheetDescription>
{t(
"credentials.device.description",
"Open the verification page and enter the code below. This page will refresh automatically.",
)}
{t("credentials.device.description")}
</SheetDescription>
</SheetHeader>
<div className="space-y-4 px-6 py-5">
<div>
<p className="text-muted-foreground text-xs uppercase">
{t("credentials.device.code", "User Code")}
{t("credentials.device.code")}
</p>
<p className="mt-1 rounded-md border px-3 py-2 font-mono text-lg font-semibold tracking-wide">
{flow?.user_code || "-"}
@ -57,7 +52,7 @@ export function DeviceCodeSheet({
<div>
<p className="text-muted-foreground text-xs uppercase">
{t("credentials.device.url", "Verification URL")}
{t("credentials.device.url")}
</p>
<a
href={flow?.verify_url || "#"}
@ -71,7 +66,7 @@ export function DeviceCodeSheet({
<div className="text-muted-foreground flex items-center gap-2 text-sm">
<IconRefresh className="size-4" />
{t("credentials.device.polling", "Polling login status...")}
{t("credentials.device.polling")}
</div>
{flow && (
@ -83,11 +78,11 @@ export function DeviceCodeSheet({
<SheetFooter className="border-t-muted border-t px-6 py-4">
<Button variant="ghost" onClick={() => onOpenChange(false)}>
{t("common.cancel", "Close")}
{t("common.cancel")}
</Button>
<Button asChild disabled={!flow?.verify_url}>
<a href={flow?.verify_url || "#"} target="_blank" rel="noreferrer">
{t("credentials.device.open", "Open Verification Page")}
{t("credentials.device.open")}
</a>
</Button>
</SheetFooter>

View file

@ -34,7 +34,7 @@ export function LogoutConfirmDialog({
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("credentials.logoutDialog.title", "Logout provider?")}
{t("credentials.logoutDialog.title")}
</AlertDialogTitle>
<AlertDialogDescription>
{t(
@ -45,10 +45,10 @@ export function LogoutConfirmDialog({
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("common.cancel", "Cancel")}</AlertDialogCancel>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm} variant="destructive">
{isSubmitting && <IconLoader2 className="size-4 animate-spin" />}
{t("credentials.actions.logout", "Logout")}
{t("credentials.actions.logout")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View file

@ -53,16 +53,13 @@ export function OpenAICredentialCard({
<span>OpenAI</span>
</span>
}
description={t(
"credentials.providers.openai.description",
"Supports browser OAuth, device code, and token login.",
)}
description={t("credentials.providers.openai.description")}
status={status?.status ?? "not_logged_in"}
authMethod={status?.auth_method}
details={
status?.account_id ? (
<p>
{t("credentials.labels.account", "Account")}: {status.account_id}
{t("credentials.labels.account")}: {status.account_id}
</p>
) : null
}
@ -81,7 +78,7 @@ export function OpenAICredentialCard({
<IconLoader2 className="size-4 animate-spin" />
)}
<IconBrandOpenai className="size-4" />
{t("credentials.actions.browser", "Browser OAuth")}
{t("credentials.actions.browser")}
</Button>
{oauthLoading && !deviceLoading && (
@ -105,7 +102,7 @@ export function OpenAICredentialCard({
<IconLoader2 className="size-4 animate-spin" />
)}
<IconClockHour4 className="size-4" />
{t("credentials.actions.deviceCode", "Device Code")}
{t("credentials.actions.deviceCode")}
</Button>
</div>
</div>
@ -116,10 +113,7 @@ export function OpenAICredentialCard({
value={token}
onChange={(e) => onTokenChange(e.target.value)}
type="password"
placeholder={t(
"credentials.fields.openaiToken",
"OpenAI token",
)}
placeholder={t("credentials.fields.openaiToken")}
/>
<Button
size="sm"
@ -130,7 +124,7 @@ export function OpenAICredentialCard({
<IconLoader2 className="size-4 animate-spin" />
)}
<IconKey className="size-4" />
{t("credentials.actions.saveToken", "Save")}
{t("credentials.actions.saveToken")}
</Button>
{tokenLoading && (
<Button
@ -159,7 +153,7 @@ export function OpenAICredentialCard({
{activeAction === "openai:logout" && (
<IconLoader2 className="size-4 animate-spin" />
)}
{t("credentials.actions.logout", "Logout")}
{t("credentials.actions.logout")}
</Button>
) : null
}

View file

@ -26,12 +26,12 @@ export function ProviderStatusLine({
<div className="flex items-center justify-between gap-2">
<span className={`rounded px-2 py-1 text-xs font-medium ${style}`}>
{status === "connected"
? t("credentials.status.connected", "Connected")
? t("credentials.status.connected")
: status === "needs_refresh"
? t("credentials.status.needsRefresh", "Needs refresh")
? t("credentials.status.needsRefresh")
: status === "expired"
? t("credentials.status.expired", "Expired")
: t("credentials.status.notLoggedIn", "Not logged in")}
? t("credentials.status.expired")
: t("credentials.status.notLoggedIn")}
</span>
{authMethod && (
<span className="text-muted-foreground text-xs uppercase">

View file

@ -136,7 +136,7 @@ export function ModelsPage() {
return (
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.models", "Models")}>
<PageHeader title={t("navigation.models")}>
<div className="flex items-center gap-3">
<Button size="sm" variant="outline" onClick={() => setAddOpen(true)}>
<IconPlus className="size-4" />

View file

@ -53,9 +53,7 @@ export function useCredentialsPage() {
setError("")
} catch (err) {
setError(
err instanceof Error
? err.message
: t("credentials.errors.loadFailed", "Failed to load credentials"),
err instanceof Error ? err.message : t("credentials.errors.loadFailed"),
)
} finally {
setLoading(false)
@ -113,10 +111,7 @@ export function useCredentialsPage() {
setError(
err instanceof Error
? err.message
: t(
"credentials.errors.flowFailed",
"Failed to check authentication flow",
),
: t("credentials.errors.flowFailed"),
)
}
}
@ -196,12 +191,7 @@ export function useCredentialsPage() {
return
}
setActiveAction("")
setError(
t(
"credentials.errors.popupBlocked",
"Unable to open a new tab. Please allow popups and try again.",
),
)
setError(t("credentials.errors.popupBlocked"))
return
}
@ -212,12 +202,7 @@ export function useCredentialsPage() {
return
}
if (!resp.auth_url || !resp.flow_id) {
throw new Error(
t(
"credentials.errors.invalidBrowserResponse",
"Invalid browser login response",
),
)
throw new Error(t("credentials.errors.invalidBrowserResponse"))
}
authTab.location.href = resp.auth_url
@ -242,7 +227,7 @@ export function useCredentialsPage() {
setError(
err instanceof Error
? err.message
: t("credentials.errors.loginFailed", "Login failed"),
: t("credentials.errors.loginFailed"),
)
}
},
@ -263,12 +248,7 @@ export function useCredentialsPage() {
return
}
if (!resp.flow_id || !resp.user_code || !resp.verify_url) {
throw new Error(
t(
"credentials.errors.invalidDeviceResponse",
"Invalid device code response",
),
)
throw new Error(t("credentials.errors.invalidDeviceResponse"))
}
const flow: OAuthFlowState = {
@ -296,7 +276,7 @@ export function useCredentialsPage() {
setError(
err instanceof Error
? err.message
: t("credentials.errors.loginFailed", "Login failed"),
: t("credentials.errors.loginFailed"),
)
}
}, [bumpActionToken, isActionTokenCurrent, t])
@ -320,7 +300,7 @@ export function useCredentialsPage() {
setError(
err instanceof Error
? err.message
: t("credentials.errors.loginFailed", "Login failed"),
: t("credentials.errors.loginFailed"),
)
} finally {
setActiveAction("")
@ -342,7 +322,7 @@ export function useCredentialsPage() {
setError(
err instanceof Error
? err.message
: t("credentials.errors.logoutFailed", "Logout failed"),
: t("credentials.errors.logoutFailed"),
)
} finally {
setActiveAction("")
@ -415,17 +395,15 @@ export function useCredentialsPage() {
return ""
}
if (activeFlow.status === "pending") {
return t("credentials.flow.pending", "Waiting for authorization...")
return t("credentials.flow.pending")
}
if (activeFlow.status === "success") {
return t("credentials.flow.success", "Authentication successful")
return t("credentials.flow.success")
}
if (activeFlow.status === "expired") {
return t("credentials.flow.expired", "Authentication session expired")
return t("credentials.flow.expired")
}
return (
activeFlow.error || t("credentials.flow.error", "Authentication failed")
)
return activeFlow.error || t("credentials.flow.error")
}, [activeFlow, t])
return {

View file

@ -6,7 +6,6 @@
"credentials": "Credentials",
"services": "Services",
"channels_group": "Channels",
"channels": "Channels",
"show_more_channels": "More",
"show_less_channels": "Less",
"config": "Config",
@ -15,13 +14,8 @@
"chat": {
"welcome": "How can I help you today?",
"welcomeDesc": "Ask me about weather, settings, or any other tasks. I'm here to assist you.",
"model": "Model",
"user": "User",
"placeholder": "Start a new message...",
"attach": "Attach file",
"voice": "Voice input",
"newChat": "New Chat",
"connecting": "Connecting...",
"notConnected": "Gateway is not running. Start it to chat.",
"thinking": {
"step1": "Thinking...",
@ -29,12 +23,6 @@
"step3": "Preparing response...",
"step4": "Almost there..."
},
"time": {
"justNow": "just now",
"minsAgo": "{{count}}m ago",
"hoursAgo": "{{count}}h ago",
"daysAgo": "{{count}}d ago"
},
"history": "History",
"noHistory": "No chat history yet",
"loadingMore": "Loading more...",
@ -79,6 +67,9 @@
"reset": "Reset",
"confirm": "Confirm"
},
"labels": {
"loading": "Loading..."
},
"credentials": {
"description": "Manage OAuth and token-based credentials for supported providers.",
"loading": "Loading credentials...",
@ -102,6 +93,7 @@
"actions": {
"browser": "Browser OAuth",
"deviceCode": "Device Code",
"stopLoading": "Stop Loading",
"saveToken": "Save",
"logout": "Logout"
},
@ -146,9 +138,6 @@
"models": {
"description": "Configure API keys for AI providers. Only configured models are available for chat.",
"loadError": "Failed to load models",
"header": {
"configured": "configured"
},
"noDefaultHintPrefix": "No default model set yet. Click",
"noDefaultHintSuffix": "to set one.",
"status": {
@ -185,8 +174,7 @@
"delete": {
"title": "Delete Model?",
"description": "\"{{name}}\" will be permanently removed from your model list. This cannot be undone.",
"confirm": "Delete",
"errorDefault": "Cannot delete the default model. Please set another model as default first."
"confirm": "Delete"
},
"advanced": {
"toggle": "Advanced options"
@ -221,19 +209,10 @@
}
},
"channels": {
"description": "Configure messaging channels to connect your AI agent to chat platforms.",
"loadError": "Failed to load channels",
"search": "Search channels...",
"noResults": "No channels match your search.",
"header": {
"enabled": "enabled"
},
"edit": "Configure {{name}}",
"status": {
"configured": "Configured",
"unconfigured": "Not configured"
},
"action": {
"configure": "Configure"
"configured": "Configured"
},
"name": {
"telegram": "Telegram",
@ -265,7 +244,6 @@
"encryptKey": "Encrypt Key",
"baseUrl": "API Base URL",
"proxy": "HTTP Proxy",
"proxyHint": "Optional. e.g. http://127.0.0.1:7890",
"mentionOnly": "Mention Only",
"typingEnabled": "Typing Indicator",
"placeholderEnabled": "Placeholder Message",
@ -273,32 +251,17 @@
"groupTriggerMentionOnly": "Group Mention Only",
"groupTriggerPrefixes": "Group Trigger Prefixes",
"allowFrom": "Allow From",
"allowFromHint": "Comma-separated list of allowed user/group IDs. Leave empty to allow all.",
"allowFromPlaceholder": "e.g. 123456, 789012",
"allowOrigins": "Allow Origins",
"allowOriginsHint": "Comma-separated list of allowed origins. Leave empty to allow all.",
"allowOriginsPlaceholder": "e.g. https://example.com, http://localhost:5173",
"secretPlaceholder": "Enter secret",
"secretPlaceholderSet": "Leave blank to keep existing",
"secretHintSet": "A value is already set. Leave blank to keep it unchanged."
},
"edit": {
"title": "Configure {{name}}",
"description": "Set up credentials and options for this channel.",
"saveError": "Failed to save channel configuration",
"saving": "Saving..."
},
"page": {
"notFound": "Channel \"{{name}}\" is not supported.",
"saveSuccess": "Channel configuration saved.",
"saveError": "Failed to save channel configuration",
"flowTitle": "Setup Flow",
"flowDescription": "Complete the following steps to put this channel into service.",
"step1": "Set credentials",
"step2": "Enable channel",
"step3": "Save and restart gateway",
"enabled": "enabled",
"disabled": "disabled",
"docLink": "Documentation",
"enableLabel": "Enable channel"
},
@ -316,7 +279,6 @@
"mentionOnly": "Only respond when the bot is explicitly mentioned in group chats.",
"typingEnabled": "Display typing status while the assistant is generating a response.",
"placeholderEnabled": "Enable temporary placeholder messages before the final reply is sent.",
"placeholderText": "Placeholder text shown while waiting for the final response.",
"groupTriggerMentionOnly": "In group chats, respond only when the bot is mentioned.",
"groupTriggerPrefixes": "Custom group-chat trigger prefixes, separated by commas.",
"allowFrom": "Allowed user or group IDs, separated by commas.",
@ -362,23 +324,8 @@
}
},
"pages": {
"providers": {
"description": "Manage AI model providers and configurations."
},
"models": {
"description": "Manage AI models here."
},
"credentials": {
"description": "Securely manage your API keys and credentials."
},
"config": {
"description": "System configuration and preferences.",
"visual_title": "Core Configuration",
"visual_desc": "Edit key runtime options here. Use Raw Config for full JSON editing.",
"load_error": "Failed to load configuration. Please refresh and try again.",
"section_agents": "Agent Defaults",
"section_runtime": "Runtime",
"section_devices": "Devices",
"workspace": "Workspace Directory",
"workspace_hint": "Base directory for agent file operations.",
"restrict_workspace": "Restrict to Workspace",
@ -422,10 +369,6 @@
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"launcher_load_error": "Failed to load service parameters.",
"launcher_restart_hint": "Service parameter changes apply after restarting PicoClaw Web.",
"autostart_enable_success": "Launch at login has been enabled.",
"autostart_disable_success": "Launch at login has been disabled.",
"autostart_update_error": "Failed to update launch-at-login setting.",
"advanced_title": "Need full configuration access?",
"advanced_desc": "Open the raw JSON page to edit every field directly.",
"open_raw": "Raw Config",
"back_to_visual": "Visual Config",
@ -441,7 +384,6 @@
"format_success": "JSON formatted successfully.",
"format_error": "Invalid JSON format.",
"format": "Format",
"lose_unsaved_changes": "You have unsaved changes. Are you sure you want to reset and lose these changes?",
"unsaved_changes": "You have unsaved changes."
},
"logs": {

View file

@ -6,7 +6,6 @@
"credentials": "凭据",
"services": "服务",
"channels_group": "频道",
"channels": "频道",
"show_more_channels": "更多",
"show_less_channels": "收起",
"config": "配置",
@ -15,13 +14,8 @@
"chat": {
"welcome": "今天我能为您做些什么?",
"welcomeDesc": "您可以询问我天气、设置或其他任何任务,我随时为您效劳。",
"model": "模型",
"user": "用户",
"placeholder": "输入新消息...",
"attach": "附加文件",
"voice": "语音输入",
"newChat": "新建对话",
"connecting": "连接中...",
"notConnected": "服务未运行,请先启动以进行对话。",
"thinking": {
"step1": "思考中...",
@ -29,12 +23,6 @@
"step3": "准备回复...",
"step4": "马上就好..."
},
"time": {
"justNow": "刚刚",
"minsAgo": "{{count}}分钟前",
"hoursAgo": "{{count}}小时前",
"daysAgo": "{{count}}天前"
},
"history": "历史记录",
"noHistory": "暂无对话历史",
"loadingMore": "加载更多...",
@ -79,6 +67,9 @@
"reset": "重置",
"confirm": "确认"
},
"labels": {
"loading": "加载中..."
},
"credentials": {
"description": "管理已支持服务商的 OAuth 与 Token 凭据。",
"loading": "正在加载凭据...",
@ -102,6 +93,7 @@
"actions": {
"browser": "浏览器 OAuth",
"deviceCode": "设备码",
"stopLoading": "停止加载",
"saveToken": "保存",
"logout": "退出登录"
},
@ -146,9 +138,6 @@
"models": {
"description": "为 AI 服务商配置 API Key。只有已配置的模型可用于对话。",
"loadError": "加载模型列表失败",
"header": {
"configured": "已配置"
},
"noDefaultHintPrefix": "尚未设置默认模型,点击",
"noDefaultHintSuffix": "设为默认。",
"status": {
@ -185,8 +174,7 @@
"delete": {
"title": "确认删除模型?",
"description": "「{{name}}」将从模型列表中永久移除,此操作不可撤销。",
"confirm": "删除",
"errorDefault": "无法删除默认模型。请先将其他模型设为默认。"
"confirm": "删除"
},
"advanced": {
"toggle": "高级选项"
@ -221,19 +209,10 @@
}
},
"channels": {
"description": "配置消息频道,将 AI 助手连接到各聊天平台。",
"loadError": "加载频道列表失败",
"search": "搜索频道...",
"noResults": "没有匹配的频道。",
"header": {
"enabled": "已启用"
},
"edit": "配置 {{name}}",
"status": {
"configured": "已配置",
"unconfigured": "未配置"
},
"action": {
"configure": "配置"
"configured": "已配置"
},
"name": {
"telegram": "Telegram",
@ -265,7 +244,6 @@
"encryptKey": "Encrypt Key",
"baseUrl": "API Base URL",
"proxy": "HTTP 代理",
"proxyHint": "可选。例如 http://127.0.0.1:7890",
"mentionOnly": "仅提及时响应",
"typingEnabled": "输入中提示",
"placeholderEnabled": "占位消息",
@ -273,32 +251,17 @@
"groupTriggerMentionOnly": "群聊仅提及时响应",
"groupTriggerPrefixes": "群聊触发前缀",
"allowFrom": "允许来源",
"allowFromHint": "用逗号分隔的用户/群组 ID 列表,留空表示允许所有。",
"allowFromPlaceholder": "例如 123456, 789012",
"allowOrigins": "允许来源域名",
"allowOriginsHint": "用逗号分隔允许的 Origin留空表示允许所有。",
"allowOriginsPlaceholder": "例如 https://example.com, http://localhost:5173",
"secretPlaceholder": "输入密钥",
"secretPlaceholderSet": "留空保持原有值不变",
"secretHintSet": "已设置密钥,留空表示不修改。"
},
"edit": {
"title": "配置 {{name}}",
"description": "设置此频道的凭据和选项。",
"saveError": "保存频道配置失败",
"saving": "保存中..."
},
"page": {
"notFound": "不支持频道“{{name}}”。",
"saveSuccess": "频道配置已保存。",
"saveError": "保存频道配置失败",
"flowTitle": "配置流程",
"flowDescription": "按以下步骤完成频道接入并投入使用。",
"step1": "填写凭据",
"step2": "启用频道",
"step3": "保存并重启网关",
"enabled": "已启用",
"disabled": "未启用",
"docLink": "配置文档",
"enableLabel": "启用频道"
},
@ -316,7 +279,6 @@
"mentionOnly": "在群聊中仅当明确提及时才响应。",
"typingEnabled": "在生成回复时显示“正在输入”状态。",
"placeholderEnabled": "在最终回复发送前,先发送临时占位消息。",
"placeholderText": "等待最终回复期间显示的占位文案。",
"groupTriggerMentionOnly": "在群聊中仅当提及机器人时才响应。",
"groupTriggerPrefixes": "群聊触发前缀,多个值用逗号分隔。",
"allowFrom": "允许访问的用户或群组 ID多个值用逗号分隔。",
@ -362,23 +324,8 @@
}
},
"pages": {
"providers": {
"description": "管理各个 AI 模型服务商的接入配置。"
},
"models": {
"description": "在此管理您下载或借用的 AI 模型。"
},
"credentials": {
"description": "安全管理您的 API 密钥与访问凭据。"
},
"config": {
"description": "系统配置和偏好设置。",
"visual_title": "核心配置",
"visual_desc": "这里可编辑关键运行配置;若需完整字段请使用原始配置页。",
"load_error": "加载配置失败,请刷新后重试。",
"section_agents": "智能体默认设置",
"section_runtime": "运行时",
"section_devices": "设备功能",
"workspace": "工作目录",
"workspace_hint": "智能体执行文件读写操作时使用的基础目录。",
"restrict_workspace": "限制工作目录访问",
@ -422,10 +369,6 @@
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"launcher_load_error": "加载服务参数失败。",
"launcher_restart_hint": "服务参数变更需重启 PicoClaw Web 后生效。",
"autostart_enable_success": "已开启开机自启。",
"autostart_disable_success": "已关闭开机自启。",
"autostart_update_error": "更新开机自启设置失败。",
"advanced_title": "需要完整配置能力?",
"advanced_desc": "可打开原始 JSON 页面直接编辑全部字段。",
"open_raw": "原始配置",
"back_to_visual": "可视化配置",
@ -441,7 +384,6 @@
"format_success": "JSON 格式化成功。",
"format_error": "JSON 格式无效。",
"format": "格式化",
"lose_unsaved_changes": "您有未保存的更改。确定要重置并丢失这些更改吗?",
"unsaved_changes": "您有未保存的更改。"
},
"logs": {

View file

@ -9,7 +9,6 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as ProvidersRouteImport } from './routes/providers'
import { Route as ModelsRouteImport } from './routes/models'
import { Route as LogsRouteImport } from './routes/logs'
import { Route as CredentialsRouteImport } from './routes/credentials'
@ -19,11 +18,6 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as ConfigRawRouteImport } from './routes/config.raw'
import { Route as ChannelsNameRouteImport } from './routes/channels/$name'
const ProvidersRoute = ProvidersRouteImport.update({
id: '/providers',
path: '/providers',
getParentRoute: () => rootRouteImport,
} as any)
const ModelsRoute = ModelsRouteImport.update({
id: '/models',
path: '/models',
@ -72,7 +66,6 @@ export interface FileRoutesByFullPath {
'/credentials': typeof CredentialsRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/providers': typeof ProvidersRoute
'/channels/$name': typeof ChannelsNameRoute
'/config/raw': typeof ConfigRawRoute
}
@ -83,7 +76,6 @@ export interface FileRoutesByTo {
'/credentials': typeof CredentialsRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/providers': typeof ProvidersRoute
'/channels/$name': typeof ChannelsNameRoute
'/config/raw': typeof ConfigRawRoute
}
@ -95,7 +87,6 @@ export interface FileRoutesById {
'/credentials': typeof CredentialsRoute
'/logs': typeof LogsRoute
'/models': typeof ModelsRoute
'/providers': typeof ProvidersRoute
'/channels/$name': typeof ChannelsNameRoute
'/config/raw': typeof ConfigRawRoute
}
@ -108,7 +99,6 @@ export interface FileRouteTypes {
| '/credentials'
| '/logs'
| '/models'
| '/providers'
| '/channels/$name'
| '/config/raw'
fileRoutesByTo: FileRoutesByTo
@ -119,7 +109,6 @@ export interface FileRouteTypes {
| '/credentials'
| '/logs'
| '/models'
| '/providers'
| '/channels/$name'
| '/config/raw'
id:
@ -130,7 +119,6 @@ export interface FileRouteTypes {
| '/credentials'
| '/logs'
| '/models'
| '/providers'
| '/channels/$name'
| '/config/raw'
fileRoutesById: FileRoutesById
@ -142,18 +130,10 @@ export interface RootRouteChildren {
CredentialsRoute: typeof CredentialsRoute
LogsRoute: typeof LogsRoute
ModelsRoute: typeof ModelsRoute
ProvidersRoute: typeof ProvidersRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/providers': {
id: '/providers'
path: '/providers'
fullPath: '/providers'
preLoaderRoute: typeof ProvidersRouteImport
parentRoute: typeof rootRouteImport
}
'/models': {
id: '/models'
path: '/models'
@ -243,7 +223,6 @@ const rootRouteChildren: RootRouteChildren = {
CredentialsRoute: CredentialsRoute,
LogsRoute: LogsRoute,
ModelsRoute: ModelsRoute,
ProvidersRoute: ProvidersRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View file

@ -15,13 +15,11 @@ function RawConfigPage() {
return (
<div className="flex h-full flex-col">
<PageHeader
title={t("pages.config.raw_json_title", "Raw JSON Configuration")}
>
<PageHeader title={t("pages.config.raw_json_title")}>
<Button variant="outline" asChild>
<Link to="/config">
<IconAdjustments className="size-4" />
{t("pages.config.back_to_visual", "Visual Config")}
{t("pages.config.back_to_visual")}
</Link>
</Button>
</PageHeader>

View file

@ -87,15 +87,15 @@ function LogsPage() {
return (
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.logs", "Logs")} />
<PageHeader title={t("navigation.logs")} />
<div className="flex flex-1 flex-col overflow-hidden p-4 sm:p-8">
<div className="mb-4">
<h1 className="text-2xl font-semibold tracking-tight">
{t("navigation.logs", "Logs")}
{t("navigation.logs")}
</h1>
<p className="text-muted-foreground mt-2 text-sm">
{t("pages.logs.description", "System logs and monitoring.")}
{t("pages.logs.description")}
</p>
</div>

View file

@ -1,30 +0,0 @@
import { createFileRoute } from "@tanstack/react-router"
import { useTranslation } from "react-i18next"
import { PageHeader } from "@/components/page-header"
export const Route = createFileRoute("/providers")({
component: ProvidersPage,
})
function ProvidersPage() {
const { t } = useTranslation()
return (
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.providers", "Providers")} />
<div className="flex flex-1 items-center justify-center p-8">
<div className="text-center">
<h1 className="text-2xl font-semibold tracking-tight">
{t("navigation.providers", "Providers")}
</h1>
<p className="text-muted-foreground mt-2 text-sm">
{t(
"pages.providers.description",
"Manage AI model providers and configurations.",
)}
</p>
</div>
</div>
</div>
)
}