| import FileIcon from '@/components/file-icon'; | |
| import HightLightMarkdown from '@/components/highlight-markdown'; | |
| import { ImageWithPopover } from '@/components/image'; | |
| import PdfDrawer from '@/components/pdf-drawer'; | |
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |
| import RetrievalDocuments from '@/components/retrieval-documents'; | |
| import SvgIcon from '@/components/svg-icon'; | |
| import { | |
| useFetchKnowledgeList, | |
| useSelectTestingResult, | |
| } from '@/hooks/knowledge-hooks'; | |
| import { useGetPaginationWithRouter } from '@/hooks/logic-hooks'; | |
| import { IReference } from '@/interfaces/database/chat'; | |
| import { | |
| Card, | |
| Divider, | |
| Flex, | |
| FloatButton, | |
| Input, | |
| Layout, | |
| List, | |
| Pagination, | |
| PaginationProps, | |
| Popover, | |
| Skeleton, | |
| Space, | |
| Spin, | |
| Tag, | |
| Tooltip, | |
| } from 'antd'; | |
| import DOMPurify from 'dompurify'; | |
| import { isEmpty } from 'lodash'; | |
| import { useMemo, useState } from 'react'; | |
| import { useTranslation } from 'react-i18next'; | |
| import MarkdownContent from '../chat/markdown-content'; | |
| import { useSendQuestion, useShowMindMapDrawer } from './hooks'; | |
| import styles from './index.less'; | |
| import MindMapDrawer from './mindmap-drawer'; | |
| import SearchSidebar from './sidebar'; | |
| const { Content } = Layout; | |
| const { Search } = Input; | |
| const SearchPage = () => { | |
| const { t } = useTranslation(); | |
| const [checkedList, setCheckedList] = useState<string[]>([]); | |
| const { chunks, total } = useSelectTestingResult(); | |
| const { list: knowledgeList } = useFetchKnowledgeList(); | |
| const checkedWithoutEmbeddingIdList = useMemo(() => { | |
| return checkedList.filter((x) => knowledgeList.some((y) => y.id === x)); | |
| }, [checkedList, knowledgeList]); | |
| const { | |
| sendQuestion, | |
| handleClickRelatedQuestion, | |
| handleSearchStrChange, | |
| handleTestChunk, | |
| setSelectedDocumentIds, | |
| answer, | |
| sendingLoading, | |
| relatedQuestions, | |
| searchStr, | |
| loading, | |
| isFirstRender, | |
| selectedDocumentIds, | |
| isSearchStrEmpty, | |
| } = useSendQuestion(checkedWithoutEmbeddingIdList); | |
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |
| useClickDrawer(); | |
| const { pagination } = useGetPaginationWithRouter(); | |
| const { | |
| mindMapVisible, | |
| hideMindMapModal, | |
| showMindMapModal, | |
| mindMapLoading, | |
| mindMap, | |
| } = useShowMindMapDrawer(checkedWithoutEmbeddingIdList, searchStr); | |
| const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => { | |
| pagination.onChange?.(pageNumber, pageSize); | |
| handleTestChunk(selectedDocumentIds, pageNumber, pageSize); | |
| }; | |
| const InputSearch = ( | |
| <Search | |
| value={searchStr} | |
| onChange={handleSearchStrChange} | |
| placeholder={t('header.search')} | |
| allowClear | |
| enterButton | |
| onSearch={sendQuestion} | |
| size="large" | |
| loading={sendingLoading} | |
| disabled={checkedWithoutEmbeddingIdList.length === 0} | |
| className={isFirstRender ? styles.globalInput : styles.partialInput} | |
| /> | |
| ); | |
| return ( | |
| <> | |
| <Layout className={styles.searchPage}> | |
| <SearchSidebar | |
| isFirstRender={isFirstRender} | |
| checkedList={checkedWithoutEmbeddingIdList} | |
| setCheckedList={setCheckedList} | |
| ></SearchSidebar> | |
| <Layout className={isFirstRender ? styles.mainLayout : ''}> | |
| <Content> | |
| {isFirstRender ? ( | |
| <Flex justify="center" className={styles.firstRenderContent}> | |
| <Flex vertical align="center" gap={'large'}> | |
| {InputSearch} | |
| </Flex> | |
| </Flex> | |
| ) : ( | |
| <Flex className={styles.content}> | |
| <section className={styles.main}> | |
| {InputSearch} | |
| <Card | |
| title={ | |
| <Flex gap={10}> | |
| <img src="/logo.svg" alt="" width={20} /> | |
| {t('chat.answerTitle')} | |
| </Flex> | |
| } | |
| className={styles.answerWrapper} | |
| > | |
| {isEmpty(answer) && sendingLoading ? ( | |
| <Skeleton active /> | |
| ) : ( | |
| answer.answer && ( | |
| <MarkdownContent | |
| loading={sendingLoading} | |
| content={answer.answer} | |
| reference={answer.reference ?? ({} as IReference)} | |
| clickDocumentButton={clickDocumentButton} | |
| ></MarkdownContent> | |
| ) | |
| )} | |
| </Card> | |
| <Divider></Divider> | |
| <RetrievalDocuments | |
| selectedDocumentIds={selectedDocumentIds} | |
| setSelectedDocumentIds={setSelectedDocumentIds} | |
| onTesting={handleTestChunk} | |
| ></RetrievalDocuments> | |
| <Divider></Divider> | |
| <Spin spinning={loading}> | |
| {chunks?.length > 0 && ( | |
| <List | |
| dataSource={chunks || []} | |
| className={styles.chunks} | |
| renderItem={(item) => ( | |
| <List.Item> | |
| <Card className={styles.card}> | |
| <Space> | |
| <ImageWithPopover | |
| id={item.img_id} | |
| ></ImageWithPopover> | |
| <Flex vertical gap={10}> | |
| <Popover | |
| content={ | |
| <div className={styles.popupMarkdown}> | |
| <HightLightMarkdown> | |
| {item.content_with_weight} | |
| </HightLightMarkdown> | |
| </div> | |
| } | |
| > | |
| <div | |
| dangerouslySetInnerHTML={{ | |
| __html: DOMPurify.sanitize( | |
| `${item.highlight}...`, | |
| ), | |
| }} | |
| className={styles.highlightContent} | |
| ></div> | |
| </Popover> | |
| <Space | |
| className={styles.documentReference} | |
| onClick={() => | |
| clickDocumentButton( | |
| item.doc_id, | |
| item as any, | |
| ) | |
| } | |
| > | |
| <FileIcon | |
| id={item.image_id} | |
| name={item.docnm_kwd} | |
| ></FileIcon> | |
| {item.docnm_kwd} | |
| </Space> | |
| </Flex> | |
| </Space> | |
| </Card> | |
| </List.Item> | |
| )} | |
| /> | |
| )} | |
| </Spin> | |
| {relatedQuestions?.length > 0 && ( | |
| <Card title={t('chat.relatedQuestion')}> | |
| <Flex wrap="wrap" gap={'10px 0'}> | |
| {relatedQuestions?.map((x, idx) => ( | |
| <Tag | |
| key={idx} | |
| className={styles.tag} | |
| onClick={handleClickRelatedQuestion(x)} | |
| > | |
| {x} | |
| </Tag> | |
| ))} | |
| </Flex> | |
| </Card> | |
| )} | |
| <Divider></Divider> | |
| <Pagination | |
| {...pagination} | |
| total={total} | |
| onChange={onChange} | |
| className={styles.pagination} | |
| /> | |
| </section> | |
| </Flex> | |
| )} | |
| </Content> | |
| </Layout> | |
| </Layout> | |
| {!isFirstRender && | |
| !isSearchStrEmpty && | |
| !isEmpty(checkedWithoutEmbeddingIdList) && ( | |
| <Tooltip title={t('chunk.mind')} zIndex={1}> | |
| <FloatButton | |
| className={styles.mindMapFloatButton} | |
| onClick={showMindMapModal} | |
| icon={ | |
| <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon> | |
| } | |
| /> | |
| </Tooltip> | |
| )} | |
| {visible && ( | |
| <PdfDrawer | |
| visible={visible} | |
| hideModal={hideModal} | |
| documentId={documentId} | |
| chunk={selectedChunk} | |
| ></PdfDrawer> | |
| )} | |
| {mindMapVisible && ( | |
| <MindMapDrawer | |
| visible={mindMapVisible} | |
| hideModal={hideMindMapModal} | |
| data={mindMap} | |
| loading={mindMapLoading} | |
| ></MindMapDrawer> | |
| )} | |
| </> | |
| ); | |
| }; | |
| export default SearchPage; | |