diff --git a/web/src/components/pdf-previewer/index.less b/web/src/components/pdf-previewer/index.less
new file mode 100644
index 000000000..b65199342
--- /dev/null
+++ b/web/src/components/pdf-previewer/index.less
@@ -0,0 +1,12 @@
+.documentContainer {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ :global(.PdfHighlighter) {
+ overflow-x: hidden;
+ }
+ :global(.Highlight--scrolledTo .Highlight__part) {
+ overflow-x: hidden;
+ background-color: rgba(255, 226, 143, 1);
+ }
+}
diff --git a/web/src/components/pdf-previewer/index.tsx b/web/src/components/pdf-previewer/index.tsx
new file mode 100644
index 000000000..e6f72ef1e
--- /dev/null
+++ b/web/src/components/pdf-previewer/index.tsx
@@ -0,0 +1,116 @@
+import {
+ useGetChunkHighlights,
+ useGetDocumentUrl,
+} from '@/hooks/documentHooks';
+import { IChunk } from '@/interfaces/database/knowledge';
+import { Skeleton } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import {
+ AreaHighlight,
+ Highlight,
+ IHighlight,
+ PdfHighlighter,
+ PdfLoader,
+ Popup,
+} from 'react-pdf-highlighter';
+
+import styles from './index.less';
+
+interface IProps {
+ chunk: IChunk;
+ documentId: string;
+ visible: boolean;
+}
+
+const HighlightPopup = ({
+ comment,
+}: {
+ comment: { text: string; emoji: string };
+}) =>
+ comment.text ? (
+
+ {comment.emoji} {comment.text}
+
+ ) : null;
+
+const DocumentPreviewer = ({ chunk, documentId, visible }: IProps) => {
+ const url = useGetDocumentUrl(documentId);
+ const state = useGetChunkHighlights(chunk);
+ const ref = useRef<(highlight: IHighlight) => void>(() => {});
+ const [loaded, setLoaded] = useState(false);
+
+ const resetHash = () => {};
+
+ useEffect(() => {
+ setLoaded(visible);
+ }, [visible]);
+
+ useEffect(() => {
+ if (state.length > 0 && loaded) {
+ setLoaded(false);
+ ref.current(state[0]);
+ }
+ }, [state, loaded]);
+
+ return (
+
+
}>
+ {(pdfDocument) => (
+
event.altKey}
+ onScrollChange={resetHash}
+ scrollRef={(scrollTo) => {
+ ref.current = scrollTo;
+ setLoaded(true);
+ }}
+ onSelectionFinished={() => null}
+ highlightTransform={(
+ highlight,
+ index,
+ setTip,
+ hideTip,
+ viewportToScaled,
+ screenshot,
+ isScrolledTo,
+ ) => {
+ const isTextHighlight = !Boolean(
+ highlight.content && highlight.content.image,
+ );
+
+ const component = isTextHighlight ? (
+
+ ) : (
+ {}}
+ />
+ );
+
+ return (
+ }
+ onMouseOver={(popupContent) =>
+ setTip(highlight, () => popupContent)
+ }
+ onMouseOut={hideTip}
+ key={index}
+ >
+ {component}
+
+ );
+ }}
+ highlights={state}
+ />
+ )}
+
+
+ );
+};
+
+export default DocumentPreviewer;
diff --git a/web/src/hooks/documentHooks.ts b/web/src/hooks/documentHooks.ts
new file mode 100644
index 000000000..a79f9d16a
--- /dev/null
+++ b/web/src/hooks/documentHooks.ts
@@ -0,0 +1,21 @@
+import { IChunk } from '@/interfaces/database/knowledge';
+import { api_host } from '@/utils/api';
+import { buildChunkHighlights } from '@/utils/documentUtils';
+import { useMemo } from 'react';
+import { IHighlight } from 'react-pdf-highlighter';
+
+export const useGetDocumentUrl = (documentId: string) => {
+ const url = useMemo(() => {
+ return `${api_host}/document/get/${documentId}`;
+ }, [documentId]);
+
+ return url;
+};
+
+export const useGetChunkHighlights = (selectedChunk: IChunk): IHighlight[] => {
+ const highlights: IHighlight[] = useMemo(() => {
+ return buildChunkHighlights(selectedChunk);
+ }, [selectedChunk]);
+
+ return highlights;
+};
diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts
index af6b12c90..76b590912 100644
--- a/web/src/interfaces/database/chat.ts
+++ b/web/src/interfaces/database/chat.ts
@@ -1,4 +1,5 @@
import { MessageType } from '@/constants/chat';
+import { IChunk } from './knowledge';
export interface PromptConfig {
empty_response: string;
@@ -66,7 +67,7 @@ export interface Message {
}
export interface IReference {
- chunks: Chunk[];
+ chunks: IChunk[];
doc_aggs: Docagg[];
total: number;
}
@@ -77,16 +78,16 @@ export interface Docagg {
doc_name: string;
}
-interface Chunk {
- chunk_id: string;
- content_ltks: string;
- content_with_weight: string;
- doc_id: string;
- docnm_kwd: string;
- img_id: string;
- important_kwd: any[];
- kb_id: string;
- similarity: number;
- term_similarity: number;
- vector_similarity: number;
-}
+// interface Chunk {
+// chunk_id: string;
+// content_ltks: string;
+// content_with_weight: string;
+// doc_id: string;
+// docnm_kwd: string;
+// img_id: string;
+// important_kwd: any[];
+// kb_id: string;
+// similarity: number;
+// term_similarity: number;
+// vector_similarity: number;
+// }
diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less
index a6e0646e2..11283f2c7 100644
--- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less
+++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less
@@ -1,8 +1,6 @@
.documentContainer {
width: 100%;
height: calc(100vh - 284px);
- // overflow-y: auto;
- // overflow-x: hidden;
position: relative;
:global(.PdfHighlighter) {
overflow-x: hidden;
diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx
index e5c26c23f..a7444fe91 100644
--- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx
@@ -16,7 +16,6 @@ import styles from './index.less';
interface IProps {
selectedChunkId: string;
}
-
const HighlightPopup = ({
comment,
}: {
@@ -28,6 +27,7 @@ const HighlightPopup = ({
) : null;
+// TODO: merge with DocumentPreviewer
const Preview = ({ selectedChunkId }: IProps) => {
const url = useGetDocumentUrl();
const state = useGetChunkHighlights(selectedChunkId);
diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts
index aad1dd083..edd256b24 100644
--- a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts
+++ b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts
@@ -1,8 +1,8 @@
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
+import { buildChunkHighlights } from '@/utils/documentUtils';
import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter';
import { useSelector } from 'umi';
-import { v4 as uuid } from 'uuid';
export const useSelectDocumentInfo = () => {
const documentInfo: IKnowledgeFile = useSelector(
@@ -41,35 +41,7 @@ export const useGetChunkHighlights = (
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
const highlights: IHighlight[] = useMemo(() => {
- return Array.isArray(selectedChunk?.positions) &&
- selectedChunk.positions.every((x) => Array.isArray(x))
- ? selectedChunk?.positions?.map((x) => {
- const actualPositions = x.map((y, index) =>
- index !== 0 ? y / 0.7 : y,
- );
- const boundingRect = {
- width: 849,
- height: 1200,
- x1: actualPositions[1],
- x2: actualPositions[2],
- y1: actualPositions[3],
- y2: actualPositions[4],
- };
- return {
- id: uuid(),
- comment: {
- text: '',
- emoji: '',
- },
- content: { text: selectedChunk.content_with_weight },
- position: {
- boundingRect: boundingRect,
- rects: [boundingRect],
- pageNumber: x[0],
- },
- };
- })
- : [];
+ return buildChunkHighlights(selectedChunk);
}, [selectedChunk]);
return highlights;
diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less
index 62dc54208..25ccfaaa2 100644
--- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less
+++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less
@@ -8,6 +8,8 @@
.popoverContentText {
white-space: pre-line;
+ max-height: 50vh;
+ overflow: auto;
.popoverContentErrorLabel {
color: red;
}
diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
index a6053f01c..6f216551c 100644
--- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
@@ -21,6 +21,25 @@ interface IProps {
}
const PopoverContent = ({ record }: IProps) => {
+ 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 (
+
+ {match}
+
+ );
+ },
+ );
+
+ return replacedText;
+ };
+
const items: DescriptionsProps['items'] = [
{
key: 'process_begin_at',
@@ -35,17 +54,7 @@ const PopoverContent = ({ record }: IProps) => {
{
key: 'progress_msg',
label: 'Progress Msg',
- children: reactStringReplace(
- record.progress_msg.trim(),
- /(\[ERROR\].+\s)/g,
- (match, i) => {
- return (
-
- {match}
-
- );
- },
- ),
+ children: replaceText(record.progress_msg.trim()),
},
];
diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
index c2e9c4a96..65c7daee5 100644
--- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
@@ -65,7 +65,11 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
>
-
+
{
const MessageItem = ({
item,
reference,
+ clickDocumentButton,
}: {
item: Message;
reference: IReference;
+ clickDocumentButton: (documentId: string, chunk: IChunk) => void;
}) => {
const userInfo = useSelectUserInfo();
const fileThumbnails = useSelectFileThumbnails();
const isAssistant = item.role === MessageType.Assistant;
+ const handleDocumentButtonClick = useCallback(
+ (documentId: string, chunk: IChunk) => () => {
+ clickDocumentButton(documentId, chunk);
+ },
+ [clickDocumentButton],
+ );
+
const getPopoverContent = useCallback(
(chunkIndex: number) => {
const chunks = reference?.chunks ?? [];
@@ -83,16 +104,19 @@ const MessageItem = ({
{documentId && (
-
+
+
)}
);
},
- [reference, fileThumbnails],
+ [reference, fileThumbnails, handleDocumentButtonClick],
);
const renderReference = useCallback(
@@ -191,6 +215,8 @@ const ChatContainer = () => {
addNewestConversation,
} = useFetchConversationOnMount();
const { sendMessage } = useSendMessage();
+ const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
+ useClickDrawer();
const loading = useOneNamespaceEffectsLoading('chatModel', [
'completeConversation',
@@ -210,41 +236,56 @@ const ChatContainer = () => {
};
return (
-
-
-
- {conversation?.message?.map((message) => {
- const assistantMessages = conversation?.message
- ?.filter((x) => x.role === MessageType.Assistant)
- .slice(1);
- const referenceIndex = assistantMessages.findIndex(
- (x) => x.id === message.id,
- );
- const reference = conversation.reference[referenceIndex];
- return (
-
- );
- })}
-
-
+ <>
+
+
+
+ {conversation?.message?.map((message) => {
+ const assistantMessages = conversation?.message
+ ?.filter((x) => x.role === MessageType.Assistant)
+ .slice(1);
+ const referenceIndex = assistantMessages.findIndex(
+ (x) => x.id === message.id,
+ );
+ const reference = conversation.reference[referenceIndex];
+ return (
+
+ );
+ })}
+
+
+
+
+ Send
+
+ }
+ onPressEnter={handlePressEnter}
+ onChange={handleInputChange}
+ />
-
- Send
-
- }
- onPressEnter={handlePressEnter}
- onChange={handleInputChange}
- />
-
+
+
+
+ >
);
};
diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts
index 52e1553e2..1c8926be5 100644
--- a/web/src/pages/chat/hooks.ts
+++ b/web/src/pages/chat/hooks.ts
@@ -4,6 +4,7 @@ import { fileIconMap } from '@/constants/common';
import { useSetModalState } from '@/hooks/commonHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IConversation, IDialog } from '@/interfaces/database/chat';
+import { IChunk } from '@/interfaces/database/knowledge';
import { getFileExtension } from '@/utils';
import omit from 'lodash/omit';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -662,4 +663,28 @@ 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,
+ };
+};
+
//#endregion
diff --git a/web/src/pages/knowledge/index.tsx b/web/src/pages/knowledge/index.tsx
index 76ad3713e..78f2af55d 100644
--- a/web/src/pages/knowledge/index.tsx
+++ b/web/src/pages/knowledge/index.tsx
@@ -50,13 +50,7 @@ const Knowledge = () => {
-
+
{list.length > 0 ? (
list.map((item: any) => {
return ;
diff --git a/web/src/utils/documentUtils.ts b/web/src/utils/documentUtils.ts
new file mode 100644
index 000000000..a2fb1e072
--- /dev/null
+++ b/web/src/utils/documentUtils.ts
@@ -0,0 +1,34 @@
+import { IChunk } from '@/interfaces/database/knowledge';
+import { v4 as uuid } from 'uuid';
+
+export const buildChunkHighlights = (selectedChunk: IChunk) => {
+ return Array.isArray(selectedChunk?.positions) &&
+ selectedChunk.positions.every((x) => Array.isArray(x))
+ ? selectedChunk?.positions?.map((x) => {
+ const actualPositions = x.map((y, index) =>
+ index !== 0 ? y / 0.7 : y,
+ );
+ const boundingRect = {
+ width: 849,
+ height: 1200,
+ x1: actualPositions[1],
+ x2: actualPositions[2],
+ y1: actualPositions[3],
+ y2: actualPositions[4],
+ };
+ return {
+ id: uuid(),
+ comment: {
+ text: '',
+ emoji: '',
+ },
+ content: { text: selectedChunk.content_with_weight },
+ position: {
+ boundingRect: boundingRect,
+ rects: [boundingRect],
+ pageNumber: x[0],
+ },
+ };
+ })
+ : [];
+};