diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx
index 3a7151ab..0fbccad4 100644
--- a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx
+++ b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { AiSettingProvider } from './contexts/AiSettingContext'
import OnlineModelList from './OnlineModelList'
+import LocalModelList from './LocalModelList'
const CONTENT_STYLE = { height: 'calc(-300px + 100vh)' } as const
@@ -47,7 +48,9 @@ const AiSettingContent = () => {
{
key: 'config',
label: $t('本地模型'),
- children:
+ children:
+
+
}
]}
/>
diff --git a/frontend/packages/core/src/pages/aiSetting/LocalModelList.tsx b/frontend/packages/core/src/pages/aiSetting/LocalModelList.tsx
new file mode 100644
index 00000000..40196bfa
--- /dev/null
+++ b/frontend/packages/core/src/pages/aiSetting/LocalModelList.tsx
@@ -0,0 +1,347 @@
+import { ActionType } from '@ant-design/pro-components'
+import PageList, { PageProColumns } from '@common/components/aoplatform/PageList'
+import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission'
+import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
+import { useFetch } from '@common/hooks/http'
+import { $t } from '@common/locales'
+import { App, Divider, Form, Space, Switch, Tag } from 'antd'
+import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
+import { ModelListData } from './types'
+import LocalAiDeploy, { LocalAiDeployHandle } from '../guide/LocalAiDeploy'
+import { ServiceDeployment } from '../system/serviceDeployment/ServiceDeployment'
+import { LogsFooter } from '../system/serviceDeployment/ServiceDeployMentFooter'
+import WithPermission from '@common/components/aoplatform/WithPermission'
+type EditLocalModelModalHandle = {
+ save: () => Promise
+}
+type EditLocalModelModalProps = {
+ enable: boolean
+ modelID?: string
+}
+const EditLocalModelModal = forwardRef((props: EditLocalModelModalProps, ref) => {
+ const { enable, modelID } = props
+ const { fetchData } = useFetch()
+ const { message } = App.useApp()
+ const [form] = Form.useForm()
+ const [currentStatus, setCurrentStatus] = useState(enable)
+
+ useEffect(() => {
+ form.setFieldsValue({ enable })
+ }, [])
+ /**
+ * 保存
+ * @returns
+ */
+ const save: () => Promise = () => {
+ return new Promise((resolve, reject) => {
+ try {
+ form
+ .validateFields()
+ .then((value) => {
+ const finalValue = {
+ disable: !value.enable
+ }
+
+ fetchData>('model/local/info', {
+ method: 'PUT',
+ eoParams: { model: modelID },
+ eoBody: finalValue,
+ })
+ .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(msg || $t(RESPONSE_TIPS.error))
+ }
+ }).catch((errorInfo) => reject(errorInfo))
+ })
+ .catch((errorInfo) => reject(errorInfo))
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+ useImperativeHandle(ref, () => ({
+ save
+ }))
+
+ return (
+
+
+
+
+ {$t('当前调用状态:')}
+ {currentStatus && {$t('正常')}}
+ {!currentStatus && {$t('停用')}}
+
+
+ {
+ form.setFieldsValue({ enable: checked })
+ setCurrentStatus(checked)
+ }}
+ />
+
+
+
+
+
+ )
+})
+
+const LocalModelList: React.FC = () => {
+ const pageListRef = useRef(null)
+ const { message, modal } = App.useApp()
+ const { fetchData } = useFetch()
+ const [searchWord, setSearchWord] = useState('')
+ const localAiDeployRef = useRef()
+ const EditLocalModelModalRef = useRef()
+
+ const handleEdit = (record: ModelListData) => {
+ modal.confirm({
+ title: $t('部署 AI 模型'),
+ content: ,
+ onOk: () => {
+ return EditLocalModelModalRef.current?.save().then((res) => {
+ if (res === true) {
+ pageListRef.current?.reload()
+ }
+ })
+ },
+ width: 600,
+ okText: $t('确认'),
+ cancelText: $t('取消'),
+ closable: true,
+ icon: <>>
+ })
+ }
+
+ const handleAdd = () => {
+ const modalInstance = modal.confirm({
+ title: $t('部署 AI 模型'),
+ content: (
+ {
+ modalInstance.destroy()
+ pageListRef.current?.reload()
+ }}
+ >
+ ),
+ onOk: () => {
+ return localAiDeployRef.current?.deployLocalAIServer().then((res) => {
+ if (res === true) {
+ pageListRef.current?.reload()
+ }
+ })
+ },
+ width: 600,
+ okText: $t('确认'),
+ cancelText: $t('取消'),
+ closable: true,
+ icon: <>>
+ })
+ }
+
+ const handleDelete = async (id: string, apiCount: number) => {
+ modal.confirm({
+ title: $t('停止部署'),
+ content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
+ onOk: () => {
+ return new Promise((resolve, reject) => {
+ try {
+ fetchData>('ai/provider', {
+ method: 'DELETE',
+ eoParams: {
+ provider: id
+ }
+ })
+ .then((response) => {
+ if (response.code === STATUS_CODE.SUCCESS) {
+ message.success($t('删除成功'))
+ pageListRef.current?.reload()
+ } else {
+ message.error(response.msg || RESPONSE_TIPS.error)
+ }
+ resolve(true)
+ })
+ .catch((error) => {
+ message.error(RESPONSE_TIPS.error)
+ resolve(true)
+ })
+ } catch (error) {
+ message.error(RESPONSE_TIPS.error)
+ resolve(true)
+ }
+ })
+ },
+ width: 600,
+ okText: $t('确认'),
+ cancelText: $t('取消'),
+ closable: true,
+ icon: <>>
+ })
+ }
+
+ const requestList = async (params: any) => {
+ try {
+ const response = await fetchData>('model/local/list', {
+ method: 'GET',
+ eoParams: {
+ page_size: params.pageSize,
+ keyword: searchWord,
+ page: params.current
+ },
+ eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/',
+ eoTransformKeys: ['can_delete', 'api_count']
+ })
+
+ if (response.code === STATUS_CODE.SUCCESS) {
+ return {
+ data: response.data.models,
+ success: true,
+ total: response.data.total
+ }
+ } else {
+ message.error(response.msg || $t(RESPONSE_TIPS.error))
+ return {
+ data: [],
+ success: false,
+ total: response.data.total
+ }
+ }
+ } catch (error) {
+ return {
+ data: [],
+ success: false,
+ total: 0
+ }
+ }
+ }
+
+ const operation: PageProColumns[] = [
+ {
+ title: '',
+ key: 'option',
+ btnNums: 4,
+ fixed: 'right',
+ valueType: 'option',
+ render: (_: React.ReactNode, entity: ModelListData) => [
+ handleEdit(entity)}
+ btnTitle={$t('设置')}
+ />,
+ ,
+ handleDelete(entity.id as string, entity?.apiCount)}
+ btnTitle={$t('删除')}
+ />
+ ]
+ }
+ ]
+
+ const openLogsModal = (record: any) => {
+ const modalInstance = modal.confirm({
+ title: $t('部署过程'),
+ content: ,
+ footer: () => {
+ return
+ },
+ width: 600,
+ okText: $t('确认'),
+ cancelText: $t('取消'),
+ closable: true,
+ icon: <>>
+ })
+ }
+
+ const columns: PageProColumns[] = [
+ {
+ title: $t('名称'),
+ dataIndex: 'name',
+ render: (dom: React.ReactNode, entity: ModelListData) => {entity.name}
+ },
+ {
+ title: $t('状态'),
+ width: 140,
+ dataIndex: 'state',
+ ellipsis: true,
+ render: (dom: React.ReactNode, entity: ModelListData) => (
+ .ant-typography]:text-[#2196f3] cursor-pointer' : entity?.state === 'error' ? '[&>.ant-typography]:text-[#ff4d4f] cursor-pointer' : ''}`}
+ onClick={(e) => {
+ if (['deploying', 'error'].includes(entity?.state as string)) {
+ e?.stopPropagation()
+ openLogsModal(entity)
+ }
+ }}
+ >
+ {dom}
+
+ )
+ },
+ {
+ title: $t('Apis'),
+ dataIndex: 'apiCount',
+ render: (dom: React.ReactNode, record: ModelListData) => (
+
+
+ {record.apiCount || '0'}
+
+
+ )
+ },
+ ...operation
+ ]
+
+ return (
+ {
+ setSearchWord(e.target.value)
+ pageListRef.current?.reload()
+ }}
+ showPagination={true}
+ searchPlaceholder={$t('请输入名称搜索')}
+ columns={columns}
+ addNewBtnTitle={$t('部署模型')}
+ onAddNewBtnClick={handleAdd}
+ />
+ )
+}
+
+export default LocalModelList
diff --git a/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx b/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx
index b59e6541..436e4e07 100644
--- a/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx
+++ b/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx
@@ -11,39 +11,62 @@ import { AiSettingListItem, ModelListData } from './types'
const OnlineModelList: React.FC = () => {
const pageListRef = useRef(null)
- const { message } = App.useApp()
+ const { message, modal } = App.useApp()
const { fetchData } = useFetch()
const [searchWord, setSearchWord] = useState('')
const [total, setTotal] = useState(0)
const { openConfigModal } = useAiSetting()
const handleEdit = (record: ModelListData) => {
- openConfigModal({ id: record.id, defaultLlm: record.defaultLlm } as AiSettingListItem)
+ openConfigModal({ id: record.id, defaultLlm: record.defaultLlm } as AiSettingListItem, () => {
+ pageListRef.current?.reload()
+ })
}
const handleAdd = () => {
- openConfigModal()
+ openConfigModal(undefined, () => {
+ pageListRef.current?.reload()
+ })
}
- const handleDelete = async (id: string) => {
- try {
- const response = await fetchData>('ai/provider', {
- method: 'DELETE',
- eoParams: {
- provider: id
- }
- // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
- })
+ const handleDelete = async (id: string, apiCount: number) => {
+ modal.confirm({
+ title: $t('停止部署'),
+ content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
+ onOk: () => {
+ return new Promise((resolve, reject) => {
+ try {
+ fetchData>('ai/provider', {
+ method: 'DELETE',
+ eoParams: {
+ provider: id
+ }
+ // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
+ }).then((response) => {
+ if (response.code === STATUS_CODE.SUCCESS) {
+ message.success($t('删除成功'))
+ pageListRef.current?.reload()
+ } else {
+ message.error(response.msg || RESPONSE_TIPS.error)
+ }
+ resolve(true)
+ }).catch((error) => {
+ message.error(RESPONSE_TIPS.error)
+ resolve(true)
+ })
+ } catch (error) {
+ message.error(RESPONSE_TIPS.error)
+ resolve(true)
+ }
+ })
+ },
+ width: 600,
+ okText: $t('确认'),
+ cancelText: $t('取消'),
+ closable: true,
+ icon: <>>
+ })
- if (response.code === STATUS_CODE.SUCCESS) {
- message.success($t('删除成功'))
- pageListRef.current?.reload()
- } else {
- message.error(response.msg || RESPONSE_TIPS.error)
- }
- } catch (error) {
- message.error(RESPONSE_TIPS.error)
- }
}
const requestList = async (params: any) => {
@@ -55,7 +78,7 @@ const OnlineModelList: React.FC = () => {
keyword: searchWord,
page: params.current
},
- eoTransformKeys: ['default_llm']
+ eoTransformKeys: ['default_llm', 'api_count', 'key_count', 'can_delete']
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
})
@@ -107,8 +130,10 @@ const OnlineModelList: React.FC = () => {
handleDelete(entity.id as string)}
+ onClick={() => handleDelete(entity.id as string, entity.apiCount)}
btnTitle={$t('删除')}
/>
]
@@ -137,11 +162,45 @@ const OnlineModelList: React.FC = () => {
},
{
title: $t('Apis'),
- dataIndex: 'api_count'
+ dataIndex: 'apiCount',
+ render: (dom: React.ReactNode, record: ModelListData) => (
+
+
+ {record.apiCount || '0'}
+
+
+ )
},
{
title: $t('Keys'),
- dataIndex: 'key_count'
+ dataIndex: 'keyCount',
+ render: (dom: React.ReactNode, record: ModelListData) => (
+
+
+ {record.keyCount || '0'}
+
+
+ )
},
...operation
]
diff --git a/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx
index 1fb60bba..ce71f5a7 100644
--- a/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx
+++ b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx
@@ -8,7 +8,7 @@ import AiSettingModalContent, { AiSettingModalContentHandle } from '../AiSetting
import { AiSettingListItem } from '../types'
interface AiSettingContextType {
- openConfigModal: (entity?: AiSettingListItem) => Promise
+ openConfigModal: (entity?: AiSettingListItem, callback?: () => void) => Promise
}
const AiSettingContext = createContext(undefined)
@@ -19,7 +19,7 @@ export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const modalRef = useRef()
const entityData = useRef(null)
- const openConfigModal = async (entity?: AiSettingListItem) => {
+ const openConfigModal = async (entity?: AiSettingListItem, callback?: () => void) => {
// 更新弹窗
const updateEntityData = (data: any) => {
entityData.current = data
@@ -41,6 +41,7 @@ export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ chi
return modalRef.current?.save().then((res) => {
if (res === true) {
setAiConfigFlushed(!aiConfigFlushed)
+ callback?.()
}
})
},
diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts
index 4dcc6f05..c9930b55 100644
--- a/frontend/packages/core/src/pages/aiSetting/types.ts
+++ b/frontend/packages/core/src/pages/aiSetting/types.ts
@@ -1,5 +1,6 @@
export type ModelStatus = 'enabled' | 'abnormal' | 'disabled'
export type KeyStatus = 'normal' | 'abnormal' | 'disabled'
+export type ModelDeployStatus = 'normal' | 'disabled' | 'deploying' | 'error' | undefined
export interface KeyData {
id: string
@@ -14,9 +15,12 @@ export interface ModelListData {
defaultLlm: string | undefined
modelMode?: string
status: ModelStatus
- api_count: number
- key_count: number
+ state?: ModelDeployStatus
+ apiCount: number
+ keyCount: number
+ isDisabled?: boolean
keys: KeyData[]
+ canDelete: boolean
}
export interface AISettingEntityItem {
diff --git a/frontend/packages/core/src/pages/loadBalancing/index.tsx b/frontend/packages/core/src/pages/loadBalancing/index.tsx
index 3441f5fd..6c2fbfd5 100644
--- a/frontend/packages/core/src/pages/loadBalancing/index.tsx
+++ b/frontend/packages/core/src/pages/loadBalancing/index.tsx
@@ -208,7 +208,7 @@ const LoadBalancingPage = () => {
ellipsis: true,
width: 100,
key: 'provider',
- render: (text: string, record: LoadBalancingItems) => (
+ render: (dom: React.ReactNode, record: LoadBalancingItems) => (
{record.provider?.name} / {record.model?.name}
@@ -220,7 +220,7 @@ const LoadBalancingPage = () => {
width: 100,
ellipsis: true,
key: 'type',
- render: (text: string, record: LoadBalancingItems) => (
+ render: (dom: React.ReactNode, record: LoadBalancingItems) => (
{record.type === 'online' ? $t('线上模型') : $t('本地模型')}
)
},
@@ -230,15 +230,15 @@ const LoadBalancingPage = () => {
width: 120,
ellipsis: true,
key: 'state',
- render: (text: string, record: LoadBalancingItems) => {statusEnum[record.state]?.text || '-'}
+ render: (dom: React.ReactNode, record: LoadBalancingItems) => {statusEnum[record.state]?.text || '-'}
},
{
- title: $t('API 数量'),
+ title: $t('Apis'),
dataIndex: 'api_count',
ellipsis: true,
width: 80,
key: 'api_count',
- render: (text: string, record: LoadBalancingItems) => (
+ render: (dom: React.ReactNode, record: LoadBalancingItems) => (
{
textDecoration: 'none'
}}
>
- {record.api_count || '-'}
+ {record.api_count || '0'}
)
},
{
- title: $t('KEY 数量'),
+ title: $t('Keys'),
dataIndex: 'key_count',
ellipsis: true,
width: 80,
key: 'key_count',
- render: (text: string, record: LoadBalancingItems) => (
+ render: (dom: React.ReactNode, record: LoadBalancingItems) => (
{
textDecoration: 'none'
}}
>
- {record.key_count || '-'}
+ {record.key_count || '0'}
)
diff --git a/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx b/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx
index 485a12cb..26f6e023 100644
--- a/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx
+++ b/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx
@@ -65,7 +65,10 @@ export const ServiceDeployment = (props: { record: SystemTableListItem }) => {
{
method: 'POST',
eoBody: { recordId: record.id },
- custom: true,
+ eoApiPrefix: '',
+ headers: {
+ 'Content-Type': 'event-stream'
+ },
isStream: true,
handleStream: (chunk) => {
const parsedChunk = JSON.parse(chunk)