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 10 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /*
  2. * @Author: 赵伟
  3. * @Date: 2024-04-16 13:58:08
  4. * @Description: 模型部署列表
  5. */
  6. import CommonTableCell from '@/components/CommonTableCell';
  7. import DateTableCell from '@/components/DateTableCell';
  8. import KFIcon from '@/components/KFIcon';
  9. import PageTitle from '@/components/PageTitle';
  10. import { ModelDeploymentStatus, modelDeploymentStatusOptions } from '@/enums';
  11. import { useCacheState } from '@/hooks/pageCacheState';
  12. import {
  13. deleteModelDeploymentReq,
  14. getModelDeploymentListReq,
  15. stopModelDeploymentReq,
  16. } from '@/services/modelDeployment';
  17. import themes from '@/styles/theme.less';
  18. import { to } from '@/utils/promise';
  19. import { modelDeploymentInfoKey, setSessionStorageItem } from '@/utils/sessionStorage';
  20. import { modalConfirm } from '@/utils/ui';
  21. import { useNavigate } from '@umijs/max';
  22. import {
  23. App,
  24. Button,
  25. ConfigProvider,
  26. Input,
  27. Select,
  28. Table,
  29. type TablePaginationConfig,
  30. type TableProps,
  31. } from 'antd';
  32. import { type SearchProps } from 'antd/es/input';
  33. import classNames from 'classnames';
  34. import { pick } from 'lodash';
  35. import { useEffect, useState } from 'react';
  36. import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell';
  37. import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
  38. import styles from './index.less';
  39. function ModelDeployment() {
  40. const navigate = useNavigate();
  41. const { message } = App.useApp();
  42. const [cacheState, setCacheState] = useCacheState();
  43. const [searchStatus, setSearchStatus] = useState(cacheState?.searchStatus ?? '');
  44. const [searchText, setSearchText] = useState(cacheState?.searchText);
  45. const [inputText, setInputText] = useState(cacheState?.searchText);
  46. const [tableData, setTableData] = useState<ModelDeploymentData[]>([]);
  47. const [total, setTotal] = useState(0);
  48. const [pagination, setPagination] = useState<TablePaginationConfig>(
  49. cacheState?.pagination ?? {
  50. current: 1,
  51. pageSize: 10,
  52. },
  53. );
  54. useEffect(() => {
  55. getModelDeploymentList();
  56. }, [pagination, searchText, searchStatus]);
  57. // 获取模型部署列表
  58. const getModelDeploymentList = async () => {
  59. const params: Record<string, any> = {
  60. page: pagination.current!,
  61. size: pagination.pageSize,
  62. service_name: searchText,
  63. status: searchStatus,
  64. };
  65. const [res] = await to(getModelDeploymentListReq(params));
  66. if (res && res.data) {
  67. const { service_list = [], total = 0 } = res.data;
  68. setTableData(service_list);
  69. setTotal(total);
  70. }
  71. };
  72. // 删除模型部署
  73. const deleteModelDeploy = async (record: ModelDeploymentData) => {
  74. const params = pick(record, ['service_id', 'service_ins_id']);
  75. const [res] = await to(deleteModelDeploymentReq(params));
  76. if (res) {
  77. message.success('删除成功');
  78. // 如果是一页的唯一数据,删除时,请求第一页的数据
  79. // 否则直接刷新这一页的数据
  80. // 避免回到第一页
  81. if (tableData.length > 1) {
  82. setPagination((prev) => ({
  83. ...prev,
  84. current: 1,
  85. }));
  86. } else {
  87. getModelDeploymentList();
  88. }
  89. }
  90. };
  91. // 停止模型部署
  92. const stopModelDeploy = async (record: ModelDeploymentData) => {
  93. const params = pick(record, ['service_id', 'service_ins_id']);
  94. const [res] = await to(stopModelDeploymentReq(params));
  95. if (res) {
  96. message.success('操作成功');
  97. getModelDeploymentList();
  98. }
  99. };
  100. // 搜索
  101. const onSearch: SearchProps['onSearch'] = (value) => {
  102. setSearchText(value);
  103. };
  104. // 处理删除
  105. const handleModelDeployDelete = (record: ModelDeploymentData) => {
  106. modalConfirm({
  107. title: '删除后,该模型部署将不可恢复',
  108. content: '是否确认删除?',
  109. onOk: () => {
  110. deleteModelDeploy(record);
  111. },
  112. });
  113. };
  114. // 处理停止
  115. const handleModelDeployStop = async (record: ModelDeploymentData) => {
  116. modalConfirm({
  117. content: '是否确认停止?',
  118. onOk: () => {
  119. stopModelDeploy(record);
  120. },
  121. });
  122. };
  123. // 创建、更新、重启模型部署
  124. const createModelDeployment = (
  125. type: ModelDeploymentOperationType,
  126. record?: ModelDeploymentData,
  127. ) => {
  128. setSessionStorageItem(
  129. modelDeploymentInfoKey,
  130. {
  131. ...record,
  132. operationType: type,
  133. },
  134. true,
  135. );
  136. setCacheState({
  137. pagination,
  138. searchText,
  139. searchStatus,
  140. });
  141. navigate(`/modelDeployment/create`);
  142. };
  143. // 查看详情
  144. const toDetail = (record: ModelDeploymentData) => {
  145. setSessionStorageItem(modelDeploymentInfoKey, record, true);
  146. setCacheState({
  147. pagination,
  148. searchText,
  149. searchStatus,
  150. });
  151. navigate(`/modelDeployment/info/${record.service_id}`);
  152. };
  153. // 分页切换
  154. const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
  155. if (action === 'paginate') {
  156. setPagination(pagination);
  157. }
  158. // console.log(pagination, filters, sorter, action);
  159. };
  160. const columns: TableProps<ModelDeploymentData>['columns'] = [
  161. {
  162. title: '序号',
  163. dataIndex: 'index',
  164. key: 'index',
  165. width: '20%',
  166. render(_text, _record, index) {
  167. return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
  168. },
  169. },
  170. {
  171. title: '服务名称',
  172. dataIndex: 'service_name',
  173. key: 'service_name',
  174. width: '20%',
  175. render: (text, record) => {
  176. return (
  177. <a className="kf-table-row-link" onClick={() => toDetail(record)}>
  178. {text}
  179. </a>
  180. );
  181. },
  182. },
  183. {
  184. title: '模型',
  185. dataIndex: ['model', 'show_value'],
  186. key: 'model',
  187. width: '20%',
  188. render: CommonTableCell(),
  189. },
  190. {
  191. title: '状态',
  192. dataIndex: 'status',
  193. key: 'status',
  194. width: '20%',
  195. render: ModelDeploymentStatusCell,
  196. },
  197. {
  198. title: '创建人',
  199. dataIndex: 'created_by',
  200. key: 'created_by',
  201. render: CommonTableCell(),
  202. width: '20%',
  203. },
  204. {
  205. title: '更新时间',
  206. dataIndex: 'update_time',
  207. key: 'update_time',
  208. width: '20%',
  209. render: DateTableCell,
  210. },
  211. {
  212. title: '操作',
  213. dataIndex: 'operation',
  214. width: 250,
  215. key: 'operation',
  216. render: (_: any, record: ModelDeploymentData) => (
  217. <div>
  218. <Button
  219. type="link"
  220. size="small"
  221. key="edit"
  222. icon={<KFIcon type="icon-bianji" />}
  223. onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)}
  224. >
  225. 更新
  226. </Button>
  227. {(record.status === ModelDeploymentStatus.Failed ||
  228. record.status === ModelDeploymentStatus.Stopped) && (
  229. <Button
  230. type="link"
  231. size="small"
  232. key="run"
  233. icon={<KFIcon type="icon-yunhang" />}
  234. onClick={() => createModelDeployment(ModelDeploymentOperationType.Restart, record)}
  235. >
  236. 重启
  237. </Button>
  238. )}
  239. {(record.status === ModelDeploymentStatus.Running ||
  240. record.status === ModelDeploymentStatus.Init ||
  241. record.status === ModelDeploymentStatus.Pending) && (
  242. <Button
  243. type="link"
  244. size="small"
  245. key="stop"
  246. icon={<KFIcon type="icon-tingzhi" />}
  247. onClick={() => handleModelDeployStop(record)}
  248. >
  249. 停止
  250. </Button>
  251. )}
  252. <ConfigProvider
  253. theme={{
  254. token: {
  255. colorLink: themes['warningColor'],
  256. },
  257. }}
  258. >
  259. <Button
  260. type="link"
  261. size="small"
  262. key="remove"
  263. icon={<KFIcon type="icon-shanchu" />}
  264. onClick={() => handleModelDeployDelete(record)}
  265. >
  266. 删除
  267. </Button>
  268. </ConfigProvider>
  269. </div>
  270. ),
  271. },
  272. ];
  273. return (
  274. <div className={styles['model-deployment']}>
  275. <PageTitle title="模型列表"></PageTitle>
  276. <div className={styles['model-deployment__content']}>
  277. <div className={styles['model-deployment__content__filter']}>
  278. <Input.Search
  279. placeholder="按模型服务名称筛选"
  280. onSearch={onSearch}
  281. onChange={(e) => setInputText(e.target.value)}
  282. style={{ width: 300 }}
  283. value={inputText}
  284. allowClear
  285. />
  286. <Select
  287. style={{ width: 100, marginLeft: '20px' }}
  288. placeholder="请选择"
  289. onChange={(value) => setSearchStatus(value)}
  290. options={modelDeploymentStatusOptions}
  291. value={searchStatus}
  292. allowClear
  293. ></Select>
  294. <Button
  295. style={{ marginLeft: '20px' }}
  296. type="default"
  297. onClick={() => createModelDeployment(ModelDeploymentOperationType.Create)}
  298. icon={<KFIcon type="icon-xinjian2" />}
  299. >
  300. 创建推理服务
  301. </Button>
  302. <Button
  303. style={{ marginRight: 0, marginLeft: 'auto' }}
  304. type="default"
  305. onClick={getModelDeploymentList}
  306. icon={<KFIcon type="icon-shuaxin" />}
  307. >
  308. 刷新
  309. </Button>
  310. </div>
  311. <div
  312. className={classNames(
  313. 'vertical-scroll-table',
  314. styles['model-deployment__content__table'],
  315. )}
  316. >
  317. <Table
  318. dataSource={tableData}
  319. columns={columns}
  320. scroll={{ y: 'calc(100% - 55px)' }}
  321. pagination={{
  322. ...pagination,
  323. total: total,
  324. showSizeChanger: true,
  325. showQuickJumper: true,
  326. showTotal: () => `共${total}条`,
  327. }}
  328. onChange={handleTableChange}
  329. rowKey="service_id"
  330. />
  331. </div>
  332. </div>
  333. </div>
  334. );
  335. }
  336. export default ModelDeployment;