From 917314463ecfb009afd9e6e373bd8d7e898f0fbf Mon Sep 17 00:00:00 2001 From: abingyyds Date: Mon, 23 Mar 2026 00:23:56 +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=B7=BB=E5=8A=A0=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E5=92=8C=E4=BB=B7=E6=A0=BC=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 GetDistSiteModels 返回 category 字段 - 前端添加分类图标筛选按钮(对话/补全/向量/图像/音频/视频/重排序) - 模型按输入价格从低到高排序 - 添加中英文 i18n 翻译 Co-Authored-By: Claude Sonnet 4.6 --- src/i18n/locales/en.json | 8 ++++++ src/i18n/locales/zh.json | 8 ++++++ src/pages/Pricing.jsx | 61 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 79fed8f..e6ec03b 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -148,6 +148,14 @@ "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", "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 65dc040..2dec961 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -148,6 +148,14 @@ "pricing.status": "状态", "pricing.online": "在线", "pricing.unknown": "未知", + "pricing.catAll": "全部", + "pricing.catChat": "对话", + "pricing.catCompletion": "补全", + "pricing.catEmbedding": "向量", + "pricing.catImage": "图像", + "pricing.catAudio": "音频", + "pricing.catVideo": "视频", + "pricing.catRerank": "重排序", "packages.title": "套餐方案", "packages.subtitle": "选择适合您用量的套餐。所有方案均可访问全部可用模型。", diff --git a/src/pages/Pricing.jsx b/src/pages/Pricing.jsx index c983cf6..1634a01 100644 --- a/src/pages/Pricing.jsx +++ b/src/pages/Pricing.jsx @@ -1,11 +1,23 @@ -import React, { useState, useEffect } from 'react'; +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 [search, setSearch] = useState(''); + const [category, setCategory] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { @@ -19,10 +31,29 @@ export default function Pricing() { const enabledModels = models.filter((m) => m.enabled !== false); - const filtered = enabledModels.filter((m) => - !search || - (m.display_name || m.model_name || '').toLowerCase().includes(search.toLowerCase()) - ); + // 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]); + + const filtered = useMemo(() => { + let list = enabledModels; + // Category filter + if (category) { + list = list.filter((m) => (m.category || 'chat') === category); + } + // Search filter + if (search) { + const q = search.toLowerCase(); + list = list.filter((m) => + (m.display_name || m.model_name || '').toLowerCase().includes(q) + ); + } + // 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]); if (loading) { return ( @@ -41,6 +72,24 @@ export default function Pricing() {

+ {/* Category Filter */} +
+ {availableCategories.map((cat) => ( + + ))} +
+ {/* Search */}
@@ -59,7 +108,7 @@ export default function Pricing() { {filtered.length === 0 ? (
- {search ? t('pricing.noMatch') : t('pricing.noModels')} + {search || category ? t('pricing.noMatch') : t('pricing.noModels')}
) : (