fix: ui issues

This commit is contained in:
jialin
2026-02-06 11:13:49 +08:00
parent d54722609c
commit 7f3d6bb55f
35 changed files with 364 additions and 141 deletions
@@ -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>
);
};
+1
View File
@@ -12,6 +12,7 @@ export interface FormData {
api_key: string;
config: {
type: maasProviderType;
openaiCustomUrl?: string;
[key: string]: any;
};
}
+1 -4
View File
@@ -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')
}
]}
>
+28 -7
View File
@@ -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'
})}
+3 -1
View File
@@ -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]);
+21 -10
View File
@@ -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] || ''
})) || []
);
},