feat: local model list page

This commit is contained in:
ningyv
2025-02-12 16:45:57 +08:00
parent 7ac385b317
commit ec951dd87f
12 changed files with 103 additions and 152 deletions
+1 -1
View File
@@ -129,7 +129,7 @@ const DEFAULT_HEADERS = {
namespace: 'default'
}
export type EoRequest = RequestInit & {
type EoRequest = RequestInit & {
eoParams?: { [k: string]: unknown }
eoTransformKeys?: string[]
eoApiPrefix?: string
@@ -57,7 +57,12 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
eoTransformKeys: ['default_config']
}).then((response) => {
const models = response.data.models || []
setLlmList(models)
setLlmList(
models.map((x: any) => ({
...x,
config: x.defaultConfig
}))
)
if (setDefaultValue && models.length) {
const id = models[0].id
form.setFieldsValue({
@@ -144,10 +149,6 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
.catch((errorInfo) => console.error(errorInfo))
}
const handleChangeProvider = (provider: string) => {
getLlmList(provider)
}
return (
<Form
layout="vertical"
@@ -179,7 +180,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
placeholder={$t(PLACEHOLDER.select)}
options={providerList}
onChange={(e) => {
handleChangeProvider(e)
getLlmList(e)
}}
></Select>
</Form.Item>
@@ -201,7 +202,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
}))
}
onChange={(e) => {
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config || llmList.find((x) => x.id === e)?.defaultConfig)
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config)
}}
></Select>
</Form.Item>
@@ -12,7 +12,7 @@ export type AiSettingModalContentProps = {
entity?: AISettingEntityItem
readOnly: boolean
modelMode?: 'auto' | 'manual'
originEntity?: string
source?: string
/** 如果是手动选择 AI 模型,那么需要更新 footer 底部的内容,所以需要这个方法去更新外部的 footer */
updateEntityData: (entity: AISettingEntityItem) => void
}
@@ -25,7 +25,7 @@ export type AiSettingModalContentHandle = {
const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingModalContentProps>((props, ref) => {
const [form] = Form.useForm()
const { message } = App.useApp()
const { entity, readOnly, modelMode = 'auto', updateEntityData, originEntity } = props
const { entity, readOnly, modelMode = 'auto', updateEntityData, source } = props
const { fetchData } = useFetch()
const [llmList, setLlmList] = useState<AiProviderLlmsItems[]>()
const [loading, setLoading] = useState<boolean>(false)
@@ -163,13 +163,12 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
}
}
useEffect(() => {
// 如果是直接在 AI 模型配置,则获取默认模型列表和团队列表
if (localEntity?.id) {
getModelConfig(localEntity.id)
setFormFieldsValue(localEntity)
} else {
getModelProviderList()
originEntity && getTeamOptionList()
source && getTeamOptionList()
}
}, [])
@@ -187,7 +186,6 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
team: value.team,
provider: localEntity?.id
}
console.log(finalValue)
fetchData<BasicResponse<null>>('quick/service/ai', {
method: 'POST',
eoBody: finalValue
@@ -306,7 +304,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
}))}
></Select>
</Form.Item>
{originEntity === 'guide' && (
{source === 'guide' && (
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={teamList} onChange={(value) => {
form.setFieldValue('team', value)
@@ -323,7 +321,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
enableToolbar={false}
/>
</Form.Item>
{originEntity !== 'guide' && (
{source !== 'guide' && (
<Form.Item className="p-4 bg-white rounded-lg" label={$t('LLM 状态管理')}>
<div className="flex justify-between items-center">
<div>
@@ -113,7 +113,7 @@ const LocalModelList: React.FC = () => {
const handleEdit = (record: ModelListData) => {
modal.confirm({
title: $t('部署 AI 模型'),
title: $t('模型设置'),
content: <EditLocalModelModal ref={EditLocalModelModalRef} modelID={record.id} enable={record.state !== 'disabled'}/>,
onOk: () => {
return EditLocalModelModalRef.current?.save().then((res) => {
@@ -132,7 +132,7 @@ const LocalModelList: React.FC = () => {
const handleAdd = () => {
const modalInstance = modal.confirm({
title: $t('部署 AI 模型'),
title: $t('部署本地模型'),
content: (
<LocalAiDeploy
ref={localAiDeployRef}
@@ -159,15 +159,15 @@ const LocalModelList: React.FC = () => {
const handleDelete = async (id: string, apiCount: number) => {
modal.confirm({
title: $t('停止部署'),
title: $t('删除模型'),
content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
onOk: () => {
return new Promise((resolve, reject) => {
try {
fetchData<BasicResponse<any>>('ai/provider', {
fetchData<BasicResponse<any>>('model/local', {
method: 'DELETE',
eoParams: {
provider: id
model: id
}
})
.then((response) => {
@@ -206,7 +206,6 @@ const LocalModelList: React.FC = () => {
keyword: searchWord,
page: params.current
},
eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/',
eoTransformKeys: ['can_delete', 'api_count']
})
@@ -31,7 +31,7 @@ const OnlineModelList: React.FC = () => {
const handleDelete = async (id: string, apiCount: number) => {
modal.confirm({
title: $t('停止部署'),
title: $t('删除模型'),
content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
onOk: () => {
return new Promise((resolve, reject) => {
@@ -41,7 +41,6 @@ const OnlineModelList: React.FC = () => {
eoParams: {
provider: id
}
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
}).then((response) => {
if (response.code === STATUS_CODE.SUCCESS) {
message.success($t('删除成功'))
@@ -79,7 +78,6 @@ const OnlineModelList: React.FC = () => {
page: params.current
},
eoTransformKeys: ['default_llm', 'api_count', 'key_count', 'can_delete']
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
})
if (response.code === STATUS_CODE.SUCCESS) {
@@ -8,7 +8,6 @@ import { App } from 'antd'
import { Card } from 'antd'
import { useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import AiSettingModalContent, { AiSettingModalContentHandle } from '../aiSetting/AiSettingModal'
import { checkAccess } from '@common/utils/permission'
import LocalAiDeploy, { LocalAiDeployHandle } from './LocalAiDeploy'
@@ -68,7 +67,7 @@ export const AIModelGuide = () => {
ref={modalRef}
modelMode="manual"
updateEntityData={updateEntityData}
originEntity="guide"
source="guide"
readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}
/>
),
@@ -111,7 +110,7 @@ export const AIModelGuide = () => {
*/
const localModelCardClick = async () => {
const modalInstance = modal.confirm({
title: $t('部署 AI 模型'),
title: $t('部署本地模型'),
content: <LocalAiDeploy ref={localAiDeployRef} onClose={() => {
modalInstance.destroy()
dumpServerPage()
@@ -10,7 +10,7 @@ import useDeployLocalModel from './deployModelUtil'
const { Dragger } = Upload
export type RestAIDeployHandle = {
deployRestAIServer: () => Promise<boolean | string>
deployRestAIServer: () => Promise<boolean | string>
}
const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any) => {
const [form] = Form.useForm()
@@ -38,36 +38,36 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
getTeamList()
}, [])
/**
/**
* 部署 rest 服务
* @param file
* @returns
*/
const deployRestServer = async (file: File) => {
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('file', file)
formData.append('type', file.type)
formData.append('team', form.getFieldValue('team'))
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>('quick/service/rest', {
method: 'POST',
body: formData
}).then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(false)
}
})
})
}
const deployRestServer = async (file: File) => {
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('file', file)
formData.append('type', file.type)
formData.append('team', form.getFieldValue('team'))
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>('quick/service/rest', {
method: 'POST',
body: formData
}).then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(false)
}
})
})
}
/**
* 部署本地AI
* @returns
* @returns
*/
const deployRestAIServer = () => {
return new Promise((resolve, reject) => {
@@ -86,39 +86,35 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
}))
return (
<WithPermission access="">
<Form
layout="vertical"
labelAlign="left"
scrollToFirstError
form={form}
className="mx-auto "
name="partitionInsideCert"
autoComplete="off"
>
<Form.Item
name="key"
className="mb-0 bg-transparent p-0 border-none rounded-none"
rules={[{ required: true }]}
<Form
layout="vertical"
labelAlign="left"
scrollToFirstError
form={form}
className="mx-auto "
name="partitionInsideCert"
autoComplete="off"
>
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<Icon className="text-[#ccc]" icon="tdesign:upload" width="50" height="50" />
</p>
<p className="ant-upload-text">{$t('选择 OpenAPI 文件 (.json / .yaml)')}</p>
</Dragger>
</Form.Item>
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}
options={teamList}
onChange={(value) => {
form.setFieldValue('team', value)
}}
></Select>
</Form.Item>
</Form>
</WithPermission>
<Form.Item name="key" className="mb-0 bg-transparent p-0 border-none rounded-none" rules={[{ required: true }]}>
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<Icon className="text-[#ccc]" icon="tdesign:upload" width="50" height="50" />
</p>
<p className="ant-upload-text">{$t('选择 OpenAPI 文件 (.json / .yaml)')}</p>
</Dragger>
</Form.Item>
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}
options={teamList}
onChange={(value) => {
form.setFieldValue('team', value)
}}
></Select>
</Form.Item>
</Form>
</WithPermission>
)
})
@@ -9,27 +9,23 @@ import { useGlobalContext } from '@common/contexts/GlobalStateContext'
const useDeployLocalModel = () => {
const { fetchData } = useFetch()
const { checkPermission } = useGlobalContext()
const deployLocalModel = (value: { modelID: string; team?: number }) => {
return new Promise((resolve, reject) => {
fetchData<BasicResponse<null>>('model/local/deploy/start', {
const deployLocalModel = async (value: { modelID: string; team?: number }) => {
const response = await fetchData<BasicResponse<null>>(
'model/local/deploy/start',
{
method: 'POST',
eoBody: {
model: value.modelID,
team: value?.team
}
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(false)
}
})
.catch((errorInfo) => reject(errorInfo))
})
}
)
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}
/**
* 获取 team 选项列表
@@ -57,42 +57,15 @@ const LoadBalancingPage = () => {
* @param dataType
* @returns
*/
const requestApis = (
params: LoadBalancingItems & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>
) => {
let filters
if (filter) {
filters = []
if (filter.isStop) {
if (filter.isStop.indexOf('true') !== -1) {
filters.push('enable')
}
if (filter.isStop.indexOf('false') !== -1) {
filters.push('disable')
}
if (filter.publishStatus?.length > 0) {
filters = [...filters, ...filter.publishStatus]
}
}
}
const requestApis = () => {
return fetchData<BasicResponse<{ list: LoadBalancingItems[]; total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
`ai/balances`,
{
method: 'GET',
eoParams: {
order: Object.keys(sort)?.[0],
sort: Object.keys(sort)?.length > 0 ? (Object.values(sort)?.[0] === 'descend' ? 'desc' : 'asc') : undefined,
filters: JSON.stringify(filters),
keyword: searchWord,
service: serviceId
keyword: searchWord
},
eoTransformKeys: ['is_stop', 'is_delete', 'update_time', 'publish_status', 'processed_total']
eoTransformKeys: ['api_count', 'key_count']
}
)
.then((response) => {
@@ -234,10 +207,10 @@ const LoadBalancingPage = () => {
},
{
title: $t('Apis'),
dataIndex: 'api_count',
dataIndex: 'apiCount',
ellipsis: true,
width: 80,
key: 'api_count',
key: 'apiCount',
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
<a
@@ -251,17 +224,17 @@ const LoadBalancingPage = () => {
textDecoration: 'none'
}}
>
{record.api_count || '0'}
{record.apiCount || '0'}
</a>
</span>
)
},
{
title: $t('Keys'),
dataIndex: 'key_count',
dataIndex: 'keyCount',
ellipsis: true,
width: 80,
key: 'key_count',
key: 'keyCount',
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
<a
@@ -275,7 +248,7 @@ const LoadBalancingPage = () => {
textDecoration: 'none'
}}
>
{record.key_count || '0'}
{record.keyCount || '0'}
</a>
</span>
)
@@ -289,7 +262,7 @@ const LoadBalancingPage = () => {
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
<TableBtnWithPermission
access="system.settings.ai_key_resource.manager"
access=""
key="delete"
btnType="delete"
onClick={() => handleDelete(entity.id as string)}
@@ -324,14 +297,7 @@ const LoadBalancingPage = () => {
</Button>
</WithPermission>
]}
request={async (
params: any & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>
) => requestApis(params, sort, filter)}
request={() => requestApis()}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
@@ -11,8 +11,8 @@ export interface LoadBalancingItems {
}
type: string
state: string
api_count: string
key_count: string
apiCount: string
keyCount: string
}
export interface LoadModelDetailData {
@@ -15,7 +15,7 @@ export const LogsFooter = (props: any) => {
return new Promise((resolve, reject) => {
fetchData<BasicResponse<any>>('model/local/cancel_deploy', {
method: 'POST',
eoBody: { recordId: record.id }
eoBody: { model: record.id }
})
.then((response) => {
const { code, msg } = response
@@ -47,7 +47,7 @@ export const LogsFooter = (props: any) => {
return new Promise((resolve, reject) => {
fetchData<BasicResponse<any>>('model/local', {
method: 'DELETE',
eoBody: { recordId: record.id }
eoBody: { model: record.id }
})
.then((response: BasicResponse<any>) => {
const { code, msg } = response
@@ -60,12 +60,10 @@ export const ServiceDeployment = (props: { record: SystemTableListItem }) => {
useEffect(() => {
fetchData(
'http://localhost:3000/stream',
// 'model/local/deploy',
'model/local/deploy',
{
method: 'POST',
eoBody: { recordId: record.id },
eoApiPrefix: '',
headers: {
'Content-Type': 'event-stream'
},