balibabu commited on
Commit
f422a06
·
1 Parent(s): e4e6a45

feat: add Preview with react-pdf-highlighter (#89)

Browse files

* feat: add selected style to chunk item

* feat: hightlight pdf

* feat: add Preview with react-pdf-highlighter

web/package-lock.json CHANGED
@@ -24,6 +24,7 @@
24
  "react-infinite-scroll-component": "^6.1.0",
25
  "react-markdown": "^9.0.1",
26
  "react-pdf": "^7.7.1",
 
27
  "react-string-replace": "^1.1.1",
28
  "umi": "^4.0.90",
29
  "umi-request": "^1.4.0",
@@ -7264,6 +7265,12 @@
7264
  "node": ">= 4"
7265
  }
7266
  },
 
 
 
 
 
 
7267
  "node_modules/domutils": {
7268
  "version": "2.8.0",
7269
  "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
@@ -8562,6 +8569,11 @@
8562
  "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
8563
  "peer": true
8564
  },
 
 
 
 
 
8565
  "node_modules/fast-redact": {
8566
  "version": "3.3.0",
8567
  "resolved": "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.3.0.tgz",
@@ -11209,8 +11221,7 @@
11209
  "node_modules/lodash.debounce": {
11210
  "version": "4.0.8",
11211
  "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
11212
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
11213
- "dev": true
11214
  },
11215
  "node_modules/lodash.merge": {
11216
  "version": "4.6.2",
@@ -14417,6 +14428,18 @@
14417
  "react-dom": "*"
14418
  }
14419
  },
 
 
 
 
 
 
 
 
 
 
 
 
14420
  "node_modules/react": {
14421
  "version": "18.2.0",
14422
  "resolved": "https://registry.npmmirror.com/react/-/react-18.2.0.tgz",
@@ -14736,6 +14759,27 @@
14736
  "react": "^18.2.0"
14737
  }
14738
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14739
  "node_modules/react-error-overlay": {
14740
  "version": "6.0.9",
14741
  "resolved": "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
@@ -14872,6 +14916,37 @@
14872
  }
14873
  }
14874
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14875
  "node_modules/react-refresh": {
14876
  "version": "0.14.0",
14877
  "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz",
@@ -14880,6 +14955,25 @@
14880
  "node": ">=0.10.0"
14881
  }
14882
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14883
  "node_modules/react-router": {
14884
  "version": "6.3.0",
14885
  "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.3.0.tgz",
 
24
  "react-infinite-scroll-component": "^6.1.0",
25
  "react-markdown": "^9.0.1",
26
  "react-pdf": "^7.7.1",
27
+ "react-pdf-highlighter": "^6.1.0",
28
  "react-string-replace": "^1.1.1",
29
  "umi": "^4.0.90",
30
  "umi-request": "^1.4.0",
 
7265
  "node": ">= 4"
7266
  }
7267
  },
7268
+ "node_modules/dommatrix": {
7269
+ "version": "1.0.3",
7270
+ "resolved": "https://registry.npmmirror.com/dommatrix/-/dommatrix-1.0.3.tgz",
7271
+ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
7272
+ "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
7273
+ },
7274
  "node_modules/domutils": {
7275
  "version": "2.8.0",
7276
  "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
 
8569
  "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
8570
  "peer": true
8571
  },
8572
+ "node_modules/fast-memoize": {
8573
+ "version": "2.5.2",
8574
+ "resolved": "https://registry.npmmirror.com/fast-memoize/-/fast-memoize-2.5.2.tgz",
8575
+ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="
8576
+ },
8577
  "node_modules/fast-redact": {
8578
  "version": "3.3.0",
8579
  "resolved": "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.3.0.tgz",
 
11221
  "node_modules/lodash.debounce": {
11222
  "version": "4.0.8",
11223
  "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
11224
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
 
11225
  },
11226
  "node_modules/lodash.merge": {
11227
  "version": "4.6.2",
 
14428
  "react-dom": "*"
14429
  }
14430
  },
