Refactor: Replace antd with shadcn (#12718)

### What problem does this PR solve?

Refactor: Replace antd with shadcn
### Type of change

- [x] Refactoring
This commit is contained in:
balibabu
2026-01-20 13:38:54 +08:00
committed by GitHub
parent 927db0b373
commit 80612bc992
12 changed files with 61 additions and 1156 deletions

View File

@ -11,8 +11,8 @@ import {
} from '@/hooks/use-user-setting-request';
import { IStats } from '@/interfaces/database/chat';
import { useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import { useCallback } from 'react';
import message from '../ui/message';
export const useOperateApiKey = (idKey: string, dialogId?: string) => {
const { removeToken } = useRemoveSystemToken();

View File

@ -1,8 +1,8 @@
import { useTranslate } from '@/hooks/common-hooks';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { useState } from 'react';
import { CopyToClipboard as Clipboard, Props } from 'react-copy-to-clipboard';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
const CopyToClipboard = ({ text }: Props) => {
const [copied, setCopied] = useState(false);
@ -16,10 +16,13 @@ const CopyToClipboard = ({ text }: Props) => {
};
return (
<Tooltip title={copied ? t('copied') : t('copy')}>
<Clipboard text={text} onCopy={handleCopy}>
{copied ? <CheckOutlined /> : <CopyOutlined />}
</Clipboard>
<Tooltip>
<TooltipTrigger>
<Clipboard text={text} onCopy={handleCopy}>
{copied ? <CheckOutlined /> : <CopyOutlined />}
</Clipboard>
</TooltipTrigger>
<TooltipContent>{copied ? t('copied') : t('copy')}</TooltipContent>
</Tooltip>
);
};

View File

@ -1,98 +0,0 @@
import { Form, FormInstance, Input, InputRef, Typography } from 'antd';
import { omit } from 'lodash';
import React, { useContext, useEffect, useRef, useState } from 'react';
const EditableContext = React.createContext<FormInstance<any> | null>(null);
const { Text } = Typography;
interface EditableRowProps {
index: number;
}
interface Item {
key: string;
name: string;
age: string;
address: string;
}
export const EditableRow: React.FC<EditableRowProps> = ({ ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...omit(props, 'index')} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
export const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0, minWidth: 70 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div onClick={toggleEdit} className="editable-cell-value-wrap">
<Text>{children}</Text>
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};

View File

@ -15,12 +15,12 @@ import {
} from '@/utils/chat';
import { getExtension } from '@/utils/document-util';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, Tooltip } from 'antd';
import classNames from 'classnames';
import DOMPurify from 'dompurify';
import 'katex/dist/katex.min.css';
import { omit } from 'lodash';
import { pipe } from 'lodash/fp';
import { Info } from 'lucide-react';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import Markdown from 'react-markdown';
@ -37,6 +37,9 @@ import remarkMath from 'remark-math';
import { visitParents } from 'unist-util-visit-parents';
import styles from './floating-chat-widget-markdown.module.less';
import { useIsDarkTheme } from './theme-provider';
import { Button } from './ui/button';
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
const getChunkIndex = (match: string) => Number(match.replace(/\[|\]/g, ''));
@ -161,19 +164,19 @@ const FloatingChatWidgetMarkdown = ({
className="flex gap-2 widget-citation-content"
>
{imageId && (
<Popover
placement="left"
content={
<Popover>
<TooltipTrigger asChild>
<Image
id={imageId}
className="w-24 h-24 object-contain rounded m-1 cursor-pointer"
/>
</TooltipTrigger>
<TooltipContent side="left">
<Image
id={imageId}
className="max-w-[80vw] max-h-[60vh] rounded"
/>
}
>
<Image
id={imageId}
className="w-24 h-24 object-contain rounded m-1 cursor-pointer"
/>
</TooltipContent>
</Popover>
)}
<div className="space-y-2 flex-1 min-w-0">
@ -184,7 +187,7 @@ const FloatingChatWidgetMarkdown = ({
className="max-h-[250px] overflow-y-auto text-xs leading-relaxed p-2 bg-gray-50 dark:bg-gray-800 rounded prose-sm"
></div>
{documentId && (
<Flex gap={'small'} align="center">
<section className="flex gap-1 justify-center">
{fileThumbnail ? (
<img
src={fileThumbnail}
@ -194,32 +197,33 @@ const FloatingChatWidgetMarkdown = ({
) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)}
<Tooltip
title={
!documentUrl && fileExtension !== 'pdf'
<Tooltip>
<TooltipTrigger asChild>
<Button
size={'sm'}
variant={'link'}
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">
{document?.doc_name ?? 'Unnamed Document'}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{!documentUrl && fileExtension !== 'pdf'
? 'Document link unavailable'
: document.doc_name
}
>
<Button
type="link"
size="small"
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">
{document?.doc_name ?? 'Unnamed Document'}
</span>
</Button>
: document.doc_name}
</TooltipContent>
</Tooltip>
</Flex>
</section>
)}
</div>
</div>
@ -236,8 +240,11 @@ const FloatingChatWidgetMarkdown = ({
if (!info) {
return (
<Tooltip key={`err-tooltip-${i}`} title="Reference unavailable">
<InfoCircleOutlined className={styles.referenceIcon} />
<Tooltip key={`err-tooltip-${i}`}>
<TooltipTrigger asChild>
<Info className={styles.referenceIcon} />
</TooltipTrigger>
<TooltipContent>Reference unavailable</TooltipContent>
</Tooltip>
);
}
@ -262,8 +269,11 @@ const FloatingChatWidgetMarkdown = ({
}
return (
<Popover content={getPopoverContent(chunkIndex)} key={`popover-${i}`}>
<InfoCircleOutlined className={styles.referenceIcon} />
<Popover key={`popover-${i}`}>
<PopoverTrigger asChild>
<InfoCircleOutlined className={styles.referenceIcon} />
</PopoverTrigger>
<PopoverContent>{getPopoverContent(chunkIndex)}</PopoverContent>
</Popover>
);
});

View File

@ -1,30 +0,0 @@
import { useTranslation } from 'react-i18next';
import IndentedTree from './indented-tree';
import { useFetchKnowledgeGraph } from '@/hooks/use-knowledge-request';
import { IModalProps } from '@/interfaces/common';
import { Modal } from 'antd';
const IndentedTreeModal = ({
visible,
hideModal,
}: IModalProps<any> & { documentId: string }) => {
const { data } = useFetchKnowledgeGraph();
const { t } = useTranslation();
return (
<Modal
title={t('chunk.mind')}
open={visible}
onCancel={hideModal}
width={'90vw'}
footer={null}
>
<section>
<IndentedTree data={data?.mind_map} show></IndentedTree>
</section>
</Modal>
);
};
export default IndentedTreeModal;

View File

@ -1,67 +0,0 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { Popover as AntPopover, Select as AntSelect } from 'antd';
import LlmSettingItems from '../llm-setting-items';
interface IProps {
id?: string;
value?: string;
onInitialValue?: (value: string, option: any) => void;
onChange?: (value: string, option: any) => void;
disabled?: boolean;
}
const LLMSelect = ({
id,
value,
onInitialValue,
onChange,
disabled,
}: IProps) => {
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
if (onInitialValue && value) {
for (const modelOption of modelOptions) {
for (const option of modelOption.options) {
if (option.value === value) {
onInitialValue(value, option);
break;
}
}
}
}
const content = (
<div style={{ width: 400 }}>
<LlmSettingItems
onChange={onChange}
formItemLayout={{ labelCol: { span: 10 }, wrapperCol: { span: 14 } }}
></LlmSettingItems>
</div>
);
return (
<AntPopover
content={content}
trigger="click"
placement="left"
arrow={false}
destroyTooltipOnHide
>
<AntSelect
options={modelOptions}
style={{ width: '100%' }}
dropdownStyle={{ display: 'none' }}
id={id}
value={value}
onChange={onChange}
disabled={disabled}
/>
</AntPopover>
);
};
export default LLMSelect;

View File

@ -1,350 +0,0 @@
import {
LlmModelType,
ModelVariableType,
settledModelVariableMap,
} from '@/constants/knowledge';
import { Flex, Form, InputNumber, Select, Slider, Switch, Tooltip } from 'antd';
import camelCase from 'lodash/camelCase';
import { useTranslate } from '@/hooks/common-hooks';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { setChatVariableEnabledFieldValuePage } from '@/utils/chat';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { useCallback, useMemo } from 'react';
import styles from './index.module.less';
interface IProps {
prefix?: string;
formItemLayout?: any;
handleParametersChange?(value: ModelVariableType): void;
onChange?(value: string, option: any): void;
}
const LlmSettingItems = ({ prefix, formItemLayout = {}, onChange }: IProps) => {
const form = Form.useFormInstance();
const { t } = useTranslate('chat');
const parameterOptions = Object.values(ModelVariableType).map((x) => ({
label: t(camelCase(x)),
value: x,
}));
const handleParametersChange = useCallback(
(value: ModelVariableType) => {
const variable = settledModelVariableMap[value];
let nextVariable: Record<string, any> = variable;
if (prefix) {
nextVariable = { [prefix]: variable };
}
const variableCheckBoxFieldMap = setChatVariableEnabledFieldValuePage();
form.setFieldsValue({ ...nextVariable, ...variableCheckBoxFieldMap });
},
[form, prefix],
);
const memorizedPrefix = useMemo(() => (prefix ? [prefix] : []), [prefix]);
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
return (
<>
<Form.Item
label={t('model')}
name="llm_id"
tooltip={t('modelTip')}
{...formItemLayout}
rules={[{ required: true, message: t('modelMessage') }]}
>
<Select
options={modelOptions}
showSearch
popupMatchSelectWidth={false}
onChange={onChange}
/>
</Form.Item>
<div className="border rounded-md">
<div className="flex justify-between bg-slate-100 p-2 mb-2">
<div className="space-x-1 items-center">
<span className="text-lg font-semibold">{t('freedom')}</span>
<Tooltip title={t('freedomTip')}>
<QuestionCircleOutlined></QuestionCircleOutlined>
</Tooltip>
</div>
<div className="w-1/4 min-w-32">
<Form.Item
label={t('freedom')}
name="parameter"
tooltip={t('freedomTip')}
initialValue={ModelVariableType.Precise}
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
className="m-0"
>
<Select<ModelVariableType>
options={parameterOptions}
onChange={handleParametersChange}
/>
</Form.Item>
</div>
</div>
<div className="pr-2">
<Form.Item
label={t('temperature')}
tooltip={t('temperatureTip')}
{...formItemLayout}
>
<Flex gap={20} align="center">
<Form.Item
name={'temperatureEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Form.Item
noStyle
dependencies={['temperatureEnabled']}
shouldUpdate
>
{({ getFieldValue }) => {
const disabled = !getFieldValue('temperatureEnabled');
return (
<>
<Flex flex={1}>
<Form.Item
name={[...memorizedPrefix, 'temperature']}
noStyle
>
<Slider
className={styles.variableSlider}
max={1}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</Flex>
<Form.Item
name={[...memorizedPrefix, 'temperature']}
noStyle
>
<InputNumber
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</>
);
}}
</Form.Item>
</Flex>
</Form.Item>
<Form.Item
label={t('topP')}
tooltip={t('topPTip')}
{...formItemLayout}
>
<Flex gap={20} align="center">
<Form.Item name={'topPEnabled'} valuePropName="checked" noStyle>
<Switch size="small" />
</Form.Item>
<Form.Item noStyle dependencies={['topPEnabled']} shouldUpdate>
{({ getFieldValue }) => {
const disabled = !getFieldValue('topPEnabled');
return (
<>
<Flex flex={1}>
<Form.Item name={[...memorizedPrefix, 'top_p']} noStyle>
<Slider
className={styles.variableSlider}
max={1}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</Flex>
<Form.Item name={[...memorizedPrefix, 'top_p']} noStyle>
<InputNumber
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</>
);
}}
</Form.Item>
</Flex>
</Form.Item>
<Form.Item
label={t('presencePenalty')}
tooltip={t('presencePenaltyTip')}
{...formItemLayout}
>
<Flex gap={20} align="center">
<Form.Item
name={'presencePenaltyEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Form.Item
noStyle
dependencies={['presencePenaltyEnabled']}
shouldUpdate
>
{({ getFieldValue }) => {
const disabled = !getFieldValue('presencePenaltyEnabled');
return (
<>
<Flex flex={1}>
<Form.Item
name={[...memorizedPrefix, 'presence_penalty']}
noStyle
>
<Slider
className={styles.variableSlider}
max={1}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</Flex>
<Form.Item
name={[...memorizedPrefix, 'presence_penalty']}
noStyle
>
<InputNumber
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</>
);
}}
</Form.Item>
</Flex>
</Form.Item>
<Form.Item
label={t('frequencyPenalty')}
tooltip={t('frequencyPenaltyTip')}
{...formItemLayout}
>
<Flex gap={20} align="center">
<Form.Item
name={'frequencyPenaltyEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Form.Item
noStyle
dependencies={['frequencyPenaltyEnabled']}
shouldUpdate
>
{({ getFieldValue }) => {
const disabled = !getFieldValue('frequencyPenaltyEnabled');
return (
<>
<Flex flex={1}>
<Form.Item
name={[...memorizedPrefix, 'frequency_penalty']}
noStyle
>
<Slider
className={styles.variableSlider}
max={1}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</Flex>
<Form.Item
name={[...memorizedPrefix, 'frequency_penalty']}
noStyle
>
<InputNumber
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
disabled={disabled}
/>
</Form.Item>
</>
);
}}
</Form.Item>
</Flex>
</Form.Item>
<Form.Item
label={t('maxTokens')}
tooltip={t('maxTokensTip')}
{...formItemLayout}
>
<Flex gap={20} align="center">
<Form.Item
name={'maxTokensEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Form.Item
noStyle
dependencies={['maxTokensEnabled']}
shouldUpdate
>
{({ getFieldValue }) => {
const disabled = !getFieldValue('maxTokensEnabled');
return (
<>
<Flex flex={1}>
<Form.Item
name={[...memorizedPrefix, 'max_tokens']}
noStyle
>
<Slider
className={styles.variableSlider}
max={128000}
disabled={disabled}
/>
</Form.Item>
</Flex>
<Form.Item
name={[...memorizedPrefix, 'max_tokens']}
noStyle
>
<InputNumber
disabled={disabled}
className={styles.sliderInputNumber}
max={128000}
min={0}
/>
</Form.Item>
</>
);
}}
</Form.Item>
</Flex>
</Form.Item>
</div>
</div>
</>
);
};
export default LlmSettingItems;

View File

@ -1,11 +1,10 @@
import { IconMap, LLMFactory } from '@/constants/llm';
import { cn } from '@/lib/utils';
import Icon, { UserOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons';
import { IconComponentProps } from '@ant-design/icons/lib/components/Icon';
import { Avatar } from 'antd';
import { AvatarSize } from 'antd/es/avatar/AvatarContext';
import { memo, useMemo } from 'react';
import { IconFontFill } from './icon-font';
import { RAGFlowAvatar } from './ragflow-avatar';
import { useIsDarkTheme } from './theme-provider';
// const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
@ -91,13 +90,11 @@ export const LlmIcon = ({
name,
height = 48,
width = 48,
size = 'large',
imgClass,
}: {
name: string;
height?: number;
width?: number;
size?: AvatarSize;
imgClass?: string;
}) => {
const isDark = useIsDarkTheme();
@ -134,7 +131,6 @@ export const LlmIcon = ({
name={'moxing-default'}
className={cn('size-8 flex items-center justify-center', imgClass)}
/>
// <Avatar shape="square" size={size} icon={<UserOutlined />} />
);
};
@ -142,13 +138,11 @@ export const HomeIcon = ({
name,
height = '32',
width = '32',
size = 'large',
imgClass,
}: {
name: string;
height?: string;
width?: string;
size?: AvatarSize;
imgClass?: string;
}) => {
const isDark = useIsDarkTheme();
@ -162,7 +156,7 @@ export const HomeIcon = ({
imgClass={imgClass}
></SvgIcon>
) : (
<Avatar shape="square" size={size} icon={<UserOutlined />} />
<RAGFlowAvatar avatar={'user'}></RAGFlowAvatar>
);
};