import { useTranslation } from "react-i18next"; import { useState, useEffect } from "react"; import { ChevronDown, ChevronRight, FlaskConical, Globe, Coins, Eye, EyeOff, X, } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import type { ProviderTestConfig, ProviderProxyConfig } from "@/types"; export type PricingModelSourceOption = "inherit" | "request" | "response"; interface ProviderPricingConfig { enabled: boolean; costMultiplier?: string; pricingModelSource: PricingModelSourceOption; } interface ProviderAdvancedConfigProps { testConfig: ProviderTestConfig; proxyConfig: ProviderProxyConfig; pricingConfig: ProviderPricingConfig; onTestConfigChange: (config: ProviderTestConfig) => void; onProxyConfigChange: (config: ProviderProxyConfig) => void; onPricingConfigChange: (config: ProviderPricingConfig) => void; } /** 从 ProviderProxyConfig 构建完整 URL */ function buildProxyUrl(config: ProviderProxyConfig): string { if (!config.proxyHost) return ""; const protocol = config.proxyType || "http"; const host = config.proxyHost; const port = config.proxyPort || (protocol === "socks5" ? 1080 : 7890); return `${protocol}://${host}:${port}`; } /** 从完整 URL 解析为 ProviderProxyConfig */ function parseProxyUrl(url: string): Partial { if (!url.trim()) { return { proxyHost: undefined, proxyPort: undefined, proxyType: undefined }; } try { const parsed = new URL(url); const protocol = parsed.protocol.replace(":", "") as | "http" | "https" | "socks5"; const host = parsed.hostname; const port = parsed.port ? parseInt(parsed.port, 10) : undefined; return { proxyType: protocol, proxyHost: host || undefined, proxyPort: port, }; } catch { // 尝试简单解析(不是标准 URL 格式) const match = url.match(/^(?:(\w+):\/\/)?([^:]+)(?::(\d+))?$/); if (match) { return { proxyType: (match[1] as "http" | "https" | "socks5") || "http", proxyHost: match[2] || undefined, proxyPort: match[3] ? parseInt(match[3], 10) : undefined, }; } return {}; } } export function ProviderAdvancedConfig({ testConfig, proxyConfig, pricingConfig, onTestConfigChange, onProxyConfigChange, onPricingConfigChange, }: ProviderAdvancedConfigProps) { const { t } = useTranslation(); const [isTestConfigOpen, setIsTestConfigOpen] = useState(testConfig.enabled); const [isProxyConfigOpen, setIsProxyConfigOpen] = useState( proxyConfig.enabled, ); const [isPricingConfigOpen, setIsPricingConfigOpen] = useState( pricingConfig.enabled, ); const [showPassword, setShowPassword] = useState(false); // 代理 URL 输入状态(仅在初始化时从 proxyConfig 构建) const [proxyUrl, setProxyUrl] = useState(() => buildProxyUrl(proxyConfig)); // 标记是否为用户主动输入(用于区分外部更新和用户输入) const [isUserTyping, setIsUserTyping] = useState(false); // 同步外部 testConfig.enabled 变化到展开状态 useEffect(() => { setIsTestConfigOpen(testConfig.enabled); }, [testConfig.enabled]); // 同步外部 proxyConfig.enabled 变化到展开状态 useEffect(() => { setIsProxyConfigOpen(proxyConfig.enabled); }, [proxyConfig.enabled]); // 同步外部 pricingConfig.enabled 变化到展开状态 useEffect(() => { setIsPricingConfigOpen(pricingConfig.enabled); }, [pricingConfig.enabled]); // 仅在外部 proxyConfig 变化且非用户输入时同步(如:重置表单、加载数据) useEffect(() => { if (!isUserTyping) { const newUrl = buildProxyUrl(proxyConfig); if (newUrl !== proxyUrl) { setProxyUrl(newUrl); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [proxyConfig.proxyType, proxyConfig.proxyHost, proxyConfig.proxyPort]); // 处理代理 URL 变化(用户输入时不触发 URL 重建) const handleProxyUrlChange = (value: string) => { setIsUserTyping(true); setProxyUrl(value); const parsed = parseProxyUrl(value); onProxyConfigChange({ ...proxyConfig, ...parsed, }); }; // 输入框失焦时结束用户输入状态 const handleProxyUrlBlur = () => { setIsUserTyping(false); }; // 清除代理配置 const handleClearProxy = () => { setProxyUrl(""); onProxyConfigChange({ ...proxyConfig, proxyType: undefined, proxyHost: undefined, proxyPort: undefined, proxyUsername: undefined, proxyPassword: undefined, }); }; return (
{/* 模型测试配置 */}

{t("providerAdvanced.testConfigDesc", { defaultValue: "为此供应商配置单独的模型测试参数,不启用时使用全局配置。", })}

onTestConfigChange({ ...testConfig, testModel: e.target.value || undefined, }) } placeholder={t("providerAdvanced.testModelPlaceholder", { defaultValue: "留空使用全局配置", })} disabled={!testConfig.enabled} />
onTestConfigChange({ ...testConfig, timeoutSecs: e.target.value ? parseInt(e.target.value, 10) : undefined, }) } placeholder="45" disabled={!testConfig.enabled} />
onTestConfigChange({ ...testConfig, testPrompt: e.target.value || undefined, }) } placeholder="Who are you?" disabled={!testConfig.enabled} />
onTestConfigChange({ ...testConfig, degradedThresholdMs: e.target.value ? parseInt(e.target.value, 10) : undefined, }) } placeholder="6000" disabled={!testConfig.enabled} />
onTestConfigChange({ ...testConfig, maxRetries: e.target.value ? parseInt(e.target.value, 10) : undefined, }) } placeholder="2" disabled={!testConfig.enabled} />
{/* 代理配置 */}

{t("providerAdvanced.proxyConfigDesc", { defaultValue: "为此供应商配置单独的网络代理,不启用时使用系统代理或全局设置。", })}

{/* 代理地址输入框(仿照全局代理样式) */}
handleProxyUrlChange(e.target.value)} onBlur={handleProxyUrlBlur} className="font-mono text-sm flex-1" disabled={!proxyConfig.enabled} />
{/* 认证信息:用户名 + 密码(可选) */}
onProxyConfigChange({ ...proxyConfig, proxyUsername: e.target.value || undefined, }) } className="font-mono text-sm flex-1" disabled={!proxyConfig.enabled} />
onProxyConfigChange({ ...proxyConfig, proxyPassword: e.target.value || undefined, }) } className="font-mono text-sm pr-10" disabled={!proxyConfig.enabled} />
{/* 计费配置 */}

{t("providerAdvanced.pricingConfigDesc", { defaultValue: "为此供应商配置单独的计费参数,不启用时使用全局默认配置。", })}

onPricingConfigChange({ ...pricingConfig, costMultiplier: e.target.value || undefined, }) } placeholder={t("providerAdvanced.costMultiplierPlaceholder", { defaultValue: "留空使用全局默认(1)", })} disabled={!pricingConfig.enabled} />

{t("providerAdvanced.costMultiplierHint", { defaultValue: "实际成本 = 基础成本 × 倍率,支持小数如 1.5", })}

{t("providerAdvanced.pricingModelSourceHint", { defaultValue: "选择按请求模型还是返回模型进行定价匹配", })}

); }