diff --git a/src/assets/images/external-link.png b/src/assets/images/external-link.png new file mode 100644 index 00000000..8ee12059 Binary files /dev/null and b/src/assets/images/external-link.png differ diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx index a3cef232..997dc979 100644 --- a/src/components/status-tag/index.tsx +++ b/src/components/status-tag/index.tsx @@ -79,7 +79,7 @@ const StatusTag: React.FC = ({ if (link) { return statusValue.message?.replace( linkReg, - '$2' + `$2` ); } return statusValue.message; diff --git a/src/locales/en-US/backends.ts b/src/locales/en-US/backends.ts index d003c5d6..bfda00db 100644 --- a/src/locales/en-US/backends.ts +++ b/src/locales/en-US/backends.ts @@ -23,5 +23,6 @@ export default { 'backend.form.versionConfig': 'Versions Config', 'backend.form.addParameter': 'Add Parameter', 'backend.form.noVersion': 'No versions added', - 'backend.noVersion': 'No versions found' + 'backend.noVersion': 'No versions found', + 'backend.versionInfo.autoImage': 'Automatically selected at runtime' }; diff --git a/src/locales/ja-JP/backends.ts b/src/locales/ja-JP/backends.ts index d003c5d6..bfda00db 100644 --- a/src/locales/ja-JP/backends.ts +++ b/src/locales/ja-JP/backends.ts @@ -23,5 +23,6 @@ export default { 'backend.form.versionConfig': 'Versions Config', 'backend.form.addParameter': 'Add Parameter', 'backend.form.noVersion': 'No versions added', - 'backend.noVersion': 'No versions found' + 'backend.noVersion': 'No versions found', + 'backend.versionInfo.autoImage': 'Automatically selected at runtime' }; diff --git a/src/locales/ru-RU/backends.ts b/src/locales/ru-RU/backends.ts index d003c5d6..bfda00db 100644 --- a/src/locales/ru-RU/backends.ts +++ b/src/locales/ru-RU/backends.ts @@ -23,5 +23,6 @@ export default { 'backend.form.versionConfig': 'Versions Config', 'backend.form.addParameter': 'Add Parameter', 'backend.form.noVersion': 'No versions added', - 'backend.noVersion': 'No versions found' + 'backend.noVersion': 'No versions found', + 'backend.versionInfo.autoImage': 'Automatically selected at runtime' }; diff --git a/src/locales/zh-CN/backends.ts b/src/locales/zh-CN/backends.ts index fe37561d..6c4d78d2 100644 --- a/src/locales/zh-CN/backends.ts +++ b/src/locales/zh-CN/backends.ts @@ -23,5 +23,6 @@ export default { 'backend.form.versionConfig': '版本配置', 'backend.form.addParameter': '添加参数', 'backend.form.noVersion': '未添加版本', - 'backend.noVersion': '未找到版本' + 'backend.noVersion': '未找到版本', + 'backend.versionInfo.autoImage': '运行时自动选择' }; diff --git a/src/pages/_components/column-wrapper/style.less b/src/pages/_components/column-wrapper/style.less index 6774f907..5770f24f 100644 --- a/src/pages/_components/column-wrapper/style.less +++ b/src/pages/_components/column-wrapper/style.less @@ -14,6 +14,7 @@ flex: 1; display: flex; position: relative; + width: 100%; .column-wrapper { border-left: none; diff --git a/src/pages/backends/forms/version-info.tsx b/src/pages/backends/forms/version-info.tsx index a2757027..f5682f05 100644 --- a/src/pages/backends/forms/version-info.tsx +++ b/src/pages/backends/forms/version-info.tsx @@ -108,7 +108,7 @@ export const VersionItem: React.FC = ({ data }) => { {data.is_built_in - ? 'Selected dynamically at runtime' + ? intl.formatMessage({ id: 'backend.versionInfo.autoImage' }) : data.image_name} diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 65acbb82..b19f914b 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -1,11 +1,14 @@ +import { getRequestId } from '@/atoms/models'; import ModalFooter from '@/components/modal-footer'; import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; +import useDeferredRequest from '@/hooks/use-deferred-request'; import { ProviderValueMap } from '@/pages/cluster-management/config'; import { useIntl } from '@umijs/max'; +import { useMemoizedFn } from 'ahooks'; import { Button } from 'antd'; import _ from 'lodash'; -import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FC, useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import ColumnWrapper from '../../_components/column-wrapper'; import { defaultFormValues, deployFormKeyMap, modelSourceMap } from '../config'; @@ -20,14 +23,21 @@ import { } from '../hooks'; import useCheckBackend from '../hooks/use-check-backend'; import CompatibilityAlert from './compatible-alert'; -import GGUFResult from './gguf-result'; +import HFModelFile from './hf-model-file'; import ModelCard from './model-card'; import SearchModel from './search-model'; import Separator from './separator'; import TitleWrapper from './title-wrapper'; const pickFieldsFromSpec = ['backend_version', 'backend_parameters', 'env']; -const dropFieldsFromForm = ['name', 'file_name', 'repo_id', 'backend']; +const dropFieldsFromForm = [ + 'name', + 'huggingface_filename', + 'model_scope_file_path', + 'model_scope_model_id', + 'huggingface_repo_id', + 'backend' +]; const resetFields = ['worker_selector', 'env']; const ModalFooterStyle = { @@ -127,6 +137,11 @@ const AddModal: FC = (props) => { const requestModelIdRef = useRef(0); const currentSelectedModel = useRef({}); + const { run: fetchModelFiles } = useDeferredRequest( + () => modelFileRef.current?.fetchModelFiles?.(), + 100 + ); + const updateSelectedModel = (model: any) => { currentSelectedModel.current = model; setSelectedModel(model); @@ -182,6 +197,78 @@ const AddModal: FC = (props) => { return categories || null; }; + const { run: onSelectFile } = useDeferredRequest( + async (item: any, modelInfo: any, manual?: boolean) => { + unlockWarningStatus(); + + const evaluateRes = await handleOnValuesChangeBefore?.({ + changedValues: {}, + allValues: form.current?.form?.getFieldsValue?.(), + source: props.source + }); + console.log('onSelectFile:', item, modelInfo, evaluateRes); + + // for cancel evaluate request case + if (!evaluateRes) { + return; + } + + const defaultSpec = getDefaultSpec({ + evaluateResult: evaluateRes + }); + + /** + * do not reset backend_parameters when select a model file + */ + const formValues = form.current?.getFieldsValue?.(pickFieldsFromSpec); + + form.current?.setFieldsValue?.({ + ..._.omit(modelInfo, ['name']), + huggingface_filename: item.fakeName, + model_scope_file_path: item.fakeName, + backend_parameters: + formValues.backend_parameters?.length > 0 + ? formValues.backend_parameters + : defaultSpec.backend_parameters || [], + backend_version: + formValues.backend_version || defaultSpec.backend_version, + env: formValues.env || defaultSpec.env, + categories: getCategory(item) + }); + }, + 100 + ); + + const handleSelectModelFile = async ( + item: any, + options: { requestModelId: number; manual?: boolean } + ) => { + const { requestModelId, manual } = options || {}; + if (requestModelId !== getRequestId()) { + return; + } + console.log('handleSelectModelFile:', item, selectedModel); + + const modelInfo = onSelectModel(selectedModel, props.source); + + form.current?.setFieldsValue?.({ + ..._.omit(modelInfo, ['name']), + huggingface_filename: item.fakeName, + model_scope_file_path: item.fakeName, + backend_parameters: [], + backend_version: '', + backend: '', + env: {}, + categories: getCategory(item) + }); + + // evaluate the form data when select a model file + // TODO: reset backend related fields when select a GGUF file + if (item.fakeName) { + // onSelectFile(item, modelInfo, manual); + } + }; + const handleCancelFiles = () => { cancelEvaluate(); modelFileRef.current?.cancelRequest(); @@ -242,8 +329,7 @@ const AddModal: FC = (props) => { }; if (item.isGGUF) { - warningStatus.type = 'danger'; - warningStatus.message = 'GGUF model is not supported.'; + fetchModelFiles(); } setWarningStatus(warningStatus, { override: true }); }; @@ -317,25 +403,21 @@ const AddModal: FC = (props) => { }; const handleBackendChange = async (backend: string) => { - setIsGGUF(false); - const data = form.current.form.getFieldsValue?.(); const res = handleBackendChangeBefore(data); if (res.show) { return; } if (data.local_path || props.source !== modelSourceMap.local_path_value) { - // TODO confirm wheather it is gguf by model file not by backend + // TODO confirm whether it is gguf by model file not by backend handleOnValuesChange?.({ changedValues: {}, - // allValues: - // backend === backendOptionsMap.llamaBox - // ? data - // : _.omit(data, [ - // 'cpu_offloading', - // 'distributed_inference_across_workers' - // ]), - allValues: data, + allValues: isGGUF + ? data + : _.omit(data, [ + 'cpu_offloading', + 'distributed_inference_across_workers' + ]), source: props.source }); } @@ -349,9 +431,9 @@ const AddModal: FC = (props) => { }); }; - const handleCancel = useCallback(() => { + const handleCancel = useMemoizedFn(() => { onCancel?.(); - }, [onCancel]); + }); const initClusterId = () => { if (initialValues?.cluster_id) { @@ -492,7 +574,7 @@ const AddModal: FC = (props) => { - + = (props) => { modelSource={props.source} setIsGGUF={handleSetIsGGUF} > - {isGGUF && } + + {isGGUF && ( + + )} @@ -532,11 +623,7 @@ const AddModal: FC = (props) => { showOkBtn={!showExtraButton} extra={ showExtraButton && ( -