From 42eeb38247c2e97d4231fbc7ca33fb176d34899c Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 9 Sep 2024 19:20:16 +0800 Subject: [PATCH] feat: Add RetrievalDocuments to SearchPage #2247 (#2327) ### 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 +++ web/src/components/pdf-drawer/index.tsx | 33 +++ .../components/retrieval-documents/index.less | 11 + .../components/retrieval-documents/index.tsx | 55 +++++ .../retrieval-documents/select-files.tsx | 73 +++++++ web/src/locales/en.ts | 2 +- web/src/locales/zh-traditional.ts | 2 +- web/src/locales/zh.ts | 2 +- web/src/pages/chat/chat-container/index.tsx | 24 +-- web/src/pages/chat/hooks.ts | 25 --- web/src/pages/flow/chat/box.tsx | 26 +-- web/src/pages/search/hooks.ts | 20 +- web/src/pages/search/index.less | 40 +++- web/src/pages/search/index.tsx | 193 ++++++++++-------- 14 files changed, 390 insertions(+), 143 deletions(-) create mode 100644 web/src/components/pdf-drawer/hooks.ts create mode 100644 web/src/components/pdf-drawer/index.tsx create mode 100644 web/src/components/retrieval-documents/index.less create mode 100644 web/src/components/retrieval-documents/index.tsx create mode 100644 web/src/components/retrieval-documents/select-files.tsx diff --git a/web/src/components/pdf-drawer/hooks.ts b/web/src/components/pdf-drawer/hooks.ts new file mode 100644 index 000000000..022b29f84 --- /dev/null +++ b/web/src/components/pdf-drawer/hooks.ts @@ -0,0 +1,27 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { IChunk } from '@/interfaces/database/knowledge'; +import { useCallback, useState } from 'react'; + +export const useClickDrawer = () => { + const { visible, showModal, hideModal } = useSetModalState(); + const [selectedChunk, setSelectedChunk] = useState({} as IChunk); + const [documentId, setDocumentId] = useState(''); + + const clickDocumentButton = useCallback( + (documentId: string, chunk: IChunk) => { + showModal(); + setSelectedChunk(chunk); + setDocumentId(documentId); + }, + [showModal], + ); + + return { + clickDocumentButton, + visible, + showModal, + hideModal, + selectedChunk, + documentId, + }; +}; diff --git a/web/src/components/pdf-drawer/index.tsx b/web/src/components/pdf-drawer/index.tsx new file mode 100644 index 000000000..77aa85358 --- /dev/null +++ b/web/src/components/pdf-drawer/index.tsx @@ -0,0 +1,33 @@ +import { IModalProps } from '@/interfaces/common'; +import { IChunk } from '@/interfaces/database/knowledge'; +import { Drawer } from 'antd'; +import DocumentPreviewer from '../pdf-previewer'; + +interface IProps extends IModalProps { + documentId: string; + chunk: IChunk; +} + +export const PdfDrawer = ({ + visible = false, + hideModal, + documentId, + chunk, +}: IProps) => { + return ( + + + + ); +}; + +export default PdfDrawer; diff --git a/web/src/components/retrieval-documents/index.less b/web/src/components/retrieval-documents/index.less new file mode 100644 index 000000000..60a5a8f6e --- /dev/null +++ b/web/src/components/retrieval-documents/index.less @@ -0,0 +1,11 @@ +.selectFilesCollapse { + :global(.ant-collapse-header) { + padding-left: 22px; + } + margin-bottom: 32px; + overflow-y: auto; +} + +.selectFilesTitle { + padding-right: 10px; +} diff --git a/web/src/components/retrieval-documents/index.tsx b/web/src/components/retrieval-documents/index.tsx new file mode 100644 index 000000000..686e410ef --- /dev/null +++ b/web/src/components/retrieval-documents/index.tsx @@ -0,0 +1,55 @@ +import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg'; +import { Collapse, Flex, Space } from 'antd'; +import SelectFiles from './select-files'; + +import { useSelectTestingResult } from '@/hooks/knowledge-hooks'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styles from './index.less'; + +interface IProps { + selectedDocumentIdsLength?: number; + onTesting(documentIds: string[]): void; +} + +const RetrievalDocuments = ({ onTesting }: IProps) => { + const { t } = useTranslation(); + const { documents } = useSelectTestingResult(); + const [selectedDocumentIds, setSelectedDocumentIds] = useState([]); + + return ( + } + className={styles.selectFilesCollapse} + items={[ + { + key: '1', + label: ( + + + + {selectedDocumentIds.length ?? 0}/{documents.length} + + {t('knowledgeDetails.filesSelected')} + + + ), + children: ( +
+ +
+ ), + }, + ]} + /> + ); +}; + +export default RetrievalDocuments; diff --git a/web/src/components/retrieval-documents/select-files.tsx b/web/src/components/retrieval-documents/select-files.tsx new file mode 100644 index 000000000..07647b501 --- /dev/null +++ b/web/src/components/retrieval-documents/select-files.tsx @@ -0,0 +1,73 @@ +import NewDocumentLink from '@/components/new-document-link'; +import { useTranslate } from '@/hooks/common-hooks'; +import { useSelectTestingResult } 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 } = useSelectTestingResult(); + const { t } = useTranslate('fileManager'); + + const columns: TableProps['columns'] = [ + { + title: 'Name', + dataIndex: 'doc_name', + key: 'doc_name', + render: (text) =>

{text}

, + }, + + { + title: 'Hits', + dataIndex: 'count', + key: 'count', + width: 80, + }, + { + title: 'View', + key: 'view', + width: 50, + render: (_, { doc_id, doc_name }) => ( + + + + + + ), + }, + ]; + + const rowSelection = { + onChange: (selectedRowKeys: React.Key[]) => { + handleTesting(selectedRowKeys as string[]); + setSelectedDocumentIds(selectedRowKeys as string[]); + }, + getCheckboxProps: (record: ITestingDocument) => ({ + disabled: record.doc_name === 'Disabled User', // Column configuration not to be checked + name: record.doc_name, + }), + }; + + return ( + + ); +}; + +export default SelectFiles; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index d85f16643..256ed4a07 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -646,7 +646,7 @@ The above is the content you need to summarize.`, operation: 'operation', run: 'Run', save: 'Save', - title: 'Title:', + title: 'ID:', beginDescription: 'This is where the flow begins.', answerDescription: `A component that serves as the interface between human and bot, receiving user inputs and displaying the agent's responses.`, 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.`, diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index ec500121b..abd403cbe 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -602,7 +602,7 @@ export default { operation: '操作', run: '運行', save: '儲存', - title: '標題:', + title: 'ID:', beginDescription: '這是流程開始的地方', answerDescription: `該組件用作機器人與人類之間的介面。它接收使用者的輸入並顯示機器人的計算結果。`, diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 2ab7dfeb2..1e2b40ae4 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -621,7 +621,7 @@ export default { operation: '操作', run: '运行', save: '保存', - title: '标题:', + title: 'ID:', beginDescription: '这是流程开始的地方', answerDescription: `该组件用作机器人与人类之间的接口。它接收用户的输入并显示机器人的计算结果。`, retrievalDescription: `此组件用于从知识库中检索相关信息。选择知识库。如果没有检索到任何内容,将返回“空响应”。`, diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index e48e68370..99192a4de 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -1,9 +1,7 @@ import MessageItem from '@/components/message-item'; -import DocumentPreviewer from '@/components/pdf-previewer'; import { MessageType } from '@/constants/chat'; -import { Drawer, Flex, Spin } from 'antd'; +import { Flex, Spin } from 'antd'; import { - useClickDrawer, useCreateConversationBeforeUploadDocument, useGetFileIcon, useGetSendButtonDisabled, @@ -13,6 +11,8 @@ import { import { buildMessageItemReference } from '../utils'; import MessageInput from '@/components/message-input'; +import PdfDrawer from '@/components/pdf-drawer'; +import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { useFetchNextConversation, useGetChatSearchParams, @@ -96,18 +96,12 @@ const ChatContainer = () => { } > - - - + ); }; diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index 92c6179ae..1257da6c2 100644 --- a/web/src/pages/chat/hooks.ts +++ b/web/src/pages/chat/hooks.ts @@ -23,7 +23,6 @@ import { useSendMessageWithSse, } from '@/hooks/logic-hooks'; import { IConversation, IDialog, Message } from '@/interfaces/database/chat'; -import { IChunk } from '@/interfaces/database/knowledge'; import { getFileExtension } from '@/utils'; import { useMutationState } from '@tanstack/react-query'; import { get } from 'lodash'; @@ -545,30 +544,6 @@ export const useRenameConversation = () => { }; }; -export const useClickDrawer = () => { - const { visible, showModal, hideModal } = useSetModalState(); - const [selectedChunk, setSelectedChunk] = useState({} as IChunk); - const [documentId, setDocumentId] = useState(''); - - const clickDocumentButton = useCallback( - (documentId: string, chunk: IChunk) => { - showModal(); - setSelectedChunk(chunk); - setDocumentId(documentId); - }, - [showModal], - ); - - return { - clickDocumentButton, - visible, - showModal, - hideModal, - selectedChunk, - documentId, - }; -}; - export const useGetSendButtonDisabled = () => { const { dialogId, conversationId } = useGetChatSearchParams(); diff --git a/web/src/pages/flow/chat/box.tsx b/web/src/pages/flow/chat/box.tsx index ef0e4851e..e85c1a290 100644 --- a/web/src/pages/flow/chat/box.tsx +++ b/web/src/pages/flow/chat/box.tsx @@ -1,13 +1,14 @@ import MessageItem from '@/components/message-item'; -import DocumentPreviewer from '@/components/pdf-previewer'; import { MessageType } from '@/constants/chat'; import { useTranslate } from '@/hooks/common-hooks'; -import { useClickDrawer, useGetFileIcon } from '@/pages/chat/hooks'; +import { useGetFileIcon } from '@/pages/chat/hooks'; import { buildMessageItemReference } from '@/pages/chat/utils'; -import { Button, Drawer, Flex, Input, Spin } from 'antd'; +import { Button, Flex, Input, Spin } from 'antd'; import { useSendNextMessage } from './hooks'; +import PdfDrawer from '@/components/pdf-drawer'; +import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; import styles from './index.less'; @@ -79,19 +80,12 @@ const FlowChatBox = () => { onChange={handleInputChange} /> - - - + ); }; diff --git a/web/src/pages/search/hooks.ts b/web/src/pages/search/hooks.ts index c18ff4f10..f960db10e 100644 --- a/web/src/pages/search/hooks.ts +++ b/web/src/pages/search/hooks.ts @@ -46,10 +46,27 @@ export const useSendQuestion = (kbIds: string[]) => { const handleClickRelatedQuestion = useCallback( (question: string) => () => { + if (sendingLoading) return; + setSearchStr(question); sendQuestion(question); }, - [sendQuestion], + [sendQuestion, sendingLoading], + ); + + const handleTestChunk = useCallback( + (documentIds: string[]) => { + const q = trim(searchStr); + if (sendingLoading || isEmpty(q)) return; + + testChunk({ + kb_id: kbIds, + highlight: true, + question: q, + doc_ids: Array.isArray(documentIds) ? documentIds : [], + }); + }, + [sendingLoading, searchStr, kbIds, testChunk], ); useEffect(() => { @@ -71,6 +88,7 @@ export const useSendQuestion = (kbIds: string[]) => { sendQuestion, handleSearchStrChange, handleClickRelatedQuestion, + handleTestChunk, loading, sendingLoading, answer: currentAnswer, diff --git a/web/src/pages/search/index.less b/web/src/pages/search/index.less index f0419c629..abb0ad8af 100644 --- a/web/src/pages/search/index.less +++ b/web/src/pages/search/index.less @@ -51,6 +51,9 @@ .firstRenderContent { height: 100%; + background-image: url(https://www.bing.com/th?id=OHR.IguazuRainbow_ZH-CN6524347982_1920x1080.webp&qlt=50); + background-position: center; + background-size: cover; } .content { @@ -79,10 +82,13 @@ .input() { :global(.ant-input-affix-wrapper) { - padding: 4px 8px; + padding: 4px 12px; border-start-start-radius: 30px !important; border-end-start-radius: 30px !important; } + :global(.ant-input-group-addon) { + background-color: transparent; + } input { height: 40px; } @@ -101,3 +107,35 @@ width: 100%; .input(); } + +.appIcon { + display: inline-block; + vertical-align: middle; + width: 60px; +} + +.appName { + vertical-align: middle; + font-family: Inter; + font-size: 40px; + font-style: normal; + font-weight: 600; + line-height: 20px; + + background: linear-gradient(to right, #095fab 10%, #25abe8 50%, #57d75b 60%); + background-size: auto auto; + background-clip: border-box; + background-size: 200% auto; + color: #fff; + background-clip: text; + text-fill-color: transparent; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + animation: textclip 1.5s linear infinite; +} + +@keyframes textclip { + to { + background-position: 200% center; + } +} diff --git a/web/src/pages/search/index.tsx b/web/src/pages/search/index.tsx index 22696406d..5dbd664c6 100644 --- a/web/src/pages/search/index.tsx +++ b/web/src/pages/search/index.tsx @@ -19,18 +19,26 @@ import MarkdownContent from '../chat/markdown-content'; import { useSendQuestion } from './hooks'; import SearchSidebar from './sidebar'; +import PdfDrawer from '@/components/pdf-drawer'; +import { useClickDrawer } from '@/components/pdf-drawer/hooks'; +import RetrievalDocuments from '@/components/retrieval-documents'; +import { useFetchAppConf } from '@/hooks/logic-hooks'; +import { useTranslation } from 'react-i18next'; import styles from './index.less'; const { Content } = Layout; const { Search } = Input; const SearchPage = () => { + const { t } = useTranslation(); const [checkedList, setCheckedList] = useState([]); const list = useSelectTestingResult(); + const appConf = useFetchAppConf(); const { sendQuestion, handleClickRelatedQuestion, handleSearchStrChange, + handleTestChunk, answer, sendingLoading, relatedQuestions, @@ -40,12 +48,14 @@ const SearchPage = () => { loading, isFirstRender, } = useSendQuestion(checkedList); + const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = + useClickDrawer(); const InputSearch = ( { ); return ( - - - - - {isFirstRender ? ( - - {InputSearch} - - ) : ( - -
- {InputSearch} - {answer.answer && ( -
- {}} - > -
- )} - - {list.chunks.length > 0 && ( - ( - - - - - - {item.highlight} - - - - - )} - /> - )} - {relatedQuestions?.length > 0 && ( - - - {relatedQuestions?.map((x, idx) => ( - - {x} - - ))} - - - )} -
-
- {mindMapLoading ? ( - - ) : ( - - )} -
-
- )} -
+ <> + + + + + {isFirstRender ? ( + + + + + {appConf.appName} + + {InputSearch} + + + ) : ( + +
+ {InputSearch} + {answer.answer && ( +
+ +
+ )} + + + + {list.chunks.length > 0 && ( + ( + + + + + + {item.highlight} + + + + + )} + /> + )} + {relatedQuestions?.length > 0 && ( + + + {relatedQuestions?.map((x, idx) => ( + + {x} + + ))} + + + )} +
+
+ {mindMapLoading ? ( + + ) : ( + + )} +
+
+ )} +
+
-
+ + ); };