diff --git a/src/atoms/models.ts b/src/atoms/models.ts index 2de5b654..e01c2a11 100644 --- a/src/atoms/models.ts +++ b/src/atoms/models.ts @@ -47,7 +47,7 @@ export const workerListAtom = atom< }[] >([]); -interface BackGroupOption { +export interface BackGroupOption { label: string; value: string; title?: string; diff --git a/src/locales/en-US/backends.ts b/src/locales/en-US/backends.ts index f8da7b44..831141df 100644 --- a/src/locales/en-US/backends.ts +++ b/src/locales/en-US/backends.ts @@ -40,5 +40,6 @@ export default { '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.', 'backend.form.defaultEnvironment': 'Default Environment Variables', - 'backend.recommendModels': 'Recommended Models' + 'backend.recommendModels': 'Recommended Models', + 'backend.filter.source': 'Filter by source' }; diff --git a/src/locales/ja-JP/backends.ts b/src/locales/ja-JP/backends.ts index f8da7b44..831141df 100644 --- a/src/locales/ja-JP/backends.ts +++ b/src/locales/ja-JP/backends.ts @@ -40,5 +40,6 @@ export default { '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.', 'backend.form.defaultEnvironment': 'Default Environment Variables', - 'backend.recommendModels': 'Recommended Models' + 'backend.recommendModels': 'Recommended Models', + 'backend.filter.source': 'Filter by source' }; diff --git a/src/locales/ru-RU/backends.ts b/src/locales/ru-RU/backends.ts index fc46de3c..6bc67092 100644 --- a/src/locales/ru-RU/backends.ts +++ b/src/locales/ru-RU/backends.ts @@ -40,10 +40,12 @@ export default { 'backend.entrypoint.tips': 'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.', 'backend.form.defaultEnvironment': 'Default Environment Variables', - 'backend.recommendModels': 'Recommended Models' + 'backend.recommendModels': 'Recommended Models', + 'backend.filter.source': 'Filter by source' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'backend.form.defaultEnvironment': 'Default Environment Variables', -// 2. 'backend.recommendModels': 'Recommended Models' +// 2. 'backend.recommendModels': 'Recommended Models', +// 3. 'backend.filter.source': 'Filter by source' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/backends.ts b/src/locales/zh-CN/backends.ts index 0d3f6b41..2cee6e80 100644 --- a/src/locales/zh-CN/backends.ts +++ b/src/locales/zh-CN/backends.ts @@ -37,5 +37,6 @@ export default { 'backend.entrypoint.tips': '如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。', 'backend.form.defaultEnvironment': '默认环境变量', - 'backend.recommendModels': '推荐模型' + 'backend.recommendModels': '推荐模型', + 'backend.filter.source': '按来源过滤' }; diff --git a/src/pages/backends/components/backend-card.tsx b/src/pages/backends/components/backend-card.tsx index 43e343cd..ce950fde 100644 --- a/src/pages/backends/components/backend-card.tsx +++ b/src/pages/backends/components/backend-card.tsx @@ -308,6 +308,28 @@ const BackendCard: React.FC = ({ data, onSelect }) => { ); }; + const renderEnabledTag = () => { + if (data.backend_source !== BackendSourceValueMap.COMMUNITY) { + return null; + } + return ( + + {data.enabled + ? `${intl.formatMessage({ id: 'common.status.enabled' })}` + : `${intl.formatMessage({ id: 'common.status.disabled' })}`} + + ); + }; + return ( = ({ data, onSelect }) => { - Source: + {intl.formatMessage({ id: 'models.form.source' })}: {renderSource()} - {data.backend_source === BackendSourceValueMap.COMMUNITY && ( - <> - - {data.enabled - ? `${intl.formatMessage({ id: 'common.status.enabled' })}` - : `${intl.formatMessage({ id: 'common.status.disabled' })}`} - - - )} + {renderEnabledTag()} {renderRecommendModels()} diff --git a/src/pages/backends/components/version-info-modal.tsx b/src/pages/backends/components/version-info-modal.tsx index 3b658680..dcee21c7 100644 --- a/src/pages/backends/components/version-info-modal.tsx +++ b/src/pages/backends/components/version-info-modal.tsx @@ -69,7 +69,9 @@ const VersionInfoModal: React.FC = ({ open={open} title={
- {intl.formatMessage({ id: 'backend.versions' })} + + {intl.formatMessage({ id: 'backend.versions' })} + diff --git a/src/pages/backends/config/form-context.ts b/src/pages/backends/config/form-context.ts new file mode 100644 index 00000000..72de2ac0 --- /dev/null +++ b/src/pages/backends/config/form-context.ts @@ -0,0 +1,19 @@ +import { PageActionType } from '@/config/types'; +import { createContext, useContext } from 'react'; + +interface FormContextProps { + action: PageActionType; + backendSource: string; +} + +export const FormContext = createContext( + {} as FormContextProps +); + +export const useFormContext = () => { + const context = useContext(FormContext); + if (!context) { + throw new Error('useFormContext must be used within a FormContextProvider'); + } + return context; +}; diff --git a/src/pages/backends/config/index.ts b/src/pages/backends/config/index.ts index 510a0471..c9c3742c 100644 --- a/src/pages/backends/config/index.ts +++ b/src/pages/backends/config/index.ts @@ -49,6 +49,24 @@ export const TagColorMap: Record = { [BackendSourceValueMap.COMMUNITY]: 'cyan' }; +export const backendSourceOptions = [ + { + label: BackendSourceLabelMap[BackendSourceValueMap.BUILTIN], + value: BackendSourceValueMap.BUILTIN, + locale: true + }, + { + label: BackendSourceLabelMap[BackendSourceValueMap.COMMUNITY], + value: BackendSourceValueMap.COMMUNITY, + locale: true + }, + { + label: BackendSourceLabelMap[BackendSourceValueMap.CUSTOM], + value: BackendSourceValueMap.CUSTOM, + locale: true + } +]; + export const backendActions = [ { label: 'common.button.edit', diff --git a/src/pages/backends/forms/basic.tsx b/src/pages/backends/forms/basic.tsx index f212c7ff..abe8a34a 100644 --- a/src/pages/backends/forms/basic.tsx +++ b/src/pages/backends/forms/basic.tsx @@ -3,22 +3,19 @@ import ListInput from '@/components/list-input'; import SealInput from '@/components/seal-form/seal-input'; import SealTextArea from '@/components/seal-form/seal-textarea'; import { PageAction } from '@/config'; -import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { BackendSourceValueMap } from '../config'; -import { FormData, ListItem } from '../config/types'; +import { useFormContext } from '../config/form-context'; +import { FormData } from '../config/types'; -type AddModalProps = { - action: PageActionType; - currentData?: ListItem; -}; -const BasicForm: React.FC = ({ action, currentData }) => { +const BasicForm = () => { const form = Form.useFormInstance(); const intl = useIntl(); const { getRuleMessage } = useAppUtils(); + const { action, backendSource } = useFormContext(); const defaultEnvs = Form.useWatch('default_environment', form); const handleEnviromentVarsChange = (labels: Record) => { @@ -44,18 +41,18 @@ const BasicForm: React.FC = ({ action, currentData }) => { > - {action === PageAction.CREATE && ( - hidden name="backend_source"> - - - )} - {!currentData?.is_built_in && ( + hidden name="backend_source"> + + + {backendSource !== BackendSourceValueMap.BUILTIN && ( <> name="health_check_path" diff --git a/src/pages/backends/forms/index.tsx b/src/pages/backends/forms/index.tsx index 52e038b3..3decf76b 100644 --- a/src/pages/backends/forms/index.tsx +++ b/src/pages/backends/forms/index.tsx @@ -3,6 +3,7 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; +import { FormContext } from '../config/form-context'; import { FormData, ListItem } from '../config/types'; import BasicForm from './basic'; import VersionsForm from './versions-config'; @@ -18,6 +19,7 @@ const BackendForm: React.FC = forwardRef( const intl = useIntl(); const [form] = Form.useForm(); const [activeKey, setActiveKey] = React.useState([]); + const backendSource = Form.useWatch('backend_source', form); const onFinishFailed = (errorInfo: any) => { const errorFields = errorInfo.errorFields || []; @@ -79,22 +81,26 @@ const BackendForm: React.FC = forwardRef( })); return ( -
- - -
+
+ + +
+ ); } ); diff --git a/src/pages/backends/forms/versions-config.tsx b/src/pages/backends/forms/versions-config.tsx index 5adfabed..259ac996 100644 --- a/src/pages/backends/forms/versions-config.tsx +++ b/src/pages/backends/forms/versions-config.tsx @@ -401,7 +401,7 @@ const VersionsForm: React.FC = ({ label={intl.formatMessage({ id: 'models.form.env' })} - labels={versionConfigs.environment} + labels={versionConfigs?.[name]?.environment} btnText={intl.formatMessage({ id: 'common.button.vars' })} onChange={(envs) => handleEnviromentVarsChange(envs, name) diff --git a/src/pages/backends/hooks/use-create-backend.ts b/src/pages/backends/hooks/use-create-backend.ts new file mode 100644 index 00000000..e8c01c6c --- /dev/null +++ b/src/pages/backends/hooks/use-create-backend.ts @@ -0,0 +1,50 @@ +import { PageAction } from '@/config'; +import { PageActionType } from '@/config/types'; +import useBodyScroll from '@/hooks/use-body-scroll'; +import { useState } from 'react'; +import { ListItem } from '../config/types'; + +const useCreateBackend = () => { + const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); + const [openModalStatus, setOpenModalStatus] = useState<{ + open: boolean; + action: PageActionType; + currentData?: ListItem; + title: string; + }>({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '' + }); + + const openModal = (action: PageActionType, title: string, row?: ListItem) => { + setOpenModalStatus({ + ...openModalStatus, + open: true, + title: title, + action, + currentData: row + }); + saveScrollHeight(); + }; + + const closeModal = () => { + setOpenModalStatus({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '' + }); + restoreScrollHeight(); + }; + + return { + openBackendModalStatus: openModalStatus, + setOpenBackendModalStatus: setOpenModalStatus, + openBackendModal: openModal, + closeBackendModal: closeModal + }; +}; + +export default useCreateBackend; diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index 6764789e..60ed4f3d 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -2,7 +2,6 @@ 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'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; @@ -23,8 +22,9 @@ import { import AddModal from './components/add-modal'; import BackendCardList from './components/backend-list'; import VersionInfoModal from './components/version-info-modal'; -import { json2Yaml, yaml2Json } from './config'; +import { backendSourceOptions, json2Yaml, yaml2Json } from './config'; import { FormData, ListItem } from './config/types'; +import useCreateBackend from './hooks/use-create-backend'; import useExportYAML from './hooks/use-export-yaml'; import useEnableBackend from './services/use-enable-backend'; @@ -36,6 +36,7 @@ const BackendList = () => { rowSelection, queryParams, modalRef, + handleQueryChange, fetchData, handleDelete, handleSearch, @@ -52,41 +53,34 @@ const BackendList = () => { } }); const { exportYAML } = useExportYAML(); - const [openModalStatus, setOpenModalStatus] = useState<{ - open: boolean; - action: PageActionType; - currentData?: Partial; - }>({ open: false, action: 'create' }); const [openVersionInfoModal, setOpenVersionInfoModal] = useState<{ open: boolean; - currentData?: Partial; + currentData?: ListItem; }>({ open: false }); const { handleEnableBackend } = useEnableBackend(); + const { openBackendModal, closeBackendModal, openBackendModalStatus } = + useCreateBackend(); // built_in_version_configs is read-only, but needs to be included when updating const handleOnSubmit = async (values: FormData) => { try { - if (openModalStatus.action === 'create') { + if (openBackendModalStatus.action === 'create') { await createBackend({ data: values }); } else { - const omitFields = openModalStatus.currentData?.is_built_in + const omitFields = openBackendModalStatus.currentData?.is_built_in ? ['built_in_version_configs', 'default_version'] : ['built_in_version_configs']; - await updateBackend(openModalStatus.currentData!.id!, { + await updateBackend(openBackendModalStatus.currentData!.id!, { data: { built_in_version_configs: - openModalStatus.currentData?.built_in_version_configs, + openBackendModalStatus.currentData?.built_in_version_configs, ..._.omit(values, omitFields), health_check_path: values.health_check_path || null } }); } - setOpenModalStatus({ - open: false, - action: 'create', - currentData: undefined - }); + closeBackendModal(); handleSearch(); } catch (error) {} }; @@ -94,46 +88,42 @@ const BackendList = () => { // built_in_version_configs needs to be included when updating from YAML, but not allowed to be changed const handleOnSubmitYaml = async (values: { content: string }) => { try { - if (openModalStatus.action === 'create') { + if (openBackendModalStatus.action === 'create') { await createBackendFromYAML({ data: values }); } else { const jsonData = yaml2Json(values.content); const yamlContent = json2Yaml({ - backend_name: openModalStatus.currentData?.backend_name, - default_version: openModalStatus.currentData?.default_version, + backend_name: openBackendModalStatus.currentData?.backend_name, + default_version: openBackendModalStatus.currentData?.default_version, built_in_version_configs: - openModalStatus.currentData?.built_in_version_configs, + openBackendModalStatus.currentData?.built_in_version_configs, ...jsonData }); - await updateBackendFromYAML(openModalStatus.currentData!.id!, { + await updateBackendFromYAML(openBackendModalStatus.currentData!.id!, { data: { content: yamlContent } }); } - setOpenModalStatus({ - open: false, - action: 'create', - currentData: undefined - }); + closeBackendModal(); handleSearch(); } catch (error) {} }; - const handleAddBackend = () => { - setOpenModalStatus({ - open: true, - action: PageAction.CREATE + const handleFilterBySource = (value: string) => { + handleQueryChange({ + backend_source: value, + page: 1 }); }; - const handleOnSelect = (item: any) => { + const handleAddBackend = () => { + openBackendModal(PageAction.CREATE, ''); + }; + + const handleOnSelect = async (item: any) => { if (item.action === 'edit') { - setOpenModalStatus({ - open: true, - action: 'edit', - currentData: item.data - }); + openBackendModal(PageAction.EDIT, '', item.data); } else if (item.action === 'delete') { handleDelete(item.data, { name: item.data.backend_name @@ -162,6 +152,10 @@ const BackendList = () => { enabled: item.action === 'enable' } }); + // delay 200ms to refresh list + await new Promise((resolve) => { + setTimeout(resolve, 300); + }); handleSearch(); } }; @@ -171,11 +165,7 @@ const BackendList = () => { open: false, currentData: undefined }); - setOpenModalStatus({ - open: true, - action: 'edit', - currentData: openVersionInfoModal.currentData - }); + openBackendModal(PageAction.EDIT, '', openVersionInfoModal.currentData); }; const loadMore = useMemoizedFn((nextPage: number) => { @@ -197,12 +187,18 @@ const BackendList = () => { input: 300 }} inputHolder={intl.formatMessage({ id: 'common.filter.name' })} + selectHolder={intl.formatMessage({ id: 'backend.filter.source' })} buttonText={intl.formatMessage({ id: 'backend.button.add' })} handleClickPrimary={handleAddBackend} handleSearch={handleSearch} + handleSelectChange={handleFilterBySource} handleInputChange={handleNameChange} rowSelection={rowSelection} - showSelect={false} + showSelect={true} + selectOptions={backendSourceOptions.map((item) => ({ + label: intl.formatMessage({ id: item.label }), + value: item.value + }))} > { > setOpenModalStatus({ open: false, action: 'create' })} + action={openBackendModalStatus.action} + onClose={closeBackendModal} onSubmit={handleOnSubmit} onSubmitYaml={handleOnSubmitYaml} - currentData={openModalStatus.currentData as ListItem} - open={openModalStatus.open} + currentData={openBackendModalStatus.currentData as ListItem} + open={openBackendModalStatus.open} title={ - openModalStatus.action === 'create' + openBackendModalStatus.action === PageAction.CREATE ? intl.formatMessage({ id: 'backend.button.add' }) : intl.formatMessage({ id: 'backend.button.edit' }) } diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index 022cbc67..ea16c290 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -316,6 +316,7 @@ export interface BackendGroupItem { label: string; value: string; title?: string; + environment?: Record; is_deprecated: boolean; }[]; } @@ -333,6 +334,7 @@ export interface BackendOption { label: string; value: string; title?: string; + environment?: Record; is_deprecated: boolean; }[]; } @@ -352,6 +354,7 @@ export interface BackendItem { enabled: boolean; versions: { version: string; + environment?: Record; is_deprecated: boolean; }[]; } diff --git a/src/pages/llmodels/forms/backend.tsx b/src/pages/llmodels/forms/backend.tsx index 81f7e9e6..c79fa237 100644 --- a/src/pages/llmodels/forms/backend.tsx +++ b/src/pages/llmodels/forms/backend.tsx @@ -2,7 +2,6 @@ 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'; @@ -44,7 +43,15 @@ const BackendFields: React.FC = () => { const [selectedBackend, setSelectedBackend] = React.useState(null); - const handleBackendVersionOnChange = (value: any) => { + const handleBackendVersionOnChange = (value: any, option: any) => { + if (Object.keys(option.data.environment || {}).length > 0) { + form.setFieldsValue({ + env: { + ...(option?.data?.environment || {}) + } + }); + } + onValuesChange?.({}, form.getFieldsValue()); }; @@ -111,7 +118,6 @@ const BackendFields: React.FC = () => { }, [backend, selectedBackend, intl]); const backendVersionLabelRender = (option: any) => { - console.log('backendVersionLabelRender option:', option); return option.title; }; @@ -122,7 +128,12 @@ const BackendFields: React.FC = () => { return ( {values.map((item) => ( - + {item.label} ))} @@ -131,9 +142,19 @@ const BackendFields: React.FC = () => { }; const handleOnBackendChange = (value: any[], option: any) => { - form.setFieldValue('backend', value?.[1]); - onBackendChange?.(value?.[1], option?.[1] || {}); - setSelectedBackend(option?.[1] || {}); + const selectedBackend = value?.[1]; + const selectedOption = option?.[1] || {}; + + console.log('handleOnBackendChange selectedOption:', selectedOption); + + form.setFieldsValue({ + backend: selectedBackend, + env: { + ...(selectedOption.environment || {}) + } + }); + onBackendChange?.(selectedBackend, selectedOption); + setSelectedBackend(selectedOption); }; const displayRender = (labels: any[], selectedOptions?: any[]) => { @@ -195,7 +216,7 @@ const BackendFields: React.FC = () => { }; useEffect(() => { - if (action === PageAction.EDIT) { + if (backend) { const selected = flatBackendOptions?.find( (item) => item.value === backend ); @@ -206,7 +227,7 @@ const BackendFields: React.FC = () => { ]); } } - }, [backend, flatBackendOptions, action]); + }, [backend, flatBackendOptions]); return ( <> diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 503fed72..b532e499 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -212,7 +212,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { setTimeout(resolve, 100); }); form.setFieldsValue({ - env: null, backend_version: null, // don't set default version here, let the user select it backend_parameters: option.default_backend_param || [], ...updateKVCacheConfig(val, option), diff --git a/src/pages/llmodels/hooks/use-query-backends.ts b/src/pages/llmodels/hooks/use-query-backends.ts index 4117ee1b..86ce4232 100644 --- a/src/pages/llmodels/hooks/use-query-backends.ts +++ b/src/pages/llmodels/hooks/use-query-backends.ts @@ -15,6 +15,7 @@ interface BackendGroup { label: string; value: string; title?: string; + isLeaf?: boolean; children: BackendOption[]; } @@ -59,7 +60,8 @@ export default function useQueryBackends() { 'default_version', 'backend_source', 'is_built_in', - 'default_backend_param' + 'default_backend_param', + 'environment' ]), backend_source: item.backend_source || BackendSourceValueMap.CUSTOM, value: item.backend_name, @@ -77,6 +79,7 @@ export default function useQueryBackends() { label: vItem.version, value: vItem.version, is_deprecated: vItem.is_deprecated, + environment: vItem.environment || {}, title: vItem.version.replace(/-custom$/, '') })) };