"use client"; import { CheckCircle2, KeyRound, PlugZap, Route, Save, XCircle, } from "lucide-react"; import { useState, type ReactNode } from "react"; import { useToast } from "@/components/Toast"; import { KimiCodeOauth } from "@/components/settings/KimiCodeOauth"; import { Badge } from "@/components/ui/Badge"; import { Button } from "@/components/ui/Button"; import { EmptyState } from "@/components/ui/EmptyState"; import { SectionHeader } from "@/components/ui/SectionHeader"; import { SegmentedControl } from "@/components/ui/SegmentedControl"; import { Select } from "@/components/ui/Select"; import { StatusNote } from "@/components/ui/StatusNote"; import { TextInput } from "@/components/ui/TextInput"; import { api } from "@/lib/api/client"; import { cardClass } from "@/lib/ui/variants"; import { API_KEY_PROVIDERS, KNOWN_PROVIDERS, TIER_LABELS, applyProviderChange, draftsToRoutingInput, toRoutingDrafts, type RoutingDraft, } from "@/lib/settings/providers"; import type { CapabilitiesView, ProvidersResponse, } from "@/lib/api/types"; interface ProvidersSettingsProps { initial: ProvidersResponse; // Kimi Code OAuth 连接态(GET .../oauth/status,进页 Server Component 取)。 kimiOauth: { connected: boolean; expiresAt: string | null }; } interface TestResult { ok: boolean; capabilities: CapabilitiesView; } type SettingsSection = "routing" | "oauth" | "keys"; const SECTION_OPTIONS: Array<{ value: SettingsSection; label: string }> = [ { value: "routing", label: "路由" }, { value: "oauth", label: "OAuth" }, { value: "keys", label: "API Key" }, ]; // 设置页主体(UX §6.10):档位路由(可编辑)+ API-key 凭据行 + Kimi Code OAuth 连接区。 export function ProvidersSettings({ initial, kimiOauth, }: ProvidersSettingsProps) { const toast = useToast(); const [activeSection, setActiveSection] = useState("routing"); const [providers, setProviders] = useState(initial.providers ?? []); const [routing, setRouting] = useState( toRoutingDrafts(initial.tier_routing ?? []), ); const [savingRouting, setSavingRouting] = useState(false); 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 updateRouting = (tier: string, providerId: string): void => { setRouting((prev) => prev.map((d) => d.tier === tier ? applyProviderChange(d, providerId) : d, ), ); }; const updateRoutingModel = (tier: string, model: string): void => { setRouting((prev) => prev.map((d) => (d.tier === tier ? { ...d, model } : d)), ); }; const saveRouting = async (): Promise => { setSavingRouting(true); try { const { data, error } = await api.PUT("/settings/providers", { body: { tier_routing: draftsToRoutingInput(routing) }, }); if (error || !data) { toast("保存档位路由失败,请重试", "error"); return; } setProviders(data.providers ?? []); setRouting(toRoutingDrafts(data.tier_routing ?? [])); toast("档位路由已保存", "success"); } catch { toast("保存档位路由失败,请检查后端连接", "error"); } finally { setSavingRouting(false); } }; const saveCredential = async (providerId: string): Promise => { const apiKey = (drafts[providerId] ?? "").trim(); if (!apiKey) { toast("请先输入 API Key", "error"); return; } setSavingId(providerId); try { const { data, error } = await api.PUT("/settings/providers", { body: { credentials: [{ provider: providerId, api_key: apiKey }] }, }); if (error || !data) { toast("保存凭据失败,请重试", "error"); return; } setProviders(data.providers ?? []); setDrafts((prev) => ({ ...prev, [providerId]: "" })); toast("凭据已保存", "success"); } catch { toast("保存凭据失败,请检查后端连接", "error"); } finally { setSavingId(null); } }; const testConnection = async (providerId: string): Promise => { setTestingId(providerId); try { const { data, error } = await api.POST("/settings/providers/test", { body: { provider: providerId }, }); if (error || !data) { toast("测试连接失败", "error"); return; } setResults((prev) => ({ ...prev, [providerId]: { ok: data.ok, capabilities: data.capabilities }, })); toast( data.ok ? "连接成功" : "连接未通过", data.ok ? "success" : "error", ); } catch { toast("测试连接失败,请检查后端连接", "error"); } finally { setTestingId(null); } }; return (
{activeSection === "routing" ? ( 写章、审稿和摘要提炼会分别读取对应档位;未完整填写的行不会覆盖现有路由。
    {routing.map((row) => (
  • {TIER_LABELS[row.tier] ?? row.tier} updateRoutingModel(row.tier, e.target.value) } placeholder="model" className="font-mono" />
  • ))}
) : null} {activeSection === "oauth" ? ( ) : null} {activeSection === "keys" ? (
已保存 {providers.length}
可配置{" "} {API_KEY_PROVIDERS.length}
测试结果{" "} {Object.keys(results).length}
{providers.length === 0 ? ( ) : null}
    {API_KEY_PROVIDERS.map((prov) => { const masked = maskedFor(prov.id); const result = results[prov.id]; return (
  • {masked ?? "未配置"} setDrafts((prev) => ({ ...prev, [prov.id]: e.target.value, })) } placeholder={ masked ? "输入新 Key 以更新" : "输入 API Key" } />

    {masked ? "已保存脱敏凭据;输入新 Key 可覆盖更新。" : "保存后再测试连接,成功后即可在档位路由中使用。"}

    {result ? (
    {result.ok ? (
    ) : null}
  • ); })}
) : null}
); } function SettingsPanel({ children }: { children: ReactNode }) { return
{children}
; } interface SettingsNavButtonProps { active: boolean; icon: ReactNode; label: string; detail: string; onClick: () => void; } function SettingsNavButton({ active, icon, label, detail, onClick, }: SettingsNavButtonProps) { return ( ); } 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} ))}
); }