balibabu
commited on
Commit
·
fa5e9f6
1
Parent(s):
e1017ef
feat: Add RetrievalDocuments to SearchPage #2247 (#2327)
Browse files### What problem does this PR solve?
feat: Add RetrievalDocuments to SearchPage #2247
feat: Click on the link in the reference to display the pdf drawer #2247
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/src/components/pdf-drawer/hooks.ts +27 -0
- web/src/components/pdf-drawer/index.tsx +33 -0
- web/src/components/retrieval-documents/index.less +11 -0
- web/src/components/retrieval-documents/index.tsx +55 -0
- web/src/components/retrieval-documents/select-files.tsx +73 -0
- web/src/locales/en.ts +1 -1
- web/src/locales/zh-traditional.ts +1 -1
- web/src/locales/zh.ts +1 -1
- web/src/pages/chat/chat-container/index.tsx +9 -15
- web/src/pages/chat/hooks.ts +0 -25
- web/src/pages/flow/chat/box.tsx +10 -16
- web/src/pages/search/hooks.ts +19 -1
- web/src/pages/search/index.less +39 -1
- web/src/pages/search/index.tsx +111 -82
web/src/components/pdf-drawer/hooks.ts
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useSetModalState } from '@/hooks/common-hooks';
|
2 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
3 |
+
import { useCallback, useState } from 'react';
|
4 |
+
|
5 |
+
export const useClickDrawer = () => {
|
6 |
+
const { visible, showModal, hideModal } = useSetModalState();
|
7 |
+
const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
|
8 |
+
const [documentId, setDocumentId] = useState<string>('');
|
9 |
+
|
10 |
+
const clickDocumentButton = useCallback(
|
11 |
+
(documentId: string, chunk: IChunk) => {
|
12 |
+
showModal();
|
13 |
+
setSelectedChunk(chunk);
|
14 |
+
setDocumentId(documentId);
|
15 |
+
},
|
16 |
+
[showModal],
|
17 |
+
);
|
18 |
+
|
19 |
+
return {
|
20 |
+
clickDocumentButton,
|
21 |
+
visible,
|
22 |
+
showModal,
|
23 |
+
hideModal,
|
24 |
+
selectedChunk,
|
25 |
+
documentId,
|
26 |
+
};
|
27 |
+
};
|
web/src/components/pdf-drawer/index.tsx
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { IModalProps } from '@/interfaces/common';
|
2 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
3 |
+
import { Drawer } from 'antd';
|
4 |
+
import DocumentPreviewer from '../pdf-previewer';
|
5 |
+
|
6 |
+
interface IProps extends IModalProps<any> {
|
7 |
+
documentId: string;
|
8 |
+
chunk: IChunk;
|
9 |
+
}
|
10 |
+
|
11 |
+
export const PdfDrawer = ({
|
12 |
+
visible = false,
|
13 |
+
hideModal,
|
14 |
+
documentId,
|
15 |
+
chunk,
|
16 |
+
}: IProps) => {
|
17 |
+
return (
|
18 |
+
<Drawer
|
19 |
+
title="Document Previewer"
|
20 |
+
onClose={hideModal}
|
21 |
+
open={visible}
|
22 |
+
width={'50vw'}
|
23 |
+
>
|
24 |
+
<DocumentPreviewer
|
25 |
+
documentId={documentId}
|
26 |
+
chunk={chunk}
|
27 |
+
visible={visible}
|
28 |
+
></DocumentPreviewer>
|
29 |
+
</Drawer>
|
30 |
+
);
|
31 |
+
};
|
32 |
+
|
33 |
+
export default PdfDrawer;
|
web/src/components/retrieval-documents/index.less
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.selectFilesCollapse {
|
2 |
+
:global(.ant-collapse-header) {
|
3 |
+
padding-left: 22px;
|
4 |
+
}
|
5 |
+
margin-bottom: 32px;
|
6 |
+
overflow-y: auto;
|
7 |
+
}
|
8 |
+
|
9 |
+
.selectFilesTitle {
|
10 |
+
padding-right: 10px;
|
11 |
+
}
|
web/src/components/retrieval-documents/index.tsx
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg';
|
2 |
+
import { Collapse, Flex, Space } from 'antd';
|
3 |
+
import SelectFiles from './select-files';
|
4 |
+
|
5 |
+
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
6 |
+
import { useState } from 'react';
|
7 |
+
import { useTranslation } from 'react-i18next';
|
8 |
+
import styles from './index.less';
|
9 |
+
|
10 |
+
interface IProps {
|
11 |
+
selectedDocumentIdsLength?: number;
|
12 |
+
onTesting(documentIds: string[]): void;
|
13 |
+
}
|
14 |
+
|
15 |
+
const RetrievalDocuments = ({ onTesting }: IProps) => {
|
16 |
+
const { t } = useTranslation();
|
17 |
+
const { documents } = useSelectTestingResult();
|
18 |
+
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
19 |
+
|
20 |
+
return (
|
21 |
+
<Collapse
|
22 |
+
expandIcon={() => <SelectedFilesCollapseIcon></SelectedFilesCollapseIcon>}
|
23 |
+
className={styles.selectFilesCollapse}
|
24 |
+
items={[
|
25 |
+
{
|
26 |
+
key: '1',
|
27 |
+
label: (
|
28 |
+
<Flex
|
29 |
+
justify={'space-between'}
|
30 |
+
align="center"
|
31 |
+
className={styles.selectFilesTitle}
|
32 |
+
>
|
33 |
+
<Space>
|
34 |
+
<span>
|
35 |
+
{selectedDocumentIds.length ?? 0}/{documents.length}
|
36 |
+
</span>
|
37 |
+
{t('knowledgeDetails.filesSelected')}
|
38 |
+
</Space>
|
39 |
+
</Flex>
|
40 |
+
),
|
41 |
+
children: (
|
42 |
+
<div>
|
43 |
+
<SelectFiles
|
44 |
+
setSelectedDocumentIds={setSelectedDocumentIds}
|
45 |
+
handleTesting={onTesting}
|
46 |
+
></SelectFiles>
|
47 |
+
</div>
|
48 |
+
),
|
49 |
+
},
|
50 |
+
]}
|
51 |
+
/>
|
52 |
+
);
|
53 |
+
};
|
54 |
+
|
55 |
+
export default RetrievalDocuments;
|
web/src/components/retrieval-documents/select-files.tsx
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import NewDocumentLink from '@/components/new-document-link';
|
2 |
+
import { useTranslate } from '@/hooks/common-hooks';
|
3 |
+
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
4 |
+
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
5 |
+
import { EyeOutlined } from '@ant-design/icons';
|
6 |
+
import { Button, Table, TableProps, Tooltip } from 'antd';
|
7 |
+
|
8 |
+
interface IProps {
|
9 |
+
handleTesting: (ids: string[]) => void;
|
10 |
+
setSelectedDocumentIds: (ids: string[]) => void;
|
11 |
+
}
|
12 |
+
|
13 |
+
const SelectFiles = ({ setSelectedDocumentIds, handleTesting }: IProps) => {
|
14 |
+
const { documents } = useSelectTestingResult();
|
15 |
+
const { t } = useTranslate('fileManager');
|
16 |
+
|
17 |
+
const columns: TableProps<ITestingDocument>['columns'] = [
|
18 |
+
{
|
19 |
+
title: 'Name',
|
20 |
+
dataIndex: 'doc_name',
|
21 |
+
key: 'doc_name',
|
22 |
+
render: (text) => <p>{text}</p>,
|
23 |
+
},
|
24 |
+
|
25 |
+
{
|
26 |
+
title: 'Hits',
|
27 |
+
dataIndex: 'count',
|
28 |
+
key: 'count',
|
29 |
+
width: 80,
|
30 |
+
},
|
31 |
+
{
|
32 |
+
title: 'View',
|
33 |
+
key: 'view',
|
34 |
+
width: 50,
|
35 |
+
render: (_, { doc_id, doc_name }) => (
|
36 |
+
<NewDocumentLink
|
37 |
+
documentName={doc_name}
|
38 |
+
documentId={doc_id}
|
39 |
+
prefix="document"
|
40 |
+
>
|
41 |
+
<Tooltip title={t('preview')}>
|
42 |
+
<Button type="text">
|
43 |
+
<EyeOutlined size={20} />
|
44 |
+
</Button>
|
45 |
+
</Tooltip>
|
46 |
+
</NewDocumentLink>
|
47 |
+
),
|
48 |
+
},
|
49 |
+
];
|
50 |
+
|
51 |
+
const rowSelection = {
|
52 |
+
onChange: (selectedRowKeys: React.Key[]) => {
|
53 |
+
handleTesting(selectedRowKeys as string[]);
|
54 |
+
setSelectedDocumentIds(selectedRowKeys as string[]);
|
55 |
+
},
|
56 |
+
getCheckboxProps: (record: ITestingDocument) => ({
|
57 |
+
disabled: record.doc_name === 'Disabled User', // Column configuration not to be checked
|
58 |
+
name: record.doc_name,
|
59 |
+
}),
|
60 |
+
};
|
61 |
+
|
62 |
+
return (
|
63 |
+
<Table
|
64 |
+
columns={columns}
|
65 |
+
dataSource={documents}
|
66 |
+
showHeader={false}
|
67 |
+
rowSelection={rowSelection}
|
68 |
+
rowKey={'doc_id'}
|
69 |
+
/>
|
70 |
+
);
|
71 |
+
};
|
72 |
+
|
73 |
+
export default SelectFiles;
|
web/src/locales/en.ts
CHANGED
@@ -646,7 +646,7 @@ The above is the content you need to summarize.`,
|
|
646 |
operation: 'operation',
|
647 |
run: 'Run',
|
648 |
save: 'Save',
|
649 |
-
title: '
|
650 |
beginDescription: 'This is where the flow begins.',
|
651 |
answerDescription: `A component that serves as the interface between human and bot, receiving user inputs and displaying the agent's responses.`,
|
652 |
retrievalDescription: `A component that retrieves information from a specified knowledge base and returns 'Empty response' if no information is found. Ensure the correct knowledge base is selected.`,
|
|
|
646 |
operation: 'operation',
|
647 |
run: 'Run',
|
648 |
save: 'Save',
|
649 |
+
title: 'ID:',
|
650 |
beginDescription: 'This is where the flow begins.',
|
651 |
answerDescription: `A component that serves as the interface between human and bot, receiving user inputs and displaying the agent's responses.`,
|
652 |
retrievalDescription: `A component that retrieves information from a specified knowledge base and returns 'Empty response' if no information is found. Ensure the correct knowledge base is selected.`,
|
web/src/locales/zh-traditional.ts
CHANGED
@@ -602,7 +602,7 @@ export default {
|
|
602 |
operation: '操作',
|
603 |
run: '運行',
|
604 |
save: '儲存',
|
605 |
-
title: '
|
606 |
|
607 |
beginDescription: '這是流程開始的地方',
|
608 |
answerDescription: `該組件用作機器人與人類之間的介面。它接收使用者的輸入並顯示機器人的計算結果。`,
|
|
|
602 |
operation: '操作',
|
603 |
run: '運行',
|
604 |
save: '儲存',
|
605 |
+
title: 'ID:',
|
606 |
|
607 |
beginDescription: '這是流程開始的地方',
|
608 |
answerDescription: `該組件用作機器人與人類之間的介面。它接收使用者的輸入並顯示機器人的計算結果。`,
|
web/src/locales/zh.ts
CHANGED
@@ -621,7 +621,7 @@ export default {
|
|
621 |
operation: '操作',
|
622 |
run: '运行',
|
623 |
save: '保存',
|
624 |
-
title: '
|
625 |
beginDescription: '这是流程开始的地方',
|
626 |
answerDescription: `该组件用作机器人与人类之间的接口。它接收用户的输入并显示机器人的计算结果。`,
|
627 |
retrievalDescription: `此组件用于从知识库中检索相关信息。选择知识库。如果没有检索到任何内容,将返回“空响应”。`,
|
|
|
621 |
operation: '操作',
|
622 |
run: '运行',
|
623 |
save: '保存',
|
624 |
+
title: 'ID:',
|
625 |
beginDescription: '这是流程开始的地方',
|
626 |
answerDescription: `该组件用作机器人与人类之间的接口。它接收用户的输入并显示机器人的计算结果。`,
|
627 |
retrievalDescription: `此组件用于从知识库中检索相关信息。选择知识库。如果没有检索到任何内容,将返回“空响应”。`,
|
web/src/pages/chat/chat-container/index.tsx
CHANGED
@@ -1,9 +1,7 @@
|
|
1 |
import MessageItem from '@/components/message-item';
|
2 |
-
import DocumentPreviewer from '@/components/pdf-previewer';
|
3 |
import { MessageType } from '@/constants/chat';
|
4 |
-
import {
|
5 |
import {
|
6 |
-
useClickDrawer,
|
7 |
useCreateConversationBeforeUploadDocument,
|
8 |
useGetFileIcon,
|
9 |
useGetSendButtonDisabled,
|
@@ -13,6 +11,8 @@ import {
|
|
13 |
import { buildMessageItemReference } from '../utils';
|
14 |
|
15 |
import MessageInput from '@/components/message-input';
|
|
|
|
|
16 |
import {
|
17 |
useFetchNextConversation,
|
18 |
useGetChatSearchParams,
|
@@ -96,18 +96,12 @@ const ChatContainer = () => {
|
|
96 |
}
|
97 |
></MessageInput>
|
98 |
</Flex>
|
99 |
-
<
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
>
|
105 |
-
<DocumentPreviewer
|
106 |
-
documentId={documentId}
|
107 |
-
chunk={selectedChunk}
|
108 |
-
visible={visible}
|
109 |
-
></DocumentPreviewer>
|
110 |
-
</Drawer>
|
111 |
</>
|
112 |
);
|
113 |
};
|
|
|
1 |
import MessageItem from '@/components/message-item';
|
|
|
2 |
import { MessageType } from '@/constants/chat';
|
3 |
+
import { Flex, Spin } from 'antd';
|
4 |
import {
|
|
|
5 |
useCreateConversationBeforeUploadDocument,
|
6 |
useGetFileIcon,
|
7 |
useGetSendButtonDisabled,
|
|
|
11 |
import { buildMessageItemReference } from '../utils';
|
12 |
|
13 |
import MessageInput from '@/components/message-input';
|
14 |
+
import PdfDrawer from '@/components/pdf-drawer';
|
15 |
+
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
16 |
import {
|
17 |
useFetchNextConversation,
|
18 |
useGetChatSearchParams,
|
|
|
96 |
}
|
97 |
></MessageInput>
|
98 |
</Flex>
|
99 |
+
<PdfDrawer
|
100 |
+
visible={visible}
|
101 |
+
hideModal={hideModal}
|
102 |
+
documentId={documentId}
|
103 |
+
chunk={selectedChunk}
|
104 |
+
></PdfDrawer>
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
</>
|
106 |
);
|
107 |
};
|
web/src/pages/chat/hooks.ts
CHANGED
@@ -23,7 +23,6 @@ import {
|
|
23 |
useSendMessageWithSse,
|
24 |
} from '@/hooks/logic-hooks';
|
25 |
import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
|
26 |
-
import { IChunk } from '@/interfaces/database/knowledge';
|
27 |
import { getFileExtension } from '@/utils';
|
28 |
import { useMutationState } from '@tanstack/react-query';
|
29 |
import { get } from 'lodash';
|
@@ -545,30 +544,6 @@ export const useRenameConversation = () => {
|
|
545 |
};
|
546 |
};
|
547 |
|
548 |
-
export const useClickDrawer = () => {
|
549 |
-
const { visible, showModal, hideModal } = useSetModalState();
|
550 |
-
const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
|
551 |
-
const [documentId, setDocumentId] = useState<string>('');
|
552 |
-
|
553 |
-
const clickDocumentButton = useCallback(
|
554 |
-
(documentId: string, chunk: IChunk) => {
|
555 |
-
showModal();
|
556 |
-
setSelectedChunk(chunk);
|
557 |
-
setDocumentId(documentId);
|
558 |
-
},
|
559 |
-
[showModal],
|
560 |
-
);
|
561 |
-
|
562 |
-
return {
|
563 |
-
clickDocumentButton,
|
564 |
-
visible,
|
565 |
-
showModal,
|
566 |
-
hideModal,
|
567 |
-
selectedChunk,
|
568 |
-
documentId,
|
569 |
-
};
|
570 |
-
};
|
571 |
-
|
572 |
export const useGetSendButtonDisabled = () => {
|
573 |
const { dialogId, conversationId } = useGetChatSearchParams();
|
574 |
|
|
|
23 |
useSendMessageWithSse,
|
24 |
} from '@/hooks/logic-hooks';
|
25 |
import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
|
|
|
26 |
import { getFileExtension } from '@/utils';
|
27 |
import { useMutationState } from '@tanstack/react-query';
|
28 |
import { get } from 'lodash';
|
|
|
544 |
};
|
545 |
};
|
546 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
547 |
export const useGetSendButtonDisabled = () => {
|
548 |
const { dialogId, conversationId } = useGetChatSearchParams();
|
549 |
|
web/src/pages/flow/chat/box.tsx
CHANGED
@@ -1,13 +1,14 @@
|
|
1 |
import MessageItem from '@/components/message-item';
|
2 |
-
import DocumentPreviewer from '@/components/pdf-previewer';
|
3 |
import { MessageType } from '@/constants/chat';
|
4 |
import { useTranslate } from '@/hooks/common-hooks';
|
5 |
-
import {
|
6 |
import { buildMessageItemReference } from '@/pages/chat/utils';
|
7 |
-
import { Button,
|
8 |
|
9 |
import { useSendNextMessage } from './hooks';
|
10 |
|
|
|
|
|
11 |
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
12 |
import styles from './index.less';
|
13 |
|
@@ -79,19 +80,12 @@ const FlowChatBox = () => {
|
|
79 |
onChange={handleInputChange}
|
80 |
/>
|
81 |
</Flex>
|
82 |
-
<
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
>
|
89 |
-
<DocumentPreviewer
|
90 |
-
documentId={documentId}
|
91 |
-
chunk={selectedChunk}
|
92 |
-
visible={visible}
|
93 |
-
></DocumentPreviewer>
|
94 |
-
</Drawer>
|
95 |
</>
|
96 |
);
|
97 |
};
|
|
|
1 |
import MessageItem from '@/components/message-item';
|
|
|
2 |
import { MessageType } from '@/constants/chat';
|
3 |
import { useTranslate } from '@/hooks/common-hooks';
|
4 |
+
import { useGetFileIcon } from '@/pages/chat/hooks';
|
5 |
import { buildMessageItemReference } from '@/pages/chat/utils';
|
6 |
+
import { Button, Flex, Input, Spin } from 'antd';
|
7 |
|
8 |
import { useSendNextMessage } from './hooks';
|
9 |
|
10 |
+
import PdfDrawer from '@/components/pdf-drawer';
|
11 |
+
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
12 |
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
13 |
import styles from './index.less';
|
14 |
|
|
|
80 |
onChange={handleInputChange}
|
81 |
/>
|
82 |
</Flex>
|
83 |
+
<PdfDrawer
|
84 |
+
visible={visible}
|
85 |
+
hideModal={hideModal}
|
86 |
+
documentId={documentId}
|
87 |
+
chunk={selectedChunk}
|
88 |
+
></PdfDrawer>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
</>
|
90 |
);
|
91 |
};
|
web/src/pages/search/hooks.ts
CHANGED
@@ -46,10 +46,27 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|
46 |
|
47 |
const handleClickRelatedQuestion = useCallback(
|
48 |
(question: string) => () => {
|
|
|
|
|
49 |
setSearchStr(question);
|
50 |
sendQuestion(question);
|
51 |
},
|
52 |
-
[sendQuestion],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
);
|
54 |
|
55 |
useEffect(() => {
|
@@ -71,6 +88,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|
71 |
sendQuestion,
|
72 |
handleSearchStrChange,
|
73 |
handleClickRelatedQuestion,
|
|
|
74 |
loading,
|
75 |
sendingLoading,
|
76 |
answer: currentAnswer,
|
|
|
46 |
|
47 |
const handleClickRelatedQuestion = useCallback(
|
48 |
(question: string) => () => {
|
49 |
+
if (sendingLoading) return;
|
50 |
+
|
51 |
setSearchStr(question);
|
52 |
sendQuestion(question);
|
53 |
},
|
54 |
+
[sendQuestion, sendingLoading],
|
55 |
+
);
|
56 |
+
|
57 |
+
const handleTestChunk = useCallback(
|
58 |
+
(documentIds: string[]) => {
|
59 |
+
const q = trim(searchStr);
|
60 |
+
if (sendingLoading || isEmpty(q)) return;
|
61 |
+
|
62 |
+
testChunk({
|
63 |
+
kb_id: kbIds,
|
64 |
+
highlight: true,
|
65 |
+
question: q,
|
66 |
+
doc_ids: Array.isArray(documentIds) ? documentIds : [],
|
67 |
+
});
|
68 |
+
},
|
69 |
+
[sendingLoading, searchStr, kbIds, testChunk],
|
70 |
);
|
71 |
|
72 |
useEffect(() => {
|
|
|
88 |
sendQuestion,
|
89 |
handleSearchStrChange,
|
90 |
handleClickRelatedQuestion,
|
91 |
+
handleTestChunk,
|
92 |
loading,
|
93 |
sendingLoading,
|
94 |
answer: currentAnswer,
|
web/src/pages/search/index.less
CHANGED
@@ -51,6 +51,9 @@
|
|
51 |
|
52 |
.firstRenderContent {
|
53 |
height: 100%;
|
|
|
|
|
|
|
54 |
}
|
55 |
|
56 |
.content {
|
@@ -79,10 +82,13 @@
|
|
79 |
|
80 |
.input() {
|
81 |
:global(.ant-input-affix-wrapper) {
|
82 |
-
padding: 4px
|
83 |
border-start-start-radius: 30px !important;
|
84 |
border-end-start-radius: 30px !important;
|
85 |
}
|
|
|
|
|
|
|
86 |
input {
|
87 |
height: 40px;
|
88 |
}
|
@@ -101,3 +107,35 @@
|
|
101 |
width: 100%;
|
102 |
.input();
|
103 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
.firstRenderContent {
|
53 |
height: 100%;
|
54 |
+
background-image: url(https://www.bing.com/th?id=OHR.IguazuRainbow_ZH-CN6524347982_1920x1080.webp&qlt=50);
|
55 |
+
background-position: center;
|
56 |
+
background-size: cover;
|
57 |
}
|
58 |
|
59 |
.content {
|
|
|
82 |
|
83 |
.input() {
|
84 |
:global(.ant-input-affix-wrapper) {
|
85 |
+
padding: 4px 12px;
|
86 |
border-start-start-radius: 30px !important;
|
87 |
border-end-start-radius: 30px !important;
|
88 |
}
|
89 |
+
:global(.ant-input-group-addon) {
|
90 |
+
background-color: transparent;
|
91 |
+
}
|
92 |
input {
|
93 |
height: 40px;
|
94 |
}
|
|
|
107 |
width: 100%;
|
108 |
.input();
|
109 |
}
|
110 |
+
|
111 |
+
.appIcon {
|
112 |
+
display: inline-block;
|
113 |
+
vertical-align: middle;
|
114 |
+
width: 60px;
|
115 |
+
}
|
116 |
+
|
117 |
+
.appName {
|
118 |
+
vertical-align: middle;
|
119 |
+
font-family: Inter;
|
120 |
+
font-size: 40px;
|
121 |
+
font-style: normal;
|
122 |
+
font-weight: 600;
|
123 |
+
line-height: 20px;
|
124 |
+
|
125 |
+
background: linear-gradient(to right, #095fab 10%, #25abe8 50%, #57d75b 60%);
|
126 |
+
background-size: auto auto;
|
127 |
+
background-clip: border-box;
|
128 |
+
background-size: 200% auto;
|
129 |
+
color: #fff;
|
130 |
+
background-clip: text;
|
131 |
+
text-fill-color: transparent;
|
132 |
+
-webkit-background-clip: text;
|
133 |
+
-webkit-text-fill-color: transparent;
|
134 |
+
animation: textclip 1.5s linear infinite;
|
135 |
+
}
|
136 |
+
|
137 |
+
@keyframes textclip {
|
138 |
+
to {
|
139 |
+
background-position: 200% center;
|
140 |
+
}
|
141 |
+
}
|
web/src/pages/search/index.tsx
CHANGED
@@ -19,18 +19,26 @@ import MarkdownContent from '../chat/markdown-content';
|
|
19 |
import { useSendQuestion } from './hooks';
|
20 |
import SearchSidebar from './sidebar';
|
21 |
|
|
|
|
|
|
|
|
|
|
|
22 |
import styles from './index.less';
|
23 |
|
24 |
const { Content } = Layout;
|
25 |
const { Search } = Input;
|
26 |
|
27 |
const SearchPage = () => {
|
|
|
28 |
const [checkedList, setCheckedList] = useState<string[]>([]);
|
29 |
const list = useSelectTestingResult();
|
|
|
30 |
const {
|
31 |
sendQuestion,
|
32 |
handleClickRelatedQuestion,
|
33 |
handleSearchStrChange,
|
|
|
34 |
answer,
|
35 |
sendingLoading,
|
36 |
relatedQuestions,
|
@@ -40,12 +48,14 @@ const SearchPage = () => {
|
|
40 |
loading,
|
41 |
isFirstRender,
|
42 |
} = useSendQuestion(checkedList);
|
|
|
|
|
43 |
|
44 |
const InputSearch = (
|
45 |
<Search
|
46 |
value={searchStr}
|
47 |
onChange={handleSearchStrChange}
|
48 |
-
placeholder=
|
49 |
allowClear
|
50 |
enterButton
|
51 |
onSearch={sendQuestion}
|
@@ -57,88 +67,107 @@ const SearchPage = () => {
|
|
57 |
);
|
58 |
|
59 |
return (
|
60 |
-
|
61 |
-
<
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
<
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
>
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
</Layout>
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
);
|
143 |
};
|
144 |
|
|
|
19 |
import { useSendQuestion } from './hooks';
|
20 |
import SearchSidebar from './sidebar';
|
21 |
|
22 |
+
import PdfDrawer from '@/components/pdf-drawer';
|
23 |
+
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
24 |
+
import RetrievalDocuments from '@/components/retrieval-documents';
|
25 |
+
import { useFetchAppConf } from '@/hooks/logic-hooks';
|
26 |
+
import { useTranslation } from 'react-i18next';
|
27 |
import styles from './index.less';
|
28 |
|
29 |
const { Content } = Layout;
|
30 |
const { Search } = Input;
|
31 |
|
32 |
const SearchPage = () => {
|
33 |
+
const { t } = useTranslation();
|
34 |
const [checkedList, setCheckedList] = useState<string[]>([]);
|
35 |
const list = useSelectTestingResult();
|
36 |
+
const appConf = useFetchAppConf();
|
37 |
const {
|
38 |
sendQuestion,
|
39 |
handleClickRelatedQuestion,
|
40 |
handleSearchStrChange,
|
41 |
+
handleTestChunk,
|
42 |
answer,
|
43 |
sendingLoading,
|
44 |
relatedQuestions,
|
|
|
48 |
loading,
|
49 |
isFirstRender,
|
50 |
} = useSendQuestion(checkedList);
|
51 |
+
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
52 |
+
useClickDrawer();
|
53 |
|
54 |
const InputSearch = (
|
55 |
<Search
|
56 |
value={searchStr}
|
57 |
onChange={handleSearchStrChange}
|
58 |
+
placeholder={t('header.search')}
|
59 |
allowClear
|
60 |
enterButton
|
61 |
onSearch={sendQuestion}
|
|
|
67 |
);
|
68 |
|
69 |
return (
|
70 |
+
<>
|
71 |
+
<Layout className={styles.searchPage}>
|
72 |
+
<SearchSidebar
|
73 |
+
checkedList={checkedList}
|
74 |
+
setCheckedList={setCheckedList}
|
75 |
+
></SearchSidebar>
|
76 |
+
<Layout>
|
77 |
+
<Content>
|
78 |
+
{isFirstRender ? (
|
79 |
+
<Flex
|
80 |
+
justify="center"
|
81 |
+
align="center"
|
82 |
+
className={styles.firstRenderContent}
|
83 |
+
>
|
84 |
+
<Flex vertical align="center" gap={'large'}>
|
85 |
+
<Space size={30}>
|
86 |
+
<img src="/logo.svg" alt="" className={styles.appIcon} />
|
87 |
+
<span className={styles.appName}>{appConf.appName}</span>
|
88 |
+
</Space>
|
89 |
+
{InputSearch}
|
90 |
+
</Flex>
|
91 |
+
</Flex>
|
92 |
+
) : (
|
93 |
+
<Flex className={styles.content}>
|
94 |
+
<section className={styles.main}>
|
95 |
+
{InputSearch}
|
96 |
+
{answer.answer && (
|
97 |
+
<div className={styles.answerWrapper}>
|
98 |
+
<MarkdownContent
|
99 |
+
loading={sendingLoading}
|
100 |
+
content={answer.answer}
|
101 |
+
reference={answer.reference ?? ({} as IReference)}
|
102 |
+
clickDocumentButton={clickDocumentButton}
|
103 |
+
></MarkdownContent>
|
104 |
+
</div>
|
105 |
+
)}
|
106 |
+
<Divider></Divider>
|
107 |
+
<RetrievalDocuments
|
108 |
+
selectedDocumentIdsLength={0}
|
109 |
+
onTesting={handleTestChunk}
|
110 |
+
></RetrievalDocuments>
|
111 |
+
<Divider></Divider>
|
112 |
+
{list.chunks.length > 0 && (
|
113 |
+
<List
|
114 |
+
dataSource={list.chunks}
|
115 |
+
loading={loading}
|
116 |
+
renderItem={(item) => (
|
117 |
+
<List.Item>
|
118 |
+
<Card className={styles.card}>
|
119 |
+
<Space>
|
120 |
+
<ImageWithPopover
|
121 |
+
id={item.img_id}
|
122 |
+
></ImageWithPopover>
|
123 |
+
<HightLightMarkdown>
|
124 |
+
{item.highlight}
|
125 |
+
</HightLightMarkdown>
|
126 |
+
</Space>
|
127 |
+
</Card>
|
128 |
+
</List.Item>
|
129 |
+
)}
|
130 |
+
/>
|
131 |
+
)}
|
132 |
+
{relatedQuestions?.length > 0 && (
|
133 |
+
<Card>
|
134 |
+
<Flex wrap="wrap" gap={'10px 0'}>
|
135 |
+
{relatedQuestions?.map((x, idx) => (
|
136 |
+
<Tag
|
137 |
+
key={idx}
|
138 |
+
className={styles.tag}
|
139 |
+
onClick={handleClickRelatedQuestion(x)}
|
140 |
+
>
|
141 |
+
{x}
|
142 |
+
</Tag>
|
143 |
+
))}
|
144 |
+
</Flex>
|
145 |
+
</Card>
|
146 |
+
)}
|
147 |
+
</section>
|
148 |
+
<section className={styles.graph}>
|
149 |
+
{mindMapLoading ? (
|
150 |
+
<Skeleton active />
|
151 |
+
) : (
|
152 |
+
<IndentedTree
|
153 |
+
data={mindMap}
|
154 |
+
show
|
155 |
+
style={{ width: '100%', height: '100%' }}
|
156 |
+
></IndentedTree>
|
157 |
+
)}
|
158 |
+
</section>
|
159 |
+
</Flex>
|
160 |
+
)}
|
161 |
+
</Content>
|
162 |
+
</Layout>
|
163 |
</Layout>
|
164 |
+
<PdfDrawer
|
165 |
+
visible={visible}
|
166 |
+
hideModal={hideModal}
|
167 |
+
documentId={documentId}
|
168 |
+
chunk={selectedChunk}
|
169 |
+
></PdfDrawer>
|
170 |
+
</>
|
171 |
);
|
172 |
};
|
173 |
|