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)}` : '-'}
|