feat: feature/1.5 Extract home page navigation component

This commit is contained in:
ningyv
2025-02-11 10:36:47 +08:00
parent 8ce65cbe3d
commit 628cd98fd0
4 changed files with 354 additions and 267 deletions
@@ -4,220 +4,43 @@ import localAIPic from '@common/assets/localAI.svg'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { $t } from '@common/locales'
import { Icon } from '@iconify/react/dist/iconify.js'
import { App, Upload, UploadProps, Form, message, Select } from 'antd'
import { App } from 'antd'
import { Card } from 'antd'
import { useRef, useState } from 'react'
import { useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { LocalModelItem, MemberItem, SimpleTeamItem } from '@common/const/type'
import AiSettingModalContent, { AiSettingModalContentHandle } from '../aiSetting/AiSettingModal'
import { checkAccess } from '@common/utils/permission'
const { Dragger } = Upload
import LocalAiDeploy, { LocalAiDeployHandle } from './LocalAiDeploy'
import useDeployLocalModel from './deployModelUtil'
import RestAIDeploy, { RestAIDeployHandle } from './RestAIDeploy'
export const AIModelGuide = () => {
const { modal } = App.useApp()
const [, forceUpdate] = useState<unknown>(null)
const [form] = Form.useForm()
const entityData = useRef<any>(null)
const { fetchData } = useFetch()
const navigateTo = useNavigate()
const { checkPermission, accessData } = useGlobalContext()
const { accessData } = useGlobalContext()
const modalRef = useRef<AiSettingModalContentHandle>()
const localAiDeployRef = useRef<LocalAiDeployHandle>()
const restAiDeployRef = useRef<RestAIDeployHandle>()
const { deployLocalModel } = useDeployLocalModel()
/**
* 获取 team 选项列表
* @returns
*/
const getTeamOptionList = async (): any[] => {
const response = await fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(
!checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams',
{ method: 'GET', eoTransformKeys: [] }
)
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const teamOptionList = data.teams?.map((x: MemberItem) => {
return { ...x, label: x.name, value: x.id }
})
if (form.getFieldValue('team') === undefined && data.teams?.length) {
form.setFieldValue('team', data.teams[0].id)
}
return teamOptionList
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return []
}
}
/**
* 部署 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 deployPopularModel = async (id: string, modalInstance: any) => {
await deployLocalModel({
modelID: id,
team: form.getFieldValue('team')
})
modalInstance.destroy()
navigateTo(`/service/list`)
}
/**
* 部署本地模型
* @param value
* @returns
*/
const deployLocalModel = (value: { modelID: string; team?: number }) => {
return new Promise((resolve, reject) => {
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))
})
}
/**
* 获取本地模型列表
* @returns 本地模型列表
*/
const getLocalModelList = async (): any[] => {
const response = await fetchData<BasicResponse<{ models: LocalModelItem[] }>>(
'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/model/local/can_deploy',
// 'model/local/can_deploy'
{ method: 'GET', custom: true, eoTransformKeys: ['is_popular'] }
)
// TODO_数据模拟
if (response.ok) {
const datas = await response.json()
const { code, data, msg } = datas
if (code === STATUS_CODE.SUCCESS) {
const modelList = data.models?.map((x: LocalModelItem) => {
return { ...x, label: x.name, value: x.id }
})
return modelList
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return []
}
} else {
console.error('HTTP error', response.status)
}
// const { code, data, msg } = response
// if (code === STATUS_CODE.SUCCESS) {
// const modelList = data.models?.map((x: LocalModelItem) => {
// return { ...x, label: x.name, value: x.id }
// })
// console.log('modelList===', modelList);
// return modelList
// } else {
// message.error(msg || $t(RESPONSE_TIPS.error))
// return []
// }
const dumpServerPage = () => {
navigateTo('/service/list')
}
/**
* rest 服务卡片点击事件
*/
const restCardClick = async () => {
form.resetFields()
const teamList = await getTeamOptionList()
const props: UploadProps = {
name: 'file',
multiple: false,
maxCount: 1,
beforeUpload: (file) => {
form.setFieldsValue({ key: file })
forceUpdate({})
return false
}
}
modal.confirm({
title: $t('添加 Rest 服务'),
content: (
<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 }]}
>
<Dragger {...props}>
<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>
),
content: <RestAIDeploy ref={restAiDeployRef}></RestAIDeploy>,
onOk: () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then(async (value) => {
await deployRestServer(value.key.file)
resolve(true)
navigateTo(`/service/list`)
})
.catch((errorInfo) => reject(errorInfo))
return restAiDeployRef.current?.deployRestAIServer().then((res) => {
if (res === true) {
dumpServerPage()
}
})
},
width: 600,
@@ -251,7 +74,7 @@ export const AIModelGuide = () => {
onOk: () => {
return modalRef.current?.deployAIServer().then((res) => {
if (res === true) {
navigateTo(`/service/list`)
dumpServerPage()
}
})
},
@@ -286,80 +109,17 @@ export const AIModelGuide = () => {
* 本地部署 AI 并生成 API
*/
const localModelCardClick = async () => {
form.resetFields()
const teamList = await getTeamOptionList()
const modelList = await getLocalModelList()
const modalInstance = modal.confirm({
title: $t('部署 AI 模型'),
content: (
<WithPermission access="">
<Form
layout="vertical"
labelAlign="left"
scrollToFirstError
form={form}
className="mx-auto "
name="partitionInsideCert"
autoComplete="off"
>
<Form.Item label={$t('模型名称')} name="modelID" rules={[{ required: true }]}>
<Select
showSearch
className="w-INPUT_NORMAL"
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
placeholder={$t(PLACEHOLDER.input)}
options={modelList}
onChange={(value) => {
form.setFieldValue('modelID', value)
}}
></Select>
<div className="mt-[10px] mb-[5px]">
<Icon className="align-text-top" icon="noto-v1:fire" width="17" height="17" />
{$t('热点模型')}
</div>
<div className="pl-[5px] flex flex-wrap">
{modelList.length &&
modelList
.filter((item) => item.is_popular)
.map((item) => (
<span
key={item.id}
className="text-[#2196f3] text-[15px] hover:text-[#1976d2] mr-[20px] cursor-pointer
"
onClick={() => {
deployPopularModel(item.id, modalInstance)
}}
>
{item.name}
</span>
))}
</div>
</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>
),
content: <LocalAiDeploy ref={localAiDeployRef} onClose={() => {
modalInstance.destroy()
dumpServerPage()
}}></LocalAiDeploy>,
onOk: () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then(async (value) => {
await deployLocalModel(value)
resolve(true)
navigateTo(`/service/list`)
})
.catch((errorInfo) => reject(errorInfo))
return localAiDeployRef.current?.deployLocalAIServer().then((res) => {
if (res === true) {
dumpServerPage()
}
})
},
width: 600,
@@ -0,0 +1,145 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { Form, message, Select } from 'antd'
import { $t } from '@common/locales'
import { LocalModelItem, SimpleTeamItem } from '@common/const/type'
import { useFetch } from '@common/hooks/http'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import useDeployLocalModel from './deployModelUtil'
export type LocalAiDeployHandle = {
deployLocalAIServer: () => Promise<boolean | string>
}
const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any) => {
const { onClose } = props
const [form] = Form.useForm()
const { fetchData } = useFetch()
const [modelList, setModelList] = useState<any[]>([])
const [teamList, setTeamList] = useState<SimpleTeamItem[]>([])
const { deployLocalModel, getTeamOptionList } = useDeployLocalModel()
/**
* 获取本地模型列表
* @returns 本地模型列表
*/
const getLocalModelList = async () => {
const response = await fetchData<BasicResponse<{ models: LocalModelItem[] }>>(
'model/local/can_deploy',
{ method: 'GET', eoTransformKeys: ['is_popular'] }
)
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const modelList = data.models?.map((x: LocalModelItem) => {
return { ...x, label: x.name, value: x.id }
})
setModelList(modelList)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return []
}
}
/**
* 部署热门模型
* @param id 模型ID
* @returns
*/
const deployPopularModel = async (id: string) => {
await deployLocalModel({
modelID: id,
team: form.getFieldValue('team')
})
onClose?.()
}
const getTeamList = async () => {
const teamOptionList = await getTeamOptionList()
setTeamList(teamOptionList)
if (form.getFieldValue('team') === undefined && teamOptionList.length) {
form.setFieldValue('team', teamOptionList[0].value)
}
}
useEffect(() => {
getLocalModelList()
getTeamList()
}, [])
/**
* 部署本地AI
* @returns
*/
const deployLocalAIServer = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then(async (value) => {
await deployLocalModel(value)
resolve(true)
})
.catch((errorInfo) => reject(errorInfo))
})
}
useImperativeHandle(ref, () => ({
deployLocalAIServer
}))
return (
<WithPermission access="">
<Form
layout="vertical"
labelAlign="left"
scrollToFirstError
form={form}
className="mx-auto "
name="partitionInsideCert"
autoComplete="off"
>
<Form.Item label={$t('模型名称')} name="modelID" rules={[{ required: true }]}>
<Select
showSearch
className="w-INPUT_NORMAL"
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
placeholder={$t(PLACEHOLDER.input)}
options={modelList}
onChange={(value) => {
form.setFieldValue('modelID', value)
}}
></Select>
<div className="mt-[10px] mb-[5px]">
<Icon className="align-text-top" icon="noto-v1:fire" width="17" height="17" />
{$t('热点模型')}
</div>
<div className="pl-[5px] flex flex-wrap">
{modelList.length ?
modelList
.filter((item) => item.is_popular)
.map((item) => (
<span
key={item.id}
className="text-[#2196f3] text-[15px] hover:text-[#1976d2] mr-[20px] cursor-pointer
"
onClick={() => {
deployPopularModel(item.id)
}}
>
{item.name}
</span>
)) : null}
</div>
</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>
)
})
export default LocalAiDeploy
@@ -0,0 +1,125 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { Upload, UploadProps, Form, message, Select } from 'antd'
import { $t } from '@common/locales'
import { SimpleTeamItem } from '@common/const/type'
import { useFetch } from '@common/hooks/http'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import useDeployLocalModel from './deployModelUtil'
const { Dragger } = Upload
export type RestAIDeployHandle = {
deployRestAIServer: () => Promise<boolean | string>
}
const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any) => {
const [form] = Form.useForm()
const { fetchData } = useFetch()
const [teamList, setTeamList] = useState<SimpleTeamItem[]>([])
const { getTeamOptionList } = useDeployLocalModel()
const uploadProps: UploadProps = {
name: 'file',
multiple: false,
maxCount: 1,
beforeUpload: (file) => {
form.setFieldsValue({ key: file })
return false
}
}
const getTeamList = async () => {
const teamOptionList = await getTeamOptionList()
setTeamList(teamOptionList)
if (form.getFieldValue('team') === undefined && teamOptionList.length) {
form.setFieldValue('team', teamOptionList[0].value)
}
}
useEffect(() => {
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)
}
})
})
}
/**
* 部署本地AI
* @returns
*/
const deployRestAIServer = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then(async (value) => {
await deployRestServer(value.key.file)
resolve(true)
})
.catch((errorInfo) => reject(errorInfo))
})
}
useImperativeHandle(ref, () => ({
deployRestAIServer
}))
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 }]}
>
<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>
)
})
export default RestAIDeploy
@@ -0,0 +1,57 @@
// deployModelUtil.ts
import { useFetch } from '@common/hooks/http'
import { message } from 'antd'
import { STATUS_CODE, RESPONSE_TIPS, BasicResponse } from '@common/const/const'
import { $t } from '@common/locales'
import { MemberItem, SimpleTeamItem } from '@common/const/type'
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', {
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))
})
}
/**
* 获取 team 选项列表
* @returns
*/
const getTeamOptionList = async (): any[] => {
const response = await fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(
!checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams',
{ method: 'GET', eoTransformKeys: [] }
)
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const teamOptionList = data.teams?.map((x: MemberItem) => {
return { ...x, label: x.name, value: x.id }
})
return teamOptionList
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return []
}
}
return { deployLocalModel, getTeamOptionList }
}
export default useDeployLocalModel