feat(web): update model management
This commit is contained in:
97
web/src/views/ModelManagement/Group.tsx
Normal file
97
web/src/views/ModelManagement/Group.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import clsx from 'clsx'
|
||||
import { Button } from 'antd'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ProviderModelItem, ModelListItem, DescriptionItem, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import { getModelNewList } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
|
||||
const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem) => void; }>(({ query, handleEdit }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [list, setList] = useState<ModelListItem[]>([])
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [query])
|
||||
const getList = () => {
|
||||
getModelNewList({
|
||||
...query,
|
||||
is_composite: true,
|
||||
is_active: true,
|
||||
})
|
||||
.then(res => {
|
||||
const response = res as ProviderModelItem[]
|
||||
setList(response[0]?.models || [])
|
||||
})
|
||||
}
|
||||
const formatData = (data: ModelListItem) => {
|
||||
return [
|
||||
{
|
||||
key: 'type',
|
||||
label: t(`modelNew.type`),
|
||||
children: data.type || '-',
|
||||
},
|
||||
{
|
||||
key: 'provider',
|
||||
label: t(`modelNew.provider`),
|
||||
children: data.provider || '-',
|
||||
},
|
||||
{
|
||||
key: 'is_active',
|
||||
label: t(`modelNew.status`),
|
||||
children: data.is_active ? t(`common.statusEnabled`) : t(`common.statusDisabled`),
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
label: t(`modelNew.created_at`),
|
||||
children: data.created_at ? formatDateTime(data.created_at, 'YYYY-MM-DD HH:mm:ss') : '-',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getList,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
:(
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{formatData(item)?.map((description: DescriptionItem) => (
|
||||
<div
|
||||
key={description.key}
|
||||
className="rb:flex rb:justify-between rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap">{(description.label as string)}</span>
|
||||
<span className={clsx({
|
||||
"rb:text-[#212332]": description.key !== 'is_active',
|
||||
"rb:text-[#369F21] rb:font-medium": description.key === 'is_active' && item.is_active,
|
||||
})}>{(description.children as string)}</span>
|
||||
</div>
|
||||
))}
|
||||
<Button className="rb:mt-2" type="primary" ghost block onClick={() => handleEdit(item)}>{t('modelNew.configureBtn')}</Button>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default Group
|
||||
83
web/src/views/ModelManagement/List.tsx
Normal file
83
web/src/views/ModelManagement/List.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useRef, useState, useEffect, type FC } from 'react';
|
||||
import { Button, Space, Row, Col } from 'antd'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ProviderModelItem, KeyConfigModalRef, ModelListDetailRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import { getModelNewList } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import Tag from '@/components/Tag';
|
||||
import KeyConfigModal from './components/KeyConfigModal'
|
||||
import ModelListDetail from './components/ModelListDetail'
|
||||
|
||||
const ModelList: FC<{ query: any }> = ({ query }) => {
|
||||
const { t } = useTranslation();
|
||||
const keyConfigModalRef = useRef<KeyConfigModalRef>(null)
|
||||
const modelListDetailRef = useRef<ModelListDetailRef>(null)
|
||||
const [list, setList] = useState<ProviderModelItem[]>([])
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [query])
|
||||
const getList = () => {
|
||||
getModelNewList({
|
||||
...query,
|
||||
is_composite: false,
|
||||
is_active: true,
|
||||
})
|
||||
.then(res => {
|
||||
setList((res || []) as ProviderModelItem[])
|
||||
})
|
||||
}
|
||||
|
||||
const handleShowModel = (vo: ProviderModelItem) => {
|
||||
modelListDetailRef.current?.handleOpen(vo)
|
||||
}
|
||||
const handleKeyConfig = (vo: ProviderModelItem) => {
|
||||
keyConfigModalRef.current?.handleOpen(vo)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
:(
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.provider}
|
||||
title={item.provider}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.provider[0]}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Space>{item.tags.map(tag => <Tag key={tag}>{t(`modelNew.${tag}`)}</Tag>)}</Space>
|
||||
<Row gutter={12} className="rb:mt-4">
|
||||
<Col span={12}>
|
||||
<Button block onClick={() => handleShowModel(item)}>{t('modelNew.showModel')}</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button type="primary" ghost block onClick={() => handleKeyConfig(item)}>{t('modelNew.keyConfig')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<KeyConfigModal
|
||||
ref={keyConfigModalRef}
|
||||
refresh={getList}
|
||||
/>
|
||||
<ModelListDetail
|
||||
ref={modelListDetailRef}
|
||||
refresh={getList}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelList
|
||||
94
web/src/views/ModelManagement/Square.tsx
Normal file
94
web/src/views/ModelManagement/Square.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { Button, Space, App, Divider, Flex } from 'antd'
|
||||
import { UsergroupAddOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import { getModelPlaza, addModelPlaza } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import Tag from '@/components/Tag';
|
||||
import ModelSquareDetail from './components/ModelSquareDetail'
|
||||
|
||||
const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPlazaItem) => void; }>(({ query, handleEdit }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const modelSquareDetailRef = useRef<ModelSquareDetailRef>(null)
|
||||
const [list, setList] = useState<ModelPlaza[]>([])
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [query])
|
||||
const getList = () => {
|
||||
getModelPlaza(query)
|
||||
.then(res => {
|
||||
setList((res as ModelPlaza[]) || [])
|
||||
})
|
||||
}
|
||||
|
||||
const handleMore = (vo: ModelPlaza) => {
|
||||
modelSquareDetailRef.current?.handleOpen(vo)
|
||||
}
|
||||
const handleAdd = (item: ModelPlazaItem) => {
|
||||
addModelPlaza(item.id)
|
||||
.then(() => {
|
||||
message.success(`${item.name}${t('modelNew.addSuccess')}`)
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getList,
|
||||
}));
|
||||
return (
|
||||
<>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
: list.map(vo => (
|
||||
<div key={vo.provider}>
|
||||
<div className="rb:flex rb:justify-between rb:items-center rb:bg-[rgba(21,94,239,0.12)] rb:px-4 rb:py-2.5 rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">
|
||||
<div className="rb:font-medium">{vo.provider}</div>
|
||||
<Button type="link" onClick={() => handleMore(vo)}>{t('modelNew.viewAll')}({t(`modelNew.modelCount`, { count: vo.models.length })})></Button>
|
||||
</div>
|
||||
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-4">
|
||||
{vo.models.slice(0, 6).map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:mt-3 rb:h-9">{item.description}</div>
|
||||
<Space size={8} className="rb:mt-3">{item.tags.map((tag, tagIndex) => <Tag key={tagIndex}>{tag}</Tag>)}</Space>
|
||||
<Divider size="middle" />
|
||||
<Flex justify="space-between">
|
||||
<Space size={8}><UsergroupAddOutlined /> {item.add_count}</Space>
|
||||
<Space>
|
||||
{!item.is_official && <Button type="primary" disabled={item.is_deprecated} onClick={() => handleEdit(item)}>{t('modelNew.edit')}</Button>}
|
||||
{item.is_added
|
||||
? <Button type="primary" disabled>{t('modelNew.added')}</Button>
|
||||
: <Button type="primary" ghost disabled={item.is_deprecated} onClick={() => handleAdd(item)}>+ {t('common.add')}</Button>
|
||||
}
|
||||
</Space>
|
||||
</Flex>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
<ModelSquareDetail
|
||||
ref={modelSquareDetailRef}
|
||||
refresh={getList}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default ModelSquare
|
||||
@@ -1,171 +0,0 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ModelFormData, Model, ConfigModalRef, ConfigModalProps } from '../types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { updateModel, addModel, modelTypeUrl, modelProviderUrl } from '@/api/models'
|
||||
|
||||
const ConfigModal = forwardRef<ConfigModalRef, ConfigModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [model, setModel] = useState<Model>({} as Model);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [form] = Form.useForm<ModelFormData>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const values = Form.useWatch<ModelFormData>([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setModel({} as Model);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (model?: Model) => {
|
||||
if (model) {
|
||||
setIsEdit(true);
|
||||
setModel(model);
|
||||
// 设置表单值
|
||||
const apiKeyInfo = model.api_keys[0]
|
||||
form.setFieldsValue({
|
||||
provider: apiKeyInfo.provider,
|
||||
model_name: apiKeyInfo.model_name,
|
||||
api_key: apiKeyInfo.api_key,
|
||||
api_base: apiKeyInfo.api_base
|
||||
});
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
form.resetFields();
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
const data = {
|
||||
name: values.name,
|
||||
type: values.type,
|
||||
api_keys: {
|
||||
provider: values.provider,
|
||||
model_name: values.model_name,
|
||||
api_key: values.api_key,
|
||||
api_base: values.api_base
|
||||
},
|
||||
}
|
||||
setLoading(true)
|
||||
const res = isEdit
|
||||
? updateModel(model.api_keys[0].id, {
|
||||
provider: values.provider,
|
||||
model_name: values.model_name,
|
||||
api_key: values.api_key,
|
||||
api_base: values.api_base
|
||||
} as ModelFormData)
|
||||
: addModel(data as ModelFormData)
|
||||
|
||||
res.then(() => {
|
||||
if (refresh) {
|
||||
refresh();
|
||||
}
|
||||
handleClose()
|
||||
message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={isEdit ? `${model.name} - ${t('model.modelConfiguration')}` : t('model.createModel')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t(`common.${isEdit ? 'save' : 'create'}`)}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{}}
|
||||
>
|
||||
{!isEdit && (
|
||||
<>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('model.displayName')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('model.displayName') }) }]}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="type"
|
||||
label={t('model.type')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('model.type') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={modelTypeUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`model.${item}`), value: item }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<Form.Item
|
||||
name="provider"
|
||||
label={t('model.provider')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('model.provider') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={modelProviderUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`model.${item}`), value: item }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="model_name"
|
||||
label={t('model.modelName')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('model.modelName') }) }]}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="api_key"
|
||||
label={t('model.apiKey')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('model.apiKey') }) }]}
|
||||
>
|
||||
<Input.Password placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="api_base"
|
||||
label={t('model.apiEndpoint')}
|
||||
>
|
||||
<Input placeholder="https://api.example.com/v1" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ConfigModal;
|
||||
162
web/src/views/ModelManagement/components/CustomModelModal.tsx
Normal file
162
web/src/views/ModelManagement/components/CustomModelModal.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App, Select } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { CustomModelForm, ModelPlazaItem, CustomModelModalRef, CustomModelModalProps } from '../types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import UploadImages from '@/components/Upload/UploadImages'
|
||||
import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models'
|
||||
import { getFileLink } from '@/api/fileStorage'
|
||||
|
||||
const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [model, setModel] = useState<ModelPlazaItem>({} as ModelPlazaItem);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [form] = Form.useForm<CustomModelForm>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const formValues = Form.useWatch([], form)
|
||||
|
||||
const handleClose = () => {
|
||||
setModel({} as ModelPlazaItem);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (model?: ModelPlazaItem) => {
|
||||
if (model) {
|
||||
setIsEdit(true);
|
||||
setModel(model);
|
||||
form.setFieldsValue({
|
||||
...model,
|
||||
});
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
form.resetFields();
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
setLoading(true)
|
||||
values.is_official = false;
|
||||
const logo = values.logo as any;
|
||||
if (typeof logo === 'object') {
|
||||
getFileLink(logo?.response?.data.file_id).then(res => {
|
||||
const logoRes = res as { url: string }
|
||||
values.logo = logoRes.url.replace('http://127.0.0.1:8000', 'https://devmemorybear.redbearai.com')
|
||||
addCustomModel(values).then(() => {
|
||||
if (refresh) {
|
||||
refresh();
|
||||
}
|
||||
handleClose()
|
||||
message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
})
|
||||
} else {
|
||||
updateCustomModel(model.id, values).then(() => {
|
||||
if (refresh) {
|
||||
refresh();
|
||||
}
|
||||
handleClose()
|
||||
message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
console.log('formValues', formValues)
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={isEdit ? `${model.name} - ${t('modelNew.modelConfiguration')}` : t('modelNew.createCustomModel')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t(`common.${isEdit ? 'save' : 'create'}`)}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
{!isEdit && <Form.Item
|
||||
name="logo"
|
||||
label={t('modelNew.logo')}
|
||||
valuePropName="fileList"
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<UploadImages />
|
||||
</Form.Item>}
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('modelNew.model_name')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.displayName') }) }]}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="type"
|
||||
label={t('modelNew.type')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('modelNew.type') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={modelTypeUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="provider"
|
||||
label={t('modelNew.provider')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('modelNew.provider') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={modelProviderUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label={t('modelNew.description')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.description') }) }]}
|
||||
>
|
||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="tags"
|
||||
label={t('modelNew.tags')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.tags') }) }]}
|
||||
>
|
||||
<Select mode="tags" placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default CustomModelModal;
|
||||
155
web/src/views/ModelManagement/components/GroupModelModal.tsx
Normal file
155
web/src/views/ModelManagement/components/GroupModelModal.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ModelListItem, CompositeModelForm, GroupModelModalRef, GroupModelModalProps, ModelApiKey } from '../types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { updateCompositeModel, modelTypeUrl, addCompositeModel } from '@/api/models'
|
||||
import UploadImages from '@/components/Upload/UploadImages'
|
||||
import ModelImplement from './ModelImplement'
|
||||
import { getFileLink } from '@/api/fileStorage'
|
||||
|
||||
const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [model, setModel] = useState<ModelListItem>({} as ModelListItem);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [form] = Form.useForm<CompositeModelForm>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const type = Form.useWatch(['type'], form)
|
||||
|
||||
const handleClose = () => {
|
||||
setModel({} as ModelListItem);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (model?: ModelListItem) => {
|
||||
if (model) {
|
||||
setIsEdit(true);
|
||||
setModel(model);
|
||||
form.setFieldsValue({
|
||||
...model,
|
||||
api_key_ids: model.api_keys,
|
||||
logo: model.logo ? [{url: model.logo, uid: model.logo, status: 'done', name: 'logo'}] : undefined
|
||||
})
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
form.resetFields();
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
const { api_key_ids, logo, ...rest } = values
|
||||
|
||||
const formData: CompositeModelForm = {
|
||||
...rest,
|
||||
api_key_ids: api_key_ids.map(vo => (vo as ModelApiKey).id)
|
||||
}
|
||||
|
||||
if (logo?.response?.data.file_id) {
|
||||
getFileLink(logo?.response?.data.file_id).then(res => {
|
||||
const logoRes = res as { url: string }
|
||||
formData.logo = logoRes.url.replace('http://127.0.0.1:8000', 'https://devmemorybear.redbearai.com')
|
||||
handleUpdate(formData)
|
||||
})
|
||||
} else {
|
||||
handleUpdate(formData)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
const handleUpdate = (data: CompositeModelForm) => {
|
||||
setLoading(true)
|
||||
const res = isEdit
|
||||
? updateCompositeModel(model.id, data)
|
||||
: addCompositeModel(data)
|
||||
|
||||
res.then(() => {
|
||||
refresh?.();
|
||||
handleClose()
|
||||
message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={isEdit ? `${model.name} - ${t('modelNew.modelConfiguration')}` : t('modelNew.createGroupModel')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t(`common.${isEdit ? 'save' : 'create'}`)}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="logo"
|
||||
label={t('modelNew.logo')}
|
||||
valuePropName="fileList"
|
||||
// rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<UploadImages />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('modelNew.name')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="type"
|
||||
label={t('modelNew.type')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('modelNew.type') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={modelTypeUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({
|
||||
label: t(`modelNew.${typeof item === 'object' ? item.value : item}`),
|
||||
value: typeof item === 'object' ? item.value : item
|
||||
}))}
|
||||
disabled={isEdit}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label={t('modelNew.description')}
|
||||
>
|
||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="api_key_ids">
|
||||
<ModelImplement type={type} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default GroupModelModal;
|
||||
92
web/src/views/ModelManagement/components/KeyConfigModal.tsx
Normal file
92
web/src/views/ModelManagement/components/KeyConfigModal.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { KeyConfigModalForm, ProviderModelItem, KeyConfigModalRef, KeyConfigModalProps } from '../types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { updateProviderApiKeys } from '@/api/models'
|
||||
|
||||
const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [model, setModel] = useState<ProviderModelItem>({} as ProviderModelItem);
|
||||
const [form] = Form.useForm<KeyConfigModalForm>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleClose = () => {
|
||||
setModel({} as ProviderModelItem);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (vo: ProviderModelItem) => {
|
||||
setVisible(true);
|
||||
setModel(vo);
|
||||
};
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
setLoading(true)
|
||||
|
||||
updateProviderApiKeys({
|
||||
...values,
|
||||
provider: model.provider
|
||||
}).then(() => {
|
||||
if (refresh) {
|
||||
refresh();
|
||||
}
|
||||
handleClose()
|
||||
message.success(t('common.updateSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={`${model.provider} - ${t('modelNew.keyConfig')}`}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t(`common.save`)}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="api_key"
|
||||
label={t('modelNew.api_key')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.apiKey') }) }]}
|
||||
>
|
||||
<Input.Password placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="api_base"
|
||||
label={t('modelNew.api_base')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.api_base') }) }]}
|
||||
>
|
||||
<Input placeholder="https://api.example.com/v1" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default KeyConfigModal;
|
||||
@@ -0,0 +1,173 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Cascader, App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { SubModelModalForm, SubModelModalRef, SubModelModalProps, ModelList } from './types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { modelProviderUrl, getModelNewList } from '@/api/models'
|
||||
import type { ProviderModelItem } from '../../types'
|
||||
|
||||
const { SHOW_CHILD } = Cascader;
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
label: string;
|
||||
children?: Option[];
|
||||
[key: string]: any;
|
||||
}
|
||||
const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
||||
refresh,
|
||||
type
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<SubModelModalForm>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selecteds, setSelecteds] = useState<any[]>([])
|
||||
const [modelList, setModelList] = useState<Option[]>([])
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
setSelecteds([])
|
||||
};
|
||||
|
||||
const handleOpen = (list?: ModelList[], provider?: string) => {
|
||||
if (list?.length && provider) {
|
||||
const initialValue: SubModelModalForm = {
|
||||
provider,
|
||||
api_key_ids: list.map(vo => {
|
||||
return [vo.model_config_ids[0], vo.id]
|
||||
})
|
||||
}
|
||||
|
||||
form.setFieldsValue(initialValue);
|
||||
handleChangeProvider(provider, initialValue.api_key_ids)
|
||||
} else {
|
||||
form.resetFields()
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
console.log('SubModelModal values', values, selecteds, selecteds.map(vo => ({
|
||||
...vo[0],
|
||||
model_name: vo[0].name,
|
||||
model_config_ids: [vo[0].id],
|
||||
id: vo[1].value
|
||||
})))
|
||||
refresh?.(selecteds.map(vo => ({
|
||||
...vo[0],
|
||||
model_name: vo[0].name,
|
||||
model_config_ids: [vo[0].id],
|
||||
id: vo[1].value
|
||||
})))
|
||||
handleClose()
|
||||
})
|
||||
}
|
||||
const handleChange = (value: (string | number)[][], selectedOptions: Option[][]) => {
|
||||
const filterList = selectedOptions.filter(vo => vo.length === 1).map(item => item[0])
|
||||
const lastFilterLit = value.filter(vo => vo.length !== 1)
|
||||
console.log('onchange', value, lastFilterLit, selectedOptions, filterList)
|
||||
if (filterList.length) {
|
||||
message.warning(`【${filterList.map(vo => vo.label)}】${t('modelNew.selectOneTip')}`)
|
||||
form.setFieldValue('api_key_ids', lastFilterLit)
|
||||
}
|
||||
setSelecteds(selectedOptions)
|
||||
}
|
||||
|
||||
const handleChangeProvider = (provider: string, api_key_ids?: any[]) => {
|
||||
form.setFieldValue('api_key_ids', undefined)
|
||||
getModelNewList({
|
||||
provider: provider,
|
||||
is_composite: false,
|
||||
is_active: true,
|
||||
type
|
||||
})
|
||||
.then(res => {
|
||||
const response = res as ProviderModelItem[]
|
||||
const list = response[0]?.models || []
|
||||
setModelList(list.map(vo => {
|
||||
const children = vo.api_keys.map(item => ({
|
||||
label: item.api_key,
|
||||
value: item.id,
|
||||
}))
|
||||
return {
|
||||
...vo,
|
||||
label: vo.name,
|
||||
value: vo.id,
|
||||
children: children
|
||||
}
|
||||
}))
|
||||
|
||||
if (api_key_ids?.length) {
|
||||
form.setFieldsValue({
|
||||
api_key_ids: api_key_ids
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('modelNew.implementConfig')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="provider"
|
||||
label={t('modelNew.provider')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('modelNew.provider') }) }]}
|
||||
>
|
||||
<CustomSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
url={modelProviderUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({
|
||||
label: t(`modelNew.${typeof item === 'object' ? item.value : item}`),
|
||||
value: typeof item === 'object' ? item.value : item
|
||||
}))}
|
||||
onChange={(value) => handleChangeProvider(value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="api_key_ids"
|
||||
label={t('modelNew.api_key_ids')}
|
||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('modelNew.api_key_ids') }) }]}
|
||||
>
|
||||
<Cascader
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={modelList}
|
||||
onChange={handleChange}
|
||||
multiple
|
||||
autoClearSearchValue
|
||||
className="rb:w-full!"
|
||||
showCheckedStrategy={SHOW_CHILD}
|
||||
changeOnSelect
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default SubModelModal;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { type FC, useRef } from "react";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flex, Button, Space, App } from 'antd'
|
||||
|
||||
import type { SubModelModalRef, ModelList } from './types'
|
||||
import SubModelModal from './SubModelModal'
|
||||
import Empty from '@/components/Empty'
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
interface ModelImplementProps {
|
||||
type?: string;
|
||||
value?: any;
|
||||
onChange?: (value: any) => void;
|
||||
}
|
||||
const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const { modal, message } = App.useApp();
|
||||
const subModelModalRef = useRef<SubModelModalRef>(null)
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!type || type.trim() === '') {
|
||||
message.warning(t('common.selectPlaceholder', { title: t('modelNew.type') }))
|
||||
return
|
||||
}
|
||||
subModelModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleEdit = (list: ModelList[], provider: string ) => {
|
||||
subModelModalRef.current?.handleOpen(list, provider)
|
||||
}
|
||||
const handleDelete = (provider: string) => {
|
||||
modal.confirm({
|
||||
title: t('common.confirmDeleteDesc', { name: provider }),
|
||||
content: t('application.apiKeyDeleteContent'),
|
||||
okText: t('common.delete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okType: 'danger',
|
||||
onOk: () => {
|
||||
onChange?.(value?.filter((item: any) => item.provider !== provider))
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleRefresh = (list: ModelList[]) => {
|
||||
const existingModels = value || [];
|
||||
let updatedModels = [...existingModels];
|
||||
|
||||
const provider = list[0].provider
|
||||
|
||||
updatedModels = updatedModels.filter(item => item.provider !== provider)
|
||||
updatedModels = [...updatedModels, ...list]
|
||||
|
||||
onChange?.([...updatedModels]);
|
||||
}
|
||||
|
||||
const groupedByProvider: Record<string, ModelList[]> = (value || []).reduce((acc: Record<string, ModelList[]>, item: ModelList) => {
|
||||
const provider = item.provider || 'unknown';
|
||||
if (!acc[provider]) acc[provider] = [];
|
||||
acc[provider].push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, ModelList[]>);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Flex justify="space-between" align="center">
|
||||
{t('modelNew.modelImplement')}
|
||||
|
||||
<Space>
|
||||
<Button type="primary" onClick={handleAdd} className="rb:px-2! rb:h-6!">+ {t('modelNew.addImplement')}</Button>
|
||||
<Button size="small" className="rb:px-2! rb:h-6!">{t('modelNew.noAuth')}</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
|
||||
<div className="rb:bg-[#F5F6F7] rb:rounded-lg rb:p-3 rb:mt-2">
|
||||
{!value || value.length === 0
|
||||
? <Empty size={88} />
|
||||
: Object.entries(groupedByProvider).map(([provider, items]: [string, ModelList[]]) => {
|
||||
return (
|
||||
<div key={provider} className="rb:mb-4 last:rb:mb-0">
|
||||
<Flex justify="space-between" align="center" className="rb:mb-2 last:rb:mb-0">
|
||||
<div className="rb:font-medium">{[...new Set(items?.map((vo) => vo.model_name))].join(', ')}</div>
|
||||
<Space>
|
||||
<div
|
||||
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||
onClick={() => handleEdit(items, provider)}
|
||||
></div>
|
||||
<div
|
||||
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||
onClick={() => handleDelete(provider)}
|
||||
></div>
|
||||
</Space>
|
||||
</Flex>
|
||||
<Tag className="rb:mb-2">{provider}</Tag>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<SubModelModal
|
||||
ref={subModelModalRef}
|
||||
refresh={handleRefresh}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelImplement
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ModelListItem } from '../../types'
|
||||
|
||||
export interface ModelList extends ModelListItem {
|
||||
api_key_id: string;
|
||||
}
|
||||
export interface SubModelModalForm {
|
||||
provider: string;
|
||||
api_key_ids: string[][];
|
||||
}
|
||||
export interface SubModelModalRef {
|
||||
handleOpen: (list?: ModelList[], provider?: string) => void;
|
||||
}
|
||||
export interface SubModelModalProps {
|
||||
type?: string;
|
||||
refresh?: (vo: ModelList[]) => void;
|
||||
}
|
||||
111
web/src/views/ModelManagement/components/ModelListDetail.tsx
Normal file
111
web/src/views/ModelManagement/components/ModelListDetail.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { useState, useImperativeHandle, forwardRef, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Switch, Row, Col, Space } from 'antd'
|
||||
|
||||
import type { ProviderModelItem, ModelListItem, ModelListDetailRef, MultiKeyConfigModalRef } from '../types';
|
||||
import RbDrawer from '@/components/RbDrawer';
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import Tag from '@/components/Tag';
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import MultiKeyConfigModal from './MultiKeyConfigModal'
|
||||
import { getModelNewList, updateModelStatus } from '@/api/models'
|
||||
|
||||
interface ModelListDetailProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
|
||||
const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({ refresh }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [data, setData] = useState<ProviderModelItem>({} as ProviderModelItem)
|
||||
const [list, setList] = useState<ModelListItem[]>([])
|
||||
const multiKeyConfigModalRef = useRef<MultiKeyConfigModalRef>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleOpen = (vo: ProviderModelItem) => {
|
||||
setOpen(true)
|
||||
getData(vo)
|
||||
}
|
||||
|
||||
const getData = (vo: ProviderModelItem) => {
|
||||
if (!vo.provider) return
|
||||
|
||||
getModelNewList({
|
||||
provider: vo.provider
|
||||
})
|
||||
.then(res => {
|
||||
const response = res as ProviderModelItem[]
|
||||
setData(response[0])
|
||||
setList(response[0].models)
|
||||
})
|
||||
}
|
||||
const handleKeyConfig = (vo: ModelListItem) => {
|
||||
multiKeyConfigModalRef.current?.handleOpen(vo, data.provider)
|
||||
}
|
||||
const handleChange = (vo: ModelListItem) => {
|
||||
setLoading(true)
|
||||
updateModelStatus(vo.id, { is_active: !vo.is_active })
|
||||
.finally(() => {
|
||||
getData(data)
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
refresh?.()
|
||||
}
|
||||
const handleRefresh = () => {
|
||||
getData(data)
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbDrawer
|
||||
title={<>{data.provider} {t('modelNew.modelList')} ({list.length}{t('modelNew.item')})</>}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
: <div className="rb:grid rb:grid-cols-2 rb:gap-4">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
subTitle={<Space>
|
||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||
<Tag color="warning">{item.api_keys.length}{t('modelNew.apiKeyNum')}</Tag>
|
||||
</Space>}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
extra={<Switch defaultChecked={item.is_active} disabled={loading} onChange={() => handleChange(item)} />}
|
||||
>
|
||||
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:mt-3">{item.description}</div>
|
||||
<Row gutter={12} className="rb:mt-4">
|
||||
<Col span={24}>
|
||||
<Button type="primary" ghost block onClick={() => handleKeyConfig(item)}>{t('modelNew.keyConfig')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
||||
<MultiKeyConfigModal
|
||||
ref={multiKeyConfigModalRef}
|
||||
refresh={handleRefresh}
|
||||
/>
|
||||
</RbDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModelListDetail;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Space, App } from 'antd'
|
||||
|
||||
import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef } from '../types';
|
||||
import RbDrawer from '@/components/RbDrawer';
|
||||
import { getModelPlaza, addModelPlaza } from '@/api/models'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import Tag from '@/components/Tag';
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
|
||||
interface ModelSquareDetailProps {
|
||||
refresh: () => void;
|
||||
}
|
||||
const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProps>(({ refresh }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const [model, setModel] = useState<ModelPlaza>({} as ModelPlaza)
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [list, setList] = useState<ModelPlazaItem[]>([])
|
||||
|
||||
const handleOpen = (vo: ModelPlaza) => {
|
||||
setModel(vo)
|
||||
setOpen(true)
|
||||
getList(vo)
|
||||
}
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
refresh()
|
||||
}
|
||||
const getList = (vo: ModelPlaza) => {
|
||||
getModelPlaza({ provider: vo.provider })
|
||||
.then(res => {
|
||||
const response = res as ModelPlaza[]
|
||||
setList(response.length > 0 ? response[0].models : [])
|
||||
})
|
||||
}
|
||||
const handleAdd = (item: ModelPlazaItem) => {
|
||||
addModelPlaza(item.id)
|
||||
.then(() => {
|
||||
message.success(`${item.name}${t('modelNew.addSuccess')}`)
|
||||
getList(model)
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbDrawer
|
||||
title={<>{model.provider} {t('modelNew.modelList')} ({list.length}{t('modelNew.item')})</>}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
: <div className="rb:grid rb:grid-cols-2 rb:gap-4">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:mt-3 rb:h-9">{item.description}</div>
|
||||
<Space size={8} className="rb:mt-3">{item.tags.map((tag, tagIndex) => <Tag key={tagIndex}>{tag}</Tag>)}</Space>
|
||||
{item.is_added
|
||||
? <Button className="rb:mt-3" type="primary" disabled block>{t('modelNew.added')}</Button>
|
||||
: <Button className="rb:mt-3" type="primary" ghost block onClick={() => handleAdd(item)}>+ {t('common.add')}</Button>
|
||||
}
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</RbDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModelSquareDetail;
|
||||
121
web/src/views/ModelManagement/components/MultiKeyConfigModal.tsx
Normal file
121
web/src/views/ModelManagement/components/MultiKeyConfigModal.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, App, Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ModelListItem, MultiKeyForm, MultiKeyConfigModalRef, MultiKeyConfigModalProps } from '../types';
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { addModelApiKey, delteModelApiKey, getModelInfo } from '@/api/models'
|
||||
|
||||
const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigModalProps>(({ refresh }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [model, setModel] = useState<ModelListItem>({} as ModelListItem);
|
||||
const [form] = Form.useForm<MultiKeyForm>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleClose = () => {
|
||||
setModel({} as ModelListItem);
|
||||
refresh?.()
|
||||
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = (vo: ModelListItem) => {
|
||||
setVisible(true);
|
||||
getData(vo)
|
||||
};
|
||||
|
||||
const getData = (vo: ModelListItem) => {
|
||||
if (!vo.id) return
|
||||
|
||||
getModelInfo(vo?.id)
|
||||
.then(res => {
|
||||
setModel(res as ModelListItem)
|
||||
})
|
||||
}
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
addModelApiKey(model.id, {
|
||||
...values,
|
||||
model_config_id: model.id,
|
||||
model_name: model.name,
|
||||
provider: model.provider,
|
||||
}).then(() => {
|
||||
message.success(t('common.saveSuccess'))
|
||||
form.resetFields();
|
||||
getData(model)
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
const handleDelete = (api_key_id: string) => {
|
||||
delteModelApiKey(api_key_id)
|
||||
.then(() => {
|
||||
message.success(t('common.deleteSuccess'))
|
||||
getData(model)
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={`${model.name} - ${t('modelNew.keyConfig')}`}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={null}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
{model.api_keys && model.api_keys.length > 0 && (
|
||||
<div className="rb:mb-4">
|
||||
{model.api_keys.map((key) => (
|
||||
<div key={key.id} className="rb:flex rb:items-center rb:justify-between rb:p-3 rb:bg-[#F5F6F7] rb:rounded-lg rb:mb-2">
|
||||
<div>
|
||||
<div className="rb:text-[#1D2129] rb:text-[14px] rb:font-medium">{key.api_key}</div>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:mt-1">{key.api_base}</div>
|
||||
</div>
|
||||
<Button type="primary" danger ghost onClick={() => handleDelete(key.id)}>{t('common.remove')}</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="api_key"
|
||||
label={t('modelNew.api_key')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.api_key') }) }]}
|
||||
>
|
||||
<Input.Password placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="api_base"
|
||||
label={t('modelNew.api_base')}
|
||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.api_base') }) }]}
|
||||
>
|
||||
<Input placeholder="https://api.example.com/v1" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" block onClick={handleSave} loading={loading}>+ {t('modelNew.add')}</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default MultiKeyConfigModal;
|
||||
@@ -1,99 +1,123 @@
|
||||
import { useState, useRef, type FC } from 'react';
|
||||
import { Row, Col, Button } from 'antd'
|
||||
import { Button, Flex, Space, type SegmentedProps } from 'antd'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import ConfigModal from './components/ConfigModal'
|
||||
import type { Model, DescriptionItem, ConfigModalRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import GroupModelModal from './components/GroupModelModal'
|
||||
import type { ModelListItem, GroupModelModalRef, CustomModelModalRef, ModelPlazaItem, BaseRef } from './types'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
||||
import { getModelListUrl } from '@/api/models'
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import PageTabs from '@/components/PageTabs'
|
||||
import GroupModel from './Group'
|
||||
import ModelList from './List'
|
||||
import ModelSquare from './Square'
|
||||
import CustomModelModal from './components/CustomModelModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { modelTypeUrl, modelProviderUrl } from '@/api/models'
|
||||
|
||||
const tabKeys = ['group', 'list', 'square']
|
||||
const ModelManagement: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState('group');
|
||||
const [query, setQuery] = useState({})
|
||||
const configModalRef = useRef<ConfigModalRef>(null)
|
||||
const scrollListRef = useRef<PageScrollListRef>(null)
|
||||
const configModalRef = useRef<GroupModelModalRef>(null)
|
||||
const customModelModalRef = useRef<CustomModelModalRef>(null)
|
||||
const groupRef = useRef<BaseRef>(null)
|
||||
const squareRef = useRef<BaseRef>(null)
|
||||
|
||||
const formatData = (data: Model) => {
|
||||
return [
|
||||
{
|
||||
key: 'type',
|
||||
label: t(`model.type`),
|
||||
children: data.type || '-',
|
||||
},
|
||||
{
|
||||
key: 'provider',
|
||||
label: t(`model.provider`),
|
||||
children: data.api_keys[0].provider || '-',
|
||||
},
|
||||
{
|
||||
key: 'is_active',
|
||||
label: t(`model.status`),
|
||||
children: data.is_active ? t(`common.statusEnabled`) : t(`common.statusDisabled`),
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
label: t(`model.created`),
|
||||
children: data.created_at ? formatDateTime(data.created_at, 'YYYY-MM-DD HH:mm:ss') : '-',
|
||||
},
|
||||
]
|
||||
const formatTabItems = () => {
|
||||
return tabKeys.map(value => ({
|
||||
value,
|
||||
label: t(`modelNew.${value}`),
|
||||
}))
|
||||
}
|
||||
const handleChangeTab = (value: SegmentedProps['value']) => {
|
||||
setActiveTab(value as string);
|
||||
setQuery({})
|
||||
}
|
||||
|
||||
const handleEdit = (model?: Model) => {
|
||||
configModalRef?.current?.handleOpen(model)
|
||||
const handleEdit = (vo?: ModelListItem | ModelPlazaItem) => {
|
||||
switch(activeTab) {
|
||||
case 'group':
|
||||
configModalRef?.current?.handleOpen(vo as ModelListItem)
|
||||
break
|
||||
case 'square':
|
||||
customModelModalRef?.current?.handleOpen(vo as ModelPlazaItem)
|
||||
break
|
||||
}
|
||||
}
|
||||
const handleRefresh = () => {
|
||||
switch (activeTab) {
|
||||
case 'group':
|
||||
groupRef.current?.getList()
|
||||
break
|
||||
case 'square':
|
||||
squareRef.current?.getList()
|
||||
break
|
||||
}
|
||||
}
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery({ search: value })
|
||||
}
|
||||
const handleTypeChange = (value: string) => {
|
||||
setQuery(pre => ({ ...pre, type: value }))
|
||||
}
|
||||
const handleProviderChange = (value: string) => {
|
||||
setQuery(pre => ({ ...pre, provider: value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rb:w-full">
|
||||
<Row className='rb:mb-[16px] rb:w-full'>
|
||||
<Col span={6}>
|
||||
<SearchInput
|
||||
placeholder={t('model.searchPlaceholder')}
|
||||
<>
|
||||
<Flex justify="space-between" align="center">
|
||||
<PageTabs
|
||||
value={activeTab}
|
||||
options={formatTabItems()}
|
||||
onChange={handleChangeTab}
|
||||
/>
|
||||
|
||||
<Space size={12}>
|
||||
{activeTab === 'list' ? <>
|
||||
<CustomSelect
|
||||
url={modelTypeUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
onChange={handleTypeChange}
|
||||
className="rb:w-30"
|
||||
allowClear={true}
|
||||
placeholder={t('modelNew.type')}
|
||||
/>
|
||||
<CustomSelect
|
||||
url={modelProviderUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
onChange={handleProviderChange}
|
||||
className="rb:w-30"
|
||||
allowClear={true}
|
||||
placeholder={t('modelNew.provider')}
|
||||
/>
|
||||
</>
|
||||
: <SearchInput
|
||||
placeholder={t(`modelNew.${activeTab}SearchPlaceholder`)}
|
||||
onSearch={handleSearch}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={18} className="rb:text-right">
|
||||
<Button type="primary" onClick={() => handleEdit()}>{t('model.createModel')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
className="rb:w-70!"
|
||||
/>}
|
||||
{activeTab === 'group' && <Button type="primary" onClick={() => handleEdit()}>+ {t('modelNew.createGroupModel')}</Button>}
|
||||
{activeTab === 'square' && <Button type="primary" onClick={() => handleEdit()}>+ {t('modelNew.createCustomModel')}</Button>}
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<PageScrollList
|
||||
ref={scrollListRef}
|
||||
url={getModelListUrl}
|
||||
query={query}
|
||||
renderItem={(item: Model) => (
|
||||
<RbCard
|
||||
title={item.name}
|
||||
>
|
||||
{formatData(item)?.map((description: DescriptionItem) => (
|
||||
<div
|
||||
key={description.key}
|
||||
className="rb:flex rb:justify-between rb:text-[#5B6167] rb:text-[14px] rb:leading-[20px] rb:mb-[12px]"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap">{(description.label as string)}</span>
|
||||
<span className={clsx({
|
||||
"rb:text-[#212332]": description.key !== 'is_active',
|
||||
"rb:text-[#369F21] rb:font-medium": description.key === 'is_active' && item.is_active,
|
||||
})}>{(description.children as string)}</span>
|
||||
</div>
|
||||
))}
|
||||
<Button className="rb:mt-[8px]" type="primary" ghost block onClick={() => handleEdit(item)}>{t('model.configureBtn')}</Button>
|
||||
</RbCard>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ConfigModal
|
||||
<div className="rb:w-full rb:h-[calc(100%-48px)] rb:my-4">
|
||||
{activeTab === 'group' && <GroupModel ref={groupRef} query={query} handleEdit={handleEdit} />}
|
||||
{activeTab === 'list' && <ModelList query={query} />}
|
||||
{activeTab === 'square' && <ModelSquare ref={squareRef} query={query} handleEdit={handleEdit} />}
|
||||
</div>
|
||||
<GroupModelModal
|
||||
ref={configModalRef}
|
||||
refresh={() => scrollListRef?.current?.refresh()}
|
||||
refresh={handleRefresh}
|
||||
/>
|
||||
</div>
|
||||
<CustomModelModal
|
||||
ref={customModelModalRef}
|
||||
refresh={handleRefresh}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +1,146 @@
|
||||
// 模型表单数据类型
|
||||
export interface ModelFormData extends ApiKey {
|
||||
name: string;
|
||||
type: string;
|
||||
api_keys: ApiKey;
|
||||
export interface Query {
|
||||
type?: string;
|
||||
provider?: string;
|
||||
is_active?: boolean;
|
||||
is_public?: boolean;
|
||||
is_composite?: boolean;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface DescriptionItem {
|
||||
key: string;
|
||||
label: string;
|
||||
children: string;
|
||||
}
|
||||
export interface CompositeModelForm {
|
||||
logo?: any;
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
api_key_ids: ModelApiKey[] | string[];
|
||||
}
|
||||
export interface GroupModelModalRef {
|
||||
handleOpen: (model?: ModelListItem) => void;
|
||||
}
|
||||
export interface GroupModelModalProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
export interface SubModelModalForm {
|
||||
provider: string;
|
||||
model_ids: string[]
|
||||
}
|
||||
export interface SubModelModalRef {
|
||||
handleOpen: (model?: SubModelModalForm) => void;
|
||||
}
|
||||
export interface SubModelModalProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
export interface ModelListDetailRef {
|
||||
handleOpen: (vo: ProviderModelItem) => void;
|
||||
}
|
||||
|
||||
// 模型类型定义
|
||||
export interface Model {
|
||||
|
||||
export interface ModelApiKey {
|
||||
model_name: string;
|
||||
description: string | null;
|
||||
provider: string;
|
||||
api_key: string;
|
||||
api_base: string;
|
||||
config: any;
|
||||
is_active: boolean;
|
||||
priority: string;
|
||||
id: string;
|
||||
usage_count: string;
|
||||
last_used_at: number;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
model_config_ids: string[];
|
||||
}
|
||||
export interface ModelListItem {
|
||||
model_name?: string;
|
||||
model_config_ids: string[];
|
||||
name: string;
|
||||
type: string;
|
||||
logo: string;
|
||||
description: string;
|
||||
provider: string;
|
||||
config: any;
|
||||
is_active: boolean;
|
||||
is_public: boolean;
|
||||
id: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
api_keys: ModelApiKey[]
|
||||
}
|
||||
export interface ProviderModelItem {
|
||||
provider: string;
|
||||
logo?: string;
|
||||
tags: string[];
|
||||
models: ModelListItem[];
|
||||
}
|
||||
export interface KeyConfigModalForm {
|
||||
provider: string;
|
||||
api_key: string;
|
||||
api_base: string;
|
||||
}
|
||||
export interface KeyConfigModalRef {
|
||||
handleOpen: (vo: ProviderModelItem) => void;
|
||||
}
|
||||
export interface KeyConfigModalProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
export interface MultiKeyForm {
|
||||
model_config_id?: string;
|
||||
model_name: string;
|
||||
provider: string;
|
||||
api_key: string;
|
||||
api_base: string;
|
||||
}
|
||||
|
||||
export interface MultiKeyConfigModalRef {
|
||||
handleOpen: (vo: ModelListItem, provider?: string) => void;
|
||||
}
|
||||
export interface MultiKeyConfigModalProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export interface ModelPlaza {
|
||||
provider: string;
|
||||
models: ModelPlazaItem[];
|
||||
}
|
||||
export interface ModelPlazaItem {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
config: Record<string, unknown>;
|
||||
is_active: boolean;
|
||||
is_public: boolean;
|
||||
created_at: string | number;
|
||||
updated_at: string | number;
|
||||
api_keys: ApiKey[];
|
||||
|
||||
// provider: string;
|
||||
// temperature: number,
|
||||
// topP: number,
|
||||
// status: string;
|
||||
// vectorDimension: number;
|
||||
// batchSize: number;
|
||||
// truncateStrategy: string;
|
||||
// created: string;
|
||||
// updatedAt: string;
|
||||
// descriptionItems?: Record<string, unknown>[];
|
||||
// basicParameters?: string;
|
||||
// normalization?: string;
|
||||
// maxInputLength?: number;
|
||||
// encodingFormat?: string;
|
||||
// enablePooling?: boolean;
|
||||
// poolingStrategy?: string;
|
||||
// apiKey?: string;
|
||||
// apiEndpoint?: string;
|
||||
// timeout?: number;
|
||||
// autoRetry?: boolean;
|
||||
// retryCount?: number;
|
||||
}
|
||||
interface ApiKey {
|
||||
model_name?: string;
|
||||
provider: string;
|
||||
api_key?: string;
|
||||
api_base?: string;
|
||||
config?: Record<string, unknown>;
|
||||
is_active?: boolean;
|
||||
priority?: string;
|
||||
id: string;
|
||||
model_config_id?: string;
|
||||
usage_count?: string;
|
||||
last_used_at?: string | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
logo: string;
|
||||
description: string;
|
||||
is_deprecated: boolean;
|
||||
is_official: boolean;
|
||||
tags: string[];
|
||||
add_count: number;
|
||||
is_added: boolean;
|
||||
}
|
||||
// 定义组件暴露的方法接口
|
||||
export interface ConfigModalRef {
|
||||
handleOpen: (model?: Model) => void;
|
||||
export interface ModelSquareDetailRef {
|
||||
handleOpen: (vo: ModelPlaza) => void;
|
||||
}
|
||||
export interface ConfigModalProps {
|
||||
export interface CustomModelForm {
|
||||
name: string;
|
||||
type: string;
|
||||
provider: string;
|
||||
logo: string;
|
||||
description: string;
|
||||
is_official: boolean;
|
||||
tags: string[];
|
||||
}
|
||||
export interface CustomModelModalRef {
|
||||
handleOpen: (vo?: ModelPlazaItem) => void;
|
||||
}
|
||||
export interface CustomModelModalProps {
|
||||
refresh?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export interface BaseRef {
|
||||
getList: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user