import { useEffect, useState } from "react"; import { api, type ProxySettings, type ProxySettingsInput, type StatisticsStorage, type StatisticsStorageScope, type TabSettings } from "../../shared/api"; import { PageContent } from "../../shell/layout/PageContent"; import { LegacyModelImport } from "../models/LegacyModelImport"; import { AppLifecycleSettingsCard } from "./AppLifecycleSettingsCard"; import { ProxySettingsCard } from "./ProxySettingsCard"; import { TabSettingsCard } from "./TabSettingsCard"; import { Button } from "../../shared/ui/Button"; import { Checkbox } from "../../shared/ui/Checkbox"; import { ConfirmDialog } from "../../shared/ui/ConfirmDialog"; import { FormField, TextInput } from "../../shared/ui/FormControls"; import { Select } from "../../shared/ui/Select"; import { TitledCard } from "../../shared/ui/TitledCard"; import { setLocalePreference, useI18n, type LocalePreference } from "../../i18n/store"; import { useMessage } from "../../shared/ui/message"; import { appStore, useAppStore } from "../../shared/store/appStore"; import { themeOptions } from "../../shared/theme/theme"; import styles from "./SettingsPage.module.scss"; export function SettingsPage() { const { detailed, ports, theme } = useAppStore(); const { preference, locale } = useI18n(); const message = useMessage(); const [proxyPort, setProxyPort] = useState(String(ports.proxy_port)); const [servicePort, setServicePort] = useState(String(ports.service_port)); const [editingPorts, setEditingPorts] = useState(false); const [savingPorts, setSavingPorts] = useState(false); const [storage, setStorage] = useState(null); const [confirmClear, setConfirmClear] = useState(false); const [clearScope, setClearScope] = useState("details"); const [clearing, setClearing] = useState(false); const [outboundProxy, setOutboundProxy] = useState(null); const [proxyDraft, setProxyDraft] = useState({ mode: "system", address: "", auth_enabled: false, username: "", password: "" }); const [editingProxy, setEditingProxy] = useState(false); const [savingProxy, setSavingProxy] = useState(false); const [tabSettings, setTabSettings] = useState(null); const [tabDraft, setTabDraft] = useState({ mode: "public", address: "" }); const [editingTab, setEditingTab] = useState(false); const [savingTab, setSavingTab] = useState(false); useEffect(() => { void Promise.all([api.statisticsStorage(), api.proxySettings(), api.tabSettings()]).then(([nextStorage, nextProxy, nextTab]) => { setStorage(nextStorage); setOutboundProxy(nextProxy); setProxyDraft({ mode: nextProxy.mode, address: nextProxy.address, auth_enabled: nextProxy.auth_enabled, username: nextProxy.username, password: "" }); setTabSettings(nextTab); setTabDraft(nextTab); }).catch((cause) => message(cause instanceof Error ? cause.message : String(cause))); }, [message]); useEffect(() => { setProxyPort(String(ports.proxy_port)); setServicePort(String(ports.service_port)); }, [ports.proxy_port, ports.service_port]); const parsePort = (value: string, label: string) => { const port = Number(value); if (!Number.isInteger(port) || port < 0 || port > 65_535) { throw new Error(`${label}${t("必须是 0–65535 之间的整数")}`); } return port; }; const savePorts = async () => { try { const next = { proxy_port: parsePort(proxyPort, t("代理端口")), service_port: parsePort(servicePort, t("服务端口")), }; setSavingPorts(true); if (await appStore.updatePorts(next)) { setEditingPorts(false); message(t("端口设置已保存,重启软件后生效"), { duration: 4_000 }); } } catch (cause) { message(cause instanceof Error ? cause.message : String(cause)); } finally { setSavingPorts(false); } }; const editPorts = () => { setProxyPort(String(ports.proxy_port)); setServicePort(String(ports.service_port)); setEditingPorts(true); }; const cancelPortEdit = () => { setProxyPort(String(ports.proxy_port)); setServicePort(String(ports.service_port)); setEditingPorts(false); }; const clearStorage = async () => { try { setClearing(true); setStorage(await api.clearStatisticsStorage(clearScope)); setConfirmClear(false); await appStore.refresh(); message(clearScope === "all" ? t("全部统计数据已清理") : t("详细记录已清理")); } catch (cause) { message(cause instanceof Error ? cause.message : String(cause)); } finally { setClearing(false); } }; const editProxy = () => { if (!outboundProxy) return; setProxyDraft({ mode: outboundProxy.mode, address: outboundProxy.address, auth_enabled: outboundProxy.auth_enabled, username: outboundProxy.username, password: "" }); setEditingProxy(true); }; const cancelProxyEdit = () => { if (outboundProxy) { setProxyDraft({ mode: outboundProxy.mode, address: outboundProxy.address, auth_enabled: outboundProxy.auth_enabled, username: outboundProxy.username, password: "" }); } setEditingProxy(false); }; const saveProxy = async () => { try { setSavingProxy(true); const saved = await api.setProxySettings({ ...proxyDraft, password: proxyDraft.password || undefined }); setOutboundProxy(saved); setProxyDraft({ mode: saved.mode, address: saved.address, auth_enabled: saved.auth_enabled, username: saved.username, password: "" }); setEditingProxy(false); message(t("代理设置已保存")); } catch (cause) { message(cause instanceof Error ? cause.message : String(cause)); } finally { setSavingProxy(false); } }; const editTab = () => { if (!tabSettings) return; setTabDraft(tabSettings); setEditingTab(true); }; const cancelTabEdit = () => { if (tabSettings) setTabDraft(tabSettings); setEditingTab(false); }; const saveTab = async () => { try { if (tabDraft.mode === "custom" && !tabDraft.address.trim()) throw new Error(t("TAB 服务地址不能为空")); setSavingTab(true); const saved = await api.setTabSettings({ ...tabDraft, address: tabDraft.address.trim() }); setTabSettings(saved); setTabDraft(saved); setEditingTab(false); message(t("TAB 设置已保存")); } catch (cause) { message(cause instanceof Error ? cause.message : String(cause)); } finally { setSavingTab(false); } }; const formatBytes = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; const units = ["KB", "MB", "GB", "TB"]; let value = bytes / 1024; let unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`; }; const clearTitle = clearScope === "all" ? t("确定要清理全部统计数据吗?") : t("确定要清理详细记录吗?"); const clearDescription = clearScope === "all" ? t("所有调用汇总、详细内容和追踪记录都会被删除。模型配置、CA 和应用设置不会受到影响,此操作无法撤销。") : t("仅删除请求、响应和追踪附件等详细内容,保留调用汇总、统计指标和配置。"); const content = (
{t("详细模式")} {t("额外保存完整请求和流响应;默认只保存时间、状态与用量。")}
void appStore.updateDetailed(checked)} />
) : ( )}>
{editingPorts ? <> setProxyPort(event.target.value)} /> setServicePort(event.target.value)} /> : <>
{t("代理端口")}{ports.proxy_port}
{t("服务端口")}{ports.service_port}
}
{t( "端口被占用时会自动选择新的随机端口并保存。修改后需要重启软件才会生效。", )}
void saveProxy()} /> void saveTab()} /> {({ busy, previewing, open }) =>
{t("旧版配置")} {t("从本机旧版配置读取模型;确认前会显示新增和已存在的模型。")}
}
{t("界面语言")} {t("默认跟随操作系统;不支持的系统语言使用英文。当前:{language}", { language: locale === "zh-CN" ? "简体中文" : "English" })}
setClearScope(value as StatisticsStorageScope)} /> {clearDescription}
); return ; }