chore: provider form test
This commit is contained in:
@@ -27,6 +27,7 @@ const SealSelect: React.FC<
|
||||
loading,
|
||||
footer,
|
||||
alwaysFocus = false,
|
||||
styles,
|
||||
...rest
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
@@ -109,6 +110,8 @@ const SealSelect: React.FC<
|
||||
>
|
||||
<BaseSelect
|
||||
{...rest}
|
||||
styles={styles}
|
||||
placeholder={placeholder}
|
||||
footer={footer}
|
||||
ref={inputRef}
|
||||
options={children ? null : _options}
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ export enum PageAction {
|
||||
CREATE = 'create',
|
||||
UPDATE = 'update',
|
||||
VIEW = 'view',
|
||||
EDIT = 'edit'
|
||||
EDIT = 'edit',
|
||||
COPY = 'copy'
|
||||
}
|
||||
|
||||
export const StatusColorMap: Record<
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ export interface DropDownItem {
|
||||
iconfont?: boolean;
|
||||
}
|
||||
|
||||
export type PageActionType = 'create' | 'update' | 'view' | 'edit';
|
||||
export type PageActionType = 'create' | 'update' | 'view' | 'edit' | 'copy';
|
||||
|
||||
export type StatusType =
|
||||
| 'error'
|
||||
|
||||
@@ -11,5 +11,6 @@ export default {
|
||||
'providers.form.proxy.url': 'Proxy URL',
|
||||
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
|
||||
'providers.form.customConfig': 'Custom Configuration',
|
||||
'providers.form.model.test': 'Test Connection'
|
||||
'providers.form.model.test': 'Test',
|
||||
'providers.form.model.test.tips': 'Test Connection'
|
||||
};
|
||||
|
||||
@@ -11,5 +11,6 @@ export default {
|
||||
'providers.form.proxy.url': 'Proxy URL',
|
||||
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
|
||||
'providers.form.customConfig': 'Custom Configuration',
|
||||
'providers.form.model.test': 'Test Connection'
|
||||
'providers.form.model.test': 'Test',
|
||||
'providers.form.model.test.tips': 'Test Connection'
|
||||
};
|
||||
|
||||
@@ -11,5 +11,6 @@ export default {
|
||||
'providers.form.proxy.url': 'Proxy URL',
|
||||
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
|
||||
'providers.form.customConfig': 'Custom Configuration',
|
||||
'providers.form.model.test': 'Test Connection'
|
||||
'providers.form.model.test': 'Test',
|
||||
'providers.form.model.test.tips': 'Test Connection'
|
||||
};
|
||||
|
||||
@@ -8,8 +8,9 @@ export default {
|
||||
'providers.form.tokens.add': '添加 API 令牌',
|
||||
'providers.form.models.add': '添加模型',
|
||||
'providers.form.proxy.enable': '启用代理',
|
||||
'providers.form.proxy.url': '代理 URL',
|
||||
'providers.form.proxy.url': '代理地址',
|
||||
'providers.form.proxy.timeout': '代理超时(秒)',
|
||||
'providers.form.customConfig': '自定义配置',
|
||||
'providers.form.model.test': '测试连接'
|
||||
'providers.form.model.test': '测试',
|
||||
'providers.form.model.test.tips': '测试连接'
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { request } from '@umijs/max';
|
||||
import { FormData, MaasProviderItem } from '../config/types';
|
||||
|
||||
export const MAAS_PROVIDERS_API = '/models';
|
||||
export const MAAS_PROVIDERS_API = '/model-providers';
|
||||
|
||||
export const PROVIDER_MODELS_API = '/models';
|
||||
export const GET_PROVIDER_MODELS_API = '/get-models';
|
||||
|
||||
export const TEST_PROVIDER_MODEL_API = '/test-model';
|
||||
|
||||
export async function queryMaasProviders(
|
||||
params: Global.SearchParams,
|
||||
@@ -37,14 +39,41 @@ export async function deleteProvider(id: number) {
|
||||
}
|
||||
|
||||
export async function queryProviderModels(
|
||||
params: { id: string },
|
||||
params: {
|
||||
data: {
|
||||
api_token: string;
|
||||
config: {
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
},
|
||||
options?: any
|
||||
) {
|
||||
return request<Global.BaseOption<string>[]>(
|
||||
`${MAAS_PROVIDERS_API}/${params.id}/models`,
|
||||
return request<{ data: any[] }>(
|
||||
`${MAAS_PROVIDERS_API}${GET_PROVIDER_MODELS_API}`,
|
||||
{
|
||||
method: 'GET',
|
||||
method: 'post',
|
||||
data: params.data,
|
||||
cancelToken: options?.token
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function testProviderModel(
|
||||
params: {
|
||||
data: {
|
||||
model_name: string;
|
||||
api_token: string;
|
||||
config: {
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
},
|
||||
options?: any
|
||||
) {
|
||||
return request<any>(`${MAAS_PROVIDERS_API}${TEST_PROVIDER_MODEL_API}`, {
|
||||
method: 'post',
|
||||
data: params.data,
|
||||
cancelToken: options?.token
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import FormDrawer from '@/pages/_components/form-drawer';
|
||||
import React, { useRef } from 'react';
|
||||
import { FormData, MaasProviderItem as ListItem } from '../config/types';
|
||||
|
||||
import { maasProviderType } from '../config';
|
||||
import ProviderForm from '../forms';
|
||||
|
||||
type AddModalProps = {
|
||||
@@ -11,7 +10,6 @@ type AddModalProps = {
|
||||
action: PageActionType;
|
||||
open: boolean;
|
||||
currentData?: ListItem; // Used when action is EDIT
|
||||
provider: maasProviderType | null;
|
||||
onOk: (values: FormData) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
@@ -19,7 +17,6 @@ const AddProvider: React.FC<AddModalProps> = ({
|
||||
title,
|
||||
action,
|
||||
open,
|
||||
provider,
|
||||
currentData,
|
||||
onOk,
|
||||
onCancel
|
||||
@@ -30,7 +27,8 @@ const AddProvider: React.FC<AddModalProps> = ({
|
||||
form.current?.submit();
|
||||
};
|
||||
|
||||
const handleOk = async (data: FormData) => {
|
||||
const handleOnFinish = async (data: FormData) => {
|
||||
console.log('handleOnFinish', data);
|
||||
onOk({
|
||||
...data
|
||||
});
|
||||
@@ -53,7 +51,7 @@ const AddProvider: React.FC<AddModalProps> = ({
|
||||
ref={form}
|
||||
action={action}
|
||||
currentData={currentData}
|
||||
onFinish={handleOk}
|
||||
onFinish={handleOnFinish}
|
||||
/>
|
||||
</FormDrawer>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import { modelCategoriesMap } from '@/pages/llmodels/config';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
@@ -8,56 +7,13 @@ import {
|
||||
import { Flex, Tag } from 'antd';
|
||||
import React from 'react';
|
||||
import { categoryConfig } from '../../_components/model-tag';
|
||||
import { ProviderModel } from '../config/types';
|
||||
|
||||
interface ProviderModelProps {
|
||||
dataList: any[];
|
||||
provider: string;
|
||||
providerId: number;
|
||||
dataList: ProviderModel[];
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
{
|
||||
name: 'model-5',
|
||||
status: 'inaccessible',
|
||||
type: modelCategoriesMap.embedding
|
||||
},
|
||||
{
|
||||
name: 'model-6model-6model-6model-6model-6model-6model-6',
|
||||
status: 'none',
|
||||
type: modelCategoriesMap.image
|
||||
},
|
||||
{
|
||||
name: 'model-7',
|
||||
status: 'accessible',
|
||||
type: modelCategoriesMap.llm
|
||||
}
|
||||
];
|
||||
|
||||
const ProviderModels: React.FC<ProviderModelProps> = () => {
|
||||
const ProviderModels: React.FC<ProviderModelProps> = ({ dataList }) => {
|
||||
const iconsMap = {
|
||||
accessible: <CheckCircleOutlined />,
|
||||
inaccessible: <CloseCircleOutlined />,
|
||||
@@ -66,10 +22,14 @@ const ProviderModels: React.FC<ProviderModelProps> = () => {
|
||||
return (
|
||||
<div style={{ paddingInline: 16 }}>
|
||||
<Flex gap="8px" wrap="wrap">
|
||||
{models.map((model) => (
|
||||
{dataList.map((model) => (
|
||||
<Tag
|
||||
key={model.name}
|
||||
icon={iconsMap[model.status]}
|
||||
icon={
|
||||
model.accessible !== null
|
||||
? iconsMap[model.accessible ? 'accessible' : 'inaccessible']
|
||||
: iconsMap['none']
|
||||
}
|
||||
variant="outlined"
|
||||
styles={{
|
||||
root: {
|
||||
@@ -80,9 +40,9 @@ const ProviderModels: React.FC<ProviderModelProps> = () => {
|
||||
}
|
||||
}}
|
||||
color={
|
||||
model.status === 'accessible'
|
||||
model.accessible === true
|
||||
? 'success'
|
||||
: model.status === 'inaccessible'
|
||||
: model.accessible === false
|
||||
? 'error'
|
||||
: 'warning'
|
||||
}
|
||||
@@ -92,7 +52,7 @@ const ProviderModels: React.FC<ProviderModelProps> = () => {
|
||||
{model.name}
|
||||
</AutoTooltip>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
{categoryConfig[model.type]?.icon}
|
||||
{categoryConfig[model.category]?.icon}
|
||||
</span>
|
||||
</span>
|
||||
</Tag>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { createContext, useContext } from 'react';
|
||||
import { maasProviderType } from '.';
|
||||
|
||||
interface FormContextProps {
|
||||
providerType?: string;
|
||||
providerType?: maasProviderType;
|
||||
action?: PageActionType;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { MaasProviderItem } from '../config/types';
|
||||
|
||||
// mock data for MaasProviderItem 5 items
|
||||
export const mockDataList: MaasProviderItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Doubao',
|
||||
state: 'Ready',
|
||||
models: 5,
|
||||
state_message: '',
|
||||
provider: 'doubao',
|
||||
api_tokens: ['token1', 'token2'],
|
||||
proxy_url: 'http://proxy.example.com',
|
||||
builtIn: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
models: 3,
|
||||
name: 'Qwen',
|
||||
state: 'inactive',
|
||||
state_message: 'Provider is inactive',
|
||||
provider: 'qwen',
|
||||
proxy_url: 'http://proxy2.example.com',
|
||||
|
||||
builtIn: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
models: 8,
|
||||
name: 'OpenAI',
|
||||
state: 'ready',
|
||||
state_message: '',
|
||||
proxy_url: null,
|
||||
api_tokens: ['tokenA'],
|
||||
provider: 'openai',
|
||||
builtIn: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
models: 2,
|
||||
name: 'Deepseek',
|
||||
state: 'ready',
|
||||
state_message: '',
|
||||
provider: 'deepseek',
|
||||
proxy_url: null,
|
||||
api_tokens: ['tokenX', 'tokenY'],
|
||||
builtIn: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
models: 4,
|
||||
name: 'Anthropic',
|
||||
state: 'inactive',
|
||||
state_message: 'Provider is inactive',
|
||||
provider: 'claude',
|
||||
proxy_url: {},
|
||||
builtIn: true
|
||||
}
|
||||
];
|
||||
@@ -1,9 +1,14 @@
|
||||
import { maasProviderType } from '.';
|
||||
|
||||
export interface ProviderModel {
|
||||
name: string;
|
||||
category: string;
|
||||
accessible: boolean | null;
|
||||
}
|
||||
export interface FormData {
|
||||
name: string;
|
||||
description: string;
|
||||
api_tokens: string[];
|
||||
models: ProviderModel[];
|
||||
config: {
|
||||
type: maasProviderType;
|
||||
[key: string]: any;
|
||||
@@ -15,7 +20,7 @@ export interface MaasProviderItem extends FormData {
|
||||
updated_at: string;
|
||||
deleted_at: string;
|
||||
timeout: number;
|
||||
support_models: unknown[];
|
||||
models: ProviderModel[];
|
||||
proxy_url: string;
|
||||
proxy_timeout: number;
|
||||
builtin: boolean;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { useState } from 'react';
|
||||
import { maasProviderType } from '../config';
|
||||
import { MaasProviderItem as ListItem } from '../config/types';
|
||||
|
||||
const useCreateProvider = (options: { refresh: () => void }) => {
|
||||
@@ -10,13 +9,11 @@ const useCreateProvider = (options: { refresh: () => void }) => {
|
||||
action: PageActionType;
|
||||
currentData?: ListItem;
|
||||
title: string;
|
||||
provider: maasProviderType | null;
|
||||
}>({
|
||||
open: false,
|
||||
action: PageAction.CREATE,
|
||||
currentData: undefined,
|
||||
title: '',
|
||||
provider: null
|
||||
title: ''
|
||||
});
|
||||
|
||||
const openModal = (action: PageActionType, title: string, row?: ListItem) => {
|
||||
@@ -25,8 +22,7 @@ const useCreateProvider = (options: { refresh: () => void }) => {
|
||||
open: true,
|
||||
title: title,
|
||||
action,
|
||||
currentData: row,
|
||||
provider: row ? row.provider : null
|
||||
currentData: row
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,8 +31,7 @@ const useCreateProvider = (options: { refresh: () => void }) => {
|
||||
open: false,
|
||||
action: PageAction.CREATE,
|
||||
currentData: undefined,
|
||||
title: '',
|
||||
provider: null
|
||||
title: ''
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useMemo } from 'react';
|
||||
import ProviderLogo from '../components/provider-logo';
|
||||
import ProviderModels from '../components/provider-models';
|
||||
import { maasProviderLabelMap, rowActionList } from '../config';
|
||||
import { MaasProviderItem } from '../config/types';
|
||||
import { MaasProviderItem, ProviderModel } from '../config/types';
|
||||
|
||||
const useProviderColumns = (
|
||||
handleSelect: (val: string, record: MaasProviderItem) => void,
|
||||
@@ -41,7 +41,7 @@ const useProviderColumns = (
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'providers.table.providerName' }),
|
||||
dataIndex: 'provider',
|
||||
dataIndex: ['config', 'type'],
|
||||
sorter: tableSorter(2),
|
||||
span: 4,
|
||||
minWidth: 160,
|
||||
@@ -61,8 +61,9 @@ const useProviderColumns = (
|
||||
dataIndex: 'models',
|
||||
span: 3,
|
||||
minWidth: 200,
|
||||
sorter: tableSorter(3),
|
||||
render: (value: number) => <ProviderModels></ProviderModels>
|
||||
render: (value: ProviderModel[]) => (
|
||||
<ProviderModels dataList={value || []}></ProviderModels>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { message } from 'antd';
|
||||
import { CancelTokenSource } from 'axios';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { queryProviderModels, testProviderModel } from '../apis';
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns loading, fetch, dataList
|
||||
*/
|
||||
export const useQueryProviderModels = () => {
|
||||
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||
const [providerModelList, setProviderModelList] = useState<any[]>([]);
|
||||
|
||||
const {
|
||||
run: fetchProviderModels,
|
||||
loading,
|
||||
cancel
|
||||
} = useRequest(
|
||||
async (params: {
|
||||
data: { api_token: string; config: { type: string } };
|
||||
}) => {
|
||||
axiosTokenRef.current?.cancel();
|
||||
axiosTokenRef.current = createAxiosToken();
|
||||
return await queryProviderModels(params, {
|
||||
token: axiosTokenRef.current.token
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (response) => {
|
||||
setProviderModelList(
|
||||
response.data?.map((item: any) => ({
|
||||
label: item.id,
|
||||
value: item.id
|
||||
})) || []
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
setProviderModelList([]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cancel();
|
||||
axiosTokenRef.current?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
providerModelList,
|
||||
fetchProviderModels
|
||||
};
|
||||
};
|
||||
|
||||
export const useTestProviderModel = () => {
|
||||
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||
|
||||
const {
|
||||
runAsync: runTestModel,
|
||||
loading,
|
||||
cancel
|
||||
} = useRequest(
|
||||
async (params: {
|
||||
data: { api_token: string; config: { type: string }; model_name: string };
|
||||
}) => {
|
||||
axiosTokenRef.current?.cancel();
|
||||
axiosTokenRef.current = createAxiosToken();
|
||||
const response = await testProviderModel(params, {
|
||||
token: axiosTokenRef.current.token
|
||||
});
|
||||
return response;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (response) => {
|
||||
if (!response?.accessible) {
|
||||
message.error(response?.error_message || 'Test model failed');
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(error?.message || 'Test model failed');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cancel();
|
||||
axiosTokenRef.current?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
runTestModel
|
||||
};
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import { PageAction } from '@/config';
|
||||
import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import useWatchList from '@/hooks/use-watch-list';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { ConfigProvider, message, Table } from 'antd';
|
||||
@@ -16,16 +15,15 @@ import _ from 'lodash';
|
||||
import NoResult from '../_components/no-result';
|
||||
import PageBox from '../_components/page-box';
|
||||
import {
|
||||
createProvider,
|
||||
deleteProvider,
|
||||
MAAS_PROVIDERS_API,
|
||||
PROVIDER_MODELS_API,
|
||||
queryMaasProviders,
|
||||
updateProvider
|
||||
} from './apis';
|
||||
import AddMaasProvider from './components/add-provider-modal';
|
||||
import ProviderModels from './components/provider-models';
|
||||
import { maasProviderOptions } from './config';
|
||||
import { mockDataList } from './config/mock';
|
||||
import {
|
||||
FormData,
|
||||
MaasProviderItem as ListItem,
|
||||
@@ -50,12 +48,10 @@ const MaasProvider: React.FC = () => {
|
||||
} = useTableFetch<ListItem>({
|
||||
fetchAPI: queryMaasProviders,
|
||||
deleteAPI: deleteProvider,
|
||||
watch: true,
|
||||
watch: false,
|
||||
API: MAAS_PROVIDERS_API,
|
||||
contentForDelete: 'menu.models.providers'
|
||||
});
|
||||
const { watchDataList: allProviderModels } =
|
||||
useWatchList(PROVIDER_MODELS_API);
|
||||
const [expandAtom] = useAtom(expandKeysAtom);
|
||||
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
||||
useExpandedRowKeys(expandAtom);
|
||||
@@ -83,6 +79,14 @@ const MaasProvider: React.FC = () => {
|
||||
id: openProviderModalStatus.currentData!.id
|
||||
});
|
||||
}
|
||||
if (
|
||||
openProviderModalStatus.action === PageAction.CREATE ||
|
||||
openProviderModalStatus.action === PageAction.COPY
|
||||
) {
|
||||
await createProvider({
|
||||
data: params
|
||||
});
|
||||
}
|
||||
fetchData();
|
||||
closeProviderModal();
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
@@ -112,7 +116,7 @@ const MaasProvider: React.FC = () => {
|
||||
handleDelete({ ...row, name: row.name });
|
||||
}
|
||||
if (val === 'copy') {
|
||||
openProviderModal(PageAction.EDIT, 'Copy Provider', row);
|
||||
openProviderModal(PageAction.COPY, 'Copy Provider', row);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -149,13 +153,7 @@ const MaasProvider: React.FC = () => {
|
||||
list: any,
|
||||
options: { parent?: any; [key: string]: any }
|
||||
) => {
|
||||
return (
|
||||
<ProviderModels
|
||||
dataList={list}
|
||||
provider={options.parent?.provider}
|
||||
providerId={options.parent?.id}
|
||||
/>
|
||||
);
|
||||
return <ProviderModels dataList={list} />;
|
||||
};
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
@@ -215,7 +213,7 @@ const MaasProvider: React.FC = () => {
|
||||
renderChildren={renderChildren}
|
||||
onTableSort={handleOnSortChange}
|
||||
showSorterTooltip={false}
|
||||
dataSource={mockDataList}
|
||||
dataSource={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
loadend={dataSource.loadend}
|
||||
rowSelection={rowSelection}
|
||||
@@ -235,7 +233,6 @@ const MaasProvider: React.FC = () => {
|
||||
</ConfigProvider>
|
||||
</PageBox>
|
||||
<AddMaasProvider
|
||||
provider={openProviderModalStatus.provider}
|
||||
open={openProviderModalStatus.open}
|
||||
action={openProviderModalStatus.action}
|
||||
title={openProviderModalStatus.title}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { request } from '@umijs/max';
|
||||
import { AccessItem, FormData } from '../config/types';
|
||||
|
||||
export const ACCESS_API = '/accesses';
|
||||
export const ACCESS_API = '/model-accesses';
|
||||
|
||||
export const ACCESS_POINTS_API = '/models';
|
||||
export const ACCESS_POINTS_API = '/model-access-endpoints';
|
||||
|
||||
export async function queryModelAccesses(
|
||||
params: Global.SearchParams,
|
||||
@@ -38,7 +38,7 @@ export async function deleteAccess(id: number) {
|
||||
|
||||
export async function queryAccessPoints(params: { id: string }, options?: any) {
|
||||
return request<Global.BaseOption<string>[]>(
|
||||
`${ACCESS_API}/${params.id}/points`,
|
||||
`${ACCESS_API}/${params.id}/access`,
|
||||
{
|
||||
method: 'GET',
|
||||
cancelToken: options?.token
|
||||
|
||||
@@ -2,47 +2,6 @@ import icons from '@/components/icon-font/icons';
|
||||
import { StatusMaps } from '@/config';
|
||||
import { StatusType } from '@/config/types';
|
||||
|
||||
export type maasProviderType =
|
||||
| 'doubao'
|
||||
| 'qwen'
|
||||
| 'openai'
|
||||
| 'deepseek'
|
||||
| 'anthropic';
|
||||
|
||||
export const maasProviderValueMap = {
|
||||
Doubao: 'doubao',
|
||||
Qwen: 'qwen',
|
||||
OpenAI: 'openai',
|
||||
Deepseek: 'deepseek',
|
||||
Anthropic: 'anthropic'
|
||||
};
|
||||
|
||||
export const maasProviderLabelMap = {
|
||||
[maasProviderValueMap.Doubao]: 'Doubao',
|
||||
[maasProviderValueMap.Qwen]: 'Qwen',
|
||||
[maasProviderValueMap.OpenAI]: 'OpenAI',
|
||||
[maasProviderValueMap.Deepseek]: 'Deepseek',
|
||||
[maasProviderValueMap.Anthropic]: 'Anthropic'
|
||||
};
|
||||
|
||||
export const providerIconsMap = {
|
||||
[maasProviderValueMap.Doubao]: 'icon-doubao',
|
||||
[maasProviderValueMap.Qwen]: 'icon-qwen',
|
||||
[maasProviderValueMap.OpenAI]: 'icon-openai',
|
||||
[maasProviderValueMap.Deepseek]: 'icon-deepseek',
|
||||
[maasProviderValueMap.Anthropic]: 'icon-anthropic'
|
||||
};
|
||||
|
||||
export const maasProviderOptions = Object.entries(maasProviderValueMap).map(
|
||||
([key, value]) => ({
|
||||
label: maasProviderLabelMap[value],
|
||||
value: value,
|
||||
key: value,
|
||||
locale: false,
|
||||
icon: providerIconsMap[value]
|
||||
})
|
||||
);
|
||||
|
||||
export const ProviderStatusValueMap: Record<string, string> = {
|
||||
Ready: 'Ready',
|
||||
InActive: 'Inactive'
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
} from './apis';
|
||||
import AccessPoints from './components/access-points';
|
||||
import AddAccessModal from './components/add-access-modal';
|
||||
import { maasProviderOptions } from './config';
|
||||
import { mockDataList } from './config/mock';
|
||||
import { FormData, AccessItem as ListItem } from './config/types';
|
||||
import useAccessColumns from './hooks/use-access-columns';
|
||||
@@ -186,12 +185,6 @@ const Accesses: React.FC = () => {
|
||||
handleSearch={handleSearch}
|
||||
handleDeleteByBatch={handleDeleteBatch}
|
||||
handleClickPrimary={handleClickDropdown}
|
||||
actionItems={maasProviderOptions.map((option) => ({
|
||||
...option,
|
||||
icon: (
|
||||
<IconFont type={option.icon as string} style={{ fontSize: 14 }} />
|
||||
)
|
||||
}))}
|
||||
></FilterBar>
|
||||
<TableContext.Provider
|
||||
value={{
|
||||
|
||||
Reference in New Issue
Block a user