Fix: Fixed the styling and logic issues on the model provider page #10703 (#10909)

### What problem does this PR solve?

Fix: Fixed the styling and logic issues on the model provider page

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-10-31 13:42:28 +08:00
committed by GitHub
parent c8a82da722
commit d8a7fb6f2b
24 changed files with 265 additions and 175 deletions

View File

@ -1,129 +0,0 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Modal } from 'antd';
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
import { ApiKeyPostBody } from '../../interface';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
base_url?: string;
group_id?: string;
};
const modelsWithBaseUrl = [
LLMFactory.OpenAI,
LLMFactory.AzureOpenAI,
LLMFactory.TongYiQianWen,
];
const ApiKeyModal = ({
visible,
hideModal,
llmFactory,
loading,
initialValue,
editMode = false,
onOk,
}: IProps) => {
const [form] = Form.useForm();
const { t } = useTranslate('setting');
const handleOk = useCallback(async () => {
const ret = await form.validateFields();
return onOk(ret);
}, [form, onOk]);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
async (e) => {
if (e.key === 'Enter') {
await handleOk();
}
},
[handleOk],
);
useEffect(() => {
if (visible) {
form.setFieldValue('api_key', initialValue);
}
}, [initialValue, form, visible]);
return (
<Modal
title={editMode ? t('editModel') : t('modify')}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form
name="basic"
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
style={{ maxWidth: 600 }}
autoComplete="off"
form={form}
>
<Form.Item<FieldType>
label={t('apiKey')}
name="api_key"
tooltip={t('apiKeyTip')}
rules={[{ required: true, message: t('apiKeyMessage') }]}
>
<Input onKeyDown={handleKeyDown} />
</Form.Item>
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
<Form.Item<FieldType>
label={t('baseUrl')}
name="base_url"
tooltip={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlTip')
: t('baseUrlTip')
}
>
<Input
placeholder={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlPlaceholder')
: 'https://api.openai.com/v1'
}
onKeyDown={handleKeyDown}
/>
</Form.Item>
)}
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
<Form.Item<FieldType>
label={t('baseUrl')}
name="base_url"
tooltip={t('baseUrlTip')}
>
<Input
placeholder="https://api.anthropic.com/v1"
onKeyDown={handleKeyDown}
/>
</Form.Item>
)}
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
<Form.Item<FieldType> label={'Group ID'} name="group_id">
<Input />
</Form.Item>
)}
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -1,6 +1,7 @@
// src/components/ModelProviderCard.tsx
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { LlmItem } from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
@ -8,7 +9,7 @@ import { EditOutlined, SettingOutlined } from '@ant-design/icons';
import { ChevronsDown, ChevronsUp, Trash2 } from 'lucide-react';
import { FC } from 'react';
import { isLocalLlmFactory } from '../../utils';
import { useHandleDeleteFactory, useHandleDeleteLlm } from '../hooks';
import { useHandleDeleteFactory, useHandleEnableLlm } from '../hooks';
interface IModelCardProps {
item: LlmItem;
@ -52,7 +53,7 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
}) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleEnableLlm } = useHandleEnableLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
@ -66,7 +67,7 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
return (
<div className={`w-full rounded-lg border border-border-default`}>
{/* Header */}
<div className="flex items-center justify-between p-4 cursor-pointer transition-colors">
<div className="flex h-16 items-center justify-between p-4 cursor-pointer transition-colors">
<div className="flex items-center space-x-3">
<LlmIcon name={item.name} />
<div>
@ -151,16 +152,12 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
<EditOutlined />
</Button>
)}
<Button
variant={'secondary'}
onClick={() => {
handleDeleteLlm(model.name);
console.log(handleDeleteLlm, model.name);
<Switch
checked={model.status === '1'}
onCheckedChange={(value) => {
handleEnableLlm(model.name, value);
}}
className="p-1 hover:text-state-error transition-colors"
>
<Trash2 />
</Button>
/>
</div>
</div>
))}

View File