14431
+ "node_modules/re-resizable": {
14432
+ "version": "6.9.6",
14433
+ "resolved": "https://registry.npmmirror.com/re-resizable/-/re-resizable-6.9.6.tgz",
14434
+ "integrity": "sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ==",
14435
+ "dependencies": {
14436
+ "fast-memoize": "^2.5.1"
14437
+ },
14438
+ "peerDependencies": {
14439
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0",
14440
+ "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0"
14441
+ }
14442
+ },
14443
  "node_modules/react": {
14444
  "version": "18.2.0",
14445
  "resolved": "https://registry.npmmirror.com/react/-/react-18.2.0.tgz",
 
14759
  "react": "^18.2.0"
14760
  }
14761
  },
14762
+ "node_modules/react-draggable": {
14763
+ "version": "4.4.5",
14764
+ "resolved": "https://registry.npmmirror.com/react-draggable/-/react-draggable-4.4.5.tgz",
14765
+ "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==",
14766
+ "dependencies": {
14767
+ "clsx": "^1.1.1",
14768
+ "prop-types": "^15.8.1"
14769
+ },
14770
+ "peerDependencies": {
14771
+ "react": ">= 16.3.0",
14772
+ "react-dom": ">= 16.3.0"
14773
+ }
14774
+ },
14775
+ "node_modules/react-draggable/node_modules/clsx": {
14776
+ "version": "1.2.1",
14777
+ "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz",
14778
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
14779
+ "engines": {
14780
+ "node": ">=6"
14781
+ }
14782
+ },
14783
  "node_modules/react-error-overlay": {
14784
  "version": "6.0.9",
14785
  "resolved": "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
 
14916
  }
14917
  }
14918
  },
14919
+ "node_modules/react-pdf-highlighter": {
14920
+ "version": "6.1.0",
14921
+ "resolved": "https://registry.npmmirror.com/react-pdf-highlighter/-/react-pdf-highlighter-6.1.0.tgz",
14922
+ "integrity": "sha512-PD7l+0q1v+pZahLA/2AeWIb0n8d1amL6o+mOKnldIqtyChBHSE3gfnY5ZNMSFrhWXdlM6l4Eet+aydnYo6Skow==",
14923
+ "dependencies": {
14924
+ "lodash.debounce": "^4.0.8",
14925
+ "pdfjs-dist": "2.16.105",
14926
+ "react-rnd": "^10.1.10"
14927
+ },
14928
+ "peerDependencies": {
14929
+ "react": ">=18.0.0",
14930
+ "react-dom": ">=18.0.0"
14931
+ }
14932
+ },
14933
+ "node_modules/react-pdf-highlighter/node_modules/pdfjs-dist": {
14934
+ "version": "2.16.105",
14935
+ "resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz",
14936
+ "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==",
14937
+ "dependencies": {
14938
+ "dommatrix": "^1.0.3",
14939
+ "web-streams-polyfill": "^3.2.1"
14940
+ },
14941
+ "peerDependencies": {
14942
+ "worker-loader": "^3.0.8"
14943
+ },
14944
+ "peerDependenciesMeta": {
14945
+ "worker-loader": {
14946
+ "optional": true
14947
+ }
14948
+ }
14949
+ },
14950
  "node_modules/react-refresh": {
14951
  "version": "0.14.0",
14952
  "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz",
 
14955
  "node": ">=0.10.0"
14956
  }
14957
  },
