fix: ui issues
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
WarningOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Flex, Tag } from 'antd';
|
||||
import OverlayScroller from '@/components/overlay-scroller';
|
||||
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Flex, Popover, Tag } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { categoryConfig } from '../../_components/model-tag';
|
||||
@@ -17,47 +15,101 @@ interface ProviderModelProps {
|
||||
const ProviderModels: React.FC<ProviderModelProps> = ({ dataList }) => {
|
||||
const iconsMap = {
|
||||
accessible: <CheckCircleOutlined />,
|
||||
inaccessible: <CloseCircleOutlined />,
|
||||
none: <WarningOutlined />
|
||||
inaccessible: null,
|
||||
none: null
|
||||
};
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const head12Items = dataList.slice(0, 8);
|
||||
const restItems = dataList.slice(8);
|
||||
|
||||
const renderModels = (dataList: ProviderModel[]) => {
|
||||
return (
|
||||
<>
|
||||
{dataList.map((model, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
icon={
|
||||
_.isBoolean(model.accessible)
|
||||
? iconsMap[model.accessible ? 'accessible' : 'inaccessible']
|
||||
: iconsMap['none']
|
||||
}
|
||||
variant="outlined"
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4
|
||||
}
|
||||
}}
|
||||
color={
|
||||
model.accessible === true ? 'var(--ant-color-success)' : 'default'
|
||||
}
|
||||
>
|
||||
<span className="flex-center">
|
||||
<AutoTooltip ghost maxWidth={'120px'}>
|
||||
{model.name}
|
||||
</AutoTooltip>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
{categoryConfig[model.category]?.icon}
|
||||
</span>
|
||||
</span>
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Flex gap="8px" wrap="wrap">
|
||||
{dataList.map((model) => (
|
||||
<Tag
|
||||
key={model.name}
|
||||
icon={
|
||||
_.isBoolean(model.accessible)
|
||||
? iconsMap[model.accessible ? 'accessible' : 'inaccessible']
|
||||
: iconsMap['none']
|
||||
}
|
||||
variant="outlined"
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4
|
||||
<div>
|
||||
<Flex gap="8px" wrap="wrap">
|
||||
{renderModels(head12Items)}
|
||||
{restItems.length > 0 && (
|
||||
<Popover
|
||||
placement="right"
|
||||
content={
|
||||
<OverlayScroller
|
||||
maxHeight={420}
|
||||
styles={{
|
||||
wrapper: {
|
||||
paddingInlineStart: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex gap="8px" wrap="wrap">
|
||||
{renderModels(restItems)}
|
||||
</Flex>
|
||||
</OverlayScroller>
|
||||
}
|
||||
}}
|
||||
color={
|
||||
model.accessible === true
|
||||
? 'success'
|
||||
: model.accessible === false
|
||||
? 'error'
|
||||
: 'warning'
|
||||
}
|
||||
>
|
||||
<span className="flex-center">
|
||||
<AutoTooltip ghost maxWidth={'120px'}>
|
||||
{model.name}
|
||||
</AutoTooltip>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
{categoryConfig[model.category]?.icon}
|
||||
</span>
|
||||
</span>
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
styles={{
|
||||
root: { maxWidth: '400px' },
|
||||
container: {
|
||||
paddingInlineEnd: 4
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tag
|
||||
variant="outlined"
|
||||
styles={{
|
||||
root: {
|
||||
width: 'fit-content',
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4
|
||||
}
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(
|
||||
{ id: 'providers.form.more' },
|
||||
{ count: restItems.length }
|
||||
)}
|
||||
</Tag>
|
||||
</Popover>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface FormData {
|
||||
api_key: string;
|
||||
config: {
|
||||
type: maasProviderType;
|
||||
openaiCustomUrl?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,10 +42,7 @@ const AccessToken = () => {
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
message: getRuleMessage(
|
||||
'input',
|
||||
intl.formatMessage({ id: 'providers.form.tokens.title' })
|
||||
)
|
||||
message: getRuleMessage('input', 'providers.form.tokens.title')
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -4,12 +4,15 @@ import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import ProviderLogo from '../components/provider-logo';
|
||||
import { maasProviderOptions } from '../config/providers';
|
||||
import { maasProviderOptions, ProviderEnum } from '../config/providers';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const Basic = () => {
|
||||
const Basic: React.FC<{
|
||||
onAPIKeyBlur?: (e: any) => void;
|
||||
}> = ({ onAPIKeyBlur }) => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const providerType = Form.useWatch(['config', 'type'], form);
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
|
||||
const optionRender = (option: any) => {
|
||||
@@ -30,7 +33,17 @@ const Basic = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData> name="name" data-field="name">
|
||||
<Form.Item<FormData>
|
||||
name="name"
|
||||
data-field="name"
|
||||
required
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'common.table.name')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
required
|
||||
label={intl.formatMessage({
|
||||
@@ -43,10 +56,7 @@ const Basic = () => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage(
|
||||
'select',
|
||||
intl.formatMessage({ id: 'common.table.type' })
|
||||
)
|
||||
message: getRuleMessage('select', 'common.table.type')
|
||||
}
|
||||
]}
|
||||
>
|
||||
@@ -63,6 +73,16 @@ const Basic = () => {
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
{providerType === ProviderEnum.OPENAI && (
|
||||
<Form.Item<FormData> name={['config', 'openaiCustomUrl']}>
|
||||
<SealInput.Input
|
||||
placeholder="http://<your-inference-server>/v1"
|
||||
label={intl.formatMessage({
|
||||
id: 'providers.form.custombeckendUrl'
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item<FormData>
|
||||
name="api_key"
|
||||
rules={[
|
||||
@@ -74,6 +94,7 @@ const Basic = () => {
|
||||
>
|
||||
<SealInput.Password
|
||||
required
|
||||
onBlur={onAPIKeyBlur}
|
||||
label={intl.formatMessage({
|
||||
id: 'providers.form.tokens.title'
|
||||
})}
|
||||
|
||||
@@ -107,7 +107,9 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
api_key: currentData.api_tokens?.[0] || '',
|
||||
api_tokens: currentData.api_tokens?.slice(1) || [],
|
||||
proxy_enabled: !!currentData.proxy_url,
|
||||
custom_config: json2Yaml(_.omit(currentData.config, ['type']) || {})
|
||||
custom_config: json2Yaml(
|
||||
_.omit(currentData.config, ['type', 'openaiCustomUrl']) || {}
|
||||
)
|
||||
});
|
||||
}
|
||||
}, [form, currentData, action]);
|
||||
|
||||
@@ -3,8 +3,8 @@ import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { categoryOptions } from '@/pages/llmodels/config';
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CloseCircleFilled,
|
||||
LoadingOutlined
|
||||
LoadingOutlined,
|
||||
WarningFilled
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Form, Tooltip } from 'antd';
|
||||
@@ -27,9 +27,15 @@ const SelectWrapper = styled.div`
|
||||
`;
|
||||
|
||||
interface ModelItemProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onChange: (data: ProviderModel) => void;
|
||||
providerModelList: Global.BaseOption<string>[];
|
||||
providerModelList: Global.BaseOption<
|
||||
string,
|
||||
{
|
||||
category: string;
|
||||
accessible: boolean;
|
||||
}
|
||||
>[];
|
||||
selectedModelList: ProviderModel[];
|
||||
item: ProviderModel;
|
||||
loading?: boolean;
|
||||
@@ -63,10 +69,9 @@ const ModelItem: React.FC<ModelItemProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnChange = (value: string) => {
|
||||
const handleOnChange = (value: string, option: any) => {
|
||||
onChange({
|
||||
...item,
|
||||
accessible: null,
|
||||
...option,
|
||||
name: value
|
||||
});
|
||||
};
|
||||
@@ -79,20 +84,26 @@ const ModelItem: React.FC<ModelItemProps> = ({
|
||||
};
|
||||
|
||||
const renderSuffixIcon = () => {
|
||||
console.log(
|
||||
'testLoading',
|
||||
testLoading,
|
||||
item.accessible,
|
||||
item.accessible === false
|
||||
);
|
||||
if (testLoading) {
|
||||
return <LoadingOutlined />;
|
||||
}
|
||||
if (item.accessible === true) {
|
||||
return (
|
||||
<CheckCircleFilled
|
||||
style={{ color: 'var(--ant-color-success)', fontSize: 16 }}
|
||||
style={{ color: 'var(--ant-color-success)', fontSize: 14 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (item.accessible === false) {
|
||||
return (
|
||||
<CloseCircleFilled
|
||||
style={{ color: 'var(--ant-color-error)', fontSize: 16 }}
|
||||
<WarningFilled
|
||||
style={{ color: 'var(--ant-color-error)', fontSize: 14 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import MetadataList from '@/components/metadata-list';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
import { FormData, ProviderModel } from '../config/types';
|
||||
import { useQueryProviderModels } from '../hooks/use-query-provider-models';
|
||||
import ModelItem from './model-item';
|
||||
@@ -11,15 +13,26 @@ const SupportedModels = () => {
|
||||
useQueryProviderModels();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const modelList = Form.useWatch('models', form) || [];
|
||||
const prevAPIKeyRef = useRef<string>('');
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
|
||||
const handleOpenChange = async (open: boolean) => {
|
||||
try {
|
||||
await form.validateFields(['api_key']);
|
||||
|
||||
if (open && providerModelList.length === 0) {
|
||||
const currentAPIKey = form.getFieldValue('api_key') || '';
|
||||
|
||||
// Avoid repeated requests with the same API key
|
||||
if (
|
||||
open &&
|
||||
providerModelList.length === 0 &&
|
||||
prevAPIKeyRef.current !== currentAPIKey &&
|
||||
currentAPIKey
|
||||
) {
|
||||
prevAPIKeyRef.current = currentAPIKey;
|
||||
fetchProviderModels({
|
||||
data: {
|
||||
api_token: form.getFieldValue('api_key') || '',
|
||||
api_token: currentAPIKey,
|
||||
config: {
|
||||
type: form.getFieldValue(['config', 'type']) || ''
|
||||
}
|
||||
@@ -55,7 +68,33 @@ const SupportedModels = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="models" data-field="supportedModels">
|
||||
<Form.Item
|
||||
name="models"
|
||||
data-field="supportedModels"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validator: async (_, value) => {
|
||||
if (!value || value.length === 0) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
getRuleMessage('input', 'providers.form.rules.models')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (value.some((item: ProviderModel) => !item.name)) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
getRuleMessage('select', 'providers.form.rules.model')
|
||||
)
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<MetadataList
|
||||
styles={{
|
||||
wrapper: {
|
||||
|
||||
@@ -33,7 +33,9 @@ export const useQueryProviderModels = () => {
|
||||
setProviderModelList(
|
||||
response.data?.map((item: any) => ({
|
||||
label: item.id,
|
||||
value: item.id
|
||||
value: item.id,
|
||||
accessible: item.accessible,
|
||||
category: item.categories?.[0] || ''
|
||||
})) || []
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user