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