fix: backend selection

This commit is contained in:
jialin
2026-01-30 20:48:55 +08:00
committed by Lawrence Li
parent a2be7f6b36
commit 4000aacb2c
27 changed files with 317 additions and 142 deletions
+3 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+3 -1
View File
@@ -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'
};
+3 -1
View File
@@ -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. '
};
+3 -1
View File
@@ -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'
};
+5 -2
View File
@@ -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 ==========
+5 -2
View File
@@ -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 ==========
+5 -2
View File
@@ -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 ==========
+3 -1
View File
@@ -35,5 +35,7 @@ export default {
'backend.replaceEntrypoint': '覆盖镜像入口命令',
'backend.entrypoint': '镜像入口命令',
'backend.entrypoint.tips':
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。'
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。',
'backend.form.defaultEnvironment': '默认环境变量',
'backend.recommendModels': '推荐模型'
};
+2 -1
View File
@@ -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': '该社区后端暂未启用,部署后将自动启用'
};
+7 -3
View File
@@ -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<AddModalProps> = (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<AddModalProps> = (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<AddModalProps> = (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',
+55 -38
View File
@@ -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<string, string> = {
[BackendSourceValueMap.CUSTOM]: 'purple',
[BackendSourceValueMap.BUILTIN]: 'geekblue',
[BackendSourceValueMap.COMMUNITY]: 'cyan'
};
const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
const intl = useIntl();
@@ -254,22 +249,65 @@ const BackendCard: React.FC<BackendCardProps> = ({ 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 (
<div className="flex-center">
<span className="dot"></span>
<TagWrapper
gap={8}
dataList={recommnadedModels}
renderTag={renderModel}
></TagWrapper>
<Tooltip
title={
<Flex gap={4} wrap="wrap">
{recommendedModels.map((item) => (
<Tag style={{ margin: 0 }} key={item}>
{item}
</Tag>
))}
</Flex>
}
>
<span>
<ThemeTag color="default">
{intl.formatMessage({ id: 'backend.recommendModels' })}
</ThemeTag>
</span>
</Tooltip>
</div>
);
};
const renderSource = () => {
const source = data.is_built_in
? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || ''
: BackendSourceLabelMap[data.backend_source] || '';
if (!source) {
return null;
}
return (
<ThemeTag
color={
TagColorMap[
data.is_built_in
? BackendSourceValueMap.BUILTIN
: data.backend_source
]
}
className="font-400"
variant="outlined"
style={{
borderRadius: 'var(--ant-border-radius)',
margin: 0,
width: 'max-content'
}}
>
{intl.formatMessage({
id: source
})}
</ThemeTag>
);
};
return (
<StyledCard
onClick={handleClick}
@@ -308,28 +346,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
<IconFont type="icon-source" className="icon" />
<span>Source:</span>
</span>
<ThemeTag
color={
TagColorMap[
data.is_built_in
? BackendSourceValueMap.BUILTIN
: data.backend_source
]
}
className="font-400"
variant="outlined"
style={{
borderRadius: 'var(--ant-border-radius)',
margin: 0,
width: 'max-content'
}}
>
{intl.formatMessage({
id: data.is_built_in
? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN]
: BackendSourceLabelMap[data.backend_source]
})}
</ThemeTag>
{renderSource()}
{data.backend_source === BackendSourceValueMap.COMMUNITY && (
<>
<ThemeTag
@@ -349,7 +366,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
</>
)}
</InfoItem>
{/* {renderRecommendModels()} */}
{renderRecommendModels()}
</SourceWrapper>
{renderFrameworks()}
</Content>
@@ -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<VersionInfoModalProps> = ({
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<VersionInfoModalProps> = ({
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 || []),
+8 -2
View File
@@ -43,6 +43,12 @@ export const BackendSourceLabelMap: Record<string, string> = {
[BackendSourceValueMap.USER_DEFINED]: 'models.form.backend.custom'
};
export const TagColorMap: Record<string, string> = {
[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
+4
View File
@@ -7,6 +7,8 @@ export interface VersionConfigs {
entrypoint?: string;
version_no?: string;
is_built_in?: boolean;
backend_source?: string;
environment: Record<string, any>;
}
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<string, any>;
}
export interface ListItem extends FormData {
+29 -1
View File
@@ -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<AddModalProps> = ({ action, currentData }) => {
const form = Form.useFormInstance();
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const defaultEnvs = Form.useWatch('default_environment', form);
const handleEnviromentVarsChange = (labels: Record<string, any>) => {
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<AddModalProps> = ({ action, currentData }) => {
required
></SealInput.Input>
</Form.Item>
{action === PageAction.CREATE && (
<Form.Item<FormData> hidden name="backend_source">
<SealInput.Input></SealInput.Input>
</Form.Item>
)}
{!currentData?.is_built_in && (
<>
<Form.Item<FormData>
@@ -86,6 +104,16 @@ const BasicForm: React.FC<AddModalProps> = ({ action, currentData }) => {
})}
></ListInput>
</Form.Item>
<Form.Item<FormData> name="default_environment">
<LabelSelector
label={intl.formatMessage({
id: 'backend.form.defaultEnvironment'
})}
labels={defaultEnvs}
btnText={intl.formatMessage({ id: 'common.button.vars' })}
onChange={handleEnviromentVarsChange}
></LabelSelector>
</Form.Item>
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
<SealInput.TextArea
+40 -10
View File
@@ -5,7 +5,13 @@ import { useIntl } from '@umijs/max';
import { Empty } from 'antd';
import { useState } from 'react';
import styled from 'styled-components';
import { frameworks, getGpuColor } from '../config';
import {
BackendSourceLabelMap,
BackendSourceValueMap,
frameworks,
getGpuColor,
TagColorMap
} from '../config';
import { VersionListItem } from '../config/types';
const ItemWrapper = styled.div`
@@ -76,19 +82,43 @@ interface VersionItemProps {
export const VersionItem: React.FC<VersionItemProps> = ({ 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 (
<ThemeTag
color={
TagColorMap[
data.is_built_in
? BackendSourceValueMap.BUILTIN
: data.backend_source || ''
]
}
className="font-400"
variant="outlined"
style={{
borderRadius: 'var(--ant-border-radius)',
margin: 0,
width: 'max-content'
}}
>
{intl.formatMessage({
id: source
})}
</ThemeTag>
);
};
return (
<ItemWrapper>
<div className="title">
<span>{data.version_no}</span>
{data.is_built_in && (
<ThemeTag
color="geekblue"
className="font-400"
style={{ marginRight: 0 }}
>
{intl.formatMessage({ id: 'backend.builtin' })}
</ThemeTag>
)}
{renderSource()}
{!data.is_built_in && data.is_default && (
<ThemeTag
color="geekblue"
+38 -6
View File
@@ -1,4 +1,5 @@
import CollapsibleContainer from '@/components/collapse-container';
import LabelSelector from '@/components/label-selector';
import BaseSelect from '@/components/seal-form/base/select';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
@@ -75,7 +76,8 @@ const VersionsForm: React.FC<AddModalProps> = ({
run_command: '',
entrypoint: '',
isBuiltin: false,
is_default: false
is_default: false,
environment: {}
}
];
@@ -102,7 +104,8 @@ const VersionsForm: React.FC<AddModalProps> = ({
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<AddModalProps> = ({
setDefaultVersion(value);
};
const handleEnviromentVarsChange = (
envs: Record<string, any>,
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<AddModalProps> = ({
{ add, remove }
) => {
const versionConfigs = form.getFieldValue('version_configs');
console.log('versionConfigs', versionConfigs);
return fields?.map(({ key, name }) => (
<div
key={name}
@@ -356,10 +379,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
})}
></SealInput.TextArea>
</Form.Item>
<Form.Item
name={[name, 'run_command']}
style={{ marginBottom: 0 }}
>
<Form.Item name={[name, 'run_command']}>
<SealTextArea
allowClear
alwaysFocus={true}
@@ -376,6 +396,18 @@ const VersionsForm: React.FC<AddModalProps> = ({
label={intl.formatMessage({ id: 'backend.runCommand' })}
></SealTextArea>
</Form.Item>
<Form.Item name={[name, 'environment']}>
<LabelSelector
label={intl.formatMessage({
id: 'models.form.env'
})}
labels={versionConfigs.environment}
btnText={intl.formatMessage({ id: 'common.button.vars' })}
onChange={(envs) =>
handleEnviromentVarsChange(envs, name)
}
></LabelSelector>
</Form.Item>
</CollapsibleContainer>
</div>
));
+3 -1
View File
@@ -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();
}
};
+7 -1
View File
@@ -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<string, any>;
clearCacheFormValues?: () => void;
+3 -3
View File
@@ -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<string, any>) => {
form.setFieldValue('env', labels);
+58 -47
View File
@@ -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<boolean>(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 (
<span style={{ color: 'var(--ant-color-error)' }}>
{intl.formatMessage({ id: 'models.form.backend.helperText' })}
</span>
);
}
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 <span>{labels?.[0]}</span>;
}
return (
<span className="flex-center">
{intl.formatMessage({ id: groupTitle })} / {labels?.[1]}
</span>
);
};
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 (
<>
<Form.Item name="backend" hidden>
@@ -205,19 +221,12 @@ const BackendFields: React.FC = () => {
message: getRuleMessage('select', 'models.form.backend')
}
]}
help={backendHelperText}
>
{/* <SealSelect
required
onChange={onBackendChange}
label={intl.formatMessage({ id: 'models.form.backend' })}
description={<TooltipList list={backendTipsList}></TooltipList>}
options={backendGroupedOptions}
optionRender={optionRender}
labelRender={labelRender}
></SealSelect> */}
<SealCascader
required
showSearch
allowClear={false}
changeOnSelect={false}
expandTrigger="hover"
multiple={false}
@@ -231,6 +240,7 @@ const BackendFields: React.FC = () => {
options={backendOptions}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
optionNode={BackendNode}
displayRender={displayRender}
onChange={handleOnBackendChange}
></SealCascader>
</Form.Item>
@@ -244,6 +254,7 @@ const BackendFields: React.FC = () => {
placeholder={intl.formatMessage({
id: 'models.form.backendVersion.holder'
})}
description={<TooltipList list={backendTipsList}></TooltipList>}
onChange={handleBackendVersionOnChange}
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
footer={
+3 -1
View File
@@ -90,7 +90,8 @@ const DataForm: React.FC<DataFormProps> = 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<DataFormProps> = forwardRef((props, ref) => {
action: action,
gpuOptions: gpuOptions,
backendOptions: backendOptions,
flatBackendOptions: flatBackendOptions,
workerLabelOptions: workerLabelOptions,
initialValues: initialValues,
modelContextData: modelContextData,
+3 -3
View File
@@ -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<any>({});
@@ -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 (
<>
@@ -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());
@@ -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 (
<>
@@ -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<BackendOption[]>(
[]
);
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
};
}