From 671fce2961a1ea929269e019835956d73c06fda6 Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 26 Mar 2025 11:35:52 +0800 Subject: [PATCH] chore: cleanup file checkbox --- src/components/delete-modal/index.tsx | 85 +++++++++++++------ src/global.less | 1 + src/hooks/use-table-fetch.ts | 21 +++-- src/locales/en-US/resources.ts | 3 +- src/locales/ru-RU/resources.ts | 32 ++++--- src/locales/zh-CN/resources.ts | 3 +- src/pages/resources/apis/index.ts | 14 ++- .../resources/components/model-files.tsx | 67 ++++++++++++--- 8 files changed, 166 insertions(+), 60 deletions(-) diff --git a/src/components/delete-modal/index.tsx b/src/components/delete-modal/index.tsx index a196322d..15d1d58e 100644 --- a/src/components/delete-modal/index.tsx +++ b/src/components/delete-modal/index.tsx @@ -1,45 +1,68 @@ import useBodyScroll from '@/hooks/use-body-scroll'; import { ExclamationCircleFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Modal, Space, message, type ModalFuncProps } from 'antd'; -import { forwardRef, useImperativeHandle, useState } from 'react'; +import { + Button, + Checkbox, + Modal, + Space, + message, + type ModalFuncProps +} from 'antd'; +import { FC, forwardRef, useImperativeHandle, useState } from 'react'; +import styled from 'styled-components'; import Styles from './index.less'; -const DeleteModal = forwardRef((props, ref) => { +const CheckboxWrapper = styled.div` + margin-top: 20px; + margin-left: 30px; + display: flex; + justify-content: flex-start; + align-items: center; + .check-text { + font-weight: 700; + color: var(--ant-color-warning); + } +`; + +interface DeleteModalProps { + ref: any; +} + +interface DataOptions { + content: string; + selection?: boolean; + name?: string; + okText?: string; + cancelText?: string; + title?: string; + operation: string; + checkConfig?: { + checkText: string; + defautlChecked: boolean; + }; +} + +const DeleteModal: FC = forwardRef((props, ref) => { const intl = useIntl(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const [visible, setVisible] = useState(false); - const [config, setConfig] = useState< - ModalFuncProps & { - content: string; - selection?: boolean; - name?: string; - okText?: string; - cancelText?: string; - title?: string; - operation: string; - } - >({} as any); + const [checked, setChecked] = useState(false); + const [config, setConfig] = useState({} as any); useImperativeHandle(ref, () => ({ - show: ( - data: ModalFuncProps & { - content: string; - selection?: boolean; - name?: string; - title?: string; - cancelText?: string; - okText?: string; - operation: string; - } - ) => { + show: (data: ModalFuncProps & DataOptions) => { saveScrollHeight(); setConfig(data); + setChecked(data.checkConfig?.defautlChecked || false); setVisible(true); }, hide: () => { setVisible(false); restoreScrollHeight(); + }, + configuration: { + checked: checked } })); @@ -111,6 +134,18 @@ const DeleteModal = forwardRef((props, ref) => { ) }} > + {config.checkConfig && ( + + setChecked(e.target.checked)} + > + + {intl.formatMessage({ id: config.checkConfig?.checkText })} + + + + )} ); }); diff --git a/src/global.less b/src/global.less index 6c969a32..013e0b7d 100644 --- a/src/global.less +++ b/src/global.less @@ -24,6 +24,7 @@ html { --color-scrollbar-track: var(--ant-color-fill-tertiary); // --color-fill-1: #fff; --ant-color-text: #000; + --color-bg-1: #f4f5f4; --color-scroll-bg: #d9d9d9; --color-fill-2: #fff; --color-fill-3: #f3f6fa; diff --git a/src/hooks/use-table-fetch.ts b/src/hooks/use-table-fetch.ts index ab063fd4..40b2d155 100644 --- a/src/hooks/use-table-fetch.ts +++ b/src/hooks/use-table-fetch.ts @@ -11,7 +11,7 @@ export default function useTableFetch(options: { API?: string; watch?: boolean; fetchAPI: (params: any) => Promise>; - deleteAPI?: (id: number) => Promise; + deleteAPI?: (id: number, params?: any) => Promise; contentForDelete?: string; }) { const { fetchAPI, deleteAPI, contentForDelete, API, watch } = options; @@ -139,27 +139,38 @@ export default function useTableFetch(options: { const handleNameChange = debounceUpdateFilter; - const handleDelete = (row: ListItem & { name: string; id: number }) => { + const handleDelete = ( + row: ListItem & { name: string; id: number }, + options?: any + ) => { modalRef.current.show({ content: contentForDelete, operation: 'common.delete.single.confirm', name: row.name, + ...options, async onOk() { console.log('OK'); - await deleteAPI?.(row.id); + await deleteAPI?.(row.id, { + ...modalRef.current?.configuration + }); fetchData(); } }); }; - const handleDeleteBatch = () => { + const handleDeleteBatch = (options = {}) => { modalRef.current.show({ content: contentForDelete, operation: 'common.delete.confirm', selection: true, + ...options, async onOk() { if (!deleteAPI) return; - await handleBatchRequest(rowSelection.selectedRowKeys, deleteAPI); + await handleBatchRequest(rowSelection.selectedRowKeys, (id) => + deleteAPI(id, { + ...modalRef.current?.configuration + }) + ); rowSelection.clearSelections(); fetchData(); } diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index cfcbb31d..20a17052 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -68,5 +68,6 @@ export default { 'resources.modelfiles.storagePath.holder': 'Waiting for download to complete...', 'resources.filter.worker': 'Filter by Worker', - 'resources.filter.source': 'Filter by Source' + 'resources.filter.source': 'Filter by Source', + 'resources.modelfiles.delete.tips': 'Also delete the file from disk!' }; diff --git a/src/locales/ru-RU/resources.ts b/src/locales/ru-RU/resources.ts index 24c07850..a5510362 100644 --- a/src/locales/ru-RU/resources.ts +++ b/src/locales/ru-RU/resources.ts @@ -13,10 +13,14 @@ export default { 'resources.form.enablePartialOffload': 'Разрешить оффлоуд на CPU', 'resources.form.placementStrategy': 'Стратегия размещения', 'resources.form.workerSelector': 'Селектор воркеров', - 'resources.form.enableDistributedInferenceAcrossWorkers': 'Разрешить распределённый инференс между воркерами', - 'resources.form.spread.tips': 'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.', - 'resources.form.binpack.tips': 'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.', - 'resources.form.workerSelector.description': 'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.', + 'resources.form.enableDistributedInferenceAcrossWorkers': + 'Разрешить распределённый инференс между воркерами', + 'resources.form.spread.tips': + 'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.', + 'resources.form.binpack.tips': + 'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.', + 'resources.form.workerSelector.description': + 'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.', 'resources.table.ip': 'IP-адрес', 'resources.table.cpu': 'CPU', 'resources.table.memory': 'ОЗУ', @@ -39,12 +43,16 @@ export default { 'resources.table.unified': 'Объединённая память', 'resources.worker.add.step1': 'Получить токен', 'resources.worker.add.step2': 'Зарегистрировать воркер', - 'resources.worker.add.step2.tips': 'Примечание: mytoken — токен из первого шага.', - 'resources.worker.add.step3': 'После успешной регистрации обновите список воркеров.', + 'resources.worker.add.step2.tips': + 'Примечание: mytoken — токен из первого шага.', + 'resources.worker.add.step3': + 'После успешной регистрации обновите список воркеров.', 'resources.worker.container.supported': 'Только для Linux.', 'resources.worker.current.version': 'Текущая версия: {version}', - 'resources.worker.driver.install': 'Установите необходимые драйверы и библиотеки перед установкой GPUStack.', - 'resources.worker.select.command': 'Выберите метку для генерации команды и скопируйте её.', + 'resources.worker.driver.install': + 'Установите необходимые драйверы и библиотеки перед установкой GPUStack.', + 'resources.worker.select.command': + 'Выберите метку для генерации команды и скопируйте её.', 'resources.worker.script.install': 'Установка скриптом', 'resources.worker.container.install': 'Установка контейнером (только Linux)', 'resources.worker.cann.tips': `Укажите индексы GPU через ASCEND_VISIBLE_DEVICES=0,1,2,3 или ASCEND_VISIBLE_DEVICES=0-3.`, @@ -54,13 +62,15 @@ export default { 'resources.modelfiles.size': 'Размер', 'resources.modelfiles.selecttarget': 'Выбрать назначение', 'resources.modelfiles.form.localdir': 'Локальный каталог', - 'resources.modelfiles.form.localdir.tips': 'Каталог по умолчанию --data-dir.', + 'resources.modelfiles.form.localdir.tips': + 'Каталог по умолчанию --data-dir.', 'resources.modelfiles.retry.download': 'Повторить загрузку', 'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...', 'resources.filter.worker': 'Фильтровать по узлу', - 'resources.filter.source': 'Фильтровать по источнику' + 'resources.filter.source': 'Фильтровать по источнику', + 'resources.modelfiles.delete.tips': 'Also delete the file from disk!' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== - +// 1. 'resources.modelfiles.delete.tips' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts index 02f22fb7..d5f54f83 100644 --- a/src/locales/zh-CN/resources.ts +++ b/src/locales/zh-CN/resources.ts @@ -66,5 +66,6 @@ export default { 'resources.modelfiles.retry.download': '重新下载', 'resources.modelfiles.storagePath.holder': '等待下载完成...', 'resources.filter.worker': '按 worker 筛选', - 'resources.filter.source': '按来源筛选' + 'resources.filter.source': '按来源筛选', + 'resources.modelfiles.delete.tips': '同时从磁盘删除文件!' }; diff --git a/src/pages/resources/apis/index.ts b/src/pages/resources/apis/index.ts index 61102c7a..59616031 100644 --- a/src/pages/resources/apis/index.ts +++ b/src/pages/resources/apis/index.ts @@ -45,10 +45,16 @@ export async function queryModelFilesList(params: Global.SearchParams) { }); } -export async function deleteModelFile(id: string | number) { - return request>(`${MODEL_FILES_API}/${id}`, { - method: 'DELETE' - }); +export async function deleteModelFile( + id: string | number, + params: { checked: boolean } +) { + return request>( + `${MODEL_FILES_API}/${id}?cleanup=${params.checked}`, + { + method: 'DELETE' + } + ); } export async function updateModelFile(id: string | number, data: any) { diff --git a/src/pages/resources/components/model-files.tsx b/src/pages/resources/components/model-files.tsx index 60e4879b..83f5d810 100644 --- a/src/pages/resources/components/model-files.tsx +++ b/src/pages/resources/components/model-files.tsx @@ -44,6 +44,7 @@ import dayjs from 'dayjs'; import { useAtom } from 'jotai'; import _ from 'lodash'; import { useEffect, useState } from 'react'; +import styled from 'styled-components'; import { useGenerateFormEditInitialValues, useGenerateModelFileOptions @@ -69,6 +70,26 @@ import { const filterPattern = /^(.*?)(?:-\d+-of-\d+)?(\.gguf)?$/; +const PathWrapper = styled.div` + position: relative; + display: flex; + align-items: center; + height: 100%; + .btn-wrapper { + position: absolute; + background: var(--color-bg-1); + right: 0; + display: none; + align-items: center; + } + &:hover { + .btn-wrapper { + display: flex; + background: var(--ant-table-row-hover-bg); + } + } +`; + const getWorkerName = ( id: number, workersList: Global.BaseOption[] @@ -76,6 +97,7 @@ const getWorkerName = ( const worker = workersList.find((item) => item.value === id); return worker?.label || ''; }; + const InstanceStatusTag = (props: { data: ListItem }) => { const { data } = props; if (!data.state) { @@ -272,10 +294,18 @@ const ModelFiles = () => { const handleSelect = async (val: any, record: ListItem) => { try { if (val === 'delete') { - handleDelete({ - ...record, - name: record.local_path - }); + handleDelete( + { + ...record, + name: record.local_path + }, + { + checkConfig: { + checkText: 'resources.modelfiles.delete.tips', + defautlChecked: record.source !== modelSourceMap.local_path_value + } + } + ); } else if (val === 'retry') { await retryDownloadModelFile(record.id); showSuccess(); @@ -357,6 +387,15 @@ const ModelFiles = () => { restoreScrollHeight(); }; + const handleDeleteByBatch = () => { + handleDeleteBatch({ + checkConfig: { + checkText: 'resources.modelfiles.delete.tips', + defautlChecked: false + } + }); + }; + const handleCreateModel = async (data: any) => { try { const result = getSourceRepoConfigValue(openDeployModal.source, data); @@ -430,17 +469,19 @@ const ModelFiles = () => { } return ( record.resolved_paths?.length > 0 && ( - + {record.resolved_paths?.[0]} - - {renderParts(record)} - + + + {renderParts(record)} + + ) ); } @@ -534,7 +575,7 @@ const ModelFiles = () => {