Compare commits

...

4 Commits

Author SHA1 Message Date
xiezhouwei 5b5d7a95f3 fix: 评测页面顶部被固定导航栏遮挡 - 添加 pt-14 偏移\n\n根因:PageLayout.jsx 中 <Header> 使用 position: fixed; top: 0(h-14, 56px),但\n非 /console 路径的 <Content> padding 为 0,评测页面顶部内容被固定头部遮盖。\n\n修改:在外容器和 loading 容器添加 pt-14,精确匹配头部高度。 2026-06-17 11:47:48 +08:00
xiezhouwei d455641b86 fix: 评测页面浅色模式样式异常 - 切换为 shadcn/ui 主题变量系统
根因:Benchmarks 页面所有 Tailwind 样式使用硬编码暗色类
(bg-gray-950/text-white/bg-white/5/border-white/10 等),
导致浅色模式下显示为深色背景+白字,完全不可见。

修改:将所有 hardcoded dark-mode 类替换为 shadcn/ui CSS 变量类:
- bg-gray-950 → bg-background
- text-white/text-gray-* → text-foreground/text-muted-foreground
- border-white/* → border-border
- bg-white/* → bg-muted/* 和 bg-primary/*
- 卡片:添加上下模式 gradient(light:from-purple-50 dark:from-purple-950/40)
- 筛选/搜索/进度条/标签等全部适配 dual-theme
- 额外优化:分类图标更新、加载动画主题化
2026-06-17 11:39:59 +08:00
xiezhouwei e29f61aa68 fix: 评测页面 401 拦截器导致未登录用户被重定向到登录页
根因:Benchmarks 页面加载时调用 API.get('/api/option/'),
未登录用户返回 401 → axios 拦截器 → showError() → window.location.href = '/login'

修复:添加 skipErrorHandler: true 参数,跳过全局错误拦截,
API 失败时静默回退到离线默认数据
2026-06-17 11:25:47 +08:00
xiezhouwei 281c0de751 feat: 评测基准页面支持离线默认数据 + 路由公开访问
- 确认 /benchmarks 路由为公开(无 PrivateRoute)
- 嵌入 32 款主流 AI 模型完整评测数据作为默认数据
- 默认数据即时渲染,后端 API 查询改为后台异步更新(无等待状态)
- 数据覆盖 OpenAI/Anthropic/Google/DeepSeek/Meta/国内模型等
- 评分维度:智能指数、速度、价格、延迟、上下文窗口
2026-06-17 10:59:09 +08:00
2 changed files with 126 additions and 80 deletions

1
$null
View File

@ -1 +0,0 @@
ERROR: The process "main.exe" not found.

View File

@ -23,6 +23,8 @@ import {
TrendingUp, TrendingUp,
Gauge, Gauge,
Sparkles, Sparkles,
Activity,
BarChart3,
} from 'lucide-react'; } from 'lucide-react';
const COLORS = { const COLORS = {
@ -34,10 +36,53 @@ const COLORS = {
}; };
const CATEGORIES = [ const CATEGORIES = [
{ key: 'all', label: '全部', icon: Sparkles }, { key: 'all', label: '全部', icon: BarChart3 },
{ key: 'language', label: '语言模型', icon: Brain }, { key: 'language', label: '语言模型', icon: Brain },
{ key: 'image', label: '图像模型', icon: Zap }, { key: 'image', label: '图像模型', icon: Sparkles },
{ key: 'video', label: '视频模型', icon: Zap }, { key: 'video', label: '视频模型', icon: Activity },
];
/* ─── Default benchmark data (fallback when API unavailable) ─── */
const DEFAULT_MODELS = [
// OpenAI
{ name: 'GPT-4o', provider: 'OpenAI', intelligence: 55, speed: 150, price: 2.50, latency: 0.8, context_window: '128K', type: 'general', category: 'language' },
{ name: 'GPT-4o Mini', provider: 'OpenAI', intelligence: 42, speed: 420, price: 0.15, latency: 0.4, context_window: '128K', type: 'general', category: 'language' },
{ name: 'o1', provider: 'OpenAI', intelligence: 58, speed: 30, price: 15.00, latency: 8.0, context_window: '200K', type: 'reasoning', category: 'language' },
{ name: 'o1-mini', provider: 'OpenAI', intelligence: 48, speed: 75, price: 3.00, latency: 3.0, context_window: '128K', type: 'reasoning', category: 'language' },
{ name: 'o3-mini', provider: 'OpenAI', intelligence: 50, speed: 85, price: 4.40, latency: 2.5, context_window: '200K', type: 'reasoning', category: 'language' },
{ name: 'GPT-4o Realtime', provider: 'OpenAI', intelligence: 54, speed: 55, price: 5.00, latency: 0.2, context_window: '128K', type: 'general', category: 'language' },
// Anthropic
{ name: 'Claude Sonnet 4', provider: 'Anthropic', intelligence: 56, speed: 120, price: 3.00, latency: 1.0, context_window: '200K', type: 'general', category: 'language' },
{ name: 'Claude Haiku 3.5', provider: 'Anthropic', intelligence: 44, speed: 380, price: 0.80, latency: 0.5, context_window: '200K', type: 'general', category: 'language' },
{ name: 'Claude Opus 4', provider: 'Anthropic', intelligence: 59, speed: 40, price: 15.00, latency: 4.0, context_window: '200K', type: 'general', category: 'language' },
// Google
{ name: 'Gemini 2.5 Pro', provider: 'Google', intelligence: 57, speed: 200, price: 1.25, latency: 1.2, context_window: '1M', type: 'general', category: 'language' },
{ name: 'Gemini 2.0 Flash', provider: 'Google', intelligence: 46, speed: 550, price: 0.10, latency: 0.3, context_window: '1M', type: 'general', category: 'language' },
{ name: 'Gemini 2.5 Flash', provider: 'Google', intelligence: 48, speed: 480, price: 0.15, latency: 0.4, context_window: '1M', type: 'general', category: 'language' },
// DeepSeek
{ name: 'DeepSeek-V3', provider: 'DeepSeek', intelligence: 52, speed: 220, price: 0.27, latency: 0.6, context_window: '128K', type: 'general', category: 'language' },
{ name: 'DeepSeek-R1', provider: 'DeepSeek', intelligence: 56, speed: 55, price: 0.55, latency: 8.0, context_window: '128K', type: 'reasoning', category: 'language' },
// Meta
{ name: 'Llama 3.1 405B', provider: 'Meta', intelligence: 50, speed: 60, price: 2.80, latency: 3.0, context_window: '128K', type: 'general', category: 'language' },
{ name: 'Llama 3.1 70B', provider: 'Meta', intelligence: 44, speed: 180, price: 0.59, latency: 1.0, context_window: '128K', type: 'general', category: 'language' },
// Mistral
{ name: 'Mistral Large 2', provider: 'Mistral', intelligence: 51, speed: 140, price: 2.00, latency: 1.5, context_window: '128K', type: 'general', category: 'language' },
{ name: 'Mistral Small', provider: 'Mistral', intelligence: 40, speed: 350, price: 0.20, latency: 0.4, context_window: '32K', type: 'general', category: 'language' },
//
{ name: 'Qwen-Max-0428', provider: 'Alibaba Cloud', intelligence: 52, speed: 160, price: 2.00, latency: 1.2, context_window: '128K', type: 'general', category: 'language' },
{ name: 'Qwen-Plus', provider: 'Alibaba Cloud', intelligence: 44, speed: 280, price: 0.80, latency: 0.6, context_window: '128K', type: 'general', category: 'language' },
{ name: 'Doubao-Pro-32k', provider: 'ByteDance', intelligence: 48, speed: 250, price: 0.60, latency: 0.8, context_window: '32K', type: 'general', category: 'language' },
{ name: 'GLM-4-Plus', provider: 'Zhipu AI', intelligence: 47, speed: 200, price: 1.00, latency: 0.9, context_window: '128K', type: 'general', category: 'language' },
{ name: 'MiniMax-Text-01', provider: 'MiniMax', intelligence: 45, speed: 210, price: 0.80, latency: 0.7, context_window: '1M', type: 'general', category: 'language' },
{ name: 'Kimi-k2', provider: 'Moonshot', intelligence: 53, speed: 120, price: 2.00, latency: 1.8, context_window: '128K', type: 'general', category: 'language' },
//
{ name: 'DALL·E 3', provider: 'OpenAI', intelligence: 48, speed: 15, price: 40.00, latency: 5.0, context_window: '-', type: 'general', category: 'image' },
{ name: 'Stable Diffusion 3', provider: 'Stability AI', intelligence: 44, speed: 8, price: 6.50, latency: 8.0, context_window: '-', type: 'general', category: 'image' },
{ name: 'Midjourney 6', provider: 'Midjourney', intelligence: 50, speed: 12, price: 30.00, latency: 10.0, context_window: '-', type: 'general', category: 'image' },
{ name: 'Flux.1 Pro', provider: 'Black Forest Labs', intelligence: 47, speed: 10, price: 5.00, latency: 6.0, context_window: '-', type: 'general', category: 'image' },
//
{ name: 'Sora', provider: 'OpenAI', intelligence: 46, speed: 0.5, price: 100.00, latency: 30.0, context_window: '-', type: 'general', category: 'video' },
{ name: 'Runway Gen-3', provider: 'Runway', intelligence: 43, speed: 0.8, price: 50.00, latency: 25.0, context_window: '-', type: 'general', category: 'video' },
]; ];
const Benchmarks = () => { const Benchmarks = () => {
@ -50,24 +95,26 @@ const Benchmarks = () => {
const [activeCategory, setActiveCategory] = useState('all'); const [activeCategory, setActiveCategory] = useState('all');
useEffect(() => { useEffect(() => {
const fetchData = async () => { // Show default data immediately for instant rendering
setModels(DEFAULT_MODELS);
// Try to fetch backend data in background
(async () => {
try { try {
const res = await API.get('/api/option/'); const res = await API.get('/api/option/', { skipErrorHandler: true });
if (res.data.success && res.data.data) { if (res.data?.success && res.data?.data) {
const option = res.data.data.find( const option = res.data.data.find((o) => o.key === 'benchmark_data.models');
(o) => o.key === 'benchmark_data.models', if (option?.value) {
); const parsed = JSON.parse(option.value);
if (option && option.value) { if (Array.isArray(parsed) && parsed.length > 0) {
setModels(JSON.parse(option.value)); setModels(parsed);
}
} }
} }
} catch { } catch {
// fallback: use embeded default data // keep default data
} finally {
setLoading(false);
} }
}; })();
fetchData(); setLoading(false);
}, []); }, []);
const filteredModels = useMemo(() => { const filteredModels = useMemo(() => {
@ -120,19 +167,19 @@ const Benchmarks = () => {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-gray-950 flex items-center justify-center"> <div className="min-h-screen bg-background flex items-center justify-center pt-14">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white/30" /> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-foreground/20" />
</div> </div>
); );
} }
const SortHeader = ({ label, sortField, icon: Icon }) => ( const SortHeader = ({ label, sortField, icon: Icon }) => (
<th <th
className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider cursor-pointer hover:text-white/70 transition-colors select-none" className="px-4 py-3 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider cursor-pointer hover:text-foreground/70 transition-colors select-none"
onClick={() => toggleSort(sortField)} onClick={() => toggleSort(sortField)}
> >
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{Icon && <Icon size={13} className="text-gray-500" />} {Icon && <Icon size={13} className="text-muted-foreground/60" />}
<span>{label}</span> <span>{label}</span>
{sortKey === sortField && ( {sortKey === sortField && (
<ArrowUpDown <ArrowUpDown
@ -145,14 +192,14 @@ const Benchmarks = () => {
); );
return ( return (
<div className="min-h-screen bg-gray-950 text-white"> <div className="min-h-screen bg-background text-foreground pt-14">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
{/* Header */} {/* Header */}
<div className="mb-8 sm:mb-12"> <div className="mb-8 sm:mb-12">
<h1 className="text-3xl sm:text-4xl font-bold font-heading mb-3"> <h1 className="text-3xl sm:text-4xl font-bold font-heading mb-3">
{t('AI 模型评测排行榜')} {t('AI 模型评测排行榜')}
</h1> </h1>
<p className="text-gray-400 text-sm sm:text-base font-body max-w-2xl"> <p className="text-muted-foreground text-sm sm:text-base font-body max-w-2xl">
{t('基于 Artificial Analysis 数据,从智能指数、输出速度、价格成本、延迟等维度对主流 AI 模型进行多维度对比评测')} {t('基于 Artificial Analysis 数据,从智能指数、输出速度、价格成本、延迟等维度对主流 AI 模型进行多维度对比评测')}
</p> </p>
</div> </div>
@ -160,82 +207,82 @@ const Benchmarks = () => {
{/* Highlight Cards */} {/* Highlight Cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 mb-8 sm:mb-10"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 mb-8 sm:mb-10">
{topIntel[0] && ( {topIntel[0] && (
<div className="bg-gradient-to-br from-purple-900/30 to-purple-950/30 border border-purple-800/30 rounded-xl p-4 sm:p-5"> <div className="bg-gradient-to-br from-purple-50 to-purple-100/50 dark:from-purple-950/40 dark:to-purple-900/20 border border-purple-200 dark:border-purple-800/30 rounded-xl p-4 sm:p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<Trophy size={18} className="text-purple-400" /> <Trophy size={18} className="text-purple-500 dark:text-purple-400" />
<span className="text-xs font-semibold text-purple-300 uppercase tracking-wider"> <span className="text-xs font-semibold text-purple-600 dark:text-purple-300 uppercase tracking-wider">
{t('智能冠军')} {t('智能冠军')}
</span> </span>
</div> </div>
<div className="text-lg sm:text-xl font-bold text-white mb-1"> <div className="text-lg sm:text-xl font-bold text-foreground mb-1">
{topIntel[0].name} {topIntel[0].name}
</div> </div>
<div className="text-xs text-gray-400 mb-2">{topIntel[0].provider}</div> <div className="text-xs text-muted-foreground mb-2">{topIntel[0].provider}</div>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-2xl sm:text-3xl font-bold text-purple-400"> <span className="text-2xl sm:text-3xl font-bold text-purple-500 dark:text-purple-400">
{topIntel[0].intelligence} {topIntel[0].intelligence}
</span> </span>
<span className="text-xs text-gray-500">/ 60</span> <span className="text-xs text-muted-foreground/60">/ 60</span>
</div> </div>
</div> </div>
)} )}
{fastest && ( {fastest && (
<div className="bg-gradient-to-br from-cyan-900/30 to-cyan-950/30 border border-cyan-800/30 rounded-xl p-4 sm:p-5"> <div className="bg-gradient-to-br from-cyan-50 to-cyan-100/50 dark:from-cyan-950/40 dark:to-cyan-900/20 border border-cyan-200 dark:border-cyan-800/30 rounded-xl p-4 sm:p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<Gauge size={18} className="text-cyan-400" /> <Gauge size={18} className="text-cyan-500 dark:text-cyan-400" />
<span className="text-xs font-semibold text-cyan-300 uppercase tracking-wider"> <span className="text-xs font-semibold text-cyan-600 dark:text-cyan-300 uppercase tracking-wider">
{t('速度之王')} {t('速度之王')}
</span> </span>
</div> </div>
<div className="text-lg sm:text-xl font-bold text-white mb-1"> <div className="text-lg sm:text-xl font-bold text-foreground mb-1">
{fastest.name} {fastest.name}
</div> </div>
<div className="text-xs text-gray-400 mb-2">{fastest.provider}</div> <div className="text-xs text-muted-foreground mb-2">{fastest.provider}</div>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-2xl sm:text-3xl font-bold text-cyan-400"> <span className="text-2xl sm:text-3xl font-bold text-cyan-500 dark:text-cyan-400">
{fastest.speed?.toLocaleString()} {fastest.speed?.toLocaleString()}
</span> </span>
<span className="text-xs text-gray-500">tok/s</span> <span className="text-xs text-muted-foreground/60">tok/s</span>
</div> </div>
</div> </div>
)} )}
{cheapest && ( {cheapest && (
<div className="bg-gradient-to-br from-emerald-900/30 to-emerald-950/30 border border-emerald-800/30 rounded-xl p-4 sm:p-5"> <div className="bg-gradient-to-br from-emerald-50 to-emerald-100/50 dark:from-emerald-950/40 dark:to-emerald-900/20 border border-emerald-200 dark:border-emerald-800/30 rounded-xl p-4 sm:p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<DollarSign size={18} className="text-emerald-400" /> <DollarSign size={18} className="text-emerald-500 dark:text-emerald-400" />
<span className="text-xs font-semibold text-emerald-300 uppercase tracking-wider"> <span className="text-xs font-semibold text-emerald-600 dark:text-emerald-300 uppercase tracking-wider">
{t('性价比首选')} {t('性价比首选')}
</span> </span>
</div> </div>
<div className="text-lg sm:text-xl font-bold text-white mb-1"> <div className="text-lg sm:text-xl font-bold text-foreground mb-1">
{cheapest.name} {cheapest.name}
</div> </div>
<div className="text-xs text-gray-400 mb-2">{cheapest.provider}</div> <div className="text-xs text-muted-foreground mb-2">{cheapest.provider}</div>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-2xl sm:text-3xl font-bold text-emerald-400"> <span className="text-2xl sm:text-3xl font-bold text-emerald-500 dark:text-emerald-400">
${cheapest.price?.toFixed(2)} ${cheapest.price?.toFixed(2)}
</span> </span>
<span className="text-xs text-gray-500">/M tokens</span> <span className="text-xs text-muted-foreground/60">/M tokens</span>
</div> </div>
</div> </div>
)} )}
{lowestLatency && ( {lowestLatency && (
<div className="bg-gradient-to-br from-amber-900/30 to-amber-950/30 border border-amber-800/30 rounded-xl p-4 sm:p-5"> <div className="bg-gradient-to-br from-amber-50 to-amber-100/50 dark:from-amber-950/40 dark:to-amber-900/20 border border-amber-200 dark:border-amber-800/30 rounded-xl p-4 sm:p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<Clock size={18} className="text-amber-400" /> <Clock size={18} className="text-amber-500 dark:text-amber-400" />
<span className="text-xs font-semibold text-amber-300 uppercase tracking-wider"> <span className="text-xs font-semibold text-amber-600 dark:text-amber-300 uppercase tracking-wider">
{t('低延迟标杆')} {t('低延迟标杆')}
</span> </span>
</div> </div>
<div className="text-lg sm:text-xl font-bold text-white mb-1"> <div className="text-lg sm:text-xl font-bold text-foreground mb-1">
{lowestLatency.name} {lowestLatency.name}
</div> </div>
<div className="text-xs text-gray-400 mb-2">{lowestLatency.provider}</div> <div className="text-xs text-muted-foreground mb-2">{lowestLatency.provider}</div>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-2xl sm:text-3xl font-bold text-amber-400"> <span className="text-2xl sm:text-3xl font-bold text-amber-500 dark:text-amber-400">
{lowestLatency.latency?.toFixed(2)} {lowestLatency.latency?.toFixed(2)}
</span> </span>
<span className="text-xs text-gray-500">{t('秒')}</span> <span className="text-xs text-muted-foreground/60">{t('秒')}</span>
</div> </div>
</div> </div>
)} )}
@ -253,8 +300,8 @@ const Benchmarks = () => {
onClick={() => setActiveCategory(cat.key)} onClick={() => setActiveCategory(cat.key)}
className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 ${ className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 ${
isActive isActive
? 'bg-white/10 text-white border border-white/20' ? 'bg-primary/10 text-foreground border border-border'
: 'bg-white/5 text-gray-400 border border-transparent hover:bg-white/10 hover:text-gray-300' : 'bg-muted/40 text-muted-foreground border border-transparent hover:bg-muted/60 hover:text-foreground/80'
}`} }`}
> >
<Icon size={13} /> <Icon size={13} />
@ -266,34 +313,34 @@ const Benchmarks = () => {
<div className="relative w-full sm:w-64"> <div className="relative w-full sm:w-64">
<Search <Search
size={15} size={15}
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/60"
/> />
<input <input
type="text" type="text"
placeholder={t('搜索模型名称或供应商...')} placeholder={t('搜索模型名称或供应商...')}
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-lg pl-9 pr-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-white/30 focus:bg-white/10 transition-all" className="w-full bg-muted/30 border border-input rounded-lg pl-9 pr-3 py-2 text-sm text-foreground placeholder-muted-foreground/50 focus:outline-none focus:border-ring focus:bg-muted/50 transition-all"
/> />
</div> </div>
</div> </div>
{/* Model Count */} {/* Model Count */}
<div className="text-xs text-gray-500 mb-3"> <div className="text-xs text-muted-foreground/60 mb-3">
{t('共 {count} 个模型', { count: filteredModels.length })} {t('共 {count} 个模型', { count: filteredModels.length })}
</div> </div>
{/* Table */} {/* Table */}
<div className="overflow-x-auto rounded-xl border border-white/10"> <div className="overflow-x-auto rounded-xl border border-border">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="bg-white/5 border-b border-white/10"> <tr className="bg-muted/30 border-b border-border">
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider w-10"> <th className="px-4 py-3 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider w-10">
# #
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider"> <th className="px-4 py-3 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Brain size={13} className="text-gray-500" /> <Brain size={13} className="text-muted-foreground/60" />
<span>{t('模型名称')}</span> <span>{t('模型名称')}</span>
</div> </div>
</th> </th>
@ -309,39 +356,39 @@ const Benchmarks = () => {
icon={DollarSign} icon={DollarSign}
/> />
<SortHeader label="延迟" sortField="latency" icon={Clock} /> <SortHeader label="延迟" sortField="latency" icon={Clock} />
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider"> <th className="px-4 py-3 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Maximize2 size={13} className="text-gray-500" /> <Maximize2 size={13} className="text-muted-foreground/60" />
<span>{t('上下文')}</span> <span>{t('上下文')}</span>
</div> </div>
</th> </th>
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider"> <th className="px-4 py-3 text-center text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{t('类型')} {t('类型')}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-white/5"> <tbody className="divide-y divide-border/40">
{filteredModels.map((model, idx) => ( {filteredModels.map((model, idx) => (
<tr <tr
key={model.name + model.provider} key={model.name + model.provider}
className="hover:bg-white/5 transition-colors" className="hover:bg-muted/50 transition-colors"
> >
<td className="px-4 py-3.5 text-gray-500 text-xs font-mono"> <td className="px-4 py-3.5 text-muted-foreground text-xs font-mono">
{idx + 1} {idx + 1}
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-medium text-white text-sm"> <span className="font-medium text-foreground text-sm">
{model.name} {model.name}
</span> </span>
<span className="text-xs text-gray-500"> <span className="text-xs text-muted-foreground">
{model.provider} {model.provider}
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-20 sm:w-24 h-2 bg-white/10 rounded-full overflow-hidden"> <div className="w-20 sm:w-24 h-2 bg-muted rounded-full overflow-hidden">
<div <div
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all"
style={{ style={{
@ -350,14 +397,14 @@ const Benchmarks = () => {
}} }}
/> />
</div> </div>
<span className="text-xs font-mono text-gray-300 w-8 text-right"> <span className="text-xs font-mono text-foreground/70 w-8 text-right">
{model.intelligence} {model.intelligence}
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-16 sm:w-20 h-2 bg-white/10 rounded-full overflow-hidden"> <div className="w-16 sm:w-20 h-2 bg-muted rounded-full overflow-hidden">
<div <div
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all"
style={{ style={{
@ -366,23 +413,23 @@ const Benchmarks = () => {
}} }}
/> />
</div> </div>
<span className="text-xs font-mono text-gray-300 whitespace-nowrap"> <span className="text-xs font-mono text-foreground/70 whitespace-nowrap">
{model.speed?.toLocaleString()} {model.speed?.toLocaleString()}
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className="text-xs font-mono text-gray-300"> <span className="text-xs font-mono text-foreground/70">
${model.price?.toFixed(2)} ${model.price?.toFixed(2)}
</span> </span>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className="text-xs font-mono text-gray-300"> <span className="text-xs font-mono text-foreground/70">
{model.latency?.toFixed(2)}s {model.latency?.toFixed(2)}s
</span> </span>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className="text-xs text-gray-400"> <span className="text-xs text-muted-foreground">
{model.context_window || '-'} {model.context_window || '-'}
</span> </span>
</td> </td>
@ -390,8 +437,8 @@ const Benchmarks = () => {
<span <span
className={`inline-block px-2 py-0.5 rounded text-[10px] font-medium ${ className={`inline-block px-2 py-0.5 rounded text-[10px] font-medium ${
model.type === 'reasoning' model.type === 'reasoning'
? 'bg-purple-900/50 text-purple-300 border border-purple-700/50' ? 'bg-purple-100 text-purple-700 border border-purple-200 dark:bg-purple-900/50 dark:text-purple-300 dark:border-purple-700/50'
: 'bg-blue-900/50 text-blue-300 border border-blue-700/50' : 'bg-blue-100 text-blue-700 border border-blue-200 dark:bg-blue-900/50 dark:text-blue-300 dark:border-blue-700/50'
}`} }`}
> >
{model.type === 'reasoning' {model.type === 'reasoning'
@ -404,14 +451,14 @@ const Benchmarks = () => {
</tbody> </tbody>
</table> </table>
{filteredModels.length === 0 && ( {filteredModels.length === 0 && (
<div className="text-center py-12 text-gray-500 text-sm"> <div className="text-center py-12 text-muted-foreground text-sm">
{t('暂未找到匹配的模型')} {t('暂未找到匹配的模型')}
</div> </div>
)} )}
</div> </div>
{/* Footer */} {/* Footer */}
<div className="mt-6 text-center text-xs text-gray-600"> <div className="mt-6 text-center text-xs text-muted-foreground/50">
{t('数据来源: Artificial Analysis | 数据持续更新中')} {t('数据来源: Artificial Analysis | 数据持续更新中')}
</div> </div>
</div> </div>