/** * Admin 后台 - 专家与终裁独立 LLM 配置卡片 * * 每个角色(5 专家 + 终裁)可独立覆盖 模型 / 接口地址 / API Key; * 留空字段不改动,「恢复继承」删除该角色全部覆盖。 */ import { useCallback, useEffect, useState } from 'react' import { fetchLLMAgents, updateSetting, clearSetting } from './dal' import type { LLMAgentConfig } from './types' import { Card, CardBody, CardHeader, Badge, Alert, Spinner, SkeletonBlock } from './components' type FieldKey = 'model' | 'base_url' | 'api_key' const FIELD_META: { key: FieldKey; label: string; sensitive: boolean; hint: string }[] = [ { key: 'model', label: '模型', sensitive: false, hint: '留空保持现状;未覆盖时继承默认' }, { key: 'base_url', label: '接口地址', sensitive: false, hint: '留空保持现状;未覆盖时继承全局' }, { key: 'api_key', label: 'API Key', sensitive: true, hint: '留空保持现状;未覆盖时继承全局' }, ] const KEY_BY_FIELD: Record string> = { model: id => `AGENT_${id.toUpperCase()}_MODEL`, base_url: id => `AGENT_${id.toUpperCase()}_BASE_URL`, api_key: id => `AGENT_${id.toUpperCase()}_API_KEY`, } export default function AgentLLMCard() { const [agents, setAgents] = useState([]) const [loading, setLoading] = useState(true) const [expandedId, setExpandedId] = useState(null) const [form, setForm] = useState>({ model: '', base_url: '', api_key: '' }) const [busy, setBusy] = useState(false) const [notice, setNotice] = useState<{ ok: boolean; text: string } | null>(null) const load = useCallback(async () => { setLoading(true) try { setAgents(await fetchLLMAgents()) } catch { setAgents([]) } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) function toggleExpand(agent: LLMAgentConfig) { if (expandedId === agent.id) { setExpandedId(null) return } setExpandedId(agent.id) setNotice(null) // 预填非敏感覆盖值;API Key 不回填 setForm({ model: agent.fields.model.origin === 'db' ? agent.fields.model.masked : '', base_url: agent.fields.base_url.origin === 'db' ? agent.fields.base_url.masked : '', api_key: '', }) } async function handleSave(agent: LLMAgentConfig) { setBusy(true) setNotice(null) try { const nonEmpty = (FIELD_META.filter(f => form[f.key].trim())).map(f => f) if (nonEmpty.length === 0) { setNotice({ ok: false, text: '没有需要保存的修改(全部为空)' }) return } for (const f of nonEmpty) { await updateSetting(KEY_BY_FIELD[f.key](agent.id), form[f.key].trim()) } setNotice({ ok: true, text: `${agent.label} 配置已保存,立即生效` }) setExpandedId(null) await load() } catch (err) { setNotice({ ok: false, text: err instanceof Error ? err.message.split('\n')[0] : '保存失败' }) } finally { setBusy(false) } } async function handleReset(agent: LLMAgentConfig) { setBusy(true) setNotice(null) try { for (const f of FIELD_META) { await clearSetting(KEY_BY_FIELD[f.key](agent.id)) } setNotice({ ok: true, text: `${agent.label} 已恢复继承默认` }) setExpandedId(null) await load() } catch (err) { setNotice({ ok: false, text: err instanceof Error ? err.message.split('\n')[0] : '恢复失败' }) } finally { setBusy(false) } } const hasOverride = (agent: LLMAgentConfig) => Object.values(agent.fields).some(f => f.origin === 'db') return ( {loading ? (<> 加载中) : '刷新'} } /> {loading ? (
{[1, 2, 3, 4, 5, 6].map(i => )}
) : agents.length === 0 ? (

无法加载角色配置

) : ( <> {notice && (
)} {agents.map(agent => { const expanded = expandedId === agent.id return (
{/* 行:角色名 + 生效模型 + 配置按钮 */}
{agent.label} {hasOverride(agent) && 独立配置}
{agent.effective_model}
{/* 展开的编辑表单 */} {expanded && (
{FIELD_META.map(f => { const state = agent.fields[f.key] return (
setForm(prev => ({ ...prev, [f.key]: e.target.value }))} placeholder={ f.key === 'api_key' && state.origin === 'db' ? `已覆盖(${state.masked}),留空保持不变` : f.hint } autoComplete="off" className="field w-full" />
) })}

生效模型:{agent.effective_model}

{hasOverride(agent) && ( )}
)}
) })} )}
) }