import { App, Button, InputNumber } from "antd"; import { SettingsRow } from "@/components/ui/product/settings-row"; import { ArrowLeft, Boxes, Brain, Bug, Cloud, MessageSquareText, RadioTower, SlidersHorizontal, Workflow } from "lucide-react"; import { useEffect, useLayoutEffect, useMemo, useState, type ReactNode } from "react"; import { useNavigate, useSearchParams } from "react-router"; import { UserOSSSettingsForm } from "@/components/layout/user-oss-settings-form"; import { refreshSystemChannels } from "@/lib/user-session"; import { defaultConfig, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store"; import { useUserStore } from "@/stores/use-user-store"; import { ChannelSettingsPane, channelValidationError, focusInvalidChannelField, isChannelReady } from "./channel-settings-pane"; import { ModelDefaultGrid } from "./model-default-grid"; import { PromptPreferencesPane } from "./prompt-preferences-pane"; import DiagnosticsPanel from "./diagnostics-panel"; import AgentMemoryPane from "./agent-memory-pane"; import { RunningHubSettingsPane } from "./runninghub-settings-pane"; import { RUNNINGHUB_PLUGIN_ID } from "@/lib/plugins/builtin/workflows"; import { usePluginStore } from "@/stores/use-plugin-store"; type ConfigSectionKey = "channels" | "models" | "runninghub" | "preferences" | "prompts" | "agent-memory" | "storage" | "diagnostics"; const configSections: Array<{ key: ConfigSectionKey; label: string; description: string; icon: ReactNode }> = [ { key: "channels", label: "个人渠道", description: "模型服务与个人工作流", icon: }, { key: "runninghub", label: "RunningHub 工作流", description: "个人渠道的云端工作流配置", icon: }, { key: "models", label: "模型选择", description: "按领域选择默认模型", icon: }, { key: "preferences", label: "生成偏好", description: "画布生成默认值", icon: }, { key: "prompts", label: "提示词偏好", description: "按任务定制平台模板", icon: }, { key: "agent-memory", label: "Agent 记忆", description: "批准、添加、导出导入、压缩", icon: }, { key: "storage", label: "我的对象存储", description: "管理个人媒体存储", icon: }, { key: "diagnostics", label: "问题诊断", description: "导出日志协助排查", icon: }, ]; export function isConfigSection(value: string | null): value is ConfigSectionKey { return configSections.some((section) => section.key === value); } export default function SettingsPage() { const { message } = App.useApp(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const requestedSection = searchParams.get("section"); const customChannelsEnabled = useUserStore((state) => state.features.customChannelsEnabled); const runtimeStatuses = usePluginStore((state) => state.runtimeStatuses); const runningHubPluginEnabled = runtimeStatuses[RUNNINGHUB_PLUGIN_ID] === "enabled"; const requestedSectionEnabled = requestedSection !== "runninghub" || runningHubPluginEnabled; const initialSection = isConfigSection(requestedSection) && requestedSectionEnabled ? requestedSection : customChannelsEnabled ? "channels" : "models"; const [activeTab, setActiveTab] = useState(initialSection === "channels" && !customChannelsEnabled ? "models" : initialSection); const config = useConfigStore((state) => state.config); const effectiveConfig = useEffectiveConfig(); const updateConfig = useConfigStore((state) => state.updateConfig); const shouldPromptContinue = searchParams.get("continue") === "1"; const userId = useUserStore((state) => state.user?.id); const userChannels = config.channels.filter((channel) => channel.scope !== "system"); const visibleConfigSections = useMemo(() => (customChannelsEnabled ? configSections : configSections.filter((section) => section.key !== "channels")) .filter((section) => section.key !== "runninghub" || runningHubPluginEnabled), [customChannelsEnabled, runningHubPluginEnabled]); const isVisibleConfigSection = (value: string | null): value is ConfigSectionKey => isConfigSection(value) && visibleConfigSections.some((section) => section.key === value); useLayoutEffect(() => { document.body.classList.add("app-user-overlays"); return () => document.body.classList.remove("app-user-overlays"); }, []); useEffect(() => { if (isVisibleConfigSection(requestedSection)) { setActiveTab(requestedSection); return; } setActiveTab((current) => visibleConfigSections.some((section) => section.key === current) ? current : customChannelsEnabled ? "channels" : "models"); }, [customChannelsEnabled, requestedSection, visibleConfigSections]); useEffect(() => { if (!userId) return; let cancelled = false; void refreshSystemChannels().catch((error) => { if (!cancelled) message.warning(error instanceof Error ? `系统模型刷新失败:${error.message}` : "系统模型刷新失败,继续使用本地缓存"); }); return () => { cancelled = true; }; }, [message, userId]); const selectSection = (section: ConfigSectionKey) => { if (section === "runninghub" && !runningHubPluginEnabled) return; setActiveTab(section); const next = new URLSearchParams(searchParams); next.set("section", section); setSearchParams(next, { replace: true }); }; const finishConfig = () => { const invalidChannel = customChannelsEnabled ? userChannels.find((channel) => channelValidationError(channel)) : undefined; if (invalidChannel) { selectSection("channels"); message.warning(`${invalidChannel.name || "未命名渠道"}:${channelValidationError(invalidChannel)}`); focusInvalidChannelField(invalidChannel); return; } const workflowReady = Boolean(runningHubPluginEnabled && config.runningHub.enabled && config.runningHub.workflowId.trim() && config.runningHub.baseUrl.trim() && config.runningHub.apiKey.trim()); if (!effectiveConfig.channels.some(isChannelReady) && !workflowReady) { selectSection(customChannelsEnabled ? "channels" : "models"); message.error(customChannelsEnabled ? (shouldPromptContinue ? "请先完成至少一个渠道的 Base URL、API Key 和模型配置" : "当前没有可用渠道,请先完成连接信息和模型配置") : "当前没有可用的系统模型,请联系管理员配置系统渠道"); return; } message.success("配置已保存,正在返回创作页面"); navigate(-1); }; const panes: Record = { channels: selectSection("models")} onOpenRunningHub={runningHubPluginEnabled ? () => selectSection("runninghub") : undefined} />, models: (

模型选择

按领域选择默认模型;模型能力与请求协议在渠道“模型与能力”中配置。

updateConfig(key, model)} />
), runninghub: , preferences: (

生成偏好

设置新建生成任务时使用的初始值,节点内仍可单独覆盖。

画布生成

用于新建图片生成任务,节点内仍可单独覆盖。

updateConfig("canvasImageCount", normalizeImageCount(String(value ?? defaultConfig.canvasImageCount)))} /> } controlClassName="w-[200px]" />
), prompts: , "agent-memory": (

Agent 记忆

只属于你。Agent 记下的先待批准;手动添加立刻生效。可导入导出,也可用文本模型压缩相近条目。

), diagnostics: , storage: (
), }; return (
{shouldPromptContinue ? (
) : null}
{panes[activeTab]}
); } function SettingsPane({ children, fill = false }: { children: ReactNode; fill?: boolean }) { return
{children}
; } function normalizeImageCount(value: string) { return String(Math.max(1, Math.min(15, Math.floor(Math.abs(Number(value)) || Number(defaultConfig.canvasImageCount))))); }