"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { Loader2, RefreshCw, Save } from "lucide-react"; import { crossfadeIn, pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion"; type SettingsField = { key: string; label: string; description?: string; secret?: boolean; type?: "text" | "password" | "number" | "select"; options?: Array<{ label: string; value: string }>; value: string; configured: boolean; }; type SettingsGroup = { id: string; title: string; description: string; fields: SettingsField[]; }; type SettingsPayload = { envPath: string; modes: { visual: string; evolink: string; seedance: string; auth: string; data: string; }; capabilities: Array<{ id: string; label: string; reqKey: string; enabled: boolean; engine?: string; engineLabel?: string; }>; engineAssignments: Array<{ id: string; label: string; engine: string; engineLabel: string; mode: string; modeLabel: string; reqKey: string; configurable: boolean; field?: SettingsField; }>; groups: SettingsGroup[]; }; export function SettingsPanel() { const [payload, setPayload] = useState(null); const [values, setValues] = useState>({}); const [activeTab, setActiveTab] = useState("status"); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const settingsRef = useRef(null); const contentRef = useRef(null); const feedbackRef = useRef(null); const fields = useMemo(() => payload?.groups.flatMap((group) => group.fields) || [], [payload]); const activeGroup = useMemo(() => payload?.groups.find((group) => group.id === activeTab), [activeTab, payload]); const tabs = useMemo(() => [ { id: "status", label: "状态" }, ...(payload?.groups.map((group) => ({ id: group.id, label: shortGroupLabel(group.id, group.title) })) || []) ], [payload]); useEffect(() => { void loadSettings(); }, []); useEffect(() => { return runScopedMotion(settingsRef, (scope) => revealChildren(scope)); }, []); useEffect(() => { pulseFeedback(feedbackRef.current); }, [error, message]); useEffect(() => { crossfadeIn(contentRef.current); }, [activeTab]); async function loadSettings() { setLoading(true); setError(null); try { const response = await fetch("/api/settings", { cache: "no-store" }); const nextPayload = await response.json(); if (!response.ok) throw new Error(nextPayload.error || "读取设置失败"); setPayload(nextPayload); setValues(valuesFromPayload(nextPayload)); if (activeTab !== "status" && !nextPayload.groups.some((group: SettingsGroup) => group.id === activeTab)) { setActiveTab(nextPayload.groups[0]?.id || "status"); } } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } } async function saveSettings() { setSaving(true); setError(null); setMessage(null); try { const response = await fetch("/api/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ values }) }); const nextPayload = await response.json(); if (!response.ok) throw new Error(nextPayload.error || "保存设置失败"); setPayload(nextPayload); setValues(valuesFromPayload(nextPayload)); setMessage("配置已保存并应用到当前服务。"); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setSaving(false); } } if (loading && !payload) { return (
正在读取配置
); } return (

服务设置

{tabs.map((tab) => ( ))}
{error || message ? (
{error ?
{error}
: null} {message ?
{message}
: null}
) : null} {activeGroup ? (

{activeGroup.title}

{activeGroup.fields.map((field) => ( ))}
) : null} {activeTab === "status" ? (

功能引擎

功能 引擎 接口 模型 / Key
{payload?.engineAssignments.map((assignment) => (
{assignment.label} {assignment.id}
{assignment.field ? ( ) : ( {assignment.engineLabel} )} {assignment.modeLabel}
{assignment.reqKey}
))}
) : null}
); } function valuesFromPayload(payload: SettingsPayload) { const nextValues: Record = {}; for (const field of payload.groups.flatMap((group) => group.fields)) { nextValues[field.key] = field.secret ? "" : field.value; } for (const assignment of payload.engineAssignments) { if (assignment.field) nextValues[assignment.field.key] = assignment.field.value; } return nextValues; } function ServiceBadge({ label, value, ready }: { label: string; value: string; ready: boolean }) { return (
{label} {value}
); } function authModeLabel(mode?: string) { if (mode === "configured") return "已启用"; if (mode === "missing") return "待配置"; return "未启用"; } function shortGroupLabel(id: string, title: string) { if (id === "auth") return "登录"; if (id === "visual") return "图片"; if (id === "evolink") return "EvoLink"; if (id === "seedance") return "视频"; if (id === "oss") return "OSS"; return title; }