feat: provider form

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent 679e10abd6
commit b2a8d27178
15 changed files with 424 additions and 32 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
import { request } from '@umijs/max';
import { FormData, MaasProviderItem } from '../config/types';
export const MAAS_PROVIDERS_API = '/maas-providers';
export const MAAS_PROVIDERS_API = '/models';
export const PROVIDER_MODELS_API = '/maas-provider-models';
export const PROVIDER_MODELS_API = '/models';
export async function queryMaasProviders(
params: Global.SearchParams,
@@ -1,3 +1,10 @@
import { categoryOptions, modelCategoriesMap } from '@/pages/llmodels/config';
import {
CheckCircleOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons';
import { Flex, Tag } from 'antd';
import React from 'react';
interface ProviderModelProps {
@@ -6,8 +13,61 @@ interface ProviderModelProps {
providerId: number;
}
const models: {
name: string;
status: 'accessible' | 'inaccessible' | 'none';
type: string;
}[] = [
{
name: 'model-1',
status: 'accessible',
type: modelCategoriesMap.llm
},
{
name: 'model-2',
status: 'inaccessible',
type: modelCategoriesMap.embedding
},
{
name: 'model-3',
status: 'none',
type: modelCategoriesMap.image
},
{
name: 'model-4',
status: 'accessible',
type: modelCategoriesMap.llm
}
];
const ProviderModels: React.FC<ProviderModelProps> = () => {
return <div>Provider Models Component</div>;
const iconsMap = {
accessible: <CheckCircleOutlined />,
inaccessible: <CloseCircleOutlined />,
none: <ExclamationCircleOutlined />
};
return (
<div style={{ paddingInline: 16 }}>
<Flex gap="8px" wrap="wrap">
{models.map((model) => (
<Tag
key={model.name}
icon={iconsMap[model.status]}
color={
model.status === 'accessible'
? 'success'
: model.status === 'inaccessible'
? 'red'
: 'gray'
}
>
{model.name} (
{categoryOptions.find((item) => item.value === model.type)?.label})
</Tag>
))}
</Flex>
</div>
);
};
export default ProviderModels;
+1 -1
View File
@@ -21,7 +21,7 @@ export const maasProviderLabelMap = {
[maasProviderValueMap.Doubao]: 'Doubao',
[maasProviderValueMap.Qwen]: 'Qwen',
[maasProviderValueMap.OpenAI]: 'OpenAI',
[maasProviderValueMap.Deepseek]: 'Deepseek',
[maasProviderValueMap.Deepseek]: 'DeepSeek',
[maasProviderValueMap.Anthropic]: 'Anthropic'
};
@@ -0,0 +1,26 @@
import ListInput from '@/components/list-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
const AccessToken = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const handleOnChange = (values: string[]) => {};
return (
<>
<Form.Item name="tokens">
<ListInput
btnText="Add Token"
label="Access Tokens"
dataList={[]}
onChange={handleOnChange}
></ListInput>
</Form.Item>
</>
);
};
export default AccessToken;
@@ -0,0 +1,58 @@
import SealInput from '@/components/seal-form/seal-input';
import { PageActionType } from '@/config/types';
import YamlEditor from '@/pages/_components/yaml-editor';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react';
import { maasProviderType } from '../config';
const AdvanceConfig: React.FC<{
action: PageActionType;
provider?: maasProviderType;
ref?: any;
}> = forwardRef(({ action, provider }, ref) => {
const [form] = Form.useForm();
const intl = useIntl();
const editorRef = React.useRef<any>(null);
const [fileContent, setFileContent] = React.useState<string>('');
useImperativeHandle(ref, () => ({
getYamlValue: () => {
return editorRef.current?.getValue();
},
setYamlValue: (values: any) => {
console.log('setYamlValue:', values);
editorRef.current?.setValue(values || '');
}
}));
return (
<>
<div data-field="customConfig"></div>
<Form.Item
hidden
name="custom_config"
rules={[
{
required: false,
message: ''
}
]}
>
<SealInput.TextArea required={false} trim={false}></SealInput.TextArea>
</Form.Item>
<YamlEditor
ref={editorRef}
title={'Yaml'}
value={fileContent}
height={300}
onUpload={(content) => {
setFileContent(content);
}}
></YamlEditor>
<div className="scroller-to-holder" style={{ height: 1 }}></div>
</>
);
});
export default AdvanceConfig;
+38
View File
@@ -0,0 +1,38 @@
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
const Basic = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
return (
<>
<Form.Item name="name" data-field="name">
<SealInput.Input
required
label={intl.formatMessage({
id: 'common.table.name'
})}
/>
</Form.Item>
<Form.Item name="provider" hidden>
<SealInput.Input
label={intl.formatMessage({
id: 'providers.table.providerName'
})}
/>
</Form.Item>
<Form.Item name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</>
);
};
export default Basic;
+100 -4
View File
@@ -1,17 +1,75 @@
import IconFont from '@/components/icon-font';
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 { forwardRef, useImperativeHandle } from 'react';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { maasProviderType } from '../config';
import { MaasProviderItem as ListItem } from '../config/types';
import AccessToken from './access-token';
import AdvanceConfig from './advance-config';
import Basic from './basic';
import SupportedModels from './supported-models';
interface ProviderFormProps {
ref?: any;
action: PageActionType;
provider?: maasProviderType;
currentData?: ListItem; // Used when action is EDIT
onFinish: (values: FormData) => void;
}
const TABKeysMap = {
BASIC: 'basic',
SUPPORTEDMODELS: 'supportedModels',
CUSTOMCONFIG: 'customConfig',
ADVANCED: 'advanced'
};
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, provider, 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 segmentOptions = [
{
value: TABKeysMap.BASIC,
label: 'Basic',
icon: <IconFont type="icon-basic" />,
field: 'name'
},
{
value: TABKeysMap.SUPPORTEDMODELS,
label: 'Supported Models',
icon: <IconFont type="icon-speed" />,
field: 'supportedModels'
},
{
value: TABKeysMap.CUSTOMCONFIG,
label: 'Custom Config',
icon: <IconFont type="icon-settings" />,
field: 'customConfig'
}
// {
// value: TABKeysMap.ADVANCED,
// label: intl.formatMessage({ id: 'resources.form.advanced' }),
// icon: <IconFont type="icon-settings" />,
// field: 'categories'
// }
];
const handleActiveChange = (key: string[]) => {
setActiveKey(key);
};
const handleOnCollapseChange = (keys: string | string[]) => {
setActiveKey(Array.isArray(keys) ? keys : [keys]);
};
useImperativeHandle(ref, () => ({
submit: () => {
@@ -23,9 +81,47 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}));
return (
<Form form={form} layout="vertical">
{/* Form fields go here */}
</Form>
<ScrollSpyTabs
ref={scrollTabsRef}
defaultTarget="basic"
segmentOptions={segmentOptions}
activeKey={activeKey}
setActiveKey={handleActiveChange}
segmentedTop={{
top: 0,
offsetTop: 96
}}
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
>
<Form form={form}>
<Basic />
<AccessToken />
<CollapsePanel
activeKey={activeKey}
accordion={false}
onChange={handleOnCollapseChange}
items={[
{
key: TABKeysMap.SUPPORTEDMODELS,
label: 'Supported Models',
forceRender: true,
children: <SupportedModels></SupportedModels>
},
{
key: TABKeysMap.CUSTOMCONFIG,
label: 'Custom Config',
forceRender: true,
children: (
<AdvanceConfig
action={action}
provider={provider}
></AdvanceConfig>
)
}
]}
></CollapsePanel>
</Form>
</ScrollSpyTabs>
);
});
@@ -0,0 +1,75 @@
import ListInput from '@/components/list-input';
import SealSelect from '@/components/seal-form/seal-select';
import { CheckCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Form } from 'antd';
import styled from 'styled-components';
import { FormData } from '../config/types';
const SelectWrapper = styled.div`
flex: 1;
display: flex;
align-items: center;
gap: 8px;
.icon-wrapper {
display: flex;
align-items: center;
height: 100%;
}
`;
const SupportedModels = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const renderModelItem = (
data: any,
{
onChange,
onBlur
}: { onChange: (value: string) => void; onBlur?: (e: any) => void }
) => {
return (
<SelectWrapper>
<SealSelect
alwaysFocus={true}
value={data.value}
onChange={onChange}
onBlur={onBlur}
options={[
{ label: 'Model A', value: 'model_a' },
{ label: 'Model B', value: 'model_b' },
{ label: 'Model C', value: 'model_c' }
]}
/>
<span className="icon-wrapper">
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
/>
</span>
<Button type="link" size="small">
Test
</Button>
</SelectWrapper>
);
};
const handleOnChange = (values: string[]) => {};
return (
<>
<Form.Item name="models">
<div data-field="supportedModels"></div>
<ListInput
btnText="Add Model"
label="Supported Models"
dataList={[]}
renderItem={renderModelItem}
onChange={handleOnChange}
></ListInput>
</Form.Item>
</>
);
};
export default SupportedModels;
@@ -19,8 +19,15 @@ const useCreateProvider = (options: { refresh: () => void }) => {
provider: null
});
const openModal = () => {
setOpenModalStatus({ ...openModalStatus, open: true });
const openModal = (action: PageActionType, row?: ListItem) => {
setOpenModalStatus({
...openModalStatus,
open: true,
title: action === PageAction.CREATE ? 'Create Provider' : 'Edit Provider',
action,
currentData: row,
provider: row ? row.provider : null
});
};
const closeModal = () => {
+6 -7
View File
@@ -22,7 +22,6 @@ import {
PROVIDER_MODELS_API,
deleteProvider,
queryMaasProviders,
queryProviderModels,
updateProvider
} from './apis';
import AddMaasProvider from './components/add-provider-modal';
@@ -73,7 +72,7 @@ const MaasProvider: React.FC = () => {
});
const handleClickDropdown = () => {
openProviderModal();
openProviderModal('create');
};
const handleModalOk = async (data: FormData) => {
@@ -149,10 +148,10 @@ const MaasProvider: React.FC = () => {
cluster_id: row.id,
page: -1
};
const data = await queryProviderModels(params, {
token: options?.token
});
return data?.items || [];
// const data = await queryProviderModels(params, {
// token: options?.token
// });
return [1, 2, 3];
}
);
@@ -217,7 +216,7 @@ const MaasProvider: React.FC = () => {
loadend={dataSource.loadend}
rowSelection={rowSelection}
columns={columns}
childParentKey="cluster_id"
childParentKey="provider_id"
expandable={true}
empty={
<NoResult