"use client"; import { LogOut, Menu, Settings2, Shield } from "lucide-react"; import Link from "next/link"; import { App, Button, Drawer, Form, Input, Modal } from "antd"; import { useConfigDialogStore } from "@/stores/use-config-dialog-store"; import { ModelPicker } from "@/components/model-picker"; import { GitHubLink } from "@/components/github-link"; import { UserStatusActions } from "@/components/user-status-actions"; import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler"; import { VersionReleaseModal } from "@/components/version-release-modal"; import type { AiConfig } from "@/lib/ai-config"; import { navigationTools, type NavigationToolSlug } from "@/lib/navigation-tools"; import { fetchImageModels } from "@/services/api/image"; import { useThemeStore } from "@/stores/use-theme-store"; 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) { const { message } = App.useApp(); const [loadingModels, setLoadingModels] = useState(false); const [mobileNavOpen, setMobileNavOpen] = useState(false); const appVersion = process.env.NEXT_PUBLIC_APP_VERSION || "dev"; const isConfigOpen = useConfigDialogStore((state) => state.isOpen); const shouldPromptContinue = useConfigDialogStore((state) => state.shouldPromptContinue); const openConfigDialog = useConfigDialogStore((state) => state.openConfigDialog); const setConfigDialogOpen = useConfigDialogStore((state) => state.setConfigDialogOpen); const clearPromptContinue = useConfigDialogStore((state) => state.clearPromptContinue); const theme = useThemeStore((state) => state.theme); const setTheme = useThemeStore((state) => state.setTheme); const user = useUserStore((state) => state.user); const isReady = useUserStore((state) => state.isReady); const logout = useUserStore((state) => state.clearSession); const finishConfig = () => { setConfigDialogOpen(false); if (!config.baseUrl.trim() || !config.imageModel.trim() || !config.textModel.trim() || !config.apiKey.trim()) return; if (shouldPromptContinue) { message.success("配置已保存,请继续刚才的请求"); } else { message.success("配置已保存"); } clearPromptContinue(); }; const refreshModels = async () => { if (!config.baseUrl.trim() || !config.apiKey.trim()) { message.error("请先填写 Base URL 和 API Key"); return; } 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]); message.success("模型列表已更新"); } catch (error) { message.error(error instanceof Error ? error.message : "读取模型失败"); } finally { setLoadingModels(false); } }; return ( <> {!hideHeader ? (
无限画布
{isReady && user ? ( openConfigDialog(false)} menuItems={[ ...(user.role === "admin" ? [{ key: "admin", icon: , label: 管理后台 }] : []), { key: "logout", icon: , label: "退出登录", onClick: logout }, ]} /> ) : ( <> 登录 )}
) : null} setMobileNavOpen(false)} className="md:hidden" >
{navigationTools.map((tool) => { const Icon = tool.icon; const active = tool.slug === activeToolSlug; return ( setMobileNavOpen(false)} className={cn( "flex items-center gap-3 rounded-lg px-3 py-3 text-base transition", active ? "bg-stone-100 font-medium text-stone-950 dark:bg-stone-800 dark:text-stone-100" : "text-stone-600 hover:bg-stone-100 hover:text-stone-950 dark:text-stone-300 dark:hover:bg-stone-800 dark:hover:text-stone-100", )} > {tool.label} ); })}
配置
模型和密钥
} open={isConfigOpen} width={560} centered onCancel={() => setConfigDialogOpen(false)} footer={} >
onConfigChange("baseUrl", event.target.value)} /> onConfigChange("apiKey", event.target.value)} />
模型列表
当前已保存 {config.models.length} 个模型
onConfigChange("imageModel", model)} fullWidth /> onConfigChange("textModel", model)} fullWidth /> onConfigChange("systemPrompt", event.target.value)} />
); }