import React, { useEffect, useState } from "react"; import { Provider } from "../types"; import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react"; import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles"; import { AppType } from "../lib/tauri-api"; import { applyProviderToVSCode, detectApplied, normalizeBaseUrl, } from "../utils/vscodeSettings"; import { getCodexBaseUrl } from "../utils/providerConfigUtils"; import { useVSCodeAutoSync } from "../hooks/useVSCodeAutoSync"; // 不再在列表中显示分类徽章,避免造成困惑 interface ProviderListProps { providers: Record; currentProviderId: string; onSwitch: (id: string) => void; onDelete: (id: string) => void; onEdit: (id: string) => void; appType?: AppType; onNotify?: ( message: string, type: "success" | "error", duration?: number ) => void; } const ProviderList: React.FC = ({ providers, currentProviderId, onSwitch, onDelete, onEdit, appType, onNotify, }) => { // 提取API地址(兼容不同供应商配置:Claude env / Codex TOML) const getApiUrl = (provider: Provider): string => { try { const cfg = provider.settingsConfig; // Claude/Anthropic: 从 env 中读取 if (cfg?.env?.ANTHROPIC_BASE_URL) { return cfg.env.ANTHROPIC_BASE_URL; } // Codex: 从 TOML 配置中解析 base_url if (typeof cfg?.config === "string" && cfg.config.includes("base_url")) { // 支持单/双引号 const match = cfg.config.match(/base_url\s*=\s*(['"])([^'\"]+)\1/); if (match && match[2]) return match[2]; } return "未配置官网地址"; } catch { return "配置错误"; } }; const handleUrlClick = async (url: string) => { try { await window.api.openExternal(url); } catch (error) { console.error("打开链接失败:", error); } }; // 解析 Codex 配置中的 base_url(已提取到公共工具) // VS Code 按钮:仅在 Codex + 当前供应商显示;按钮文案根据是否"已应用"变化 const [vscodeAppliedFor, setVscodeAppliedFor] = useState(null); const { enableAutoSync, disableAutoSync } = useVSCodeAutoSync(); // 当当前供应商或 appType 变化时,尝试读取 VS Code settings 并检测状态 useEffect(() => { const check = async () => { if (appType !== "codex" || !currentProviderId) { setVscodeAppliedFor(null); return; } const status = await window.api.getVSCodeSettingsStatus(); if (!status.exists) { setVscodeAppliedFor(null); return; } try { const content = await window.api.readVSCodeSettings(); const detected = detectApplied(content); // 认为“已应用”的条件(非官方供应商):VS Code 中的 apiBase 与当前供应商的 base_url 完全一致 const current = providers[currentProviderId]; let applied = false; if (current && current.category !== "official") { const base = getCodexBaseUrl(current); if (detected.apiBase && base) { applied = normalizeBaseUrl(detected.apiBase) === normalizeBaseUrl(base); } } setVscodeAppliedFor(applied ? currentProviderId : null); } catch { setVscodeAppliedFor(null); } }; check(); }, [appType, currentProviderId, providers]); const handleApplyToVSCode = async (provider: Provider) => { try { const status = await window.api.getVSCodeSettingsStatus(); if (!status.exists) { onNotify?.( "未找到 VS Code 用户设置文件 (settings.json)", "error", 3000 ); return; } const raw = await window.api.readVSCodeSettings(); const isOfficial = provider.category === "official"; // 非官方且缺少 base_url 时直接报错并返回,避免“空写入”假成功 if (!isOfficial) { const parsed = getCodexBaseUrl(provider); if (!parsed) { onNotify?.("当前配置缺少 base_url,无法写入 VS Code", "error", 4000); return; } } const baseUrl = isOfficial ? undefined : getCodexBaseUrl(provider); const next = applyProviderToVSCode(raw, { baseUrl, isOfficial }); if (next === raw) { // 幂等:没有变化也提示成功 onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(provider.id); // 用户手动应用时,启用自动同步 enableAutoSync(); return; } await window.api.writeVSCodeSettings(next); onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(provider.id); // 用户手动应用时,启用自动同步 enableAutoSync(); } catch (e: any) { console.error(e); const msg = e && e.message ? e.message : "应用到 VS Code 失败"; onNotify?.(msg, "error", 5000); } }; const handleRemoveFromVSCode = async () => { try { const status = await window.api.getVSCodeSettingsStatus(); if (!status.exists) { onNotify?.( "未找到 VS Code 用户设置文件 (settings.json)", "error", 3000 ); return; } const raw = await window.api.readVSCodeSettings(); const next = applyProviderToVSCode(raw, { baseUrl: undefined, isOfficial: true, }); if (next === raw) { onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(null); // 用户手动移除时,禁用自动同步 disableAutoSync(); return; } await window.api.writeVSCodeSettings(next); onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000); setVscodeAppliedFor(null); // 用户手动移除时,禁用自动同步 disableAutoSync(); } catch (e: any) { console.error(e); const msg = e && e.message ? e.message : "移除失败"; onNotify?.(msg, "error", 5000); } }; // 对供应商列表进行排序 const sortedProviders = Object.values(providers).sort((a, b) => { // 按添加时间排序 // 没有时间戳的视为最早添加的(排在最前面) // 有时间戳的按时间升序排列 const timeA = a.createdAt || 0; const timeB = b.createdAt || 0; // 如果都没有时间戳,按名称排序 if (timeA === 0 && timeB === 0) { return a.name.localeCompare(b.name, "zh-CN"); } // 如果只有一个没有时间戳,没有时间戳的排在前面 if (timeA === 0) return -1; if (timeB === 0) return 1; // 都有时间戳,按时间升序 return timeA - timeB; }); return (
{sortedProviders.length === 0 ? (

还没有添加任何供应商

点击右上角的"添加供应商"按钮开始配置您的第一个API供应商

) : (
{sortedProviders.map((provider) => { const isCurrent = provider.id === currentProviderId; const apiUrl = getApiUrl(provider); return (

{provider.name}

{/* 分类徽章已移除 */}
当前使用
{provider.websiteUrl ? ( ) : ( {apiUrl} )}
{appType === "codex" && provider.category !== "official" && ( )}
); })}
)}
); }; export default ProviderList;