修改配置编辑不了的问题

This commit is contained in:
andy
2026-08-18 12:41:39 +08:00
parent 22b504e53e
commit 2a2f78c81e
42 changed files with 1082 additions and 178 deletions

View File

@@ -2,6 +2,7 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { Loader2, RefreshCw, Save } from "lucide-react";
import { selectDirtySettingsValues } from "@/lib/settings-client";
import { crossfadeIn, pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion";
type SettingsField = {
@@ -23,7 +24,7 @@ type SettingsGroup = {
};
type SettingsPayload = {
restartRequired?: boolean;
restartRequired?: boolean;
services: {
visual: boolean;
evolink: boolean;
@@ -57,6 +58,7 @@ type SettingsPayload = {
export function SettingsPanel() {
const [payload, setPayload] = useState<SettingsPayload | null>(null);
const [values, setValues] = useState<Record<string, string>>({});
const [dirtyKeys, setDirtyKeys] = useState<Set<string>>(() => new Set());
const [activeTab, setActiveTab] = useState("status");
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -92,12 +94,14 @@ export function SettingsPanel() {
async function loadSettings() {
setLoading(true);
setError(null);
setMessage(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));
setDirtyKeys(new Set());
if (activeTab !== "status" && !nextPayload.groups.some((group: SettingsGroup) => group.id === activeTab)) {
setActiveTab(nextPayload.groups[0]?.id || "status");
}
@@ -109,6 +113,7 @@ export function SettingsPanel() {
}
async function saveSettings() {
if (dirtyKeys.size === 0) return;
setSaving(true);
setError(null);
setMessage(null);
@@ -116,13 +121,14 @@ export function SettingsPanel() {
const response = await fetch("/api/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ values })
body: JSON.stringify({ values: selectDirtySettingsValues(values, dirtyKeys) })
});
const nextPayload = await response.json();
if (!response.ok) throw new Error(nextPayload.error || "保存设置失败");
setPayload(nextPayload);
setValues(valuesFromPayload(nextPayload));
setMessage(nextPayload.restartRequired ? "配置已保存,重启 Go 服务后生效。" : "配置已保存并应用到当前服务。");
setDirtyKeys(new Set());
setMessage(nextPayload.restartRequired ? "配置已保存,重启 Go 服务后生效。" : "配置已保存并应用到当前服务。");
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
@@ -130,6 +136,15 @@ export function SettingsPanel() {
}
}
function updateValue(key: string, value: string) {
setValues((items) => ({ ...items, [key]: value }));
setDirtyKeys((keys) => {
const next = new Set(keys);
next.add(key);
return next;
});
}
if (loading && !payload) {
return (
<section className="panel settings-loading">
@@ -164,7 +179,7 @@ export function SettingsPanel() {
className="button primary"
type="button"
onClick={saveSettings}
disabled={saving || (!fields.length && !payload?.engineAssignments.length)}
disabled={saving || dirtyKeys.size === 0 || (!fields.length && !payload?.engineAssignments.length)}
>
{saving ? <Loader2 className="spin" size={18} /> : <Save size={18} />}
保存
@@ -194,7 +209,7 @@ export function SettingsPanel() {
{field.secret ? <em>{field.configured ? "已配置" : "未配置"}</em> : null}
</span>
{field.type === "select" ? (
<select value={values[field.key] ?? field.value} onChange={(event) => setValues((items) => ({ ...items, [field.key]: event.target.value }))}>
<select value={values[field.key] ?? field.value} onChange={(event) => updateValue(field.key, event.target.value)}>
{(field.options || []).map((option) => (
<option value={option.value} key={option.value}>{option.label}</option>
))}
@@ -204,7 +219,7 @@ export function SettingsPanel() {
type={field.secret ? "password" : field.type === "number" ? "number" : "text"}
value={values[field.key] ?? ""}
placeholder={field.secret && field.configured ? "留空保留当前密钥" : field.description || ""}
onChange={(event) => setValues((items) => ({ ...items, [field.key]: event.target.value }))}
onChange={(event) => updateValue(field.key, event.target.value)}
/>
)}
<small>{field.key}</small>
@@ -248,7 +263,7 @@ export function SettingsPanel() {
<select
className="settings-engine-control"
value={values[assignment.field.key] ?? assignment.field.value}
onChange={(event) => setValues((items) => ({ ...items, [assignment.field!.key]: event.target.value }))}
onChange={(event) => updateValue(assignment.field!.key, event.target.value)}
aria-label={`${assignment.label}创作引擎`}
>
{(assignment.field.options || []).map((option) => (