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' }), LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }),
Permission: React.createElement(IconFont, { type: 'icon-permission' }), Permission: React.createElement(IconFont, { type: 'icon-permission' }),
CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }), 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; export default icons;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js'; // import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({ 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; export default IconFont;
+3 -1
View File
@@ -38,5 +38,7 @@ export default {
'backend.replaceEntrypoint': 'Override Image Entrypoint', 'backend.replaceEntrypoint': 'Override Image Entrypoint',
'backend.entrypoint': 'Image Entrypoint', 'backend.entrypoint': 'Image Entrypoint',
'backend.entrypoint.tips': '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.', '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.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.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.replaceEntrypoint': 'Override Image Entrypoint',
'backend.entrypoint': 'Image Entrypoint', 'backend.entrypoint': 'Image Entrypoint',
'backend.entrypoint.tips': '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.', '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.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.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) ========== // ========== 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.', // 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.`, // 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', // 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 ========== // ========== End of To-Do List ==========
+5 -2
View File
@@ -38,9 +38,12 @@ export default {
'backend.replaceEntrypoint': 'Переопределить точку входа образа', 'backend.replaceEntrypoint': 'Переопределить точку входа образа',
'backend.entrypoint': 'Точка входа образа', 'backend.entrypoint': 'Точка входа образа',
'backend.entrypoint.tips': 'backend.entrypoint.tips':
'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.' 'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.',
'backend.form.defaultEnvironment': 'Default Environment Variables',
'backend.recommendModels': 'Recommended Models'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'backend.form.defaultEnvironment': 'Default Environment Variables',
// 2. 'backend.recommendModels': 'Recommended Models'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -2
View File
@@ -277,9 +277,12 @@ export default {
'В выбранном кластере нет доступных GPU, совместимых с этой моделью.', 'В выбранном кластере нет доступных GPU, совместимых с этой моделью.',
'models.form.modelfile.notfound': `Указанный путь к файлу модели не существует на сервере GPUStack. Рекомендуется размещать файл модели по одному и тому же пути как на сервере GPUStack, так и на воркерах GPUStack. Это поможет системе принимать лучшие решения по распределению ресурсов.`, 'models.form.modelfile.notfound': `Указанный путь к файлу модели не существует на сервере GPUStack. Рекомендуется размещать файл модели по одному и тому же пути как на сервере GPUStack, так и на воркерах GPUStack. Это поможет системе принимать лучшие решения по распределению ресурсов.`,
'models.form.readyWorkers': 'воркеров готово', '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) ========== // ========== 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 ========== // ========== End of To-Do List ==========
+3 -1
View File
@@ -35,5 +35,7 @@ export default {
'backend.replaceEntrypoint': '覆盖镜像入口命令', 'backend.replaceEntrypoint': '覆盖镜像入口命令',
'backend.entrypoint': '镜像入口命令', 'backend.entrypoint': '镜像入口命令',
'backend.entrypoint.tips': 'backend.entrypoint.tips':
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。' '如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。',
'backend.form.defaultEnvironment': '默认环境变量',
'backend.recommendModels': '推荐模型'
}; };
+2 -1
View File
@@ -257,5 +257,6 @@ export default {
'models.form.modelfile.notfound': 'models.form.modelfile.notfound':
'你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', '你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。',
'models.form.readyWorkers': '节点就绪', '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 styled from 'styled-components';
import ColumnWrapper from '../../_components/column-wrapper'; import ColumnWrapper from '../../_components/column-wrapper';
import { import {
BackendSourceValueMap,
builtInBackendFields, builtInBackendFields,
customBackendFields, customBackendFields,
json2Yaml json2Yaml
@@ -55,7 +56,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
'image_name', 'image_name',
'run_command', 'run_command',
'custom_framework', 'custom_framework',
'entrypoint' 'entrypoint',
'environment'
]; ];
// remove '-custom' suffix from version_no in currentData, when action is EDIT // 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( data.version_configs = Object.entries(data.version_configs || {}).reduce(
(acc, [key, value]) => { (acc, [key, value]) => {
const version = key.replace(/-custom$/, ''); const version = key.replace(/-custom$/, '');
acc[version] = { ...value }; acc[version] = { ...value, backend_source: data.backend_source };
return acc; return acc;
}, },
{} as any {} as any
@@ -128,7 +130,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
is_default: key === values.default_version, is_default: key === values.default_version,
built_in_frameworks: built_in_frameworks:
values.built_in_version_configs?.[key]?.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], [ ..._.pick(values.built_in_version_configs?.[key], [
'image_name', 'image_name',
'run_command', '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 ThemeTag from '@/components/tags-wrapper/theme-tag';
import Card from '@/components/templates/card'; import Card from '@/components/templates/card';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Tag } from 'antd'; import { Button, Flex, Tag, Tooltip } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
import semverCoerce from 'semver/functions/coerce'; import semverCoerce from 'semver/functions/coerce';
@@ -18,7 +18,8 @@ import {
builtInBackendLogos, builtInBackendLogos,
customColors, customColors,
customIcons, customIcons,
getGpuColor getGpuColor,
TagColorMap
} from '../config'; } from '../config';
import { ListItem } from '../config/types'; import { ListItem } from '../config/types';
@@ -142,12 +143,6 @@ interface BackendCardProps {
data: ListItem; data: ListItem;
} }
const TagColorMap: Record<string, string> = {
[BackendSourceValueMap.CUSTOM]: 'purple',
[BackendSourceValueMap.BUILTIN]: 'geekblue',
[BackendSourceValueMap.COMMUNITY]: 'cyan'
};
const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => { const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
const intl = useIntl(); const intl = useIntl();
@@ -254,22 +249,65 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
}; };
const renderRecommendModels = () => { const renderRecommendModels = () => {
const recommnadedModels = data.recommend_models || []; const recommendedModels = data.recommend_models || [];
if (recommnadedModels.length === 0) { if (recommendedModels.length === 0) {
return null; return null;
} }
return ( return (
<div className="flex-center"> <div className="flex-center">
<span className="dot"></span> <span className="dot"></span>
<TagWrapper <Tooltip
gap={8} title={
dataList={recommnadedModels} <Flex gap={4} wrap="wrap">
renderTag={renderModel} {recommendedModels.map((item) => (
></TagWrapper> <Tag style={{ margin: 0 }} key={item}>
{item}
</Tag>
))}
</Flex>
}
>
<span>
<ThemeTag color="default">
{intl.formatMessage({ id: 'backend.recommendModels' })}
</ThemeTag>
</span>
</Tooltip>
</div> </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 ( return (
<StyledCard <StyledCard
onClick={handleClick} onClick={handleClick}
@@ -308,28 +346,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
<IconFont type="icon-source" className="icon" /> <IconFont type="icon-source" className="icon" />
<span>Source:</span> <span>Source:</span>
</span> </span>
<ThemeTag {renderSource()}
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>
{data.backend_source === BackendSourceValueMap.COMMUNITY && ( {data.backend_source === BackendSourceValueMap.COMMUNITY && (
<> <>
<ThemeTag <ThemeTag
@@ -349,7 +366,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
</> </>
)} )}
</InfoItem> </InfoItem>
{/* {renderRecommendModels()} */} {renderRecommendModels()}
</SourceWrapper> </SourceWrapper>
{renderFrameworks()} {renderFrameworks()}
</Content> </Content>
@@ -2,7 +2,9 @@ import ScrollerModal from '@/components/scroller-modal';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button } from 'antd'; import { Button } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { BackendSourceValueMap } from '../config';
import { VersionListItem } from '../config/types'; import { VersionListItem } from '../config/types';
import VersionInfo from '../forms/version-info'; import VersionInfo from '../forms/version-info';
@@ -26,10 +28,11 @@ const VersionInfoModal: React.FC<VersionInfoModalProps> = ({
if (open && currentData) { if (open && currentData) {
// add is_built_in field to built_in_version_configs // add is_built_in field to built_in_version_configs
const builtInVersions = currentData.built_in_version_configs || {}; const builtInVersions = currentData.built_in_version_configs || {};
for (const key in builtInVersions) { for (const key in builtInVersions) {
if (builtInVersions?.hasOwnProperty(key)) { 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( const versionList: VersionListItem[] = Object.entries(versions).map(
([key, value]: [string, any]) => ({ ([key, value]: [string, any]) => ({
version_no: key, version_no: key,
image_name: value.image_name, ..._.pick(value, [
run_command: value.run_command, 'image_name',
entrypoint: value.entrypoint, 'run_command',
'entrypoint',
'environment',
'backend_source'
]),
is_default: key === currentData.default_version, is_default: key === currentData.default_version,
availableFrameworks: [ availableFrameworks: [
...(value.built_in_frameworks || []), ...(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' [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 = [ export const backendActions = [
{ {
label: 'common.button.edit', label: 'common.button.edit',
@@ -63,7 +69,7 @@ export const backendActions = [
value: 'enable', value: 'enable',
key: 'enable', key: 'enable',
locale: true, locale: true,
icon: icons.Yaml, icon: icons.Charger,
show: (record: any) => show: (record: any) =>
!record.enabled && !record.enabled &&
record.backend_source === BackendSourceValueMap.COMMUNITY record.backend_source === BackendSourceValueMap.COMMUNITY
@@ -73,7 +79,7 @@ export const backendActions = [
value: 'disable', value: 'disable',
key: 'disable', key: 'disable',
locale: true, locale: true,
icon: icons.Yaml, icon: icons.Disabled,
show: (record: any) => show: (record: any) =>
record.enabled && record.enabled &&
record.backend_source === BackendSourceValueMap.COMMUNITY record.backend_source === BackendSourceValueMap.COMMUNITY
+4
View File
@@ -7,6 +7,8 @@ export interface VersionConfigs {
entrypoint?: string; entrypoint?: string;
version_no?: string; version_no?: string;
is_built_in?: boolean; is_built_in?: boolean;
backend_source?: string;
environment: Record<string, any>;
} }
export interface VersionListItem extends VersionConfigs { export interface VersionListItem extends VersionConfigs {
@@ -24,6 +26,8 @@ export interface FormData {
allowed_proxy_uris?: string[]; allowed_proxy_uris?: string[];
content?: string; content?: string;
enabled?: boolean; enabled?: boolean;
backend_source?: string;
default_environment?: Record<string, any>;
} }
export interface ListItem extends FormData { 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 ListInput from '@/components/list-input';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealTextArea from '@/components/seal-form/seal-textarea'; 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 useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React from 'react'; import React, { useEffect } from 'react';
import { BackendSourceValueMap } from '../config';
import { FormData, ListItem } from '../config/types'; import { FormData, ListItem } from '../config/types';
type AddModalProps = { type AddModalProps = {
@@ -17,6 +19,17 @@ const BasicForm: React.FC<AddModalProps> = ({ action, currentData }) => {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const intl = useIntl(); const intl = useIntl();
const { getRuleMessage } = useAppUtils(); 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 ( return (
<> <>
@@ -37,6 +50,11 @@ const BasicForm: React.FC<AddModalProps> = ({ action, currentData }) => {
required required
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
{action === PageAction.CREATE && (
<Form.Item<FormData> hidden name="backend_source">
<SealInput.Input></SealInput.Input>
</Form.Item>
)}
{!currentData?.is_built_in && ( {!currentData?.is_built_in && (
<> <>
<Form.Item<FormData> <Form.Item<FormData>
@@ -86,6 +104,16 @@ const BasicForm: React.FC<AddModalProps> = ({ action, currentData }) => {
})} })}
></ListInput> ></ListInput>
</Form.Item> </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 }]}> <Form.Item<FormData> name="description" rules={[{ required: false }]}>
<SealInput.TextArea <SealInput.TextArea
+40 -10
View File
@@ -5,7 +5,13 @@ import { useIntl } from '@umijs/max';
import { Empty } from 'antd'; import { Empty } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { frameworks, getGpuColor } from '../config'; import {
BackendSourceLabelMap,
BackendSourceValueMap,
frameworks,
getGpuColor,
TagColorMap
} from '../config';
import { VersionListItem } from '../config/types'; import { VersionListItem } from '../config/types';
const ItemWrapper = styled.div` const ItemWrapper = styled.div`
@@ -76,19 +82,43 @@ interface VersionItemProps {
export const VersionItem: React.FC<VersionItemProps> = ({ data }) => { export const VersionItem: React.FC<VersionItemProps> = ({ data }) => {
const intl = useIntl(); 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 ( return (
<ItemWrapper> <ItemWrapper>
<div className="title"> <div className="title">
<span>{data.version_no}</span> <span>{data.version_no}</span>
{data.is_built_in && ( {renderSource()}
<ThemeTag
color="geekblue"
className="font-400"
style={{ marginRight: 0 }}
>
{intl.formatMessage({ id: 'backend.builtin' })}
</ThemeTag>
)}
{!data.is_built_in && data.is_default && ( {!data.is_built_in && data.is_default && (
<ThemeTag <ThemeTag
color="geekblue" color="geekblue"
+38 -6
View File
@@ -1,4 +1,5 @@
import CollapsibleContainer from '@/components/collapse-container'; import CollapsibleContainer from '@/components/collapse-container';
import LabelSelector from '@/components/label-selector';
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
@@ -75,7 +76,8 @@ const VersionsForm: React.FC<AddModalProps> = ({
run_command: '', run_command: '',
entrypoint: '', entrypoint: '',
isBuiltin: false, isBuiltin: false,
is_default: false is_default: false,
environment: {}
} }
]; ];
@@ -102,7 +104,8 @@ const VersionsForm: React.FC<AddModalProps> = ({
run_command: '', run_command: '',
entrypoint: '', entrypoint: '',
isBuiltin: false, isBuiltin: false,
is_default: false is_default: false,
environment: {}
}; };
form.setFieldValue('version_configs', [...versions, newVersion]); form.setFieldValue('version_configs', [...versions, newVersion]);
}; };
@@ -136,6 +139,25 @@ const VersionsForm: React.FC<AddModalProps> = ({
setDefaultVersion(value); 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(() => { useEffect(() => {
const versions = form.getFieldValue('version_configs') || []; const versions = form.getFieldValue('version_configs') || [];
@@ -227,6 +249,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
{ add, remove } { add, remove }
) => { ) => {
const versionConfigs = form.getFieldValue('version_configs'); const versionConfigs = form.getFieldValue('version_configs');
console.log('versionConfigs', versionConfigs);
return fields?.map(({ key, name }) => ( return fields?.map(({ key, name }) => (
<div <div
key={name} key={name}
@@ -356,10 +379,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
})} })}
></SealInput.TextArea> ></SealInput.TextArea>
</Form.Item> </Form.Item>
<Form.Item <Form.Item name={[name, 'run_command']}>
name={[name, 'run_command']}
style={{ marginBottom: 0 }}
>
<SealTextArea <SealTextArea
allowClear allowClear
alwaysFocus={true} alwaysFocus={true}
@@ -376,6 +396,18 @@ const VersionsForm: React.FC<AddModalProps> = ({
label={intl.formatMessage({ id: 'backend.runCommand' })} label={intl.formatMessage({ id: 'backend.runCommand' })}
></SealTextArea> ></SealTextArea>
</Form.Item> </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> </CollapsibleContainer>
</div> </div>
)); ));
+3 -1
View File
@@ -1,6 +1,7 @@
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools'; import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -122,7 +123,7 @@ const BackendList = () => {
const handleAddBackend = () => { const handleAddBackend = () => {
setOpenModalStatus({ setOpenModalStatus({
open: true, open: true,
action: 'create' action: PageAction.CREATE
}); });
}; };
@@ -161,6 +162,7 @@ const BackendList = () => {
enabled: item.action === 'enable' enabled: item.action === 'enable'
} }
}); });
handleSearch();
} }
}; };
+7 -1
View File
@@ -20,7 +20,13 @@ interface FormContextProps {
action: PageActionType; action: PageActionType;
gpuOptions: CascaderOption[]; gpuOptions: CascaderOption[];
workerLabelOptions: CascaderOption[]; workerLabelOptions: CascaderOption[];
backendOptions: BackendOption[]; backendOptions: {
label: string;
value: string;
title?: string;
children: BackendOption[];
}[];
flatBackendOptions: BackendOption[];
initialValues?: FormData; // for editing model initialValues?: FormData; // for editing model
modelContextData?: Record<string, any>; modelContextData?: Record<string, any>;
clearCacheFormValues?: () => void; clearCacheFormValues?: () => void;
+3 -3
View File
@@ -16,12 +16,12 @@ const AdvanceConfig = () => {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const EnviromentVars = Form.useWatch('env', form); const EnviromentVars = Form.useWatch('env', form);
const backend = Form.useWatch('backend', form); const backend = Form.useWatch('backend', form);
const { onValuesChange, backendOptions, isGGUF, modelContextData } = const { onValuesChange, isGGUF, modelContextData, flatBackendOptions } =
useFormContext(); useFormContext();
const currentBackendOptions = useMemo(() => { const currentBackendOptions = useMemo(() => {
return backendOptions?.find((item) => item.value === backend); return flatBackendOptions?.find((item) => item.value === backend);
}, [backend, backendOptions]); }, [backend, flatBackendOptions]);
const handleEnviromentVarsChange = (labels: Record<string, any>) => { const handleEnviromentVarsChange = (labels: Record<string, any>) => {
form.setFieldValue('env', labels); form.setFieldValue('env', labels);
+58 -47
View File
@@ -1,13 +1,16 @@
import SealCascader from '@/components/seal-form/seal-cascader'; import SealCascader from '@/components/seal-form/seal-cascader';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; 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 useAppUtils from '@/hooks/use-app-utils';
import { BackendSourceValueMap } from '@/pages/backends/config'; import { BackendSourceValueMap } from '@/pages/backends/config';
import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { Form, Select } from 'antd'; import { Form, Select } from 'antd';
import React, { useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { backendTipsList } from '../config';
import { backendOptionsMap } from '../config/backend-parameters'; import { backendOptionsMap } from '../config/backend-parameters';
import { useFormContext } from '../config/form-context'; import { useFormContext } from '../config/form-context';
import { BackendOption } from '../config/types'; import { BackendOption } from '../config/types';
@@ -29,7 +32,13 @@ const BackendFields: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { onValuesChange, backendOptions, onBackendChange } = useFormContext(); const {
action,
onValuesChange,
backendOptions,
flatBackendOptions,
onBackendChange
} = useFormContext();
const backend = Form.useWatch('backend', form); const backend = Form.useWatch('backend', form);
const [showDeprecated, setShowDeprecated] = React.useState<boolean>(false); const [showDeprecated, setShowDeprecated] = React.useState<boolean>(false);
const [selectedBackend, setSelectedBackend] = const [selectedBackend, setSelectedBackend] =
@@ -39,31 +48,21 @@ const BackendFields: React.FC = () => {
onValuesChange?.({}, form.getFieldsValue()); onValuesChange?.({}, form.getFieldsValue());
}; };
const backendGroupedOptions = useMemo(() => { const backendHelperText = useMemo(() => {
const builtInBackends = backendOptions?.filter( const selected = flatBackendOptions?.find((item) => item.value === backend);
(item) => item.isBuiltIn || item.value === backendOptionsMap.custom if (
); selected &&
const customBackends = backendOptions?.filter( !selected.enabled &&
(item) => !item.isBuiltIn && item.value !== backendOptionsMap.custom selected.backend_source === BackendSourceValueMap.COMMUNITY
); ) {
return (
const options = []; <span style={{ color: 'var(--ant-color-error)' }}>
{intl.formatMessage({ id: 'models.form.backend.helperText' })}
if (builtInBackends && builtInBackends.length > 0) { </span>
options.push({ );
label: intl.formatMessage({ id: 'backend.builtin' }),
options: builtInBackends
});
} }
return null;
if (customBackends && customBackends.length > 0) { }, [backend, flatBackendOptions, intl]);
options.push({
label: intl.formatMessage({ id: 'models.form.backend.custom' }),
options: customBackends
});
}
return options;
}, [backendOptions, intl]);
const backendVersions = useMemo((): { const backendVersions = useMemo((): {
builtIn: any[]; builtIn: any[];
@@ -111,14 +110,6 @@ const BackendFields: React.FC = () => {
}; };
}, [backend, selectedBackend, intl]); }, [backend, selectedBackend, intl]);
const optionRender = (option: any) => {
return option.data.title;
};
const labelRender = (option: any) => {
return option.title;
};
const backendVersionLabelRender = (option: any) => { const backendVersionLabelRender = (option: any) => {
console.log('backendVersionLabelRender option:', option); console.log('backendVersionLabelRender option:', option);
return option.title; return option.title;
@@ -140,10 +131,21 @@ const BackendFields: React.FC = () => {
}; };
const handleOnBackendChange = (value: any[], option: any) => { const handleOnBackendChange = (value: any[], option: any) => {
console.log('handleOnBackendChange value, option:', value, option); form.setFieldValue('backend', value?.[1]);
form.setFieldsValue({ backend: value[0] }); onBackendChange?.(value?.[1], option?.[1] || {});
onBackendChange?.(value[0], option[1]); setSelectedBackend(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[]) => { 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 ( return (
<> <>
<Form.Item name="backend" hidden> <Form.Item name="backend" hidden>
@@ -205,19 +221,12 @@ const BackendFields: React.FC = () => {
message: getRuleMessage('select', 'models.form.backend') 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 <SealCascader
required required
showSearch showSearch
allowClear={false}
changeOnSelect={false} changeOnSelect={false}
expandTrigger="hover" expandTrigger="hover"
multiple={false} multiple={false}
@@ -231,6 +240,7 @@ const BackendFields: React.FC = () => {
options={backendOptions} options={backendOptions}
getPopupContainer={(triggerNode) => triggerNode.parentNode} getPopupContainer={(triggerNode) => triggerNode.parentNode}
optionNode={BackendNode} optionNode={BackendNode}
displayRender={displayRender}
onChange={handleOnBackendChange} onChange={handleOnBackendChange}
></SealCascader> ></SealCascader>
</Form.Item> </Form.Item>
@@ -244,6 +254,7 @@ const BackendFields: React.FC = () => {
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'models.form.backendVersion.holder' id: 'models.form.backendVersion.holder'
})} })}
description={<TooltipList list={backendTipsList}></TooltipList>}
onChange={handleBackendVersionOnChange} onChange={handleBackendVersionOnChange}
label={intl.formatMessage({ id: 'models.form.backendVersion' })} label={intl.formatMessage({ id: 'models.form.backendVersion' })}
footer={ footer={
+3 -1
View File
@@ -90,7 +90,8 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
onOk onOk
} = props; } = props;
const { getScrollElementScrollableHeight } = useWrapperContext(); const { getScrollElementScrollableHeight } = useWrapperContext();
const { backendOptions, getBackendOptions } = useQueryBackends(); const { backendOptions, flatBackendOptions, getBackendOptions } =
useQueryBackends();
const { getGPUOptionList, gpuOptions, workerLabelOptions } = const { getGPUOptionList, gpuOptions, workerLabelOptions } =
useGenerateGPUOptions(); useGenerateGPUOptions();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -394,6 +395,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
action: action, action: action,
gpuOptions: gpuOptions, gpuOptions: gpuOptions,
backendOptions: backendOptions, backendOptions: backendOptions,
flatBackendOptions: flatBackendOptions,
workerLabelOptions: workerLabelOptions, workerLabelOptions: workerLabelOptions,
initialValues: initialValues, initialValues: initialValues,
modelContextData: modelContextData, modelContextData: modelContextData,
+3 -3
View File
@@ -11,7 +11,7 @@ import { FormData } from '../config/types';
const KVCacheForm = () => { const KVCacheForm = () => {
const intl = useIntl(); const intl = useIntl();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { onValuesChange, backendOptions, formKey } = useFormContext(); const { onValuesChange, flatBackendOptions, formKey } = useFormContext();
const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form); const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form);
const backend = Form.useWatch('backend', form); const backend = Form.useWatch('backend', form);
const configCacheRef = useRef<any>({}); const configCacheRef = useRef<any>({});
@@ -68,7 +68,7 @@ const KVCacheForm = () => {
}; };
const builtInBackend = useMemo(() => { const builtInBackend = useMemo(() => {
const currentBackend = backendOptions.find( const currentBackend = flatBackendOptions.find(
(item) => item.value === backend (item) => item.value === backend
); );
@@ -78,7 +78,7 @@ const KVCacheForm = () => {
backend as string backend as string
) )
); );
}, [backend, backendOptions]); }, [backend, flatBackendOptions]);
return ( return (
<> <>
@@ -18,7 +18,7 @@ const LocalPathForm: React.FC = () => {
const { const {
formKey, formKey,
gpuOptions, gpuOptions,
backendOptions, flatBackendOptions,
onValuesChange, onValuesChange,
onBackendChange onBackendChange
} = useFormContext(); } = useFormContext();
@@ -68,7 +68,7 @@ const LocalPathForm: React.FC = () => {
}); });
if (oldBackend !== backend) { if (oldBackend !== backend) {
const option = backendOptions.find((item) => item.value === backend); const option = flatBackendOptions.find((item) => item.value === backend);
onBackendChange?.(backend, option); onBackendChange?.(backend, option);
} else { } else {
onValuesChange?.({ local_path: value }, form.getFieldsValue()); onValuesChange?.({ local_path: value }, form.getFieldsValue());
@@ -21,7 +21,7 @@ const AlgorithmMap = {
const SpeculativeDecode = () => { const SpeculativeDecode = () => {
const intl = useIntl(); const intl = useIntl();
const { source, backendOptions, onValuesChange } = useFormContext(); const { source, flatBackendOptions, onValuesChange } = useFormContext();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const backend = Form.useWatch('backend', form); const backend = Form.useWatch('backend', form);
@@ -76,7 +76,7 @@ const SpeculativeDecode = () => {
}; };
const builtInBackend = useMemo(() => { const builtInBackend = useMemo(() => {
const currentBackend = backendOptions.find( const currentBackend = flatBackendOptions.find(
(item) => item.value === backend (item) => item.value === backend
); );
@@ -86,7 +86,7 @@ const SpeculativeDecode = () => {
backend as string backend as string
) )
); );
}, [backend, backendOptions]); }, [backend, flatBackendOptions]);
return ( return (
<> <>
@@ -6,6 +6,7 @@ import {
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
import { useState } from 'react';
import { queryBackendList } from '../apis'; import { queryBackendList } from '../apis';
import { backendOptionsMap } from '../config/backend-parameters'; import { backendOptionsMap } from '../config/backend-parameters';
import { BackendOption } from '../config/types'; import { BackendOption } from '../config/types';
@@ -43,6 +44,9 @@ const groupByBackendSource = (list: BackendOption[]): BackendGroup[] => {
export default function useQueryBackends() { export default function useQueryBackends() {
const [backendOptions, setBackendOptions] = useAtom(backendOptionsAtom); const [backendOptions, setBackendOptions] = useAtom(backendOptionsAtom);
const [flatBackendOptions, setFlatBackendOptions] = useState<BackendOption[]>(
[]
);
const intl = useIntl(); const intl = useIntl();
const getBackendOptions = async (params?: { cluster_id: number }) => { const getBackendOptions = async (params?: { cluster_id: number }) => {
@@ -57,6 +61,7 @@ export default function useQueryBackends() {
'is_built_in', 'is_built_in',
'default_backend_param' 'default_backend_param'
]), ]),
backend_source: item.backend_source || BackendSourceValueMap.CUSTOM,
value: item.backend_name, value: item.backend_name,
label: label:
item.backend_name === backendOptionsMap.custom item.backend_name === backendOptionsMap.custom
@@ -78,22 +83,23 @@ export default function useQueryBackends() {
}); });
const groupList = groupByBackendSource(list); const groupList = groupByBackendSource(list);
setFlatBackendOptions(list);
setBackendOptions(groupList);
console.log('Fetched backend options:', list, groupList); console.log('Fetched backend options:', list, groupList);
if (res?.items) {
setBackendOptions(groupList);
}
return groupList || []; return groupList || [];
} catch (error) { } catch (error) {
// ignore // ignore
setBackendOptions([]); setBackendOptions([]);
setFlatBackendOptions([]);
return []; return [];
} }
}; };
return { return {
backendOptions, backendOptions,
flatBackendOptions,
getBackendOptions getBackendOptions
}; };
} }