From 69642b4bb14c41d2cc988789109795d5b4143139 Mon Sep 17 00:00:00 2001 From: abingyyds Date: Mon, 23 Mar 2026 00:55:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=86=E7=AB=99=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=AE=9A=E4=BB=B7=E9=A1=B5=E6=94=B9=E4=B8=BA=E6=8C=89=E5=93=81?= =?UTF-8?q?=E7=89=8C=E5=8E=82=E5=95=86=E7=AD=9B=E9=80=89=20+=20=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端返回 vendor_name/vendor_icon 和 vendors 列表 - 前端按品牌筛选(OpenAI/Anthropic/DeepSeek 等) - 表格新增厂商列,按输入价格从低到高排序 Co-Authored-By: Claude Sonnet 4.6 --- src/i18n/locales/en.json | 10 ++---- src/i18n/locales/zh.json | 10 ++---- src/pages/Pricing.jsx | 78 +++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index e6ec03b..f5062c2 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -148,14 +148,8 @@ "pricing.status": "Status", "pricing.online": "Online", "pricing.unknown": "Unknown", - "pricing.catAll": "All", - "pricing.catChat": "Chat", - "pricing.catCompletion": "Completion", - "pricing.catEmbedding": "Embedding", - "pricing.catImage": "Image", - "pricing.catAudio": "Audio", - "pricing.catVideo": "Video", - "pricing.catRerank": "Rerank", + "pricing.vendor": "Vendor", + "pricing.allVendors": "All", "packages.title": "Packages & Plans", "packages.subtitle": "Choose a package that fits your usage. All plans include access to all available models.", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 2dec961..3038a30 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -148,14 +148,8 @@ "pricing.status": "状态", "pricing.online": "在线", "pricing.unknown": "未知", - "pricing.catAll": "全部", - "pricing.catChat": "对话", - "pricing.catCompletion": "补全", - "pricing.catEmbedding": "向量", - "pricing.catImage": "图像", - "pricing.catAudio": "音频", - "pricing.catVideo": "视频", - "pricing.catRerank": "重排序", + "pricing.vendor": "厂商", + "pricing.allVendors": "全部", "packages.title": "套餐方案", "packages.subtitle": "选择适合您用量的套餐。所有方案均可访问全部可用模型。", diff --git a/src/pages/Pricing.jsx b/src/pages/Pricing.jsx index 1634a01..7658a83 100644 --- a/src/pages/Pricing.jsx +++ b/src/pages/Pricing.jsx @@ -2,28 +2,21 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { getSiteModels } from '../api'; -const CATEGORIES = [ - { value: '', icon: '🌐', labelKey: 'pricing.catAll' }, - { value: 'chat', icon: '💬', labelKey: 'pricing.catChat' }, - { value: 'completion', icon: '✍️', labelKey: 'pricing.catCompletion' }, - { value: 'embedding', icon: '📐', labelKey: 'pricing.catEmbedding' }, - { value: 'image', icon: '🖼️', labelKey: 'pricing.catImage' }, - { value: 'audio', icon: '🎵', labelKey: 'pricing.catAudio' }, - { value: 'video', icon: '🎬', labelKey: 'pricing.catVideo' }, - { value: 'rerank', icon: '🔀', labelKey: 'pricing.catRerank' }, -]; - export default function Pricing() { const { t } = useTranslation(); const [models, setModels] = useState([]); + const [vendors, setVendors] = useState([]); const [search, setSearch] = useState(''); - const [category, setCategory] = useState(''); + const [vendor, setVendor] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { getSiteModels() .then((r) => { - if (r.data.success) setModels(r.data.data || []); + if (r.data.success) { + setModels(r.data.data || []); + setVendors(r.data.vendors || []); + } }) .catch(() => {}) .finally(() => setLoading(false)); @@ -31,17 +24,17 @@ export default function Pricing() { const enabledModels = models.filter((m) => m.enabled !== false); - // Collect categories that actually have models - const availableCategories = useMemo(() => { - const cats = new Set(enabledModels.map((m) => m.category || 'chat')); - return CATEGORIES.filter((c) => c.value === '' || cats.has(c.value)); - }, [enabledModels]); + // Collect vendor names that actually have models + const availableVendors = useMemo(() => { + const vendorNames = new Set(enabledModels.map((m) => m.vendor_name).filter(Boolean)); + return vendors.filter((v) => vendorNames.has(v.name)); + }, [enabledModels, vendors]); const filtered = useMemo(() => { let list = enabledModels; - // Category filter - if (category) { - list = list.filter((m) => (m.category || 'chat') === category); + // Vendor filter + if (vendor) { + list = list.filter((m) => m.vendor_name === vendor); } // Search filter if (search) { @@ -53,7 +46,7 @@ export default function Pricing() { // Sort by input_price ascending list = [...list].sort((a, b) => (Number(a.input_price) || 0) - (Number(b.input_price) || 0)); return list; - }, [enabledModels, category, search]); + }, [enabledModels, vendor, search]); if (loading) { return ( @@ -72,23 +65,34 @@ export default function Pricing() {

- {/* Category Filter */} -
- {availableCategories.map((cat) => ( + {/* Vendor Filter */} + {availableVendors.length > 0 && ( +
- ))} -
+ {availableVendors.map((v) => ( + + ))} +
+ )} {/* Search */}
@@ -108,7 +112,7 @@ export default function Pricing() { {filtered.length === 0 ? (
- {search || category ? t('pricing.noMatch') : t('pricing.noModels')} + {search || vendor ? t('pricing.noMatch') : t('pricing.noModels')}
) : (
@@ -116,6 +120,7 @@ export default function Pricing() { {t('pricing.model')} + {t('pricing.vendor')} {t('pricing.inputPrice')} {t('pricing.outputPrice')} {t('pricing.status')} @@ -127,6 +132,13 @@ export default function Pricing() { {m.display_name || m.model_name} + + {m.vendor_name ? ( + + {m.vendor_name} + + ) : '-'} + {m.input_price != null ? `$${(Number(m.input_price) * 1000).toFixed(4)}` : '-'}