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