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 +96 -2
- web/package.json +1 -0
- web/src/less/variable.less +1 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less +4 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx +12 -3
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hightlights.ts +33 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/hooks.ts +36 -1
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.less +7 -2
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/index.tsx +12 -28
- web/src/pages/add-knowledge/components/knowledge-chunk/components/document-preview/preview.tsx +189 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts +24 -1
- web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +9 -3
- web/src/pages/add-knowledge/components/knowledge-chunk/model.ts +2 -2
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
|
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={
|
|
|
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 {
|
|
|
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 |
-
|
20 |
-
|
21 |
-
|
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 |
-
|
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
|
|
|
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
|
|
|
|
|
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:
|
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;
|