You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. * @Author: 赵伟
  3. * @Date: 2024-04-16 13:58:08
  4. * @Description: 创建模型部署
  5. */
  6. import KFIcon from '@/components/KFIcon';
  7. import PageTitle from '@/components/PageTitle';
  8. import ResourceSelect, {
  9. requiredValidator,
  10. type ParameterInputObject,
  11. } from '@/components/ResourceSelect';
  12. import SubAreaTitle from '@/components/SubAreaTitle';
  13. import { CommonTabKeys } from '@/enums';
  14. import { useComputingResource } from '@/hooks/resource';
  15. import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal';
  16. import {
  17. createModelDeploymentReq,
  18. restartModelDeploymentReq,
  19. updateModelDeploymentReq,
  20. } from '@/services/modelDeployment';
  21. import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils';
  22. import { to } from '@/utils/promise';
  23. import {
  24. getSessionStorageItem,
  25. modelDeploymentInfoKey,
  26. removeSessionStorageItem,
  27. } from '@/utils/sessionStorage';
  28. import { modalConfirm } from '@/utils/ui';
  29. import { useNavigate } from '@umijs/max';
  30. import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd';
  31. import { omit, pick } from 'lodash';
  32. import { useEffect, useState } from 'react';
  33. import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
  34. import styles from './index.less';
  35. // 表单数据
  36. export type FormData = {
  37. serviceName: string; // 服务名称
  38. description: string; // 描述
  39. model: ParameterInputObject; // 模型
  40. image: ParameterInputObject; // 镜像
  41. resource: string; // 资源规格
  42. replicas: string; // 副本数量
  43. modelPath: string; // 模型路径
  44. env: { key: string; value: string }[]; // 环境变量
  45. };
  46. function ModelDeploymentCreate() {
  47. const navigate = useNavigate();
  48. const [form] = Form.useForm();
  49. const [resourceStandardList, filterResourceStandard] = useComputingResource();
  50. const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create);
  51. const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>(
  52. undefined,
  53. );
  54. const { message } = App.useApp();
  55. useEffect(() => {
  56. const res = getSessionStorageItem(modelDeploymentInfoKey, true);
  57. if (res) {
  58. setOperationType(res.operationType);
  59. setModelDeploymentInfo(res);
  60. const formData = underscoreToCamelCase(res) as FormData;
  61. form.setFieldsValue(formData);
  62. }
  63. return () => {
  64. removeSessionStorageItem(modelDeploymentInfoKey);
  65. };
  66. }, []);
  67. // 创建
  68. const createModelDeployment = async (formData: FormData) => {
  69. const envList = formData['env'] ?? [];
  70. const image = formData['image'];
  71. const model = formData['model'];
  72. const env = envList.reduce((acc, cur) => {
  73. acc[cur.key] = cur.value;
  74. return acc;
  75. }, {} as Record<string, string>);
  76. // 根据后台要求,修改表单数据
  77. const object = camelCaseToUnderscore({
  78. ...omit(formData, ['replicas', 'env', 'image', 'model']),
  79. replicas: Number(formData.replicas),
  80. env,
  81. image: image.value,
  82. model: pick(model, ['id', 'version', 'path', 'showValue']),
  83. });
  84. const params =
  85. operationType === ModelDeploymentOperationType.Create
  86. ? object
  87. : {
  88. ...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']),
  89. update_model: {
  90. ...pick(object, ['description', 'env', 'replicas', 'resource', 'image']),
  91. },
  92. };
  93. let request = createModelDeploymentReq;
  94. if (operationType === ModelDeploymentOperationType.Restart) {
  95. request = restartModelDeploymentReq;
  96. } else if (operationType === ModelDeploymentOperationType.Update) {
  97. request = updateModelDeploymentReq;
  98. }
  99. const [res] = await to(request(params));
  100. if (res) {
  101. message.success('操作成功');
  102. navigate(-1);
  103. }
  104. };
  105. // 提交
  106. const handleSubmit = (values: FormData) => {
  107. createModelDeployment(values);
  108. };
  109. // 取消
  110. const cancel = () => {
  111. navigate(-1);
  112. };
  113. const disabled = operationType !== ModelDeploymentOperationType.Create;
  114. let buttonText = '新建';
  115. if (operationType === ModelDeploymentOperationType.Update) {
  116. buttonText = '更新';
  117. } else if (operationType === ModelDeploymentOperationType.Restart) {
  118. buttonText = '重启';
  119. }
  120. return (
  121. <div className={styles['model-deployment-create']}>
  122. <PageTitle title="创建推理服务"></PageTitle>
  123. <div className={styles['model-deployment-create__content']}>
  124. <div>
  125. <Form
  126. name="model-deployment-create"
  127. labelCol={{ flex: '100px' }}
  128. labelAlign="left"
  129. form={form}
  130. initialValues={{ upload_type: CommonTabKeys.Public }}
  131. onFinish={handleSubmit}
  132. size="large"
  133. autoComplete="off"
  134. >
  135. <SubAreaTitle
  136. title="基本信息"
  137. image={require('@/assets/img/mirror-basic.png')}
  138. style={{ marginBottom: '26px' }}
  139. ></SubAreaTitle>
  140. <Row gutter={8}>
  141. <Col span={10}>
  142. <Form.Item
  143. label="服务名称"
  144. name="serviceName"
  145. rules={[
  146. {
  147. required: true,
  148. message: '请输入服务名称',
  149. },
  150. ]}
  151. >
  152. <Input
  153. placeholder="请输入服务名称"
  154. disabled={disabled}
  155. maxLength={30}
  156. showCount
  157. allowClear
  158. />
  159. </Form.Item>
  160. </Col>
  161. </Row>
  162. <Row gutter={8}>
  163. <Col span={20}>
  164. <Form.Item
  165. label="描  述"
  166. name="description"
  167. rules={[
  168. {
  169. required: true,
  170. message: '请输入描述',
  171. },
  172. ]}
  173. >
  174. <Input.TextArea
  175. autoSize={{ minRows: 2, maxRows: 6 }}
  176. placeholder="请输入描述,最长128字符"
  177. maxLength={128}
  178. showCount
  179. allowClear
  180. />
  181. </Form.Item>
  182. </Col>
  183. </Row>
  184. <SubAreaTitle
  185. title="部署构建"
  186. image={require('@/assets/img/model-deployment.png')}
  187. style={{ marginTop: '20px', marginBottom: '24px' }}
  188. ></SubAreaTitle>
  189. <Row gutter={8}>
  190. <Col span={10}>
  191. <Form.Item
  192. label="选择模型"
  193. name="model"
  194. rules={[
  195. {
  196. validator: requiredValidator,
  197. message: '请选择模型',
  198. },
  199. ]}
  200. required
  201. >
  202. <ResourceSelect
  203. type={ResourceSelectorType.Model}
  204. placeholder="请选择模型"
  205. disabled={disabled}
  206. canInput={false}
  207. size="large"
  208. />
  209. </Form.Item>
  210. </Col>
  211. </Row>
  212. <Row gutter={8}>
  213. <Col span={10}>
  214. <Form.Item
  215. label="选择镜像"
  216. name="image"
  217. rules={[
  218. {
  219. validator: requiredValidator,
  220. message: '请选择镜像',
  221. },
  222. ]}
  223. required
  224. >
  225. <ResourceSelect
  226. type={ResourceSelectorType.Mirror}
  227. placeholder="请选择镜像"
  228. canInput={false}
  229. size="large"
  230. />
  231. </Form.Item>
  232. </Col>
  233. </Row>
  234. <Row gutter={8}>
  235. <Col span={10}>
  236. <Form.Item
  237. label="资源规格"
  238. name="resource"
  239. rules={[
  240. {
  241. required: true,
  242. message: '请选择资源规格',
  243. },
  244. ]}
  245. >
  246. <Select
  247. showSearch
  248. placeholder="请选择资源规格"
  249. filterOption={filterResourceStandard}
  250. options={resourceStandardList}
  251. fieldNames={{
  252. label: 'description',
  253. value: 'standard',
  254. }}
  255. />
  256. </Form.Item>
  257. </Col>
  258. </Row>
  259. <Row gutter={8}>
  260. <Col span={10}>
  261. <Form.Item
  262. label="副本数量"
  263. name="replicas"
  264. rules={[
  265. {
  266. required: true,
  267. message: '请输入副本数量',
  268. },
  269. {
  270. pattern: /^[1-9]\d*$/,
  271. message: '副本数量必须是正整数',
  272. },
  273. ]}
  274. >
  275. <Input placeholder="请输入副本数量" allowClear />
  276. </Form.Item>
  277. </Col>
  278. </Row>
  279. <Row gutter={8}>
  280. <Col span={10}>
  281. <Form.Item
  282. label="挂载路径"
  283. name="modelPath"
  284. rules={[
  285. {
  286. required: true,
  287. message: '请输入模型挂载路径',
  288. },
  289. ]}
  290. >
  291. <Input
  292. placeholder="请输入模型挂载路径"
  293. disabled={disabled}
  294. maxLength={64}
  295. showCount
  296. allowClear
  297. />
  298. </Form.Item>
  299. </Col>
  300. </Row>
  301. <Form.List name="env">
  302. {(fields, { add, remove }) => (
  303. <>
  304. <Row gutter={8}>
  305. <Col span={10}>
  306. <Form.Item label="环境变量">
  307. <Button type="link" style={{ padding: '0' }} onClick={() => add()}>
  308. 添加环境变量
  309. </Button>
  310. </Form.Item>
  311. </Col>
  312. </Row>
  313. {fields.map(({ key, name, ...restField }) => (
  314. <Flex key={key} align="center" gap="0 8px" style={{ width: '50%' }}>
  315. <Form.Item
  316. {...restField}
  317. name={[name, 'key']}
  318. style={{ flex: 1 }}
  319. rules={[{ required: true, message: '请输入变量名' }]}
  320. >
  321. <Input placeholder="请输入变量名" />
  322. </Form.Item>
  323. <span style={{ marginBottom: '24px' }}>=</span>
  324. <Form.Item
  325. {...restField}
  326. name={[name, 'value']}
  327. style={{ flex: 1 }}
  328. rules={[{ required: true, message: '请输入变量值' }]}
  329. >
  330. <Input placeholder="请输入变量值" />
  331. </Form.Item>
  332. <Button
  333. type="link"
  334. style={{ marginBottom: '24px' }}
  335. icon={<KFIcon type="icon-shanchu" font={16} />}
  336. onClick={() => {
  337. modalConfirm({
  338. content: '是否确认删除?',
  339. onOk: () => {
  340. remove(name);
  341. },
  342. });
  343. }}
  344. ></Button>
  345. </Flex>
  346. ))}
  347. </>
  348. )}
  349. </Form.List>
  350. <Form.Item wrapperCol={{ offset: 0, span: 16 }}>
  351. <Button type="primary" htmlType="submit">
  352. {buttonText}
  353. </Button>
  354. <Button
  355. type="default"
  356. htmlType="button"
  357. onClick={cancel}
  358. style={{ marginLeft: '20px' }}
  359. >
  360. 取消
  361. </Button>
  362. </Form.Item>
  363. </Form>
  364. </div>
  365. </div>
  366. </div>
  367. );
  368. }
  369. export default ModelDeploymentCreate;