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>
);
};

View File

@ -1,37 +0,0 @@
import { Divider, Layout, theme } from 'antd';
import React from 'react';
import { Outlet } from 'react-router';
import '../locales/config';
import Header from './components/header';
import styles from './index.module.less';
const { Content } = Layout;
const App: React.FC = () => {
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout className={styles.layout}>
<Layout>
<Header></Header>
<Divider orientationMargin={0} className={styles.divider} />
<Content
style={{
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
overflow: 'auto',
display: 'flex',
}}
>
<Outlet />
</Content>
</Layout>
</Layout>
);
};
export default App;

View File

@ -1,223 +0,0 @@
import FilterIcon from '@/assets/filter.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
IChunkListResult,
useSelectChunkList,
} from '@/hooks/use-chunk-request';
import { useKnowledgeBaseId } from '@/hooks/use-knowledge-request';
import {
ArrowLeftOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
DeleteOutlined,
DownOutlined,
FilePdfOutlined,
PlusOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {
Button,
Checkbox,
Flex,
Input,
Menu,
MenuProps,
Popover,
Radio,
RadioChangeEvent,
Segmented,
SegmentedProps,
Space,
Typography,
} from 'antd';
import { useCallback, useMemo, useState } from 'react';
import { Link } from 'react-router';
import { ChunkTextMode } from '../../constant';
const { Text } = Typography;
interface IProps extends Pick<
IChunkListResult,
'searchString' | 'handleInputChange' | 'available' | 'handleSetAvailable'
> {
checked: boolean;
selectAllChunk: (checked: boolean) => void;
createChunk: () => void;
removeChunk: () => void;
switchChunk: (available: number) => void;
changeChunkTextMode(mode: ChunkTextMode): void;
}
const ChunkToolBar = ({
selectAllChunk,
checked,
createChunk,
removeChunk,
switchChunk,
changeChunkTextMode,
available,
handleSetAvailable,
searchString,
handleInputChange,
}: IProps) => {
const data = useSelectChunkList();
const documentInfo = data?.documentInfo;
const knowledgeBaseId = useKnowledgeBaseId();
const [isShowSearchBox, setIsShowSearchBox] = useState(false);
const { t } = useTranslate('chunk');
const handleSelectAllCheck = useCallback(
(e: any) => {
selectAllChunk(e.target.checked);
},
[selectAllChunk],
);
const handleSearchIconClick = () => {
setIsShowSearchBox(true);
};
const handleSearchBlur = () => {
if (!searchString?.trim()) {
setIsShowSearchBox(false);
}
};
const handleDelete = useCallback(() => {
removeChunk();
}, [removeChunk]);
const handleEnabledClick = useCallback(() => {
switchChunk(1);
}, [switchChunk]);
const handleDisabledClick = useCallback(() => {
switchChunk(0);
}, [switchChunk]);
const items: MenuProps['items'] = useMemo(() => {
return [
{
key: '1',
label: (
<>
<Checkbox onChange={handleSelectAllCheck} checked={checked}>
<b>{t('selectAll')}</b>
</Checkbox>
</>
),
},
{ type: 'divider' },
{
key: '2',
label: (
<Space onClick={handleEnabledClick}>
<CheckCircleOutlined />
<b>{t('enabledSelected')}</b>
</Space>
),
},
{
key: '3',
label: (
<Space onClick={handleDisabledClick}>
<CloseCircleOutlined />
<b>{t('disabledSelected')}</b>
</Space>
),
},
{ type: 'divider' },
{
key: '4',
label: (
<Space onClick={handleDelete}>
<DeleteOutlined />
<b>{t('deleteSelected')}</b>
</Space>
),
},
];
}, [
checked,
handleSelectAllCheck,
handleDelete,
handleEnabledClick,
handleDisabledClick,
t,
]);
const content = (
<Menu style={{ width: 200 }} items={items} selectable={false} />
);
const handleFilterChange = (e: RadioChangeEvent) => {
selectAllChunk(false);
handleSetAvailable(e.target.value);
};
const filterContent = (
<Radio.Group onChange={handleFilterChange} value={available}>
<Space direction="vertical">
<Radio value={undefined}>{t('all')}</Radio>
<Radio value={1}>{t('enabled')}</Radio>
<Radio value={0}>{t('disabled')}</Radio>
</Space>
</Radio.Group>
);
return (
<Flex justify="space-between" align="center">
<Space size={'middle'}>
<Link
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
>
<ArrowLeftOutlined />
</Link>
<FilePdfOutlined />
<Text ellipsis={{ tooltip: documentInfo?.name }} style={{ width: 150 }}>
{documentInfo?.name}
</Text>
</Space>
<Space>
<Segmented
options={[
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
]}
onChange={changeChunkTextMode as SegmentedProps['onChange']}
/>
<Popover content={content} placement="bottom" arrow={false}>
<Button>
{t('bulk')}
<DownOutlined />
</Button>
</Popover>
{isShowSearchBox ? (
<Input
size="middle"
placeholder={t('search')}
prefix={<SearchOutlined />}
allowClear
onChange={handleInputChange}
onBlur={handleSearchBlur}
value={searchString}
/>
) : (
<Button icon={<SearchOutlined />} onClick={handleSearchIconClick} />
)}
<Popover content={filterContent} placement="bottom" arrow={false}>
<Button icon={<FilterIcon />} />
</Popover>
<Button
icon={<PlusOutlined />}
type="primary"
onClick={() => createChunk()}
/>
</Space>
</Flex>
);
};
export default ChunkToolBar;

View File

@ -1,74 +0,0 @@
import {
useNavigateWithFromState,
useSecondPathName,
useThirdPathName,
} from '@/hooks/route-hook';
import { useKnowledgeBaseId } from '@/hooks/use-knowledge-request';
import { Breadcrumb } from 'antd';
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, Outlet } from 'react-router';
import Siderbar from './components/knowledge-sidebar';
import { KnowledgeDatasetRouteKey, KnowledgeRouteKey } from './constant';
import styles from './index.module.less';
const KnowledgeAdding = () => {
const knowledgeBaseId = useKnowledgeBaseId();
const { t } = useTranslation();
const activeKey: KnowledgeRouteKey =
(useSecondPathName() as KnowledgeRouteKey) || KnowledgeRouteKey.Dataset;
const datasetActiveKey: KnowledgeDatasetRouteKey =
useThirdPathName() as KnowledgeDatasetRouteKey;
const gotoList = useNavigateWithFromState();
const breadcrumbItems: ItemType[] = useMemo(() => {
const items: ItemType[] = [
{
title: (
<a onClick={() => gotoList('/knowledge')}>
{t('header.knowledgeBase')}
</a>
),
},
{
title: datasetActiveKey ? (
<Link
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
>
{t(`knowledgeDetails.${activeKey}`)}
</Link>
) : (
t(`knowledgeDetails.${activeKey}`)
),
},
];
if (datasetActiveKey) {
items.push({
title: t(`knowledgeDetails.${datasetActiveKey}`),
});
}
return items;
}, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId, t]);
return (
<>
<div className={styles.container}>
<Siderbar></Siderbar>
<div className={styles.contentWrapper}>
<Breadcrumb items={breadcrumbItems} />
<div className={styles.content}>
<Outlet></Outlet>
</div>
</div>
</div>
</>
);
};
export default KnowledgeAdding;

