diff --git a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx index bc7218a..1a8d6bf 100644 --- a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx +++ b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx @@ -2160,16 +2160,12 @@ function CanvasTopBar({
node.parentElement || document.body} onOpenShortcuts={() => { setShortcutsOpen(true); setAccountOpen(false); }} - iconStyle={{ color: theme.node.text }} - gitHubClassName="size-11 text-base" - gitHubStyle={{ color: theme.node.text }} - versionStyle={{ color: theme.node.text }} - avatarStyle={{ borderColor: theme.toolbar.border, color: theme.node.text }} /> {assistantCollapsed ? ( <> diff --git a/web/src/app/(user)/layout.tsx b/web/src/app/(user)/layout.tsx index 0fb23f4..1363cf1 100644 --- a/web/src/app/(user)/layout.tsx +++ b/web/src/app/(user)/layout.tsx @@ -1,22 +1,13 @@ "use client"; import type { ReactNode } from "react"; -import { usePathname } from "next/navigation"; import { AppTopNav } from "@/components/layout/app-top-nav"; -import { type NavigationToolSlug, navigationTools } from "@/constant/navigation-tools"; -import { useConfigStore } from "@/stores/use-config-store"; export default function UserLayout({ children }: { children: ReactNode }) { - const pathname = usePathname(); - const config = useConfigStore((state) => state.config); - const updateConfig = useConfigStore((state) => state.updateConfig); - const slug = pathname.split("/").filter(Boolean)[0]; - const activeToolSlug = navigationTools.some((tool) => tool.slug === slug) ? (slug as NavigationToolSlug) : undefined; - return (
- +
{children}
); diff --git a/web/src/components/layout/app-top-nav.tsx b/web/src/components/layout/app-top-nav.tsx index a0bc124..3554cd7 100644 --- a/web/src/components/layout/app-top-nav.tsx +++ b/web/src/components/layout/app-top-nav.tsx @@ -3,6 +3,7 @@ import { Menu, Settings2 } from "lucide-react"; import Link from "next/link"; import { App, Button, Drawer, Form, Input, Modal, Segmented } from "antd"; +import { usePathname } from "next/navigation"; import { ModelPicker } from "@/components/model-picker"; import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler"; @@ -17,17 +18,13 @@ import { useUserStore } from "@/stores/use-user-store"; import { cn } from "@/lib/utils"; import { useState } from "react"; -type AppTopNavProps = { - activeToolSlug?: NavigationToolSlug; - config: AiConfig; - onConfigChange: (key: K, value: AiConfig[K]) => void; - hideHeader?: boolean; -}; - -export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = false }: AppTopNavProps) { +export function AppTopNav() { const { message } = App.useApp(); + const pathname = usePathname(); const [loadingModels, setLoadingModels] = useState(false); const [mobileNavOpen, setMobileNavOpen] = useState(false); + const config = useConfigStore((state) => state.config); + const updateConfig = useConfigStore((state) => state.updateConfig); const isConfigOpen = useConfigStore((state) => state.isConfigOpen); const shouldPromptContinue = useConfigStore((state) => state.shouldPromptContinue); const openConfigDialog = useConfigStore((state) => state.openConfigDialog); @@ -39,6 +36,9 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = const isReady = useUserStore((state) => state.isReady); const publicSettings = useConfigStore((state) => state.publicSettings); const effectiveConfig = useEffectiveConfig(); + const hideHeader = /^\/canvas\/[^/]+/.test(pathname); + const slug = pathname.split("/").filter(Boolean)[0]; + const activeToolSlug = navigationTools.some((tool) => tool.slug === slug) ? (slug as NavigationToolSlug) : undefined; const modelChannel = publicSettings?.modelChannel; const allowCustomChannel = modelChannel?.allowCustomChannel === true; const effectiveMode = allowCustomChannel ? config.channelMode : "remote"; @@ -48,7 +48,7 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = setConfigDialogOpen(false); if (effectiveMode === "local" && (!config.baseUrl.trim() || !config.apiKey.trim())) return; if (!modelConfig.imageModel.trim() || !modelConfig.textModel.trim()) return; - if (!allowCustomChannel && config.channelMode !== "remote") onConfigChange("channelMode", "remote"); + if (!allowCustomChannel && config.channelMode !== "remote") updateConfig("channelMode", "remote"); if (shouldPromptContinue) { message.success("配置已保存,请继续刚才的请求"); } else { @@ -65,9 +65,9 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = setLoadingModels(true); try { const models = await fetchImageModels(config); - onConfigChange("models", models); - if (models.length && !models.includes(config.imageModel)) onConfigChange("imageModel", models[0]); - if (models.length && !models.includes(config.textModel)) onConfigChange("textModel", models[0]); + updateConfig("models", models); + if (models.length && !models.includes(config.imageModel)) updateConfig("imageModel", models[0]); + if (models.length && !models.includes(config.textModel)) updateConfig("textModel", models[0]); message.success("模型列表已更新"); } catch (error) { message.error(error instanceof Error ? error.message : "读取模型失败"); @@ -209,7 +209,7 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = onConfigChange("channelMode", value as AiConfig["channelMode"])} + onChange={(value) => updateConfig("channelMode", value as AiConfig["channelMode"])} options={[{ label: "云端渠道", value: "remote" }, { label: "本地直连", value: "local" }]} /> @@ -218,10 +218,10 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = <>
- onConfigChange("baseUrl", event.target.value)} /> + updateConfig("baseUrl", event.target.value)} /> - onConfigChange("apiKey", event.target.value)} /> + updateConfig("apiKey", event.target.value)} />
@@ -240,15 +240,15 @@ export function AppTopNav({ activeToolSlug, config, onConfigChange, hideHeader = )}
- onConfigChange("imageModel", model)} fullWidth /> + updateConfig("imageModel", model)} fullWidth /> - onConfigChange("textModel", model)} fullWidth /> + updateConfig("textModel", model)} fullWidth />
{effectiveMode === "local" ? ( - onConfigChange("systemPrompt", event.target.value)} /> + updateConfig("systemPrompt", event.target.value)} /> ) : null} diff --git a/web/src/components/layout/user-status-actions.tsx b/web/src/components/layout/user-status-actions.tsx index a850c08..e5483c1 100644 --- a/web/src/components/layout/user-status-actions.tsx +++ b/web/src/components/layout/user-status-actions.tsx @@ -10,47 +10,44 @@ import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler"; import { GitHubLink } from "@/components/layout/github-link"; import { VersionReleaseModal } from "@/components/layout/version-release-modal"; import { cn } from "@/lib/utils"; +import { canvasThemes } from "@/lib/canvas-theme"; import { useConfigStore } from "@/stores/use-config-store"; import { useThemeStore } from "@/stores/use-theme-store"; import { useUserStore } from "@/stores/use-user-store"; type UserStatusActionsProps = { showConfig?: boolean; + variant?: "default" | "canvas"; onOpenShortcuts?: () => void; accountOpen?: boolean; onAccountOpenChange?: (open: boolean) => void; accountRef?: RefObject; getPopupContainer?: (node: HTMLElement) => HTMLElement; - avatarClassName?: string; - avatarStyle?: CSSProperties; - gitHubClassName?: string; - gitHubStyle?: CSSProperties; - versionStyle?: CSSProperties; - iconStyle?: CSSProperties; }; export function UserStatusActions({ showConfig = true, + variant = "default", onOpenShortcuts, accountOpen, onAccountOpenChange, accountRef, getPopupContainer, - avatarClassName, - avatarStyle, - gitHubClassName, - gitHubStyle, - versionStyle, - iconStyle, }: UserStatusActionsProps) { const theme = useThemeStore((state) => state.theme); const setTheme = useThemeStore((state) => state.setTheme); const user = useUserStore((state) => state.user); const logout = useUserStore((state) => state.clearSession); const openConfigDialog = useConfigStore((state) => state.openConfigDialog); + const canvasTheme = canvasThemes[theme]; const userName = user?.username || "用户"; const avatarText = (userName.trim()[0] || "U").toUpperCase(); const naturalIconClass = "inline-flex size-8 shrink-0 items-center justify-center text-stone-600 transition hover:text-stone-950 dark:text-stone-300 dark:hover:text-white [&_svg]:size-4"; + const iconStyle: CSSProperties | undefined = variant === "canvas" ? { color: canvasTheme.node.text } : undefined; + const versionStyle = iconStyle; + const gitHubClassName = variant === "canvas" ? "size-11 text-base" : undefined; + const gitHubStyle = iconStyle; + const avatarStyle: CSSProperties | undefined = variant === "canvas" ? { borderColor: canvasTheme.toolbar.border, color: canvasTheme.node.text } : undefined; const menuItems: ItemType[] = [ { key: "user", disabled: true, label: {userName} }, ...(user?.role === "admin" ? [{ key: "admin", icon: , label: 管理后台 }] : []), @@ -95,7 +92,7 @@ export function UserStatusActions({ >