import { useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import { Pencil, Copy, Activity, Loader2, Trash2 } from "lucide-react"; import { useTranslation } from "../i18n"; import { ProviderIcon } from "./ProviderIcon"; import { UsageFooter } from "./UsageFooter"; import type { Agent, Model, Provider } from "../types"; interface ConnectivityResult { ok: boolean; latencyMs: number; statusCode?: number | null; error?: string | null; } interface TestState { status: "testing" | "ok" | "fail"; latency?: number; error?: string; } interface ProviderListProps { providers: Provider[]; defaultModel: string | null; models: Record; onEdit: (name: string) => void; onDelete: (name: string) => void; onDuplicate: (name: string) => void; onAdd: () => void; onSwitchProvider: (name: string) => void; agent: Agent; } const iconBtn = "w-8 h-8 flex items-center justify-center rounded border border-border text-content-muted hover:text-content-primary hover:bg-hover-2 focus:ring-2 focus:ring-blue-500 focus:outline-none transition-colors"; export function ProviderList({ providers, defaultModel, models, onEdit, onDelete, onDuplicate, onAdd, onSwitchProvider, agent, }: ProviderListProps) { const { t } = useTranslation(); const [testState, setTestState] = useState>({}); const handleTest = async (provider: Provider) => { setTestState((s) => ({ ...s, [provider.name]: { status: "testing" } })); try { const result = await invoke("test_connectivity", { provider }); setTestState((s) => ({ ...s, [provider.name]: { status: result.ok ? "ok" : "fail", latency: result.latencyMs, error: result.error ?? undefined, }, })); } catch (e) { setTestState((s) => ({ ...s, [provider.name]: { status: "fail", error: e instanceof Error ? e.message : String(e), }, })); } // auto-clear the inline result after a few seconds setTimeout(() => { setTestState((s) => { const next = { ...s }; delete next[provider.name]; return next; }); }, 6000); }; return (

{t("providers")}

{providers.length === 0 ? (
⊘
{t("noProviders")}
) : (
{providers.map((provider) => { const providerModels = Object.values(models).filter( (m) => m.provider === provider.name ); const defaultModelName = defaultModel ? models[defaultModel]?.model || models[defaultModel]?.display_name || defaultModel : null; const isActive = provider.active === true; const ts = testState[provider.name]; return (
onEdit(provider.name)} >

{provider.name}

{isActive && ( {t("inUse")} )} {provider.note && ( {provider.note} )}
{provider.official_url || provider.base_url || t("noUrl")} {provider.provider_type} {t("modelCount", { count: providerModels.length })}
{defaultModel && models[defaultModel]?.provider === provider.name && ( {t("defaultModel", { name: defaultModelName ?? "" })} )} {/* inline connectivity test result */} {ts && ts.status !== "testing" && ( 6000 ? "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-500/30" : "bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400 border-green-300 dark:border-green-500/30" : "bg-red-100 dark:bg-red-900/30 text-red-500 dark:text-red-400 border-red-300 dark:border-red-500/30" }`} title={ ts.status === "fail" ? ts.error || t("connectivityFail", { name: provider.name, error: "" }) : ts.latency && ts.latency > 6000 ? t("connectivitySlow", { name: provider.name, latency: ts.latency ?? 0 }) : t("connectivityOk", { name: provider.name, latency: ts.latency ?? 0 }) } > {ts.status === "ok" ? `${ts.latency}ms` : "✕"} )} {agent === "kimi_code" && ( ⓘ )} {/* icon button group */}
); })}
)}
); }