import { useEffect, useMemo, useRef, useState } from "react"; import "./admin-models.css"; interface AdminSession { csrf_token: string } interface RuntimeAvailability { available_for_new_jobs: boolean; checked_at: string; reason: string; } interface ModelConfig { config_version: number; contract_evidence_ref: string | null; contract_validation_status: "blocked" | "unverified" | "verified"; credit_cost: number; display_name: string; enabled: boolean; error_mapping_profile: Record; gateway_account_ref: string; is_default: boolean; model_id: string; prompt_max_length: number; recommendation_priority: number; reference_limits: { max_file_bytes: number; max_files: number; max_total_bytes: number }; route_profile: Record; runtime_availability: RuntimeAvailability; safety_source: string; supported_ratios: string[]; } interface ModelConfiguration { config_set_version: number; configured_default_model_id: string; models: ModelConfig[]; recommended_model_id: string | null; } async function responseJson(url: string, init?: RequestInit) { const response = await fetch(url, { credentials: "same-origin", ...init }); const body = await response.json() as T; return { body, response }; } function idempotencyKey() { return crypto.randomUUID().replaceAll("-", "") + crypto.randomUUID().replaceAll("-", ""); } function editableModel(model: ModelConfig) { const { config_version: _configVersion, contract_evidence_ref: _evidence, contract_validation_status: _status, runtime_availability: _runtime, ...editable } = model; return editable; } function runtimeLabel(runtime: RuntimeAvailability) { return runtime.available_for_new_jobs ? "可用于新任务" : `不可用 · ${runtime.reason}`; } export function AdminModelsPage() { const [session, setSession] = useState(); const [configuration, setConfiguration] = useState(); const [draft, setDraft] = useState(); const [loadingFailed, setLoadingFailed] = useState(false); const [saving, setSaving] = useState(false); const [notice, setNotice] = useState(""); const [conflicted, setConflicted] = useState(false); const priorityRefs = useRef>({}); const defaultRefs = useRef>({}); async function load() { setLoadingFailed(false); setConflicted(false); try { const [sessionResult, modelResult] = await Promise.all([ responseJson("/api/v1/admin-auth/session"), responseJson("/api/v1/models"), ]); if (!sessionResult.response.ok || !modelResult.response.ok) throw new Error("load_failed"); setSession(sessionResult.body); setConfiguration(modelResult.body); setDraft(structuredClone(modelResult.body.models)); setNotice(""); } catch { setLoadingFailed(true); } } useEffect(() => { void load(); }, []); const validation = useMemo(() => { if (!draft) return { valid: false, message: "" }; const invalidPriority = draft.find((model) => !Number.isSafeInteger(model.recommendation_priority) || model.recommendation_priority <= 0); if (invalidPriority) return { field: invalidPriority.model_id, kind: "priority" as const, message: "推荐优先级必须是正整数。", valid: false }; const duplicatePriority = draft.find((model, index) => draft.findIndex((item) => item.recommendation_priority === model.recommendation_priority) !== index); if (duplicatePriority) return { field: duplicatePriority.model_id, kind: "priority" as const, message: "推荐优先级不能重复。", valid: false }; const defaults = draft.filter((model) => model.enabled && model.is_default); if (defaults.length !== 1) return { field: draft.find((model) => model.is_default)?.model_id ?? draft[0]?.model_id, kind: "default" as const, message: "必须指定一个已启用的默认模型。", valid: false }; return { message: "", valid: true }; }, [draft]); function updateModel(modelId: string, change: Partial) { setDraft((current) => current?.map((model) => model.model_id === modelId ? { ...model, ...change } : model)); setNotice(""); } function chooseDefault(modelId: string) { setDraft((current) => current?.map((model) => ({ ...model, is_default: model.model_id === modelId }))); setNotice(""); } async function save() { if (!configuration || !draft || !session || !validation.valid || saving || conflicted) return; setSaving(true); setNotice(""); try { const { body, response } = await responseJson("/api/v1/admin/models/configuration", { body: JSON.stringify({ expected_config_set_version: configuration.config_set_version, models: draft.map(editableModel) }), headers: { "Content-Type": "application/json", "Idempotency-Key": idempotencyKey(), "X-CSRF-Token": session.csrf_token, }, method: "PUT", }); if (!response.ok) { const code = body.error?.code; if (code === "MODEL_CONFIG_VERSION_CONFLICT") { setConflicted(true); setNotice("配置已被其他管理员更新,请刷新最新配置后重新编辑。"); } else if (code === "MODEL_RECOMMENDATION_PRIORITY_INVALID" || code === "MODEL_RECOMMENDATION_PRIORITY_CONFLICT") { const modelId = validation.field ?? draft[0]?.model_id; if (modelId) priorityRefs.current[modelId]?.focus(); setNotice("请修正推荐优先级后再保存。"); } else if (code === "MODEL_DEFAULT_REPLACEMENT_REQUIRED" || code === "MODEL_DEFAULT_REPLACEMENT_INVALID") { const modelId = validation.field ?? draft[0]?.model_id; if (modelId) defaultRefs.current[modelId]?.focus(); setNotice("请指定一个已启用的替代默认模型。"); } else setNotice("操作未完成。"); return; } setConfiguration(body); setDraft(structuredClone(body.models)); setNotice("模型配置已原子保存并记录审计。"); } catch { setNotice("操作未完成。"); } finally { setSaving(false); } } return (
DADA ADMIN

MODEL OPERATIONS

模型配置

{configuration ? 配置集合 v{configuration.config_set_version} : null}
{!configuration && !loadingFailed ?
: null} {loadingFailed ?

模型配置暂时无法读取。

: null} {configuration && draft ? ( <>
配置默认 {configuration.configured_default_model_id} 当前推荐 {configuration.recommended_model_id ?? "无"} 运行时可用 {configuration.models.filter((model) => model.runtime_availability.available_for_new_jobs).length} / 3
{draft.map((model) => ( ))}
模型启用默认推荐优先级契约运行时可用当前推荐能力配置版本
{model.display_name}{model.model_id} updateModel(model.model_id, { enabled: event.target.checked })} type="checkbox" /> chooseDefault(model.model_id)} ref={(node) => { defaultRefs.current[model.model_id] = node; }} type="radio" /> updateModel(model.model_id, { recommendation_priority: Number(event.target.value) })} ref={(node) => { priorityRefs.current[model.model_id] = node; }} type="number" value={model.recommendation_priority} /> {model.contract_validation_status} {runtimeLabel(model.runtime_availability)}{new Date(model.runtime_availability.checked_at).toLocaleString("zh-CN")} {configuration.recommended_model_id === model.model_id ? "是" : "否"} {model.credit_cost} 点{model.supported_ratios.join(" / ")} · 最多 {model.reference_limits.max_files} 张参考图 v{model.config_version}
) : null}
); }