feat: provider form
This commit is contained in:
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
||||
// import './iconfont/iconfont.js';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_qm5jah0zmp8.js'
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_j0qgzymxj98.js'
|
||||
});
|
||||
|
||||
export default IconFont;
|
||||
|
||||
@@ -15,6 +15,13 @@ interface ListInputProps {
|
||||
onChange: (data: string[]) => void;
|
||||
onBlur?: (e: any, index: number) => void;
|
||||
onDelete?: (index: number) => void;
|
||||
renderItem?: (
|
||||
data: any,
|
||||
props: {
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: (e: any) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
@@ -28,7 +35,8 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
btnText,
|
||||
options,
|
||||
labelExtra,
|
||||
trim = true
|
||||
trim = true,
|
||||
renderItem
|
||||
} = props;
|
||||
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
||||
const countRef = React.useRef(0);
|
||||
@@ -92,12 +100,14 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
<ListItem
|
||||
placeholder={props.placeholder}
|
||||
options={options}
|
||||
data={item}
|
||||
key={item.uid}
|
||||
value={item.value}
|
||||
onBlur={(e) => onBlur?.(e, index)}
|
||||
onRemove={() => handleOnRemove(index)}
|
||||
onChange={(val) => handleOnChange(val, index)}
|
||||
trim={trim}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -8,11 +8,19 @@ interface LabelItemProps {
|
||||
onRemove: () => void;
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: (e: any) => void;
|
||||
renderItem?: (
|
||||
data: any,
|
||||
props: {
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: (e: any) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
value: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
options?: Global.HintOptions[];
|
||||
trim?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const ListItem: React.FC<LabelItemProps> = (props) => {
|
||||
@@ -23,7 +31,9 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
trim = true
|
||||
trim = true,
|
||||
data,
|
||||
renderItem
|
||||
} = props;
|
||||
|
||||
const handleOnChange = (value: any) => {
|
||||
@@ -32,15 +42,22 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div className="list-item">
|
||||
<HintInput
|
||||
value={value}
|
||||
onChange={handleOnChange}
|
||||
onBlur={onBlur}
|
||||
label={label}
|
||||
sourceOptions={options}
|
||||
trim={trim}
|
||||
placeholder={props.placeholder}
|
||||
/>
|
||||
{renderItem ? (
|
||||
renderItem(data, {
|
||||
onChange: handleOnChange,
|
||||
onBlur
|
||||
})
|
||||
) : (
|
||||
<HintInput
|
||||
value={value}
|
||||
onChange={handleOnChange}
|
||||
onBlur={onBlur}
|
||||
label={label}
|
||||
sourceOptions={options}
|
||||
trim={trim}
|
||||
placeholder={props.placeholder}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
size="small"
|
||||
className="btn"
|
||||
|
||||
@@ -11,7 +11,8 @@ import Wrapper from './wrapper';
|
||||
import SelectWrapper from './wrapper/select';
|
||||
|
||||
const SealSelect: React.FC<
|
||||
SelectProps & SealFormItemProps & { footer?: React.ReactNode }
|
||||
SelectProps &
|
||||
SealFormItemProps & { footer?: React.ReactNode; alwaysFocus?: boolean }
|
||||
> = (props) => {
|
||||
const {
|
||||
label,
|
||||
@@ -25,6 +26,7 @@ const SealSelect: React.FC<
|
||||
notFoundContent = null,
|
||||
loading,
|
||||
footer,
|
||||
alwaysFocus = false,
|
||||
...rest
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
@@ -99,7 +101,7 @@ const SealSelect: React.FC<
|
||||
className="seal-select-wrapper"
|
||||
status={status}
|
||||
label={label}
|
||||
isFocus={isFocus}
|
||||
isFocus={alwaysFocus || isFocus}
|
||||
required={required}
|
||||
description={description}
|
||||
disabled={props.disabled}
|
||||
|
||||
@@ -2,6 +2,7 @@ import styled from 'styled-components';
|
||||
import { BORDERRADIUS, INPUT_INNER_PADDING, INPUTHEIGHT } from '../config';
|
||||
|
||||
const SelectWrapper = styled.div`
|
||||
flex: 1;
|
||||
.seal-select-wrapper {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
@@ -89,6 +90,9 @@ const SelectWrapper = styled.div`
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.ant-select .ant-select-content {
|
||||
padding-block: 0px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-select-selection-overflow-item > span {
|
||||
@@ -118,7 +122,7 @@ const SelectWrapper = styled.div`
|
||||
}
|
||||
|
||||
.ant-select-content {
|
||||
padding-block: 20px 0 !important;
|
||||
padding-block: 20px 0;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
&.ant-cascader {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user