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:
parent
b06e9e3707
commit
a418554441
23 changed files with 149 additions and 495 deletions
|
|
@ -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/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 className="bg-destructive absolute inline-flex size-full animate-ping rounded-full opacity-75"></span>
|
||||||
</span>
|
</span>
|
||||||
{t(
|
{t("chat.notConnected")}
|
||||||
"chat.notConnected",
|
|
||||||
"Gateway is not running. Start it to chat.",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,24 +96,19 @@ export function AppHeader() {
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{t("header.gateway.stopDialog.title", "Stop Gateway Service?")}
|
{t("header.gateway.stopDialog.title")}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t(
|
{t("header.gateway.stopDialog.description")}
|
||||||
"header.gateway.stopDialog.description",
|
|
||||||
"Are you sure you want to stop the gateway? This will disconnect your active chat sessions and halt inference.",
|
|
||||||
)}
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||||
{t("common.cancel", "Cancel")}
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={confirmStop}
|
onClick={confirmStop}
|
||||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
>
|
>
|
||||||
{t("header.gateway.stopDialog.confirm", "Stop Gateway")}
|
{t("header.gateway.stopDialog.confirm")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|
@ -146,10 +138,10 @@ export function AppHeader() {
|
||||||
)}
|
)}
|
||||||
<span className="text-xs font-semibold">
|
<span className="text-xs font-semibold">
|
||||||
{isRunning
|
{isRunning
|
||||||
? t("header.gateway.action.stop", "Stop Gateway")
|
? t("header.gateway.action.stop")
|
||||||
: isStarting
|
: isStarting
|
||||||
? t("header.gateway.status.starting", "Starting Gateway...")
|
? t("header.gateway.status.starting")
|
||||||
: t("header.gateway.action.start", "Start Gateway")}
|
: t("header.gateway.action.start")}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -494,7 +494,7 @@ export function ChannelConfigPage({ channelName }: ChannelConfigPageProps) {
|
||||||
<div className="w-full max-w-250 space-y-5 pt-2">
|
<div className="w-full max-w-250 space-y-5 pt-2">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{t("channels.edit.title", {
|
{t("channels.edit", {
|
||||||
name: channelDisplayName,
|
name: channelDisplayName,
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export function ChatPage() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-background/95 flex h-full flex-col">
|
<div className="bg-background/95 flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t("navigation.chat", "Chat")}
|
title={t("navigation.chat")}
|
||||||
titleExtra={
|
titleExtra={
|
||||||
hasConfiguredModels && (
|
hasConfiguredModels && (
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export function ModelSelector({
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{apiKeyModels.length > 0 && (
|
{apiKeyModels.length > 0 && (
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>{t("chat.modelGroup.apikey", "API Key")}</SelectLabel>
|
<SelectLabel>{t("chat.modelGroup.apikey")}</SelectLabel>
|
||||||
{apiKeyModels.map((model) => (
|
{apiKeyModels.map((model) => (
|
||||||
<SelectItem key={model.index} value={model.model_name}>
|
<SelectItem key={model.index} value={model.model_name}>
|
||||||
{model.model_name}
|
{model.model_name}
|
||||||
|
|
@ -55,7 +55,7 @@ export function ModelSelector({
|
||||||
|
|
||||||
{oauthModels.length > 0 && (
|
{oauthModels.length > 0 && (
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>{t("chat.modelGroup.oauth", "OAuth")}</SelectLabel>
|
<SelectLabel>{t("chat.modelGroup.oauth")}</SelectLabel>
|
||||||
{oauthModels.map((model) => (
|
{oauthModels.map((model) => (
|
||||||
<SelectItem key={model.index} value={model.model_name}>
|
<SelectItem key={model.index} value={model.model_name}>
|
||||||
{model.model_name}
|
{model.model_name}
|
||||||
|
|
@ -70,7 +70,7 @@ export function ModelSelector({
|
||||||
|
|
||||||
{localModels.length > 0 && (
|
{localModels.length > 0 && (
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>{t("chat.modelGroup.local", "Local")}</SelectLabel>
|
<SelectLabel>{t("chat.modelGroup.local")}</SelectLabel>
|
||||||
{localModels.map((model) => (
|
{localModels.map((model) => (
|
||||||
<SelectItem key={model.index} value={model.model_name}>
|
<SelectItem key={model.index} value={model.model_name}>
|
||||||
{model.model_name}
|
{model.model_name}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export function SessionHistoryMenu({
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
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"
|
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) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
@ -87,7 +87,7 @@ export function SessionHistoryMenu({
|
||||||
{hasMore && sessions.length > 0 && (
|
{hasMore && sessions.length > 0 && (
|
||||||
<div ref={observerRef} className="py-2 text-center">
|
<div ref={observerRef} className="py-2 text-center">
|
||||||
<span className="text-muted-foreground animate-pulse text-xs">
|
<span className="text-muted-foreground animate-pulse text-xs">
|
||||||
{t("chat.loadingMore", "Loading more...")}
|
{t("chat.loadingMore")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -106,29 +106,14 @@ export function ConfigPage() {
|
||||||
|
|
||||||
const autoStartSupported = autoStartStatus?.supported !== false
|
const autoStartSupported = autoStartStatus?.supported !== false
|
||||||
const autoStartHint = autoStartError
|
const autoStartHint = autoStartError
|
||||||
? t(
|
? t("pages.config.autostart_load_error")
|
||||||
"pages.config.autostart_load_error",
|
|
||||||
"Failed to load launch-at-login status.",
|
|
||||||
)
|
|
||||||
: !autoStartSupported
|
: !autoStartSupported
|
||||||
? t(
|
? t("pages.config.autostart_unsupported")
|
||||||
"pages.config.autostart_unsupported",
|
: t("pages.config.autostart_hint")
|
||||||
"Launch at login is not supported on this platform.",
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"pages.config.autostart_hint",
|
|
||||||
"Start PicoClaw Web automatically when you log in.",
|
|
||||||
)
|
|
||||||
|
|
||||||
const launcherHint = launcherError
|
const launcherHint = launcherError
|
||||||
? t(
|
? t("pages.config.launcher_load_error")
|
||||||
"pages.config.launcher_load_error",
|
: t("pages.config.launcher_restart_hint")
|
||||||
"Failed to load service parameters.",
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"pages.config.launcher_restart_hint",
|
|
||||||
"Service parameter changes apply after restarting PicoClaw Web.",
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateField = <K extends keyof CoreConfigForm>(
|
const updateField = <K extends keyof CoreConfigForm>(
|
||||||
key: K,
|
key: K,
|
||||||
|
|
@ -148,12 +133,7 @@ export function ConfigPage() {
|
||||||
setForm(baseline)
|
setForm(baseline)
|
||||||
setLauncherForm(launcherBaseline)
|
setLauncherForm(launcherBaseline)
|
||||||
setAutoStartEnabled(autoStartBaseline)
|
setAutoStartEnabled(autoStartBaseline)
|
||||||
toast.info(
|
toast.info(t("pages.config.reset_success"))
|
||||||
t(
|
|
||||||
"pages.config.reset_success",
|
|
||||||
"Changes have been reset to the last saved state.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
|
@ -251,12 +231,7 @@ export function ConfigPage() {
|
||||||
|
|
||||||
if (autoStartDirty) {
|
if (autoStartDirty) {
|
||||||
if (!autoStartSupported) {
|
if (!autoStartSupported) {
|
||||||
throw new Error(
|
throw new Error(t("pages.config.autostart_unsupported"))
|
||||||
t(
|
|
||||||
"pages.config.autostart_unsupported",
|
|
||||||
"Launch at login is not supported on this platform.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const status = await updateAutoStartEnabled(autoStartEnabled)
|
const status = await updateAutoStartEnabled(autoStartEnabled)
|
||||||
setAutoStartEnabled(status.enabled)
|
setAutoStartEnabled(status.enabled)
|
||||||
|
|
@ -264,14 +239,10 @@ export function ConfigPage() {
|
||||||
queryClient.setQueryData(["system", "autostart"], status)
|
queryClient.setQueryData(["system", "autostart"], status)
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(
|
toast.success(t("pages.config.save_success"))
|
||||||
t("pages.config.save_success", "Configuration saved successfully."),
|
|
||||||
)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(
|
toast.error(
|
||||||
err instanceof Error
|
err instanceof Error ? err.message : t("pages.config.save_error"),
|
||||||
? err.message
|
|
||||||
: t("pages.config.save_error", "Failed to save configuration."),
|
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
|
|
@ -281,12 +252,12 @@ export function ConfigPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t("navigation.config", "Config")}
|
title={t("navigation.config")}
|
||||||
children={
|
children={
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link to="/config/raw">
|
<Link to="/config/raw">
|
||||||
<IconCode className="size-4" />
|
<IconCode className="size-4" />
|
||||||
{t("pages.config.open_raw", "Raw Config")}
|
{t("pages.config.open_raw")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
@ -295,23 +266,17 @@ export function ConfigPage() {
|
||||||
<div className="mx-auto w-full max-w-[1000px] space-y-6">
|
<div className="mx-auto w-full max-w-[1000px] space-y-6">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="text-muted-foreground py-6 text-sm">
|
<div className="text-muted-foreground py-6 text-sm">
|
||||||
{t("labels.loading", "Loading...")}
|
{t("labels.loading")}
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="text-destructive py-6 text-sm">
|
<div className="text-destructive py-6 text-sm">
|
||||||
{t(
|
{t("pages.config.load_error")}
|
||||||
"pages.config.load_error",
|
|
||||||
"Failed to load configuration. Please refresh and try again.",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{isDirty && (
|
{isDirty && (
|
||||||
<div className="bg-yellow-50 px-3 py-2 text-sm text-yellow-700">
|
<div className="bg-yellow-50 px-3 py-2 text-sm text-yellow-700">
|
||||||
{t(
|
{t("pages.config.unsaved_changes")}
|
||||||
"pages.config.unsaved_changes",
|
|
||||||
"You have unsaved changes.",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -356,13 +321,11 @@ export function ConfigPage() {
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
disabled={!isDirty || saving}
|
disabled={!isDirty || saving}
|
||||||
>
|
>
|
||||||
{t("common.reset", "Reset")}
|
{t("common.reset")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSave} disabled={!isDirty || saving}>
|
<Button onClick={handleSave} disabled={!isDirty || saving}>
|
||||||
<IconDeviceFloppy className="size-4" />
|
<IconDeviceFloppy className="size-4" />
|
||||||
{saving
|
{saving ? t("common.saving") : t("common.save")}
|
||||||
? t("common.saving", "Saving...")
|
|
||||||
: t("common.save", "Save")}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,8 @@ export function AgentDefaultsSection({
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.workspace", "Workspace Directory")}
|
label={t("pages.config.workspace")}
|
||||||
hint={t(
|
hint={t("pages.config.workspace_hint")}
|
||||||
"pages.config.workspace_hint",
|
|
||||||
"Base directory for agent file operations.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
value={form.workspace}
|
value={form.workspace}
|
||||||
|
|
@ -58,11 +55,8 @@ export function AgentDefaultsSection({
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.restrict_workspace", "Restrict to Workspace")}
|
label={t("pages.config.restrict_workspace")}
|
||||||
hint={t(
|
hint={t("pages.config.restrict_workspace_hint")}
|
||||||
"pages.config.restrict_workspace_hint",
|
|
||||||
"Only allow file operations inside workspace.",
|
|
||||||
)}
|
|
||||||
checked={form.restrictToWorkspace}
|
checked={form.restrictToWorkspace}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
onFieldChange("restrictToWorkspace", checked)
|
onFieldChange("restrictToWorkspace", checked)
|
||||||
|
|
@ -70,11 +64,8 @@ export function AgentDefaultsSection({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.max_tokens", "Max Tokens")}
|
label={t("pages.config.max_tokens")}
|
||||||
hint={t(
|
hint={t("pages.config.max_tokens_hint")}
|
||||||
"pages.config.max_tokens_hint",
|
|
||||||
"Upper token limit per model response.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -85,11 +76,8 @@ export function AgentDefaultsSection({
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.max_tool_iterations", "Max Tool Iterations")}
|
label={t("pages.config.max_tool_iterations")}
|
||||||
hint={t(
|
hint={t("pages.config.max_tool_iterations_hint")}
|
||||||
"pages.config.max_tool_iterations_hint",
|
|
||||||
"Maximum tool-call loops in a single task.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -100,14 +88,8 @@ export function AgentDefaultsSection({
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label={t(
|
label={t("pages.config.summarize_threshold")}
|
||||||
"pages.config.summarize_threshold",
|
hint={t("pages.config.summarize_threshold_hint")}
|
||||||
"Summarize Message Threshold",
|
|
||||||
)}
|
|
||||||
hint={t(
|
|
||||||
"pages.config.summarize_threshold_hint",
|
|
||||||
"Start summarization after this many messages.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -120,14 +102,8 @@ export function AgentDefaultsSection({
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label={t(
|
label={t("pages.config.summarize_token_percent")}
|
||||||
"pages.config.summarize_token_percent",
|
hint={t("pages.config.summarize_token_percent_hint")}
|
||||||
"Summarize Token Percent",
|
|
||||||
)}
|
|
||||||
hint={t(
|
|
||||||
"pages.config.summarize_token_percent_hint",
|
|
||||||
"Used when conversation summary is triggered.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -159,11 +135,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.session_scope", "Session Scope")}
|
label={t("pages.config.session_scope")}
|
||||||
hint={t(
|
hint={t("pages.config.session_scope_hint")}
|
||||||
"pages.config.session_scope_hint",
|
|
||||||
"How chat context is isolated across peers/channels.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={form.dmScope}
|
value={form.dmScope}
|
||||||
|
|
@ -183,11 +156,9 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
{DM_SCOPE_OPTIONS.map((scope) => (
|
{DM_SCOPE_OPTIONS.map((scope) => (
|
||||||
<SelectItem key={scope.value} value={scope.value}>
|
<SelectItem key={scope.value} value={scope.value}>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span className="font-medium">
|
<span className="font-medium">{t(scope.labelKey)}</span>
|
||||||
{t(scope.labelKey, scope.labelDefault)}
|
|
||||||
</span>
|
|
||||||
<span className="text-muted-foreground text-xs">
|
<span className="text-muted-foreground text-xs">
|
||||||
{t(scope.descKey, scope.descDefault)}
|
{t(scope.descKey)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
@ -197,11 +168,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.heartbeat_enabled", "Heartbeat")}
|
label={t("pages.config.heartbeat_enabled")}
|
||||||
hint={t(
|
hint={t("pages.config.heartbeat_enabled_hint")}
|
||||||
"pages.config.heartbeat_enabled_hint",
|
|
||||||
"Send periodic heartbeat messages.",
|
|
||||||
)}
|
|
||||||
checked={form.heartbeatEnabled}
|
checked={form.heartbeatEnabled}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
onFieldChange("heartbeatEnabled", checked)
|
onFieldChange("heartbeatEnabled", checked)
|
||||||
|
|
@ -210,14 +178,8 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
|
|
||||||
{form.heartbeatEnabled && (
|
{form.heartbeatEnabled && (
|
||||||
<Field
|
<Field
|
||||||
label={t(
|
label={t("pages.config.heartbeat_interval")}
|
||||||
"pages.config.heartbeat_interval",
|
hint={t("pages.config.heartbeat_interval_hint")}
|
||||||
"Heartbeat Interval (minutes)",
|
|
||||||
)}
|
|
||||||
hint={t(
|
|
||||||
"pages.config.heartbeat_interval_hint",
|
|
||||||
"Interval in minutes between heartbeat signals.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -253,11 +215,8 @@ export function LauncherSection({
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.server_port", "Service Port")}
|
label={t("pages.config.server_port")}
|
||||||
hint={t(
|
hint={t("pages.config.server_port_hint")}
|
||||||
"pages.config.server_port_hint",
|
|
||||||
"HTTP port used by PicoClaw Web.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -270,30 +229,21 @@ export function LauncherSection({
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.lan_access", "Enable LAN Access")}
|
label={t("pages.config.lan_access")}
|
||||||
hint={t(
|
hint={t("pages.config.lan_access_hint")}
|
||||||
"pages.config.lan_access_hint",
|
|
||||||
"Allow access from other devices on your local network.",
|
|
||||||
)}
|
|
||||||
checked={launcherForm.publicAccess}
|
checked={launcherForm.publicAccess}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onCheckedChange={(checked) => onFieldChange("publicAccess", checked)}
|
onCheckedChange={(checked) => onFieldChange("publicAccess", checked)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label={t("pages.config.allowed_cidrs", "Allowed Network CIDRs")}
|
label={t("pages.config.allowed_cidrs")}
|
||||||
hint={t(
|
hint={t("pages.config.allowed_cidrs_hint")}
|
||||||
"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.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={launcherForm.allowedCIDRsText}
|
value={launcherForm.allowedCIDRsText}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={t(
|
placeholder={t("pages.config.allowed_cidrs_placeholder")}
|
||||||
"pages.config.allowed_cidrs_placeholder",
|
|
||||||
"192.168.1.0/24\n10.0.0.0/8",
|
|
||||||
)}
|
|
||||||
className="min-h-[88px]"
|
className="min-h-[88px]"
|
||||||
onChange={(e) => onFieldChange("allowedCIDRsText", e.target.value)}
|
onChange={(e) => onFieldChange("allowedCIDRsText", e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -328,11 +278,8 @@ export function DevicesSection({
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.devices_enabled", "Enable Devices")}
|
label={t("pages.config.devices_enabled")}
|
||||||
hint={t(
|
hint={t("pages.config.devices_enabled_hint")}
|
||||||
"pages.config.devices_enabled_hint",
|
|
||||||
"Enable hardware-device integrations.",
|
|
||||||
)}
|
|
||||||
checked={form.devicesEnabled}
|
checked={form.devicesEnabled}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
onFieldChange("devicesEnabled", checked)
|
onFieldChange("devicesEnabled", checked)
|
||||||
|
|
@ -340,17 +287,14 @@ export function DevicesSection({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.monitor_usb", "Monitor USB")}
|
label={t("pages.config.monitor_usb")}
|
||||||
hint={t(
|
hint={t("pages.config.monitor_usb_hint")}
|
||||||
"pages.config.monitor_usb_hint",
|
|
||||||
"Watch USB plug/unplug events when devices are enabled.",
|
|
||||||
)}
|
|
||||||
checked={form.monitorUSB}
|
checked={form.monitorUSB}
|
||||||
onCheckedChange={(checked) => onFieldChange("monitorUSB", checked)}
|
onCheckedChange={(checked) => onFieldChange("monitorUSB", checked)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SwitchCardField
|
<SwitchCardField
|
||||||
label={t("pages.config.autostart_label", "Launch at Login")}
|
label={t("pages.config.autostart_label")}
|
||||||
hint={autoStartHint}
|
hint={autoStartHint}
|
||||||
checked={autoStartEnabled}
|
checked={autoStartEnabled}
|
||||||
disabled={autoStartDisabled}
|
disabled={autoStartDisabled}
|
||||||
|
|
@ -367,16 +311,13 @@ export function AdvancedSection() {
|
||||||
return (
|
return (
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{t(
|
{t("pages.config.advanced_desc")}
|
||||||
"pages.config.advanced_desc",
|
|
||||||
"Open the raw JSON page to edit every field directly.",
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link to="/config/raw">
|
<Link to="/config/raw">
|
||||||
<IconCode className="size-4" />
|
<IconCode className="size-4" />
|
||||||
{t("pages.config.open_raw", "Raw Config")}
|
{t("pages.config.open_raw")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ export function RawJsonPanel() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: (_, submittedConfig) => {
|
onSuccess: (_, submittedConfig) => {
|
||||||
toast.success(
|
toast.success(t("pages.config.save_success"))
|
||||||
t("pages.config.save_success", "Configuration saved successfully."),
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
const savedConfig = JSON.parse(submittedConfig)
|
const savedConfig = JSON.parse(submittedConfig)
|
||||||
setLastSavedConfig(savedConfig)
|
setLastSavedConfig(savedConfig)
|
||||||
|
|
@ -65,7 +63,7 @@ export function RawJsonPanel() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
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,
|
2,
|
||||||
)
|
)
|
||||||
setEditorValue(formatted)
|
setEditorValue(formatted)
|
||||||
toast.success(
|
toast.success(t("pages.config.format_success"))
|
||||||
t("pages.config.format_success", "JSON formatted successfully."),
|
|
||||||
)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
t(
|
t(
|
||||||
|
|
@ -123,38 +119,26 @@ export function RawJsonPanel() {
|
||||||
setEditorValue(JSON.stringify(config, null, 2))
|
setEditorValue(JSON.stringify(config, null, 2))
|
||||||
}
|
}
|
||||||
setIsDirty(false)
|
setIsDirty(false)
|
||||||
toast.info(
|
toast.info(t("pages.config.reset_success"))
|
||||||
t(
|
|
||||||
"pages.config.reset_success",
|
|
||||||
"Changes have been reset to the last saved state.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setShowResetDialog(false)
|
setShowResetDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>{t("pages.config.raw_json_title")}</CardTitle>
|
||||||
{t("pages.config.raw_json_title", "Raw JSON Configuration")}
|
<CardDescription>{t("pages.config.raw_json_desc")}</CardDescription>
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{t(
|
|
||||||
"pages.config.raw_json_desc",
|
|
||||||
"Advanced users can directly edit the raw JSON configuration below.",
|
|
||||||
)}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex h-64 items-center justify-center">
|
<div className="flex h-64 items-center justify-center">
|
||||||
<p>{t("labels.loading", "Loading...")}</p>
|
<p>{t("labels.loading")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{isDirty && (
|
{isDirty && (
|
||||||
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-2 text-sm text-yellow-700">
|
<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>
|
||||||
)}
|
)}
|
||||||
<div className="bg-muted/30 relative rounded-lg border">
|
<div className="bg-muted/30 relative rounded-lg border">
|
||||||
|
|
@ -166,10 +150,7 @@ export function RawJsonPanel() {
|
||||||
setIsDirty(true)
|
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"
|
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(
|
placeholder={t("pages.config.json_placeholder")}
|
||||||
"pages.config.json_placeholder",
|
|
||||||
"Enter valid JSON configuration...",
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -179,7 +160,7 @@ export function RawJsonPanel() {
|
||||||
onClick={handleFormat}
|
onClick={handleFormat}
|
||||||
disabled={mutation.isPending}
|
disabled={mutation.isPending}
|
||||||
>
|
>
|
||||||
{t("pages.config.format", "Format")}
|
{t("pages.config.format")}
|
||||||
</Button>
|
</Button>
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
open={showResetDialog}
|
open={showResetDialog}
|
||||||
|
|
@ -191,35 +172,28 @@ export function RawJsonPanel() {
|
||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
onClick={() => setShowResetDialog(true)}
|
onClick={() => setShowResetDialog(true)}
|
||||||
>
|
>
|
||||||
{t("common.reset", "Reset")}
|
{t("common.reset")}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{t("pages.config.reset_confirm_title", "Reset Changes")}
|
{t("pages.config.reset_confirm_title")}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t(
|
{t("pages.config.reset_confirm_desc")}
|
||||||
"pages.config.reset_confirm_desc",
|
|
||||||
"Are you sure you want to reset your unsaved changes back to the last saved state?",
|
|
||||||
)}
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||||
{t("common.cancel", "Cancel")}
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={confirmReset}>
|
<AlertDialogAction onClick={confirmReset}>
|
||||||
{t("common.confirm", "Confirm")}
|
{t("common.confirm")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
<Button onClick={handleSave} disabled={mutation.isPending}>
|
<Button onClick={handleSave} disabled={mutation.isPending}>
|
||||||
{mutation.isPending
|
{mutation.isPending ? t("common.saving") : t("common.save")}
|
||||||
? t("common.saving", "Saving...")
|
|
||||||
: t("common.save", "Save")}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export function AnthropicCredentialCard({
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const actionBusy = activeAction !== ""
|
const actionBusy = activeAction !== ""
|
||||||
const tokenLoading = activeAction === "anthropic:token"
|
const tokenLoading = activeAction === "anthropic:token"
|
||||||
const stopLabel = t("credentials.actions.stopLoading", "Stop Loading")
|
const stopLabel = t("credentials.actions.stopLoading")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredentialCard
|
<CredentialCard
|
||||||
|
|
@ -46,10 +46,7 @@ export function AnthropicCredentialCard({
|
||||||
<span>Anthropic</span>
|
<span>Anthropic</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
description={t(
|
description={t("credentials.providers.anthropic.description")}
|
||||||
"credentials.providers.anthropic.description",
|
|
||||||
"Uses token login for Claude access.",
|
|
||||||
)}
|
|
||||||
status={status?.status ?? "not_logged_in"}
|
status={status?.status ?? "not_logged_in"}
|
||||||
authMethod={status?.auth_method}
|
authMethod={status?.auth_method}
|
||||||
actions={
|
actions={
|
||||||
|
|
@ -60,10 +57,7 @@ export function AnthropicCredentialCard({
|
||||||
value={token}
|
value={token}
|
||||||
onChange={(e) => onTokenChange(e.target.value)}
|
onChange={(e) => onTokenChange(e.target.value)}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={t(
|
placeholder={t("credentials.fields.anthropicToken")}
|
||||||
"credentials.fields.anthropicToken",
|
|
||||||
"Anthropic token",
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -75,7 +69,7 @@ export function AnthropicCredentialCard({
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<IconKey className="size-4" />
|
<IconKey className="size-4" />
|
||||||
{t("credentials.actions.saveToken", "Save")}
|
{t("credentials.actions.saveToken")}
|
||||||
</Button>
|
</Button>
|
||||||
{tokenLoading && (
|
{tokenLoading && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -105,7 +99,7 @@ export function AnthropicCredentialCard({
|
||||||
{activeAction === "anthropic:logout" && (
|
{activeAction === "anthropic:logout" && (
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
{t("credentials.actions.logout", "Logout")}
|
{t("credentials.actions.logout")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,22 +40,19 @@ export function AntigravityCredentialCard({
|
||||||
<span>Google Antigravity</span>
|
<span>Google Antigravity</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
description={t(
|
description={t("credentials.providers.antigravity.description")}
|
||||||
"credentials.providers.antigravity.description",
|
|
||||||
"Uses browser OAuth for Google Cloud Code Assist.",
|
|
||||||
)}
|
|
||||||
status={status?.status ?? "not_logged_in"}
|
status={status?.status ?? "not_logged_in"}
|
||||||
authMethod={status?.auth_method}
|
authMethod={status?.auth_method}
|
||||||
details={
|
details={
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{status?.email && (
|
{status?.email && (
|
||||||
<p>
|
<p>
|
||||||
{t("credentials.labels.email", "Email")}: {status.email}
|
{t("credentials.labels.email")}: {status.email}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{status?.project_id && (
|
{status?.project_id && (
|
||||||
<p>
|
<p>
|
||||||
{t("credentials.labels.project", "Project")}: {status.project_id}
|
{t("credentials.labels.project")}: {status.project_id}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,7 +70,7 @@ export function AntigravityCredentialCard({
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<IconLockOpen className="size-4" />
|
<IconLockOpen className="size-4" />
|
||||||
{t("credentials.actions.browser", "Browser OAuth")}
|
{t("credentials.actions.browser")}
|
||||||
</Button>
|
</Button>
|
||||||
{browserLoading && (
|
{browserLoading && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -100,7 +97,7 @@ export function AntigravityCredentialCard({
|
||||||
{activeAction === "google-antigravity:logout" && (
|
{activeAction === "google-antigravity:logout" && (
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
{t("credentials.actions.logout", "Logout")}
|
{t("credentials.actions.logout")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,15 +42,12 @@ export function CredentialsPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<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="min-h-0 flex-1 overflow-y-auto px-4 sm:px-6">
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{t(
|
{t("credentials.description")}
|
||||||
"credentials.description",
|
|
||||||
"Manage OAuth and token-based credentials for supported providers.",
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -62,9 +59,7 @@ export function CredentialsPage() {
|
||||||
|
|
||||||
{activeFlow && (
|
{activeFlow && (
|
||||||
<div className="bg-muted mt-4 rounded-lg border px-4 py-3 text-sm">
|
<div className="bg-muted mt-4 rounded-lg border px-4 py-3 text-sm">
|
||||||
<p className="font-medium">
|
<p className="font-medium">{t("credentials.flow.current")}</p>
|
||||||
{t("credentials.flow.current", "Current authentication status")}
|
|
||||||
</p>
|
|
||||||
<p className="text-muted-foreground mt-1">{flowHint}</p>
|
<p className="text-muted-foreground mt-1">{flowHint}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -72,7 +67,7 @@ export function CredentialsPage() {
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-muted-foreground flex items-center gap-2 py-10 text-sm">
|
<div className="text-muted-foreground flex items-center gap-2 py-10 text-sm">
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
{t("credentials.loading", "Loading credentials...")}
|
{t("credentials.loading")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-4 py-5 lg:auto-rows-fr lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-4 py-5 lg:auto-rows-fr lg:grid-cols-3">
|
||||||
|
|
|
||||||
|
|
@ -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]"
|
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">
|
<SheetHeader className="border-b-muted border-b px-6 py-5">
|
||||||
<SheetTitle>
|
<SheetTitle>{t("credentials.device.title")}</SheetTitle>
|
||||||
{t("credentials.device.title", "OpenAI Device Login")}
|
|
||||||
</SheetTitle>
|
|
||||||
<SheetDescription>
|
<SheetDescription>
|
||||||
{t(
|
{t("credentials.device.description")}
|
||||||
"credentials.device.description",
|
|
||||||
"Open the verification page and enter the code below. This page will refresh automatically.",
|
|
||||||
)}
|
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
|
|
||||||
<div className="space-y-4 px-6 py-5">
|
<div className="space-y-4 px-6 py-5">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-xs uppercase">
|
<p className="text-muted-foreground text-xs uppercase">
|
||||||
{t("credentials.device.code", "User Code")}
|
{t("credentials.device.code")}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 rounded-md border px-3 py-2 font-mono text-lg font-semibold tracking-wide">
|
<p className="mt-1 rounded-md border px-3 py-2 font-mono text-lg font-semibold tracking-wide">
|
||||||
{flow?.user_code || "-"}
|
{flow?.user_code || "-"}
|
||||||
|
|
@ -57,7 +52,7 @@ export function DeviceCodeSheet({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-xs uppercase">
|
<p className="text-muted-foreground text-xs uppercase">
|
||||||
{t("credentials.device.url", "Verification URL")}
|
{t("credentials.device.url")}
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href={flow?.verify_url || "#"}
|
href={flow?.verify_url || "#"}
|
||||||
|
|
@ -71,7 +66,7 @@ export function DeviceCodeSheet({
|
||||||
|
|
||||||
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
||||||
<IconRefresh className="size-4" />
|
<IconRefresh className="size-4" />
|
||||||
{t("credentials.device.polling", "Polling login status...")}
|
{t("credentials.device.polling")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{flow && (
|
{flow && (
|
||||||
|
|
@ -83,11 +78,11 @@ export function DeviceCodeSheet({
|
||||||
|
|
||||||
<SheetFooter className="border-t-muted border-t px-6 py-4">
|
<SheetFooter className="border-t-muted border-t px-6 py-4">
|
||||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||||
{t("common.cancel", "Close")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild disabled={!flow?.verify_url}>
|
<Button asChild disabled={!flow?.verify_url}>
|
||||||
<a href={flow?.verify_url || "#"} target="_blank" rel="noreferrer">
|
<a href={flow?.verify_url || "#"} target="_blank" rel="noreferrer">
|
||||||
{t("credentials.device.open", "Open Verification Page")}
|
{t("credentials.device.open")}
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetFooter>
|
</SheetFooter>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export function LogoutConfirmDialog({
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{t("credentials.logoutDialog.title", "Logout provider?")}
|
{t("credentials.logoutDialog.title")}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{t(
|
{t(
|
||||||
|
|
@ -45,10 +45,10 @@ export function LogoutConfirmDialog({
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>{t("common.cancel", "Cancel")}</AlertDialogCancel>
|
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={onConfirm} variant="destructive">
|
<AlertDialogAction onClick={onConfirm} variant="destructive">
|
||||||
{isSubmitting && <IconLoader2 className="size-4 animate-spin" />}
|
{isSubmitting && <IconLoader2 className="size-4 animate-spin" />}
|
||||||
{t("credentials.actions.logout", "Logout")}
|
{t("credentials.actions.logout")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,13 @@ export function OpenAICredentialCard({
|
||||||
<span>OpenAI</span>
|
<span>OpenAI</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
description={t(
|
description={t("credentials.providers.openai.description")}
|
||||||
"credentials.providers.openai.description",
|
|
||||||
"Supports browser OAuth, device code, and token login.",
|
|
||||||
)}
|
|
||||||
status={status?.status ?? "not_logged_in"}
|
status={status?.status ?? "not_logged_in"}
|
||||||
authMethod={status?.auth_method}
|
authMethod={status?.auth_method}
|
||||||
details={
|
details={
|
||||||
status?.account_id ? (
|
status?.account_id ? (
|
||||||
<p>
|
<p>
|
||||||
{t("credentials.labels.account", "Account")}: {status.account_id}
|
{t("credentials.labels.account")}: {status.account_id}
|
||||||
</p>
|
</p>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +78,7 @@ export function OpenAICredentialCard({
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<IconBrandOpenai className="size-4" />
|
<IconBrandOpenai className="size-4" />
|
||||||
{t("credentials.actions.browser", "Browser OAuth")}
|
{t("credentials.actions.browser")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{oauthLoading && !deviceLoading && (
|
{oauthLoading && !deviceLoading && (
|
||||||
|
|
@ -105,7 +102,7 @@ export function OpenAICredentialCard({
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<IconClockHour4 className="size-4" />
|
<IconClockHour4 className="size-4" />
|
||||||
{t("credentials.actions.deviceCode", "Device Code")}
|
{t("credentials.actions.deviceCode")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -116,10 +113,7 @@ export function OpenAICredentialCard({
|
||||||
value={token}
|
value={token}
|
||||||
onChange={(e) => onTokenChange(e.target.value)}
|
onChange={(e) => onTokenChange(e.target.value)}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={t(
|
placeholder={t("credentials.fields.openaiToken")}
|
||||||
"credentials.fields.openaiToken",
|
|
||||||
"OpenAI token",
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -130,7 +124,7 @@ export function OpenAICredentialCard({
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<IconKey className="size-4" />
|
<IconKey className="size-4" />
|
||||||
{t("credentials.actions.saveToken", "Save")}
|
{t("credentials.actions.saveToken")}
|
||||||
</Button>
|
</Button>
|
||||||
{tokenLoading && (
|
{tokenLoading && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -159,7 +153,7 @@ export function OpenAICredentialCard({
|
||||||
{activeAction === "openai:logout" && (
|
{activeAction === "openai:logout" && (
|
||||||
<IconLoader2 className="size-4 animate-spin" />
|
<IconLoader2 className="size-4 animate-spin" />
|
||||||
)}
|
)}
|
||||||
{t("credentials.actions.logout", "Logout")}
|
{t("credentials.actions.logout")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ export function ProviderStatusLine({
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className={`rounded px-2 py-1 text-xs font-medium ${style}`}>
|
<span className={`rounded px-2 py-1 text-xs font-medium ${style}`}>
|
||||||
{status === "connected"
|
{status === "connected"
|
||||||
? t("credentials.status.connected", "Connected")
|
? t("credentials.status.connected")
|
||||||
: status === "needs_refresh"
|
: status === "needs_refresh"
|
||||||
? t("credentials.status.needsRefresh", "Needs refresh")
|
? t("credentials.status.needsRefresh")
|
||||||
: status === "expired"
|
: status === "expired"
|
||||||
? t("credentials.status.expired", "Expired")
|
? t("credentials.status.expired")
|
||||||
: t("credentials.status.notLoggedIn", "Not logged in")}
|
: t("credentials.status.notLoggedIn")}
|
||||||
</span>
|
</span>
|
||||||
{authMethod && (
|
{authMethod && (
|
||||||
<span className="text-muted-foreground text-xs uppercase">
|
<span className="text-muted-foreground text-xs uppercase">
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ export function ModelsPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<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">
|
<div className="flex items-center gap-3">
|
||||||
<Button size="sm" variant="outline" onClick={() => setAddOpen(true)}>
|
<Button size="sm" variant="outline" onClick={() => setAddOpen(true)}>
|
||||||
<IconPlus className="size-4" />
|
<IconPlus className="size-4" />
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,7 @@ export function useCredentialsPage() {
|
||||||
setError("")
|
setError("")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error ? err.message : t("credentials.errors.loadFailed"),
|
||||||
? err.message
|
|
||||||
: t("credentials.errors.loadFailed", "Failed to load credentials"),
|
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
@ -113,10 +111,7 @@ export function useCredentialsPage() {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: t(
|
: t("credentials.errors.flowFailed"),
|
||||||
"credentials.errors.flowFailed",
|
|
||||||
"Failed to check authentication flow",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,12 +191,7 @@ export function useCredentialsPage() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setActiveAction("")
|
setActiveAction("")
|
||||||
setError(
|
setError(t("credentials.errors.popupBlocked"))
|
||||||
t(
|
|
||||||
"credentials.errors.popupBlocked",
|
|
||||||
"Unable to open a new tab. Please allow popups and try again.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,12 +202,7 @@ export function useCredentialsPage() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!resp.auth_url || !resp.flow_id) {
|
if (!resp.auth_url || !resp.flow_id) {
|
||||||
throw new Error(
|
throw new Error(t("credentials.errors.invalidBrowserResponse"))
|
||||||
t(
|
|
||||||
"credentials.errors.invalidBrowserResponse",
|
|
||||||
"Invalid browser login response",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authTab.location.href = resp.auth_url
|
authTab.location.href = resp.auth_url
|
||||||
|
|
@ -242,7 +227,7 @@ export function useCredentialsPage() {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: t("credentials.errors.loginFailed", "Login failed"),
|
: t("credentials.errors.loginFailed"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -263,12 +248,7 @@ export function useCredentialsPage() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!resp.flow_id || !resp.user_code || !resp.verify_url) {
|
if (!resp.flow_id || !resp.user_code || !resp.verify_url) {
|
||||||
throw new Error(
|
throw new Error(t("credentials.errors.invalidDeviceResponse"))
|
||||||
t(
|
|
||||||
"credentials.errors.invalidDeviceResponse",
|
|
||||||
"Invalid device code response",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const flow: OAuthFlowState = {
|
const flow: OAuthFlowState = {
|
||||||
|
|
@ -296,7 +276,7 @@ export function useCredentialsPage() {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: t("credentials.errors.loginFailed", "Login failed"),
|
: t("credentials.errors.loginFailed"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [bumpActionToken, isActionTokenCurrent, t])
|
}, [bumpActionToken, isActionTokenCurrent, t])
|
||||||
|
|
@ -320,7 +300,7 @@ export function useCredentialsPage() {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: t("credentials.errors.loginFailed", "Login failed"),
|
: t("credentials.errors.loginFailed"),
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setActiveAction("")
|
setActiveAction("")
|
||||||
|
|
@ -342,7 +322,7 @@ export function useCredentialsPage() {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: t("credentials.errors.logoutFailed", "Logout failed"),
|
: t("credentials.errors.logoutFailed"),
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setActiveAction("")
|
setActiveAction("")
|
||||||
|
|
@ -415,17 +395,15 @@ export function useCredentialsPage() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if (activeFlow.status === "pending") {
|
if (activeFlow.status === "pending") {
|
||||||
return t("credentials.flow.pending", "Waiting for authorization...")
|
return t("credentials.flow.pending")
|
||||||
}
|
}
|
||||||
if (activeFlow.status === "success") {
|
if (activeFlow.status === "success") {
|
||||||
return t("credentials.flow.success", "Authentication successful")
|
return t("credentials.flow.success")
|
||||||
}
|
}
|
||||||
if (activeFlow.status === "expired") {
|
if (activeFlow.status === "expired") {
|
||||||
return t("credentials.flow.expired", "Authentication session expired")
|
return t("credentials.flow.expired")
|
||||||
}
|
}
|
||||||
return (
|
return activeFlow.error || t("credentials.flow.error")
|
||||||
activeFlow.error || t("credentials.flow.error", "Authentication failed")
|
|
||||||
)
|
|
||||||
}, [activeFlow, t])
|
}, [activeFlow, t])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
"credentials": "Credentials",
|
"credentials": "Credentials",
|
||||||
"services": "Services",
|
"services": "Services",
|
||||||
"channels_group": "Channels",
|
"channels_group": "Channels",
|
||||||
"channels": "Channels",
|
|
||||||
"show_more_channels": "More",
|
"show_more_channels": "More",
|
||||||
"show_less_channels": "Less",
|
"show_less_channels": "Less",
|
||||||
"config": "Config",
|
"config": "Config",
|
||||||
|
|
@ -15,13 +14,8 @@
|
||||||
"chat": {
|
"chat": {
|
||||||
"welcome": "How can I help you today?",
|
"welcome": "How can I help you today?",
|
||||||
"welcomeDesc": "Ask me about weather, settings, or any other tasks. I'm here to assist you.",
|
"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...",
|
"placeholder": "Start a new message...",
|
||||||
"attach": "Attach file",
|
|
||||||
"voice": "Voice input",
|
|
||||||
"newChat": "New Chat",
|
"newChat": "New Chat",
|
||||||
"connecting": "Connecting...",
|
|
||||||
"notConnected": "Gateway is not running. Start it to chat.",
|
"notConnected": "Gateway is not running. Start it to chat.",
|
||||||
"thinking": {
|
"thinking": {
|
||||||
"step1": "Thinking...",
|
"step1": "Thinking...",
|
||||||
|
|
@ -29,12 +23,6 @@
|
||||||
"step3": "Preparing response...",
|
"step3": "Preparing response...",
|
||||||
"step4": "Almost there..."
|
"step4": "Almost there..."
|
||||||
},
|
},
|
||||||
"time": {
|
|
||||||
"justNow": "just now",
|
|
||||||
"minsAgo": "{{count}}m ago",
|
|
||||||
"hoursAgo": "{{count}}h ago",
|
|
||||||
"daysAgo": "{{count}}d ago"
|
|
||||||
},
|
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"noHistory": "No chat history yet",
|
"noHistory": "No chat history yet",
|
||||||
"loadingMore": "Loading more...",
|
"loadingMore": "Loading more...",
|
||||||
|
|
@ -79,6 +67,9 @@
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"confirm": "Confirm"
|
"confirm": "Confirm"
|
||||||
},
|
},
|
||||||
|
"labels": {
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"description": "Manage OAuth and token-based credentials for supported providers.",
|
"description": "Manage OAuth and token-based credentials for supported providers.",
|
||||||
"loading": "Loading credentials...",
|
"loading": "Loading credentials...",
|
||||||
|
|
@ -102,6 +93,7 @@
|
||||||
"actions": {
|
"actions": {
|
||||||
"browser": "Browser OAuth",
|
"browser": "Browser OAuth",
|
||||||
"deviceCode": "Device Code",
|
"deviceCode": "Device Code",
|
||||||
|
"stopLoading": "Stop Loading",
|
||||||
"saveToken": "Save",
|
"saveToken": "Save",
|
||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
},
|
},
|
||||||
|
|
@ -146,9 +138,6 @@
|
||||||
"models": {
|
"models": {
|
||||||
"description": "Configure API keys for AI providers. Only configured models are available for chat.",
|
"description": "Configure API keys for AI providers. Only configured models are available for chat.",
|
||||||
"loadError": "Failed to load models",
|
"loadError": "Failed to load models",
|
||||||
"header": {
|
|
||||||
"configured": "configured"
|
|
||||||
},
|
|
||||||
"noDefaultHintPrefix": "No default model set yet. Click",
|
"noDefaultHintPrefix": "No default model set yet. Click",
|
||||||
"noDefaultHintSuffix": "to set one.",
|
"noDefaultHintSuffix": "to set one.",
|
||||||
"status": {
|
"status": {
|
||||||
|
|
@ -185,8 +174,7 @@
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Delete Model?",
|
"title": "Delete Model?",
|
||||||
"description": "\"{{name}}\" will be permanently removed from your model list. This cannot be undone.",
|
"description": "\"{{name}}\" will be permanently removed from your model list. This cannot be undone.",
|
||||||
"confirm": "Delete",
|
"confirm": "Delete"
|
||||||
"errorDefault": "Cannot delete the default model. Please set another model as default first."
|
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"toggle": "Advanced options"
|
"toggle": "Advanced options"
|
||||||
|
|
@ -221,19 +209,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels": {
|
"channels": {
|
||||||
"description": "Configure messaging channels to connect your AI agent to chat platforms.",
|
|
||||||
"loadError": "Failed to load channels",
|
"loadError": "Failed to load channels",
|
||||||
"search": "Search channels...",
|
"edit": "Configure {{name}}",
|
||||||
"noResults": "No channels match your search.",
|
|
||||||
"header": {
|
|
||||||
"enabled": "enabled"
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
"configured": "Configured",
|
"configured": "Configured"
|
||||||
"unconfigured": "Not configured"
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"configure": "Configure"
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
|
|
@ -265,7 +244,6 @@
|
||||||
"encryptKey": "Encrypt Key",
|
"encryptKey": "Encrypt Key",
|
||||||
"baseUrl": "API Base URL",
|
"baseUrl": "API Base URL",
|
||||||
"proxy": "HTTP Proxy",
|
"proxy": "HTTP Proxy",
|
||||||
"proxyHint": "Optional. e.g. http://127.0.0.1:7890",
|
|
||||||
"mentionOnly": "Mention Only",
|
"mentionOnly": "Mention Only",
|
||||||
"typingEnabled": "Typing Indicator",
|
"typingEnabled": "Typing Indicator",
|
||||||
"placeholderEnabled": "Placeholder Message",
|
"placeholderEnabled": "Placeholder Message",
|
||||||
|
|
@ -273,32 +251,17 @@
|
||||||
"groupTriggerMentionOnly": "Group Mention Only",
|
"groupTriggerMentionOnly": "Group Mention Only",
|
||||||
"groupTriggerPrefixes": "Group Trigger Prefixes",
|
"groupTriggerPrefixes": "Group Trigger Prefixes",
|
||||||
"allowFrom": "Allow From",
|
"allowFrom": "Allow From",
|
||||||
"allowFromHint": "Comma-separated list of allowed user/group IDs. Leave empty to allow all.",
|
|
||||||
"allowFromPlaceholder": "e.g. 123456, 789012",
|
"allowFromPlaceholder": "e.g. 123456, 789012",
|
||||||
"allowOrigins": "Allow Origins",
|
"allowOrigins": "Allow Origins",
|
||||||
"allowOriginsHint": "Comma-separated list of allowed origins. Leave empty to allow all.",
|
|
||||||
"allowOriginsPlaceholder": "e.g. https://example.com, http://localhost:5173",
|
"allowOriginsPlaceholder": "e.g. https://example.com, http://localhost:5173",
|
||||||
"secretPlaceholder": "Enter secret",
|
"secretPlaceholder": "Enter secret",
|
||||||
"secretPlaceholderSet": "Leave blank to keep existing",
|
|
||||||
"secretHintSet": "A value is already set. Leave blank to keep it unchanged."
|
"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": {
|
"page": {
|
||||||
"notFound": "Channel \"{{name}}\" is not supported.",
|
"notFound": "Channel \"{{name}}\" is not supported.",
|
||||||
"saveSuccess": "Channel configuration saved.",
|
"saveSuccess": "Channel configuration saved.",
|
||||||
"saveError": "Failed to save channel configuration",
|
"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",
|
"enabled": "enabled",
|
||||||
"disabled": "disabled",
|
|
||||||
"docLink": "Documentation",
|
"docLink": "Documentation",
|
||||||
"enableLabel": "Enable channel"
|
"enableLabel": "Enable channel"
|
||||||
},
|
},
|
||||||
|
|
@ -316,7 +279,6 @@
|
||||||
"mentionOnly": "Only respond when the bot is explicitly mentioned in group chats.",
|
"mentionOnly": "Only respond when the bot is explicitly mentioned in group chats.",
|
||||||
"typingEnabled": "Display typing status while the assistant is generating a response.",
|
"typingEnabled": "Display typing status while the assistant is generating a response.",
|
||||||
"placeholderEnabled": "Enable temporary placeholder messages before the final reply is sent.",
|
"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.",
|
"groupTriggerMentionOnly": "In group chats, respond only when the bot is mentioned.",
|
||||||
"groupTriggerPrefixes": "Custom group-chat trigger prefixes, separated by commas.",
|
"groupTriggerPrefixes": "Custom group-chat trigger prefixes, separated by commas.",
|
||||||
"allowFrom": "Allowed user or group IDs, separated by commas.",
|
"allowFrom": "Allowed user or group IDs, separated by commas.",
|
||||||
|
|
@ -362,23 +324,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"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": {
|
"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.",
|
"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": "Workspace Directory",
|
||||||
"workspace_hint": "Base directory for agent file operations.",
|
"workspace_hint": "Base directory for agent file operations.",
|
||||||
"restrict_workspace": "Restrict to Workspace",
|
"restrict_workspace": "Restrict to Workspace",
|
||||||
|
|
@ -422,10 +369,6 @@
|
||||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
||||||
"launcher_load_error": "Failed to load service parameters.",
|
"launcher_load_error": "Failed to load service parameters.",
|
||||||
"launcher_restart_hint": "Service parameter changes apply after restarting PicoClaw Web.",
|
"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.",
|
"advanced_desc": "Open the raw JSON page to edit every field directly.",
|
||||||
"open_raw": "Raw Config",
|
"open_raw": "Raw Config",
|
||||||
"back_to_visual": "Visual Config",
|
"back_to_visual": "Visual Config",
|
||||||
|
|
@ -441,7 +384,6 @@
|
||||||
"format_success": "JSON formatted successfully.",
|
"format_success": "JSON formatted successfully.",
|
||||||
"format_error": "Invalid JSON format.",
|
"format_error": "Invalid JSON format.",
|
||||||
"format": "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."
|
"unsaved_changes": "You have unsaved changes."
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
"credentials": "凭据",
|
"credentials": "凭据",
|
||||||
"services": "服务",
|
"services": "服务",
|
||||||
"channels_group": "频道",
|
"channels_group": "频道",
|
||||||
"channels": "频道",
|
|
||||||
"show_more_channels": "更多",
|
"show_more_channels": "更多",
|
||||||
"show_less_channels": "收起",
|
"show_less_channels": "收起",
|
||||||
"config": "配置",
|
"config": "配置",
|
||||||
|
|
@ -15,13 +14,8 @@
|
||||||
"chat": {
|
"chat": {
|
||||||
"welcome": "今天我能为您做些什么?",
|
"welcome": "今天我能为您做些什么?",
|
||||||
"welcomeDesc": "您可以询问我天气、设置或其他任何任务,我随时为您效劳。",
|
"welcomeDesc": "您可以询问我天气、设置或其他任何任务,我随时为您效劳。",
|
||||||
"model": "模型",
|
|
||||||
"user": "用户",
|
|
||||||
"placeholder": "输入新消息...",
|
"placeholder": "输入新消息...",
|
||||||
"attach": "附加文件",
|
|
||||||
"voice": "语音输入",
|
|
||||||
"newChat": "新建对话",
|
"newChat": "新建对话",
|
||||||
"connecting": "连接中...",
|
|
||||||
"notConnected": "服务未运行,请先启动以进行对话。",
|
"notConnected": "服务未运行,请先启动以进行对话。",
|
||||||
"thinking": {
|
"thinking": {
|
||||||
"step1": "思考中...",
|
"step1": "思考中...",
|
||||||
|
|
@ -29,12 +23,6 @@
|
||||||
"step3": "准备回复...",
|
"step3": "准备回复...",
|
||||||
"step4": "马上就好..."
|
"step4": "马上就好..."
|
||||||
},
|
},
|
||||||
"time": {
|
|
||||||
"justNow": "刚刚",
|
|
||||||
"minsAgo": "{{count}}分钟前",
|
|
||||||
"hoursAgo": "{{count}}小时前",
|
|
||||||
"daysAgo": "{{count}}天前"
|
|
||||||
},
|
|
||||||
"history": "历史记录",
|
"history": "历史记录",
|
||||||
"noHistory": "暂无对话历史",
|
"noHistory": "暂无对话历史",
|
||||||
"loadingMore": "加载更多...",
|
"loadingMore": "加载更多...",
|
||||||
|
|
@ -79,6 +67,9 @@
|
||||||
"reset": "重置",
|
"reset": "重置",
|
||||||
"confirm": "确认"
|
"confirm": "确认"
|
||||||
},
|
},
|
||||||
|
"labels": {
|
||||||
|
"loading": "加载中..."
|
||||||
|
},
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"description": "管理已支持服务商的 OAuth 与 Token 凭据。",
|
"description": "管理已支持服务商的 OAuth 与 Token 凭据。",
|
||||||
"loading": "正在加载凭据...",
|
"loading": "正在加载凭据...",
|
||||||
|
|
@ -102,6 +93,7 @@
|
||||||
"actions": {
|
"actions": {
|
||||||
"browser": "浏览器 OAuth",
|
"browser": "浏览器 OAuth",
|
||||||
"deviceCode": "设备码",
|
"deviceCode": "设备码",
|
||||||
|
"stopLoading": "停止加载",
|
||||||
"saveToken": "保存",
|
"saveToken": "保存",
|
||||||
"logout": "退出登录"
|
"logout": "退出登录"
|
||||||
},
|
},
|
||||||
|
|
@ -146,9 +138,6 @@
|
||||||
"models": {
|
"models": {
|
||||||
"description": "为 AI 服务商配置 API Key。只有已配置的模型可用于对话。",
|
"description": "为 AI 服务商配置 API Key。只有已配置的模型可用于对话。",
|
||||||
"loadError": "加载模型列表失败",
|
"loadError": "加载模型列表失败",
|
||||||
"header": {
|
|
||||||
"configured": "已配置"
|
|
||||||
},
|
|
||||||
"noDefaultHintPrefix": "尚未设置默认模型,点击",
|
"noDefaultHintPrefix": "尚未设置默认模型,点击",
|
||||||
"noDefaultHintSuffix": "设为默认。",
|
"noDefaultHintSuffix": "设为默认。",
|
||||||
"status": {
|
"status": {
|
||||||
|
|
@ -185,8 +174,7 @@
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "确认删除模型?",
|
"title": "确认删除模型?",
|
||||||
"description": "「{{name}}」将从模型列表中永久移除,此操作不可撤销。",
|
"description": "「{{name}}」将从模型列表中永久移除,此操作不可撤销。",
|
||||||
"confirm": "删除",
|
"confirm": "删除"
|
||||||
"errorDefault": "无法删除默认模型。请先将其他模型设为默认。"
|
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"toggle": "高级选项"
|
"toggle": "高级选项"
|
||||||
|
|
@ -221,19 +209,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels": {
|
"channels": {
|
||||||
"description": "配置消息频道,将 AI 助手连接到各聊天平台。",
|
|
||||||
"loadError": "加载频道列表失败",
|
"loadError": "加载频道列表失败",
|
||||||
"search": "搜索频道...",
|
"edit": "配置 {{name}}",
|
||||||
"noResults": "没有匹配的频道。",
|
|
||||||
"header": {
|
|
||||||
"enabled": "已启用"
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
"configured": "已配置",
|
"configured": "已配置"
|
||||||
"unconfigured": "未配置"
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"configure": "配置"
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
|
|
@ -265,7 +244,6 @@
|
||||||
"encryptKey": "Encrypt Key",
|
"encryptKey": "Encrypt Key",
|
||||||
"baseUrl": "API Base URL",
|
"baseUrl": "API Base URL",
|
||||||
"proxy": "HTTP 代理",
|
"proxy": "HTTP 代理",
|
||||||
"proxyHint": "可选。例如 http://127.0.0.1:7890",
|
|
||||||
"mentionOnly": "仅提及时响应",
|
"mentionOnly": "仅提及时响应",
|
||||||
"typingEnabled": "输入中提示",
|
"typingEnabled": "输入中提示",
|
||||||
"placeholderEnabled": "占位消息",
|
"placeholderEnabled": "占位消息",
|
||||||
|
|
@ -273,32 +251,17 @@
|
||||||
"groupTriggerMentionOnly": "群聊仅提及时响应",
|
"groupTriggerMentionOnly": "群聊仅提及时响应",
|
||||||
"groupTriggerPrefixes": "群聊触发前缀",
|
"groupTriggerPrefixes": "群聊触发前缀",
|
||||||
"allowFrom": "允许来源",
|
"allowFrom": "允许来源",
|
||||||
"allowFromHint": "用逗号分隔的用户/群组 ID 列表,留空表示允许所有。",
|
|
||||||
"allowFromPlaceholder": "例如 123456, 789012",
|
"allowFromPlaceholder": "例如 123456, 789012",
|
||||||
"allowOrigins": "允许来源域名",
|
"allowOrigins": "允许来源域名",
|
||||||
"allowOriginsHint": "用逗号分隔允许的 Origin,留空表示允许所有。",
|
|
||||||
"allowOriginsPlaceholder": "例如 https://example.com, http://localhost:5173",
|
"allowOriginsPlaceholder": "例如 https://example.com, http://localhost:5173",
|
||||||
"secretPlaceholder": "输入密钥",
|
"secretPlaceholder": "输入密钥",
|
||||||
"secretPlaceholderSet": "留空保持原有值不变",
|
|
||||||
"secretHintSet": "已设置密钥,留空表示不修改。"
|
"secretHintSet": "已设置密钥,留空表示不修改。"
|
||||||
},
|
},
|
||||||
"edit": {
|
|
||||||
"title": "配置 {{name}}",
|
|
||||||
"description": "设置此频道的凭据和选项。",
|
|
||||||
"saveError": "保存频道配置失败",
|
|
||||||
"saving": "保存中..."
|
|
||||||
},
|
|
||||||
"page": {
|
"page": {
|
||||||
"notFound": "不支持频道“{{name}}”。",
|
"notFound": "不支持频道“{{name}}”。",
|
||||||
"saveSuccess": "频道配置已保存。",
|
"saveSuccess": "频道配置已保存。",
|
||||||
"saveError": "保存频道配置失败",
|
"saveError": "保存频道配置失败",
|
||||||
"flowTitle": "配置流程",
|
|
||||||
"flowDescription": "按以下步骤完成频道接入并投入使用。",
|
|
||||||
"step1": "填写凭据",
|
|
||||||
"step2": "启用频道",
|
|
||||||
"step3": "保存并重启网关",
|
|
||||||
"enabled": "已启用",
|
"enabled": "已启用",
|
||||||
"disabled": "未启用",
|
|
||||||
"docLink": "配置文档",
|
"docLink": "配置文档",
|
||||||
"enableLabel": "启用频道"
|
"enableLabel": "启用频道"
|
||||||
},
|
},
|
||||||
|
|
@ -316,7 +279,6 @@
|
||||||
"mentionOnly": "在群聊中仅当明确提及时才响应。",
|
"mentionOnly": "在群聊中仅当明确提及时才响应。",
|
||||||
"typingEnabled": "在生成回复时显示“正在输入”状态。",
|
"typingEnabled": "在生成回复时显示“正在输入”状态。",
|
||||||
"placeholderEnabled": "在最终回复发送前,先发送临时占位消息。",
|
"placeholderEnabled": "在最终回复发送前,先发送临时占位消息。",
|
||||||
"placeholderText": "等待最终回复期间显示的占位文案。",
|
|
||||||
"groupTriggerMentionOnly": "在群聊中仅当提及机器人时才响应。",
|
"groupTriggerMentionOnly": "在群聊中仅当提及机器人时才响应。",
|
||||||
"groupTriggerPrefixes": "群聊触发前缀,多个值用逗号分隔。",
|
"groupTriggerPrefixes": "群聊触发前缀,多个值用逗号分隔。",
|
||||||
"allowFrom": "允许访问的用户或群组 ID,多个值用逗号分隔。",
|
"allowFrom": "允许访问的用户或群组 ID,多个值用逗号分隔。",
|
||||||
|
|
@ -362,23 +324,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"providers": {
|
|
||||||
"description": "管理各个 AI 模型服务商的接入配置。"
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"description": "在此管理您下载或借用的 AI 模型。"
|
|
||||||
},
|
|
||||||
"credentials": {
|
|
||||||
"description": "安全管理您的 API 密钥与访问凭据。"
|
|
||||||
},
|
|
||||||
"config": {
|
"config": {
|
||||||
"description": "系统配置和偏好设置。",
|
|
||||||
"visual_title": "核心配置",
|
|
||||||
"visual_desc": "这里可编辑关键运行配置;若需完整字段请使用原始配置页。",
|
|
||||||
"load_error": "加载配置失败,请刷新后重试。",
|
"load_error": "加载配置失败,请刷新后重试。",
|
||||||
"section_agents": "智能体默认设置",
|
|
||||||
"section_runtime": "运行时",
|
|
||||||
"section_devices": "设备功能",
|
|
||||||
"workspace": "工作目录",
|
"workspace": "工作目录",
|
||||||
"workspace_hint": "智能体执行文件读写操作时使用的基础目录。",
|
"workspace_hint": "智能体执行文件读写操作时使用的基础目录。",
|
||||||
"restrict_workspace": "限制工作目录访问",
|
"restrict_workspace": "限制工作目录访问",
|
||||||
|
|
@ -422,10 +369,6 @@
|
||||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
||||||
"launcher_load_error": "加载服务参数失败。",
|
"launcher_load_error": "加载服务参数失败。",
|
||||||
"launcher_restart_hint": "服务参数变更需重启 PicoClaw Web 后生效。",
|
"launcher_restart_hint": "服务参数变更需重启 PicoClaw Web 后生效。",
|
||||||
"autostart_enable_success": "已开启开机自启。",
|
|
||||||
"autostart_disable_success": "已关闭开机自启。",
|
|
||||||
"autostart_update_error": "更新开机自启设置失败。",
|
|
||||||
"advanced_title": "需要完整配置能力?",
|
|
||||||
"advanced_desc": "可打开原始 JSON 页面直接编辑全部字段。",
|
"advanced_desc": "可打开原始 JSON 页面直接编辑全部字段。",
|
||||||
"open_raw": "原始配置",
|
"open_raw": "原始配置",
|
||||||
"back_to_visual": "可视化配置",
|
"back_to_visual": "可视化配置",
|
||||||
|
|
@ -441,7 +384,6 @@
|
||||||
"format_success": "JSON 格式化成功。",
|
"format_success": "JSON 格式化成功。",
|
||||||
"format_error": "JSON 格式无效。",
|
"format_error": "JSON 格式无效。",
|
||||||
"format": "格式化",
|
"format": "格式化",
|
||||||
"lose_unsaved_changes": "您有未保存的更改。确定要重置并丢失这些更改吗?",
|
|
||||||
"unsaved_changes": "您有未保存的更改。"
|
"unsaved_changes": "您有未保存的更改。"
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 rootRouteImport } from './routes/__root'
|
||||||
import { Route as ProvidersRouteImport } from './routes/providers'
|
|
||||||
import { Route as ModelsRouteImport } from './routes/models'
|
import { Route as ModelsRouteImport } from './routes/models'
|
||||||
import { Route as LogsRouteImport } from './routes/logs'
|
import { Route as LogsRouteImport } from './routes/logs'
|
||||||
import { Route as CredentialsRouteImport } from './routes/credentials'
|
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 ConfigRawRouteImport } from './routes/config.raw'
|
||||||
import { Route as ChannelsNameRouteImport } from './routes/channels/$name'
|
import { Route as ChannelsNameRouteImport } from './routes/channels/$name'
|
||||||
|
|
||||||
const ProvidersRoute = ProvidersRouteImport.update({
|
|
||||||
id: '/providers',
|
|
||||||
path: '/providers',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const ModelsRoute = ModelsRouteImport.update({
|
const ModelsRoute = ModelsRouteImport.update({
|
||||||
id: '/models',
|
id: '/models',
|
||||||
path: '/models',
|
path: '/models',
|
||||||
|
|
@ -72,7 +66,6 @@ export interface FileRoutesByFullPath {
|
||||||
'/credentials': typeof CredentialsRoute
|
'/credentials': typeof CredentialsRoute
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/models': typeof ModelsRoute
|
'/models': typeof ModelsRoute
|
||||||
'/providers': typeof ProvidersRoute
|
|
||||||
'/channels/$name': typeof ChannelsNameRoute
|
'/channels/$name': typeof ChannelsNameRoute
|
||||||
'/config/raw': typeof ConfigRawRoute
|
'/config/raw': typeof ConfigRawRoute
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +76,6 @@ export interface FileRoutesByTo {
|
||||||
'/credentials': typeof CredentialsRoute
|
'/credentials': typeof CredentialsRoute
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/models': typeof ModelsRoute
|
'/models': typeof ModelsRoute
|
||||||
'/providers': typeof ProvidersRoute
|
|
||||||
'/channels/$name': typeof ChannelsNameRoute
|
'/channels/$name': typeof ChannelsNameRoute
|
||||||
'/config/raw': typeof ConfigRawRoute
|
'/config/raw': typeof ConfigRawRoute
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +87,6 @@ export interface FileRoutesById {
|
||||||
'/credentials': typeof CredentialsRoute
|
'/credentials': typeof CredentialsRoute
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/models': typeof ModelsRoute
|
'/models': typeof ModelsRoute
|
||||||
'/providers': typeof ProvidersRoute
|
|
||||||
'/channels/$name': typeof ChannelsNameRoute
|
'/channels/$name': typeof ChannelsNameRoute
|
||||||
'/config/raw': typeof ConfigRawRoute
|
'/config/raw': typeof ConfigRawRoute
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +99,6 @@ export interface FileRouteTypes {
|
||||||
| '/credentials'
|
| '/credentials'
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/models'
|
| '/models'
|
||||||
| '/providers'
|
|
||||||
| '/channels/$name'
|
| '/channels/$name'
|
||||||
| '/config/raw'
|
| '/config/raw'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
|
@ -119,7 +109,6 @@ export interface FileRouteTypes {
|
||||||
| '/credentials'
|
| '/credentials'
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/models'
|
| '/models'
|
||||||
| '/providers'
|
|
||||||
| '/channels/$name'
|
| '/channels/$name'
|
||||||
| '/config/raw'
|
| '/config/raw'
|
||||||
id:
|
id:
|
||||||
|
|
@ -130,7 +119,6 @@ export interface FileRouteTypes {
|
||||||
| '/credentials'
|
| '/credentials'
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/models'
|
| '/models'
|
||||||
| '/providers'
|
|
||||||
| '/channels/$name'
|
| '/channels/$name'
|
||||||
| '/config/raw'
|
| '/config/raw'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
|
|
@ -142,18 +130,10 @@ export interface RootRouteChildren {
|
||||||
CredentialsRoute: typeof CredentialsRoute
|
CredentialsRoute: typeof CredentialsRoute
|
||||||
LogsRoute: typeof LogsRoute
|
LogsRoute: typeof LogsRoute
|
||||||
ModelsRoute: typeof ModelsRoute
|
ModelsRoute: typeof ModelsRoute
|
||||||
ProvidersRoute: typeof ProvidersRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
'/providers': {
|
|
||||||
id: '/providers'
|
|
||||||
path: '/providers'
|
|
||||||
fullPath: '/providers'
|
|
||||||
preLoaderRoute: typeof ProvidersRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/models': {
|
'/models': {
|
||||||
id: '/models'
|
id: '/models'
|
||||||
path: '/models'
|
path: '/models'
|
||||||
|
|
@ -243,7 +223,6 @@ const rootRouteChildren: RootRouteChildren = {
|
||||||
CredentialsRoute: CredentialsRoute,
|
CredentialsRoute: CredentialsRoute,
|
||||||
LogsRoute: LogsRoute,
|
LogsRoute: LogsRoute,
|
||||||
ModelsRoute: ModelsRoute,
|
ModelsRoute: ModelsRoute,
|
||||||
ProvidersRoute: ProvidersRoute,
|
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,11 @@ function RawConfigPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader title={t("pages.config.raw_json_title")}>
|
||||||
title={t("pages.config.raw_json_title", "Raw JSON Configuration")}
|
|
||||||
>
|
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link to="/config">
|
<Link to="/config">
|
||||||
<IconAdjustments className="size-4" />
|
<IconAdjustments className="size-4" />
|
||||||
{t("pages.config.back_to_visual", "Visual Config")}
|
{t("pages.config.back_to_visual")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
|
||||||
|
|
@ -87,15 +87,15 @@ function LogsPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<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="flex flex-1 flex-col overflow-hidden p-4 sm:p-8">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
<h1 className="text-2xl font-semibold tracking-tight">
|
||||||
{t("navigation.logs", "Logs")}
|
{t("navigation.logs")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
{t("pages.logs.description", "System logs and monitoring.")}
|
{t("pages.logs.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue