diff --git a/src/components/icon-font/icons.ts b/src/components/icon-font/icons.ts index 8e36f053..b35be6e4 100644 --- a/src/components/icon-font/icons.ts +++ b/src/components/icon-font/icons.ts @@ -60,7 +60,9 @@ const icons = { LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }), Permission: React.createElement(IconFont, { type: 'icon-permission' }), CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }), - StarOutlined: React.createElement(StarOutlined) + StarOutlined: React.createElement(StarOutlined), + Charger: React.createElement(IconFont, { type: 'icon-charger' }), + Disabled: React.createElement(IconFont, { type: 'icon-disabled' }) }; export default icons; diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index cd197314..98032ffb 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; // import './iconfont/iconfont.js'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_2h8628ohalv.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_339f1qeidmn.js' }); export default IconFont; diff --git a/src/locales/en-US/backends.ts b/src/locales/en-US/backends.ts index 58340113..f8da7b44 100644 --- a/src/locales/en-US/backends.ts +++ b/src/locales/en-US/backends.ts @@ -38,5 +38,7 @@ export default { 'backend.replaceEntrypoint': 'Override Image Entrypoint', 'backend.entrypoint': 'Image Entrypoint', 'backend.entrypoint.tips': - 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.' + 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.', + 'backend.form.defaultEnvironment': 'Default Environment Variables', + 'backend.recommendModels': 'Recommended Models' }; diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 4ef4cb04..aa5e4372 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -273,5 +273,7 @@ export default { 'No compatible GPUs are available in the selected cluster for this model.', 'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, 'models.form.readyWorkers': 'workers ready', - 'models.form.maxContextLength': 'Maximum Context Length' + 'models.form.maxContextLength': 'Maximum Context Length', + 'models.form.backend.helperText': + 'Not enabled yet. Will be enabled after deployment. ' }; diff --git a/src/locales/ja-JP/backends.ts b/src/locales/ja-JP/backends.ts index 58340113..f8da7b44 100644 --- a/src/locales/ja-JP/backends.ts +++ b/src/locales/ja-JP/backends.ts @@ -38,5 +38,7 @@ export default { 'backend.replaceEntrypoint': 'Override Image Entrypoint', 'backend.entrypoint': 'Image Entrypoint', 'backend.entrypoint.tips': - 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.' + 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.', + 'backend.form.defaultEnvironment': 'Default Environment Variables', + 'backend.recommendModels': 'Recommended Models' }; diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index 17c5778b..a799282d 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -273,7 +273,9 @@ export default { 'No compatible GPUs are available in the selected cluster for this model.', 'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, 'models.form.readyWorkers': 'workers ready', - 'models.form.maxContextLength': 'Maximum Context Length' + 'models.form.maxContextLength': 'Maximum Context Length', + 'models.form.backend.helperText': + 'Not enabled yet. Will be enabled after deployment. ' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -369,5 +371,6 @@ export default { // 73. 'models.catalog.nogpus.tips': 'No compatible GPUs are available in the selected cluster for this model.', // 74. 'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, // 75. 'models.form.readyWorkers': 'workers ready', -// 76. 'models.form.maxContextLength': 'Maximum Context Length' +// 76. 'models.form.maxContextLength': 'Maximum Context Length', +// 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/backends.ts b/src/locales/ru-RU/backends.ts index d3c1ae2a..fc46de3c 100644 --- a/src/locales/ru-RU/backends.ts +++ b/src/locales/ru-RU/backends.ts @@ -38,9 +38,12 @@ export default { 'backend.replaceEntrypoint': 'Переопределить точку входа образа', 'backend.entrypoint': 'Точка входа образа', 'backend.entrypoint.tips': - 'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.' + 'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.', + 'backend.form.defaultEnvironment': 'Default Environment Variables', + 'backend.recommendModels': 'Recommended Models' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== - +// 1. 'backend.form.defaultEnvironment': 'Default Environment Variables', +// 2. 'backend.recommendModels': 'Recommended Models' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index fc4e6df3..39ddf2d1 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -277,9 +277,12 @@ export default { 'В выбранном кластере нет доступных GPU, совместимых с этой моделью.', 'models.form.modelfile.notfound': `Указанный путь к файлу модели не существует на сервере GPUStack. Рекомендуется размещать файл модели по одному и тому же пути как на сервере GPUStack, так и на воркерах GPUStack. Это поможет системе принимать лучшие решения по распределению ресурсов.`, 'models.form.readyWorkers': 'воркеров готово', - 'models.form.maxContextLength': 'Maximum Context Length' + 'models.form.maxContextLength': 'Maximum Context Length', + 'models.form.backend.helperText': + 'Not enabled yet. Will be enabled after deployment. ' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== -// 1. 'models.form.maxContextLength': 'Maximum Context Length' +// 1. 'models.form.maxContextLength': 'Maximum Context Length', +// 2. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/backends.ts b/src/locales/zh-CN/backends.ts index 71a699fe..0d3f6b41 100644 --- a/src/locales/zh-CN/backends.ts +++ b/src/locales/zh-CN/backends.ts @@ -35,5 +35,7 @@ export default { 'backend.replaceEntrypoint': '覆盖镜像入口命令', 'backend.entrypoint': '镜像入口命令', 'backend.entrypoint.tips': - '如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。' + '如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。', + 'backend.form.defaultEnvironment': '默认环境变量', + 'backend.recommendModels': '推荐模型' }; diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index a4bb7851..ced774a5 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -257,5 +257,6 @@ export default { 'models.form.modelfile.notfound': '你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', 'models.form.readyWorkers': '节点就绪', - 'models.form.maxContextLength': '最大上下文长度' + 'models.form.maxContextLength': '最大上下文长度', + 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用' }; diff --git a/src/pages/backends/components/add-modal.tsx b/src/pages/backends/components/add-modal.tsx index 80923c19..1dd8d952 100644 --- a/src/pages/backends/components/add-modal.tsx +++ b/src/pages/backends/components/add-modal.tsx @@ -11,6 +11,7 @@ import React, { useEffect, useId, useRef, useState } from 'react'; import styled from 'styled-components'; import ColumnWrapper from '../../_components/column-wrapper'; import { + BackendSourceValueMap, builtInBackendFields, customBackendFields, json2Yaml @@ -55,7 +56,8 @@ const AddModal: React.FC = (props) => { 'image_name', 'run_command', 'custom_framework', - 'entrypoint' + 'entrypoint', + 'environment' ]; // remove '-custom' suffix from version_no in currentData, when action is EDIT @@ -64,7 +66,7 @@ const AddModal: React.FC = (props) => { data.version_configs = Object.entries(data.version_configs || {}).reduce( (acc, [key, value]) => { const version = key.replace(/-custom$/, ''); - acc[version] = { ...value }; + acc[version] = { ...value, backend_source: data.backend_source }; return acc; }, {} as any @@ -128,7 +130,9 @@ const AddModal: React.FC = (props) => { is_default: key === values.default_version, built_in_frameworks: values.built_in_version_configs?.[key]?.built_in_frameworks || [], - is_built_in: true, + is_built_in: + data.is_built_in && + data.backend_source === BackendSourceValueMap.BUILTIN, ..._.pick(values.built_in_version_configs?.[key], [ 'image_name', 'run_command', diff --git a/src/pages/backends/components/backend-card.tsx b/src/pages/backends/components/backend-card.tsx index c83f90ae..43e343cd 100644 --- a/src/pages/backends/components/backend-card.tsx +++ b/src/pages/backends/components/backend-card.tsx @@ -5,7 +5,7 @@ import TagWrapper from '@/components/tags-wrapper'; import ThemeTag from '@/components/tags-wrapper/theme-tag'; import Card from '@/components/templates/card'; import { useIntl } from '@umijs/max'; -import { Button, Tag } from 'antd'; +import { Button, Flex, Tag, Tooltip } from 'antd'; import _ from 'lodash'; import { useMemo } from 'react'; import semverCoerce from 'semver/functions/coerce'; @@ -18,7 +18,8 @@ import { builtInBackendLogos, customColors, customIcons, - getGpuColor + getGpuColor, + TagColorMap } from '../config'; import { ListItem } from '../config/types'; @@ -142,12 +143,6 @@ interface BackendCardProps { data: ListItem; } -const TagColorMap: Record = { - [BackendSourceValueMap.CUSTOM]: 'purple', - [BackendSourceValueMap.BUILTIN]: 'geekblue', - [BackendSourceValueMap.COMMUNITY]: 'cyan' -}; - const BackendCard: React.FC = ({ data, onSelect }) => { const intl = useIntl(); @@ -254,22 +249,65 @@ const BackendCard: React.FC = ({ data, onSelect }) => { }; const renderRecommendModels = () => { - const recommnadedModels = data.recommend_models || []; - if (recommnadedModels.length === 0) { + const recommendedModels = data.recommend_models || []; + if (recommendedModels.length === 0) { return null; } return (
- + + {recommendedModels.map((item) => ( + + {item} + + ))} + + } + > + + + {intl.formatMessage({ id: 'backend.recommendModels' })} + + +
); }; + const renderSource = () => { + const source = data.is_built_in + ? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || '' + : BackendSourceLabelMap[data.backend_source] || ''; + if (!source) { + return null; + } + return ( + + {intl.formatMessage({ + id: source + })} + + ); + }; + return ( = ({ data, onSelect }) => { Source: - - {intl.formatMessage({ - id: data.is_built_in - ? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] - : BackendSourceLabelMap[data.backend_source] - })} - + {renderSource()} {data.backend_source === BackendSourceValueMap.COMMUNITY && ( <> = ({ data, onSelect }) => { )} - {/* {renderRecommendModels()} */} + {renderRecommendModels()} {renderFrameworks()} diff --git a/src/pages/backends/components/version-info-modal.tsx b/src/pages/backends/components/version-info-modal.tsx index b3d0fc0b..3b658680 100644 --- a/src/pages/backends/components/version-info-modal.tsx +++ b/src/pages/backends/components/version-info-modal.tsx @@ -2,7 +2,9 @@ import ScrollerModal from '@/components/scroller-modal'; import { PlusOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; +import _ from 'lodash'; import { useEffect, useState } from 'react'; +import { BackendSourceValueMap } from '../config'; import { VersionListItem } from '../config/types'; import VersionInfo from '../forms/version-info'; @@ -26,10 +28,11 @@ const VersionInfoModal: React.FC = ({ if (open && currentData) { // add is_built_in field to built_in_version_configs const builtInVersions = currentData.built_in_version_configs || {}; - for (const key in builtInVersions) { if (builtInVersions?.hasOwnProperty(key)) { - builtInVersions[key].is_built_in = true; + builtInVersions[key].is_built_in = + currentData.backend_source === BackendSourceValueMap.BUILTIN && + currentData.is_built_in; } } @@ -41,9 +44,13 @@ const VersionInfoModal: React.FC = ({ const versionList: VersionListItem[] = Object.entries(versions).map( ([key, value]: [string, any]) => ({ version_no: key, - image_name: value.image_name, - run_command: value.run_command, - entrypoint: value.entrypoint, + ..._.pick(value, [ + 'image_name', + 'run_command', + 'entrypoint', + 'environment', + 'backend_source' + ]), is_default: key === currentData.default_version, availableFrameworks: [ ...(value.built_in_frameworks || []), diff --git a/src/pages/backends/config/index.ts b/src/pages/backends/config/index.ts index c113c68b..510a0471 100644 --- a/src/pages/backends/config/index.ts +++ b/src/pages/backends/config/index.ts @@ -43,6 +43,12 @@ export const BackendSourceLabelMap: Record = { [BackendSourceValueMap.USER_DEFINED]: 'models.form.backend.custom' }; +export const TagColorMap: Record = { + [BackendSourceValueMap.CUSTOM]: 'purple', + [BackendSourceValueMap.BUILTIN]: 'geekblue', + [BackendSourceValueMap.COMMUNITY]: 'cyan' +}; + export const backendActions = [ { label: 'common.button.edit', @@ -63,7 +69,7 @@ export const backendActions = [ value: 'enable', key: 'enable', locale: true, - icon: icons.Yaml, + icon: icons.Charger, show: (record: any) => !record.enabled && record.backend_source === BackendSourceValueMap.COMMUNITY @@ -73,7 +79,7 @@ export const backendActions = [ value: 'disable', key: 'disable', locale: true, - icon: icons.Yaml, + icon: icons.Disabled, show: (record: any) => record.enabled && record.backend_source === BackendSourceValueMap.COMMUNITY diff --git a/src/pages/backends/config/types.ts b/src/pages/backends/config/types.ts index 8029977c..7cfd8d10 100644 --- a/src/pages/backends/config/types.ts +++ b/src/pages/backends/config/types.ts @@ -7,6 +7,8 @@ export interface VersionConfigs { entrypoint?: string; version_no?: string; is_built_in?: boolean; + backend_source?: string; + environment: Record; } export interface VersionListItem extends VersionConfigs { @@ -24,6 +26,8 @@ export interface FormData { allowed_proxy_uris?: string[]; content?: string; enabled?: boolean; + backend_source?: string; + default_environment?: Record; } export interface ListItem extends FormData { diff --git a/src/pages/backends/forms/basic.tsx b/src/pages/backends/forms/basic.tsx index 6686b1af..f212c7ff 100644 --- a/src/pages/backends/forms/basic.tsx +++ b/src/pages/backends/forms/basic.tsx @@ -1,3 +1,4 @@ +import LabelSelector from '@/components/label-selector'; import ListInput from '@/components/list-input'; import SealInput from '@/components/seal-form/seal-input'; import SealTextArea from '@/components/seal-form/seal-textarea'; @@ -6,7 +7,8 @@ import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; -import React from 'react'; +import React, { useEffect } from 'react'; +import { BackendSourceValueMap } from '../config'; import { FormData, ListItem } from '../config/types'; type AddModalProps = { @@ -17,6 +19,17 @@ const BasicForm: React.FC = ({ action, currentData }) => { const form = Form.useFormInstance(); const intl = useIntl(); const { getRuleMessage } = useAppUtils(); + const defaultEnvs = Form.useWatch('default_environment', form); + + const handleEnviromentVarsChange = (labels: Record) => { + form.setFieldValue('env', labels); + }; + + useEffect(() => { + if (action === PageAction.CREATE) { + form.setFieldValue('backend_source', BackendSourceValueMap.CUSTOM); + } + }, [action]); return ( <> @@ -37,6 +50,11 @@ const BasicForm: React.FC = ({ action, currentData }) => { required > + {action === PageAction.CREATE && ( + hidden name="backend_source"> + + + )} {!currentData?.is_built_in && ( <> @@ -86,6 +104,16 @@ const BasicForm: React.FC = ({ action, currentData }) => { })} > + name="default_environment"> + + name="description" rules={[{ required: false }]}> = ({ data }) => { const intl = useIntl(); + + const renderSource = () => { + console.log('data.is_built_in', data.is_built_in); + const source = data.is_built_in + ? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || '' + : BackendSourceLabelMap[data.backend_source || ''] || ''; + if (!source) { + return null; + } + return ( + + {intl.formatMessage({ + id: source + })} + + ); + }; return (
{data.version_no} - {data.is_built_in && ( - - {intl.formatMessage({ id: 'backend.builtin' })} - - )} + {renderSource()} {!data.is_built_in && data.is_default && ( = ({ run_command: '', entrypoint: '', isBuiltin: false, - is_default: false + is_default: false, + environment: {} } ]; @@ -102,7 +104,8 @@ const VersionsForm: React.FC = ({ run_command: '', entrypoint: '', isBuiltin: false, - is_default: false + is_default: false, + environment: {} }; form.setFieldValue('version_configs', [...versions, newVersion]); }; @@ -136,6 +139,25 @@ const VersionsForm: React.FC = ({ setDefaultVersion(value); }; + const handleEnviromentVarsChange = ( + envs: Record, + name: number + ) => { + const versions = form.getFieldValue('version_configs') || []; + const updatedVersions = versions.map((version: any, idx: number) => { + if (idx === name) { + return { + ...version, + environment: { + ...envs + } + }; + } + return version; + }); + form.setFieldValue('version_configs', updatedVersions); + }; + useEffect(() => { const versions = form.getFieldValue('version_configs') || []; @@ -227,6 +249,7 @@ const VersionsForm: React.FC = ({ { add, remove } ) => { const versionConfigs = form.getFieldValue('version_configs'); + console.log('versionConfigs', versionConfigs); return fields?.map(({ key, name }) => (
= ({ })} > - + = ({ label={intl.formatMessage({ id: 'backend.runCommand' })} > + + + handleEnviromentVarsChange(envs, name) + } + > +
)); diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index e6fd10b6..6764789e 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -1,6 +1,7 @@ import DeleteModal from '@/components/delete-modal'; import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; +import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; import { useIntl } from '@umijs/max'; @@ -122,7 +123,7 @@ const BackendList = () => { const handleAddBackend = () => { setOpenModalStatus({ open: true, - action: 'create' + action: PageAction.CREATE }); }; @@ -161,6 +162,7 @@ const BackendList = () => { enabled: item.action === 'enable' } }); + handleSearch(); } }; diff --git a/src/pages/llmodels/config/form-context.ts b/src/pages/llmodels/config/form-context.ts index e4eae078..7ff22151 100644 --- a/src/pages/llmodels/config/form-context.ts +++ b/src/pages/llmodels/config/form-context.ts @@ -20,7 +20,13 @@ interface FormContextProps { action: PageActionType; gpuOptions: CascaderOption[]; workerLabelOptions: CascaderOption[]; - backendOptions: BackendOption[]; + backendOptions: { + label: string; + value: string; + title?: string; + children: BackendOption[]; + }[]; + flatBackendOptions: BackendOption[]; initialValues?: FormData; // for editing model modelContextData?: Record; clearCacheFormValues?: () => void; diff --git a/src/pages/llmodels/forms/advance-config.tsx b/src/pages/llmodels/forms/advance-config.tsx index 70d8765e..62ed79b7 100644 --- a/src/pages/llmodels/forms/advance-config.tsx +++ b/src/pages/llmodels/forms/advance-config.tsx @@ -16,12 +16,12 @@ const AdvanceConfig = () => { const form = Form.useFormInstance(); const EnviromentVars = Form.useWatch('env', form); const backend = Form.useWatch('backend', form); - const { onValuesChange, backendOptions, isGGUF, modelContextData } = + const { onValuesChange, isGGUF, modelContextData, flatBackendOptions } = useFormContext(); const currentBackendOptions = useMemo(() => { - return backendOptions?.find((item) => item.value === backend); - }, [backend, backendOptions]); + return flatBackendOptions?.find((item) => item.value === backend); + }, [backend, flatBackendOptions]); const handleEnviromentVarsChange = (labels: Record) => { form.setFieldValue('env', labels); diff --git a/src/pages/llmodels/forms/backend.tsx b/src/pages/llmodels/forms/backend.tsx index 95910410..81f7e9e6 100644 --- a/src/pages/llmodels/forms/backend.tsx +++ b/src/pages/llmodels/forms/backend.tsx @@ -1,13 +1,16 @@ import SealCascader from '@/components/seal-form/seal-cascader'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; +import TooltipList from '@/components/tooltip-list'; +import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; import { BackendSourceValueMap } from '@/pages/backends/config'; import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { useIntl, useNavigate } from '@umijs/max'; import { Form, Select } from 'antd'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import styled from 'styled-components'; +import { backendTipsList } from '../config'; import { backendOptionsMap } from '../config/backend-parameters'; import { useFormContext } from '../config/form-context'; import { BackendOption } from '../config/types'; @@ -29,7 +32,13 @@ const BackendFields: React.FC = () => { const navigate = useNavigate(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); - const { onValuesChange, backendOptions, onBackendChange } = useFormContext(); + const { + action, + onValuesChange, + backendOptions, + flatBackendOptions, + onBackendChange + } = useFormContext(); const backend = Form.useWatch('backend', form); const [showDeprecated, setShowDeprecated] = React.useState(false); const [selectedBackend, setSelectedBackend] = @@ -39,31 +48,21 @@ const BackendFields: React.FC = () => { onValuesChange?.({}, form.getFieldsValue()); }; - const backendGroupedOptions = useMemo(() => { - const builtInBackends = backendOptions?.filter( - (item) => item.isBuiltIn || item.value === backendOptionsMap.custom - ); - const customBackends = backendOptions?.filter( - (item) => !item.isBuiltIn && item.value !== backendOptionsMap.custom - ); - - const options = []; - - if (builtInBackends && builtInBackends.length > 0) { - options.push({ - label: intl.formatMessage({ id: 'backend.builtin' }), - options: builtInBackends - }); + const backendHelperText = useMemo(() => { + const selected = flatBackendOptions?.find((item) => item.value === backend); + if ( + selected && + !selected.enabled && + selected.backend_source === BackendSourceValueMap.COMMUNITY + ) { + return ( + + {intl.formatMessage({ id: 'models.form.backend.helperText' })} + + ); } - - if (customBackends && customBackends.length > 0) { - options.push({ - label: intl.formatMessage({ id: 'models.form.backend.custom' }), - options: customBackends - }); - } - return options; - }, [backendOptions, intl]); + return null; + }, [backend, flatBackendOptions, intl]); const backendVersions = useMemo((): { builtIn: any[]; @@ -111,14 +110,6 @@ const BackendFields: React.FC = () => { }; }, [backend, selectedBackend, intl]); - const optionRender = (option: any) => { - return option.data.title; - }; - - const labelRender = (option: any) => { - return option.title; - }; - const backendVersionLabelRender = (option: any) => { console.log('backendVersionLabelRender option:', option); return option.title; @@ -140,10 +131,21 @@ const BackendFields: React.FC = () => { }; const handleOnBackendChange = (value: any[], option: any) => { - console.log('handleOnBackendChange value, option:', value, option); - form.setFieldsValue({ backend: value[0] }); - onBackendChange?.(value[0], option[1]); - setSelectedBackend(option[1]); + form.setFieldValue('backend', value?.[1]); + onBackendChange?.(value?.[1], option?.[1] || {}); + setSelectedBackend(option?.[1] || {}); + }; + + const displayRender = (labels: any[], selectedOptions?: any[]) => { + const groupTitle = selectedOptions?.[0]?.title; + if (!groupTitle) { + return {labels?.[0]}; + } + return ( + + {intl.formatMessage({ id: groupTitle })} / {labels?.[1]} + + ); }; const renderDeprecatedVersionOptions = (values: any[]) => { @@ -192,6 +194,20 @@ const BackendFields: React.FC = () => { ); }; + useEffect(() => { + if (action === PageAction.EDIT) { + const selected = flatBackendOptions?.find( + (item) => item.value === backend + ); + if (selected) { + form.setFieldValue('backend_selection', [ + selected.backend_source, + backend + ]); + } + } + }, [backend, flatBackendOptions, action]); + return ( <> @@ -244,6 +254,7 @@ const BackendFields: React.FC = () => { placeholder={intl.formatMessage({ id: 'models.form.backendVersion.holder' })} + description={} onChange={handleBackendVersionOnChange} label={intl.formatMessage({ id: 'models.form.backendVersion' })} footer={ diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 792ff91c..503fed72 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -90,7 +90,8 @@ const DataForm: React.FC = forwardRef((props, ref) => { onOk } = props; const { getScrollElementScrollableHeight } = useWrapperContext(); - const { backendOptions, getBackendOptions } = useQueryBackends(); + const { backendOptions, flatBackendOptions, getBackendOptions } = + useQueryBackends(); const { getGPUOptionList, gpuOptions, workerLabelOptions } = useGenerateGPUOptions(); const [form] = Form.useForm(); @@ -394,6 +395,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { action: action, gpuOptions: gpuOptions, backendOptions: backendOptions, + flatBackendOptions: flatBackendOptions, workerLabelOptions: workerLabelOptions, initialValues: initialValues, modelContextData: modelContextData, diff --git a/src/pages/llmodels/forms/kv-cache.tsx b/src/pages/llmodels/forms/kv-cache.tsx index ca3543bc..952cf16f 100644 --- a/src/pages/llmodels/forms/kv-cache.tsx +++ b/src/pages/llmodels/forms/kv-cache.tsx @@ -11,7 +11,7 @@ import { FormData } from '../config/types'; const KVCacheForm = () => { const intl = useIntl(); const form = Form.useFormInstance(); - const { onValuesChange, backendOptions, formKey } = useFormContext(); + const { onValuesChange, flatBackendOptions, formKey } = useFormContext(); const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form); const backend = Form.useWatch('backend', form); const configCacheRef = useRef({}); @@ -68,7 +68,7 @@ const KVCacheForm = () => { }; const builtInBackend = useMemo(() => { - const currentBackend = backendOptions.find( + const currentBackend = flatBackendOptions.find( (item) => item.value === backend ); @@ -78,7 +78,7 @@ const KVCacheForm = () => { backend as string ) ); - }, [backend, backendOptions]); + }, [backend, flatBackendOptions]); return ( <> diff --git a/src/pages/llmodels/forms/local-path-source.tsx b/src/pages/llmodels/forms/local-path-source.tsx index 69b2bd86..4fef4bc0 100644 --- a/src/pages/llmodels/forms/local-path-source.tsx +++ b/src/pages/llmodels/forms/local-path-source.tsx @@ -18,7 +18,7 @@ const LocalPathForm: React.FC = () => { const { formKey, gpuOptions, - backendOptions, + flatBackendOptions, onValuesChange, onBackendChange } = useFormContext(); @@ -68,7 +68,7 @@ const LocalPathForm: React.FC = () => { }); if (oldBackend !== backend) { - const option = backendOptions.find((item) => item.value === backend); + const option = flatBackendOptions.find((item) => item.value === backend); onBackendChange?.(backend, option); } else { onValuesChange?.({ local_path: value }, form.getFieldsValue()); diff --git a/src/pages/llmodels/forms/speculative-decode.tsx b/src/pages/llmodels/forms/speculative-decode.tsx index c5047f47..9ae0b6ee 100644 --- a/src/pages/llmodels/forms/speculative-decode.tsx +++ b/src/pages/llmodels/forms/speculative-decode.tsx @@ -21,7 +21,7 @@ const AlgorithmMap = { const SpeculativeDecode = () => { const intl = useIntl(); - const { source, backendOptions, onValuesChange } = useFormContext(); + const { source, flatBackendOptions, onValuesChange } = useFormContext(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); const backend = Form.useWatch('backend', form); @@ -76,7 +76,7 @@ const SpeculativeDecode = () => { }; const builtInBackend = useMemo(() => { - const currentBackend = backendOptions.find( + const currentBackend = flatBackendOptions.find( (item) => item.value === backend ); @@ -86,7 +86,7 @@ const SpeculativeDecode = () => { backend as string ) ); - }, [backend, backendOptions]); + }, [backend, flatBackendOptions]); return ( <> diff --git a/src/pages/llmodels/hooks/use-query-backends.ts b/src/pages/llmodels/hooks/use-query-backends.ts index dc37e8d4..4117ee1b 100644 --- a/src/pages/llmodels/hooks/use-query-backends.ts +++ b/src/pages/llmodels/hooks/use-query-backends.ts @@ -6,6 +6,7 @@ import { import { useIntl } from '@umijs/max'; import { useAtom } from 'jotai'; import _ from 'lodash'; +import { useState } from 'react'; import { queryBackendList } from '../apis'; import { backendOptionsMap } from '../config/backend-parameters'; import { BackendOption } from '../config/types'; @@ -43,6 +44,9 @@ const groupByBackendSource = (list: BackendOption[]): BackendGroup[] => { export default function useQueryBackends() { const [backendOptions, setBackendOptions] = useAtom(backendOptionsAtom); + const [flatBackendOptions, setFlatBackendOptions] = useState( + [] + ); const intl = useIntl(); const getBackendOptions = async (params?: { cluster_id: number }) => { @@ -57,6 +61,7 @@ export default function useQueryBackends() { 'is_built_in', 'default_backend_param' ]), + backend_source: item.backend_source || BackendSourceValueMap.CUSTOM, value: item.backend_name, label: item.backend_name === backendOptionsMap.custom @@ -78,22 +83,23 @@ export default function useQueryBackends() { }); const groupList = groupByBackendSource(list); + setFlatBackendOptions(list); + setBackendOptions(groupList); console.log('Fetched backend options:', list, groupList); - if (res?.items) { - setBackendOptions(groupList); - } return groupList || []; } catch (error) { // ignore setBackendOptions([]); + setFlatBackendOptions([]); return []; } }; return { backendOptions, + flatBackendOptions, getBackendOptions }; }