import { Candy, CheckCircle2, FlaskConical, Power, Sparkles, WandSparkles } from "lucide-react"; import { type ChangeEvent, type FormEvent, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { AiProviderResponse, AiProviderStatus } from "../api/types"; import { EmptyState } from "../components/StateViews"; import { aiProviderToForm, emptyAiProviderForm, type AiProviderActionState, type AiProviderFilter, type AiProviderFormState, summarizeAiProviders, type AiProviderViewState } from "../contracts/aiProviders"; import { aiProviderCreateRequestFromForm, aiProviderUpdateRequestFromForm } from "../schemas/aiProviders"; import { cx } from "../utils/classes"; const seedProviders: AiProviderResponse[] = [ { id: "ai.openai", name: "OpenAI Relay", kind: "openai-compatible", baseUrl: "https://relay.example.test/v1", apiKeyRef: "secret://providers/openai", models: ["gpt-4.1", "gpt-4.1-mini"], defaultModel: "gpt-4.1-mini", relayMode: "relay", timeoutMs: 30000, status: "active", redactionPolicy: "default" }, { id: "ai.local", name: "Local Ollama", kind: "ollama", baseUrl: "http://127.0.0.1:11434/v1", apiKeyRef: "env://OLLAMA_API_KEY", models: ["llama3.1", "qwen2.5-coder"], defaultModel: "qwen2.5-coder", relayMode: "local", timeoutMs: 20000, status: "disabled", redactionPolicy: "local" } ]; export function AiProvidersPage() { const [providers, setProviders] = useState(seedProviders); const [selectedId, setSelectedId] = useState(seedProviders[0]?.id ?? ""); const [filter, setFilter] = useState("all"); const [form, setForm] = useState(() => aiProviderToForm(seedProviders[0])); const [viewState, setViewState] = useState("local"); const [action, setAction] = useState(null); useEffect(() => { let cancelled = false; platformApiClient .listAiProviders() .then((response) => { if (cancelled || response.items.length === 0) { return; } setProviders(response.items); setSelectedId(response.items[0].id); setForm(aiProviderToForm(response.items[0])); setViewState("api"); }) .catch(() => { if (!cancelled) { setViewState("local"); } }); return () => { cancelled = true; }; }, []); const metrics = useMemo(() => summarizeAiProviders(providers), [providers]); const filteredProviders = useMemo( () => providers.filter((provider) => filter === "all" || provider.status === filter), [filter, providers] ); const selectedProvider = providers.find((provider) => provider.id === selectedId); function updateForm(key: K, value: AiProviderFormState[K]) { setForm((current) => ({ ...current, [key]: value })); } function handleInput(event: ChangeEvent) { updateForm(event.target.name as keyof AiProviderFormState, event.target.value); } function selectProvider(provider: AiProviderResponse) { setSelectedId(provider.id); setForm(aiProviderToForm(provider)); setAction(null); } function startCreate() { setSelectedId(""); setForm(emptyAiProviderForm()); setAction(null); } async function handleSubmit(event: FormEvent) { event.preventDefault(); setViewState("saving"); const existing = providers.some((provider) => provider.id === form.id.trim()); try { const saved = existing ? await platformApiClient.updateAiProvider(form.id.trim(), aiProviderUpdateRequestFromForm(form)) : await platformApiClient.createAiProvider(aiProviderCreateRequestFromForm(form)); upsertProvider(saved); setSelectedId(saved.id); setForm(aiProviderToForm(saved)); setViewState("api"); setAction({ providerId: saved.id, label: "save", success: true, message: "saved" }); } catch { const local = localProviderFromForm(form, existing ? selectedProvider?.status ?? "active" : "active"); upsertProvider(local); setSelectedId(local.id); setForm(aiProviderToForm(local)); setViewState("local"); setAction({ providerId: local.id, label: "save", success: true, message: "saved locally" }); } } async function handleStatus(provider: AiProviderResponse) { const nextStatus: Extract = provider.status === "active" ? "disabled" : "active"; try { const updated = await platformApiClient.setAiProviderStatus(provider.id, { status: nextStatus }); upsertProvider(updated); setAction({ providerId: provider.id, label: "status", success: true, message: updated.status }); } catch { const updated = { ...provider, status: nextStatus }; upsertProvider(updated); setAction({ providerId: provider.id, label: "status", success: true, message: nextStatus }); } } async function handleTest(provider: AiProviderResponse) { try { const result = await platformApiClient.testAiProvider(provider.id); setAction({ providerId: provider.id, label: "test", success: result.success, message: result.message }); } catch { const success = provider.status === "active"; setAction({ providerId: provider.id, label: "test", success, message: success ? "metadata validation passed" : "provider must be active" }); } } async function handleModels(provider: AiProviderResponse) { try { const result = await platformApiClient.listAiProviderModels(provider.id); setAction({ providerId: provider.id, label: "models", success: true, message: `${result.models.length}` }); } catch { setAction({ providerId: provider.id, label: "models", success: true, message: `${provider.models.length}` }); } } function upsertProvider(provider: AiProviderResponse) { setProviders((current) => { const exists = current.some((item) => item.id === provider.id); return exists ? current.map((item) => (item.id === provider.id ? provider : item)) : [...current, provider]; }); } return (

模型

AI 提供商管理

{viewState === "api" ? "已连接" : viewState === "saving" ? "保存中" : "本地视图"}
提供商 {metrics.total}
启用 {metrics.active}
模型 {metrics.models}
{(["all", "active", "disabled", "error"] as AiProviderFilter[]).map((item) => ( ))}
{filteredProviders.map((provider) => ( ))}
名称 状态 类型 模式 模型 密钥引用 操作
{provider.id} {statusLabel(provider.status)} {provider.kind} {provider.relayMode} {provider.models.length} {provider.apiKeyRef}
{filteredProviders.length === 0 && ( )}
void handleSubmit(event)}>

{selectedProvider ? "编辑提供商" : "新增提供商"}

{action && ( {action.message} )}
); } function localProviderFromForm(form: AiProviderFormState, status: AiProviderStatus): AiProviderResponse { const request = aiProviderCreateRequestFromForm(form); return { ...request, defaultModel: request.defaultModel, status }; } function filterLabel(filter: AiProviderFilter): string { switch (filter) { case "active": return "启用"; case "disabled": return "停用"; case "error": return "错误"; default: return "全部"; } } function statusLabel(status: AiProviderStatus): string { switch (status) { case "active": return "启用"; case "disabled": return "停用"; default: return "错误"; } }