chore: access, endpoints to route targets
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import CheckboxField from '@/components/seal-form/checkbox-field';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import CategorySelect from '@/pages/_components/category-select';
|
||||
import { categoryOptions } from '@/pages/llmodels/config';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const Basic = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="name" data-field="name">
|
||||
<SealInput.Input
|
||||
required
|
||||
label={intl.formatMessage({ id: 'models.table.name' })}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="categories"
|
||||
normalize={(value) => (value ? [value] : [])}
|
||||
getValueProps={(value) => ({
|
||||
value: Array.isArray(value) ? value[0] || null : value
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('select', 'models.form.categories')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CategorySelect
|
||||
required
|
||||
options={categoryOptions}
|
||||
label={intl.formatMessage({ id: 'models.form.categories' })}
|
||||
></CategorySelect>
|
||||
</Form.Item>
|
||||
<Form.Item name="description" style={{ marginBottom: 8 }}>
|
||||
<SealInput.TextArea
|
||||
scaleSize={true}
|
||||
label={intl.formatMessage({
|
||||
id: 'common.table.description'
|
||||
})}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="generic_proxy"
|
||||
valuePropName="checked"
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<CheckboxField
|
||||
description={intl.formatMessage({
|
||||
id: 'models.form.generic_proxy.tips'
|
||||
})}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.generic_proxy'
|
||||
})}
|
||||
></CheckboxField>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Basic;
|
||||
@@ -0,0 +1,206 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
|
||||
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { FormData, RouteItem as ListItem } from '../config/types';
|
||||
import useEditTargets from '../hooks/use-edit-targets';
|
||||
import Basic from './basic';
|
||||
import Targets from './targets';
|
||||
|
||||
interface ProviderFormProps {
|
||||
ref?: any;
|
||||
action: PageActionType;
|
||||
currentData?: ListItem; // Used when action is EDIT
|
||||
onFinish: (values: FormData) => Promise<void>;
|
||||
}
|
||||
|
||||
const TABKeysMap = {
|
||||
BASIC: 'basic',
|
||||
METADATA: 'metadata',
|
||||
TARGETS: 'targets',
|
||||
ADVANCED: 'advanced'
|
||||
};
|
||||
|
||||
const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
const { action, currentData, onFinish } = props;
|
||||
const intl = useIntl();
|
||||
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
|
||||
const [form] = Form.useForm();
|
||||
const scrollTabsRef = useRef<any>(null);
|
||||
const targetsRef = useRef<any>(null);
|
||||
const { generateTargetData, fetchTargets } = useEditTargets();
|
||||
const segmentOptions = [
|
||||
{
|
||||
value: TABKeysMap.BASIC,
|
||||
label: intl.formatMessage({ id: 'common.title.basicInfo' }),
|
||||
icon: <IconFont type="icon-basic" />,
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
value: TABKeysMap.TARGETS,
|
||||
label: intl.formatMessage({ id: 'routes.form.target.title' }),
|
||||
icon: <IconFont type="icon-language" />,
|
||||
field: 'targets'
|
||||
}
|
||||
];
|
||||
|
||||
const handleActiveChange = (key: string[]) => {
|
||||
setActiveKey(key);
|
||||
};
|
||||
|
||||
const formatTargets = (values: FormData) => {
|
||||
let targetList = [...(values.targets || [])];
|
||||
let fallbackTarget = values.fallback_target;
|
||||
|
||||
if (fallbackTarget) {
|
||||
const exsitinged = targetList.find((ep) => {
|
||||
if (fallbackTarget!.model_id) {
|
||||
return ep.model_id === fallbackTarget!.model_id;
|
||||
}
|
||||
return (
|
||||
ep.provider_id === fallbackTarget!.provider_id &&
|
||||
ep.provider_model_name === fallbackTarget!.provider_model_name
|
||||
);
|
||||
});
|
||||
if (exsitinged) {
|
||||
targetList = targetList.map((ep) => {
|
||||
if (
|
||||
ep.model_id === fallbackTarget.model_id ||
|
||||
ep.provider_model_name === fallbackTarget.provider_model_name
|
||||
) {
|
||||
return {
|
||||
...ep,
|
||||
fallback_status_codes: ['4xx', '5xx']
|
||||
};
|
||||
}
|
||||
return ep;
|
||||
});
|
||||
}
|
||||
|
||||
if (!exsitinged) {
|
||||
targetList.push({
|
||||
...fallbackTarget,
|
||||
weight: 0,
|
||||
fallback_status_codes: ['4xx', '5xx']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return targetList;
|
||||
};
|
||||
|
||||
const handleOnFinish = (values: FormData) => {
|
||||
const targets = formatTargets(values);
|
||||
const data = {
|
||||
..._.omit(values, ['targets', 'fallback_target']),
|
||||
targets: targets
|
||||
};
|
||||
console.log('data=========', data);
|
||||
onFinish(data);
|
||||
};
|
||||
|
||||
const handleOnCollapseChange = (keys: string | string[]) => {
|
||||
setActiveKey(Array.isArray(keys) ? keys : [keys]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initEditionForm = async () => {
|
||||
const targetList = await fetchTargets(currentData!.id);
|
||||
const { targets, fallbackTarget } = generateTargetData(targetList);
|
||||
|
||||
// init form values
|
||||
form.setFieldsValue({
|
||||
...currentData,
|
||||
targets: targets,
|
||||
fallback_target: fallbackTarget
|
||||
});
|
||||
|
||||
// init targets form list
|
||||
targetsRef.current?.initDataList(
|
||||
targets?.map((ep) => ({
|
||||
weight: ep.weight,
|
||||
value: ep.model_id
|
||||
? ['deployments', ep.model_id]
|
||||
: [ep.provider_id, ep.provider_model_name]
|
||||
})) || []
|
||||
);
|
||||
|
||||
// init fallback value
|
||||
if (fallbackTarget) {
|
||||
targetsRef.current?.initFallbackValues({
|
||||
value: fallbackTarget.model_id
|
||||
? ['deployments', fallbackTarget.model_id]
|
||||
: [fallbackTarget.provider_id, fallbackTarget.provider_model_name]
|
||||
});
|
||||
}
|
||||
};
|
||||
if (action === PageAction.EDIT && currentData) {
|
||||
initEditionForm();
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [action, currentData, form]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => {
|
||||
form.submit();
|
||||
},
|
||||
resetFields: () => {
|
||||
form.resetFields();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<ScrollSpyTabs
|
||||
ref={scrollTabsRef}
|
||||
defaultTarget="basic"
|
||||
segmentOptions={segmentOptions}
|
||||
activeKey={activeKey}
|
||||
setActiveKey={handleActiveChange}
|
||||
segmentedTop={{
|
||||
top: 0,
|
||||
offsetTop: 96
|
||||
}}
|
||||
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleOnFinish}
|
||||
initialValues={{
|
||||
categories: [],
|
||||
meta: {}
|
||||
}}
|
||||
>
|
||||
<Basic />
|
||||
<CollapsePanel
|
||||
activeKey={activeKey}
|
||||
accordion={false}
|
||||
onChange={handleOnCollapseChange}
|
||||
items={[
|
||||
{
|
||||
key: TABKeysMap.TARGETS,
|
||||
label: intl.formatMessage({ id: 'routes.form.target.title' }),
|
||||
forceRender: true,
|
||||
children: <Targets ref={targetsRef}></Targets>
|
||||
}
|
||||
]}
|
||||
></CollapsePanel>
|
||||
</Form>
|
||||
</ScrollSpyTabs>
|
||||
);
|
||||
});
|
||||
|
||||
export default AccessForm;
|
||||
@@ -0,0 +1,176 @@
|
||||
import SingleImage from '@/components/auto-image/single-image';
|
||||
import ListInput from '@/components/list-input';
|
||||
import SealInputNumber from '@/components/seal-form/input-number';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import UploadImg from '@/pages/playground/components/upload-img';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Form } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const SizeWrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 16px;
|
||||
`;
|
||||
|
||||
const UploadWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const IconBox = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--ant-border-radius);
|
||||
.icon {
|
||||
color: var(--ant-color-text-tertiary);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const MetaData = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const icon = Form.useWatch(['meta', 'icon'], form);
|
||||
|
||||
const handleMetadataChange = (list: string[], field: string) => {
|
||||
form.setFieldValue(['meta', field], list);
|
||||
};
|
||||
|
||||
const handleUpdateImageList = (fileList: any[]) => {
|
||||
if (fileList.length === 0) {
|
||||
return;
|
||||
}
|
||||
form.setFieldValue(['meta', 'icon'], fileList[0]?.dataUrl || null);
|
||||
};
|
||||
|
||||
const handleDeleteIcon = () => {
|
||||
form.setFieldValue(['meta', 'icon'], null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SizeWrapper>
|
||||
<Form.Item<FormData>
|
||||
data-field="metaSize"
|
||||
name={['meta', 'size']}
|
||||
normalize={(v) => (v === 0 ? null : v)}
|
||||
>
|
||||
<SealInputNumber
|
||||
min={0}
|
||||
label={`${intl.formatMessage({ id: 'routes.form.metadata.size' })} (B)`}
|
||||
></SealInputNumber>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name={['meta', 'activated_size']}
|
||||
normalize={(v) => (v === 0 ? null : v)}
|
||||
>
|
||||
<SealInputNumber
|
||||
min={0}
|
||||
label={`${intl.formatMessage({
|
||||
id: 'routes.form.metadata.activeSize'
|
||||
})} (B)`}
|
||||
></SealInputNumber>
|
||||
</Form.Item>
|
||||
</SizeWrapper>
|
||||
<SizeWrapper>
|
||||
<Form.Item<FormData> name={['meta', 'max_tokens']}>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({
|
||||
id: 'routes.form.metadata.maxTokens'
|
||||
})}
|
||||
placeholder={intl.formatMessage(
|
||||
{ id: 'common.help.eg' },
|
||||
{ content: 'context/128k' }
|
||||
)}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name={['meta', 'dimensions']}
|
||||
normalize={(v) => (v === 0 ? null : v)}
|
||||
>
|
||||
<SealInputNumber
|
||||
min={0}
|
||||
step={1}
|
||||
precision={0}
|
||||
label={intl.formatMessage({
|
||||
id: 'routes.form.metadata.dimension'
|
||||
})}
|
||||
></SealInputNumber>
|
||||
</Form.Item>
|
||||
</SizeWrapper>
|
||||
<Form.Item<FormData> name={['meta', 'release_date']}>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({
|
||||
id: 'routes.form.metadata.releaseDate'
|
||||
})}
|
||||
placeholder={intl.formatMessage(
|
||||
{ id: 'common.help.eg' },
|
||||
{ content: '2025-05-19' }
|
||||
)}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['meta', 'tags']} data-field="metadata">
|
||||
<ListInput
|
||||
label={intl.formatMessage({ id: 'resources.form.label' })}
|
||||
btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
|
||||
dataList={[]}
|
||||
onChange={(list: string[]) => handleMetadataChange(list, 'tags')}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['meta', 'licenses']} data-field="metadata">
|
||||
<ListInput
|
||||
label={intl.formatMessage({ id: 'routes.form.metadata.license' })}
|
||||
btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
|
||||
dataList={[]}
|
||||
onChange={(list: string[]) => handleMetadataChange(list, 'licenses')}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['meta', 'languages']} data-field="metadata">
|
||||
<ListInput
|
||||
label={intl.formatMessage({ id: 'routes.form.metadata.languages' })}
|
||||
btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
|
||||
dataList={[]}
|
||||
onChange={(list: string[]) => handleMetadataChange(list, 'languages')}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['meta', 'icon']} data-field="metadata">
|
||||
<IconBox>
|
||||
<span className="icon">
|
||||
{intl.formatMessage({ id: 'routes.form.metadata.icons' })}
|
||||
</span>
|
||||
<UploadWrapper>
|
||||
<UploadImg
|
||||
handleUpdateImgList={handleUpdateImageList}
|
||||
size="middle"
|
||||
>
|
||||
{icon ? (
|
||||
<SingleImage
|
||||
uid={1}
|
||||
editable={true}
|
||||
onDelete={handleDeleteIcon}
|
||||
dataUrl={icon}
|
||||
preview={false}
|
||||
/>
|
||||
) : (
|
||||
<Button size="middle" variant="dashed" color="default">
|
||||
{intl.formatMessage({
|
||||
id: 'routes.form.metadata.uploadIcon'
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</UploadImg>
|
||||
</UploadWrapper>
|
||||
</IconBox>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetaData;
|
||||
@@ -0,0 +1,245 @@
|
||||
import MetadataList from '@/components/metadata-list';
|
||||
import SealCascader from '@/components/seal-form/seal-cascader';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
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();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const targets = Form.useWatch('targets', form) || [];
|
||||
const [fallbackValues, setFallbackValues] = useState<{ value: any[] }>({
|
||||
value: []
|
||||
});
|
||||
const [dataList, setDataList] = useState<
|
||||
{
|
||||
weight: number | null;
|
||||
value: any[];
|
||||
}[]
|
||||
>([]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
initFallbackValues: (values: { value: any[] }) => {
|
||||
setFallbackValues(values);
|
||||
},
|
||||
initDataList: (
|
||||
list: {
|
||||
weight: number | null;
|
||||
value: any[];
|
||||
}[]
|
||||
) => {
|
||||
setDataList(list);
|
||||
}
|
||||
}));
|
||||
|
||||
const handleTargetsChange = (value: any[], index: number, options: any[]) => {
|
||||
const selectedOption =
|
||||
options?.find?.((opt) => opt.value === value[1]) || {};
|
||||
const targetList = [...targets];
|
||||
targetList[index] = {
|
||||
weight: targetList[index]?.weight || null,
|
||||
...selectedOption?.data
|
||||
};
|
||||
|
||||
form.setFieldValue('targets', [...targetList]);
|
||||
|
||||
const newDataList = [...dataList];
|
||||
newDataList[index] = {
|
||||
weight: newDataList[index]?.weight || null,
|
||||
value: value
|
||||
};
|
||||
setDataList(newDataList);
|
||||
};
|
||||
|
||||
const handleOnAdd = () => {
|
||||
const newDataList = [
|
||||
...dataList,
|
||||
{
|
||||
weight: null,
|
||||
value: []
|
||||
}
|
||||
];
|
||||
setDataList(newDataList);
|
||||
};
|
||||
|
||||
const handleOnDelete = (index: number, item: any) => {
|
||||
const newDataList = dataList.filter((_, i) => i !== index);
|
||||
setDataList(newDataList);
|
||||
|
||||
const targetList = [...targets];
|
||||
targetList.splice(index, 1);
|
||||
form.setFieldValue('targets', [...targetList]);
|
||||
};
|
||||
|
||||
const handleFallbackChange = (value: any[], options?: any[]) => {
|
||||
const selectedOption =
|
||||
options?.find?.((opt) => opt.value === value[1]) || {};
|
||||
|
||||
console.log('fallback selected option data:', value);
|
||||
form.setFieldValue('fallback_target', {
|
||||
...selectedOption?.data
|
||||
});
|
||||
setFallbackValues({
|
||||
value: value
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnWeightChange = (value: any, index: number) => {
|
||||
const targetList = [...targets];
|
||||
if (targetList[index]) {
|
||||
targetList[index] = {
|
||||
...targetList[index],
|
||||
weight: value
|
||||
};
|
||||
form.setFieldValue('targets', [...targetList]);
|
||||
}
|
||||
|
||||
const newDataList = [...dataList];
|
||||
newDataList[index] = {
|
||||
...newDataList[index],
|
||||
weight: value
|
||||
};
|
||||
setDataList(newDataList);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSourceModels();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="targets"
|
||||
data-field="targets"
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (_.keys(value).length > 0) {
|
||||
if (_.some(_.keys(value), (k: string) => !value[k])) {
|
||||
return Promise.reject(
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'common.validate.value'
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
id: 'models.form.selector'
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
]}
|
||||
>
|
||||
<MetadataList
|
||||
label={''}
|
||||
styles={{
|
||||
wrapper: {
|
||||
paddingTop: 14
|
||||
}
|
||||
}}
|
||||
dataList={dataList}
|
||||
btnText={intl.formatMessage({ id: 'routes.form.target.add' })}
|
||||
onAdd={handleOnAdd}
|
||||
onDelete={handleOnDelete}
|
||||
>
|
||||
{(item, index) => (
|
||||
<>
|
||||
<SealCascader
|
||||
required
|
||||
showSearch
|
||||
expandTrigger="hover"
|
||||
multiple={false}
|
||||
alwaysFocus={true}
|
||||
onChange={(value, options) =>
|
||||
handleTargetsChange(value, index, options)
|
||||
}
|
||||
classNames={{
|
||||
popup: {
|
||||
root: 'cascader-popup-wrapper gpu-selector'
|
||||
}
|
||||
}}
|
||||
maxTagCount={1}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'providers.form.target.placeholder'
|
||||
})}
|
||||
value={item.value}
|
||||
options={sourceModels}
|
||||
showCheckedStrategy="SHOW_CHILD"
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
></SealCascader>
|
||||
<span className="seprator">:</span>
|
||||
<SealInput.Number
|
||||
style={{ flex: 100 }}
|
||||
min={0}
|
||||
value={item.weight}
|
||||
onChange={(value) => handleOnWeightChange(value, index)}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'routes.form.target.weight'
|
||||
})}
|
||||
></SealInput.Number>
|
||||
</>
|
||||
)}
|
||||
</MetadataList>
|
||||
</Form.Item>
|
||||
<Form.Item name="fallback_target">
|
||||
<div>
|
||||
<SealCascader
|
||||
showSearch
|
||||
expandTrigger="hover"
|
||||
multiple={false}
|
||||
alwaysFocus={true}
|
||||
classNames={{
|
||||
popup: {
|
||||
root: 'cascader-popup-wrapper gpu-selector'
|
||||
}
|
||||
}}
|
||||
label={intl.formatMessage({
|
||||
id: 'routes.form.target.fallback'
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'providers.form.target.placeholder'
|
||||
})}
|
||||
maxTagCount={1}
|
||||
value={fallbackValues.value}
|
||||
options={sourceModels}
|
||||
onChange={(value, options) => handleFallbackChange(value, options)}
|
||||
showCheckedStrategy="SHOW_CHILD"
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
></SealCascader>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default TargetsForm;
|
||||
Reference in New Issue
Block a user