feat(web): model add is_vision/is_omni config
This commit is contained in:
@@ -603,7 +603,13 @@ export const en = {
|
|||||||
ollama: "Ollama",
|
ollama: "Ollama",
|
||||||
xinference: "Xinference",
|
xinference: "Xinference",
|
||||||
gpustack: "Gpustack",
|
gpustack: "Gpustack",
|
||||||
bedrock: "Bedrock"
|
bedrock: "Bedrock",
|
||||||
|
|
||||||
|
is_vision: 'Vision Support',
|
||||||
|
is_omni: 'Omni Support',
|
||||||
|
vision: 'Vision',
|
||||||
|
audio: 'Audio',
|
||||||
|
video: 'Video',
|
||||||
},
|
},
|
||||||
knowledgeBase: {
|
knowledgeBase: {
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
|
|||||||
@@ -1184,6 +1184,12 @@ export const zh = {
|
|||||||
xinference: "Xinference",
|
xinference: "Xinference",
|
||||||
gpustack: "Gpustack",
|
gpustack: "Gpustack",
|
||||||
bedrock: "Bedrock",
|
bedrock: "Bedrock",
|
||||||
|
|
||||||
|
is_vision: '支持视觉',
|
||||||
|
is_omni: '支持全模态',
|
||||||
|
vision: '视觉',
|
||||||
|
audio: '音频',
|
||||||
|
video: '视频',
|
||||||
},
|
},
|
||||||
timezones: {
|
timezones: {
|
||||||
'Asia/Shanghai': '中国标准时间 (UTC+8)',
|
'Asia/Shanghai': '中国标准时间 (UTC+8)',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:49:28
|
* @Date: 2026-02-03 16:49:28
|
||||||
* @Last Modified by: ZhaoYing
|
* @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
|
* Custom Model Modal
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
* Supports logo upload, type/provider selection, and tagging
|
* Supports logo upload, type/provider selection, and tagging
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App } from 'antd';
|
import { Form, Input, App, Checkbox } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps } from '../types';
|
import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps } from '../types';
|
||||||
@@ -35,6 +35,14 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const [form] = Form.useForm<CustomModelForm>();
|
const [form] = Form.useForm<CustomModelForm>();
|
||||||
const [loading, setLoading] = useState(false)
|
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 */
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -49,9 +57,12 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
if (model) {
|
if (model) {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
setModel(model);
|
setModel(model);
|
||||||
|
const { capability, is_omni, ...rest} = model
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...model,
|
...rest,
|
||||||
logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined
|
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 {
|
} else {
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
@@ -79,9 +90,14 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
.then((values) => {
|
.then((values) => {
|
||||||
const { logo, ...rest } = values;
|
const { logo, type, is_vision, is_omni, ...rest } = values;
|
||||||
const formData: CustomModelForm = {
|
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) {
|
if (typeof logo === 'object' && logo?.response?.data.file_id) {
|
||||||
@@ -108,7 +124,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
console.log('modelType', modelType)
|
||||||
return (
|
return (
|
||||||
<RbModal
|
<RbModal
|
||||||
title={isEdit ? `${model.name} - ${t('modelNew.modelConfiguration')}` : t('modelNew.createCustomModel')}
|
title={isEdit ? `${model.name} - ${t('modelNew.modelConfiguration')}` : t('modelNew.createCustomModel')}
|
||||||
@@ -180,7 +196,6 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["api_keys", 0, "api_key"]}
|
name={["api_keys", 0, "api_key"]}
|
||||||
label={t('modelNew.api_key')}
|
label={t('modelNew.api_key')}
|
||||||
@@ -196,6 +211,17 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
>
|
>
|
||||||
<Input placeholder="https://api.example.com/v1" />
|
<Input placeholder="https://api.example.com/v1" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
{!['embedding', 'rerank'].includes(modelType as string) &&
|
||||||
|
<>
|
||||||
|
<Form.Item name="is_omni" valuePropName="checked" className="rb:mb-2!">
|
||||||
|
<Checkbox>{t('modelNew.is_omni')}</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="is_vision" valuePropName="checked" className="rb:mb-0!">
|
||||||
|
<Checkbox disabled={isOmni}>{t('modelNew.is_vision')}</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
</RbModal>
|
</RbModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:49:20
|
* @Date: 2026-02-03 16:49:20
|
||||||
* @Last Modified by: ZhaoYing
|
* @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
|
* Sub-Model Modal
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
* Uses cascader for hierarchical selection
|
* Uses cascader for hierarchical selection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
import { type ReactNode, forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
import { Form, Cascader, App, type CascaderProps } from 'antd';
|
import { Form, Cascader, App, type CascaderProps, Space } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { SubModelModalForm, SubModelModalRef, SubModelModalProps } from './types';
|
import type { SubModelModalForm, SubModelModalRef, SubModelModalProps } from './types';
|
||||||
@@ -19,6 +19,7 @@ import RbModal from '@/components/RbModal'
|
|||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
import { modelProviderUrl, getModelNewList } from '@/api/models'
|
import { modelProviderUrl, getModelNewList } from '@/api/models'
|
||||||
import type { ProviderModelItem } from '../../types'
|
import type { ProviderModelItem } from '../../types'
|
||||||
|
import Tag from '@/components/Tag';
|
||||||
|
|
||||||
const { SHOW_CHILD } = Cascader;
|
const { SHOW_CHILD } = Cascader;
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ const { SHOW_CHILD } = Cascader;
|
|||||||
*/
|
*/
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
label: string;
|
label: string | ReactNode;
|
||||||
children?: Option[];
|
children?: Option[];
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,11 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
}))
|
}))
|
||||||
return {
|
return {
|
||||||
...vo,
|
...vo,
|
||||||
label: vo.name,
|
label: <Space>
|
||||||
|
{vo.name}
|
||||||
|
<Tag>{t(`modelNew.${vo.type}`)}</Tag>
|
||||||
|
{vo.capability?.filter(item => item !== 'video').map(vo => <Tag key={vo}>{t(`modelNew.${vo}`)}</Tag>)}
|
||||||
|
</Space>,
|
||||||
value: vo.id,
|
value: vo.id,
|
||||||
children: children
|
children: children
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:49:45
|
* @Date: 2026-02-03 16:49:45
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 16:49:45
|
* @Last Modified time: 2026-03-04 11:50:47
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Model List Detail Drawer
|
* Model List Detail Drawer
|
||||||
@@ -133,9 +133,10 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
|||||||
<RbCard
|
<RbCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
subTitle={<Space className="rb:mt-1!">
|
subTitle={<Space size={8} className="rb:mt-1!">
|
||||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||||
<Tag color="warning">{item.api_keys.length}{t('modelNew.apiKeyNum')}</Tag>
|
<Tag color="warning">{item.api_keys.length}{t('modelNew.apiKeyNum')}</Tag>
|
||||||
|
{item.capability?.filter(item => item !=='video').map(vo => <Tag key={vo}>{t(`modelNew.${vo}`)}</Tag>)}
|
||||||
</Space>}
|
</Space>}
|
||||||
avatarUrl={getLogoUrl(item.logo)}
|
avatarUrl={getLogoUrl(item.logo)}
|
||||||
avatar={
|
avatar={
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:49:49
|
* @Date: 2026-02-03 16:49:49
|
||||||
* @Last Modified by: ZhaoYing
|
* @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
|
* Model Square Detail Drawer
|
||||||
@@ -89,9 +89,10 @@ const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProp
|
|||||||
<RbCard
|
<RbCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
subTitle={<Space size={8}>
|
subTitle={<Space size={8} className="rb:mt-1!">
|
||||||
<Tag className="rb:mt-1">{t(`modelNew.${item.type}`)}</Tag>
|
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||||
{item.is_official && <Tag color="success" className="rb:mt-1">{t(`modelNew.official`)}</Tag>}
|
{item.is_official && <Tag color="success">{t(`modelNew.official`)}</Tag>}
|
||||||
|
{item.capability?.filter(item => item !== 'video').map(vo => <Tag key={vo}>{t(`modelNew.${vo}`)}</Tag>)}
|
||||||
</Space>}
|
</Space>}
|
||||||
avatarUrl={getLogoUrl(item.logo)}
|
avatarUrl={getLogoUrl(item.logo)}
|
||||||
avatar={
|
avatar={
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:50:18
|
* @Date: 2026-02-03 16:50:18
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 16:50:18
|
* @Last Modified time: 2026-03-04 11:39:20
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Type definitions for Model Management
|
* Type definitions for Model Management
|
||||||
@@ -148,7 +148,9 @@ export interface ModelListItem {
|
|||||||
/** Update timestamp */
|
/** Update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
/** Associated API keys */
|
/** Associated API keys */
|
||||||
api_keys: ModelApiKey[]
|
api_keys: ModelApiKey[];
|
||||||
|
capability?: string[];
|
||||||
|
is_omni?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,6 +263,8 @@ export interface ModelPlazaItem {
|
|||||||
add_count: number;
|
add_count: number;
|
||||||
/** Whether user has added this model */
|
/** Whether user has added this model */
|
||||||
is_added: boolean;
|
is_added: boolean;
|
||||||
|
capability?: string[];
|
||||||
|
is_omni?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,6 +295,9 @@ export interface CustomModelForm {
|
|||||||
/** API base URL */
|
/** API base URL */
|
||||||
api_base: string;
|
api_base: string;
|
||||||
}>
|
}>
|
||||||
|
is_vision?: boolean;
|
||||||
|
is_omni?: boolean;
|
||||||
|
capability?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user