"use client"; import { useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import { KNOWN_PROVIDERS, TIER_LABELS } from "@/lib/settings/providers"; import type { CapabilitiesView, ProvidersResponse, TierRoutingView, } from "@/lib/api/types"; interface ProvidersSettingsProps { initial: ProvidersResponse; } interface TestResult { ok: boolean; capabilities: CapabilitiesView; } // 设置页主体(UX §6.10):档位路由(只读展示)+ 凭据行(脱敏 / 添加更新 / 测试连接)。 export function ProvidersSettings({ initial }: ProvidersSettingsProps) { const toast = useToast(); const [providers, setProviders] = useState(initial.providers ?? []); const tierRouting: TierRoutingView[] = initial.tier_routing ?? []; const [drafts, setDrafts] = useState>({}); const [savingId, setSavingId] = useState(null); const [testingId, setTestingId] = useState(null); const [results, setResults] = useState>({}); const maskedFor = (id: string): string | null => providers.find((p) => p.provider === id)?.masked_key ?? null; const saveCredential = async (providerId: string): Promise => { const apiKey = (drafts[providerId] ?? "").trim(); if (!apiKey) { toast("请先输入 API Key", "error"); return; } setSavingId(providerId); const { data, error } = await api.PUT("/settings/providers", { body: { credentials: [{ provider: providerId, api_key: apiKey }] }, }); setSavingId(null); if (error || !data) { toast("保存凭据失败,请重试", "error"); return; } setProviders(data.providers ?? []); setDrafts((prev) => ({ ...prev, [providerId]: "" })); toast("凭据已保存", "success"); }; const testConnection = async (providerId: string): Promise => { setTestingId(providerId); const { data, error } = await api.POST("/settings/providers/test", { body: { provider: providerId }, }); setTestingId(null); if (error || !data) { toast("测试连接失败", "error"); return; } setResults((prev) => ({ ...prev, [providerId]: { ok: data.ok, capabilities: data.capabilities }, })); toast(data.ok ? "连接成功" : "连接未通过", data.ok ? "success" : "error"); }; return (

能力档位路由

{tierRouting.length === 0 ? (

尚未配置档位路由。连接提供商后将使用默认路由。

) : (
    {tierRouting.map((t) => (
  • {TIER_LABELS[t.tier] ?? t.tier} {t.provider} · {t.model} {t.fallback && t.fallback.length > 0 ? ( 回退 {t.fallback.join(", ")} ) : null}
  • ))}
)}

提供商凭据

{providers.length === 0 ? (

至少连接一个提供商即可开始写作(求质量选 Anthropic / 求性价比选 DeepSeek)。

) : null}
    {KNOWN_PROVIDERS.map((prov) => { const masked = maskedFor(prov.id); const result = results[prov.id]; return (
  • {result ? (
    {result.ok ? "✓ 已连接" : "✗ 未连接"}
    ) : null}
  • ); })}
); } function CapabilityBadges({ caps }: { caps: CapabilitiesView }) { const badges: { on: boolean; label: string }[] = [ { on: caps.structured_output, label: "结构化" }, { on: caps.prefix_cache, label: "前缀缓存" }, { on: caps.thinking, label: "思考" }, ]; return (
{badges.map((b) => ( {b.label} ))}
); }