chore: provider form test

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent d86d061f97
commit e188d788b9
27 changed files with 448 additions and 317 deletions
@@ -8,13 +8,16 @@ const AccessToken = () => {
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance<FormData>();
const tokenList = Form.useWatch('api_tokens', form) || [];
const handleOnChange = (values: string[]) => {};
const handleOnChange = (values: string[]) => {
form.setFieldValue('api_tokens', values);
};
return (
<>
<Form.Item
name="tokens"
name="api_tokens"
rules={[
{
required: true,
@@ -29,7 +32,7 @@ const AccessToken = () => {
required={true}
btnText={intl.formatMessage({ id: 'providers.form.tokens.add' })}
label={intl.formatMessage({ id: 'providers.form.tokens.title' })}
dataList={[]}
dataList={tokenList}
onChange={handleOnChange}
></ListInput>
</Form.Item>
@@ -4,14 +4,12 @@ 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';
import ProxyConfig from './proxy-config';
const AdvanceConfig: React.FC<{
action: PageActionType;
provider?: maasProviderType;
ref?: any;
}> = forwardRef(({ action, provider }, ref) => {
}> = forwardRef(({ action }, ref) => {
const form = Form.useFormInstance();
const intl = useIntl();
const editorRef = React.useRef<any>(null);
+15 -1
View File
@@ -1,5 +1,6 @@
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import ProviderLogo from '../components/provider-logo';
@@ -9,6 +10,7 @@ import { FormData } from '../config/types';
const Basic = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const { getRuleMessage } = useAppUtils();
const optionRender = (option: any) => {
return (
@@ -29,9 +31,21 @@ const Basic = () => {
})}
/>
</Form.Item>
<Form.Item<FormData> name={['config', 'type']}>
<Form.Item<FormData>
name={['config', 'type']}
rules={[
{
required: true,
message: getRuleMessage(
'select',
intl.formatMessage({ id: 'providers.table.providerName' })
)
}
]}
>
<SealSelect
showSearch
required
options={maasProviderOptions}
optionRender={optionRender}
labelRender={optionRender}
+11 -17
View File
@@ -13,7 +13,6 @@ import {
useRef,
useState
} from 'react';
import { maasProviderType } from '../config';
import FormContext from '../config/form-context';
import { FormData, MaasProviderItem as ListItem } from '../config/types';
import AccessToken from './access-token';
@@ -24,7 +23,6 @@ import SupportedModels from './supported-models';
interface ProviderFormProps {
ref?: any;
action: PageActionType;
provider?: maasProviderType;
currentData?: ListItem; // Used when action is EDIT
onFinish: (values: FormData) => Promise<void>;
}
@@ -37,7 +35,7 @@ const TABKeysMap = {
};
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, provider, currentData, onFinish } = props;
const { action, currentData, onFinish } = props;
const intl = useIntl();
const [form] = Form.useForm();
const { getScrollElementScrollableHeight } = useWrapperContext();
@@ -83,10 +81,13 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}));
useEffect(() => {
if (action === PageAction.EDIT && currentData) {
if (
(action === PageAction.EDIT || action === PageAction.COPY) &&
currentData
) {
form.setFieldsValue({
...currentData,
models: currentData.models || []
proxy_enabled: !!currentData.proxy_url
});
}
}, [form, currentData, action]);
@@ -104,16 +105,14 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}}
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
>
<FormContext.Provider value={{ providerType: provider, action }}>
<FormContext.Provider value={{ action }}>
<Form
form={form}
onFinish={onFinish}
initialValues={{
proxy_config: {
enabled: false,
url: '',
timeout: 30
}
proxy_enabled: false,
proxy_url: '',
proxy_timeout: 30
}}
>
<Basic />
@@ -133,12 +132,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
key: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }),
forceRender: true,
children: (
<AdvanceConfig
action={action}
provider={provider}
></AdvanceConfig>
)
children: <AdvanceConfig action={action}></AdvanceConfig>
}
]}
></CollapsePanel>
@@ -0,0 +1,134 @@
import SealSelect from '@/components/seal-form/seal-select';
import { categoryOptions } from '@/pages/llmodels/config';
import {
CheckCircleFilled,
CloseCircleFilled,
LoadingOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Form, Tooltip } from 'antd';
import React from 'react';
import styled from 'styled-components';
import { FormData, ProviderModel } from '../config/types';
import { useTestProviderModel } from '../hooks/use-query-provider-models';
const SelectWrapper = styled.div`
width: 100%;
display: grid;
grid-template-columns: 265px 160px max-content;
align-items: center;
gap: 8px;
.icon-wrapper {
display: flex;
align-items: center;
height: 100%;
}
`;
interface ModelItemProps {
onOpenChange: (open: boolean) => void;
onChange: (data: ProviderModel) => void;
providerModelList: ProviderModel[];
item: ProviderModel;
loading?: boolean;
}
const ModelItem: React.FC<ModelItemProps> = ({
onOpenChange,
onChange,
loading,
providerModelList,
item
}) => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const { runTestModel, loading: testLoading } = useTestProviderModel();
const handleTestModel = async () => {
const res = await runTestModel({
data: {
model_name: item.name,
api_token: form.getFieldValue('api_tokens')?.[0] || '',
config: {
type: form.getFieldValue(['config', 'type']) || ''
}
}
});
onChange({
...item,
accessible: res.accessible
});
};
const handleOnChange = (value: string) => {
onChange({
...item,
accessible: null,
name: value
});
};
const handleOnCategoryChange = (value: string) => {
onChange({
...item,
category: value
});
};
const renderSuffixIcon = () => {
if (testLoading) {
return <LoadingOutlined />;
}
if (item.accessible === true) {
return (
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
/>
);
}
if (item.accessible === false) {
return (
<CloseCircleFilled
style={{ color: 'var(--ant-color-error)', fontSize: 16 }}
/>
);
}
return null;
};
return (
<SelectWrapper>
<SealSelect
loading={loading}
showSearch
onOpenChange={onOpenChange}
suffixIcon={renderSuffixIcon()}
alwaysFocus={true}
value={item.name}
onChange={handleOnChange}
options={providerModelList}
/>
<SealSelect
value={item.category}
onChange={handleOnCategoryChange}
options={categoryOptions}
placeholder={intl.formatMessage({
id: 'models.form.categories'
})}
></SealSelect>
<Tooltip
title={intl.formatMessage({ id: 'providers.form.model.test.tips' })}
>
<Button type="link" size="small" onClick={handleTestModel}>
{testLoading ? (
<LoadingOutlined />
) : (
intl.formatMessage({ id: 'providers.form.model.test' })
)}
</Button>
</Tooltip>
</SelectWrapper>
);
};
export default ModelItem;
+19 -11
View File
@@ -12,36 +12,44 @@ const AdvanceConfig = () => {
const intl = useIntl();
const { action } = useFormContext();
const proxyConfigRef = React.useRef<any>({});
const proxyConfigEnabled = Form.useWatch(['proxy_config', 'enabled'], form);
const proxyConfigEnabled = Form.useWatch('proxy_enabled', form);
const handleSpeculativeEnabledChange = (e: any) => {
const handleProxyEnabledChange = (e: any) => {
if (e.target.checked) {
form.setFieldValue('speculative_config', {
enabled: true,
url: proxyConfigRef.current?.url || '',
timeout: proxyConfigRef.current?.timeout || 30
form.setFieldsValue({
proxy_enabled: true,
proxy_url: proxyConfigRef.current.proxy_url || '',
proxy_timeout: proxyConfigRef.current.proxy_timeout || 30
});
} else {
proxyConfigRef.current = form.getFieldValue('proxy_config');
proxyConfigRef.current = form.getFieldsValue([
'proxy_url',
'proxy_timeout'
]);
form.setFieldsValue({
proxy_enabled: false,
proxy_url: '',
proxy_timeout: 30
});
}
};
return (
<>
<Form.Item
name={['proxy_config', 'enabled']}
name="proxy_enabled"
valuePropName="checked"
style={{ marginBottom: 8 }}
>
<CheckboxField
label={intl.formatMessage({ id: 'providers.form.proxy.enable' })}
onChange={handleSpeculativeEnabledChange}
onChange={handleProxyEnabledChange}
></CheckboxField>
</Form.Item>
{proxyConfigEnabled && (
<>
<Form.Item
name={['proxy_config', 'url']}
name="proxy_url"
rules={[
{
required: true,
@@ -58,7 +66,7 @@ const AdvanceConfig = () => {
placeholder="http://proxy.example.com:8080"
></SealInput.Input>
</Form.Item>
<Form.Item name={['proxy_config', 'timeout']}>
<Form.Item name="proxy_timeout">
<SealInput.Number
label={intl.formatMessage({ id: 'providers.form.proxy.timeout' })}
placeholder="30"
@@ -1,88 +1,79 @@
import ListInput from '@/components/list-input';
import SealSelect from '@/components/seal-form/seal-select';
import { CheckCircleFilled } from '@ant-design/icons';
import MetadataList from '@/components/metadata-list';
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%;
}
`;
import { Form } from 'antd';
import { FormData, ProviderModel } from '../config/types';
import { useQueryProviderModels } from '../hooks/use-query-provider-models';
import ModelItem from './model-item';
const SupportedModels = () => {
const intl = useIntl();
const { providerModelList, loading, fetchProviderModels } =
useQueryProviderModels();
const form = Form.useFormInstance<FormData>();
const modelList = Form.useWatch('models', form) || [];
const handleTestModel = (data: any) => {
data.loading = true;
setTimeout(() => {
data.loading = false;
}, 1000);
};
const handleOpenChange = async (open: boolean) => {
try {
await form.validateFields(['api_tokens', ['config', 'type']]);
const renderModelItem = (
data: any,
{
onChange,
onBlur
}: { onChange: (value: string) => void; onBlur?: (e: any) => void }
) => {
return (
<SelectWrapper>
<SealSelect
suffixIcon={
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
/>
if (open && providerModelList.length === 0) {
fetchProviderModels({
data: {
api_token: form.getFieldValue('api_tokens')?.[0] || '',
config: {
type: form.getFieldValue(['config', 'type']) || ''
}
}
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' }
]}
/>
<Button
type="link"
size="small"
loading={data.loading}
onClick={() => handleTestModel(data)}
>
{intl.formatMessage({ id: 'providers.form.model.test' })}
</Button>
</SelectWrapper>
);
});
}
} catch (error) {}
};
const handleOnChange = (values: string[]) => {};
const updateModelList = (models: ProviderModel[]) => {
form.setFieldsValue({
models: models
});
};
const onAdd = () => {
const newList = [...modelList];
newList.push({ name: '', category: '', accessible: null });
updateModelList(newList);
};
const onDelete = (index: number) => {
const newList = [...modelList];
newList.splice(index, 1);
updateModelList(newList);
};
const onChange = (data: ProviderModel, index: number) => {
const newList = [...modelList];
newList[index] = { ...data };
updateModelList(newList);
};
return (
<>
<Form.Item name="models">
<div data-field="supportedModels"></div>
<ListInput
styles={{
wrapper: {
paddingTop: 14
}
}}
<Form.Item name="models" data-field="supportedModels">
<MetadataList
dataList={modelList}
label={intl.formatMessage({ id: 'providers.table.models' })}
btnText={intl.formatMessage({ id: 'providers.form.models.add' })}
dataList={[]}
renderItem={renderModelItem}
onChange={handleOnChange}
></ListInput>
onAdd={onAdd}
onDelete={onDelete}
>
{(item, index) => (
<ModelItem
key={index}
onOpenChange={handleOpenChange}
onChange={(data) => onChange(data, index)}
loading={loading}
providerModelList={providerModelList}
item={item}
></ModelItem>
)}
</MetadataList>
</Form.Item>
</>
);