@ -10,8 +10,8 @@ export const UsedModel = ({
}) => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
return (
<div className="flex flex-col w-full">
<div className="text-text-primary text-2xl mb-4 mt-4">Added models</div>
<div className="flex flex-col w-full gap-4 mb-4">
<div className="text-text-primary text-2xl mb-2 mt-4">Added models</div>
{llmList.map((llm) => {
return (
<ModelProviderCard

View File

@ -5,6 +5,7 @@ import {
useAddLlm,
useDeleteFactory,
useDeleteLlm,
useEnableLlm,
useSaveApiKey,
useSaveTenantInfo,
useSelectLlmOptionsByModelType,
@ -421,7 +422,7 @@ export const useHandleDeleteLlm = (llmFactory: string) => {
const { deleteLlm } = useDeleteLlm();
const showDeleteConfirm = useShowDeleteConfirm();
const handleDeleteLlm = (name: string) => () => {
const handleDeleteLlm = (name: string) => {
showDeleteConfirm({
onOk: async () => {
deleteLlm({ llm_factory: llmFactory, llm_name: name });
@ -432,6 +433,16 @@ export const useHandleDeleteLlm = (llmFactory: string) => {
return { handleDeleteLlm };
};
export const useHandleEnableLlm = (llmFactory: string) => {
const { enableLlm } = useEnableLlm();
const handleEnableLlm = (name: string, enable: boolean) => {
enableLlm({ llm_factory: llmFactory, llm_name: name, enable });
};
return { handleEnableLlm };
};
export const useHandleDeleteFactory = (llmFactory: string) => {
const { deleteFactory } = useDeleteFactory();
const showDeleteConfirm = useShowDeleteConfirm();

View File

@ -2,14 +2,9 @@ import { LLMFactory } from '@/constants/llm';
import { LlmItem, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks';
import { useCallback, useMemo } from 'react';
import { isLocalLlmFactory } from '../utils';
import ApiKeyModal from './api-key-modal';
import AzureOpenAIModal from './azure-openai-modal';
import BedrockModal from './bedrock-modal';
import SystemSetting from './components/system-setting';
import { AvailableModels } from './components/un-add-model';
import { UsedModel } from './components/used-model';
import FishAudioModal from './fish-audio-modal';
import GoogleModal from './google-modal';
import {
useSubmitApiKey,
useSubmitAzure,
@ -24,12 +19,17 @@ import {
useSubmitVolcEngine,
useSubmityiyan,
} from './hooks';
import HunyuanModal from './hunyuan-modal';
import TencentCloudModal from './next-tencent-modal';
import OllamaModal from './ollama-modal';
import SparkModal from './spark-modal';
import VolcEngineModal from './volcengine-modal';
import YiyanModal from './yiyan-modal';
import ApiKeyModal from './modal/api-key-modal';
import AzureOpenAIModal from './modal/azure-openai-modal';
import BedrockModal from './modal/bedrock-modal';
import FishAudioModal from './modal/fish-audio-modal';
import GoogleModal from './modal/google-modal';
import HunyuanModal from './modal/hunyuan-modal';
import TencentCloudModal from './modal/next-tencent-modal';
import OllamaModal from './modal/ollama-modal';
import SparkModal from './modal/spark-modal';
import VolcEngineModal from './modal/volcengine-modal';
import YiyanModal from './modal/yiyan-modal';
const ModelProviders = () => {
const { saveSystemModelSettingLoading, onSystemSettingSavingOk } =
useSubmitSystemModelSetting();

View File

@ -34,11 +34,6 @@ import { CircleHelp } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import SettingTitle from '../components/setting-title';
import { isLocalLlmFactory } from '../utils';
import ApiKeyModal from './api-key-modal';
import AzureOpenAIModal from './azure-openai-modal';
import BedrockModal from './bedrock-modal';
import FishAudioModal from './fish-audio-modal';
import GoogleModal from './google-modal';
import {
useHandleDeleteFactory,
useHandleDeleteLlm,
@ -55,14 +50,19 @@ import {
useSubmitVolcEngine,
useSubmityiyan,
} from './hooks';
import HunyuanModal from './hunyuan-modal';
import styles from './index.less';
import TencentCloudModal from './next-tencent-modal';
import OllamaModal from './ollama-modal';
import SparkModal from './spark-modal';
import SystemModelSettingModal from './system-model-setting-modal';
import VolcEngineModal from './volcengine-modal';
import YiyanModal from './yiyan-modal';
import ApiKeyModal from './modal/api-key-modal';
import AzureOpenAIModal from './modal/azure-openai-modal';
import BedrockModal from './modal/bedrock-modal';
import FishAudioModal from './modal/fish-audio-modal';
import GoogleModal from './modal/google-modal';
import HunyuanModal from './modal/hunyuan-modal';
import TencentCloudModal from './modal/next-tencent-modal';
import OllamaModal from './modal/ollama-modal';
import SparkModal from './modal/spark-modal';
import SystemModelSettingModal from './modal/system-model-setting-modal';
import VolcEngineModal from './modal/volcengine-modal';
import YiyanModal from './modal/yiyan-modal';
const { Text } = Typography;
interface IModelCardProps {

View File

@ -0,0 +1,174 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal/modal';
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { ApiKeyPostBody } from '../../../interface';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
base_url?: string;
group_id?: string;
};
const modelsWithBaseUrl = [
LLMFactory.OpenAI,
LLMFactory.AzureOpenAI,
LLMFactory.TongYiQianWen,
];
const ApiKeyModal = ({
visible,
hideModal,
llmFactory,
loading,
initialValue,
editMode = false,
onOk,
}: IProps) => {
const form = useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = useCallback(async () => {
await form.handleSubmit((values) => onOk(values))();
}, [form, onOk]);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
async (e) => {
if (e.key === 'Enter') {
await handleOk();
}
},
[handleOk],
);
useEffect(() => {
if (visible) {
form.setValue('api_key', initialValue);
}
}, [initialValue, form, visible]);
return (
<Modal
title={editMode ? t('editModel') : t('modify')}
open={visible}
onOpenChange={(open) => !open && hideModal()}
onOk={handleOk}
onCancel={hideModal}
confirmLoading={loading}
okText={t('save')}
cancelText={t('cancel')}
>
<Form {...form}>
<div className="space-y-4 py-4">
<FormField
name="api_key"
rules={{ required: t('apiKeyMessage') }}
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('apiKey')}
<span className="ml-1 text-destructive">*</span>
</FormLabel>
<FormControl>
<Input
{...field}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
<FormField
name="base_url"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('baseUrl')}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlPlaceholder')
: 'https://api.openai.com/v1'
}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
<FormField
name="base_url"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('baseUrl')}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder="https://api.anthropic.com/v1"
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
<FormField
name="group_id"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
Group ID
</FormLabel>
<FormControl>
<Input {...field} className="w-full" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import { useMemo } from 'react';
import { BedrockRegionList } from '../constant';
import { BedrockRegionList } from '../../constant';
type FieldType = IAddLlmRequestBody & {
bedrock_ak: string;

View File

@ -7,7 +7,7 @@ import {
} from '@/hooks/llm-hooks';
import { Form, Modal, Select } from 'antd';
import { useEffect } from 'react';
import { useFetchSystemModelSettingOnMount } from '../hooks';
import { useFetchSystemModelSettingOnMount } from '../../hooks';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;