- 后端 Update 函数添加 type 字段支持更新 - 前端 EditModelModal 添加模型类型下拉选择器 - 新增 MODEL_TYPE_OPTIONS 共享常量(与后端 ModelType 一致) - 添加中英文 i18n 翻译 - 修复首页"全部供应商"计数显示错误的 bug Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>feat/alipay-payment
| @@ -89,7 +89,7 @@ func (mi *Model) Update() error { | |||||
| mi.UpdatedTime = common.GetTimestamp() | mi.UpdatedTime = common.GetTimestamp() | ||||
| // 使用 Select 强制更新所有字段,包括零值 | // 使用 Select 强制更新所有字段,包括零值 | ||||
| return DB.Model(&Model{}).Where("id = ?", mi.Id). | return DB.Model(&Model{}).Where("id = ?", mi.Id). | ||||
| Select("model_name", "description", "icon", "tags", "vendor_id", "endpoints", "status", "sync_official", "name_rule", "updated_time"). | |||||
| Select("model_name", "description", "icon", "tags", "type", "vendor_id", "endpoints", "status", "sync_official", "name_rule", "updated_time"). | |||||
| Updates(mi).Error | Updates(mi).Error | ||||
| } | } | ||||
| @@ -38,6 +38,7 @@ import { IconAlertTriangle, IconLink } from '@douyinfe/semi-icons'; | |||||
| import { API, showError, showSuccess } from '../../../../helpers'; | import { API, showError, showSuccess } from '../../../../helpers'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useIsMobile } from '../../../../hooks/common/useIsMobile'; | import { useIsMobile } from '../../../../hooks/common/useIsMobile'; | ||||
| import { MODEL_TYPE_OPTIONS } from '../../../../constants/common.constant'; | |||||
| const { Text, Title } = Typography; | const { Text, Title } = Typography; | ||||
| @@ -117,6 +118,7 @@ const EditModelModal = (props) => { | |||||
| description: '', | description: '', | ||||
| icon: '', | icon: '', | ||||
| tags: [], | tags: [], | ||||
| type: 1, // 默认对话模型 | |||||
| vendor_id: undefined, | vendor_id: undefined, | ||||
| vendor: '', | vendor: '', | ||||
| vendor_icon: '', | vendor_icon: '', | ||||
| @@ -330,6 +332,25 @@ const EditModelModal = (props) => { | |||||
| /> | /> | ||||
| </Col> | </Col> | ||||
| <Col span={24}> | |||||
| <Form.Select | |||||
| field='type' | |||||
| label={t('模型类型')} | |||||
| placeholder={t('请选择模型类型')} | |||||
| optionList={MODEL_TYPE_OPTIONS.map((o) => ({ | |||||
| label: t(o.label), | |||||
| value: o.value, | |||||
| }))} | |||||
| rules={[ | |||||
| { required: true, message: t('请选择模型类型') }, | |||||
| ]} | |||||
| extraText={t( | |||||
| '选择模型的类型,用于分类和筛选', | |||||
| )} | |||||
| style={{ width: '100%' }} | |||||
| /> | |||||
| </Col> | |||||
| <Col span={24}> | <Col span={24}> | ||||
| <Form.Input | <Form.Input | ||||
| field='icon' | field='icon' | ||||
| @@ -44,3 +44,16 @@ export const TASK_ACTION_TEXT_GENERATE = 'textGenerate'; | |||||
| export const TASK_ACTION_FIRST_TAIL_GENERATE = 'firstTailGenerate'; | export const TASK_ACTION_FIRST_TAIL_GENERATE = 'firstTailGenerate'; | ||||
| export const TASK_ACTION_REFERENCE_GENERATE = 'referenceGenerate'; | export const TASK_ACTION_REFERENCE_GENERATE = 'referenceGenerate'; | ||||
| export const TASK_ACTION_REMIX_GENERATE = 'remixGenerate'; | export const TASK_ACTION_REMIX_GENERATE = 'remixGenerate'; | ||||
| // 模型类型常量,与 model/model_meta.go ModelType 保持一致 | |||||
| export const MODEL_TYPE_OPTIONS = [ | |||||
| { value: 0, label: 'modelType.unset' }, | |||||
| { value: 1, label: 'modelType.chat' }, | |||||
| { value: 2, label: 'modelType.image' }, | |||||
| { value: 3, label: 'modelType.audio' }, | |||||
| { value: 4, label: 'modelType.video' }, | |||||
| { value: 5, label: 'modelType.embedding' }, | |||||
| { value: 6, label: 'modelType.rerank' }, | |||||
| { value: 7, label: 'modelType.vision' }, | |||||
| { value: 8, label: 'modelType.other' }, | |||||
| ]; | |||||
| @@ -2868,6 +2868,15 @@ | |||||
| "绑定渠道": "Bind Channel", | "绑定渠道": "Bind Channel", | ||||
| "可选,绑定后该令牌只能使用指定渠道": "Optional. Once bound, the token can only use the specified channel.", | "可选,绑定后该令牌只能使用指定渠道": "Optional. Once bound, the token can only use the specified channel.", | ||||
| "请选择标签": "Please select tags", | "请选择标签": "Please select tags", | ||||
| "modelType.unset": "Unset", | |||||
| "modelType.chat": "Chat", | |||||
| "modelType.image": "Image", | |||||
| "modelType.audio": "Audio", | |||||
| "modelType.video": "Video", | |||||
| "modelType.embedding": "Embedding", | |||||
| "modelType.rerank": "Rerank", | |||||
| "modelType.vision": "Vision", | |||||
| "modelType.other": "Other", | |||||
| "设计版本": "b80c3466cb6feafeb3990c7820e10e50" | "设计版本": "b80c3466cb6feafeb3990c7820e10e50" | ||||
| } | } | ||||
| } | } | ||||
| @@ -2845,6 +2845,15 @@ | |||||
| "暂无渠道定价数据,请先创建渠道并设置模型": "暂无渠道定价数据,请先创建渠道并设置模型", | "暂无渠道定价数据,请先创建渠道并设置模型": "暂无渠道定价数据,请先创建渠道并设置模型", | ||||
| "绑定渠道": "绑定渠道", | "绑定渠道": "绑定渠道", | ||||
| "可选,绑定后该令牌只能使用指定渠道": "可选,绑定后该令牌只能使用指定渠道", | "可选,绑定后该令牌只能使用指定渠道": "可选,绑定后该令牌只能使用指定渠道", | ||||
| "请选择标签": "请选择标签" | |||||
| "请选择标签": "请选择标签", | |||||
| "modelType.unset": "未设置", | |||||
| "modelType.chat": "对话", | |||||
| "modelType.image": "图像", | |||||
| "modelType.audio": "音频", | |||||
| "modelType.video": "视频", | |||||
| "modelType.embedding": "嵌入", | |||||
| "modelType.rerank": "重排序", | |||||
| "modelType.vision": "视觉", | |||||
| "modelType.other": "其他" | |||||
| } | } | ||||
| } | } | ||||
| @@ -71,16 +71,21 @@ const HomePricingFilters = ({ t }) => { | |||||
| } | } | ||||
| }); | }); | ||||
| const vendors = Array.from(vendorSet).sort(); | const vendors = Array.from(vendorSet).sort(); | ||||
| const getCount = (v) => { | |||||
| const getVendorCount = () => { | |||||
| // 计算供应商数量(包含未知供应商) | |||||
| const count = vendorSet.size + (hasUnknown ? 1 : 0); | |||||
| return count; | |||||
| }; | |||||
| const getModelCount = (v) => { | |||||
| if (v === 'all') return vendorModels.length; | if (v === 'all') return vendorModels.length; | ||||
| if (v === 'unknown') return vendorModels.filter((m) => !m.vendor_name).length; | if (v === 'unknown') return vendorModels.filter((m) => !m.vendor_name).length; | ||||
| return vendorModels.filter((m) => m.vendor_name === v).length; | return vendorModels.filter((m) => m.vendor_name === v).length; | ||||
| }; | }; | ||||
| const items = [ | const items = [ | ||||
| { value: 'all', label: t('全部供应商'), tagCount: getCount('all'), disabled: (models || []).length === 0 }, | |||||
| { value: 'all', label: t('全部供应商'), tagCount: getVendorCount(), disabled: (models || []).length === 0 }, | |||||
| ]; | ]; | ||||
| vendors.forEach((v) => { | vendors.forEach((v) => { | ||||
| const count = getCount(v); | |||||
| const count = getModelCount(v); | |||||
| items.push({ | items.push({ | ||||
| value: v, | value: v, | ||||
| label: v, | label: v, | ||||
| @@ -93,8 +98,8 @@ const HomePricingFilters = ({ t }) => { | |||||
| items.push({ | items.push({ | ||||
| value: 'unknown', | value: 'unknown', | ||||
| label: t('未知供应商'), | label: t('未知供应商'), | ||||
| tagCount: getCount('unknown'), | |||||
| disabled: getCount('unknown') === 0, | |||||
| tagCount: getModelCount('unknown'), | |||||
| disabled: getModelCount('unknown') === 0, | |||||
| }); | }); | ||||
| } | } | ||||
| return items; | return items; | ||||