chore: fallback selection

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent 051d9421b0
commit 04c25ab0f7
9 changed files with 114 additions and 53 deletions
+1
View File
@@ -48,6 +48,7 @@ const BaseSelect: React.FC<
notFoundContent={ notFoundContent={
<NotFoundContent loading={loading} notFoundContent={notFoundContent} /> <NotFoundContent loading={loading} notFoundContent={notFoundContent} />
} }
placeholder={props.placeholder}
ref={inputRef} ref={inputRef}
onFocus={handleFocus} onFocus={handleFocus}
onBlur={handleBlur} onBlur={handleBlur}
+3 -3
View File
@@ -3,15 +3,15 @@ export default {
'routes.button.add': '添加路由', 'routes.button.add': '添加路由',
'routes.table.routeTargets': '路由目标', 'routes.table.routeTargets': '路由目标',
'routes.table.traffic': '流量', 'routes.table.traffic': '流量',
'routes.table.setAsFallback': '降级', 'routes.table.setAsFallback': 'Fallback',
'routes.form.target.title': '路由目标', 'routes.form.target.title': '路由目标',
'routes.form.target.add': '添加路由目标', 'routes.form.target.add': '添加路由目标',
'routes.form.target.fallback': '降级路由目标', 'routes.form.target.fallback': 'Fallback 路由目标',
'routes.form.target.weight': '权重', 'routes.form.target.weight': '权重',
'routes.form.target.model': '模型', 'routes.form.target.model': '模型',
'routes.form.metadata.title': '元数据', 'routes.form.metadata.title': '元数据',
'routes.form.metadata.add': '添加元数据', 'routes.form.metadata.add': '添加元数据',
'routes.table.label.fallback': '降级', 'routes.table.label.fallback': 'Fallback',
'routes.form.metadata.size': '参数量', 'routes.form.metadata.size': '参数量',
'routes.form.metadata.activeSize': '激活参数量', 'routes.form.metadata.activeSize': '激活参数量',
'routes.form.metadata.tags': '标签', 'routes.form.metadata.tags': '标签',
@@ -5,6 +5,7 @@ import {
WarningOutlined WarningOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Flex, Tag } from 'antd'; import { Flex, Tag } from 'antd';
import _ from 'lodash';
import React from 'react'; import React from 'react';
import { categoryConfig } from '../../_components/model-tag'; import { categoryConfig } from '../../_components/model-tag';
import { ProviderModel } from '../config/types'; import { ProviderModel } from '../config/types';
@@ -26,7 +27,7 @@ const ProviderModels: React.FC<ProviderModelProps> = ({ dataList }) => {
<Tag <Tag
key={model.name} key={model.name}
icon={ icon={
model.accessible !== null _.isBoolean(model.accessible)
? iconsMap[model.accessible ? 'accessible' : 'inaccessible'] ? iconsMap[model.accessible ? 'accessible' : 'inaccessible']
: iconsMap['none'] : iconsMap['none']
} }
@@ -4,7 +4,7 @@ import YamlEditor from '@/pages/_components/yaml-editor';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import AccessToken from './access-token'; import APIKeys from './api-keys';
import ProxyConfig from './proxy-config'; import ProxyConfig from './proxy-config';
const AdvanceConfig: React.FC<{ const AdvanceConfig: React.FC<{
@@ -29,7 +29,7 @@ const AdvanceConfig: React.FC<{
return ( return (
<> <>
<div data-field="advanceConfig"></div> <div data-field="advanceConfig"></div>
<AccessToken></AccessToken> <APIKeys></APIKeys>
<ProxyConfig></ProxyConfig> <ProxyConfig></ProxyConfig>
<Form.Item <Form.Item
hidden hidden
@@ -57,8 +57,13 @@ const SupportedModels = () => {
<> <>
<Form.Item name="models" data-field="supportedModels"> <Form.Item name="models" data-field="supportedModels">
<MetadataList <MetadataList
styles={{
wrapper: {
paddingTop: 14
}
}}
dataList={modelList} dataList={modelList}
label={intl.formatMessage({ id: 'providers.table.models' })} label=""
btnText={intl.formatMessage({ id: 'providers.form.models.add' })} btnText={intl.formatMessage({ id: 'providers.form.models.add' })}
onAdd={onAdd} onAdd={onAdd}
onDelete={onDelete} onDelete={onDelete}
@@ -1,6 +1,5 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import DropdownButtons from '@/components/drop-down-buttons'; import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import RowChildren from '@/components/seal-table/components/row-children'; import RowChildren from '@/components/seal-table/components/row-children';
import StatusTag from '@/components/status-tag'; import StatusTag from '@/components/status-tag';
import { DeleteOutlined } from '@ant-design/icons'; import { DeleteOutlined } from '@ant-design/icons';
@@ -9,7 +8,7 @@ import { Col, Row } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { TargetStatus, TargetStatusValueMap } from '../config'; import { TargetStatus, TargetStatusLabelMap } from '../config';
import { RouteTarget } from '../config/types'; import { RouteTarget } from '../config/types';
const CellContent = styled.div` const CellContent = styled.div`
display: flex; display: flex;
@@ -30,11 +29,11 @@ interface AccessItemProps {
} }
export const childActionList = [ export const childActionList = [
{ // {
key: 'fallback', // key: 'fallback',
label: 'routes.table.setAsFallback', // label: 'routes.table.setAsFallback',
icon: <IconFont type="icon-shield" /> // icon: <IconFont type="icon-shield" />
}, // },
{ {
key: 'delete', key: 'delete',
label: 'common.button.delete', label: 'common.button.delete',
@@ -108,7 +107,7 @@ const RouteItem: React.FC<AccessItemProps> = ({
<StatusTag <StatusTag
statusValue={{ statusValue={{
status: TargetStatus[data.state], status: TargetStatus[data.state],
text: TargetStatusValueMap[data.state], text: TargetStatusLabelMap[data.state],
message: '' message: ''
}} }}
/> />
+65 -23
View File
@@ -5,29 +5,9 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import styled from 'styled-components';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
import useTargetSourceModels from '../hooks/use-target-source-models'; import useTargetSourceModels from '../hooks/use-target-source-models';
const Inner = styled.div`
display: flex;
align-items: center;
width: 100%;
ul.ant-cascader-menu:first-child {
li[data-path-key='deployments'] {
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-bottom: 1px solid var(--ant-color-split);
}
}
}
`;
const TargetsForm = forwardRef((props, ref) => { const TargetsForm = forwardRef((props, ref) => {
const intl = useIntl(); const intl = useIntl();
const { sourceModels, loading, fetchSourceModels } = useTargetSourceModels(); const { sourceModels, loading, fetchSourceModels } = useTargetSourceModels();
@@ -58,6 +38,14 @@ const TargetsForm = forwardRef((props, ref) => {
})); }));
const handleTargetsChange = (value: any[], index: number, options: any[]) => { const handleTargetsChange = (value: any[], index: number, options: any[]) => {
console.log(
'handleTargetsChange:',
value,
index,
options,
dataList,
sourceModels
);
const selectedOption = const selectedOption =
options?.find?.((opt) => opt.value === value[1]) || {}; options?.find?.((opt) => opt.value === value[1]) || {};
const targetList = [...targets]; const targetList = [...targets];
@@ -100,10 +88,10 @@ const TargetsForm = forwardRef((props, ref) => {
const selectedOption = const selectedOption =
options?.find?.((opt) => opt.value === value[1]) || {}; options?.find?.((opt) => opt.value === value[1]) || {};
console.log('fallback selected option data:', value);
form.setFieldValue('fallback_target', { form.setFieldValue('fallback_target', {
...selectedOption?.data ...selectedOption?.data
}); });
console.log('handleFallbackChange:', value, dataList);
setFallbackValues({ setFallbackValues({
value: value value: value
}); });
@@ -127,6 +115,44 @@ const TargetsForm = forwardRef((props, ref) => {
setDataList(newDataList); setDataList(newDataList);
}; };
const buildKey = (path?: any[]) =>
Array.isArray(path) ? path.join('/') : '';
const filterOptions = (currentValue: any[]) => {
const currKey = buildKey(currentValue);
const selectedDataList = [...dataList, { value: fallbackValues.value }];
const selectedKeys = selectedDataList
.filter((item) => item.value)
.map((item) => buildKey(item.value));
return sourceModels
.map((model) => {
const children = model.children?.filter((child) => {
const key = buildKey([child.data?.parentId, child.value]);
return !selectedKeys.includes(key) || key === currKey;
});
return {
...model,
children
};
})
.filter((model) => model.children && model.children.length > 0);
};
const displayRender = (labels: any[]) => {
return (
<span className="flex-center gap-4">
{labels[0]}
<span>/</span>
<span>{labels[1]}</span>
</span>
);
};
useEffect(() => { useEffect(() => {
fetchSourceModels(); fetchSourceModels();
}, []); }, []);
@@ -188,13 +214,21 @@ const TargetsForm = forwardRef((props, ref) => {
root: 'cascader-popup-wrapper gpu-selector' root: 'cascader-popup-wrapper gpu-selector'
} }
}} }}
styles={{
popup: {
listItem: {
padding: '5px 10px'
}
}
}}
maxTagCount={1} maxTagCount={1}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'providers.form.target.placeholder' id: 'providers.form.target.placeholder'
})} })}
value={item.value} value={item.value}
options={sourceModels} options={filterOptions(item.value)}
showCheckedStrategy="SHOW_CHILD" showCheckedStrategy="SHOW_CHILD"
displayRender={displayRender}
getPopupContainer={(triggerNode) => triggerNode.parentNode} getPopupContainer={(triggerNode) => triggerNode.parentNode}
></SealCascader> ></SealCascader>
<span className="seprator">:</span> <span className="seprator">:</span>
@@ -223,6 +257,13 @@ const TargetsForm = forwardRef((props, ref) => {
root: 'cascader-popup-wrapper gpu-selector' root: 'cascader-popup-wrapper gpu-selector'
} }
}} }}
styles={{
popup: {
listItem: {
padding: '5px 10px'
}
}
}}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'routes.form.target.fallback' id: 'routes.form.target.fallback'
})} })}
@@ -231,9 +272,10 @@ const TargetsForm = forwardRef((props, ref) => {
})} })}
maxTagCount={1} maxTagCount={1}
value={fallbackValues.value} value={fallbackValues.value}
options={sourceModels} options={filterOptions(fallbackValues.value)}
onChange={(value, options) => handleFallbackChange(value, options)} onChange={(value, options) => handleFallbackChange(value, options)}
showCheckedStrategy="SHOW_CHILD" showCheckedStrategy="SHOW_CHILD"
displayRender={displayRender}
getPopupContainer={(triggerNode) => triggerNode.parentNode} getPopupContainer={(triggerNode) => triggerNode.parentNode}
></SealCascader> ></SealCascader>
</div> </div>
@@ -1,17 +1,30 @@
import GPUStackLogo from '@/assets/images/small-logo-200x200.png';
import { queryModelsList } from '@/pages/llmodels/apis'; import { queryModelsList } from '@/pages/llmodels/apis';
import { ListItem as ModelListItem } from '@/pages/llmodels/config/types'; import { ListItem as ModelListItem } from '@/pages/llmodels/config/types';
import { queryMaasProviders } from '@/pages/maas-provider/apis'; import { queryMaasProviders } from '@/pages/maas-provider/apis';
import ProviderLogo from '@/pages/maas-provider/components/provider-logo';
import { MaasProviderItem } from '@/pages/maas-provider/config/types'; import { MaasProviderItem } from '@/pages/maas-provider/config/types';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { useState } from 'react'; import React, { useState } from 'react';
import styled from 'styled-components';
const OptionWrapper = styled.span`
display: flex;
align-items: center;
gap: 8px;
`;
type EmptyObject = Record<never, never>; type EmptyObject = Record<never, never>;
type CascaderOption<T extends object = EmptyObject> = { type CascaderOption<T extends object = EmptyObject> = {
label: string; label: React.ReactNode;
value: string | number; value: string | number;
parent?: boolean; parent?: boolean;
disabled?: boolean; disabled?: boolean;
index?: number; index?: number;
data?: {
[key: string]: any;
parentId?: string | number;
};
children?: CascaderOption<T>[]; children?: CascaderOption<T>[];
} & Partial<T>; } & Partial<T>;
@@ -31,17 +44,10 @@ const useTargetSourceModels = () => {
const modelsList = [ const modelsList = [
{ {
label: ( label: (
<span> <OptionWrapper>
<img src={GPUStackLogo} alt="GPUStack" width={16} height={16} />
{intl.formatMessage({ id: 'menu.models.deployment' })} {intl.formatMessage({ id: 'menu.models.deployment' })}
<span </OptionWrapper>
style={{
color: 'var(--ant-color-text-tertiary)',
marginLeft: 4
}}
>
[GPUStack]
</span>
</span>
), ),
value: 'deployments', value: 'deployments',
parent: true, parent: true,
@@ -49,7 +55,8 @@ const useTargetSourceModels = () => {
label: model.name, label: model.name,
value: model.id, value: model.id,
data: { data: {
model_id: model.id model_id: model.id,
parentId: 'deployments'
}, },
source: 'deployment' source: 'deployment'
})) }))
@@ -58,7 +65,12 @@ const useTargetSourceModels = () => {
const providerOptions: CascaderOption[] = providers.items const providerOptions: CascaderOption[] = providers.items
?.map?.((provider: MaasProviderItem) => ({ ?.map?.((provider: MaasProviderItem) => ({
label: provider.name, label: (
<OptionWrapper>
<ProviderLogo provider={provider.config?.type as string} />
<span>{provider.name}</span>
</OptionWrapper>
),
value: provider.id, value: provider.id,
parent: true, parent: true,
children: provider.models?.map?.((model) => ({ children: provider.models?.map?.((model) => ({
@@ -66,7 +78,8 @@ const useTargetSourceModels = () => {
value: model.name, value: model.name,
data: { data: {
provider_model_name: model.name, provider_model_name: model.name,
provider_id: provider.id provider_id: provider.id,
parentId: provider.id
}, },
source: 'providerModel' source: 'providerModel'
})) }))