View File

@ -1,223 +0,0 @@
import FilterIcon from '@/assets/filter.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
IChunkListResult,
useSelectChunkList,
} from '@/hooks/use-chunk-request';
import { useKnowledgeBaseId } from '@/hooks/use-knowledge-request';
import {
ArrowLeftOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
DeleteOutlined,
DownOutlined,
FilePdfOutlined,
PlusOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {
Button,
Checkbox,
Flex,
Input,
Menu,
MenuProps,
Popover,
Radio,
RadioChangeEvent,
Segmented,
SegmentedProps,
Space,
Typography,
} from 'antd';
import { useCallback, useMemo, useState } from 'react';
import { Link } from 'react-router';
import { ChunkTextMode } from '../../constant';
const { Text } = Typography;
interface IProps extends Pick<
IChunkListResult,
'searchString' | 'handleInputChange' | 'available' | 'handleSetAvailable'
> {
checked: boolean;
selectAllChunk: (checked: boolean) => void;
createChunk: () => void;
removeChunk: () => void;
switchChunk: (available: number) => void;
changeChunkTextMode(mode: ChunkTextMode): void;
}
const ChunkToolBar = ({
selectAllChunk,
checked,
createChunk,
removeChunk,
switchChunk,
changeChunkTextMode,
available,
handleSetAvailable,
searchString,
handleInputChange,
}: IProps) => {
const data = useSelectChunkList();
const documentInfo = data?.documentInfo;
const knowledgeBaseId = useKnowledgeBaseId();
const [isShowSearchBox, setIsShowSearchBox] = useState(false);
const { t } = useTranslate('chunk');
const handleSelectAllCheck = useCallback(
(e: any) => {
selectAllChunk(e.target.checked);
},
[selectAllChunk],
);
const handleSearchIconClick = () => {
setIsShowSearchBox(true);
};
const handleSearchBlur = () => {
if (!searchString?.trim()) {
setIsShowSearchBox(false);
}
};
const handleDelete = useCallback(() => {
removeChunk();
}, [removeChunk]);
const handleEnabledClick = useCallback(() => {
switchChunk(1);
}, [switchChunk]);
const handleDisabledClick = useCallback(() => {
switchChunk(0);
}, [switchChunk]);
const items: MenuProps['items'] = useMemo(() => {
return [
{
key: '1',
label: (
<>
<Checkbox onChange={handleSelectAllCheck} checked={checked}>
<b>{t('selectAll')}</b>
</Checkbox>
</>
),
},
{ type: 'divider' },
{
key: '2',
label: (
<Space onClick={handleEnabledClick}>
<CheckCircleOutlined />
<b>{t('enabledSelected')}</b>
</Space>
),
},
{
key: '3',
label: (
<Space onClick={handleDisabledClick}>
<CloseCircleOutlined />
<b>{t('disabledSelected')}</b>
</Space>
),
},
{ type: 'divider' },
{
key: '4',
label: (
<Space onClick={handleDelete}>
<DeleteOutlined />
<b>{t('deleteSelected')}</b>
</Space>
),
},
];
}, [
checked,
handleSelectAllCheck,
handleDelete,
handleEnabledClick,
handleDisabledClick,
t,
]);
const content = (
<Menu style={{ width: 200 }} items={items} selectable={false} />
);
const handleFilterChange = (e: RadioChangeEvent) => {
selectAllChunk(false);
handleSetAvailable(e.target.value);
};
const filterContent = (
<Radio.Group onChange={handleFilterChange} value={available}>
<Space direction="vertical">
<Radio value={undefined}>{t('all')}</Radio>
<Radio value={1}>{t('enabled')}</Radio>
<Radio value={0}>{t('disabled')}</Radio>
</Space>
</Radio.Group>
);
return (
<Flex justify="space-between" align="center">
<Space size={'middle'}>
<Link
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
>
<ArrowLeftOutlined />
</Link>
<FilePdfOutlined />
<Text ellipsis={{ tooltip: documentInfo?.name }} style={{ width: 150 }}>
{documentInfo?.name}
</Text>
</Space>
<Space>
<Segmented
options={[
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
]}
onChange={changeChunkTextMode as SegmentedProps['onChange']}
/>
<Popover content={content} placement="bottom" arrow={false}>
<Button>
{t('bulk')}
<DownOutlined />
</Button>
</Popover>
{isShowSearchBox ? (
<Input
size="middle"
placeholder={t('search')}
prefix={<SearchOutlined />}
allowClear
onChange={handleInputChange}
onBlur={handleSearchBlur}
value={searchString}
/>
) : (
<Button icon={<SearchOutlined />} onClick={handleSearchIconClick} />
)}
<Popover content={filterContent} placement="bottom" arrow={false}>
<Button icon={<FilterIcon />} />
</Popover>
<Button
icon={<PlusOutlined />}
type="primary"
onClick={() => createChunk()}
/>
</Space>
</Flex>
);
};
export default ChunkToolBar;