diff --git a/frontend/src/app/workspace/settings/about/page.tsx b/frontend/src/app/workspace/settings/about/page.tsx new file mode 100644 index 0000000000..243cbb551d --- /dev/null +++ b/frontend/src/app/workspace/settings/about/page.tsx @@ -0,0 +1,5 @@ +import { AboutSettingsPage } from "@/components/workspace/settings/about-settings-page"; + +export default function SettingsAboutRoute() { + return ; +} diff --git a/frontend/src/app/workspace/settings/account/page.tsx b/frontend/src/app/workspace/settings/account/page.tsx new file mode 100644 index 0000000000..4882de37cb --- /dev/null +++ b/frontend/src/app/workspace/settings/account/page.tsx @@ -0,0 +1,5 @@ +import { AccountSettingsPage } from "@/components/workspace/settings/account-settings-page"; + +export default function SettingsAccountRoute() { + return ; +} diff --git a/frontend/src/app/workspace/settings/general/page.tsx b/frontend/src/app/workspace/settings/general/page.tsx new file mode 100644 index 0000000000..69fefe0f13 --- /dev/null +++ b/frontend/src/app/workspace/settings/general/page.tsx @@ -0,0 +1,5 @@ +import { GeneralSettingsPage } from "@/components/workspace/settings/general-settings-page"; + +export default function SettingsGeneralRoute() { + return ; +} diff --git a/frontend/src/app/workspace/settings/layout.tsx b/frontend/src/app/workspace/settings/layout.tsx new file mode 100644 index 0000000000..13655bdcbe --- /dev/null +++ b/frontend/src/app/workspace/settings/layout.tsx @@ -0,0 +1,7 @@ +import { SettingsShell } from "@/components/workspace/settings/settings-shell"; + +export default function SettingsLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return {children}; +} diff --git a/frontend/src/app/workspace/settings/memory/page.tsx b/frontend/src/app/workspace/settings/memory/page.tsx new file mode 100644 index 0000000000..9bf034f25e --- /dev/null +++ b/frontend/src/app/workspace/settings/memory/page.tsx @@ -0,0 +1,5 @@ +import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page"; + +export default function SettingsMemoryRoute() { + return ; +} diff --git a/frontend/src/app/workspace/settings/page.tsx b/frontend/src/app/workspace/settings/page.tsx new file mode 100644 index 0000000000..ac6010dea2 --- /dev/null +++ b/frontend/src/app/workspace/settings/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function SettingsIndexPage() { + redirect("/workspace/settings/general"); +} diff --git a/frontend/src/app/workspace/settings/skills/page.tsx b/frontend/src/app/workspace/settings/skills/page.tsx new file mode 100644 index 0000000000..fce3267bde --- /dev/null +++ b/frontend/src/app/workspace/settings/skills/page.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useCallback } from "react"; + +import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page"; + +export default function SettingsSkillsRoute() { + const router = useRouter(); + const handleClose = useCallback(() => { + router.push("/workspace"); + }, [router]); + return ; +} diff --git a/frontend/src/app/workspace/settings/tools/page.tsx b/frontend/src/app/workspace/settings/tools/page.tsx new file mode 100644 index 0000000000..a4c63860da --- /dev/null +++ b/frontend/src/app/workspace/settings/tools/page.tsx @@ -0,0 +1,5 @@ +import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page"; + +export default function SettingsToolsRoute() { + return ; +} diff --git a/frontend/src/components/workspace/command-palette.tsx b/frontend/src/components/workspace/command-palette.tsx index 1d754f61dc..aaa2dee368 100644 --- a/frontend/src/components/workspace/command-palette.tsx +++ b/frontend/src/components/workspace/command-palette.tsx @@ -27,14 +27,11 @@ import { import { useI18n } from "@/core/i18n/hooks"; import { useGlobalShortcuts } from "@/hooks/use-global-shortcuts"; -import { SettingsDialog } from "./settings"; - export function CommandPalette() { const { t } = useI18n(); const router = useRouter(); const [open, setOpen] = useState(false); const [shortcutsOpen, setShortcutsOpen] = useState(false); - const [settingsOpen, setSettingsOpen] = useState(false); const [isMac, setIsMac] = useState(false); const handleNewChat = useCallback(() => { @@ -44,8 +41,8 @@ export function CommandPalette() { const handleOpenSettings = useCallback(() => { setOpen(false); - setSettingsOpen(true); - }, []); + router.push("/workspace/settings/general"); + }, [router]); const handleShowShortcuts = useCallback(() => { setOpen(false); @@ -72,7 +69,6 @@ export function CommandPalette() { return ( <> - diff --git a/frontend/src/components/workspace/settings/account-settings-page.tsx b/frontend/src/components/workspace/settings/account-settings-page.tsx index 6c15771623..ef68d388be 100644 --- a/frontend/src/components/workspace/settings/account-settings-page.tsx +++ b/frontend/src/components/workspace/settings/account-settings-page.tsx @@ -10,7 +10,7 @@ import { useAuth } from "@/core/auth/AuthProvider"; import { parseAuthError } from "@/core/auth/types"; import { useI18n } from "@/core/i18n/hooks"; -import { SettingsSection } from "./settings-section"; +import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section"; export function AccountSettingsPage() { const { user, logout } = useAuth(); @@ -68,73 +68,126 @@ export function AccountSettingsPage() { } }; + const initial = ( + user?.email?.[0] ?? + user?.system_role?.[0] ?? + "?" + ).toUpperCase(); + return ( -
+
-
-
- - {t.settings.account.email} - - {user?.email ?? "—"} - - {t.settings.account.role} - - - {user?.system_role ?? "—"} - + +
+
+ {initial} +
+
+
+ {user?.email ?? "—"} +
+
+ {user?.system_role ?? "—"} +
+
-
+ -
- setCurrentPassword(e.target.value)} - required - /> - setNewPassword(e.target.value)} - required - minLength={8} - /> - setConfirmPassword(e.target.value)} - required - minLength={8} - /> - {error &&

{error}

} - {message &&

{message}

} - -
+ +
+ setCurrentPassword(e.target.value)} + required + className="w-[260px]" + /> + } + /> + setNewPassword(e.target.value)} + required + minLength={8} + className="w-[260px]" + /> + } + /> + setConfirmPassword(e.target.value)} + required + minLength={8} + className="w-[260px]" + /> + } + /> + {(error || message) && ( +
+ {error &&

{error}

} + {message && ( +

+ {message} +

+ )} +
+ )} +
+ +
+ +
- - + + + + + {t.settings.account.signOut} + + } + /> +
); diff --git a/frontend/src/components/workspace/settings/appearance-settings-page.tsx b/frontend/src/components/workspace/settings/appearance-settings-page.tsx index 2e76d68e5a..28ea15b766 100644 --- a/frontend/src/components/workspace/settings/appearance-settings-page.tsx +++ b/frontend/src/components/workspace/settings/appearance-settings-page.tsx @@ -11,12 +11,11 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; import { enUS, isLocale, zhCN, type Locale } from "@/core/i18n"; import { useI18n } from "@/core/i18n/hooks"; import { cn } from "@/lib/utils"; -import { SettingsSection } from "./settings-section"; +import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section"; const languageOptions: { value: Locale; label: string }[] = [ { value: "en-US", label: enUS.locale.localName }, @@ -60,7 +59,7 @@ export function AppearanceSettingsPage() { ); return ( -
+
- - - - } - }} - > - - - - - {languageOptions.map((item) => ( - - {item.label} - - ))} - - + /> +
); diff --git a/frontend/src/components/workspace/settings/general-settings-page.tsx b/frontend/src/components/workspace/settings/general-settings-page.tsx new file mode 100644 index 0000000000..442118a370 --- /dev/null +++ b/frontend/src/components/workspace/settings/general-settings-page.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { AppearanceSettingsPage } from "./appearance-settings-page"; +import { NotificationSettingsPage } from "./notification-settings-page"; + +export function GeneralSettingsPage() { + return ( +
+ + +
+ ); +} diff --git a/frontend/src/components/workspace/settings/index.ts b/frontend/src/components/workspace/settings/index.ts index 658450689b..87d3c38bcc 100644 --- a/frontend/src/components/workspace/settings/index.ts +++ b/frontend/src/components/workspace/settings/index.ts @@ -1 +1 @@ -export { SettingsDialog } from "./settings-dialog"; +export { SettingsShell } from "./settings-shell"; diff --git a/frontend/src/components/workspace/settings/memory-settings-page.tsx b/frontend/src/components/workspace/settings/memory-settings-page.tsx index ce01d3c271..da917ba2a9 100644 --- a/frontend/src/components/workspace/settings/memory-settings-page.tsx +++ b/frontend/src/components/workspace/settings/memory-settings-page.tsx @@ -571,9 +571,16 @@ export function MemorySettingsPage() { }} variant="outline" > - {filterAll} - {filterFacts} - + + {filterAll} + + + {filterFacts} + + {filterSummaries} @@ -591,6 +598,7 @@ export function MemorySettingsPage() { variant="outline" onClick={() => fileInputRef.current?.click()} disabled={importMemoryMutation.isPending} + className="whitespace-nowrap" > {importButton} @@ -599,11 +607,16 @@ export function MemorySettingsPage() { variant="outline" onClick={() => void handleExportMemory()} disabled={isExporting} + className="whitespace-nowrap" > {isExporting ? t.common.loading : exportButton} - @@ -611,6 +624,7 @@ export function MemorySettingsPage() { variant="destructive" onClick={() => setClearDialogOpen(true)} disabled={clearMemory.isPending} + className="whitespace-nowrap" > {clearMemory.isPending ? t.common.loading : clearAllLabel} diff --git a/frontend/src/components/workspace/settings/notification-settings-page.tsx b/frontend/src/components/workspace/settings/notification-settings-page.tsx index 5fbdb5002d..0e99fa19ec 100644 --- a/frontend/src/components/workspace/settings/notification-settings-page.tsx +++ b/frontend/src/components/workspace/settings/notification-settings-page.tsx @@ -8,7 +8,7 @@ import { useI18n } from "@/core/i18n/hooks"; import { useNotification } from "@/core/notification/hooks"; import { useLocalSettings } from "@/core/settings"; -import { SettingsSection } from "./settings-section"; +import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section"; export function NotificationSettingsPage() { const { t } = useI18n(); @@ -27,18 +27,13 @@ export function NotificationSettingsPage() { }); }; - const handleEnableNotification = async (enabled: boolean) => { - setSettings("notification", { - enabled, - }); + const handleEnableNotification = (enabled: boolean) => { + setSettings("notification", { enabled }); }; if (!isSupported) { return ( - +

{t.settings.notification.notSupported}

@@ -47,12 +42,12 @@ export function NotificationSettingsPage() { } return ( - -
{t.settings.notification.description}
-
+ + + -
-
- } - > -
+ } + /> + {permission === "default" && ( - + + + {t.settings.notification.requestPermission} + + } + /> )} {permission === "denied" && ( -

- {t.settings.notification.deniedHint} -

+
+

+ {t.settings.notification.deniedHint} +

+
)} {permission === "granted" && settings.notification.enabled && ( -
- -
+ + + {t.settings.notification.testButton} + + } + /> )} -
+
); } diff --git a/frontend/src/components/workspace/settings/settings-dialog.tsx b/frontend/src/components/workspace/settings/settings-dialog.tsx deleted file mode 100644 index 6e9fa5ddf9..0000000000 --- a/frontend/src/components/workspace/settings/settings-dialog.tsx +++ /dev/null @@ -1,153 +0,0 @@ -"use client"; - -import { - BellIcon, - InfoIcon, - BrainIcon, - PaletteIcon, - SparklesIcon, - UserIcon, - WrenchIcon, -} from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; - -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { AboutSettingsPage } from "@/components/workspace/settings/about-settings-page"; -import { AccountSettingsPage } from "@/components/workspace/settings/account-settings-page"; -import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page"; -import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page"; -import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page"; -import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page"; -import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page"; -import { useI18n } from "@/core/i18n/hooks"; -import { cn } from "@/lib/utils"; - -type SettingsSection = - | "account" - | "appearance" - | "memory" - | "tools" - | "skills" - | "notification" - | "about"; - -type SettingsDialogProps = React.ComponentProps & { - defaultSection?: SettingsSection; -}; - -export function SettingsDialog(props: SettingsDialogProps) { - const { defaultSection = "appearance", ...dialogProps } = props; - const { t } = useI18n(); - const [activeSection, setActiveSection] = - useState(defaultSection); - - useEffect(() => { - // When opening the dialog, ensure the active section follows the caller's intent. - // This allows triggers like "About" to open the dialog directly on that page. - if (dialogProps.open) { - setActiveSection(defaultSection); - } - }, [defaultSection, dialogProps.open]); - - const sections = useMemo( - () => [ - { - id: "account", - label: t.settings.sections.account, - icon: UserIcon, - }, - { - id: "appearance", - label: t.settings.sections.appearance, - icon: PaletteIcon, - }, - { - id: "notification", - label: t.settings.sections.notification, - icon: BellIcon, - }, - { - id: "memory", - label: t.settings.sections.memory, - icon: BrainIcon, - }, - { id: "tools", label: t.settings.sections.tools, icon: WrenchIcon }, - { id: "skills", label: t.settings.sections.skills, icon: SparklesIcon }, - { id: "about", label: t.settings.sections.about, icon: InfoIcon }, - ], - [ - t.settings.sections.account, - t.settings.sections.appearance, - t.settings.sections.memory, - t.settings.sections.tools, - t.settings.sections.skills, - t.settings.sections.notification, - t.settings.sections.about, - ], - ); - return ( - props.onOpenChange?.(open)} - > - - - {t.settings.title} -

