import { useState } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { motion, AnimatePresence } from 'framer-motion' import { Wifi, Play, Loader2, X, Check, Crown } from 'lucide-react' import { api, type EndpointItem } from '@/lib/api' import { QK } from '@/lib/queryKeys' import { EXPERT_RANK, tierRank } from '@/lib/capability-labels' interface EpResult { ok: boolean median_ms?: number | null min_ms?: number | null max_ms?: number | null rounds?: number success?: number error?: string } export function EndpointTestDialog({ hasKey, tierLabel, currentEndpoint, onClose }: { hasKey: boolean; tierLabel: string; currentEndpoint: string; onClose: () => void }) { const qc = useQueryClient() const [results, setResults] = useState>({}) const [testing, setTesting] = useState>({}) const [switching, setSwitching] = useState(null) // 动态加载端点清单 —— 前端无法跨域直连数据源官网,走后端代理 const { data, isLoading } = useQuery({ queryKey: QK.endpoints, queryFn: api.listEndpoints, staleTime: 5 * 60 * 1000, }) const endpoints = data?.endpoints ?? [] const isFallback = data?.source === 'fallback' const testRounds = data?.testRounds async function testOne(url: string) { setTesting(prev => ({ ...prev, [url]: true })) setResults(prev => ({ ...prev, [url]: null })) try { const res = await api.testEndpoint(url, testRounds) setResults(prev => ({ ...prev, [url]: res })) } catch (e: any) { setResults(prev => ({ ...prev, [url]: { ok: false, error: e?.message ?? '请求失败' } })) } finally { setTesting(prev => ({ ...prev, [url]: false })) } } async function testAll() { setResults({}) await Promise.all(endpoints.map(ep => testOne(ep.url))) } const anyTesting = Object.values(testing).some(Boolean) const isFree = !hasKey // 专线端点需 Expert 及以上套餐;Free 模式必然不可用 const canUsePremium = !isFree && tierRank(tierLabel) >= EXPERT_RANK const currentLabel = endpoints.find(ep => ep.url === currentEndpoint)?.label ?? currentEndpoint async function applyEndpoint(url: string) { setSwitching(url) try { await api.switchEndpoint(url) await qc.invalidateQueries({ queryKey: QK.settings }) onClose() } catch { // 错误由 query 处理 } finally { setSwitching(null) } } return (
{/* 顶栏 */}
端点测速
{isFree && ( Free 模式 )}
{/* 当前使用 */}
当前使用 {currentLabel} {currentEndpoint.replace('https://', '')}
{/* Free 模式提示 —— 以下均为 Starter+ 付费端点 */} {isFree && (
以下均为 Starter+ 付费端点,Free 模式不可使用。配置 API Key 后可自动切换并测速选优。
)} {/* 端点列表 —— 可滚动区,顶栏/当前使用/底栏始终可见 */}
{isLoading ? (
加载端点列表…
) : endpoints.length === 0 ? (
未能加载端点列表
) : ( endpoints.map(ep => ( )) )}
{isFallback ? ( 远程获取失败,显示内置列表 ) : null}
) } function EpRow({ ep, result, testing, isCurrent, isFree, canUsePremium, switching, onApply }: { ep: EndpointItem result: EpResult | null testing?: boolean isCurrent?: boolean isFree?: boolean canUsePremium?: boolean switching: string | null onApply: (url: string) => void }) { const isError = result && !result.ok const canApply = result?.ok && !testing && !isCurrent const isPremium = ep.premium === true const median = result?.median_ms return (
{/* 第1行:label + 徽章(左) / 中位延迟(右) */}
{ep.label} {isPremium && ( 专线 )} {isCurrent && ( 使用中 )}
{/* 中位延迟 —— 测试中/前/后都占位,避免高度跳动 */} {isFree ? ( // Free 模式:普通付费端点需 Starter+,premium 端点需 Expert+ {isPremium ? 'Expert+' : 'Starter+'} ) : testing ? ( 测试中… ) : result && result.ok && median != null ? ( {median} ms ) : result && !result.ok ? ( {result.error ?? '不可达'} ) : ( )}
{/* 第2行:description */} {ep.description} {/* 第3行:URL(左) / min~max·成功率(右) —— 副信息始终占位,行数不变 */}
{ep.url.replace('https://', '')} {result && result.ok && result.min_ms != null ? `${result.min_ms}~${result.max_ms} · ${result.success}/${result.rounds}` : '\u00A0'}
{/* 应用按钮区域 —— Free 模式不可用任何付费端点;专线端点需 Expert+ */} {isFree ? null : (isPremium && !canUsePremium) ? ( // 专线端点:需 Expert 及以上套餐权限,当前套餐不足,不可应用 Expert+ ) : canApply ? ( ) : null}
) }