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
@@ -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;