balibabu
commited on
Commit
·
ae21b62
1
Parent(s):
b83edb4
feat: add DocumentPreviewer for chunk of chat reference and remove duplicate \n from record.progress_msg (#97)
Browse files* feat: Remove duplicate \n from record.progress_msg
* feat: add DocumentPreviewer for chunk of chat reference
- web/src/components/pdf-previewer/index.less +12 -0
- web/src/components/pdf-previewer/index.tsx +116 -0
- web/src/hooks/documentHooks.ts +21 -0
- web/src/interfaces/database/chat.ts +15 -14
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less +0 -2
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx +1 -1
- web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts +2 -30
- web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less +2 -0
- web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx +20 -11
- web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx +5 -1
- web/src/pages/chat/chat-container/index.tsx +79 -38
- web/src/pages/chat/hooks.ts +25 -0
- web/src/pages/knowledge/index.tsx +1 -7
- web/src/utils/documentUtils.ts +34 -0
web/src/components/pdf-previewer/index.less
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.documentContainer {
|
2 |
+
width: 100%;
|
3 |
+
height: 100%;
|
4 |
+
position: relative;
|
5 |
+
:global(.PdfHighlighter) {
|
6 |
+
overflow-x: hidden;
|
7 |
+
}
|
8 |
+
:global(.Highlight--scrolledTo .Highlight__part) {
|
9 |
+
overflow-x: hidden;
|
10 |
+
background-color: rgba(255, 226, 143, 1);
|
11 |
+
}
|
12 |
+
}
|
web/src/components/pdf-previewer/index.tsx
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
useGetChunkHighlights,
|
3 |
+
useGetDocumentUrl,
|
4 |
+
} from '@/hooks/documentHooks';
|
5 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
6 |
+
import { Skeleton } from 'antd';
|
7 |
+
import { useEffect, useRef, useState } from 'react';
|
8 |
+
import {
|
9 |
+
AreaHighlight,
|
10 |
+
Highlight,
|
11 |
+
IHighlight,
|
12 |
+
PdfHighlighter,
|
13 |
+
PdfLoader,
|
14 |
+
Popup,
|
15 |
+
} from 'react-pdf-highlighter';
|
16 |
+
|
17 |
+
import styles from './index.less';
|
18 |
+
|
19 |
+
interface IProps {
|
20 |
+
chunk: IChunk;
|
21 |
+
documentId: string;
|
22 |
+
visible: boolean;
|
23 |
+
}
|
24 |
+
|
25 |
+
const HighlightPopup = ({
|
26 |
+
comment,
|
27 |
+
}: {
|
28 |
+
comment: { text: string; emoji: string };
|
29 |
+
}) =>
|
30 |
+
comment.text ? (
|
31 |
+
<div className="Highlight__popup">
|
32 |
+
{comment.emoji} {comment.text}
|
33 |
+
</div>
|
34 |
+
) : null;
|
35 |
+
|
36 |
+
const DocumentPreviewer = ({ chunk, documentId, visible }: IProps) => {
|
37 |
+
const url = useGetDocumentUrl(documentId);
|
38 |
+
const state = useGetChunkHighlights(chunk);
|
39 |
+
const ref = useRef<(highlight: IHighlight) => void>(() => {});
|
40 |
+
const [loaded, setLoaded] = useState(false);
|
41 |
+
|
42 |
+
const resetHash = () => {};
|
43 |
+
|
44 |
+
useEffect(() => {
|
45 |
+
setLoaded(visible);
|
46 |
+
}, [visible]);
|
47 |
+
|
48 |
+
useEffect(() => {
|
49 |
+
if (state.length > 0 && loaded) {
|
50 |
+
setLoaded(false);
|
51 |
+
ref.current(state[0]);
|
52 |
+
}
|
53 |
+
}, [state, loaded]);
|
54 |
+
|
55 |
+
return (
|
56 |
+
<div className={styles.documentContainer}>
|
57 |
+
<PdfLoader url={url} beforeLoad={<Skeleton active />}>
|
58 |
+
{(pdfDocument) => (
|
59 |
+
<PdfHighlighter
|
60 |
+
pdfDocument={pdfDocument}
|
61 |
+
enableAreaSelection={(event) => event.altKey}
|
62 |
+
onScrollChange={resetHash}
|
63 |
+
scrollRef={(scrollTo) => {
|
64 |
+
ref.current = scrollTo;
|
65 |
+
setLoaded(true);
|
66 |
+
}}
|
67 |
+
onSelectionFinished={() => null}
|
68 |
+
highlightTransform={(
|
69 |
+
highlight,
|
70 |
+
index,
|
71 |
+
setTip,
|
72 |
+
hideTip,
|
73 |
+
viewportToScaled,
|
74 |
+
screenshot,
|
75 |
+
isScrolledTo,
|
76 |
+
) => {
|
77 |
+
const isTextHighlight = !Boolean(
|
78 |
+
highlight.content && highlight.content.image,
|
79 |
+
);
|
80 |
+
|
81 |
+
const component = isTextHighlight ? (
|
82 |
+
<Highlight
|
83 |
+
isScrolledTo={isScrolledTo}
|
84 |
+
position={highlight.position}
|
85 |
+
comment={highlight.comment}
|
86 |
+
/>
|
87 |
+
) : (
|
88 |
+
<AreaHighlight
|
89 |
+
isScrolledTo={isScrolledTo}
|
90 |
+
highlight={highlight}
|
91 |
+
onChange={() => {}}
|
92 |
+
/>
|
93 |
+
);
|
94 |
+
|
95 |
+
return (
|
96 |
+
<Popup
|
97 |
+
popupContent={<HighlightPopup {...highlight} />}
|
98 |
+
onMouseOver={(popupContent) =>
|
99 |
+
setTip(highlight, () => popupContent)
|
100 |
+
}
|
101 |
+
onMouseOut={hideTip}
|
102 |
+
key={index}
|
103 |
+
>
|
104 |
+
{component}
|
105 |
+
</Popup>
|
106 |
+
);
|
107 |
+
}}
|
108 |
+
highlights={state}
|
109 |
+
/>
|
110 |
+
)}
|
111 |
+
</PdfLoader>
|
112 |
+
</div>
|
113 |
+
);
|
114 |
+
};
|
115 |
+
|
116 |
+
export default DocumentPreviewer;
|
web/src/hooks/documentHooks.ts
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
2 |
+
import { api_host } from '@/utils/api';
|
3 |
+
import { buildChunkHighlights } from '@/utils/documentUtils';
|
4 |
+
import { useMemo } from 'react';
|
5 |
+
import { IHighlight } from 'react-pdf-highlighter';
|
6 |
+
|
7 |
+
export const useGetDocumentUrl = (documentId: string) => {
|
8 |
+
const url = useMemo(() => {
|
9 |
+
return `${api_host}/document/get/${documentId}`;
|
10 |
+
}, [documentId]);
|
11 |
+
|
12 |
+
return url;
|
13 |
+
};
|
14 |
+
|
15 |
+
export const useGetChunkHighlights = (selectedChunk: IChunk): IHighlight[] => {
|
16 |
+
const highlights: IHighlight[] = useMemo(() => {
|
17 |
+
return buildChunkHighlights(selectedChunk);
|
18 |
+
}, [selectedChunk]);
|
19 |
+
|
20 |
+
return highlights;
|
21 |
+
};
|
web/src/interfaces/database/chat.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import { MessageType } from '@/constants/chat';
|
|
|
2 |
|
3 |
export interface PromptConfig {
|
4 |
empty_response: string;
|
@@ -66,7 +67,7 @@ export interface Message {
|
|
66 |
}
|
67 |
|
68 |
export interface IReference {
|
69 |
-
chunks:
|
70 |
doc_aggs: Docagg[];
|
71 |
total: number;
|
72 |
}
|
@@ -77,16 +78,16 @@ export interface Docagg {
|
|
77 |
doc_name: string;
|
78 |
}
|
79 |
|
80 |
-
interface Chunk {
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
}
|
|
|
1 |
import { MessageType } from '@/constants/chat';
|
2 |
+
import { IChunk } from './knowledge';
|
3 |
|
4 |
export interface PromptConfig {
|
5 |
empty_response: string;
|
|
|
67 |
}
|
68 |
|
69 |
export interface IReference {
|
70 |
+
chunks: IChunk[];
|
71 |
doc_aggs: Docagg[];
|
72 |
total: number;
|
73 |
}
|
|
|
78 |
doc_name: string;
|
79 |
}
|
80 |
|
81 |
+
// interface Chunk {
|
82 |
+
// chunk_id: string;
|
83 |
+
// content_ltks: string;
|
84 |
+
// content_with_weight: string;
|
85 |
+
// doc_id: string;
|
86 |
+
// docnm_kwd: string;
|
87 |
+
// img_id: string;
|
88 |
+
// important_kwd: any[];
|
89 |
+
// kb_id: string;
|
90 |
+
// similarity: number;
|
91 |
+
// term_similarity: number;
|
92 |
+
// vector_similarity: number;
|
93 |
+
// }
|
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less
CHANGED
@@ -1,8 +1,6 @@
|
|
1 |
.documentContainer {
|
2 |
width: 100%;
|
3 |
height: calc(100vh - 284px);
|
4 |
-
// overflow-y: auto;
|
5 |
-
// overflow-x: hidden;
|
6 |
position: relative;
|
7 |
:global(.PdfHighlighter) {
|
8 |
overflow-x: hidden;
|
|
|
1 |
.documentContainer {
|
2 |
width: 100%;
|
3 |
height: calc(100vh - 284px);
|
|
|
|
|
4 |
position: relative;
|
5 |
:global(.PdfHighlighter) {
|
6 |
overflow-x: hidden;
|
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx
CHANGED
@@ -16,7 +16,6 @@ import styles from './index.less';
|
|
16 |
interface IProps {
|
17 |
selectedChunkId: string;
|
18 |
}
|
19 |
-
|
20 |
const HighlightPopup = ({
|
21 |
comment,
|
22 |
}: {
|
@@ -28,6 +27,7 @@ const HighlightPopup = ({
|
|
28 |
</div>
|
29 |
) : null;
|
30 |
|
|
|
31 |
const Preview = ({ selectedChunkId }: IProps) => {
|
32 |
const url = useGetDocumentUrl();
|
33 |
const state = useGetChunkHighlights(selectedChunkId);
|
|
|
16 |
interface IProps {
|
17 |
selectedChunkId: string;
|
18 |
}
|
|
|
19 |
const HighlightPopup = ({
|
20 |
comment,
|
21 |
}: {
|
|
|
27 |
</div>
|
28 |
) : null;
|
29 |
|
30 |
+
// TODO: merge with DocumentPreviewer
|
31 |
const Preview = ({ selectedChunkId }: IProps) => {
|
32 |
const url = useGetDocumentUrl();
|
33 |
const state = useGetChunkHighlights(selectedChunkId);
|
web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
|
|
2 |
import { useCallback, useMemo, useState } from 'react';
|
3 |
import { IHighlight } from 'react-pdf-highlighter';
|
4 |
import { useSelector } from 'umi';
|
5 |
-
import { v4 as uuid } from 'uuid';
|
6 |
|
7 |
export const useSelectDocumentInfo = () => {
|
8 |
const documentInfo: IKnowledgeFile = useSelector(
|
@@ -41,35 +41,7 @@ export const useGetChunkHighlights = (
|
|
41 |
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
|
42 |
|
43 |
const highlights: IHighlight[] = useMemo(() => {
|
44 |
-
return
|
45 |
-
selectedChunk.positions.every((x) => Array.isArray(x))
|
46 |
-
? selectedChunk?.positions?.map((x) => {
|
47 |
-
const actualPositions = x.map((y, index) =>
|
48 |
-
index !== 0 ? y / 0.7 : y,
|
49 |
-
);
|
50 |
-
const boundingRect = {
|
51 |
-
width: 849,
|
52 |
-
height: 1200,
|
53 |
-
x1: actualPositions[1],
|
54 |
-
x2: actualPositions[2],
|
55 |
-
y1: actualPositions[3],
|
56 |
-
y2: actualPositions[4],
|
57 |
-
};
|
58 |
-
return {
|
59 |
-
id: uuid(),
|
60 |
-
comment: {
|
61 |
-
text: '',
|
62 |
-
emoji: '',
|
63 |
-
},
|
64 |
-
content: { text: selectedChunk.content_with_weight },
|
65 |
-
position: {
|
66 |
-
boundingRect: boundingRect,
|
67 |
-
rects: [boundingRect],
|
68 |
-
pageNumber: x[0],
|
69 |
-
},
|
70 |
-
};
|
71 |
-
})
|
72 |
-
: [];
|
73 |
}, [selectedChunk]);
|
74 |
|
75 |
return highlights;
|
|
|
1 |
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
2 |
+
import { buildChunkHighlights } from '@/utils/documentUtils';
|
3 |
import { useCallback, useMemo, useState } from 'react';
|
4 |
import { IHighlight } from 'react-pdf-highlighter';
|
5 |
import { useSelector } from 'umi';
|
|
|
6 |
|
7 |
export const useSelectDocumentInfo = () => {
|
8 |
const documentInfo: IKnowledgeFile = useSelector(
|
|
|
41 |
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
|
42 |
|
43 |
const highlights: IHighlight[] = useMemo(() => {
|
44 |
+
return buildChunkHighlights(selectedChunk);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
}, [selectedChunk]);
|
46 |
|
47 |
return highlights;
|
web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less
CHANGED
@@ -8,6 +8,8 @@
|
|
8 |
|
9 |
.popoverContentText {
|
10 |
white-space: pre-line;
|
|
|
|
|
11 |
.popoverContentErrorLabel {
|
12 |
color: red;
|
13 |
}
|
|
|
8 |
|
9 |
.popoverContentText {
|
10 |
white-space: pre-line;
|
11 |
+
max-height: 50vh;
|
12 |
+
overflow: auto;
|
13 |
.popoverContentErrorLabel {
|
14 |
color: red;
|
15 |
}
|
web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
CHANGED
@@ -21,6 +21,25 @@ interface IProps {
|
|
21 |
}
|
22 |
|
23 |
const PopoverContent = ({ record }: IProps) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
const items: DescriptionsProps['items'] = [
|
25 |
{
|
26 |
key: 'process_begin_at',
|
@@ -35,17 +54,7 @@ const PopoverContent = ({ record }: IProps) => {
|
|
35 |
{
|
36 |
key: 'progress_msg',
|
37 |
label: 'Progress Msg',
|
38 |
-
children:
|
39 |
-
record.progress_msg.trim(),
|
40 |
-
/(\[ERROR\].+\s)/g,
|
41 |
-
(match, i) => {
|
42 |
-
return (
|
43 |
-
<span key={i} className={styles.popoverContentErrorLabel}>
|
44 |
-
{match}
|
45 |
-
</span>
|
46 |
-
);
|
47 |
-
},
|
48 |
-
),
|
49 |
},
|
50 |
];
|
51 |
|
|
|
21 |
}
|
22 |
|
23 |
const PopoverContent = ({ record }: IProps) => {
|
24 |
+
const replaceText = (text: string) => {
|
25 |
+
// Remove duplicate \n
|
26 |
+
const nextText = text.replace(/(\n)\1+/g, '$1');
|
27 |
+
|
28 |
+
const replacedText = reactStringReplace(
|
29 |
+
nextText,
|
30 |
+
/(\[ERROR\].+\s)/g,
|
31 |
+
(match, i) => {
|
32 |
+
return (
|
33 |
+
<span key={i} className={styles.popoverContentErrorLabel}>
|
34 |
+
{match}
|
35 |
+
</span>
|
36 |
+
);
|
37 |
+
},
|
38 |
+
);
|
39 |
+
|
40 |
+
return replacedText;
|
41 |
+
};
|
42 |
+
|
43 |
const items: DescriptionsProps['items'] = [
|
44 |
{
|
45 |
key: 'process_begin_at',
|
|
|
54 |
{
|
55 |
key: 'progress_msg',
|
56 |
label: 'Progress Msg',
|
57 |
+
children: replaceText(record.progress_msg.trim()),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
},
|
59 |
];
|
60 |
|
web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
CHANGED
@@ -65,7 +65,11 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
|
|
65 |
>
|
66 |
<Input placeholder="" />
|
67 |
</Form.Item>
|
68 |
-
<Form.Item
|
|
|
|
|
|
|
|
|
69 |
<Input.TextArea autoSize={{ minRows: 5 }} />
|
70 |
</Form.Item>
|
71 |
<Form.Item
|
|
|
65 |
>
|
66 |
<Input placeholder="" />
|
67 |
</Form.Item>
|
68 |
+
<Form.Item
|
69 |
+
name={['prompt_config', 'prologue']}
|
70 |
+
label="Set an opener"
|
71 |
+
initialValue={"Hi! I'm your assistant, what can I do for you?"}
|
72 |
+
>
|
73 |
<Input.TextArea autoSize={{ minRows: 5 }} />
|
74 |
</Form.Item>
|
75 |
<Form.Item
|
web/src/pages/chat/chat-container/index.tsx
CHANGED
@@ -3,11 +3,21 @@ import { MessageType } from '@/constants/chat';
|
|
3 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
4 |
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
5 |
import { IReference, Message } from '@/interfaces/database/chat';
|
6 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
import classNames from 'classnames';
|
8 |
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
9 |
import reactStringReplace from 'react-string-replace';
|
10 |
import {
|
|
|
11 |
useFetchConversationOnMount,
|
12 |
useGetFileIcon,
|
13 |
useSendMessage,
|
@@ -15,7 +25,9 @@ import {
|
|
15 |
|
16 |
import Image from '@/components/image';
|
17 |
import NewDocumentLink from '@/components/new-document-link';
|
|
|
18 |
import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
|
|
19 |
import { InfoCircleOutlined } from '@ant-design/icons';
|
20 |
import Markdown from 'react-markdown';
|
21 |
import { visitParents } from 'unist-util-visit-parents';
|
@@ -41,15 +53,24 @@ const rehypeWrapReference = () => {
|
|
41 |
const MessageItem = ({
|
42 |
item,
|
43 |
reference,
|
|
|
44 |
}: {
|
45 |
item: Message;
|
46 |
reference: IReference;
|
|
|
47 |
}) => {
|
48 |
const userInfo = useSelectUserInfo();
|
49 |
const fileThumbnails = useSelectFileThumbnails();
|
50 |
|
51 |
const isAssistant = item.role === MessageType.Assistant;
|
52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
const getPopoverContent = useCallback(
|
54 |
(chunkIndex: number) => {
|
55 |
const chunks = reference?.chunks ?? [];
|
@@ -83,16 +104,19 @@ const MessageItem = ({
|
|
83 |
{documentId && (
|
84 |
<Flex gap={'middle'}>
|
85 |
<img src={fileThumbnails[documentId]} alt="" />
|
86 |
-
<
|
|
|
|
|
|
|
87 |
{document?.doc_name}
|
88 |
-
</
|
89 |
</Flex>
|
90 |
)}
|
91 |
</Space>
|
92 |
</Flex>
|
93 |
);
|
94 |
},
|
95 |
-
[reference, fileThumbnails],
|
96 |
);
|
97 |
|
98 |
const renderReference = useCallback(
|
@@ -191,6 +215,8 @@ const ChatContainer = () => {
|
|
191 |
addNewestConversation,
|
192 |
} = useFetchConversationOnMount();
|
193 |
const { sendMessage } = useSendMessage();
|
|
|
|
|
194 |
|
195 |
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
196 |
'completeConversation',
|
@@ -210,41 +236,56 @@ const ChatContainer = () => {
|
|
210 |
};
|
211 |
|
212 |
return (
|
213 |
-
|
214 |
-
<Flex flex={1}
|
215 |
-
<
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
</Flex>
|
235 |
-
<
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
);
|
249 |
};
|
250 |
|
|
|
3 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
4 |
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
5 |
import { IReference, Message } from '@/interfaces/database/chat';
|
6 |
+
import {
|
7 |
+
Avatar,
|
8 |
+
Button,
|
9 |
+
Drawer,
|
10 |
+
Flex,
|
11 |
+
Input,
|
12 |
+
List,
|
13 |
+
Popover,
|
14 |
+
Space,
|
15 |
+
} from 'antd';
|
16 |
import classNames from 'classnames';
|
17 |
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
18 |
import reactStringReplace from 'react-string-replace';
|
19 |
import {
|
20 |
+
useClickDrawer,
|
21 |
useFetchConversationOnMount,
|
22 |
useGetFileIcon,
|
23 |
useSendMessage,
|
|
|
25 |
|
26 |
import Image from '@/components/image';
|
27 |
import NewDocumentLink from '@/components/new-document-link';
|
28 |
+
import DocumentPreviewer from '@/components/pdf-previewer';
|
29 |
import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
30 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
31 |
import { InfoCircleOutlined } from '@ant-design/icons';
|
32 |
import Markdown from 'react-markdown';
|
33 |
import { visitParents } from 'unist-util-visit-parents';
|
|
|
53 |
const MessageItem = ({
|
54 |
item,
|
55 |
reference,
|
56 |
+
clickDocumentButton,
|
57 |
}: {
|
58 |
item: Message;
|
59 |
reference: IReference;
|
60 |
+
clickDocumentButton: (documentId: string, chunk: IChunk) => void;
|
61 |
}) => {
|
62 |
const userInfo = useSelectUserInfo();
|
63 |
const fileThumbnails = useSelectFileThumbnails();
|
64 |
|
65 |
const isAssistant = item.role === MessageType.Assistant;
|
66 |
|
67 |
+
const handleDocumentButtonClick = useCallback(
|
68 |
+
(documentId: string, chunk: IChunk) => () => {
|
69 |
+
clickDocumentButton(documentId, chunk);
|
70 |
+
},
|
71 |
+
[clickDocumentButton],
|
72 |
+
);
|
73 |
+
|
74 |
const getPopoverContent = useCallback(
|
75 |
(chunkIndex: number) => {
|
76 |
const chunks = reference?.chunks ?? [];
|
|
|
104 |
{documentId && (
|
105 |
<Flex gap={'middle'}>
|
106 |
<img src={fileThumbnails[documentId]} alt="" />
|
107 |
+
<Button
|
108 |
+
type="link"
|
109 |
+
onClick={handleDocumentButtonClick(documentId, chunkItem)}
|
110 |
+
>
|
111 |
{document?.doc_name}
|
112 |
+
</Button>
|
113 |
</Flex>
|
114 |
)}
|
115 |
</Space>
|
116 |
</Flex>
|
117 |
);
|
118 |
},
|
119 |
+
[reference, fileThumbnails, handleDocumentButtonClick],
|
120 |
);
|
121 |
|
122 |
const renderReference = useCallback(
|
|
|
215 |
addNewestConversation,
|
216 |
} = useFetchConversationOnMount();
|
217 |
const { sendMessage } = useSendMessage();
|
218 |
+
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
219 |
+
useClickDrawer();
|
220 |
|
221 |
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
222 |
'completeConversation',
|
|
|
236 |
};
|
237 |
|
238 |
return (
|
239 |
+
<>
|
240 |
+
<Flex flex={1} className={styles.chatContainer} vertical>
|
241 |
+
<Flex flex={1} vertical className={styles.messageContainer}>
|
242 |
+
<div>
|
243 |
+
{conversation?.message?.map((message) => {
|
244 |
+
const assistantMessages = conversation?.message
|
245 |
+
?.filter((x) => x.role === MessageType.Assistant)
|
246 |
+
.slice(1);
|
247 |
+
const referenceIndex = assistantMessages.findIndex(
|
248 |
+
(x) => x.id === message.id,
|
249 |
+
);
|
250 |
+
const reference = conversation.reference[referenceIndex];
|
251 |
+
return (
|
252 |
+
<MessageItem
|
253 |
+
key={message.id}
|
254 |
+
item={message}
|
255 |
+
reference={reference}
|
256 |
+
clickDocumentButton={clickDocumentButton}
|
257 |
+
></MessageItem>
|
258 |
+
);
|
259 |
+
})}
|
260 |
+
</div>
|
261 |
+
<div ref={ref} />
|
262 |
+
</Flex>
|
263 |
+
<Input
|
264 |
+
size="large"
|
265 |
+
placeholder="Message Resume Assistant..."
|
266 |
+
value={value}
|
267 |
+
suffix={
|
268 |
+
<Button type="primary" onClick={handlePressEnter} loading={loading}>
|
269 |
+
Send
|
270 |
+
</Button>
|
271 |
+
}
|
272 |
+
onPressEnter={handlePressEnter}
|
273 |
+
onChange={handleInputChange}
|
274 |
+
/>
|
275 |
</Flex>
|
276 |
+
<Drawer
|
277 |
+
title="Document Previewer"
|
278 |
+
onClose={hideModal}
|
279 |
+
open={visible}
|
280 |
+
width={'50vw'}
|
281 |
+
>
|
282 |
+
<DocumentPreviewer
|
283 |
+
documentId={documentId}
|
284 |
+
chunk={selectedChunk}
|
285 |
+
visible={visible}
|
286 |
+
></DocumentPreviewer>
|
287 |
+
</Drawer>
|
288 |
+
</>
|
289 |
);
|
290 |
};
|
291 |
|
web/src/pages/chat/hooks.ts
CHANGED
@@ -4,6 +4,7 @@ import { fileIconMap } from '@/constants/common';
|
|
4 |
import { useSetModalState } from '@/hooks/commonHooks';
|
5 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
6 |
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
|
|
7 |
import { getFileExtension } from '@/utils';
|
8 |
import omit from 'lodash/omit';
|
9 |
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
@@ -662,4 +663,28 @@ export const useRenameConversation = () => {
|
|
662 |
};
|
663 |
};
|
664 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
665 |
//#endregion
|
|
|
4 |
import { useSetModalState } from '@/hooks/commonHooks';
|
5 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
6 |
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
7 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
8 |
import { getFileExtension } from '@/utils';
|
9 |
import omit from 'lodash/omit';
|
10 |
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
663 |
};
|
664 |
};
|
665 |
|
666 |
+
export const useClickDrawer = () => {
|
667 |
+
const { visible, showModal, hideModal } = useSetModalState();
|
668 |
+
const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
|
669 |
+
const [documentId, setDocumentId] = useState<string>('');
|
670 |
+
|
671 |
+
const clickDocumentButton = useCallback(
|
672 |
+
(documentId: string, chunk: IChunk) => {
|
673 |
+
showModal();
|
674 |
+
setSelectedChunk(chunk);
|
675 |
+
setDocumentId(documentId);
|
676 |
+
},
|
677 |
+
[showModal],
|
678 |
+
);
|
679 |
+
|
680 |
+
return {
|
681 |
+
clickDocumentButton,
|
682 |
+
visible,
|
683 |
+
showModal,
|
684 |
+
hideModal,
|
685 |
+
selectedChunk,
|
686 |
+
documentId,
|
687 |
+
};
|
688 |
+
};
|
689 |
+
|
690 |
//#endregion
|
web/src/pages/knowledge/index.tsx
CHANGED
@@ -50,13 +50,7 @@ const Knowledge = () => {
|
|
50 |
</ModalManager>
|
51 |
</Space>
|
52 |
</div>
|
53 |
-
<Flex
|
54 |
-
gap="large"
|
55 |
-
wrap="wrap"
|
56 |
-
flex={1}
|
57 |
-
// justify="center"
|
58 |
-
className={styles.knowledgeCardContainer}
|
59 |
-
>
|
60 |
{list.length > 0 ? (
|
61 |
list.map((item: any) => {
|
62 |
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
|
|
|
50 |
</ModalManager>
|
51 |
</Space>
|
52 |
</div>
|
53 |
+
<Flex gap={'large'} wrap="wrap" className={styles.knowledgeCardContainer}>
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
{list.length > 0 ? (
|
55 |
list.map((item: any) => {
|
56 |
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
|
web/src/utils/documentUtils.ts
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { IChunk } from '@/interfaces/database/knowledge';
|
2 |
+
import { v4 as uuid } from 'uuid';
|
3 |
+
|
4 |
+
export const buildChunkHighlights = (selectedChunk: IChunk) => {
|
5 |
+
return Array.isArray(selectedChunk?.positions) &&
|
6 |
+
selectedChunk.positions.every((x) => Array.isArray(x))
|
7 |
+
? selectedChunk?.positions?.map((x) => {
|
8 |
+
const actualPositions = x.map((y, index) =>
|
9 |
+
index !== 0 ? y / 0.7 : y,
|
10 |
+
);
|
11 |
+
const boundingRect = {
|
12 |
+
width: 849,
|
13 |
+
height: 1200,
|
14 |
+
x1: actualPositions[1],
|
15 |
+
x2: actualPositions[2],
|
16 |
+
y1: actualPositions[3],
|
17 |
+
y2: actualPositions[4],
|
18 |
+
};
|
19 |
+
return {
|
20 |
+
id: uuid(),
|
21 |
+
comment: {
|
22 |
+
text: '',
|
23 |
+
emoji: '',
|
24 |
+
},
|
25 |
+
content: { text: selectedChunk.content_with_weight },
|
26 |
+
position: {
|
27 |
+
boundingRect: boundingRect,
|
28 |
+
rects: [boundingRect],
|
29 |
+
pageNumber: x[0],
|
30 |
+
},
|
31 |
+
};
|
32 |
+
})
|
33 |
+
: [];
|
34 |
+
};
|