diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index d404dd6e..6bd3ea3f 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -603,7 +603,13 @@ export const en = { ollama: "Ollama", xinference: "Xinference", gpustack: "Gpustack", - bedrock: "Bedrock" + bedrock: "Bedrock", + + is_vision: 'Vision Support', + is_omni: 'Omni Support', + vision: 'Vision', + audio: 'Audio', + video: 'Video', }, knowledgeBase: { home: 'Home', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index fc6bb822..43c3b8be 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1184,6 +1184,12 @@ export const zh = { xinference: "Xinference", gpustack: "Gpustack", bedrock: "Bedrock", + + is_vision: '支持视觉', + is_omni: '支持全模态', + vision: '视觉', + audio: '音频', + video: '视频', }, timezones: { 'Asia/Shanghai': '中国标准时间 (UTC+8)', diff --git a/web/src/views/ModelManagement/components/CustomModelModal.tsx b/web/src/views/ModelManagement/components/CustomModelModal.tsx index 112534a5..d47fc996 100644 --- a/web/src/views/ModelManagement/components/CustomModelModal.tsx +++ b/web/src/views/ModelManagement/components/CustomModelModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:49:28 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-28 17:24:05 + * @Last Modified time: 2026-03-04 11:31:43 */ /** * Custom Model Modal @@ -10,8 +10,8 @@ * Supports logo upload, type/provider selection, and tagging */ -import { forwardRef, useImperativeHandle, useState } from 'react'; -import { Form, Input, App } from 'antd'; +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { Form, Input, App, Checkbox } from 'antd'; import { useTranslation } from 'react-i18next'; import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps } from '../types'; @@ -35,6 +35,14 @@ const CustomModelModal = forwardRef( const [isEdit, setIsEdit] = useState(false); const [form] = Form.useForm(); const [loading, setLoading] = useState(false) + const modelType = Form.useWatch(['type'], form); + const isOmni = Form.useWatch(['is_omni'], form); + + useEffect(() => { + if (isOmni) { + form.setFieldsValue({ is_vision: true }) + } + }, [isOmni]) /** Close modal and reset state */ const handleClose = () => { @@ -49,9 +57,12 @@ const CustomModelModal = forwardRef( if (model) { setIsEdit(true); setModel(model); + const { capability, is_omni, ...rest} = model form.setFieldsValue({ - ...model, - logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined + ...rest, + logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined, + is_omni, + is_vision: capability?.includes('vision') || false, }); } else { setIsEdit(false); @@ -79,9 +90,14 @@ const CustomModelModal = forwardRef( form .validateFields() .then((values) => { - const { logo, ...rest } = values; + const { logo, type, is_vision, is_omni, ...rest } = values; const formData: CustomModelForm = { - ...rest + ...rest, + type, + } + if (!['embedding', 'rerank'].includes(type as string)) { + formData.capability = is_omni ? ["vision", "audio"] : is_vision ? ['vision'] : [] + formData.is_omni = is_omni } if (typeof logo === 'object' && logo?.response?.data.file_id) { @@ -108,7 +124,7 @@ const CustomModelModal = forwardRef( useImperativeHandle(ref, () => ({ handleOpen, })); - + console.log('modelType', modelType) return ( ( - ( > + + {!['embedding', 'rerank'].includes(modelType as string) && + <> + + {t('modelNew.is_omni')} + + + {t('modelNew.is_vision')} + + + } ); diff --git a/web/src/views/ModelManagement/components/ModelImplement/SubModelModal.tsx b/web/src/views/ModelManagement/components/ModelImplement/SubModelModal.tsx index e312b779..b2b44bf3 100644 --- a/web/src/views/ModelManagement/components/ModelImplement/SubModelModal.tsx +++ b/web/src/views/ModelManagement/components/ModelImplement/SubModelModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:49:20 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:54:54 + * @Last Modified time: 2026-03-04 11:51:01 */ /** * Sub-Model Modal @@ -10,8 +10,8 @@ * Uses cascader for hierarchical selection */ -import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'; -import { Form, Cascader, App, type CascaderProps } from 'antd'; +import { type ReactNode, forwardRef, useImperativeHandle, useState, useEffect } from 'react'; +import { Form, Cascader, App, type CascaderProps, Space } from 'antd'; import { useTranslation } from 'react-i18next'; import type { SubModelModalForm, SubModelModalRef, SubModelModalProps } from './types'; @@ -19,6 +19,7 @@ import RbModal from '@/components/RbModal' import CustomSelect from '@/components/CustomSelect' import { modelProviderUrl, getModelNewList } from '@/api/models' import type { ProviderModelItem } from '../../types' +import Tag from '@/components/Tag'; const { SHOW_CHILD } = Cascader; @@ -27,7 +28,7 @@ const { SHOW_CHILD } = Cascader; */ interface Option { value: string | number; - label: string; + label: string | ReactNode; children?: Option[]; [key: string]: any; } @@ -116,7 +117,11 @@ const SubModelModal = forwardRef(({ })) return { ...vo, - label: vo.name, + label: + {vo.name} + {t(`modelNew.${vo.type}`)} + {vo.capability?.filter(item => item !== 'video').map(vo => {t(`modelNew.${vo}`)})} + , value: vo.id, children: children } diff --git a/web/src/views/ModelManagement/components/ModelListDetail.tsx b/web/src/views/ModelManagement/components/ModelListDetail.tsx index aad7b887..5291d5c4 100644 --- a/web/src/views/ModelManagement/components/ModelListDetail.tsx +++ b/web/src/views/ModelManagement/components/ModelListDetail.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:49:45 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:49:45 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-04 11:50:47 */ /** * Model List Detail Drawer @@ -133,9 +133,10 @@ const ModelListDetail = forwardRef(({ + subTitle={ {t(`modelNew.${item.type}`)} {item.api_keys.length}{t('modelNew.apiKeyNum')} + {item.capability?.filter(item => item !=='video').map(vo => {t(`modelNew.${vo}`)})} } avatarUrl={getLogoUrl(item.logo)} avatar={ diff --git a/web/src/views/ModelManagement/components/ModelSquareDetail.tsx b/web/src/views/ModelManagement/components/ModelSquareDetail.tsx index 4fee5a7b..6826e9f5 100644 --- a/web/src/views/ModelManagement/components/ModelSquareDetail.tsx +++ b/web/src/views/ModelManagement/components/ModelSquareDetail.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:49:49 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:54:26 + * @Last Modified time: 2026-03-04 11:50:31 */ /** * Model Square Detail Drawer @@ -89,9 +89,10 @@ const ModelSquareDetail = forwardRef - {t(`modelNew.${item.type}`)} - {item.is_official && {t(`modelNew.official`)}} + subTitle={ + {t(`modelNew.${item.type}`)} + {item.is_official && {t(`modelNew.official`)}} + {item.capability?.filter(item => item !== 'video').map(vo => {t(`modelNew.${vo}`)})} } avatarUrl={getLogoUrl(item.logo)} avatar={ diff --git a/web/src/views/ModelManagement/types.ts b/web/src/views/ModelManagement/types.ts index e7e1f9ac..3233353b 100644 --- a/web/src/views/ModelManagement/types.ts +++ b/web/src/views/ModelManagement/types.ts @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:50:18 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:50:18 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-04 11:39:20 */ /** * Type definitions for Model Management @@ -148,7 +148,9 @@ export interface ModelListItem { /** Update timestamp */ updated_at: number; /** Associated API keys */ - api_keys: ModelApiKey[] + api_keys: ModelApiKey[]; + capability?: string[]; + is_omni?: boolean; } /** @@ -261,6 +263,8 @@ export interface ModelPlazaItem { add_count: number; /** Whether user has added this model */ is_added: boolean; + capability?: string[]; + is_omni?: boolean; } /** @@ -291,6 +295,9 @@ export interface CustomModelForm { /** API base URL */ api_base: string; }> + is_vision?: boolean; + is_omni?: boolean; + capability?: string[]; } /**