- {t.settings.description} -

-
-
- - -
- {activeSection === "account" && } - {activeSection === "appearance" && } - {activeSection === "memory" && } - {activeSection === "tools" && } - {activeSection === "skills" && ( - props.onOpenChange?.(false)} - /> - )} - {activeSection === "notification" && } - {activeSection === "about" && } -
-
-
-
-
- ); -} diff --git a/frontend/src/components/workspace/settings/settings-section.tsx b/frontend/src/components/workspace/settings/settings-section.tsx index 957ead8b72..751ca61e8a 100644 --- a/frontend/src/components/workspace/settings/settings-section.tsx +++ b/frontend/src/components/workspace/settings/settings-section.tsx @@ -1,5 +1,10 @@ import { cn } from "@/lib/utils"; +/** + * Top-level section under a settings page (e.g. "Theme", "Change password"). + * Title sits flush with the page; description gives a one-line summary; + * children render below in a `mt-4` block. + */ export function SettingsSection({ className, title, @@ -7,19 +12,89 @@ export function SettingsSection({ children, }: { className?: string; - title: React.ReactNode; + title?: React.ReactNode; description?: React.ReactNode; children: React.ReactNode; }) { + const hasHeader = Boolean(title) || Boolean(description); return (
-
-
{title}
+ {hasHeader && ( +
+ {title && ( +

{title}

+ )} + {description && ( +
{description}
+ )} +
+ )} +
{children}
+
+ ); +} + +/** + * Bordered container that groups related rows. Children are separated by a + * subtle divider (`divide-y`). + */ +export function SettingsCard({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} + +/** + * Single row within a SettingsCard. Label/description on the left, control on + * the right. Falls back to stacked layout on small screens. + * + * Use `size="compact"` for dense form rows (e.g. password fields) where the + * default vertical padding feels too airy. + */ +export function SettingsRow({ + label, + description, + control, + className, + align = "center", + size = "default", +}: { + label: React.ReactNode; + description?: React.ReactNode; + control: React.ReactNode; + className?: string; + align?: "center" | "start"; + size?: "default" | "compact"; +}) { + return ( +
+
+
{label}
{description && ( -
{description}
+

{description}

)} - -
{children}
- +
+
{control}
+
); } diff --git a/frontend/src/components/workspace/settings/settings-shell.tsx b/frontend/src/components/workspace/settings/settings-shell.tsx new file mode 100644 index 0000000000..79ec9260d7 --- /dev/null +++ b/frontend/src/components/workspace/settings/settings-shell.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { + InfoIcon, + BrainIcon, + SettingsIcon, + SparklesIcon, + UserIcon, + WrenchIcon, +} from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useMemo } from "react"; + +import { useI18n } from "@/core/i18n/hooks"; +import { cn } from "@/lib/utils"; + +const SETTINGS_ROOT = "/workspace/settings"; + +export function SettingsShell({ children }: { children: React.ReactNode }) { + const { t } = useI18n(); + const pathname = usePathname(); + + const sections = useMemo( + () => [ + { + id: "general", + href: `${SETTINGS_ROOT}/general`, + label: t.settings.sections.general, + icon: SettingsIcon, + }, + { + id: "account", + href: `${SETTINGS_ROOT}/account`, + label: t.settings.sections.account, + icon: UserIcon, + }, + { + id: "memory", + href: `${SETTINGS_ROOT}/memory`, + label: t.settings.sections.memory, + icon: BrainIcon, + }, + { + id: "tools", + href: `${SETTINGS_ROOT}/tools`, + label: t.settings.sections.tools, + icon: WrenchIcon, + }, + { + id: "skills", + href: `${SETTINGS_ROOT}/skills`, + label: t.settings.sections.skills, + icon: SparklesIcon, + }, + { + id: "about", + href: `${SETTINGS_ROOT}/about`, + label: t.settings.sections.about, + icon: InfoIcon, + }, + ], + [ + t.settings.sections.general, + t.settings.sections.account, + t.settings.sections.memory, + t.settings.sections.tools, + t.settings.sections.skills, + t.settings.sections.about, + ], + ); + + return ( +
+
+
+ {/* Page header */} +
+

+ {t.settings.title} +

+

+ {t.settings.description} +

+
+ + {/* Two-column: sticky text nav + content */} +
+ +
{children}
+
+
+
+
+ ); +} diff --git a/frontend/src/components/workspace/settings/tool-settings-page.tsx b/frontend/src/components/workspace/settings/tool-settings-page.tsx index dd3cd0d9d1..df5ad92c38 100644 --- a/frontend/src/components/workspace/settings/tool-settings-page.tsx +++ b/frontend/src/components/workspace/settings/tool-settings-page.tsx @@ -1,19 +1,12 @@ "use client"; -import { - Item, - ItemActions, - ItemContent, - ItemDescription, - ItemTitle, -} from "@/components/ui/item"; import { Switch } from "@/components/ui/switch"; import { useI18n } from "@/core/i18n/hooks"; import { useMCPConfig, useEnableMCPServer } from "@/core/mcp/hooks"; import type { MCPServerConfig } from "@/core/mcp/types"; import { env } from "@/env"; -import { SettingsSection } from "./settings-section"; +import { SettingsCard, SettingsRow, SettingsSection } from "./settings-section"; export function ToolSettingsPage() { const { t } = useI18n(); @@ -26,7 +19,7 @@ export function ToolSettingsPage() { {isLoading ? (
{t.common.loading}
) : error ? ( -
Error: {error.message}
+
Error: {error.message}
) : ( config && )} @@ -40,21 +33,18 @@ function MCPServerList({ servers: Record; }) { const { mutate: enableMCPServer } = useEnableMCPServer(); + const entries = Object.entries(servers); + if (entries.length === 0) { + return null; + } return ( -
- {Object.entries(servers).map(([name, config]) => ( - - - -
-
{name}
-
-
- - {config.description} - -
- + + {entries.map(([name, config]) => ( + - -
+ } + /> ))} -
+ ); } diff --git a/frontend/src/components/workspace/workspace-nav-menu.tsx b/frontend/src/components/workspace/workspace-nav-menu.tsx index 8b99be0789..7b1022b905 100644 --- a/frontend/src/components/workspace/workspace-nav-menu.tsx +++ b/frontend/src/components/workspace/workspace-nav-menu.tsx @@ -9,6 +9,7 @@ import { Settings2Icon, SettingsIcon, } from "lucide-react"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { @@ -28,7 +29,6 @@ import { import { useI18n } from "@/core/i18n/hooks"; import { GithubIcon } from "./github-icon"; -import { SettingsDialog } from "./settings"; function NavMenuButtonContent({ isSidebarOpen, @@ -51,10 +51,7 @@ function NavMenuButtonContent({ } export function WorkspaceNavMenu() { - const [settingsOpen, setSettingsOpen] = useState(false); - const [settingsDefaultSection, setSettingsDefaultSection] = useState< - "appearance" | "memory" | "tools" | "skills" | "notification" | "about" - >("appearance"); + const router = useRouter(); const [mounted, setMounted] = useState(false); const { open: isSidebarOpen } = useSidebar(); const { t } = useI18n(); @@ -65,11 +62,6 @@ export function WorkspaceNavMenu() { return ( <> - {mounted ? ( @@ -90,8 +82,7 @@ export function WorkspaceNavMenu() { { - setSettingsDefaultSection("appearance"); - setSettingsOpen(true); + router.push("/workspace/settings/general"); }} > @@ -139,8 +130,7 @@ export function WorkspaceNavMenu() { { - setSettingsDefaultSection("about"); - setSettingsOpen(true); + router.push("/workspace/settings/about"); }} > diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts index d2b9538b94..10b11f847f 100644 --- a/frontend/src/core/i18n/locales/en-US.ts +++ b/frontend/src/core/i18n/locales/en-US.ts @@ -351,6 +351,7 @@ export const enUS: Translations = { title: "Settings", description: "Adjust how DeerFlow looks and behaves for you.", sections: { + general: "General", account: "Account", appearance: "Appearance", memory: "Memory", @@ -365,9 +366,9 @@ export const enUS: Translations = { "DeerFlow automatically learns from your conversations in the background. These memories help DeerFlow understand you better and deliver a more personalized experience.", empty: "No memory data to display.", rawJson: "Raw JSON", - exportButton: "Export memory", + exportButton: "Export", exportSuccess: "Memory exported", - importButton: "Import memory", + importButton: "Import", importConfirmTitle: "Import memory?", importConfirmDescription: "This will overwrite your current memory with the selected JSON backup.", @@ -381,7 +382,7 @@ export const enUS: Translations = { editFactTitle: "Edit memory fact", addFactSuccess: "Fact created", editFactSuccess: "Fact updated", - clearAll: "Clear all memory", + clearAll: "Clear all", clearAllConfirmTitle: "Clear all memory?", clearAllConfirmDescription: "This will remove all saved summaries and facts. This action cannot be undone.", @@ -493,7 +494,10 @@ export const enUS: Translations = { networkError: "Network error. Please try again.", updating: "Updating...", updatePassword: "Update Password", + sessionTitle: "Session", signOut: "Sign Out", + signOutDescription: + "Sign out of this device. You can sign back in any time.", }, acknowledge: { emptyTitle: "Acknowledgements", diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts index 79e279192f..8cf001a68e 100644 --- a/frontend/src/core/i18n/locales/types.ts +++ b/frontend/src/core/i18n/locales/types.ts @@ -278,6 +278,7 @@ export interface Translations { title: string; description: string; sections: { + general: string; account: string; appearance: string; memory: string; @@ -409,7 +410,9 @@ export interface Translations { networkError: string; updating: string; updatePassword: string; + sessionTitle: string; signOut: string; + signOutDescription: string; }; acknowledge: { emptyTitle: string; diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts index c4fc6b9457..7d968cf501 100644 --- a/frontend/src/core/i18n/locales/zh-CN.ts +++ b/frontend/src/core/i18n/locales/zh-CN.ts @@ -335,6 +335,7 @@ export const zhCN: Translations = { title: "设置", description: "根据你的偏好调整 DeerFlow 的界面和行为。", sections: { + general: "通用", account: "账号", appearance: "外观", memory: "记忆", @@ -473,7 +474,9 @@ export const zhCN: Translations = { networkError: "网络错误,请重试。", updating: "更新中...", updatePassword: "修改密码", + sessionTitle: "会话", signOut: "退出登录", + signOutDescription: "退出当前设备的登录,你可以随时重新登录。", }, acknowledge: { emptyTitle: "致谢",