| @@ -43,7 +43,7 @@ function ParameterInput({ | |||
| } | |||
| const isSelect = valueObj?.fromSelect; | |||
| const InputComponent = textArea ? Input.TextArea : Input; | |||
| const placeholder = valueObj?.placeholder; | |||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | |||
| return ( | |||
| <> | |||
| @@ -0,0 +1,17 @@ | |||
| .editor-create { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 10px; | |||
| overflow: auto; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__type { | |||
| color: @text-color; | |||
| font-size: @font-size-input-lg; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,344 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建镜像 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import ParameterInput from '@/components/ParameterInput'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { createEditorReq } from '@/services/developmentEnvironment'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import styles from './index.less'; | |||
| type FormData = { | |||
| name: string; | |||
| computing_resource: string; | |||
| standard: string; | |||
| image: string; | |||
| model: ResourceSelectorResponse; | |||
| dataset: ResourceSelectorResponse; | |||
| }; | |||
| enum ComputingResourceType { | |||
| GPU = 'GPU', | |||
| NPU = 'NPU', | |||
| } | |||
| const EditorRadioItems: KFRadioItem[] = [ | |||
| { | |||
| key: ComputingResourceType.GPU, | |||
| title: '英伟达GPU', | |||
| icon: <KFIcon type="icon-jiyugongwangjingxiang" />, | |||
| }, | |||
| { | |||
| key: ComputingResourceType.NPU, | |||
| title: '昇腾NPU', | |||
| icon: <KFIcon type="icon-bendishangchuan" />, | |||
| }, | |||
| ]; | |||
| function EditorCreate() { | |||
| const navgite = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const { message } = App.useApp(); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [selectedDataset, setSelectedDataset] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的数据集,为了再次打开时恢复原来的选择 | |||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||
| // 创建编辑器 | |||
| const createEditor = async (formData: FormData) => { | |||
| // const { model, dataset } = formData; | |||
| // const params = { | |||
| // ...formData, | |||
| // model: JSON.stringify(omit(model, ['showValue'])), | |||
| // dataset: JSON.stringify(dataset, ['showValue']), | |||
| // }; | |||
| const [res] = await to(createEditorReq(formData)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navgite(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| createEditor(values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navgite(-1); | |||
| }; | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||
| }; | |||
| // 选择模型、镜像、数据集 | |||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||
| let resource: ResourceSelectorResponse | undefined; | |||
| switch (type) { | |||
| case ResourceSelectorType.Model: | |||
| resource = selectedModel; | |||
| break; | |||
| case ResourceSelectorType.Dataset: | |||
| resource = selectedDataset; | |||
| break; | |||
| default: | |||
| resource = selectedMirror; | |||
| break; | |||
| } | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| form.setFieldValue(name, res.path); | |||
| setSelectedMirror(res); | |||
| } else { | |||
| const showValue = `${res.name}:${res.version}`; | |||
| form.setFieldValue(name, { | |||
| ...pick(res, ['id', 'version', 'path']), | |||
| showValue, | |||
| }); | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(res); | |||
| } else if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(res); | |||
| } | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } else if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(undefined); | |||
| } else if (type === ResourceSelectorType.Mirror) { | |||
| setSelectedMirror(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| return ( | |||
| <div className={styles['editor-create']}> | |||
| <PageTitle title="创建编辑器"></PageTitle> | |||
| <div className={styles['editor-create__content']}> | |||
| <div> | |||
| <Form | |||
| name="editor-create" | |||
| labelCol={{ flex: '100px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="任务名称" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入任务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入任务名称" maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="计算资源" | |||
| name="computing_resource" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择计算资源', | |||
| }, | |||
| ]} | |||
| > | |||
| <KFRadio items={EditorRadioItems}></KFRadio> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| showSearch | |||
| placeholder="请选择资源规格" | |||
| filterOption={filterResourceStandard} | |||
| options={resourceStandardList} | |||
| fieldNames={{ | |||
| label: 'description', | |||
| value: 'standard', | |||
| }} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <SubAreaTitle | |||
| title="参数设置" | |||
| image={require('@/assets/img/editor-parameter.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| > | |||
| 选择镜像 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型" | |||
| name="model" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择模型', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput | |||
| placeholder="请选择模型" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| > | |||
| 选择模型 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集" | |||
| name="dataset" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择数据集', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Dataset)} | |||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||
| > | |||
| 选择数据集 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| 确定 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default EditorCreate; | |||
| @@ -0,0 +1,22 @@ | |||
| .develop-env { | |||
| height: 100%; | |||
| &__header { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| height: 50px; | |||
| margin-bottom: 10px; | |||
| padding: 0 30px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 60px); | |||
| padding: 20px 30px 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,263 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 开发环境 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { DevEditorStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { | |||
| deleteEditorReq, | |||
| getEditorListReq, | |||
| startEditorReq, | |||
| stopEditorReq, | |||
| } from '@/services/developmentEnvironment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Table, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import EditorStatusCell from '../components/EditorStatusCell'; | |||
| import styles from './index.less'; | |||
| export type EditorData = { | |||
| id: number; | |||
| name: string; | |||
| status: string; | |||
| computing_resource: string; | |||
| update_by: string; | |||
| create_time: string; | |||
| }; | |||
| function EditorList() { | |||
| const navigate = useNavigate(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const { message } = App.useApp(); | |||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getEditorList(); | |||
| }, [pagination]); | |||
| // 获取编辑器列表 | |||
| const getEditorList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| }; | |||
| const [res] = await to(getEditorListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| } | |||
| }; | |||
| // 删除编辑器 | |||
| const deleteEditor = async (id: number) => { | |||
| const [res] = await to(deleteEditorReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getEditorList(); | |||
| } | |||
| } | |||
| }; | |||
| // 启动编辑器 | |||
| const startEditor = async (id: number) => { | |||
| const [res] = await to(startEditorReq(id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getEditorList(); | |||
| } | |||
| }; | |||
| // 停止编辑器 | |||
| const stopEditor = async (id: number) => { | |||
| const [res] = await to(stopEditorReq(id)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| getEditorList(); | |||
| } | |||
| }; | |||
| // 处理删除 | |||
| const handleEditorDelete = (record: EditorData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该编辑器将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteEditor(record.id); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建编辑器 | |||
| const createEditor = () => { | |||
| navigate(`/developmentEnvironment/create`); | |||
| setCacheState({ | |||
| pagination, | |||
| }); | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| }; | |||
| const columns: TableProps<EditorData>['columns'] = [ | |||
| { | |||
| title: '编辑器名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: '10%', | |||
| render: EditorStatusCell, | |||
| }, | |||
| { | |||
| title: '资源', | |||
| dataIndex: 'computing_resource', | |||
| key: 'computing_resource', | |||
| width: '20%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '创建者', | |||
| dataIndex: 'update_by', | |||
| key: 'update_by', | |||
| width: '20%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: '20%', | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 300, | |||
| key: 'operation', | |||
| render: (_: any, record: EditorData) => ( | |||
| <div> | |||
| {record.status === DevEditorStatus.Pending || | |||
| record.status === DevEditorStatus.Running ? ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => stopEditor(record.id)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| ) : ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="debug" | |||
| icon={<KFIcon type="icon-tiaoshi" />} | |||
| onClick={() => startEditor(record.id)} | |||
| > | |||
| 再次调试 | |||
| </Button> | |||
| )} | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| colorLink: themes['warningColor'], | |||
| }, | |||
| }} | |||
| > | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => handleEditorDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </ConfigProvider> | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['develop-env']}> | |||
| <div className={styles['develop-env__header']}> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={createEditor} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 创建编辑器 | |||
| </Button> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={getEditorList} | |||
| icon={<KFIcon type="icon-shuaxin" />} | |||
| > | |||
| 刷新 | |||
| </Button> | |||
| </div> | |||
| <div className={classNames('vertical-scroll-table', styles['develop-env__table'])}> | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default EditorList; | |||
| @@ -0,0 +1,19 @@ | |||
| .model-deployment-status-cell { | |||
| color: @text-color; | |||
| &--running { | |||
| color: @primary-color; | |||
| } | |||
| &--terminated { | |||
| color: @abort-color; | |||
| } | |||
| &--error { | |||
| color: @error-color; | |||
| } | |||
| &--pending { | |||
| color: @warning-color; | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: 编辑器状态组件 | |||
| */ | |||
| import { DevEditorStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| export type DevEditorStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| export const statusInfo: Record<DevEditorStatus, DevEditorStatusInfo> = { | |||
| [DevEditorStatus.Unknown]: { | |||
| text: '未启动', | |||
| classname: styles['model-deployment-status-cell'], | |||
| }, | |||
| [DevEditorStatus.Running]: { | |||
| classname: styles['model-deployment-status-cell--running'], | |||
| text: '运行中', | |||
| }, | |||
| [DevEditorStatus.Terminated]: { | |||
| classname: styles['model-deployment-status-cell--terminated'], | |||
| text: '已停止', | |||
| }, | |||
| [DevEditorStatus.Failed]: { | |||
| classname: styles['model-deployment-status-cell--error'], | |||
| text: '失败', | |||
| }, | |||
| [DevEditorStatus.Pending]: { | |||
| classname: styles['model-deployment-status-cell--pending'], | |||
| text: '启动中', | |||
| }, | |||
| }; | |||
| function EditorStatusCell(status?: DevEditorStatus | null) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||
| } | |||
| export default EditorStatusCell; | |||
| @@ -31,23 +31,5 @@ | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| &__table { | |||
| :global { | |||
| .ant-table-wrapper { | |||
| height: 100%; | |||
| .ant-spin-nested-loading { | |||
| height: 100%; | |||
| } | |||
| .ant-spin-container { | |||
| height: 100%; | |||
| } | |||
| .ant-table { | |||
| height: calc(100% - 74px); | |||
| overflow: auto; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -32,7 +32,6 @@ import { | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | |||
| import styles from './index.less'; | |||
| @@ -282,7 +281,7 @@ function MirrorInfo() { | |||
| </Flex> | |||
| </div> | |||
| <div | |||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | |||
| className="vertical-scroll-table" | |||
| style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} | |||
| > | |||
| <Table | |||
| @@ -63,6 +63,9 @@ function ModelDeploymentCreate() { | |||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | |||
| undefined, | |||
| ); | |||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| @@ -84,16 +87,14 @@ function ModelDeploymentCreate() { | |||
| }; | |||
| // 选择模型、镜像 | |||
| const selectResource = (name: string, selectType: string) => { | |||
| let type; | |||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||
| let resource: ResourceSelectorResponse | undefined; | |||
| switch (selectType) { | |||
| case 'model': | |||
| type = ResourceSelectorType.Model; | |||
| switch (type) { | |||
| case ResourceSelectorType.Model: | |||
| resource = selectedModel; | |||
| break; | |||
| default: | |||
| type = ResourceSelectorType.Mirror; | |||
| resource = selectedMirror; | |||
| break; | |||
| } | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| @@ -105,18 +106,20 @@ function ModelDeploymentCreate() { | |||
| if (res) { | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| form.setFieldValue(name, res.path); | |||
| setSelectedMirror(res); | |||
| } else { | |||
| const response = res as ResourceSelectorResponse; | |||
| const showValue = `${response.name}:${response.version}`; | |||
| const showValue = `${res.name}:${res.version}`; | |||
| form.setFieldValue(name, { | |||
| ...pick(response, ['id', 'version', 'path']), | |||
| ...pick(res, ['id', 'version', 'path']), | |||
| showValue, | |||
| }); | |||
| setSelectedModel(response); | |||
| setSelectedModel(res); | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } else { | |||
| setSelectedMirror(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| @@ -248,7 +251,6 @@ function ModelDeploymentCreate() { | |||
| image={require('@/assets/img/model-deployment.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| @@ -266,6 +268,7 @@ function ModelDeploymentCreate() { | |||
| disabled={disabled} | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| @@ -275,7 +278,7 @@ function ModelDeploymentCreate() { | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||
| onClick={() => selectResource('model', 'model')} | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| > | |||
| 选择模型 | |||
| </Button> | |||
| @@ -293,7 +296,12 @@ function ModelDeploymentCreate() { | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput placeholder="请选择镜像" canInput={false} size="large" /> | |||
| <ParameterInput | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| @@ -301,7 +309,7 @@ function ModelDeploymentCreate() { | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||
| onClick={() => selectResource('image', 'image')} | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| > | |||
| 选择镜像 | |||
| </Button> | |||
| @@ -1,16 +0,0 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 查询开发环境url | |||
| export function getJupyterUrl(params) { | |||
| return request(`/api/mmp/jupyter/getURL`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 查询 labelStudio url | |||
| export function getLabelStudioUrl(params) { | |||
| return request(`/api/mmp/labelStudio/getURL`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 查询开发环境url | |||
| export function getJupyterUrl(params: any) { | |||
| return request(`/api/mmp/jupyter/getURL`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 查询 labelStudio url | |||
| export function getLabelStudioUrl(params: any) { | |||
| return request(`/api/mmp/labelStudio/getURL`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 创建编辑器 | |||
| export function createEditorReq(data: any) { | |||
| return request(`/api/mmp/devEnvironment`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 获取编辑器列表 | |||
| export function getEditorListReq(params: any) { | |||
| return request(`/api/mmp/devEnvironment`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| // 获取编辑器详情 | |||
| export function getEditorInfoReq(id: number) { | |||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 创建编辑器 | |||
| export function deleteEditorReq(id: number) { | |||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 启动编辑器 | |||
| export function startEditorReq(id: number) { | |||
| return request(`/api/mmp/jupyter/run/${id}`, { | |||
| method: 'POST', | |||
| }); | |||
| } | |||
| // 停止编辑器 | |||
| export function stopEditorReq(id: number) { | |||
| return request(`/api/mmp/jupyter/stop/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||