14958
+ "node_modules/react-rnd": {
14959
+ "version": "10.4.1",
14960
+ "resolved": "https://registry.npmmirror.com/react-rnd/-/react-rnd-10.4.1.tgz",
14961
+ "integrity": "sha512-0m887AjQZr6p2ADLNnipquqsDq4XJu/uqVqI3zuoGD19tRm6uB83HmZWydtkilNp5EWsOHbLGF4IjWMdd5du8Q==",
14962
+ "dependencies": {
14963
+ "re-resizable": "6.9.6",
14964
+ "react-draggable": "4.4.5",
14965
+ "tslib": "2.3.1"
14966
+ },
14967
+ "peerDependencies": {
14968
+ "react": ">=16.3.0",
14969
+ "react-dom": ">=16.3.0"
14970
+ }
14971
+ },
14972
+ "node_modules/react-rnd/node_modules/tslib": {
14973
+ "version": "2.3.1",
14974
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.1.tgz",
14975
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
14976
+ },
14977
  "node_modules/react-router": {
14978
  "version": "6.3.0",
14979
  "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.3.0.tgz",
web/package.json CHANGED
@@ -28,6 +28,7 @@
28
  "react-infinite-scroll-component": "^6.1.0",
29
  "react-markdown": "^9.0.1",
30
  "react-pdf": "^7.7.1",
 
31
  "react-string-replace": "^1.1.1",
32
  "umi": "^4.0.90",
33
  "umi-request": "^1.4.0",
 
28
  "react-infinite-scroll-component": "^6.1.0",
29
  "react-markdown": "^9.0.1",
30
  "react-pdf": "^7.7.1",
31
+ "react-pdf-highlighter": "^6.1.0",
32
  "react-string-replace": "^1.1.1",
33
  "umi": "^4.0.90",
34
  "umi-request": "^1.4.0",
web/src/less/variable.less CHANGED
@@ -7,6 +7,7 @@
7
  @gray8: rgba(165, 163, 169, 1);
8
  @gray11: rgba(232, 232, 234, 1);
9
  @purple: rgba(127, 86, 217, 1);
 
10
 
11
  @fontSize12: 12px;
12
  @fontSize14: 14px;
 
7
  @gray8: rgba(165, 163, 169, 1);
8
  @gray11: rgba(232, 232, 234, 1);
9
  @purple: rgba(127, 86, 217, 1);
10
+ @selectedBackgroundColor: rgba(239, 248, 255, 1);
11
 
12
  @fontSize12: 12px;
13
  @fontSize14: 14px;
web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less CHANGED
@@ -14,3 +14,7 @@
14
  font-style: normal;
15
  }
16
  }
 
 
 
 
 
14
  font-style: normal;
15
  }
16
  }
17
+
18
+ .cardSelected {
19
+ background-color: @selectedBackgroundColor;
20
+ }
web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx CHANGED
@@ -10,6 +10,8 @@ interface IProps {
10
  switchChunk: (available?: number, chunkIds?: string[]) => void;
11
  editChunk: (chunkId: string) => void;
12
  handleCheckboxClick: (chunkId: string, checked: boolean) => void;
 
 
13
  }
14
 
