chore: access, endpoints to route targets

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent 6d65797b4f
commit 051d9421b0
36 changed files with 411 additions and 435 deletions
+67
View File
@@ -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;
+206
View File
@@ -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;
+176
View File
@@ -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;
+245
View File
@@ -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;