mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Feat: Delete useless knowledge base, chat, and search files. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -6,7 +6,9 @@ import {
|
|||||||
} from '@/hooks/document-hooks';
|
} from '@/hooks/document-hooks';
|
||||||
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
||||||
import {
|
import {
|
||||||
|
currentReg,
|
||||||
preprocessLaTeX,
|
preprocessLaTeX,
|
||||||
|
replaceTextByOldReg,
|
||||||
replaceThinkToSection,
|
replaceThinkToSection,
|
||||||
showImage,
|
showImage,
|
||||||
} from '@/utils/chat';
|
} from '@/utils/chat';
|
||||||
@ -32,7 +34,6 @@ import rehypeRaw from 'rehype-raw';
|
|||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
import { visitParents } from 'unist-util-visit-parents';
|
import { visitParents } from 'unist-util-visit-parents';
|
||||||
import { currentReg, replaceTextByOldReg } from '../pages/next-chats/utils';
|
|
||||||
import styles from './floating-chat-widget-markdown.less';
|
import styles from './floating-chat-widget-markdown.less';
|
||||||
import { useIsDarkTheme } from './theme-provider';
|
import { useIsDarkTheme } from './theme-provider';
|
||||||
|
|
||||||
|
|||||||
@ -21,11 +21,12 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
|
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
currentReg,
|
||||||
preprocessLaTeX,
|
preprocessLaTeX,
|
||||||
|
replaceTextByOldReg,
|
||||||
replaceThinkToSection,
|
replaceThinkToSection,
|
||||||
showImage,
|
showImage,
|
||||||
} from '@/utils/chat';
|
} from '@/utils/chat';
|
||||||
import { currentReg, replaceTextByOldReg } from '../utils';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
@ -1,6 +1,10 @@
|
|||||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
import {
|
||||||
|
IMessage,
|
||||||
|
IReference,
|
||||||
|
IReferenceChunk,
|
||||||
|
} from '@/interfaces/database/chat';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
@ -10,9 +14,8 @@ import {
|
|||||||
} from '@/hooks/document-hooks';
|
} from '@/hooks/document-hooks';
|
||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import MarkdownContent from '@/pages/chat/markdown-content';
|
|
||||||
import { Avatar, Flex, Space } from 'antd';
|
import { Avatar, Flex, Space } from 'antd';
|
||||||
|
import MarkdownContent from '../markdown-content';
|
||||||
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||||
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
||||||
import { useTheme } from '../theme-provider';
|
import { useTheme } from '../theme-provider';
|
||||||
|
|||||||
@ -19,13 +19,14 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
|
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
currentReg,
|
||||||
preprocessLaTeX,
|
preprocessLaTeX,
|
||||||
|
replaceTextByOldReg,
|
||||||
replaceThinkToSection,
|
replaceThinkToSection,
|
||||||
showImage,
|
showImage,
|
||||||
} from '@/utils/chat';
|
} from '@/utils/chat';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { currentReg, replaceTextByOldReg } from '@/pages/chat/utils';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { pipe } from 'lodash/fp';
|
import { pipe } from 'lodash/fp';
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { IReferenceChunk, IReferenceObject } from '@/interfaces/database/chat';
|
import {
|
||||||
|
IMessage,
|
||||||
|
IReferenceChunk,
|
||||||
|
IReferenceObject,
|
||||||
|
} from '@/interfaces/database/chat';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
@ -17,7 +21,6 @@ import { INodeEvent, MessageEventType } from '@/hooks/use-send-message';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { AgentChatContext } from '@/pages/agent/context';
|
import { AgentChatContext } from '@/pages/agent/context';
|
||||||
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import { downloadFile } from '@/services/file-manager-service';
|
import { downloadFile } from '@/services/file-manager-service';
|
||||||
import { downloadFileFromBlob } from '@/utils/file-util';
|
import { downloadFileFromBlob } from '@/utils/file-util';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
import { TagRenameId } from '@/constants/knowledge';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ButtonLoading } from '../ui/button';
|
import { ButtonLoading } from '../ui/button';
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { TagRenameId } from '@/constants/knowledge';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|||||||
@ -92,3 +92,5 @@ export enum DocumentParserType {
|
|||||||
Tag = 'tag',
|
Tag = 'tag',
|
||||||
KnowledgeGraph = 'knowledge_graph',
|
KnowledgeGraph = 'knowledge_graph',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TagRenameId = 'tagRename';
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ChatSearchParams } from '@/constants/chat';
|
import { ChatSearchParams } from '@/constants/chat';
|
||||||
import {
|
import {
|
||||||
|
IClientConversation,
|
||||||
IConversation,
|
IConversation,
|
||||||
IDialog,
|
IDialog,
|
||||||
IStats,
|
IStats,
|
||||||
@ -10,8 +11,7 @@ import {
|
|||||||
IFeedbackRequestBody,
|
IFeedbackRequestBody,
|
||||||
} from '@/interfaces/request/chat';
|
} from '@/interfaces/request/chat';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { IClientConversation } from '@/pages/chat/interface';
|
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
|
||||||
import chatService from '@/services/chat-service';
|
import chatService from '@/services/chat-service';
|
||||||
import {
|
import {
|
||||||
buildMessageListWithUuid,
|
buildMessageListWithUuid,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { DSL, IFlow } from '@/interfaces/database/flow';
|
import { DSL, IFlow } from '@/interfaces/database/flow';
|
||||||
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||||
import flowService from '@/services/flow-service';
|
import flowService from '@/services/flow-service';
|
||||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|||||||
@ -2,9 +2,13 @@ import { Authorization } from '@/constants/authorization';
|
|||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { LanguageTranslationMap } from '@/constants/common';
|
import { LanguageTranslationMap } from '@/constants/common';
|
||||||
import { ResponseType } from '@/interfaces/database/base';
|
import { ResponseType } from '@/interfaces/database/base';
|
||||||
import { IAnswer, Message } from '@/interfaces/database/chat';
|
import {
|
||||||
|
IAnswer,
|
||||||
|
IClientConversation,
|
||||||
|
IMessage,
|
||||||
|
Message,
|
||||||
|
} from '@/interfaces/database/chat';
|
||||||
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||||
import { IClientConversation, IMessage } from '@/pages/chat/interface';
|
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { getAuthorization } from '@/utils/authorization-util';
|
import { getAuthorization } from '@/utils/authorization-util';
|
||||||
import { buildMessageUuid } from '@/utils/chat';
|
import { buildMessageUuid } from '@/utils/chat';
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { IDebugSingleRequestBody } from '@/interfaces/request/agent';
|
|||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { BeginId } from '@/pages/agent/constant';
|
import { BeginId } from '@/pages/agent/constant';
|
||||||
import { IInputs } from '@/pages/agent/interface';
|
import { IInputs } from '@/pages/agent/interface';
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||||
import agentService, {
|
import agentService, {
|
||||||
fetchAgentLogsByCanvasId,
|
fetchAgentLogsByCanvasId,
|
||||||
fetchPipeLineList,
|
fetchPipeLineList,
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import { FileUploadProps } from '@/components/file-upload';
|
|||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import { ChatSearchParams } from '@/constants/chat';
|
import { ChatSearchParams } from '@/constants/chat';
|
||||||
import {
|
import {
|
||||||
|
IClientConversation,
|
||||||
IConversation,
|
IConversation,
|
||||||
IDialog,
|
IDialog,
|
||||||
IExternalChatInfo,
|
IExternalChatInfo,
|
||||||
} from '@/interfaces/database/chat';
|
} from '@/interfaces/database/chat';
|
||||||
import { IAskRequestBody } from '@/interfaces/request/chat';
|
import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||||
import { IClientConversation } from '@/pages/next-chats/chat/interface';
|
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||||
import chatService from '@/services/next-chat-service';
|
import chatService from '@/services/next-chat-service';
|
||||||
|
|||||||
@ -183,3 +183,12 @@ export interface IExternalChatInfo {
|
|||||||
title: string;
|
title: string;
|
||||||
prologue?: string;
|
prologue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMessage extends Message {
|
||||||
|
id: string;
|
||||||
|
reference?: IReference; // the latest news has reference
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClientConversation extends IConversation {
|
||||||
|
message: IMessage[];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
.image {
|
|
||||||
width: 100px !important;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePreview {
|
|
||||||
max-width: 50vw;
|
|
||||||
max-height: 50vh;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
.chunkText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentEllipsis {
|
|
||||||
.multipleLineEllipsis(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentText {
|
|
||||||
word-break: break-all !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chunkCard {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardSelected {
|
|
||||||
background-color: @selectedBackgroundColor;
|
|
||||||
}
|
|
||||||
.cardSelectedDark {
|
|
||||||
background-color: #ffffff2f;
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
import Image from '@/components/image';
|
|
||||||
import { IChunk } from '@/interfaces/database/knowledge';
|
|
||||||
import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import DOMPurify from 'dompurify';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ChunkTextMode } from '../../constant';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
item: IChunk;
|
|
||||||
checked: boolean;
|
|
||||||
switchChunk: (available?: number, chunkIds?: string[]) => void;
|
|
||||||
editChunk: (chunkId: string) => void;
|
|
||||||
handleCheckboxClick: (chunkId: string, checked: boolean) => void;
|
|
||||||
selected: boolean;
|
|
||||||
clickChunkCard: (chunkId: string) => void;
|
|
||||||
textMode: ChunkTextMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChunkCard = ({
|
|
||||||
item,
|
|
||||||
checked,
|
|
||||||
handleCheckboxClick,
|
|
||||||
editChunk,
|
|
||||||
switchChunk,
|
|
||||||
selected,
|
|
||||||
clickChunkCard,
|
|
||||||
textMode,
|
|
||||||
}: IProps) => {
|
|
||||||
const available = Number(item.available_int);
|
|
||||||
const [enabled, setEnabled] = useState(false);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const onChange = (checked: boolean) => {
|
|
||||||
setEnabled(checked);
|
|
||||||
switchChunk(available === 0 ? 1 : 0, [item.chunk_id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheck: CheckboxProps['onChange'] = (e) => {
|
|
||||||
handleCheckboxClick(item.chunk_id, e.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContentDoubleClick = () => {
|
|
||||||
editChunk(item.chunk_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContentClick = () => {
|
|
||||||
clickChunkCard(item.chunk_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setEnabled(available === 1);
|
|
||||||
}, [available]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className={classNames(styles.chunkCard, {
|
|
||||||
[`${theme === 'dark' ? styles.cardSelectedDark : styles.cardSelected}`]:
|
|
||||||
selected,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Flex gap={'middle'} justify={'space-between'}>
|
|
||||||
<Checkbox onChange={handleCheck} checked={checked}></Checkbox>
|
|
||||||
{item.image_id && (
|
|
||||||
<Popover
|
|
||||||
placement="right"
|
|
||||||
content={
|
|
||||||
<Image id={item.image_id} className={styles.imagePreview}></Image>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Image id={item.image_id} className={styles.image}></Image>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<section
|
|
||||||
onDoubleClick={handleContentDoubleClick}
|
|
||||||
onClick={handleContentClick}
|
|
||||||
className={styles.content}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(item.content_with_weight),
|
|
||||||
}}
|
|
||||||
className={classNames(styles.contentText, {
|
|
||||||
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
|
||||||
})}
|
|
||||||
></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Switch checked={enabled} onChange={onChange} />
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChunkCard;
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
import EditTag from '@/components/edit-tag';
|
|
||||||
import { useFetchChunk } from '@/hooks/chunk-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { IChunk } from '@/interfaces/database/knowledge';
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
|
||||||
import { Divider, Form, Input, Modal, Space, Switch } from 'antd';
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useDeleteChunkByIds } from '../../hooks';
|
|
||||||
import {
|
|
||||||
transformTagFeaturesArrayToObject,
|
|
||||||
transformTagFeaturesObjectToArray,
|
|
||||||
} from '../../utils';
|
|
||||||
import { TagFeatureItem } from './tag-feature-item';
|
|
||||||
|
|
||||||
type FieldType = Pick<
|
|
||||||
IChunk,
|
|
||||||
'content_with_weight' | 'tag_kwd' | 'question_kwd' | 'important_kwd'
|
|
||||||
>;
|
|
||||||
|
|
||||||
interface kFProps {
|
|
||||||
doc_id: string;
|
|
||||||
chunkId: string | undefined;
|
|
||||||
parserId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
|
||||||
doc_id,
|
|
||||||
chunkId,
|
|
||||||
hideModal,
|
|
||||||
onOk,
|
|
||||||
loading,
|
|
||||||
parserId,
|
|
||||||
}) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [checked, setChecked] = useState(false);
|
|
||||||
const { removeChunk } = useDeleteChunkByIds();
|
|
||||||
const { data } = useFetchChunk(chunkId);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const isTagParser = parserId === 'tag';
|
|
||||||
|
|
||||||
const handleOk = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
console.log('🚀 ~ handleOk ~ values:', values);
|
|
||||||
|
|
||||||
onOk?.({
|
|
||||||
...values,
|
|
||||||
tag_feas: transformTagFeaturesArrayToObject(values.tag_feas),
|
|
||||||
available_int: checked ? 1 : 0, // available_int
|
|
||||||
});
|
|
||||||
} catch (errorInfo) {
|
|
||||||
console.log('Failed:', errorInfo);
|
|
||||||
}
|
|
||||||
}, [checked, form, onOk]);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
|
||||||
if (chunkId) {
|
|
||||||
return removeChunk([chunkId], doc_id);
|
|
||||||
}
|
|
||||||
}, [chunkId, doc_id, removeChunk]);
|
|
||||||
|
|
||||||
const handleCheck = useCallback(() => {
|
|
||||||
setChecked(!checked);
|
|
||||||
}, [checked]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.code === 0) {
|
|
||||||
const { available_int, tag_feas } = data.data;
|
|
||||||
form.setFieldsValue({
|
|
||||||
...(data.data || {}),
|
|
||||||
tag_feas: transformTagFeaturesObjectToArray(tag_feas),
|
|
||||||
});
|
|
||||||
|
|
||||||
setChecked(available_int !== 0);
|
|
||||||
}
|
|
||||||
}, [data, form, chunkId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={`${chunkId ? t('common.edit') : t('common.create')} ${t('chunk.chunk')}`}
|
|
||||||
open={true}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
okButtonProps={{ loading }}
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<Form form={form} autoComplete="off" layout={'vertical'}>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label={t('chunk.chunk')}
|
|
||||||
name="content_with_weight"
|
|
||||||
rules={[{ required: true, message: t('chunk.chunkMessage') }]}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize={{ minRows: 4, maxRows: 10 }} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item<FieldType> label={t('chunk.keyword')} name="important_kwd">
|
|
||||||
<EditTag></EditTag>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label={t('chunk.question')}
|
|
||||||
name="question_kwd"
|
|
||||||
tooltip={t('chunk.questionTip')}
|
|
||||||
>
|
|
||||||
<EditTag></EditTag>
|
|
||||||
</Form.Item>
|
|
||||||
{isTagParser && (
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label={t('knowledgeConfiguration.tagName')}
|
|
||||||
name="tag_kwd"
|
|
||||||
>
|
|
||||||
<EditTag></EditTag>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isTagParser && <TagFeatureItem></TagFeatureItem>}
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
{chunkId && (
|
|
||||||
<section>
|
|
||||||
<Divider></Divider>
|
|
||||||
<Space size={'large'}>
|
|
||||||
<Switch
|
|
||||||
checkedChildren={t('chunk.enabled')}
|
|
||||||
unCheckedChildren={t('chunk.disabled')}
|
|
||||||
onChange={handleCheck}
|
|
||||||
checked={checked}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span onClick={handleRemove}>
|
|
||||||
<DeleteOutlined /> {t('common.delete')}
|
|
||||||
</span>
|
|
||||||
</Space>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ChunkCreatingModal;
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
import {
|
|
||||||
useFetchKnowledgeBaseConfiguration,
|
|
||||||
useFetchTagListByKnowledgeIds,
|
|
||||||
} from '@/hooks/knowledge-hooks';
|
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, InputNumber, Select } from 'antd';
|
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FormListItem } from '../../utils';
|
|
||||||
|
|
||||||
const FieldKey = 'tag_feas';
|
|
||||||
|
|
||||||
export const TagFeatureItem = () => {
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
|
|
||||||
const { setKnowledgeIds, list } = useFetchTagListByKnowledgeIds();
|
|
||||||
|
|
||||||
const tagKnowledgeIds = useMemo(() => {
|
|
||||||
return knowledgeConfiguration?.parser_config?.tag_kb_ids ?? [];
|
|
||||||
}, [knowledgeConfiguration?.parser_config?.tag_kb_ids]);
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return list.map((x) => ({
|
|
||||||
value: x[0],
|
|
||||||
label: x[0],
|
|
||||||
}));
|
|
||||||
}, [list]);
|
|
||||||
|
|
||||||
const filterOptions = useCallback(
|
|
||||||
(index: number) => {
|
|
||||||
const tags: FormListItem[] = form.getFieldValue(FieldKey) ?? [];
|
|
||||||
|
|
||||||
// Exclude it's own current data
|
|
||||||
const list = tags
|
|
||||||
.filter((x, idx) => x && index !== idx)
|
|
||||||
.map((x) => x.tag);
|
|
||||||
|
|
||||||
// Exclude the selected data from other options from one's own options.
|
|
||||||
return options.filter((x) => !list.some((y) => x.value === y));
|
|
||||||
},
|
|
||||||
[form, options],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setKnowledgeIds(tagKnowledgeIds);
|
|
||||||
}, [setKnowledgeIds, tagKnowledgeIds]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item label={t('knowledgeConfiguration.tags')}>
|
|
||||||
<Form.List name={FieldKey} initialValue={[]}>
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<div key={key} className="flex gap-3 items-center">
|
|
||||||
<div className="flex flex-1 gap-8">
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'tag']}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: t('common.pleaseSelect') },
|
|
||||||
]}
|
|
||||||
className="w-2/3"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
showSearch
|
|
||||||
placeholder={t('knowledgeConfiguration.tagName')}
|
|
||||||
options={filterOptions(name)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'frequency']}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: t('common.pleaseInput') },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
placeholder={t('knowledgeConfiguration.frequency')}
|
|
||||||
max={10}
|
|
||||||
min={0}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
<MinusCircleOutlined
|
|
||||||
onClick={() => remove(name)}
|
|
||||||
className="mb-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add()}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
{t('knowledgeConfiguration.addTag')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
|
|
||||||
import { KnowledgeRouteKey } from '@/constants/knowledge';
|
|
||||||
import { IChunkListResult, useSelectChunkList } from '@/hooks/chunk-hooks';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks';
|
|
||||||
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 'umi';
|
|
||||||
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;
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
|
||||||
import { api_host } from '@/utils/api';
|
|
||||||
import { useSize } from 'ahooks';
|
|
||||||
import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
export const useDocumentResizeObserver = () => {
|
|
||||||
const [containerWidth, setContainerWidth] = useState<number>();
|
|
||||||
const [containerRef, setContainerRef] = useState<HTMLElement | null>(null);
|
|
||||||
const size = useSize(containerRef);
|
|
||||||
|
|
||||||
const onResize = useCallback((width?: number) => {
|
|
||||||
if (width) {
|
|
||||||
setContainerWidth(width);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onResize(size?.width);
|
|
||||||
}, [size?.width, onResize]);
|
|
||||||
|
|
||||||
return { containerWidth, setContainerRef };
|
|
||||||
};
|
|
||||||
|
|
||||||
function highlightPattern(text: string, pattern: string, pageNumber: number) {
|
|
||||||
if (pageNumber === 2) {
|
|
||||||
return `<mark>${text}</mark>`;
|
|
||||||
}
|
|
||||||
if (text.trim() !== '' && pattern.match(text)) {
|
|
||||||
// return pattern.replace(text, (value) => `<mark>${value}</mark>`);
|
|
||||||
return `<mark>${text}</mark>`;
|
|
||||||
}
|
|
||||||
return text.replace(pattern, (value) => `<mark>${value}</mark>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useHighlightText = (searchText: string = '') => {
|
|
||||||
const textRenderer: CustomTextRenderer = useCallback(
|
|
||||||
(textItem) => {
|
|
||||||
return highlightPattern(textItem.str, searchText, textItem.pageNumber);
|
|
||||||
},
|
|
||||||
[searchText],
|
|
||||||
);
|
|
||||||
|
|
||||||
return textRenderer;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetDocumentUrl = () => {
|
|
||||||
const { documentId } = useGetKnowledgeSearchParams();
|
|
||||||
|
|
||||||
const url = useMemo(() => {
|
|
||||||
return `${api_host}/document/get/${documentId}`;
|
|
||||||
}, [documentId]);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.documentContainer {
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100vh - 284px);
|
|
||||||
position: relative;
|
|
||||||
:global(.PdfHighlighter) {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
:global(.Highlight--scrolledTo .Highlight__part) {
|
|
||||||
overflow-x: hidden;
|
|
||||||
background-color: rgba(255, 226, 143, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import { Skeleton } from 'antd';
|
|
||||||
import { memo, useEffect, useRef } from 'react';
|
|
||||||
import {
|
|
||||||
AreaHighlight,
|
|
||||||
Highlight,
|
|
||||||
IHighlight,
|
|
||||||
PdfHighlighter,
|
|
||||||
PdfLoader,
|
|
||||||
Popup,
|
|
||||||
} from 'react-pdf-highlighter';
|
|
||||||
import { useGetDocumentUrl } from './hooks';
|
|
||||||
|
|
||||||
import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
|
|
||||||
import FileError from '@/pages/document-viewer/file-error';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
highlights: IHighlight[];
|
|
||||||
setWidthAndHeight: (width: number, height: number) => void;
|
|
||||||
}
|
|
||||||
const HighlightPopup = ({
|
|
||||||
comment,
|
|
||||||
}: {
|
|
||||||
comment: { text: string; emoji: string };
|
|
||||||
}) =>
|
|
||||||
comment.text ? (
|
|
||||||
<div className="Highlight__popup">
|
|
||||||
{comment.emoji} {comment.text}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
// TODO: merge with DocumentPreviewer
|
|
||||||
const Preview = ({ highlights: state, setWidthAndHeight }: IProps) => {
|
|
||||||
const url = useGetDocumentUrl();
|
|
||||||
|
|
||||||
const ref = useRef<(highlight: IHighlight) => void>(() => {});
|
|
||||||
const error = useCatchDocumentError(url);
|
|
||||||
|
|
||||||
const resetHash = () => {};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (state.length > 0) {
|
|
||||||
ref?.current(state[0]);
|
|
||||||
}
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.documentContainer}>
|
|
||||||
<PdfLoader
|
|
||||||
url={url}
|
|
||||||
beforeLoad={<Skeleton active />}
|
|
||||||
workerSrc="/pdfjs-dist/pdf.worker.min.js"
|
|
||||||
errorMessage={<FileError>{error}</FileError>}
|
|
||||||
>
|
|
||||||
{(pdfDocument) => {
|
|
||||||
pdfDocument.getPage(1).then((page) => {
|
|
||||||
const viewport = page.getViewport({ scale: 1 });
|
|
||||||
const width = viewport.width;
|
|
||||||
const height = viewport.height;
|
|
||||||
setWidthAndHeight(width, height);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PdfHighlighter
|
|
||||||
pdfDocument={pdfDocument}
|
|
||||||
enableAreaSelection={(event) => event.altKey}
|
|
||||||
onScrollChange={resetHash}
|
|
||||||
scrollRef={(scrollTo) => {
|
|
||||||
ref.current = scrollTo;
|
|
||||||
}}
|
|
||||||
onSelectionFinished={() => null}
|
|
||||||
highlightTransform={(
|
|
||||||
highlight,
|
|
||||||
index,
|
|
||||||
setTip,
|
|
||||||
hideTip,
|
|
||||||
viewportToScaled,
|
|
||||||
screenshot,
|
|
||||||
isScrolledTo,
|
|
||||||
) => {
|
|
||||||
const isTextHighlight = !Boolean(
|
|
||||||
highlight.content && highlight.content.image,
|
|
||||||
);
|
|
||||||
|
|
||||||
const component = isTextHighlight ? (
|
|
||||||
<Highlight
|
|
||||||
isScrolledTo={isScrolledTo}
|
|
||||||
position={highlight.position}
|
|
||||||
comment={highlight.comment}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AreaHighlight
|
|
||||||
isScrolledTo={isScrolledTo}
|
|
||||||
highlight={highlight}
|
|
||||||
onChange={() => {}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popup
|
|
||||||
popupContent={<HighlightPopup {...highlight} />}
|
|
||||||
onMouseOver={(popupContent) =>
|
|
||||||
setTip(highlight, () => popupContent)
|
|
||||||
}
|
|
||||||
onMouseOut={hideTip}
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
{component}
|
|
||||||
</Popup>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
highlights={state}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</PdfLoader>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(Preview);
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export enum ChunkTextMode {
|
|
||||||
Full = 'full',
|
|
||||||
Ellipse = 'ellipse',
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import {
|
|
||||||
useCreateChunk,
|
|
||||||
useDeleteChunk,
|
|
||||||
useSelectChunkList,
|
|
||||||
} from '@/hooks/chunk-hooks';
|
|
||||||
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
|
||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
|
||||||
import { IChunk } from '@/interfaces/database/knowledge';
|
|
||||||
import { buildChunkHighlights } from '@/utils/document-util';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { IHighlight } from 'react-pdf-highlighter';
|
|
||||||
import { ChunkTextMode } from './constant';
|
|
||||||
|
|
||||||
export const useHandleChunkCardClick = () => {
|
|
||||||
const [selectedChunkId, setSelectedChunkId] = useState<string>('');
|
|
||||||
|
|
||||||
const handleChunkCardClick = useCallback((chunkId: string) => {
|
|
||||||
setSelectedChunkId(chunkId);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { handleChunkCardClick, selectedChunkId };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetSelectedChunk = (selectedChunkId: string) => {
|
|
||||||
const data = useSelectChunkList();
|
|
||||||
return (
|
|
||||||
data?.data?.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetChunkHighlights = (selectedChunkId: string) => {
|
|
||||||
const [size, setSize] = useState({ width: 849, height: 1200 });
|
|
||||||
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
|
|
||||||
|
|
||||||
const highlights: IHighlight[] = useMemo(() => {
|
|
||||||
return buildChunkHighlights(selectedChunk, size);
|
|
||||||
}, [selectedChunk, size]);
|
|
||||||
|
|
||||||
const setWidthAndHeight = useCallback((width: number, height: number) => {
|
|
||||||
setSize((pre) => {
|
|
||||||
if (pre.height !== height || pre.width !== width) {
|
|
||||||
return { height, width };
|
|
||||||
}
|
|
||||||
return pre;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { highlights, setWidthAndHeight };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Switch chunk text to be fully displayed or ellipse
|
|
||||||
export const useChangeChunkTextMode = () => {
|
|
||||||
const [textMode, setTextMode] = useState<ChunkTextMode>(ChunkTextMode.Full);
|
|
||||||
|
|
||||||
const changeChunkTextMode = useCallback((mode: ChunkTextMode) => {
|
|
||||||
setTextMode(mode);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { textMode, changeChunkTextMode };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteChunkByIds = (): {
|
|
||||||
removeChunk: (chunkIds: string[], documentId: string) => Promise<number>;
|
|
||||||
} => {
|
|
||||||
const { deleteChunk } = useDeleteChunk();
|
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
|
||||||
|
|
||||||
const removeChunk = useCallback(
|
|
||||||
(chunkIds: string[], documentId: string) => () => {
|
|
||||||
return deleteChunk({ chunkIds, doc_id: documentId });
|
|
||||||
},
|
|
||||||
[deleteChunk],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onRemoveChunk = useCallback(
|
|
||||||
(chunkIds: string[], documentId: string): Promise<number> => {
|
|
||||||
return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) });
|
|
||||||
},
|
|
||||||
[removeChunk, showDeleteConfirm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
removeChunk: onRemoveChunk,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdateChunk = () => {
|
|
||||||
const [chunkId, setChunkId] = useState<string | undefined>('');
|
|
||||||
const {
|
|
||||||
visible: chunkUpdatingVisible,
|
|
||||||
hideModal: hideChunkUpdatingModal,
|
|
||||||
showModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
const { createChunk, loading } = useCreateChunk();
|
|
||||||
const { documentId } = useGetKnowledgeSearchParams();
|
|
||||||
|
|
||||||
const onChunkUpdatingOk = useCallback(
|
|
||||||
async (params: IChunk) => {
|
|
||||||
const code = await createChunk({
|
|
||||||
...params,
|
|
||||||
doc_id: documentId,
|
|
||||||
chunk_id: chunkId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (code === 0) {
|
|
||||||
hideChunkUpdatingModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[createChunk, hideChunkUpdatingModal, chunkId, documentId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleShowChunkUpdatingModal = useCallback(
|
|
||||||
async (id?: string) => {
|
|
||||||
setChunkId(id);
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chunkUpdatingLoading: loading,
|
|
||||||
onChunkUpdatingOk,
|
|
||||||
chunkUpdatingVisible,
|
|
||||||
hideChunkUpdatingModal,
|
|
||||||
showChunkUpdatingModal: handleShowChunkUpdatingModal,
|
|
||||||
chunkId,
|
|
||||||
documentId,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
.chunkPage {
|
|
||||||
padding: 24px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
// height: calc(100vh - 112px);
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.filter {
|
|
||||||
margin: 10px 0;
|
|
||||||
display: flex;
|
|
||||||
height: 32px;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagePdfWrapper {
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageWrapper {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageContent {
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 12px;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.spin {
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentPreview {
|
|
||||||
width: 40%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chunkContainer {
|
|
||||||
display: flex;
|
|
||||||
height: calc(100vh - 332px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chunkOtherContainer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageFooter {
|
|
||||||
padding-top: 10px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: 100px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.context {
|
|
||||||
flex: 1;
|
|
||||||
// width: 207px;
|
|
||||||
height: 88px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
height: 20px;
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
:global {
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
import { useFetchNextChunkList, useSwitchChunk } from '@/hooks/chunk-hooks';
|
|
||||||
import type { PaginationProps } from 'antd';
|
|
||||||
import { Divider, Flex, Pagination, Space, Spin, message } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ChunkCard from './components/chunk-card';
|
|
||||||
import CreatingModal from './components/chunk-creating-modal';
|
|
||||||
import ChunkToolBar from './components/chunk-toolbar';
|
|
||||||
import DocumentPreview from './components/document-preview/preview';
|
|
||||||
import {
|
|
||||||
useChangeChunkTextMode,
|
|
||||||
useDeleteChunkByIds,
|
|
||||||
useGetChunkHighlights,
|
|
||||||
useHandleChunkCardClick,
|
|
||||||
useUpdateChunk,
|
|
||||||
} from './hooks';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const Chunk = () => {
|
|
||||||
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
|
|
||||||
const { removeChunk } = useDeleteChunkByIds();
|
|
||||||
const {
|
|
||||||
data: { documentInfo, data = [], total },
|
|
||||||
pagination,
|
|
||||||
loading,
|
|
||||||
searchString,
|
|
||||||
handleInputChange,
|
|
||||||
available,
|
|
||||||
handleSetAvailable,
|
|
||||||
} = useFetchNextChunkList();
|
|
||||||
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
|
|
||||||
const isPdf = documentInfo?.type === 'pdf';
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
|
|
||||||
const { switchChunk } = useSwitchChunk();
|
|
||||||
const {
|
|
||||||
chunkUpdatingLoading,
|
|
||||||
onChunkUpdatingOk,
|
|
||||||
showChunkUpdatingModal,
|
|
||||||
hideChunkUpdatingModal,
|
|
||||||
chunkId,
|
|
||||||
chunkUpdatingVisible,
|
|
||||||
documentId,
|
|
||||||
} = useUpdateChunk();
|
|
||||||
|
|
||||||
const onPaginationChange: PaginationProps['onShowSizeChange'] = (
|
|
||||||
page,
|
|
||||||
size,
|
|
||||||
) => {
|
|
||||||
setSelectedChunkIds([]);
|
|
||||||
pagination.onChange?.(page, size);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectAllChunk = useCallback(
|
|
||||||
(checked: boolean) => {
|
|
||||||
setSelectedChunkIds(checked ? data.map((x) => x.chunk_id) : []);
|
|
||||||
},
|
|
||||||
[data],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSingleCheckboxClick = useCallback(
|
|
||||||
(chunkId: string, checked: boolean) => {
|
|
||||||
setSelectedChunkIds((previousIds) => {
|
|
||||||
const idx = previousIds.findIndex((x) => x === chunkId);
|
|
||||||
const nextIds = [...previousIds];
|
|
||||||
if (checked && idx === -1) {
|
|
||||||
nextIds.push(chunkId);
|
|
||||||
} else if (!checked && idx !== -1) {
|
|
||||||
nextIds.splice(idx, 1);
|
|
||||||
}
|
|
||||||
return nextIds;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const showSelectedChunkWarning = useCallback(() => {
|
|
||||||
message.warning(t('message.pleaseSelectChunk'));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const handleRemoveChunk = useCallback(async () => {
|
|
||||||
if (selectedChunkIds.length > 0) {
|
|
||||||
const resCode: number = await removeChunk(selectedChunkIds, documentId);
|
|
||||||
if (resCode === 0) {
|
|
||||||
setSelectedChunkIds([]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showSelectedChunkWarning();
|
|
||||||
}
|
|
||||||
}, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]);
|
|
||||||
|
|
||||||
const handleSwitchChunk = useCallback(
|
|
||||||
async (available?: number, chunkIds?: string[]) => {
|
|
||||||
let ids = chunkIds;
|
|
||||||
if (!chunkIds) {
|
|
||||||
ids = selectedChunkIds;
|
|
||||||
if (selectedChunkIds.length === 0) {
|
|
||||||
showSelectedChunkWarning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resCode: number = await switchChunk({
|
|
||||||
chunk_ids: ids,
|
|
||||||
available_int: available,
|
|
||||||
doc_id: documentId,
|
|
||||||
});
|
|
||||||
if (!chunkIds && resCode === 0) {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[switchChunk, documentId, selectedChunkIds, showSelectedChunkWarning],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { highlights, setWidthAndHeight } =
|
|
||||||
useGetChunkHighlights(selectedChunkId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={styles.chunkPage}>
|
|
||||||
<ChunkToolBar
|
|
||||||
selectAllChunk={selectAllChunk}
|
|
||||||
createChunk={showChunkUpdatingModal}
|
|
||||||
removeChunk={handleRemoveChunk}
|
|
||||||
checked={selectedChunkIds.length === data.length}
|
|
||||||
switchChunk={handleSwitchChunk}
|
|
||||||
changeChunkTextMode={changeChunkTextMode}
|
|
||||||
searchString={searchString}
|
|
||||||
handleInputChange={handleInputChange}
|
|
||||||
available={available}
|
|
||||||
handleSetAvailable={handleSetAvailable}
|
|
||||||
></ChunkToolBar>
|
|
||||||
<Divider></Divider>
|
|
||||||
<Flex flex={1} gap={'middle'}>
|
|
||||||
<Flex
|
|
||||||
vertical
|
|
||||||
className={isPdf ? styles.pagePdfWrapper : styles.pageWrapper}
|
|
||||||
>
|
|
||||||
<Spin spinning={loading} className={styles.spin} size="large">
|
|
||||||
<div className={styles.pageContent}>
|
|
||||||
<Space
|
|
||||||
direction="vertical"
|
|
||||||
size={'middle'}
|
|
||||||
className={classNames(styles.chunkContainer, {
|
|
||||||
[styles.chunkOtherContainer]: !isPdf,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{data.map((item) => (
|
|
||||||
<ChunkCard
|
|
||||||
item={item}
|
|
||||||
key={item.chunk_id}
|
|
||||||
editChunk={showChunkUpdatingModal}
|
|
||||||
checked={selectedChunkIds.some(
|
|
||||||
(x) => x === item.chunk_id,
|
|
||||||
)}
|
|
||||||
handleCheckboxClick={handleSingleCheckboxClick}
|
|
||||||
switchChunk={handleSwitchChunk}
|
|
||||||
clickChunkCard={handleChunkCardClick}
|
|
||||||
selected={item.chunk_id === selectedChunkId}
|
|
||||||
textMode={textMode}
|
|
||||||
></ChunkCard>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
<div className={styles.pageFooter}>
|
|
||||||
<Pagination
|
|
||||||
{...pagination}
|
|
||||||
total={total}
|
|
||||||
size={'small'}
|
|
||||||
onChange={onPaginationChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
{isPdf && (
|
|
||||||
<section className={styles.documentPreview}>
|
|
||||||
<DocumentPreview
|
|
||||||
highlights={highlights}
|
|
||||||
setWidthAndHeight={setWidthAndHeight}
|
|
||||||
></DocumentPreview>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</div>
|
|
||||||
{chunkUpdatingVisible && (
|
|
||||||
<CreatingModal
|
|
||||||
doc_id={documentId}
|
|
||||||
chunkId={chunkId}
|
|
||||||
hideModal={hideChunkUpdatingModal}
|
|
||||||
visible={chunkUpdatingVisible}
|
|
||||||
loading={chunkUpdatingLoading}
|
|
||||||
onOk={onChunkUpdatingOk}
|
|
||||||
parserId={documentInfo.parser_id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Chunk;
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
export type FormListItem = {
|
|
||||||
frequency: number;
|
|
||||||
tag: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function transformTagFeaturesArrayToObject(
|
|
||||||
list: Array<FormListItem> = [],
|
|
||||||
) {
|
|
||||||
return list.reduce<Record<string, number>>((pre, cur) => {
|
|
||||||
pre[cur.tag] = cur.frequency;
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformTagFeaturesObjectToArray(
|
|
||||||
object: Record<string, number> = {},
|
|
||||||
) {
|
|
||||||
return Object.keys(object).reduce<Array<FormListItem>>((pre, key) => {
|
|
||||||
pre.push({ frequency: object[key], tag: key });
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { Outlet } from 'umi';
|
|
||||||
|
|
||||||
export const KnowledgeDataset = () => {
|
|
||||||
return <Outlet></Outlet>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeDataset;
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { RunningStatus } from '@/constants/knowledge';
|
|
||||||
|
|
||||||
export const RunningStatusMap = {
|
|
||||||
[RunningStatus.UNSTART]: {
|
|
||||||
label: 'UNSTART',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
[RunningStatus.RUNNING]: {
|
|
||||||
label: 'Parsing',
|
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'orange' },
|
|
||||||
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'geekblue' },
|
|
||||||
[RunningStatus.FAIL]: { label: 'FAIL', color: 'red' },
|
|
||||||
};
|
|
||||||
|
|
||||||
export * from '@/constants/knowledge';
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
|
||||||
import { Form, Input, Modal } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
type FieldType = {
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
|
||||||
loading: boolean;
|
|
||||||
onOk: (name: string) => void;
|
|
||||||
showModal?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FileCreatingModal: React.FC<IProps> = ({ visible, hideModal, onOk }) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const handleOk = async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
onOk(values.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title="File Name"
|
|
||||||
open={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
name="validateOnly"
|
|
||||||
labelCol={{ span: 4 }}
|
|
||||||
wrapperCol={{ span: 20 }}
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label="File Name"
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: 'Please input name!' }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default FileCreatingModal;
|
|
||||||
@ -1,240 +0,0 @@
|
|||||||
import { ReactComponent as CancelIcon } from '@/assets/svg/cancel.svg';
|
|
||||||
import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg';
|
|
||||||
import { ReactComponent as DisableIcon } from '@/assets/svg/disable.svg';
|
|
||||||
import { ReactComponent as EnableIcon } from '@/assets/svg/enable.svg';
|
|
||||||
import { ReactComponent as RunIcon } from '@/assets/svg/run.svg';
|
|
||||||
import { useShowDeleteConfirm, useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import {
|
|
||||||
useRemoveNextDocument,
|
|
||||||
useRunNextDocument,
|
|
||||||
useSetNextDocumentStatus,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import {
|
|
||||||
DownOutlined,
|
|
||||||
FileOutlined,
|
|
||||||
FileTextOutlined,
|
|
||||||
PlusOutlined,
|
|
||||||
SearchOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Button, Dropdown, Flex, Input, MenuProps, Space } from 'antd';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
import { RunningStatus } from './constant';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
selectedRowKeys: string[];
|
|
||||||
showCreateModal(): void;
|
|
||||||
showWebCrawlModal(): void;
|
|
||||||
showDocumentUploadModal(): void;
|
|
||||||
searchString: string;
|
|
||||||
handleInputChange: React.ChangeEventHandler<HTMLInputElement>;
|
|
||||||
documents: IDocumentInfo[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const DocumentToolbar = ({
|
|
||||||
searchString,
|
|
||||||
selectedRowKeys,
|
|
||||||
showCreateModal,
|
|
||||||
showDocumentUploadModal,
|
|
||||||
handleInputChange,
|
|
||||||
documents,
|
|
||||||
}: IProps) => {
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
const { removeDocument } = useRemoveNextDocument();
|
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
|
||||||
const { runDocumentByIds } = useRunNextDocument();
|
|
||||||
const { setDocumentStatus } = useSetNextDocumentStatus();
|
|
||||||
|
|
||||||
const actionItems: MenuProps['items'] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
onClick: showDocumentUploadModal,
|
|
||||||
label: (
|
|
||||||
<div>
|
|
||||||
<Button type="link">
|
|
||||||
<Space>
|
|
||||||
<FileTextOutlined />
|
|
||||||
{t('localFiles')}
|
|
||||||
</Space>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
key: '3',
|
|
||||||
onClick: showCreateModal,
|
|
||||||
label: (
|
|
||||||
<div>
|
|
||||||
<Button type="link">
|
|
||||||
<FileOutlined />
|
|
||||||
{t('emptyFiles')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [showDocumentUploadModal, showCreateModal, t]);
|
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
|
||||||
const deletedKeys = selectedRowKeys.filter(
|
|
||||||
(x) =>
|
|
||||||
!documents
|
|
||||||
.filter((y) => y.run === RunningStatus.RUNNING)
|
|
||||||
.some((y) => y.id === x),
|
|
||||||
);
|
|
||||||
if (deletedKeys.length === 0) {
|
|
||||||
toast.error(t('theDocumentBeingParsedCannotBeDeleted'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showDeleteConfirm({
|
|
||||||
onOk: () => {
|
|
||||||
removeDocument(deletedKeys);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [selectedRowKeys, showDeleteConfirm, documents, t, removeDocument]);
|
|
||||||
|
|
||||||
const runDocument = useCallback(
|
|
||||||
(run: number) => {
|
|
||||||
runDocumentByIds({
|
|
||||||
documentIds: selectedRowKeys,
|
|
||||||
run,
|
|
||||||
shouldDelete: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[runDocumentByIds, selectedRowKeys],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRunClick = useCallback(() => {
|
|
||||||
runDocument(1);
|
|
||||||
}, [runDocument]);
|
|
||||||
|
|
||||||
const handleCancelClick = useCallback(() => {
|
|
||||||
runDocument(2);
|
|
||||||
}, [runDocument]);
|
|
||||||
|
|
||||||
const onChangeStatus = useCallback(
|
|
||||||
(enabled: boolean) => {
|
|
||||||
selectedRowKeys.forEach((id) => {
|
|
||||||
setDocumentStatus({ status: enabled, documentId: id });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[selectedRowKeys, setDocumentStatus],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleEnableClick = useCallback(() => {
|
|
||||||
onChangeStatus(true);
|
|
||||||
}, [onChangeStatus]);
|
|
||||||
|
|
||||||
const handleDisableClick = useCallback(() => {
|
|
||||||
onChangeStatus(false);
|
|
||||||
}, [onChangeStatus]);
|
|
||||||
|
|
||||||
const disabled = selectedRowKeys.length === 0;
|
|
||||||
|
|
||||||
const items: MenuProps['items'] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: '0',
|
|
||||||
onClick: handleEnableClick,
|
|
||||||
label: (
|
|
||||||
<Flex gap={10}>
|
|
||||||
<EnableIcon></EnableIcon>
|
|
||||||
<b>{t('enabled')}</b>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
onClick: handleDisableClick,
|
|
||||||
label: (
|
|
||||||
<Flex gap={10}>
|
|
||||||
<DisableIcon></DisableIcon>
|
|
||||||
<b>{t('disabled')}</b>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
onClick: handleRunClick,
|
|
||||||
label: (
|
|
||||||
<Flex gap={10}>
|
|
||||||
<RunIcon></RunIcon>
|
|
||||||
<b>{t('run')}</b>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '3',
|
|
||||||
onClick: handleCancelClick,
|
|
||||||
label: (
|
|
||||||
<Flex gap={10}>
|
|
||||||
<CancelIcon />
|
|
||||||
<b>{t('cancel')}</b>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
key: '4',
|
|
||||||
onClick: handleDelete,
|
|
||||||
label: (
|
|
||||||
<Flex gap={10}>
|
|
||||||
<span className={styles.deleteIconWrapper}>
|
|
||||||
<DeleteIcon width={18} />
|
|
||||||
</span>
|
|
||||||
<b>{t('delete', { keyPrefix: 'common' })}</b>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [
|
|
||||||
handleDelete,
|
|
||||||
handleRunClick,
|
|
||||||
handleCancelClick,
|
|
||||||
t,
|
|
||||||
handleDisableClick,
|
|
||||||
handleEnableClick,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.filter}>
|
|
||||||
<Dropdown
|
|
||||||
menu={{ items }}
|
|
||||||
placement="bottom"
|
|
||||||
arrow={false}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<Button>
|
|
||||||
<Space>
|
|
||||||
<b> {t('bulk')}</b>
|
|
||||||
<DownOutlined />
|
|
||||||
</Space>
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
<Space>
|
|
||||||
<Input
|
|
||||||
placeholder={t('searchFiles')}
|
|
||||||
value={searchString}
|
|
||||||
style={{ width: 220 }}
|
|
||||||
allowClear
|
|
||||||
onChange={handleInputChange}
|
|
||||||
prefix={<SearchOutlined />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown menu={{ items: actionItems }} trigger={['click']}>
|
|
||||||
<Button type="primary" icon={<PlusOutlined />}>
|
|
||||||
{t('addFile')}
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DocumentToolbar;
|
|
||||||
@ -1,364 +0,0 @@
|
|||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import {
|
|
||||||
useCreateNextDocument,
|
|
||||||
useNextWebCrawl,
|
|
||||||
useRunNextDocument,
|
|
||||||
useSaveNextDocumentName,
|
|
||||||
useSetDocumentMeta,
|
|
||||||
useSetNextDocumentParser,
|
|
||||||
useUploadNextDocument,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
|
||||||
import { UploadFile } from 'antd';
|
|
||||||
import { TableRowSelection } from 'antd/es/table/interface';
|
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { useNavigate } from 'umi';
|
|
||||||
import { KnowledgeRouteKey } from './constant';
|
|
||||||
|
|
||||||
export const useNavigateToOtherPage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
|
||||||
|
|
||||||
const linkToUploadPage = useCallback(() => {
|
|
||||||
navigate(`/knowledge/dataset/upload?id=${knowledgeId}`);
|
|
||||||
}, [navigate, knowledgeId]);
|
|
||||||
|
|
||||||
const toChunk = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
navigate(
|
|
||||||
`/knowledge/${KnowledgeRouteKey.Dataset}/chunk?id=${knowledgeId}&doc_id=${id}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[navigate, knowledgeId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { linkToUploadPage, toChunk };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRenameDocument = (documentId: string) => {
|
|
||||||
const { saveName, loading } = useSaveNextDocumentName();
|
|
||||||
|
|
||||||
const {
|
|
||||||
visible: renameVisible,
|
|
||||||
hideModal: hideRenameModal,
|
|
||||||
showModal: showRenameModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const onRenameOk = useCallback(
|
|
||||||
async (name: string) => {
|
|
||||||
const ret = await saveName({ documentId, name });
|
|
||||||
if (ret === 0) {
|
|
||||||
hideRenameModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hideRenameModal, saveName, documentId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
renameLoading: loading,
|
|
||||||
onRenameOk,
|
|
||||||
renameVisible,
|
|
||||||
hideRenameModal,
|
|
||||||
showRenameModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useCreateEmptyDocument = () => {
|
|
||||||
const { createDocument, loading } = useCreateNextDocument();
|
|
||||||
|
|
||||||
const {
|
|
||||||
visible: createVisible,
|
|
||||||
hideModal: hideCreateModal,
|
|
||||||
showModal: showCreateModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const onCreateOk = useCallback(
|
|
||||||
async (name: string) => {
|
|
||||||
const ret = await createDocument(name);
|
|
||||||
if (ret === 0) {
|
|
||||||
hideCreateModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hideCreateModal, createDocument],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createLoading: loading,
|
|
||||||
onCreateOk,
|
|
||||||
createVisible,
|
|
||||||
hideCreateModal,
|
|
||||||
showCreateModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChangeDocumentParser = (documentId: string) => {
|
|
||||||
const { setDocumentParser, loading } = useSetNextDocumentParser();
|
|
||||||
|
|
||||||
const {
|
|
||||||
visible: changeParserVisible,
|
|
||||||
hideModal: hideChangeParserModal,
|
|
||||||
showModal: showChangeParserModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const onChangeParserOk = useCallback(
|
|
||||||
async (parserId: string, parserConfig: IChangeParserConfigRequestBody) => {
|
|
||||||
const ret = await setDocumentParser({
|
|
||||||
parserId,
|
|
||||||
documentId,
|
|
||||||
parserConfig,
|
|
||||||
});
|
|
||||||
if (ret === 0) {
|
|
||||||
hideChangeParserModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hideChangeParserModal, setDocumentParser, documentId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
changeParserLoading: loading,
|
|
||||||
onChangeParserOk,
|
|
||||||
changeParserVisible,
|
|
||||||
hideChangeParserModal,
|
|
||||||
showChangeParserModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetRowSelection = () => {
|
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
||||||
|
|
||||||
const rowSelection: TableRowSelection<IDocumentInfo> = {
|
|
||||||
selectedRowKeys,
|
|
||||||
onChange: (newSelectedRowKeys: React.Key[]) => {
|
|
||||||
setSelectedRowKeys(newSelectedRowKeys);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return rowSelection;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleUploadDocument = () => {
|
|
||||||
const {
|
|
||||||
visible: documentUploadVisible,
|
|
||||||
hideModal: hideDocumentUploadModal,
|
|
||||||
showModal: showDocumentUploadModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|
||||||
const [uploadProgress, setUploadProgress] = useState<number>(0);
|
|
||||||
const { uploadDocument, loading } = useUploadNextDocument();
|
|
||||||
const { runDocumentByIds } = useRunNextDocument();
|
|
||||||
|
|
||||||
const onDocumentUploadOk = useCallback(
|
|
||||||
async ({
|
|
||||||
parseOnCreation,
|
|
||||||
directoryFileList,
|
|
||||||
}: {
|
|
||||||
directoryFileList: UploadFile[];
|
|
||||||
parseOnCreation: boolean;
|
|
||||||
}): Promise<number | undefined> => {
|
|
||||||
const processFileGroup = async (filesPart: UploadFile[]) => {
|
|
||||||
// set status to uploading on files
|
|
||||||
setFileList(
|
|
||||||
fileList.map((file) => {
|
|
||||||
if (!filesPart.includes(file)) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newFile = file;
|
|
||||||
newFile.status = 'uploading';
|
|
||||||
newFile.percent = 1;
|
|
||||||
return newFile;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const ret = await uploadDocument(filesPart);
|
|
||||||
|
|
||||||
const files = ret?.data || [];
|
|
||||||
const successfulFilenames = files.map((file: any) => file.name);
|
|
||||||
|
|
||||||
// set status to done or error on files (based on response)
|
|
||||||
setFileList(
|
|
||||||
fileList.map((file) => {
|
|
||||||
if (!filesPart.includes(file)) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newFile = file;
|
|
||||||
newFile.status = successfulFilenames.includes(file.name)
|
|
||||||
? 'done'
|
|
||||||
: 'error';
|
|
||||||
newFile.percent = 100;
|
|
||||||
newFile.response = ret.message;
|
|
||||||
return newFile;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: ret?.code,
|
|
||||||
fileIds: files.map((file: any) => file.id),
|
|
||||||
totalSuccess: successfulFilenames.length,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const totalFiles = fileList.length;
|
|
||||||
|
|
||||||
if (directoryFileList.length > 0) {
|
|
||||||
const ret = await uploadDocument(directoryFileList);
|
|
||||||
if (ret?.code === 0) {
|
|
||||||
hideDocumentUploadModal();
|
|
||||||
}
|
|
||||||
if (totalFiles === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalFiles === 0) {
|
|
||||||
console.log('No files to upload');
|
|
||||||
hideDocumentUploadModal();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalSuccess = 0;
|
|
||||||
let codes = [];
|
|
||||||
let toRunFileIds: any[] = [];
|
|
||||||
for (let i = 0; i < totalFiles; i += 10) {
|
|
||||||
setUploadProgress(Math.floor((i / totalFiles) * 100));
|
|
||||||
const files = fileList.slice(i, i + 10);
|
|
||||||
const {
|
|
||||||
code,
|
|
||||||
totalSuccess: count,
|
|
||||||
fileIds,
|
|
||||||
} = await processFileGroup(files);
|
|
||||||
codes.push(code);
|
|
||||||
totalSuccess += count;
|
|
||||||
toRunFileIds = toRunFileIds.concat(fileIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allSuccess = codes.every((code) => code === 0);
|
|
||||||
const any500 = codes.some((code) => code === 500);
|
|
||||||
|
|
||||||
let code = 500;
|
|
||||||
if (allSuccess || (any500 && totalSuccess === totalFiles)) {
|
|
||||||
code = 0;
|
|
||||||
hideDocumentUploadModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseOnCreation) {
|
|
||||||
await runDocumentByIds({
|
|
||||||
documentIds: toRunFileIds,
|
|
||||||
run: 1,
|
|
||||||
shouldDelete: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setUploadProgress(100);
|
|
||||||
|
|
||||||
return code;
|
|
||||||
},
|
|
||||||
[fileList, uploadDocument, hideDocumentUploadModal, runDocumentByIds],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
documentUploadLoading: loading,
|
|
||||||
onDocumentUploadOk,
|
|
||||||
documentUploadVisible,
|
|
||||||
hideDocumentUploadModal,
|
|
||||||
showDocumentUploadModal,
|
|
||||||
uploadFileList: fileList,
|
|
||||||
setUploadFileList: setFileList,
|
|
||||||
uploadProgress,
|
|
||||||
setUploadProgress,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleWebCrawl = () => {
|
|
||||||
const {
|
|
||||||
visible: webCrawlUploadVisible,
|
|
||||||
hideModal: hideWebCrawlUploadModal,
|
|
||||||
showModal: showWebCrawlUploadModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
const { webCrawl, loading } = useNextWebCrawl();
|
|
||||||
|
|
||||||
const onWebCrawlUploadOk = useCallback(
|
|
||||||
async (name: string, url: string) => {
|
|
||||||
const ret = await webCrawl({ name, url });
|
|
||||||
if (ret === 0) {
|
|
||||||
hideWebCrawlUploadModal();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
[webCrawl, hideWebCrawlUploadModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
webCrawlUploadLoading: loading,
|
|
||||||
onWebCrawlUploadOk,
|
|
||||||
webCrawlUploadVisible,
|
|
||||||
hideWebCrawlUploadModal,
|
|
||||||
showWebCrawlUploadModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleRunDocumentByIds = (id: string) => {
|
|
||||||
const { runDocumentByIds, loading } = useRunNextDocument();
|
|
||||||
const [currentId, setCurrentId] = useState<string>('');
|
|
||||||
const isLoading = loading && currentId !== '' && currentId === id;
|
|
||||||
|
|
||||||
const handleRunDocumentByIds = async (
|
|
||||||
documentId: string,
|
|
||||||
isRunning: boolean,
|
|
||||||
shouldDelete: boolean = false,
|
|
||||||
) => {
|
|
||||||
if (isLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCurrentId(documentId);
|
|
||||||
try {
|
|
||||||
await runDocumentByIds({
|
|
||||||
documentIds: [documentId],
|
|
||||||
run: isRunning ? 2 : 1,
|
|
||||||
shouldDelete,
|
|
||||||
});
|
|
||||||
setCurrentId('');
|
|
||||||
} catch (error) {
|
|
||||||
setCurrentId('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleRunDocumentByIds,
|
|
||||||
loading: isLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useShowMetaModal = (documentId: string) => {
|
|
||||||
const { setDocumentMeta, loading } = useSetDocumentMeta();
|
|
||||||
|
|
||||||
const {
|
|
||||||
visible: setMetaVisible,
|
|
||||||
hideModal: hideSetMetaModal,
|
|
||||||
showModal: showSetMetaModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const onSetMetaModalOk = useCallback(
|
|
||||||
async (meta: string) => {
|
|
||||||
const ret = await setDocumentMeta({
|
|
||||||
documentId,
|
|
||||||
meta,
|
|
||||||
});
|
|
||||||
if (ret === 0) {
|
|
||||||
hideSetMetaModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setDocumentMeta, documentId, hideSetMetaModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
setMetaLoading: loading,
|
|
||||||
onSetMetaModalOk,
|
|
||||||
setMetaVisible,
|
|
||||||
hideSetMetaModal,
|
|
||||||
showSetMetaModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
.datasetWrapper {
|
|
||||||
padding: 30px 30px 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTable {
|
|
||||||
tbody {
|
|
||||||
// height: calc(100vh - 508px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter {
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
margin: 10px 0;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 24px 0;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteIconWrapper {
|
|
||||||
width: 22px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toChunks {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageInputNumber {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.questionIcon {
|
|
||||||
margin-inline-start: 4px;
|
|
||||||
color: rgba(0, 0, 0, 0.45);
|
|
||||||
cursor: help;
|
|
||||||
writing-mode: horizontal-tb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nameText {
|
|
||||||
color: #1677ff;
|
|
||||||
}
|
|
||||||
@ -1,275 +0,0 @@
|
|||||||
import ChunkMethodModal from '@/components/chunk-method-modal';
|
|
||||||
import SvgIcon from '@/components/svg-icon';
|
|
||||||
import {
|
|
||||||
useFetchNextDocumentList,
|
|
||||||
useSetNextDocumentStatus,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
|
||||||
import { useSelectParserList } from '@/hooks/user-setting-hooks';
|
|
||||||
import { getExtension } from '@/utils/document-util';
|
|
||||||
import { Divider, Flex, Switch, Table, Tooltip, Typography } from 'antd';
|
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import CreateFileModal from './create-file-modal';
|
|
||||||
import DocumentToolbar from './document-toolbar';
|
|
||||||
import {
|
|
||||||
useChangeDocumentParser,
|
|
||||||
useCreateEmptyDocument,
|
|
||||||
useGetRowSelection,
|
|
||||||
useHandleUploadDocument,
|
|
||||||
useHandleWebCrawl,
|
|
||||||
useNavigateToOtherPage,
|
|
||||||
useRenameDocument,
|
|
||||||
useShowMetaModal,
|
|
||||||
} from './hooks';
|
|
||||||
import ParsingActionCell from './parsing-action-cell';
|
|
||||||
import ParsingStatusCell from './parsing-status-cell';
|
|
||||||
import RenameModal from './rename-modal';
|
|
||||||
import WebCrawlModal from './web-crawl-modal';
|
|
||||||
|
|
||||||
import FileUploadModal from '@/components/file-upload-modal';
|
|
||||||
import { RunningStatus } from '@/constants/knowledge';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import { formatDate } from '@/utils/date';
|
|
||||||
import { CircleHelp } from 'lucide-react';
|
|
||||||
import styles from './index.less';
|
|
||||||
import { SetMetaModal } from './set-meta-modal';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const KnowledgeFile = () => {
|
|
||||||
const { searchString, documents, pagination, handleInputChange } =
|
|
||||||
useFetchNextDocumentList();
|
|
||||||
const parserList = useSelectParserList();
|
|
||||||
const { setDocumentStatus } = useSetNextDocumentStatus();
|
|
||||||
const { toChunk } = useNavigateToOtherPage();
|
|
||||||
const { currentRecord, setRecord } = useSetSelectedRecord<IDocumentInfo>();
|
|
||||||
const {
|
|
||||||
renameLoading,
|
|
||||||
onRenameOk,
|
|
||||||
renameVisible,
|
|
||||||
hideRenameModal,
|
|
||||||
showRenameModal,
|
|
||||||
} = useRenameDocument(currentRecord.id);
|
|
||||||
const {
|
|
||||||
createLoading,
|
|
||||||
onCreateOk,
|
|
||||||
createVisible,
|
|
||||||
hideCreateModal,
|
|
||||||
showCreateModal,
|
|
||||||
} = useCreateEmptyDocument();
|
|
||||||
const {
|
|
||||||
changeParserLoading,
|
|
||||||
onChangeParserOk,
|
|
||||||
changeParserVisible,
|
|
||||||
hideChangeParserModal,
|
|
||||||
showChangeParserModal,
|
|
||||||
} = useChangeDocumentParser(currentRecord.id);
|
|
||||||
const {
|
|
||||||
documentUploadVisible,
|
|
||||||
hideDocumentUploadModal,
|
|
||||||
showDocumentUploadModal,
|
|
||||||
onDocumentUploadOk,
|
|
||||||
documentUploadLoading,
|
|
||||||
uploadFileList,
|
|
||||||
setUploadFileList,
|
|
||||||
uploadProgress,
|
|
||||||
setUploadProgress,
|
|
||||||
} = useHandleUploadDocument();
|
|
||||||
const {
|
|
||||||
webCrawlUploadVisible,
|
|
||||||
hideWebCrawlUploadModal,
|
|
||||||
showWebCrawlUploadModal,
|
|
||||||
onWebCrawlUploadOk,
|
|
||||||
webCrawlUploadLoading,
|
|
||||||
} = useHandleWebCrawl();
|
|
||||||
const { t } = useTranslation('translation', {
|
|
||||||
keyPrefix: 'knowledgeDetails',
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
showSetMetaModal,
|
|
||||||
hideSetMetaModal,
|
|
||||||
setMetaVisible,
|
|
||||||
setMetaLoading,
|
|
||||||
onSetMetaModalOk,
|
|
||||||
} = useShowMetaModal(currentRecord.id);
|
|
||||||
|
|
||||||
const rowSelection = useGetRowSelection();
|
|
||||||
|
|
||||||
const columns: ColumnsType<IDocumentInfo> = [
|
|
||||||
{
|
|
||||||
title: t('name'),
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
fixed: 'left',
|
|
||||||
render: (text: any, { id, thumbnail, name }) => (
|
|
||||||
<div className={styles.toChunks} onClick={() => toChunk(id)}>
|
|
||||||
<Flex gap={10} align="center">
|
|
||||||
{thumbnail ? (
|
|
||||||
<img className={styles.img} src={thumbnail} alt="" />
|
|
||||||
) : (
|
|
||||||
<SvgIcon
|
|
||||||
name={`file-icon/${getExtension(name)}`}
|
|
||||||
width={24}
|
|
||||||
></SvgIcon>
|
|
||||||
)}
|
|
||||||
<Text ellipsis={{ tooltip: text }} className={styles.nameText}>
|
|
||||||
{text}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('chunkNumber'),
|
|
||||||
dataIndex: 'chunk_num',
|
|
||||||
key: 'chunk_num',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('uploadDate'),
|
|
||||||
dataIndex: 'create_time',
|
|
||||||
key: 'create_time',
|
|
||||||
render(value) {
|
|
||||||
return formatDate(value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('chunkMethod'),
|
|
||||||
dataIndex: 'parser_id',
|
|
||||||
key: 'parser_id',
|
|
||||||
render: (text) => {
|
|
||||||
return parserList.find((x) => x.value === text)?.label;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('enabled'),
|
|
||||||
key: 'status',
|
|
||||||
dataIndex: 'status',
|
|
||||||
render: (_, { status, id }) => (
|
|
||||||
<>
|
|
||||||
<Switch
|
|
||||||
checked={status === '1'}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDocumentStatus({ status: e, documentId: id });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
{t('parsingStatus')}
|
|
||||||
<Tooltip title={t('parsingStatusTip')}>
|
|
||||||
<CircleHelp className="size-3" />
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
dataIndex: 'run',
|
|
||||||
key: 'run',
|
|
||||||
filters: Object.values(RunningStatus).map((value) => ({
|
|
||||||
text: t(`runningStatus${value}`),
|
|
||||||
value: value,
|
|
||||||
})),
|
|
||||||
onFilter: (value, record: IDocumentInfo) => record.run === value,
|
|
||||||
render: (text, record) => {
|
|
||||||
return <ParsingStatusCell record={record}></ParsingStatusCell>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('action'),
|
|
||||||
key: 'action',
|
|
||||||
render: (_, record) => (
|
|
||||||
<ParsingActionCell
|
|
||||||
setCurrentRecord={setRecord}
|
|
||||||
showRenameModal={showRenameModal}
|
|
||||||
showChangeParserModal={showChangeParserModal}
|
|
||||||
showSetMetaModal={showSetMetaModal}
|
|
||||||
record={record}
|
|
||||||
></ParsingActionCell>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const finalColumns = columns.map((x) => ({
|
|
||||||
...x,
|
|
||||||
className: `${styles.column}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.datasetWrapper}>
|
|
||||||
<h3>{t('dataset')}</h3>
|
|
||||||
<p>{t('datasetDescription')}</p>
|
|
||||||
<Divider></Divider>
|
|
||||||
<DocumentToolbar
|
|
||||||
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
|
|
||||||
showCreateModal={showCreateModal}
|
|
||||||
showWebCrawlModal={showWebCrawlUploadModal}
|
|
||||||
showDocumentUploadModal={showDocumentUploadModal}
|
|
||||||
searchString={searchString}
|
|
||||||
handleInputChange={handleInputChange}
|
|
||||||
documents={documents}
|
|
||||||
></DocumentToolbar>
|
|
||||||
<Table
|
|
||||||
rowKey="id"
|
|
||||||
columns={finalColumns}
|
|
||||||
dataSource={documents}
|
|
||||||
pagination={pagination}
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
className={styles.documentTable}
|
|
||||||
scroll={{ scrollToFirstRowOnChange: true, x: 1300 }}
|
|
||||||
/>
|
|
||||||
<CreateFileModal
|
|
||||||
visible={createVisible}
|
|
||||||
hideModal={hideCreateModal}
|
|
||||||
loading={createLoading}
|
|
||||||
onOk={onCreateOk}
|
|
||||||
/>
|
|
||||||
<ChunkMethodModal
|
|
||||||
documentId={currentRecord.id}
|
|
||||||
parserId={currentRecord.parser_id}
|
|
||||||
parserConfig={currentRecord.parser_config}
|
|
||||||
documentExtension={getExtension(currentRecord.name)}
|
|
||||||
onOk={onChangeParserOk}
|
|
||||||
visible={changeParserVisible}
|
|
||||||
hideModal={hideChangeParserModal}
|
|
||||||
loading={changeParserLoading}
|
|
||||||
/>
|
|
||||||
<RenameModal
|
|
||||||
visible={renameVisible}
|
|
||||||
onOk={onRenameOk}
|
|
||||||
loading={renameLoading}
|
|
||||||
hideModal={hideRenameModal}
|
|
||||||
initialName={currentRecord.name}
|
|
||||||
></RenameModal>
|
|
||||||
<FileUploadModal
|
|
||||||
visible={documentUploadVisible}
|
|
||||||
hideModal={hideDocumentUploadModal}
|
|
||||||
loading={documentUploadLoading}
|
|
||||||
onOk={onDocumentUploadOk}
|
|
||||||
uploadFileList={uploadFileList}
|
|
||||||
setUploadFileList={setUploadFileList}
|
|
||||||
uploadProgress={uploadProgress}
|
|
||||||
setUploadProgress={setUploadProgress}
|
|
||||||
></FileUploadModal>
|
|
||||||
<WebCrawlModal
|
|
||||||
visible={webCrawlUploadVisible}
|
|
||||||
hideModal={hideWebCrawlUploadModal}
|
|
||||||
loading={webCrawlUploadLoading}
|
|
||||||
onOk={onWebCrawlUploadOk}
|
|
||||||
></WebCrawlModal>
|
|
||||||
{setMetaVisible && (
|
|
||||||
<SetMetaModal
|
|
||||||
visible={setMetaVisible}
|
|
||||||
hideModal={hideSetMetaModal}
|
|
||||||
onOk={onSetMetaModalOk}
|
|
||||||
loading={setMetaLoading}
|
|
||||||
initialMetaData={currentRecord.meta_fields}
|
|
||||||
></SetMetaModal>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeFile;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.iconButton {
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
import { useShowDeleteConfirm, useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useRemoveNextDocument } from '@/hooks/document-hooks';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import { downloadDocument } from '@/utils/file-util';
|
|
||||||
import {
|
|
||||||
DeleteOutlined,
|
|
||||||
DownloadOutlined,
|
|
||||||
EditOutlined,
|
|
||||||
ToolOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Button, Dropdown, MenuProps, Space, Tooltip } from 'antd';
|
|
||||||
import { isParserRunning } from '../utils';
|
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { DocumentType } from '../constant';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
record: IDocumentInfo;
|
|
||||||
setCurrentRecord: (record: IDocumentInfo) => void;
|
|
||||||
showRenameModal: () => void;
|
|
||||||
showChangeParserModal: () => void;
|
|
||||||
showSetMetaModal: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ParsingActionCell = ({
|
|
||||||
record,
|
|
||||||
setCurrentRecord,
|
|
||||||
showRenameModal,
|
|
||||||
showChangeParserModal,
|
|
||||||
showSetMetaModal,
|
|
||||||
}: IProps) => {
|
|
||||||
const documentId = record.id;
|
|
||||||
const isRunning = isParserRunning(record.run);
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
const { removeDocument } = useRemoveNextDocument();
|
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
|
||||||
const isVirtualDocument = record.type === DocumentType.Virtual;
|
|
||||||
|
|
||||||
const onRmDocument = () => {
|
|
||||||
if (!isRunning) {
|
|
||||||
showDeleteConfirm({
|
|
||||||
onOk: () => removeDocument([documentId]),
|
|
||||||
content: record?.parser_config?.graphrag?.use_graphrag
|
|
||||||
? t('deleteDocumentConfirmContent')
|
|
||||||
: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDownloadDocument = () => {
|
|
||||||
downloadDocument({
|
|
||||||
id: documentId,
|
|
||||||
filename: record.name,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setRecord = useCallback(() => {
|
|
||||||
setCurrentRecord(record);
|
|
||||||
}, [record, setCurrentRecord]);
|
|
||||||
|
|
||||||
const onShowRenameModal = () => {
|
|
||||||
setRecord();
|
|
||||||
showRenameModal();
|
|
||||||
};
|
|
||||||
const onShowChangeParserModal = () => {
|
|
||||||
setRecord();
|
|
||||||
showChangeParserModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onShowSetMetaModal = useCallback(() => {
|
|
||||||
setRecord();
|
|
||||||
showSetMetaModal();
|
|
||||||
}, [setRecord, showSetMetaModal]);
|
|
||||||
|
|
||||||
const chunkItems: MenuProps['items'] = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<Button type="link" onClick={onShowChangeParserModal}>
|
|
||||||
{t('chunkMethod')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
label: (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<Button type="link" onClick={onShowSetMetaModal}>
|
|
||||||
{t('setMetaData')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space size={0}>
|
|
||||||
{isVirtualDocument || (
|
|
||||||
<Dropdown
|
|
||||||
menu={{ items: chunkItems }}
|
|
||||||
trigger={['click']}
|
|
||||||
disabled={isRunning || record.parser_id === 'tag'}
|
|
||||||
>
|
|
||||||
<Button type="text" className={styles.iconButton}>
|
|
||||||
<ToolOutlined size={20} />
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
<Tooltip title={t('rename', { keyPrefix: 'common' })}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
disabled={isRunning}
|
|
||||||
onClick={onShowRenameModal}
|
|
||||||
className={styles.iconButton}
|
|
||||||
>
|
|
||||||
<EditOutlined size={20} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
disabled={isRunning}
|
|
||||||
onClick={onRmDocument}
|
|
||||||
className={styles.iconButton}
|
|
||||||
>
|
|
||||||
<DeleteOutlined size={20} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
{isVirtualDocument || (
|
|
||||||
<Tooltip title={t('download', { keyPrefix: 'common' })}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
disabled={isRunning}
|
|
||||||
onClick={onDownloadDocument}
|
|
||||||
className={styles.iconButton}
|
|
||||||
>
|
|
||||||
<DownloadOutlined size={20} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ParsingActionCell;
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
.popoverContent {
|
|
||||||
width: 40vw;
|
|
||||||
|
|
||||||
.popoverContentItem {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popoverContentText {
|
|
||||||
white-space: pre-line;
|
|
||||||
max-height: 50vh;
|
|
||||||
overflow: auto;
|
|
||||||
.popoverContentErrorLabel {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.operationIcon {
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.operationIconSpin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
import { ReactComponent as CancelIcon } from '@/assets/svg/cancel.svg';
|
|
||||||
import { ReactComponent as RefreshIcon } from '@/assets/svg/refresh.svg';
|
|
||||||
import { ReactComponent as RunIcon } from '@/assets/svg/run.svg';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import {
|
|
||||||
Badge,
|
|
||||||
DescriptionsProps,
|
|
||||||
Flex,
|
|
||||||
Popconfirm,
|
|
||||||
Popover,
|
|
||||||
Space,
|
|
||||||
Tag,
|
|
||||||
} from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import reactStringReplace from 'react-string-replace';
|
|
||||||
import { DocumentType, RunningStatus, RunningStatusMap } from '../constant';
|
|
||||||
import { useHandleRunDocumentByIds } from '../hooks';
|
|
||||||
import { isParserRunning } from '../utils';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const iconMap = {
|
|
||||||
[RunningStatus.UNSTART]: RunIcon,
|
|
||||||
[RunningStatus.RUNNING]: CancelIcon,
|
|
||||||
[RunningStatus.CANCEL]: RefreshIcon,
|
|
||||||
[RunningStatus.DONE]: RefreshIcon,
|
|
||||||
[RunningStatus.FAIL]: RefreshIcon,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
record: IDocumentInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopoverContent = ({ record }: IProps) => {
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
|
|
||||||
const replaceText = (text: string) => {
|
|
||||||
// Remove duplicate \n
|
|
||||||
const nextText = text.replace(/(\n)\1+/g, '$1');
|
|
||||||
|
|
||||||
const replacedText = reactStringReplace(
|
|
||||||
nextText,
|
|
||||||
/(\[ERROR\].+\s)/g,
|
|
||||||
(match, i) => {
|
|
||||||
return (
|
|
||||||
<span key={i} className={styles.popoverContentErrorLabel}>
|
|
||||||
{match}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return replacedText;
|
|
||||||
};
|
|
||||||
|
|
||||||
const items: DescriptionsProps['items'] = [
|
|
||||||
{
|
|
||||||
key: 'process_begin_at',
|
|
||||||
label: t('processBeginAt'),
|
|
||||||
children: record.process_begin_at,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'process_duration',
|
|
||||||
label: t('processDuration'),
|
|
||||||
children: `${record.process_duration.toFixed(2)} s`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'progress_msg',
|
|
||||||
label: t('progressMsg'),
|
|
||||||
children: replaceText(record.progress_msg.trim()),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex vertical className={styles.popoverContent}>
|
|
||||||
{items.map((x, idx) => {
|
|
||||||
return (
|
|
||||||
<div key={x.key} className={idx < 2 ? styles.popoverContentItem : ''}>
|
|
||||||
<b>{x.label}:</b>
|
|
||||||
<div className={styles.popoverContentText}>{x.children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ParsingStatusCell = ({ record }: IProps) => {
|
|
||||||
const text = record.run;
|
|
||||||
const runningStatus = RunningStatusMap[text];
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { handleRunDocumentByIds } = useHandleRunDocumentByIds(record.id);
|
|
||||||
|
|
||||||
const isRunning = isParserRunning(text);
|
|
||||||
|
|
||||||
const OperationIcon = iconMap[text];
|
|
||||||
|
|
||||||
const label = t(`knowledgeDetails.runningStatus${text}`);
|
|
||||||
|
|
||||||
const handleOperationIconClick =
|
|
||||||
(shouldDelete: boolean = false) =>
|
|
||||||
() => {
|
|
||||||
handleRunDocumentByIds(record.id, isRunning, shouldDelete);
|
|
||||||
};
|
|
||||||
|
|
||||||
return record.type === DocumentType.Virtual ? null : (
|
|
||||||
<Flex justify={'space-between'} align="center">
|
|
||||||
<Popover content={<PopoverContent record={record}></PopoverContent>}>
|
|
||||||
<Tag color={runningStatus.color}>
|
|
||||||
{isRunning ? (
|
|
||||||
<Space>
|
|
||||||
<Badge color={runningStatus.color} />
|
|
||||||
{label}
|
|
||||||
<span>{(record.progress * 100).toFixed(2)}%</span>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
label
|
|
||||||
)}
|
|
||||||
</Tag>
|
|
||||||
</Popover>
|
|
||||||
<Popconfirm
|
|
||||||
title={t(`knowledgeDetails.redo`, { chunkNum: record.chunk_num })}
|
|
||||||
onConfirm={handleOperationIconClick(true)}
|
|
||||||
onCancel={handleOperationIconClick(false)}
|
|
||||||
disabled={record.chunk_num === 0}
|
|
||||||
okText={t('common.yes')}
|
|
||||||
cancelText={t('common.no')}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames(styles.operationIcon)}
|
|
||||||
onClick={
|
|
||||||
record.chunk_num === 0 ? handleOperationIconClick(false) : () => {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<OperationIcon />
|
|
||||||
</div>
|
|
||||||
</Popconfirm>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ParsingStatusCell;
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Modal } from 'antd';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
|
||||||
loading: boolean;
|
|
||||||
initialName: string;
|
|
||||||
onOk: (name: string) => void;
|
|
||||||
showModal?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenameModal = ({
|
|
||||||
visible,
|
|
||||||
onOk,
|
|
||||||
loading,
|
|
||||||
initialName,
|
|
||||||
hideModal,
|
|
||||||
}: IProps) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { t } = useTranslate('common');
|
|
||||||
type FieldType = {
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOk = async () => {
|
|
||||||
const ret = await form.validateFields();
|
|
||||||
onOk(ret.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinish = (values: any) => {
|
|
||||||
console.log('Success:', values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinishFailed = (errorInfo: any) => {
|
|
||||||
console.log('Failed:', errorInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
form.setFieldValue('name', initialName);
|
|
||||||
}
|
|
||||||
}, [initialName, form, visible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t('rename')}
|
|
||||||
open={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
okButtonProps={{ loading }}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
labelCol={{ span: 4 }}
|
|
||||||
wrapperCol={{ span: 20 }}
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
onFinish={onFinish}
|
|
||||||
onFinishFailed={onFinishFailed}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label={t('name')}
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: t('namePlaceholder') }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RenameModal;
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
|
||||||
import Editor, { loader } from '@monaco-editor/react';
|
|
||||||
|
|
||||||
import { Form, Modal } from 'antd';
|
|
||||||
import DOMPurify from 'dompurify';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
|
||||||
|
|
||||||
type FieldType = {
|
|
||||||
meta?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SetMetaModal({
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
onOk,
|
|
||||||
initialMetaData,
|
|
||||||
}: IModalProps<any> & { initialMetaData?: IDocumentInfo['meta_fields'] }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const handleOk = useCallback(async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
onOk?.(values.meta);
|
|
||||||
}, [form, onOk]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setFieldValue('meta', JSON.stringify(initialMetaData, null, 4));
|
|
||||||
}, [form, initialMetaData]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t('knowledgeDetails.setMetaData')}
|
|
||||||
open={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
initialValues={{ remember: true }}
|
|
||||||
autoComplete="off"
|
|
||||||
layout={'vertical'}
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
label={t('knowledgeDetails.metaData')}
|
|
||||||
name="meta"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
validator(rule, value) {
|
|
||||||
try {
|
|
||||||
JSON.parse(value);
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(
|
|
||||||
new Error(t('knowledgeDetails.pleaseInputJson')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
tooltip={
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(
|
|
||||||
t('knowledgeDetails.documentMetaTips'),
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Editor height={200} defaultLanguage="json" theme="vs-dark" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { RunningStatus } from './constant';
|
|
||||||
|
|
||||||
export const isParserRunning = (text: RunningStatus) => {
|
|
||||||
const isRunning = text === RunningStatus.RUNNING;
|
|
||||||
return isRunning;
|
|
||||||
};
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Modal } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
|
||||||
loading: boolean;
|
|
||||||
onOk: (name: string, url: string) => void;
|
|
||||||
showModal?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WebCrawlModal: React.FC<IProps> = ({ visible, hideModal, onOk }) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
const handleOk = async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
onOk(values.name, values.url);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t('webCrawl')}
|
|
||||||
open={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
name="validateOnly"
|
|
||||||
labelCol={{ span: 4 }}
|
|
||||||
wrapperCol={{ span: 20 }}
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label="Name"
|
|
||||||
name="name"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Please input name!' },
|
|
||||||
{
|
|
||||||
max: 10,
|
|
||||||
message: 'The maximum length of name is 128 characters',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input placeholder="Document name" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="URL"
|
|
||||||
name="url"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Please input url!' },
|
|
||||||
{
|
|
||||||
pattern: new RegExp(
|
|
||||||
'(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]',
|
|
||||||
),
|
|
||||||
message: 'Please enter a valid URL!',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input placeholder="https://www.baidu.com" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default WebCrawlModal;
|
|
||||||
@ -1,241 +0,0 @@
|
|||||||
const nodes = [
|
|
||||||
{
|
|
||||||
type: '"ORGANIZATION"',
|
|
||||||
description:
|
|
||||||
'"厦门象屿是一家公司,其营业收入和市场占有率在2018年至2022年间有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
id: '"厦门象屿"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"2018年是一个时间点,标志着厦门象屿营业收入和市场占有率的记录开始。"',
|
|
||||||
source_id: '0',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2018"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"2019年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2019"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"2020年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2020"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"2021年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2021"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"2022年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2022"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"ORGANIZATION"',
|
|
||||||
description:
|
|
||||||
'"厦门象屿股份有限公司是一家公司,中文简称为厦门象屿,外文名称为Xiamen Xiangyu Co.,Ltd.,外文名称缩写为Xiangyu,法定代表人为邓启东。"',
|
|
||||||
source_id: '1',
|
|
||||||
id: '"厦门象屿股份有限公司"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"PERSON"',
|
|
||||||
description: '"邓启东是厦门象屿股份有限公司的法定代表人。"',
|
|
||||||
source_id: '1',
|
|
||||||
entity_type: '"PERSON"',
|
|
||||||
id: '"邓启东"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"GEO"',
|
|
||||||
description: '"厦门是一个地理位置,与厦门象屿股份有限公司相关。"',
|
|
||||||
source_id: '1',
|
|
||||||
entity_type: '"GEO"',
|
|
||||||
id: '"厦门"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"PERSON"',
|
|
||||||
description:
|
|
||||||
'"廖杰 is the Board Secretary, responsible for handling board-related matters and communications."',
|
|
||||||
source_id: '2',
|
|
||||||
id: '"廖杰"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"PERSON"',
|
|
||||||
description:
|
|
||||||
'"史经洋 is the Securities Affairs Representative, responsible for handling securities-related matters and communications."',
|
|
||||||
source_id: '2',
|
|
||||||
entity_type: '"PERSON"',
|
|
||||||
id: '"史经洋"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"GEO"',
|
|
||||||
description:
|
|
||||||
'"A geographic location in Xiamen, specifically in the Free Trade Zone, where the company\'s office is situated."',
|
|
||||||
source_id: '2',
|
|
||||||
entity_type: '"GEO"',
|
|
||||||
id: '"厦门市湖里区自由贸易试验区厦门片区"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"GEO"',
|
|
||||||
description:
|
|
||||||
'"The building where the company\'s office is located, situated at Xiangyu Road, Xiamen."',
|
|
||||||
source_id: '2',
|
|
||||||
entity_type: '"GEO"',
|
|
||||||
id: '"象屿集团大厦"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"Refers to the year 2021, used for comparing financial metrics with the year 2022."',
|
|
||||||
source_id: '3',
|
|
||||||
id: '"2021年"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"Refers to the year 2022, used for presenting current financial metrics and comparing them with the year 2021."',
|
|
||||||
source_id: '3',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"2022年"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: '"EVENT"',
|
|
||||||
description:
|
|
||||||
'"Indicates the focus on key financial metrics in the table, such as weighted averages and percentages."',
|
|
||||||
source_id: '3',
|
|
||||||
entity_type: '"EVENT"',
|
|
||||||
id: '"主要财务指标"',
|
|
||||||
},
|
|
||||||
].map(({ type, ...x }) => ({ ...x }));
|
|
||||||
|
|
||||||
const edges = [
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿在2018年的营业收入和市场占有率被记录。"',
|
|
||||||
source_id: '0',
|
|
||||||
source: '"厦门象屿"',
|
|
||||||
target: '"2018"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿在2019年的营业收入和市场占有率有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
source: '"厦门象屿"',
|
|
||||||
target: '"2019"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿在2020年的营业收入和市场占有率有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
source: '"厦门象屿"',
|
|
||||||
target: '"2020"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿在2021年的营业收入和市场占有率有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
source: '"厦门象屿"',
|
|
||||||
target: '"2021"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿在2022年的营业收入和市场占有率有所变化。"',
|
|
||||||
source_id: '0',
|
|
||||||
source: '"厦门象屿"',
|
|
||||||
target: '"2022"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿股份有限公司的法定代表人是邓启东。"',
|
|
||||||
source_id: '1',
|
|
||||||
source: '"厦门象屿股份有限公司"',
|
|
||||||
target: '"邓启东"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description: '"厦门象屿股份有限公司位于厦门。"',
|
|
||||||
source_id: '1',
|
|
||||||
source: '"厦门象屿股份有限公司"',
|
|
||||||
target: '"厦门"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"廖杰\'s office is located in the Xiangyu Group Building, indicating his workplace."',
|
|
||||||
source_id: '2',
|
|
||||||
source: '"廖杰"',
|
|
||||||
target: '"象屿集团大厦"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"廖杰 works in the Xiamen Free Trade Zone, a specific area within Xiamen."',
|
|
||||||
source_id: '2',
|
|
||||||
source: '"廖杰"',
|
|
||||||
target: '"厦门市湖里区自由贸易试验区厦门片区"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"史经洋\'s office is also located in the Xiangyu Group Building, indicating his workplace."',
|
|
||||||
source_id: '2',
|
|
||||||
source: '"史经洋"',
|
|
||||||
target: '"象屿集团大厦"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"史经洋 works in the Xiamen Free Trade Zone, a specific area within Xiamen."',
|
|
||||||
source_id: '2',
|
|
||||||
source: '"史经洋"',
|
|
||||||
target: '"厦门市湖里区自由贸易试验区厦门片区"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"The years 2021 and 2022 are related as they are used for comparing financial metrics, showing changes and adjustments over time."',
|
|
||||||
source_id: '3',
|
|
||||||
source: '"2021年"',
|
|
||||||
target: '"2022年"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"The \'主要财务指标\' is related to the year 2021 as it provides the basis for financial comparisons and adjustments."',
|
|
||||||
source_id: '3',
|
|
||||||
source: '"2021年"',
|
|
||||||
target: '"主要财务指标"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
weight: 2.0,
|
|
||||||
description:
|
|
||||||
'"The \'主要财务指标\' is related to the year 2022 as it presents the current financial metrics and their changes compared to 2021."',
|
|
||||||
source_id: '3',
|
|
||||||
source: '"2022年"',
|
|
||||||
target: '"主要财务指标"',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const graphData = {
|
|
||||||
directed: false,
|
|
||||||
multigraph: false,
|
|
||||||
graph: {},
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
combos: [],
|
|
||||||
};
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
import { ElementDatum, Graph, IElementEvent } from '@antv/g6';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
||||||
import { buildNodesAndCombos } from './util';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const TooltipColorMap = {
|
|
||||||
combo: 'red',
|
|
||||||
node: 'black',
|
|
||||||
edge: 'blue',
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: any;
|
|
||||||
show: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ForceGraph = ({ data, show }: IProps) => {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const graphRef = useRef<Graph | null>(null);
|
|
||||||
|
|
||||||
const nextData = useMemo(() => {
|
|
||||||
if (!isEmpty(data)) {
|
|
||||||
const graphData = data;
|
|
||||||
const mi = buildNodesAndCombos(graphData.nodes);
|
|
||||||
return { edges: graphData.edges, ...mi };
|
|
||||||
}
|
|
||||||
return { nodes: [], edges: [] };
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const render = useCallback(() => {
|
|
||||||
const graph = new Graph({
|
|
||||||
container: containerRef.current!,
|
|
||||||
autoFit: 'view',
|
|
||||||
autoResize: true,
|
|
||||||
behaviors: [
|
|
||||||
'drag-element',
|
|
||||||
'drag-canvas',
|
|
||||||
'zoom-canvas',
|
|
||||||
'collapse-expand',
|
|
||||||
{
|
|
||||||
type: 'hover-activate',
|
|
||||||
degree: 1, // 👈🏻 Activate relations.
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
type: 'tooltip',
|
|
||||||
enterable: true,
|
|
||||||
getContent: (e: IElementEvent, items: ElementDatum) => {
|
|
||||||
if (Array.isArray(items)) {
|
|
||||||
if (items.some((x) => x?.isCombo)) {
|
|
||||||
return `<p style="font-weight:600;color:red">${items?.[0]?.data?.label}</p>`;
|
|
||||||
}
|
|
||||||
let result = ``;
|
|
||||||
items.forEach((item) => {
|
|
||||||
result += `<section style="color:${TooltipColorMap[e['targetType'] as keyof typeof TooltipColorMap]};"><h3>${item?.id}</h3>`;
|
|
||||||
if (item?.entity_type) {
|
|
||||||
result += `<div style="padding-bottom: 6px;"><b>Entity type: </b>${item?.entity_type}</div>`;
|
|
||||||
}
|
|
||||||
if (item?.weight) {
|
|
||||||
result += `<div><b>Weight: </b>${item?.weight}</div>`;
|
|
||||||
}
|
|
||||||
if (item?.description) {
|
|
||||||
result += `<p>${item?.description}</p>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result + '</section>';
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
layout: {
|
|
||||||
type: 'combo-combined',
|
|
||||||
preventOverlap: true,
|
|
||||||
comboPadding: 1,
|
|
||||||
spacing: 100,
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
style: {
|
|
||||||
size: 150,
|
|
||||||
labelText: (d) => d.id,
|
|
||||||
// labelPadding: 30,
|
|
||||||
labelFontSize: 40,
|
|
||||||
// labelOffsetX: 20,
|
|
||||||
labelOffsetY: 20,
|
|
||||||
labelPlacement: 'center',
|
|
||||||
labelWordWrap: true,
|
|
||||||
},
|
|
||||||
palette: {
|
|
||||||
type: 'group',
|
|
||||||
field: (d) => {
|
|
||||||
return d?.entity_type as string;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
edge: {
|
|
||||||
style: (model) => {
|
|
||||||
const weight: number = Number(model?.weight) || 2;
|
|
||||||
const lineWeight = weight * 4;
|
|
||||||
return {
|
|
||||||
stroke: '#99ADD1',
|
|
||||||
lineWidth: lineWeight > 10 ? 10 : lineWeight,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (graphRef.current) {
|
|
||||||
graphRef.current.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
graphRef.current = graph;
|
|
||||||
|
|
||||||
graph.setData(nextData);
|
|
||||||
|
|
||||||
graph.render();
|
|
||||||
}, [nextData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEmpty(data)) {
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
}, [data, render]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className={styles.forceContainer}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
display: show ? 'block' : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ForceGraph;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
.forceContainer {
|
|
||||||
:global(.tooltip) {
|
|
||||||
border-radius: 10px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { useFetchKnowledgeGraph } from '@/hooks/knowledge-hooks';
|
|
||||||
import { Trash2 } from 'lucide-react';
|
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ForceGraph from './force-graph';
|
|
||||||
import { useDeleteKnowledgeGraph } from './use-delete-graph';
|
|
||||||
|
|
||||||
const KnowledgeGraph: React.FC = () => {
|
|
||||||
const { data } = useFetchKnowledgeGraph();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { handleDeleteKnowledgeGraph } = useDeleteKnowledgeGraph();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={'w-full h-full relative'}>
|
|
||||||
<ConfirmDeleteDialog onOk={handleDeleteKnowledgeGraph}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size={'sm'}
|
|
||||||
className="absolute right-0 top-0 z-50"
|
|
||||||
>
|
|
||||||
<Trash2 /> {t('common.delete')}
|
|
||||||
</Button>
|
|
||||||
</ConfirmDeleteDialog>
|
|
||||||
<ForceGraph data={data?.graph} show></ForceGraph>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeGraph;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import {
|
|
||||||
useKnowledgeBaseId,
|
|
||||||
useRemoveKnowledgeGraph,
|
|
||||||
} from '@/hooks/knowledge-hooks';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useNavigate } from 'umi';
|
|
||||||
|
|
||||||
export function useDeleteKnowledgeGraph() {
|
|
||||||
const { removeKnowledgeGraph, loading } = useRemoveKnowledgeGraph();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const knowledgeBaseId = useKnowledgeBaseId();
|
|
||||||
|
|
||||||
const handleDeleteKnowledgeGraph = useCallback(async () => {
|
|
||||||
const ret = await removeKnowledgeGraph();
|
|
||||||
if (ret === 0) {
|
|
||||||
navigate(`/knowledge/dataset?id=${knowledgeBaseId}`);
|
|
||||||
}
|
|
||||||
}, [knowledgeBaseId, navigate, removeKnowledgeGraph]);
|
|
||||||
|
|
||||||
return { handleDeleteKnowledgeGraph, loading };
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
class KeyGenerator {
|
|
||||||
idx = 0;
|
|
||||||
chars: string[] = [];
|
|
||||||
constructor() {
|
|
||||||
const chars = Array(26)
|
|
||||||
.fill(1)
|
|
||||||
.map((x, idx) => String.fromCharCode(97 + idx)); // 26 char
|
|
||||||
this.chars = chars;
|
|
||||||
}
|
|
||||||
generateKey() {
|
|
||||||
const key = this.chars[this.idx];
|
|
||||||
this.idx++;
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Classify nodes based on edge relationships
|
|
||||||
export class Converter {
|
|
||||||
keyGenerator;
|
|
||||||
dict: Record<string, string> = {}; // key is node id, value is combo
|
|
||||||
constructor() {
|
|
||||||
this.keyGenerator = new KeyGenerator();
|
|
||||||
}
|
|
||||||
buildDict(edges: { source: string; target: string }[]) {
|
|
||||||
edges.forEach((x) => {
|
|
||||||
if (this.dict[x.source] && !this.dict[x.target]) {
|
|
||||||
this.dict[x.target] = this.dict[x.source];
|
|
||||||
} else if (!this.dict[x.source] && this.dict[x.target]) {
|
|
||||||
this.dict[x.source] = this.dict[x.target];
|
|
||||||
} else if (!this.dict[x.source] && !this.dict[x.target]) {
|
|
||||||
this.dict[x.source] = this.dict[x.target] =
|
|
||||||
this.keyGenerator.generateKey();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this.dict;
|
|
||||||
}
|
|
||||||
buildNodesAndCombos(nodes: any[], edges: any[]) {
|
|
||||||
this.buildDict(edges);
|
|
||||||
const nextNodes = nodes.map((x) => ({ ...x, combo: this.dict[x.id] }));
|
|
||||||
|
|
||||||
const combos = Object.values(this.dict).reduce<any[]>((pre, cur) => {
|
|
||||||
if (pre.every((x) => x.id !== cur)) {
|
|
||||||
pre.push({
|
|
||||||
id: cur,
|
|
||||||
data: {
|
|
||||||
label: `Combo ${cur}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { nodes: nextNodes, combos };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isDataExist = (data: any) => {
|
|
||||||
return (
|
|
||||||
data?.data && typeof data?.data !== 'boolean' && !isEmpty(data?.data?.graph)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const findCombo = (communities: string[]) => {
|
|
||||||
const combo = Array.isArray(communities) ? communities[0] : undefined;
|
|
||||||
return combo;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildNodesAndCombos = (nodes: any[]) => {
|
|
||||||
const combos: any[] = [];
|
|
||||||
nodes.forEach((x) => {
|
|
||||||
const combo = findCombo(x?.communities);
|
|
||||||
if (combo && combos.every((y) => y.data.label !== combo)) {
|
|
||||||
combos.push({
|
|
||||||
isCombo: true,
|
|
||||||
id: uuid(),
|
|
||||||
data: {
|
|
||||||
label: combo,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextNodes = nodes.map((x) => {
|
|
||||||
return {
|
|
||||||
...x,
|
|
||||||
combo: combos.find((y) => y.data.label === findCombo(x?.communities))?.id,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { nodes: nextNodes, combos };
|
|
||||||
};
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import SvgIcon from '@/components/svg-icon';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useSelectParserList } from '@/hooks/user-setting-hooks';
|
|
||||||
import { Col, Divider, Empty, Row, Typography } from 'antd';
|
|
||||||
import DOMPurify from 'dompurify';
|
|
||||||
import camelCase from 'lodash/camelCase';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import styles from './index.less';
|
|
||||||
import { TagTabs } from './tag-tabs';
|
|
||||||
import { ImageMap } from './utils';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
|
||||||
const parserList = useSelectParserList();
|
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
|
||||||
|
|
||||||
const item = useMemo(() => {
|
|
||||||
const item = parserList.find((x) => x.value === chunkMethod);
|
|
||||||
if (item) {
|
|
||||||
return {
|
|
||||||
title: item.label,
|
|
||||||
description: t(camelCase(item.value)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { title: '', description: '' };
|
|
||||||
}, [parserList, chunkMethod, t]);
|
|
||||||
|
|
||||||
const imageList = useMemo(() => {
|
|
||||||
if (chunkMethod in ImageMap) {
|
|
||||||
return ImageMap[chunkMethod as keyof typeof ImageMap];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}, [chunkMethod]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.categoryPanelWrapper}>
|
|
||||||
{imageList.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<h5 className="font-semibold text-base mt-0 mb-1">
|
|
||||||
{`"${item.title}" ${t('methodTitle')}`}
|
|
||||||
</h5>
|
|
||||||
<p
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(item.description),
|
|
||||||
}}
|
|
||||||
></p>
|
|
||||||
<h5 className="font-semibold text-base mt-4 mb-1">{`"${item.title}" ${t('methodExamples')}`}</h5>
|
|
||||||
<Text>{t('methodExamplesDescription')}</Text>
|
|
||||||
<Row gutter={[10, 10]} className={styles.imageRow}>
|
|
||||||
{imageList.map((x) => (
|
|
||||||
<Col span={12} key={x}>
|
|
||||||
<SvgIcon
|
|
||||||
name={x}
|
|
||||||
width={'100%'}
|
|
||||||
className={styles.image}
|
|
||||||
></SvgIcon>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
<h5 className="font-semibold text-base mt-4 mb-1">
|
|
||||||
{item.title} {t('dialogueExamplesTitle')}
|
|
||||||
</h5>
|
|
||||||
<Divider></Divider>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Empty description={''} image={null}>
|
|
||||||
<p>{t('methodEmpty')}</p>
|
|
||||||
<SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
|
|
||||||
</Empty>
|
|
||||||
)}
|
|
||||||
{chunkMethod === 'tag' && <TagTabs></TagTabs>}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategoryPanel;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function AudioConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function BookConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useHandleChunkMethodSelectChange } from '@/hooks/logic-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import {
|
|
||||||
useHasParsedDocument,
|
|
||||||
useSelectChunkMethodList,
|
|
||||||
useSelectEmbeddingModelOptions,
|
|
||||||
} from '../hooks';
|
|
||||||
|
|
||||||
export const EmbeddingModelItem = memo(function EmbeddingModelItem() {
|
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
|
||||||
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
|
||||||
const disabled = useHasParsedDocument();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
name="embd_id"
|
|
||||||
label={t('embeddingModel')}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
tooltip={t('embeddingModelTip')}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder={t('embeddingModelPlaceholder')}
|
|
||||||
options={embeddingModelOptions}
|
|
||||||
disabled={disabled}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ChunkMethodItem = memo(function ChunkMethodItem() {
|
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
const handleChunkMethodSelectChange = useHandleChunkMethodSelectChange(form);
|
|
||||||
const parserList = useSelectChunkMethodList();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
name="parser_id"
|
|
||||||
label={t('chunkMethod')}
|
|
||||||
tooltip={t('chunkMethodTip')}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder={t('chunkMethodPlaceholder')}
|
|
||||||
onChange={handleChunkMethodSelectChange}
|
|
||||||
options={parserList}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function EmailConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
import { DocumentParserType } from '@/constants/knowledge';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { normFile } from '@/utils/file-util';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input, Radio, Space, Upload } from 'antd';
|
|
||||||
import { FormInstance } from 'antd/lib';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import {
|
|
||||||
useFetchKnowledgeConfigurationOnMount,
|
|
||||||
useSubmitKnowledgeConfiguration,
|
|
||||||
} from '../hooks';
|
|
||||||
import { AudioConfiguration } from './audio';
|
|
||||||
import { BookConfiguration } from './book';
|
|
||||||
import { EmailConfiguration } from './email';
|
|
||||||
import { KnowledgeGraphConfiguration } from './knowledge-graph';
|
|
||||||
import { LawsConfiguration } from './laws';
|
|
||||||
import { ManualConfiguration } from './manual';
|
|
||||||
import { NaiveConfiguration } from './naive';
|
|
||||||
import { OneConfiguration } from './one';
|
|
||||||
import { PaperConfiguration } from './paper';
|
|
||||||
import { PictureConfiguration } from './picture';
|
|
||||||
import { PresentationConfiguration } from './presentation';
|
|
||||||
import { QAConfiguration } from './qa';
|
|
||||||
import { ResumeConfiguration } from './resume';
|
|
||||||
import { TableConfiguration } from './table';
|
|
||||||
import { TagConfiguration } from './tag';
|
|
||||||
|
|
||||||
import styles from '../index.less';
|
|
||||||
|
|
||||||
const ConfigurationComponentMap = {
|
|
||||||
[DocumentParserType.Naive]: NaiveConfiguration,
|
|
||||||
[DocumentParserType.Qa]: QAConfiguration,
|
|
||||||
[DocumentParserType.Resume]: ResumeConfiguration,
|
|
||||||
[DocumentParserType.Manual]: ManualConfiguration,
|
|
||||||
[DocumentParserType.Table]: TableConfiguration,
|
|
||||||
[DocumentParserType.Paper]: PaperConfiguration,
|
|
||||||
[DocumentParserType.Book]: BookConfiguration,
|
|
||||||
[DocumentParserType.Laws]: LawsConfiguration,
|
|
||||||
[DocumentParserType.Presentation]: PresentationConfiguration,
|
|
||||||
[DocumentParserType.Picture]: PictureConfiguration,
|
|
||||||
[DocumentParserType.One]: OneConfiguration,
|
|
||||||
[DocumentParserType.Audio]: AudioConfiguration,
|
|
||||||
[DocumentParserType.Email]: EmailConfiguration,
|
|
||||||
[DocumentParserType.Tag]: TagConfiguration,
|
|
||||||
[DocumentParserType.KnowledgeGraph]: KnowledgeGraphConfiguration,
|
|
||||||
};
|
|
||||||
|
|
||||||
function EmptyComponent() {
|
|
||||||
return <div></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ConfigurationForm = ({ form }: { form: FormInstance }) => {
|
|
||||||
const { submitKnowledgeConfiguration, submitLoading, navigateToDataset } =
|
|
||||||
useSubmitKnowledgeConfiguration(form);
|
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
|
||||||
|
|
||||||
const [finalParserId, setFinalParserId] = useState<DocumentParserType>();
|
|
||||||
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
|
|
||||||
const parserId: DocumentParserType = Form.useWatch('parser_id', form);
|
|
||||||
const ConfigurationComponent = useMemo(() => {
|
|
||||||
return finalParserId
|
|
||||||
? ConfigurationComponentMap[finalParserId]
|
|
||||||
: EmptyComponent;
|
|
||||||
}, [finalParserId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFinalParserId(parserId);
|
|
||||||
}, [parserId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFinalParserId(knowledgeDetails.parser_id as DocumentParserType);
|
|
||||||
}, [knowledgeDetails.parser_id]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form form={form} name="validateOnly" layout="vertical" autoComplete="off">
|
|
||||||
<Form.Item name="name" label={t('name')} rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="avatar"
|
|
||||||
label={t('photo')}
|
|
||||||
valuePropName="fileList"
|
|
||||||
getValueFromEvent={normFile}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
listType="picture-card"
|
|
||||||
maxCount={1}
|
|
||||||
beforeUpload={() => false}
|
|
||||||
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
|
||||||
>
|
|
||||||
<button style={{ border: 0, background: 'none' }} type="button">
|
|
||||||
<PlusOutlined />
|
|
||||||
<div style={{ marginTop: 8 }}>{t('upload')}</div>
|
|
||||||
</button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="description" label={t('description')}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="permission"
|
|
||||||
label={t('permissions')}
|
|
||||||
tooltip={t('permissionsTip')}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="me">{t('me')}</Radio>
|
|
||||||
<Radio value="team">{t('team')}</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<ConfigurationComponent></ConfigurationComponent>
|
|
||||||
|
|
||||||
<Form.Item>
|
|
||||||
<div className={styles.buttonWrapper}>
|
|
||||||
<Space>
|
|
||||||
<Button size={'middle'} onClick={navigateToDataset}>
|
|
||||||
{t('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size={'middle'}
|
|
||||||
loading={submitLoading}
|
|
||||||
onClick={submitKnowledgeConfiguration}
|
|
||||||
>
|
|
||||||
{t('save')}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import Delimiter from '@/components/delimiter';
|
|
||||||
import EntityTypesItem from '@/components/entity-types-item';
|
|
||||||
import MaxTokenNumber from '@/components/max-token-number';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function KnowledgeGraphConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<EntityTypesItem></EntityTypesItem>
|
|
||||||
<MaxTokenNumber max={8192 * 2}></MaxTokenNumber>
|
|
||||||
<Delimiter></Delimiter>
|
|
||||||
</>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function LawsConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function ManualConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import { DatasetConfigurationContainer } from '@/components/dataset-configuration-container';
|
|
||||||
import Delimiter from '@/components/delimiter';
|
|
||||||
import ExcelToHtml from '@/components/excel-to-html';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import MaxTokenNumber from '@/components/max-token-number';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { Divider } from 'antd';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function NaiveConfiguration() {
|
|
||||||
return (
|
|
||||||
<section className="space-y-4 mb-4">
|
|
||||||
<DatasetConfigurationContainer>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
<MaxTokenNumber></MaxTokenNumber>
|
|
||||||
<Delimiter></Delimiter>
|
|
||||||
</DatasetConfigurationContainer>
|
|
||||||
<Divider></Divider>
|
|
||||||
<DatasetConfigurationContainer>
|
|
||||||
<PageRank></PageRank>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
<ExcelToHtml></ExcelToHtml>
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</DatasetConfigurationContainer>
|
|
||||||
<Divider></Divider>
|
|
||||||
<DatasetConfigurationContainer>
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
</DatasetConfigurationContainer>
|
|
||||||
<Divider></Divider>
|
|
||||||
<GraphRagItems></GraphRagItems>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function OneConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function PaperConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function PictureConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
} from '@/components/auto-keywords-item';
|
|
||||||
import LayoutRecognize from '@/components/layout-recognize';
|
|
||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import ParseConfiguration from '@/components/parse-configuration';
|
|
||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-items';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function PresentationConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LayoutRecognize></LayoutRecognize>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<AutoKeywordsItem></AutoKeywordsItem>
|
|
||||||
<AutoQuestionsItem></AutoQuestionsItem>
|
|
||||||
</>
|
|
||||||
|
|
||||||
<ParseConfiguration></ParseConfiguration>
|
|
||||||
|
|
||||||
<GraphRagItems marginBottom></GraphRagItems>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function QAConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { TagItems } from '../tag-item';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function ResumeConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
|
|
||||||
<TagItems></TagItems>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function TableConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import PageRank from '@/components/page-rank';
|
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
|
|
||||||
|
|
||||||
export function TagConfiguration() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EmbeddingModelItem></EmbeddingModelItem>
|
|
||||||
<ChunkMethodItem></ChunkMethodItem>
|
|
||||||
|
|
||||||
<PageRank></PageRank>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import { LlmModelType } from '@/constants/knowledge';
|
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import {
|
|
||||||
useFetchKnowledgeBaseConfiguration,
|
|
||||||
useUpdateKnowledge,
|
|
||||||
} from '@/hooks/knowledge-hooks';
|
|
||||||
import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
|
|
||||||
import { useNavigateToDataset } from '@/hooks/route-hook';
|
|
||||||
import { useSelectParserList } from '@/hooks/user-setting-hooks';
|
|
||||||
import {
|
|
||||||
getBase64FromUploadFileList,
|
|
||||||
getUploadFileListFromBase64,
|
|
||||||
} from '@/utils/file-util';
|
|
||||||
import { useIsFetching } from '@tanstack/react-query';
|
|
||||||
import { Form, UploadFile } from 'antd';
|
|
||||||
import { FormInstance } from 'antd/lib';
|
|
||||||
import pick from 'lodash/pick';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const useSubmitKnowledgeConfiguration = (form: FormInstance) => {
|
|
||||||
const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge();
|
|
||||||
const navigateToDataset = useNavigateToDataset();
|
|
||||||
|
|
||||||
const submitKnowledgeConfiguration = useCallback(async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
const avatar = await getBase64FromUploadFileList(values.avatar);
|
|
||||||
saveKnowledgeConfiguration({
|
|
||||||
...values,
|
|
||||||
avatar,
|
|
||||||
});
|
|
||||||
navigateToDataset();
|
|
||||||
}, [saveKnowledgeConfiguration, form, navigateToDataset]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
submitKnowledgeConfiguration,
|
|
||||||
submitLoading: loading,
|
|
||||||
navigateToDataset,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// The value that does not need to be displayed in the analysis method Select
|
|
||||||
const HiddenFields = ['email', 'picture', 'audio'];
|
|
||||||
|
|
||||||
export function useSelectChunkMethodList() {
|
|
||||||
const parserList = useSelectParserList();
|
|
||||||
|
|
||||||
return parserList.filter((x) => !HiddenFields.some((y) => y === x.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSelectEmbeddingModelOptions() {
|
|
||||||
const allOptions = useSelectLlmOptionsByModelType();
|
|
||||||
return allOptions[LlmModelType.Embedding];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useHasParsedDocument() {
|
|
||||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
return knowledgeDetails.chunk_num > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useFetchKnowledgeConfigurationOnMount = (form: FormInstance) => {
|
|
||||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fileList: UploadFile[] = getUploadFileListFromBase64(
|
|
||||||
knowledgeDetails.avatar,
|
|
||||||
);
|
|
||||||
form.setFieldsValue({
|
|
||||||
...pick(knowledgeDetails, [
|
|
||||||
'description',
|
|
||||||
'name',
|
|
||||||
'permission',
|
|
||||||
'embd_id',
|
|
||||||
'parser_id',
|
|
||||||
'language',
|
|
||||||
'parser_config',
|
|
||||||
'pagerank',
|
|
||||||
]),
|
|
||||||
avatar: fileList,
|
|
||||||
});
|
|
||||||
}, [form, knowledgeDetails]);
|
|
||||||
|
|
||||||
return knowledgeDetails;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSelectKnowledgeDetailsLoading = () =>
|
|
||||||
useIsFetching({ queryKey: ['fetchKnowledgeDetail'] }) > 0;
|
|
||||||
|
|
||||||
export const useHandleChunkMethodChange = () => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const chunkMethod = Form.useWatch('parser_id', form);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('🚀 ~ useHandleChunkMethodChange ~ chunkMethod:', chunkMethod);
|
|
||||||
}, [chunkMethod]);
|
|
||||||
|
|
||||||
return { form, chunkMethod };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRenameKnowledgeTag = () => {
|
|
||||||
const [tag, setTag] = useState<string>('');
|
|
||||||
const {
|
|
||||||
visible: tagRenameVisible,
|
|
||||||
hideModal: hideTagRenameModal,
|
|
||||||
showModal: showFileRenameModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const handleShowTagRenameModal = useCallback(
|
|
||||||
(record: string) => {
|
|
||||||
setTag(record);
|
|
||||||
showFileRenameModal();
|
|
||||||
},
|
|
||||||
[showFileRenameModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
initialName: tag,
|
|
||||||
tagRenameVisible,
|
|
||||||
hideTagRenameModal,
|
|
||||||
showTagRenameModal: handleShowTagRenameModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
.tags {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preset {
|
|
||||||
display: flex;
|
|
||||||
height: 80px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
width: 100px;
|
|
||||||
border-left: 1px solid rgba(0, 0, 0, 0.4);
|
|
||||||
margin: 10px 0px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.configurationWrapper {
|
|
||||||
padding: 0 52px;
|
|
||||||
.buttonWrapper {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.variableSlider {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.categoryPanelWrapper {
|
|
||||||
.topTitle {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.imageRow {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { Col, Divider, Row, Spin, Typography } from 'antd';
|
|
||||||
import CategoryPanel from './category-panel';
|
|
||||||
import { ConfigurationForm } from './configuration';
|
|
||||||
import {
|
|
||||||
useHandleChunkMethodChange,
|
|
||||||
useSelectKnowledgeDetailsLoading,
|
|
||||||
} from './hooks';
|
|
||||||
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const { Title } = Typography;
|
|
||||||
|
|
||||||
const Configuration = () => {
|
|
||||||
const loading = useSelectKnowledgeDetailsLoading();
|
|
||||||
const { form, chunkMethod } = useHandleChunkMethodChange();
|
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.configurationWrapper}>
|
|
||||||
<Title level={5}>
|
|
||||||
{t('configuration', { keyPrefix: 'knowledgeDetails' })}
|
|
||||||
</Title>
|
|
||||||
<p>{t('titleDescription')}</p>
|
|
||||||
<Divider></Divider>
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
<Row gutter={32}>
|
|
||||||
<Col span={8}>
|
|
||||||
<ConfigurationForm form={form}></ConfigurationForm>
|
|
||||||
</Col>
|
|
||||||
<Col span={16}>
|
|
||||||
<CategoryPanel chunkMethod={chunkMethod}></CategoryPanel>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Spin>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Configuration;
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
|
||||||
import { Avatar, Flex, Form, InputNumber, Select, Slider, Space } from 'antd';
|
|
||||||
import DOMPurify from 'dompurify';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const TagSetItem = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
|
||||||
|
|
||||||
const knowledgeOptions = knowledgeList
|
|
||||||
.filter((x) => x.parser_id === 'tag')
|
|
||||||
.map((x) => ({
|
|
||||||
label: (
|
|
||||||
<Space>
|
|
||||||
<Avatar size={20} icon={<UserOutlined />} src={x.avatar} />
|
|
||||||
{x.name}
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
value: x.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
label={t('knowledgeConfiguration.tagSet')}
|
|
||||||
name={['parser_config', 'tag_kb_ids']}
|
|
||||||
tooltip={
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(t('knowledgeConfiguration.tagSetTip')),
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
message: t('chat.knowledgeBasesMessage'),
|
|
||||||
type: 'array',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
mode="multiple"
|
|
||||||
options={knowledgeOptions}
|
|
||||||
placeholder={t('chat.knowledgeBasesMessage')}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TopNTagsItem = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item label={t('knowledgeConfiguration.topnTags')}>
|
|
||||||
<Flex gap={20} align="center">
|
|
||||||
<Flex flex={1}>
|
|
||||||
<Form.Item
|
|
||||||
name={['parser_config', 'topn_tags']}
|
|
||||||
noStyle
|
|
||||||
initialValue={3}
|
|
||||||
>
|
|
||||||
<Slider max={10} min={1} style={{ width: '100%' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Flex>
|
|
||||||
<Form.Item name={['parser_config', 'topn_tags']} noStyle>
|
|
||||||
<InputNumber max={10} min={1} />
|
|
||||||
</Form.Item>
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TagItems() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TagSetItem></TagSetItem>
|
|
||||||
<Form.Item noStyle dependencies={[['parser_config', 'tag_kb_ids']]}>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const ids: string[] = getFieldValue(['parser_config', 'tag_kb_ids']);
|
|
||||||
|
|
||||||
return (
|
|
||||||
Array.isArray(ids) &&
|
|
||||||
ids.length > 0 && <TopNTagsItem></TopNTagsItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,307 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from '@tanstack/react-table';
|
|
||||||
import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { useDeleteTag, useFetchTagList } from '@/hooks/knowledge-hooks';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useRenameKnowledgeTag } from '../hooks';
|
|
||||||
import { RenameDialog } from './rename-dialog';
|
|
||||||
|
|
||||||
export type ITag = {
|
|
||||||
tag: string;
|
|
||||||
frequency: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TagTable() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { list } = useFetchTagList();
|
|
||||||
const [tagList, setTagList] = useState<ITag[]>([]);
|
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = useState({});
|
|
||||||
|
|
||||||
const { deleteTag } = useDeleteTag();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTagList(list.map((x) => ({ tag: x[0], frequency: x[1] })));
|
|
||||||
}, [list]);
|
|
||||||
|
|
||||||
const handleDeleteTag = useCallback(
|
|
||||||
(tags: string[]) => () => {
|
|
||||||
deleteTag(tags);
|
|
||||||
},
|
|
||||||
[deleteTag],
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
showTagRenameModal,
|
|
||||||
hideTagRenameModal,
|
|
||||||
tagRenameVisible,
|
|
||||||
initialName,
|
|
||||||
} = useRenameKnowledgeTag();
|
|
||||||
|
|
||||||
const columns: ColumnDef<ITag>[] = [
|
|
||||||
{
|
|
||||||
id: 'select',
|
|
||||||
header: ({ table }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
table.getIsAllPageRowsSelected() ||
|
|
||||||
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
|
||||||
}
|
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
||||||
aria-label="Select all"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={row.getIsSelected()}
|
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
||||||
aria-label="Select row"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'tag',
|
|
||||||
header: ({ column }) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
>
|
|
||||||
{t('knowledgeConfiguration.tagName')}
|
|
||||||
<ArrowUpDown />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const value: string = row.getValue('tag');
|
|
||||||
return <div>{value}</div>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'frequency',
|
|
||||||
header: ({ column }) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
>
|
|
||||||
{t('knowledgeConfiguration.frequency')}
|
|
||||||
<ArrowUpDown />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="capitalize ">{row.getValue('frequency')}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
enableHiding: false,
|
|
||||||
header: t('common.action'),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Tooltip>
|
|
||||||
<ConfirmDeleteDialog onOk={handleDeleteTag([row.original.tag])}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon">
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
</ConfirmDeleteDialog>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('common.delete')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => showTagRenameModal(row.original.tag)}
|
|
||||||
>
|
|
||||||
<Pencil />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('common.rename')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: tagList,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedRowLength = table.getFilteredSelectedRowModel().rows.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TooltipProvider>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="flex items-center justify-between py-4 ">
|
|
||||||
<Input
|
|
||||||
placeholder={t('knowledgeConfiguration.searchTags')}
|
|
||||||
value={(table.getColumn('tag')?.getFilterValue() as string) ?? ''}
|
|
||||||
onChange={(event) =>
|
|
||||||
table.getColumn('tag')?.setFilterValue(event.target.value)
|
|
||||||
}
|
|
||||||
className="w-1/2"
|
|
||||||
/>
|
|
||||||
{selectedRowLength > 0 && (
|
|
||||||
<ConfirmDeleteDialog
|
|
||||||
onOk={handleDeleteTag(
|
|
||||||
table
|
|
||||||
.getFilteredSelectedRowModel()
|
|
||||||
.rows.map((x) => x.original.tag),
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</ConfirmDeleteDialog>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md border">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext(),
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && 'selected'}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext(),
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={columns.length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
|
||||||
<div className="flex-1 text-sm text-muted-foreground">
|
|
||||||
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
|
|
||||||
row(s) selected.
|
|
||||||
</div>
|
|
||||||
<div className="space-x-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
{t('common.previousPage')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
{t('common.nextPage')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{tagRenameVisible && (
|
|
||||||
<RenameDialog
|
|
||||||
hideModal={hideTagRenameModal}
|
|
||||||
initialName={initialName}
|
|
||||||
></RenameDialog>
|
|
||||||
)}
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import { useTagIsRenaming } from '@/hooks/knowledge-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { RenameForm } from './rename-form';
|
|
||||||
|
|
||||||
export function RenameDialog({
|
|
||||||
hideModal,
|
|
||||||
initialName,
|
|
||||||
}: IModalProps<any> & { initialName: string }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const loading = useTagIsRenaming();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onOpenChange={hideModal}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t('common.rename')}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<RenameForm
|
|
||||||
initialName={initialName}
|
|
||||||
hideModal={hideModal}
|
|
||||||
></RenameForm>
|
|
||||||
<DialogFooter>
|
|
||||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
|
||||||
{t('common.save')}
|
|
||||||
</LoadingButton>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { useRenameTag } from '@/hooks/knowledge-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export function RenameForm({
|
|
||||||
initialName,
|
|
||||||
hideModal,
|
|
||||||
}: IModalProps<any> & { initialName: string }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const FormSchema = z.object({
|
|
||||||
name: z
|
|
||||||
.string()
|
|
||||||
.min(1, {
|
|
||||||
message: t('common.namePlaceholder'),
|
|
||||||
})
|
|
||||||
.trim(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { renameTag } = useRenameTag();
|
|
||||||
|
|
||||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
|
||||||
const ret = await renameTag({ fromTag: initialName, toTag: data.name });
|
|
||||||
if (ret) {
|
|
||||||
hideModal?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setValue('name', initialName);
|
|
||||||
}, [form, initialName]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-6"
|
|
||||||
id={TagRenameId}
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={t('common.namePlaceholder')}
|
|
||||||
{...field}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { Segmented } from 'antd';
|
|
||||||
import { SegmentedLabeledOption } from 'antd/es/segmented';
|
|
||||||
import { upperFirst } from 'lodash';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { TagTable } from './tag-table';
|
|
||||||
import { TagWordCloud } from './tag-word-cloud';
|
|
||||||
|
|
||||||
enum TagType {
|
|
||||||
Cloud = 'cloud',
|
|
||||||
Table = 'table',
|
|
||||||
}
|
|
||||||
|
|
||||||
const TagContentMap = {
|
|
||||||
[TagType.Cloud]: <TagWordCloud></TagWordCloud>,
|
|
||||||
[TagType.Table]: <TagTable></TagTable>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TagTabs() {
|
|
||||||
const [value, setValue] = useState<TagType>(TagType.Cloud);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const options: SegmentedLabeledOption[] = [TagType.Cloud, TagType.Table].map(
|
|
||||||
(x) => ({
|
|
||||||
label: t(`knowledgeConfiguration.tag${upperFirst(x)}`),
|
|
||||||
value: x,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="mt-4">
|
|
||||||
<Segmented
|
|
||||||
value={value}
|
|
||||||
options={options}
|
|
||||||
onChange={(val) => setValue(val as TagType)}
|
|
||||||
/>
|
|
||||||
{TagContentMap[value]}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
import { useFetchTagList } from '@/hooks/knowledge-hooks';
|
|
||||||
import { Chart } from '@antv/g2';
|
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
||||||
|
|
||||||
export function TagWordCloud() {
|
|
||||||
const domRef = useRef<HTMLDivElement>(null);
|
|
||||||
let chartRef = useRef<Chart>();
|
|
||||||
const { list } = useFetchTagList();
|
|
||||||
|
|
||||||
const { list: tagList } = useMemo(() => {
|
|
||||||
const nextList = list.sort((a, b) => b[1] - a[1]).slice(0, 256);
|
|
||||||
|
|
||||||
return {
|
|
||||||
list: nextList.map((x) => ({ text: x[0], value: x[1], name: x[0] })),
|
|
||||||
sumValue: sumBy(nextList, (x: [string, number]) => x[1]),
|
|
||||||
length: nextList.length,
|
|
||||||
};
|
|
||||||
}, [list]);
|
|
||||||
|
|
||||||
const renderWordCloud = useCallback(() => {
|
|
||||||
if (domRef.current) {
|
|
||||||
chartRef.current = new Chart({ container: domRef.current });
|
|
||||||
|
|
||||||
chartRef.current.options({
|
|
||||||
type: 'wordCloud',
|
|
||||||
autoFit: true,
|
|
||||||
layout: {
|
|
||||||
fontSize: [10, 50],
|
|
||||||
// fontSize: (d: any) => {
|
|
||||||
// if (d.value) {
|
|
||||||
// return (d.value / sumValue) * 100 * (length / 10);
|
|
||||||
// }
|
|
||||||
// return 0;
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
type: 'inline',
|
|
||||||
value: tagList,
|
|
||||||
},
|
|
||||||
encode: { color: 'text' },
|
|
||||||
legend: false,
|
|
||||||
tooltip: {
|
|
||||||
title: 'name', // title
|
|
||||||
items: ['value'], // data item
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
chartRef.current.render();
|
|
||||||
}
|
|
||||||
}, [tagList]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
renderWordCloud();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
chartRef.current?.destroy();
|
|
||||||
};
|
|
||||||
}, [renderWordCloud]);
|
|
||||||
|
|
||||||
return <div ref={domRef} className="w-full h-[38vh]"></div>;
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
const getImageName = (prefix: string, length: number) =>
|
|
||||||
new Array(length)
|
|
||||||
.fill(0)
|
|
||||||
.map((x, idx) => `chunk-method/${prefix}-0${idx + 1}`);
|
|
||||||
|
|
||||||
export const ImageMap = {
|
|
||||||
book: getImageName('book', 4),
|
|
||||||
laws: getImageName('law', 2),
|
|
||||||
manual: getImageName('manual', 4),
|
|
||||||
picture: getImageName('media', 2),
|
|
||||||
naive: getImageName('naive', 2),
|
|
||||||
paper: getImageName('paper', 2),
|
|
||||||
presentation: getImageName('presentation', 2),
|
|
||||||
qa: getImageName('qa', 2),
|
|
||||||
resume: getImageName('resume', 2),
|
|
||||||
table: getImageName('table', 2),
|
|
||||||
one: getImageName('one', 2),
|
|
||||||
knowledge_graph: getImageName('knowledge-graph', 2),
|
|
||||||
tag: getImageName('tag', 2),
|
|
||||||
};
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
.sidebarWrapper {
|
|
||||||
max-width: 288px;
|
|
||||||
padding: 32px 24px 24px 24px;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.sidebarTop {
|
|
||||||
text-align: center;
|
|
||||||
.knowledgeLogo {
|
|
||||||
}
|
|
||||||
.knowledgeTitle {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: @fontWeight700;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.knowledgeDescription {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: @fontWeight600;
|
|
||||||
color: @gray8;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
.divider {
|
|
||||||
height: 2px;
|
|
||||||
background-image: linear-gradient(
|
|
||||||
to right,
|
|
||||||
@gray11 0%,
|
|
||||||
@gray11 50%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
|
||||||
background-size: 10px 2px;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menuWrapper {
|
|
||||||
padding-top: 10px;
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
border: none;
|
|
||||||
font-size: @fontSize16;
|
|
||||||
font-weight: @fontWeight600;
|
|
||||||
:global(.ant-menu-item) {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.defaultWidth {
|
|
||||||
width: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minWidth {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menuText {
|
|
||||||
color: @gray3;
|
|
||||||
font-size: @fontSize14;
|
|
||||||
font-weight: @fontWeight700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
import { ReactComponent as ConfigurationIcon } from '@/assets/svg/knowledge-configration.svg';
|
|
||||||
import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg';
|
|
||||||
import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg';
|
|
||||||
import {
|
|
||||||
useFetchKnowledgeBaseConfiguration,
|
|
||||||
useFetchKnowledgeGraph,
|
|
||||||
} from '@/hooks/knowledge-hooks';
|
|
||||||
import {
|
|
||||||
useGetKnowledgeSearchParams,
|
|
||||||
useSecondPathName,
|
|
||||||
} from '@/hooks/route-hook';
|
|
||||||
import { getWidth } from '@/utils';
|
|
||||||
import { Avatar, Menu, MenuProps, Space } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useNavigate } from 'umi';
|
|
||||||
import { KnowledgeRouteKey } from '../../constant';
|
|
||||||
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { GitGraph } from 'lucide-react';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const KnowledgeSidebar = () => {
|
|
||||||
let navigate = useNavigate();
|
|
||||||
const activeKey = useSecondPathName();
|
|
||||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
|
||||||
|
|
||||||
const [windowWidth, setWindowWidth] = useState(getWidth());
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
|
|
||||||
const handleSelect: MenuProps['onSelect'] = (e) => {
|
|
||||||
navigate(`/knowledge/${e.key}?id=${knowledgeId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = useFetchKnowledgeGraph();
|
|
||||||
|
|
||||||
type MenuItem = Required<MenuProps>['items'][number];
|
|
||||||
|
|
||||||
const getItem = useCallback(
|
|
||||||
(
|
|
||||||
label: string,
|
|
||||||
key: React.Key,
|
|
||||||
icon?: React.ReactNode,
|
|
||||||
disabled?: boolean,
|
|
||||||
children?: MenuItem[],
|
|
||||||
type?: 'group',
|
|
||||||
): MenuItem => {
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
icon,
|
|
||||||
children,
|
|
||||||
label: t(`knowledgeDetails.${label}`),
|
|
||||||
type,
|
|
||||||
disabled,
|
|
||||||
} as MenuItem;
|
|
||||||
},
|
|
||||||
[t],
|
|
||||||
);
|
|
||||||
|
|
||||||
const items: MenuItem[] = useMemo(() => {
|
|
||||||
const list = [
|
|
||||||
getItem(
|
|
||||||
KnowledgeRouteKey.Dataset, // TODO: Change icon color when selected
|
|
||||||
KnowledgeRouteKey.Dataset,
|
|
||||||
<DatasetIcon />,
|
|
||||||
),
|
|
||||||
getItem(
|
|
||||||
KnowledgeRouteKey.Testing,
|
|
||||||
KnowledgeRouteKey.Testing,
|
|
||||||
<TestingIcon />,
|
|
||||||
),
|
|
||||||
getItem(
|
|
||||||
KnowledgeRouteKey.Configuration,
|
|
||||||
KnowledgeRouteKey.Configuration,
|
|
||||||
<ConfigurationIcon />,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isEmpty(data?.graph)) {
|
|
||||||
list.push(
|
|
||||||
getItem(
|
|
||||||
KnowledgeRouteKey.KnowledgeGraph,
|
|
||||||
KnowledgeRouteKey.KnowledgeGraph,
|
|
||||||
<GitGraph />,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [data, getItem]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const widthSize = () => {
|
|
||||||
const width = getWidth();
|
|
||||||
|
|
||||||
setWindowWidth(width);
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', widthSize);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', widthSize);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.sidebarWrapper}>
|
|
||||||
<div className={styles.sidebarTop}>
|
|
||||||
<Space size={8} direction="vertical">
|
|
||||||
<Avatar size={64} src={knowledgeDetails.avatar} />
|
|
||||||
<div className={styles.knowledgeTitle}>{knowledgeDetails.name}</div>
|
|
||||||
</Space>
|
|
||||||
<p className={styles.knowledgeDescription}>
|
|
||||||
{knowledgeDetails.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={styles.divider}></div>
|
|
||||||
<div className={styles.menuWrapper}>
|
|
||||||
<Menu
|
|
||||||
selectedKeys={[activeKey]}
|
|
||||||
// mode="inline"
|
|
||||||
className={classNames(styles.menu, {
|
|
||||||
[styles.defaultWidth]: windowWidth.width > 957,
|
|
||||||
[styles.minWidth]: windowWidth.width <= 957,
|
|
||||||
})}
|
|
||||||
// inlineCollapsed={collapsed}
|
|
||||||
items={items}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeSidebar;
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.testingWrapper {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import {
|
|
||||||
useTestChunkAllRetrieval,
|
|
||||||
useTestChunkRetrieval,
|
|
||||||
} from '@/hooks/knowledge-hooks';
|
|
||||||
import { Flex, Form } from 'antd';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import TestingControl from './testing-control';
|
|
||||||
import TestingResult from './testing-result';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const KnowledgeTesting = () => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const {
|
|
||||||
data: retrievalData,
|
|
||||||
testChunk,
|
|
||||||
loading: retrievalLoading,
|
|
||||||
} = useTestChunkRetrieval();
|
|
||||||
const {
|
|
||||||
data: allRetrievalData,
|
|
||||||
testChunkAll,
|
|
||||||
loading: allRetrievalLoading,
|
|
||||||
} = useTestChunkAllRetrieval();
|
|
||||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const handleTesting = async (documentIds: string[] = []) => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
const params = {
|
|
||||||
...values,
|
|
||||||
vector_similarity_weight: 1 - values.vector_similarity_weight,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Array.isArray(documentIds) && documentIds.length > 0) {
|
|
||||||
testChunk({
|
|
||||||
...params,
|
|
||||||
doc_ids: documentIds,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
testChunkAll({
|
|
||||||
...params,
|
|
||||||
doc_ids: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const testingResult = useMemo(() => {
|
|
||||||
return selectedDocumentIds.length > 0 ? retrievalData : allRetrievalData;
|
|
||||||
}, [allRetrievalData, retrievalData, selectedDocumentIds.length]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex className={styles.testingWrapper} gap={16}>
|
|
||||||
<TestingControl
|
|
||||||
form={form}
|
|
||||||
handleTesting={handleTesting}
|
|
||||||
selectedDocumentIds={selectedDocumentIds}
|
|
||||||
></TestingControl>
|
|
||||||
<TestingResult
|
|
||||||
data={testingResult}
|
|
||||||
loading={retrievalLoading || allRetrievalLoading}
|
|
||||||
handleTesting={handleTesting}
|
|
||||||
selectedDocumentIds={selectedDocumentIds}
|
|
||||||
setSelectedDocumentIds={setSelectedDocumentIds}
|
|
||||||
></TestingResult>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeTesting;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
.testingControlWrapper {
|
|
||||||
width: 350px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 30px 20px;
|
|
||||||
overflow: auto;
|
|
||||||
height: calc(100vh - 160px);
|
|
||||||
|
|
||||||
.historyTitle {
|
|
||||||
padding: 30px 0 20px;
|
|
||||||
}
|
|
||||||
.historyIcon {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.historyCardWrapper {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.historyCard {
|
|
||||||
width: 100%;
|
|
||||||
:global(.ant-card-body) {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.historyText {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
import Rerank from '@/components/rerank';
|
|
||||||
import SimilaritySlider from '@/components/similarity-slider';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useChunkIsTesting } from '@/hooks/knowledge-hooks';
|
|
||||||
import { Button, Card, Divider, Flex, Form, Input } from 'antd';
|
|
||||||
import { FormInstance } from 'antd/lib';
|
|
||||||
import { LabelWordCloud } from './label-word-cloud';
|
|
||||||
|
|
||||||
import { CrossLanguageItem } from '@/components/cross-language-item';
|
|
||||||
import { UseKnowledgeGraphItem } from '@/components/use-knowledge-graph-item';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
type FieldType = {
|
|
||||||
similarity_threshold?: number;
|
|
||||||
vector_similarity_weight?: number;
|
|
||||||
question: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
form: FormInstance;
|
|
||||||
handleTesting: (documentIds?: string[]) => Promise<any>;
|
|
||||||
selectedDocumentIds: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestingControl = ({
|
|
||||||
form,
|
|
||||||
handleTesting,
|
|
||||||
selectedDocumentIds,
|
|
||||||
}: IProps) => {
|
|
||||||
const question = Form.useWatch('question', { form, preserve: true });
|
|
||||||
const loading = useChunkIsTesting();
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
|
|
||||||
const buttonDisabled =
|
|
||||||
!question || (typeof question === 'string' && question.trim() === '');
|
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
handleTesting(selectedDocumentIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.testingControlWrapper}>
|
|
||||||
<div>
|
|
||||||
<b>{t('testing')}</b>
|
|
||||||
</div>
|
|
||||||
<p>{t('testingDescription')}</p>
|
|
||||||
<Divider></Divider>
|
|
||||||
<section>
|
|
||||||
<Form name="testing" layout="vertical" form={form}>
|
|
||||||
<SimilaritySlider isTooltipShown></SimilaritySlider>
|
|
||||||
<Rerank></Rerank>
|
|
||||||
<UseKnowledgeGraphItem filedName={['use_kg']}></UseKnowledgeGraphItem>
|
|
||||||
<CrossLanguageItem name={'cross_languages'}></CrossLanguageItem>
|
|
||||||
<Card size="small" title={t('testText')}>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
name={'question'}
|
|
||||||
rules={[{ required: true, message: t('testTextPlaceholder') }]}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize={{ minRows: 8 }}></Input.TextArea>
|
|
||||||
</Form.Item>
|
|
||||||
<Flex justify={'end'}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={buttonDisabled}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{t('testingLabel')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
</Form>
|
|
||||||
</section>
|
|
||||||
<LabelWordCloud></LabelWordCloud>
|
|
||||||
{/* <section>
|
|
||||||
<div className={styles.historyTitle}>
|
|
||||||
<Space size={'middle'}>
|
|
||||||
<HistoryOutlined className={styles.historyIcon} />
|
|
||||||
<b>Test history</b>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
<Space
|
|
||||||
direction="vertical"
|
|
||||||
size={'middle'}
|
|
||||||
className={styles.historyCardWrapper}
|
|
||||||
>
|
|
||||||
{list.map((x) => (
|
|
||||||
<Card className={styles.historyCard} key={x}>
|
|
||||||
<Flex justify={'space-between'} gap={'small'}>
|
|
||||||
<span>{x}</span>
|
|
||||||
<div className={styles.historyText}>
|
|
||||||
content dcjsjl snldsh svnodvn svnodrfn svjdoghdtbnhdo
|
|
||||||
sdvhodhbuid sldghdrlh
|
|
||||||
</div>
|
|
||||||
<Flex gap={'small'}>
|
|
||||||
<span>time</span>
|
|
||||||
<DeleteOutlined></DeleteOutlined>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</section> */}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TestingControl;
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
|
||||||
import { Chart } from '@antv/g2';
|
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
||||||
|
|
||||||
export function LabelWordCloud() {
|
|
||||||
const domRef = useRef<HTMLDivElement>(null);
|
|
||||||
let chartRef = useRef<Chart>();
|
|
||||||
const { labels } = useSelectTestingResult();
|
|
||||||
|
|
||||||
const list = useMemo(() => {
|
|
||||||
if (!labels) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(labels).reduce<
|
|
||||||
Array<{ text: string; name: string; value: number }>
|
|
||||||
>((pre, cur) => {
|
|
||||||
pre.push({ name: cur, text: cur, value: labels[cur] });
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
}, [labels]);
|
|
||||||
|
|
||||||
const renderWordCloud = useCallback(() => {
|
|
||||||
if (domRef.current && list.length) {
|
|
||||||
chartRef.current = new Chart({ container: domRef.current });
|
|
||||||
|
|
||||||
chartRef.current.options({
|
|
||||||
type: 'wordCloud',
|
|
||||||
autoFit: true,
|
|
||||||
layout: {
|
|
||||||
fontSize: [6, 15],
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
type: 'inline',
|
|
||||||
value: list,
|
|
||||||
},
|
|
||||||
encode: { color: 'text' },
|
|
||||||
legend: false,
|
|
||||||
tooltip: {
|
|
||||||
title: 'name', // title
|
|
||||||
items: ['value'], // data item
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
chartRef.current.render();
|
|
||||||
}
|
|
||||||
}, [list]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
renderWordCloud();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
chartRef.current?.destroy();
|
|
||||||
};
|
|
||||||
}, [renderWordCloud]);
|
|
||||||
|
|
||||||
return <div ref={domRef} className="w-full h-[13vh]"></div>;
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
.testingResultWrapper {
|
|
||||||
flex: 1;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 30px 20px;
|
|
||||||
overflow: auto;
|
|
||||||
height: calc(100vh - 160px);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.selectFilesCollapse {
|
|
||||||
:global(.ant-collapse-header) {
|
|
||||||
padding-left: 22px;
|
|
||||||
}
|
|
||||||
margin-bottom: 32px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectFilesTitle {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.similarityCircle {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.similarityText {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.image {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 30vh;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.imagePreview {
|
|
||||||
display: block;
|
|
||||||
max-width: 45vw;
|
|
||||||
max-height: 40vh;
|
|
||||||
}
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { ITestingChunk, ITestingResult } from '@/interfaces/database/knowledge';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Collapse,
|
|
||||||
Empty,
|
|
||||||
Flex,
|
|
||||||
Image,
|
|
||||||
Pagination,
|
|
||||||
PaginationProps,
|
|
||||||
Space,
|
|
||||||
} from 'antd';
|
|
||||||
import camelCase from 'lodash/camelCase';
|
|
||||||
import SelectFiles from './select-files';
|
|
||||||
|
|
||||||
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
|
|
||||||
import { api_host } from '@/utils/api';
|
|
||||||
import { showImage } from '@/utils/chat';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [
|
|
||||||
{ field: 'similarity', label: 'Hybrid Similarity' },
|
|
||||||
{ field: 'term_similarity', label: 'Term Similarity' },
|
|
||||||
{ field: 'vector_similarity', label: 'Vector Similarity' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const ChunkTitle = ({ item }: { item: ITestingChunk }) => {
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
return (
|
|
||||||
<Flex gap={10}>
|
|
||||||
{similarityList.map((x) => (
|
|
||||||
<Space key={x.field}>
|
|
||||||
<span className={styles.similarityCircle}>
|
|
||||||
{((item[x.field] as number) * 100).toFixed(2)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.similarityText}>{t(camelCase(x.field))}</span>
|
|
||||||
</Space>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
handleTesting: (documentIds?: string[]) => Promise<any>;
|
|
||||||
selectedDocumentIds: string[];
|
|
||||||
setSelectedDocumentIds: (ids: string[]) => void;
|
|
||||||
data?: ITestingResult;
|
|
||||||
loading?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestingResult = ({
|
|
||||||
handleTesting,
|
|
||||||
selectedDocumentIds,
|
|
||||||
setSelectedDocumentIds,
|
|
||||||
data,
|
|
||||||
loading,
|
|
||||||
}: IProps) => {
|
|
||||||
const { documents, chunks, total } = data || {};
|
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
|
||||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
|
||||||
|
|
||||||
const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
|
|
||||||
pagination.onChange?.(pageNumber, pageSize);
|
|
||||||
handleTesting(selectedDocumentIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTesting = useCallback(
|
|
||||||
(ids: string[]) => {
|
|
||||||
setPagination({ page: 1 });
|
|
||||||
handleTesting(ids);
|
|
||||||
},
|
|
||||||
[setPagination, handleTesting],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.testingResultWrapper}>
|
|
||||||
<Collapse
|
|
||||||
expandIcon={() => (
|
|
||||||
<SelectedFilesCollapseIcon></SelectedFilesCollapseIcon>
|
|
||||||
)}
|
|
||||||
className={styles.selectFilesCollapse}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: (
|
|
||||||
<Flex
|
|
||||||
justify={'space-between'}
|
|
||||||
align="center"
|
|
||||||
className={styles.selectFilesTitle}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
<span>
|
|
||||||
{selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0}
|
|
||||||
</span>
|
|
||||||
{t('filesSelected')}
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
<SelectFiles
|
|
||||||
setSelectedDocumentIds={setSelectedDocumentIds}
|
|
||||||
handleTesting={onTesting}
|
|
||||||
></SelectFiles>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Flex
|
|
||||||
gap={'large'}
|
|
||||||
vertical
|
|
||||||
flex={1}
|
|
||||||
className={styles.selectFilesCollapse}
|
|
||||||
>
|
|
||||||
{loading === false && chunks && chunks.length > 0 ? (
|
|
||||||
chunks?.map((x) => (
|
|
||||||
<Card key={x.chunk_id} title={<ChunkTitle item={x}></ChunkTitle>}>
|
|
||||||
<div className="flex justify-center">
|
|
||||||
{showImage(x.doc_type_kwd) && (
|
|
||||||
<Image
|
|
||||||
id={x.image_id}
|
|
||||||
className={'object-contain max-h-[30vh] w-full text-center'}
|
|
||||||
src={`${api_host}/document/image/${x.image_id}`}
|
|
||||||
></Image>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="pt-4">{x.content_with_weight}</div>
|
|
||||||
</Card>
|
|
||||||
))
|
|
||||||
) : loading === false && chunks && chunks.length === 0 ? (
|
|
||||||
<Empty></Empty>
|
|
||||||
) : null}
|
|
||||||
</Flex>
|
|
||||||
<Pagination
|
|
||||||
{...pagination}
|
|
||||||
size={'small'}
|
|
||||||
total={total}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TestingResult;
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import NewDocumentLink from '@/components/new-document-link';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useAllTestingResult } from '@/hooks/knowledge-hooks';
|
|
||||||
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
|
||||||
import { EyeOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Table, TableProps, Tooltip } from 'antd';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
handleTesting: (ids: string[]) => void;
|
|
||||||
setSelectedDocumentIds: (ids: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SelectFiles = ({ setSelectedDocumentIds, handleTesting }: IProps) => {
|
|
||||||
const { documents } = useAllTestingResult();
|
|
||||||
const { t } = useTranslate('fileManager');
|
|
||||||
|
|
||||||
const columns: TableProps<ITestingDocument>['columns'] = [
|
|
||||||
{
|
|
||||||
title: 'Name',
|
|
||||||
dataIndex: 'doc_name',
|
|
||||||
key: 'doc_name',
|
|
||||||
render: (text) => <p>{text}</p>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'Hits',
|
|
||||||
dataIndex: 'count',
|
|
||||||
key: 'count',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'View',
|
|
||||||
key: 'view',
|
|
||||||
width: 50,
|
|
||||||
render: (_, { doc_id, doc_name }) => (
|
|
||||||
<NewDocumentLink
|
|
||||||
documentName={doc_name}
|
|
||||||
documentId={doc_id}
|
|
||||||
prefix="document"
|
|
||||||
>
|
|
||||||
<Tooltip title={t('preview')}>
|
|
||||||
<Button type="text">
|
|
||||||
<EyeOutlined size={20} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</NewDocumentLink>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const rowSelection = {
|
|
||||||
onChange: (selectedRowKeys: React.Key[]) => {
|
|
||||||
setSelectedDocumentIds(selectedRowKeys as string[]);
|
|
||||||
handleTesting(selectedRowKeys as string[]);
|
|
||||||
},
|
|
||||||
getCheckboxProps: (record: ITestingDocument) => ({
|
|
||||||
disabled: record.doc_name === 'Disabled User', // Column configuration not to be checked
|
|
||||||
name: record.doc_name,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
dataSource={documents}
|
|
||||||
showHeader={false}
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
rowKey={'doc_id'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SelectFiles;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { KnowledgeRouteKey } from '@/constants/knowledge';
|
|
||||||
|
|
||||||
export const routeMap = {
|
|
||||||
[KnowledgeRouteKey.Dataset]: 'Dataset',
|
|
||||||
[KnowledgeRouteKey.Testing]: 'Retrieval testing',
|
|
||||||
[KnowledgeRouteKey.Configuration]: 'Configuration',
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum KnowledgeDatasetRouteKey {
|
|
||||||
Chunk = 'chunk',
|
|
||||||
File = 'file',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const datasetRouteMap = {
|
|
||||||
[KnowledgeDatasetRouteKey.Chunk]: 'Chunk',
|
|
||||||
[KnowledgeDatasetRouteKey.File]: 'File Upload',
|
|
||||||
};
|
|
||||||
|
|
||||||
export * from '@/constants/knowledge';
|
|
||||||
|
|
||||||
export const TagRenameId = 'tagRename';
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
.contentWrapper {
|
|
||||||
flex: 1;
|
|
||||||
overflow-x: auto;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 16px 20px 28px 40px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
margin-top: 16px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks';
|
|
||||||
import {
|
|
||||||
useNavigateWithFromState,
|
|
||||||
useSecondPathName,
|
|
||||||
useThirdPathName,
|
|
||||||
} from '@/hooks/route-hook';
|
|
||||||
import { Breadcrumb } from 'antd';
|
|
||||||
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Link, Outlet } from 'umi';
|
|
||||||
import Siderbar from './components/knowledge-sidebar';
|
|
||||||
import { KnowledgeDatasetRouteKey, KnowledgeRouteKey } from './constant';
|
|
||||||
import styles from './index.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;
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useGetFileIcon } from '@/pages/chat/hooks';
|
|
||||||
|
|
||||||
import { useSendAgentMessage } from './use-send-agent-message';
|
import { useSendAgentMessage } from './use-send-agent-message';
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ import { useParams } from 'umi';
|
|||||||
import DebugContent from '../debug-content';
|
import DebugContent from '../debug-content';
|
||||||
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
||||||
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
||||||
|
import { useGetFileIcon } from './use-get-file-icon';
|
||||||
|
|
||||||
function AgentChatBox() {
|
function AgentChatBox() {
|
||||||
const { data: canvasInfo, refetch } = useFetchAgent();
|
const { data: canvasInfo, refetch } = useFetchAgent();
|
||||||
|
|||||||
12
web/src/pages/agent/chat/use-get-file-icon.tsx
Normal file
12
web/src/pages/agent/chat/use-get-file-icon.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { fileIconMap } from '@/constants/common';
|
||||||
|
import { getFileExtension } from '@/utils';
|
||||||
|
|
||||||
|
export const useGetFileIcon = () => {
|
||||||
|
const getFileIcon = (filename: string) => {
|
||||||
|
const ext: string = getFileExtension(filename);
|
||||||
|
const iconPath = fileIconMap[ext as keyof typeof fileIconMap];
|
||||||
|
return `@/assets/svg/file-icon/${iconPath}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return getFileIcon;
|
||||||
|
};
|
||||||
@ -11,7 +11,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
import { RAGFlowSelect } from '@/components/ui/select';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/interfaces/database/chat';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { Message } from '@/interfaces/database/chat';
|
import { IMessage, Message } from '@/interfaces/database/chat';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { BeginQuery } from '../interface';
|
import { BeginQuery } from '../interface';
|
||||||
|
|||||||
@ -11,12 +11,12 @@ import i18n from '@/locales/config';
|
|||||||
import DebugContent from '@/pages/agent/debug-content';
|
import DebugContent from '@/pages/agent/debug-content';
|
||||||
import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log';
|
import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log';
|
||||||
import { useAwaitCompentData } from '@/pages/agent/hooks/use-chat-logic';
|
import { useAwaitCompentData } from '@/pages/agent/hooks/use-chat-logic';
|
||||||
import { useSendButtonDisabled } from '@/pages/chat/hooks';
|
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React, { forwardRef, useCallback } from 'react';
|
import React, { forwardRef, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
useGetSharedChatSearchParams,
|
useGetSharedChatSearchParams,
|
||||||
|
useSendButtonDisabled,
|
||||||
useSendNextSharedMessage,
|
useSendNextSharedMessage,
|
||||||
} from '../hooks/use-send-shared-message';
|
} from '../hooks/use-send-shared-message';
|
||||||
import { ParameterDialog } from './parameter-dialog';
|
import { ParameterDialog } from './parameter-dialog';
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { IReference } from '@/interfaces/database/chat';
|
import { IMessage, IReference } from '@/interfaces/database/chat';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
export const buildAgentMessageItemReference = (
|
export const buildAgentMessageItemReference = (
|
||||||
|
|||||||
@ -3,10 +3,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
|||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { IAgentLogMessage } from '@/interfaces/database/agent';
|
import { IAgentLogMessage } from '@/interfaces/database/agent';
|
||||||
import { IReferenceObject, Message } from '@/interfaces/database/chat';
|
import {
|
||||||
|
IMessage,
|
||||||
|
IReferenceObject,
|
||||||
|
Message,
|
||||||
|
} from '@/interfaces/database/chat';
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { IMessage } from '../chat/interface';
|
|
||||||
|
|
||||||
interface CustomModalProps {
|
interface CustomModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
import { TagRenameId } from '@/constants/knowledge';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CreateAgentForm, CreateAgentFormProps } from './create-agent-form';
|
import { CreateAgentForm, CreateAgentFormProps } from './create-agent-form';
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { z } from 'zod';
|
|||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form } from '@/components/ui/form';
|
||||||
|
import { TagRenameId } from '@/constants/knowledge';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { BrainCircuit, Check, Route } from 'lucide-react';
|
import { BrainCircuit, Check, Route } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user