15
  const ChunkCard = ({
@@ -18,6 +20,8 @@ const ChunkCard = ({
18
  handleCheckboxClick,
19
  editChunk,
20
  switchChunk,
 
 
21
  }: IProps) => {
22
  const available = Number(item.available_int);
23
  const [enabled, setEnabled] = useState(available === 1);
@@ -31,13 +35,17 @@ const ChunkCard = ({
31
  handleCheckboxClick(item.chunk_id, e.target.checked);
32
  };
33
 
34
- const handleContentClick = () => {
35
  editChunk(item.chunk_id);
36
  };
37
 
 
 
 
 
38
  return (
39
  <div>
40
- <Card>
41
  <Flex gap={'middle'} justify={'space-between'}>
42
  <Checkbox onChange={handleCheck} checked={checked}></Checkbox>
43
  {item.img_id && (
@@ -52,7 +60,8 @@ const ChunkCard = ({
52
  )}
53
 
54
  <section
55
- onDoubleClick={handleContentClick}
 
56
  className={styles.content}
57
  dangerouslySetInnerHTML={{ __html: item.content_with_weight }}
58
  >
 
10
  switchChunk: (available?: number, chunkIds?: string[]) => void;
11
  editChunk: (chunkId: string) => void;
12
  handleCheckboxClick: (chunkId: string, checked: boolean) => void;
13
+ selected: boolean;
14
+ clickChunkCard: (chunkId: string) => void;
15
  }
16
 
17
  const ChunkCard = ({
 
20
  handleCheckboxClick,
21
  editChunk,
22
  switchChunk,
23
+ selected,
24
+ clickChunkCard,
25
  }: IProps) => {
26
  const available = Number(item.available_int);
27
  const [enabled, setEnabled] = useState(available === 1);
 
35
  handleCheckboxClick(item.chunk_id, e.target.checked);
36
  };
37
 
38
+ const handleContentDoubleClick = () => {
39
  editChunk(item.chunk_id);
40
  };
41
 
42
+ const handleContentClick = () => {
43
+ clickChunkCard(item.chunk_id);
44
+ };
45
+
46
  return (
47
  <div>
48
+ <Card className={selected ? styles.cardSelected : ''}>
49
  <Flex gap={'middle'} justify={'space-between'}>
50
  <Checkbox onChange={handleCheck} checked={checked}></Checkbox>
51
  {item.img_id && (
 
60
  )}
61
 
62
  <section
63
+ onDoubleClick={handleContentDoubleClick}
64
+ onClick={handleContentClick}
65
  className={styles.content}
66
  dangerouslySetInnerHTML={{ __html: item.content_with_weight }}
67
  >
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hightlights.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const testHighlights = [
2
+ {
3
+ content: {
4
+ text: '实验证明,由氧氯化锆锂和高镍三元正极组成的全固态锂电池展示了极为优异的性能:在12 分钟快速充电的条件下,该电池仍然成功地在室温稳定循环2000 圈以上。',
5
+ },
6
+ position: {
7
+ boundingRect: {
8
+ x1: 219.7,
9
+ y1: 204.3,
10
+ x2: 547.0,
11
+ y2: 264.0,
12
+ width: 849,
13
+ height: 1200,
14
+ },
15
+ rects: [
16
+ {
17
+ x1: 219.7,
18
+ y1: 204.3,
19
+ x2: 547.0,
20
+ y2: 264.0,
21
+ width: 849,
22
+ height: 1200,
23
+ },
24
+ ],
25
+ pageNumber: 9,
26
+ },
27
+ comment: {
28
+ text: 'Flow or TypeScript?',
29
+ emoji: '🔥',
30
+ },
31
+ id: '8245652131754351',
32
+ },
33
+ ];
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts CHANGED
@@ -1,5 +1,8 @@
 
 
1
  import { useSize } from 'ahooks';
2
- import { useCallback, useEffect, useState } from 'react';
 
3
 
4
  export const useDocumentResizeObserver = () => {
5
  const [containerWidth, setContainerWidth] = useState<number>();
@@ -18,3 +21,35 @@ export const useDocumentResizeObserver = () => {
18
 
19
  return { containerWidth, setContainerRef };
20
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useGetKnowledgeSearchParams } from '@/hooks/knowledgeHook';
2
+ import { api_host } from '@/utils/api';
3
  import { useSize } from 'ahooks';
4
+ import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
5
+ import { useCallback, useEffect, useMemo, useState } from 'react';
6
 
7
  export const useDocumentResizeObserver = () => {
8
  const [containerWidth, setContainerWidth] = useState<number>();
 
21
 
22
  return { containerWidth, setContainerRef };
23
  };
24
+
25
+ function highlightPattern(text: string, pattern: string, pageNumber: number) {
26
+ if (pageNumber === 2) {
27
+ return `<mark>${text}</mark>`;
28
+ }
29
+ if (text.trim() !== '' && pattern.match(text)) {
30
+ // return pattern.replace(text, (value) => `<mark>${value}</mark>`);
31
+ return `<mark>${text}</mark>`;
32
+ }
33
+ return text.replace(pattern, (value) => `<mark>${value}</mark>`);
34
+ }
35
+
36
+ export const useHighlightText = (searchText: string = '') => {
37
+ const textRenderer: CustomTextRenderer = useCallback(
38
+ (textItem) => {
39
+ return highlightPattern(textItem.str, searchText, textItem.pageNumber);
40
+ },
41
+ [searchText],
42
+ );
43
+
44
+ return textRenderer;
45
+ };
46
+
47
+ export const useGetDocumentUrl = () => {
48
+ const { documentId } = useGetKnowledgeSearchParams();
49
+
50
+ const url = useMemo(() => {
51
+ return `${api_host}/document/get/${documentId}`;
52
+ }, [documentId]);
53
+
54
+ return url;
55
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less CHANGED
@@ -1,6 +1,11 @@
1
  .documentContainer {
2
  width: 100%;
3
  height: calc(100vh - 284px);
4
- overflow-y: auto;
5
- overflow-x: hidden;
 
 
 
 
 
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;
9
+ // left: 0;
10
+ }
11
  }
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.tsx CHANGED
@@ -5,69 +5,53 @@ import { Document, Page, pdfjs } from 'react-pdf';
5
 
6
  import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
7
  import 'react-pdf/dist/esm/Page/TextLayer.css';
8
- import { useDocumentResizeObserver } from './hooks';
9
 
 
 
10
  import styles from './index.less';
11
 
12
- // type PDFFile = string | File | null;
13
-
14
  pdfjs.GlobalWorkerOptions.workerSrc = new URL(
15
  'pdfjs-dist/build/pdf.worker.min.js',
16
  import.meta.url,
17
  ).toString();
18
 
19
- // const options = {
20
- // cMapUrl: '/cmaps/',
21
- // standardFontDataUrl: '/standard_fonts/',
22
- // };
23
 
24
- const DocumentPreview = () => {
25
  const [numPages, setNumPages] = useState<number>();
26
  const { documentId } = useGetKnowledgeSearchParams();
27
- // const [file, setFile] = useState<PDFFile>(null);
28
  const { containerWidth, setContainerRef } = useDocumentResizeObserver();
 
 
 
29
 
30
  function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
31
  setNumPages(numPages);
32
  }
33
 
34
- // const handleChange = (e: any) => {
35
- // console.info(e.files);
36
- // setFile(e.target.files[0] || null);
37
- // };
38
-
39
  const url = useMemo(() => {
40
  return `${api_host}/document/get/${documentId}`;
41
  }, [documentId]);
42
 
43
- // const fetch_document_file = useCallback(async () => {
44
- // const ret: Blob = await getDocumentFile(documentId);
45
- // console.info(ret);
46
- // const f = new File([ret], 'xx.pdf', { type: ret.type });
47
- // setFile(f);
48
- // }, [documentId]);
49
-
50
- // useEffect(() => {
51
- // // dispatch({ type: 'kFModel/fetch_document_file', payload: documentId });
52
- // fetch_document_file();
53
- // }, [fetch_document_file]);
54
-
55
  return (
56
  <div ref={setContainerRef} className={styles.documentContainer}>
57
  <Document
58
  file={url}
59
  onLoadSuccess={onDocumentLoadSuccess}
60
- // options={options}
61
  >
62
  {Array.from(new Array(numPages), (el, index) => (
63
  <Page
64
  key={`page_${index + 1}`}
65
  pageNumber={index + 1}
66
  width={containerWidth}
 
67
  />
68
  ))}
69
  </Document>
70
- {/* <input type="file" onChange={handleChange} /> */}
71
  </div>
72
  );
73
  };
 
5
 
6
  import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
7
  import 'react-pdf/dist/esm/Page/TextLayer.css';
8
+ import { useDocumentResizeObserver, useHighlightText } from './hooks';
9
 
10
+ import { Spin } from 'antd';
11
+ import { useGetSelectedChunk } from '../../hooks';
12
  import styles from './index.less';
13
 
 
 
14
  pdfjs.GlobalWorkerOptions.workerSrc = new URL(
15
  'pdfjs-dist/build/pdf.worker.min.js',
16
  import.meta.url,
17
  ).toString();
18
 
19
+ interface IProps {
20
+ selectedChunkId: string;
21
+ }
 
22
 
23
+ const DocumentPreview = ({ selectedChunkId }: IProps) => {
24
  const [numPages, setNumPages] = useState<number>();
25
  const { documentId } = useGetKnowledgeSearchParams();
 
26
  const { containerWidth, setContainerRef } = useDocumentResizeObserver();
27
+ const selectedChunk = useGetSelectedChunk(selectedChunkId);
28
+ console.info(selectedChunk?.content_with_weight);
29
+ const textRenderer = useHighlightText(selectedChunk?.content_with_weight);
30
 
31
  function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
32
  setNumPages(numPages);
33
  }
34
 
 
 
 
 
 
35
  const url = useMemo(() => {
36
  return `${api_host}/document/get/${documentId}`;
37
  }, [documentId]);
38
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  return (
40
  <div ref={setContainerRef} className={styles.documentContainer}>
41
  <Document
42
  file={url}
43
  onLoadSuccess={onDocumentLoadSuccess}
44
+ loading={<Spin></Spin>}
45
  >
46
  {Array.from(new Array(numPages), (el, index) => (
47
  <Page
48
  key={`page_${index + 1}`}
49
  pageNumber={index + 1}
50
  width={containerWidth}
51
+ customTextRenderer={textRenderer}
52
  />
53
  ))}
54
  </Document>
 
55
  </div>
56
  );
57
  };
web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Spin } from 'antd';
2
+ import { useRef, useState } from 'react';
3
+ import type { NewHighlight } from 'react-pdf-highlighter';
4
+ import {
5
+ AreaHighlight,
6
+ Highlight,
7
+ PdfHighlighter,
8
+ PdfLoader,
9
+ Popup,
10
+ Tip,
11
+ } from 'react-pdf-highlighter';
12
+ import { useGetSelectedChunk } from '../../hooks';
13
+ import { testHighlights } from './hightlights';
14
+ import { useGetDocumentUrl } from './hooks';
15
+
16
+ import styles from './index.less';
17
+
18
+ interface IProps {
19
+ selectedChunkId: string;
20
+ }
21
+
22
+ const getNextId = () => String(Math.random()).slice(2);
23
+
24
+ const HighlightPopup = ({
25
+ comment,
26
+ }: {
27
+ comment: { text: string; emoji: string };
28
+ }) =>
29
+ comment.text ? (
30
+ <div className="Highlight__popup">
31
+ {comment.emoji} {comment.text}
32
+ </div>
33
+ ) : null;
34
+
35
+ const Preview = ({ selectedChunkId }: IProps) => {
36
+ const url = useGetDocumentUrl();
37
+ const selectedChunk = useGetSelectedChunk(selectedChunkId);
38
+
39
+ const [state, setState] = useState<any>(testHighlights);
40
+ const ref = useRef((highlight: any) => {});
41
+
42
+ const parseIdFromHash = () =>
43
+ document.location.hash.slice('#highlight-'.length);
44
+
45
+ const resetHash = () => {
46
+ document.location.hash = '';
47
+ };
48
+
49
+ const getHighlightById = (id: string) => {
50
+ const highlights = state;
51
+
52
+ return highlights.find((highlight: any) => highlight.id === id);
53
+ };
54
+
55
+ // let scrollViewerTo = (highlight: any) => {};
56
+
57
+ let scrollToHighlightFromHash = () => {
58
+ const highlight = getHighlightById(parseIdFromHash());
59
+
60
+ if (highlight) {
61
+ ref.current(highlight);
62
+ }
63
+ };
64
+
65
+ const addHighlight = (highlight: NewHighlight) => {
66
+ const highlights = state;
67
+
68
+ console.log('Saving highlight', highlight);
69
+
70
+ setState([{ ...highlight, id: getNextId() }, ...highlights]);
71
+ };
72
+
73
+ const updateHighlight = (
74
+ highlightId: string,
75
+ position: Object,
76
+ content: Object,
77
+ ) => {
78
+ console.log('Updating highlight', highlightId, position, content);
79
+
80
+ setState(
81
+ state.map((h: any) => {
82
+ const {
83
+ id,
84
+ position: originalPosition,
85
+ content: originalContent,
86
+ ...rest
87
+ } = h;
88
+ return id === highlightId
89
+ ? {
90
+ id,
91
+ position: { ...originalPosition, ...position },
92
+ content: { ...originalContent, ...content },
93
+ ...rest,
94
+ }
95
+ : h;
96
+ }),
97
+ );
98
+ };
99
+
100
+ // useEffect(() => {
101
+ // ref.current(testHighlights[0]);
102
+ // }, [selectedChunk]);
103
+
104
+ return (
105
+ <div className={styles.documentContainer}>
106
+ <PdfLoader url={url} beforeLoad={<Spin />}>
107
+ {(pdfDocument) => (
108
+ <PdfHighlighter
109
+ pdfDocument={pdfDocument}
110
+ enableAreaSelection={(event) => event.altKey}
111
+ onScrollChange={resetHash}
112
+ // pdfScaleValue="page-width"
113
+
114
+ scrollRef={(scrollTo) => {
115
+ // scrollViewerTo = scrollTo;
116
+ ref.current = scrollTo;
117
+
118
+ scrollToHighlightFromHash();
119
+ }}
120
+ onSelectionFinished={(
121
+ position,
122
+ content,
123
+ hideTipAndSelection,
124
+ transformSelection,
125
+ ) => (
126
+ <Tip
127
+ onOpen={transformSelection}
128
+ onConfirm={(comment) => {
129
+ addHighlight({ content, position, comment });
130
+
131
+ hideTipAndSelection();
132
+ }}
133
+ />
134
+ )}
135
+ highlightTransform={(
136
+ highlight,
137
+ index,
138
+ setTip,
139
+ hideTip,
140
+ viewportToScaled,
141
+ screenshot,
142
+ isScrolledTo,
143
+ ) => {
144
+ const isTextHighlight = !Boolean(
145
+ highlight.content && highlight.content.image,
146
+ );
147
+
148
+ const component = isTextHighlight ? (
149
+ <Highlight
150
+ isScrolledTo={isScrolledTo}
151
+ position={highlight.position}
152
+ comment={highlight.comment}
153
+ />
154
+ ) : (
155
+ <AreaHighlight
156
+ isScrolledTo={isScrolledTo}
157
+ highlight={highlight}
158
+ onChange={(boundingRect) => {
159
+ updateHighlight(
160
+ highlight.id,
161
+ { boundingRect: viewportToScaled(boundingRect) },
162
+ { image: screenshot(boundingRect) },
163
+ );
164
+ }}
165
+ />
166
+ );
167
+
168
+ return (
169
+ <Popup
170
+ popupContent={<HighlightPopup {...highlight} />}
171
+ onMouseOver={(popupContent) =>
172
+ setTip(highlight, (highlight: any) => popupContent)
173
+ }
174
+ onMouseOut={hideTip}
175
+ key={index}
176
+ >
177
+ {component}
178
+ </Popup>
179
+ );
180
+ }}
181
+ highlights={state}
182
+ />
183
+ )}
184
+ </PdfLoader>
185
+ </div>
186
+ );
187
+ };
188
+
189
+ export default Preview;
web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { IKnowledgeFile } from '@/interfaces/database/knowledge';
 
2
  import { useSelector } from 'umi';
3
 
4
  export const useSelectDocumentInfo = () => {
@@ -7,3 +8,25 @@ export const useSelectDocumentInfo = () => {
7
  );
8
  return documentInfo;
9
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
2
+ import { useCallback, useState } from 'react';
3
  import { useSelector } from 'umi';
4
 
5
  export const useSelectDocumentInfo = () => {
 
8
  );
9
  return documentInfo;
10
  };
11
+
12
+ export const useSelectChunkList = () => {
13
+ const chunkList: IChunk[] = useSelector(
14
+ (state: any) => state.chunkModel.data,
15
+ );
16
+ return chunkList;
17
+ };
18
+
19
+ export const useHandleChunkCardClick = () => {
20
+ const [selectedChunkId, setSelectedChunkId] = useState<string>('');
21
+
22
+ const handleChunkCardClick = useCallback((chunkId: string) => {
23
+ setSelectedChunkId(chunkId);
24
+ }, []);
25
+
26
+ return { handleChunkCardClick, selectedChunkId };
27
+ };
28
+
29
+ export const useGetSelectedChunk = (selectedChunkId: string) => {
30
+ const chunkList: IChunk[] = useSelectChunkList();
31
+ return chunkList.find((x) => x.chunk_id === selectedChunkId);
32
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx CHANGED
@@ -8,8 +8,9 @@ import CreatingModal from './components/chunk-creating-modal';
8
  import { useDeleteChunkByIds } from '@/hooks/knowledgeHook';
9
  import ChunkCard from './components/chunk-card';
10
  import ChunkToolBar from './components/chunk-toolbar';
11
- import DocumentPreview from './components/document-preview';
12
- import { useSelectDocumentInfo } from './hooks';
 
13
  import styles from './index.less';
14
  import { ChunkModelState } from './model';
15
 
@@ -36,6 +37,7 @@ const Chunk = () => {
36
  const [chunkId, setChunkId] = useState<string | undefined>();
37
  const { removeChunk } = useDeleteChunkByIds();
38
  const documentInfo = useSelectDocumentInfo();
 
39
 
40
  const getChunkList = useCallback(() => {
41
  const payload: PayloadType = {
@@ -180,6 +182,8 @@ const Chunk = () => {
180
  )}
181
  handleCheckboxClick={handleSingleCheckboxClick}
182
  switchChunk={switchChunk}
 
 
183
  ></ChunkCard>
184
  ))}
185
  </Space>
@@ -202,7 +206,9 @@ const Chunk = () => {
202
 
203
  {documentInfo.type === 'pdf' && (
204
  <section className={styles.documentPreview}>
205
- <DocumentPreview></DocumentPreview>
 
 
206
  </section>
207
  )}
208
  </Flex>
 
8
  import { useDeleteChunkByIds } from '@/hooks/knowledgeHook';
9
  import ChunkCard from './components/chunk-card';
10
  import ChunkToolBar from './components/chunk-toolbar';
11
+ // import DocumentPreview from './components/document-preview';
12
+ import DocumentPreview from './components/document-preview/preview';
13
+ import { useHandleChunkCardClick, useSelectDocumentInfo } from './hooks';
14
  import styles from './index.less';
15
  import { ChunkModelState } from './model';
16
 
 
37
  const [chunkId, setChunkId] = useState<string | undefined>();
38
  const { removeChunk } = useDeleteChunkByIds();
39
  const documentInfo = useSelectDocumentInfo();
40
+ const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
41
 
42
  const getChunkList = useCallback(() => {
43
  const payload: PayloadType = {
 
182
  )}
183
  handleCheckboxClick={handleSingleCheckboxClick}
184
  switchChunk={switchChunk}
185
+ clickChunkCard={handleChunkCardClick}
186
+ selected={item.chunk_id === selectedChunkId}
187
  ></ChunkCard>
188
  ))}
189
  </Space>
 
206
 
207
  {documentInfo.type === 'pdf' && (
208
  <section className={styles.documentPreview}>
209
+ <DocumentPreview
210
+ selectedChunkId={selectedChunkId}
211
+ ></DocumentPreview>
212
  </section>
213
  )}
214
  </Flex>
web/src/pages/add-knowledge/components/knowledge-chunk/model.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { BaseState } from '@/interfaces/common';
2
- import { IKnowledgeFile } from '@/interfaces/database/knowledge';
3
  import kbService from '@/services/kbService';
4
  import { message } from 'antd';
5
  import { pick } from 'lodash';
@@ -7,7 +7,7 @@ import { pick } from 'lodash';
7
  import { DvaModel } from 'umi';
8
 
9
  export interface ChunkModelState extends BaseState {
10
- data: any[];
11
  total: number;
12
  isShowCreateModal: boolean;
13
  chunk_id: string;
 
1
  import { BaseState } from '@/interfaces/common';
2
+ import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
3
  import kbService from '@/services/kbService';
4
  import { message } from 'antd';
5
  import { pick } from 'lodash';
 
7
  import { DvaModel } from 'umi';
8
 
9
  export interface ChunkModelState extends BaseState {
10
+ data: IChunk[];
11
  total: number;
12
  isShowCreateModal: boolean;
13
  chunk_id: string;