balibabu
commited on
Commit
·
b0b89da
1
Parent(s):
8ee4f9f
feat: add corresponding icons to files (#164)
Browse files- web/src/components/new-document-link.tsx +7 -1
- web/src/hooks/knowledgeHook.ts +23 -0
- web/src/pages/add-knowledge/components/knowledge-chunk/index.less +1 -0
- web/src/pages/add-knowledge/components/knowledge-file/index.less +0 -1
- web/src/pages/add-knowledge/components/knowledge-file/index.tsx +16 -5
- web/src/pages/add-knowledge/components/knowledge-testing/index.tsx +5 -12
- web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx +5 -2
- web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less +6 -4
- web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx +1 -1
- web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx +3 -2
- web/src/pages/chat/chat-container/index.less +3 -0
- web/src/pages/chat/chat-container/index.tsx +63 -27
- web/src/utils/documentUtils.ts +4 -0
web/src/components/new-document-link.tsx
CHANGED
@@ -3,12 +3,18 @@ import React from 'react';
|
|
3 |
|
4 |
interface IProps extends React.PropsWithChildren {
|
5 |
documentId: string;
|
|
|
6 |
}
|
7 |
|
8 |
-
const NewDocumentLink = ({
|
|
|
|
|
|
|
|
|
9 |
return (
|
10 |
<a
|
11 |
target="_blank"
|
|
|
12 |
href={`${api_host}/document/get/${documentId}`}
|
13 |
rel="noreferrer"
|
14 |
>
|
|
|
3 |
|
4 |
interface IProps extends React.PropsWithChildren {
|
5 |
documentId: string;
|
6 |
+
preventDefault?: boolean;
|
7 |
}
|
8 |
|
9 |
+
const NewDocumentLink = ({
|
10 |
+
children,
|
11 |
+
documentId,
|
12 |
+
preventDefault = false,
|
13 |
+
}: IProps) => {
|
14 |
return (
|
15 |
<a
|
16 |
target="_blank"
|
17 |
+
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
|
18 |
href={`${api_host}/document/get/${documentId}`}
|
19 |
rel="noreferrer"
|
20 |
>
|
web/src/hooks/knowledgeHook.ts
CHANGED
@@ -206,3 +206,26 @@ export const useSelectKnowledgeDetails = () => {
|
|
206 |
return knowledgeDetails;
|
207 |
};
|
208 |
//#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
return knowledgeDetails;
|
207 |
};
|
208 |
//#endregion
|
209 |
+
|
210 |
+
//#region Retrieval testing
|
211 |
+
|
212 |
+
export const useTestChunkRetrieval = () => {
|
213 |
+
const dispatch = useDispatch();
|
214 |
+
const knowledgeBaseId = useKnowledgeBaseId();
|
215 |
+
|
216 |
+
const testChunk = useCallback(
|
217 |
+
(values: any) => {
|
218 |
+
dispatch({
|
219 |
+
type: 'testingModel/testDocumentChunk',
|
220 |
+
payload: {
|
221 |
+
...values,
|
222 |
+
kb_id: knowledgeBaseId,
|
223 |
+
},
|
224 |
+
});
|
225 |
+
},
|
226 |
+
[dispatch, knowledgeBaseId],
|
227 |
+
);
|
228 |
+
|
229 |
+
return testChunk;
|
230 |
+
};
|
231 |
+
//#endregion
|
web/src/pages/add-knowledge/components/knowledge-chunk/index.less
CHANGED
@@ -37,6 +37,7 @@
|
|
37 |
}
|
38 |
|
39 |
.chunkContainer {
|
|
|
40 |
height: calc(100vh - 332px);
|
41 |
}
|
42 |
|
|
|
37 |
}
|
38 |
|
39 |
.chunkContainer {
|
40 |
+
display: flex;
|
41 |
height: calc(100vh - 332px);
|
42 |
}
|
43 |
|
web/src/pages/add-knowledge/components/knowledge-file/index.less
CHANGED
@@ -22,7 +22,6 @@
|
|
22 |
.img {
|
23 |
height: 24px;
|
24 |
width: 24px;
|
25 |
-
margin-right: 10px;
|
26 |
display: inline-block;
|
27 |
vertical-align: middle;
|
28 |
}
|
|
|
22 |
.img {
|
23 |
height: 24px;
|
24 |
width: 24px;
|
|
|
25 |
display: inline-block;
|
26 |
vertical-align: middle;
|
27 |
}
|
web/src/pages/add-knowledge/components/knowledge-file/index.tsx
CHANGED
@@ -1,9 +1,12 @@
|
|
|
|
|
|
1 |
import {
|
2 |
useSelectDocumentList,
|
3 |
useSetDocumentStatus,
|
4 |
} from '@/hooks/documentHooks';
|
5 |
import { useSelectParserList } from '@/hooks/userSettingHook';
|
6 |
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
|
|
7 |
import {
|
8 |
FileOutlined,
|
9 |
FileTextOutlined,
|
@@ -15,6 +18,7 @@ import {
|
|
15 |
Button,
|
16 |
Divider,
|
17 |
Dropdown,
|
|
|
18 |
Input,
|
19 |
Space,
|
20 |
Switch,
|
@@ -38,8 +42,6 @@ import ParsingActionCell from './parsing-action-cell';
|
|
38 |
import ParsingStatusCell from './parsing-status-cell';
|
39 |
import RenameModal from './rename-modal';
|
40 |
|
41 |
-
import ChunkMethodModal from '@/components/chunk-method-modal';
|
42 |
-
import { getExtension } from '@/utils/documentUtils';
|
43 |
import styles from './index.less';
|
44 |
|
45 |
const KnowledgeFile = () => {
|
@@ -114,10 +116,19 @@ const KnowledgeFile = () => {
|
|
114 |
dataIndex: 'name',
|
115 |
key: 'name',
|
116 |
fixed: 'left',
|
117 |
-
render: (text: any, { id, thumbnail }) => (
|
118 |
<div className={styles.tochunks} onClick={() => toChunk(id)}>
|
119 |
-
<
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
</div>
|
122 |
),
|
123 |
},
|
|
|
1 |
+
import ChunkMethodModal from '@/components/chunk-method-modal';
|
2 |
+
import SvgIcon from '@/components/svg-icon';
|
3 |
import {
|
4 |
useSelectDocumentList,
|
5 |
useSetDocumentStatus,
|
6 |
} from '@/hooks/documentHooks';
|
7 |
import { useSelectParserList } from '@/hooks/userSettingHook';
|
8 |
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
9 |
+
import { getExtension } from '@/utils/documentUtils';
|
10 |
import {
|
11 |
FileOutlined,
|
12 |
FileTextOutlined,
|
|
|
18 |
Button,
|
19 |
Divider,
|
20 |
Dropdown,
|
21 |
+
Flex,
|
22 |
Input,
|
23 |
Space,
|
24 |
Switch,
|
|
|
42 |
import ParsingStatusCell from './parsing-status-cell';
|
43 |
import RenameModal from './rename-modal';
|
44 |
|
|
|
|
|
45 |
import styles from './index.less';
|
46 |
|
47 |
const KnowledgeFile = () => {
|
|
|
116 |
dataIndex: 'name',
|
117 |
key: 'name',
|
118 |
fixed: 'left',
|
119 |
+
render: (text: any, { id, thumbnail, name }) => (
|
120 |
<div className={styles.tochunks} onClick={() => toChunk(id)}>
|
121 |
+
<Flex gap={10} align="center">
|
122 |
+
{thumbnail ? (
|
123 |
+
<img className={styles.img} src={thumbnail} alt="" />
|
124 |
+
) : (
|
125 |
+
<SvgIcon
|
126 |
+
name={`file-icon/${getExtension(name)}`}
|
127 |
+
width={24}
|
128 |
+
></SvgIcon>
|
129 |
+
)}
|
130 |
+
{text}
|
131 |
+
</Flex>
|
132 |
</div>
|
133 |
),
|
134 |
},
|
web/src/pages/add-knowledge/components/knowledge-testing/index.tsx
CHANGED
@@ -1,28 +1,21 @@
|
|
|
|
1 |
import { Flex, Form } from 'antd';
|
|
|
|
|
2 |
import TestingControl from './testing-control';
|
3 |
import TestingResult from './testing-result';
|
4 |
|
5 |
-
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
|
6 |
-
import { useEffect } from 'react';
|
7 |
-
import { useDispatch } from 'umi';
|
8 |
import styles from './index.less';
|
9 |
|
10 |
const KnowledgeTesting = () => {
|
11 |
const [form] = Form.useForm();
|
|
|
12 |
|
13 |
const dispatch = useDispatch();
|
14 |
-
const knowledgeBaseId = useKnowledgeBaseId();
|
15 |
|
16 |
const handleTesting = async () => {
|
17 |
const values = await form.validateFields();
|
18 |
-
|
19 |
-
dispatch({
|
20 |
-
type: 'testingModel/testDocumentChunk',
|
21 |
-
payload: {
|
22 |
-
...values,
|
23 |
-
kb_id: knowledgeBaseId,
|
24 |
-
},
|
25 |
-
});
|
26 |
};
|
27 |
|
28 |
useEffect(() => {
|
|
|
1 |
+
import { useTestChunkRetrieval } from '@/hooks/knowledgeHook';
|
2 |
import { Flex, Form } from 'antd';
|
3 |
+
import { useEffect } from 'react';
|
4 |
+
import { useDispatch } from 'umi';
|
5 |
import TestingControl from './testing-control';
|
6 |
import TestingResult from './testing-result';
|
7 |
|
|
|
|
|
|
|
8 |
import styles from './index.less';
|
9 |
|
10 |
const KnowledgeTesting = () => {
|
11 |
const [form] = Form.useForm();
|
12 |
+
const testChunk = useTestChunkRetrieval();
|
13 |
|
14 |
const dispatch = useDispatch();
|
|
|
15 |
|
16 |
const handleTesting = async () => {
|
17 |
const values = await form.validateFields();
|
18 |
+
testChunk(values);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
};
|
20 |
|
21 |
useEffect(() => {
|
web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx
CHANGED
@@ -2,10 +2,9 @@ import SimilaritySlider from '@/components/similarity-slider';
|
|
2 |
import { Button, Card, Divider, Flex, Form, Input, Slider, Tag } from 'antd';
|
3 |
import { FormInstance } from 'antd/lib';
|
4 |
|
|
|
5 |
import styles from './index.less';
|
6 |
|
7 |
-
const list = [1, 2, 3];
|
8 |
-
|
9 |
type FieldType = {
|
10 |
similarity_threshold?: number;
|
11 |
vector_similarity_weight?: number;
|
@@ -20,6 +19,9 @@ interface IProps {
|
|
20 |
|
21 |
const TestingControl = ({ form, handleTesting }: IProps) => {
|
22 |
const question = Form.useWatch('question', { form, preserve: true });
|
|
|
|
|
|
|
23 |
|
24 |
const buttonDisabled =
|
25 |
!question || (typeof question === 'string' && question.trim() === '');
|
@@ -65,6 +67,7 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
|
|
65 |
size="small"
|
66 |
onClick={handleTesting}
|
67 |
disabled={buttonDisabled}
|
|
|
68 |
>
|
69 |
Testing
|
70 |
</Button>
|
|
|
2 |
import { Button, Card, Divider, Flex, Form, Input, Slider, Tag } from 'antd';
|
3 |
import { FormInstance } from 'antd/lib';
|
4 |
|
5 |
+
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
6 |
import styles from './index.less';
|
7 |
|
|
|
|
|
8 |
type FieldType = {
|
9 |
similarity_threshold?: number;
|
10 |
vector_similarity_weight?: number;
|
|
|
19 |
|
20 |
const TestingControl = ({ form, handleTesting }: IProps) => {
|
21 |
const question = Form.useWatch('question', { form, preserve: true });
|
22 |
+
const loading = useOneNamespaceEffectsLoading('testingModel', [
|
23 |
+
'testDocumentChunk',
|
24 |
+
]);
|
25 |
|
26 |
const buttonDisabled =
|
27 |
!question || (typeof question === 'string' && question.trim() === '');
|
|
|
67 |
size="small"
|
68 |
onClick={handleTesting}
|
69 |
disabled={buttonDisabled}
|
70 |
+
loading={loading}
|
71 |
>
|
72 |
Testing
|
73 |
</Button>
|
web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less
CHANGED
@@ -35,9 +35,11 @@
|
|
35 |
}
|
36 |
.image {
|
37 |
width: 100px;
|
|
|
38 |
}
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
43 |
}
|
|
|
35 |
}
|
36 |
.image {
|
37 |
width: 100px;
|
38 |
+
object-fit: contain;
|
39 |
}
|
40 |
+
}
|
41 |
+
.imagePreview {
|
42 |
+
display: block;
|
43 |
+
max-width: 45vw;
|
44 |
+
max-height: 40vh;
|
45 |
}
|
web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx
CHANGED
@@ -104,7 +104,7 @@ const TestingResult = ({ handleTesting }: IProps) => {
|
|
104 |
<Flex gap={'middle'}>
|
105 |
{x.img_id && (
|
106 |
<Popover
|
107 |
-
placement="
|
108 |
content={
|
109 |
<Image
|
110 |
id={x.img_id}
|
|
|
104 |
<Flex gap={'middle'}>
|
105 |
{x.img_id && (
|
106 |
<Popover
|
107 |
+
placement="left"
|
108 |
content={
|
109 |
<Image
|
110 |
id={x.img_id}
|
web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
|
2 |
import NewDocumentLink from '@/components/new-document-link';
|
3 |
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
|
|
4 |
import { Table, TableProps } from 'antd';
|
5 |
import { useDispatch, useSelector } from 'umi';
|
6 |
|
@@ -33,8 +34,8 @@ const SelectFiles = ({ handleTesting }: IProps) => {
|
|
33 |
title: 'View',
|
34 |
key: 'view',
|
35 |
width: 50,
|
36 |
-
render: (_, { doc_id }) => (
|
37 |
-
<NewDocumentLink documentId={doc_id}>
|
38 |
<NavigationPointerIcon />
|
39 |
</NewDocumentLink>
|
40 |
),
|
|
|
1 |
import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
|
2 |
import NewDocumentLink from '@/components/new-document-link';
|
3 |
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
4 |
+
import { isPdf } from '@/utils/documentUtils';
|
5 |
import { Table, TableProps } from 'antd';
|
6 |
import { useDispatch, useSelector } from 'umi';
|
7 |
|
|
|
34 |
title: 'View',
|
35 |
key: 'view',
|
36 |
width: 50,
|
37 |
+
render: (_, { doc_id, doc_name }) => (
|
38 |
+
<NewDocumentLink documentId={doc_id} preventDefault={!isPdf(doc_name)}>
|
39 |
<NavigationPointerIcon />
|
40 |
</NewDocumentLink>
|
41 |
),
|
web/src/pages/chat/chat-container/index.less
CHANGED
@@ -64,3 +64,6 @@
|
|
64 |
max-height: 45vh;
|
65 |
overflow-y: auto;
|
66 |
}
|
|
|
|
|
|
|
|
64 |
max-height: 45vh;
|
65 |
overflow-y: auto;
|
66 |
}
|
67 |
+
.documentLink {
|
68 |
+
padding: 0;
|
69 |
+
}
|
web/src/pages/chat/chat-container/index.tsx
CHANGED
@@ -36,6 +36,8 @@ import {
|
|
36 |
useSendMessage,
|
37 |
} from '../hooks';
|
38 |
|
|
|
|
|
39 |
import styles from './index.less';
|
40 |
|
41 |
const reg = /(#{2}\d+\${2})/g;
|
@@ -74,7 +76,10 @@ const MessageItem = ({
|
|
74 |
const isAssistant = item.role === MessageType.Assistant;
|
75 |
|
76 |
const handleDocumentButtonClick = useCallback(
|
77 |
-
(documentId: string, chunk: IChunk) => () => {
|
|
|
|
|
|
|
78 |
clickDocumentButton(documentId, chunk);
|
79 |
},
|
80 |
[clickDocumentButton],
|
@@ -88,26 +93,31 @@ const MessageItem = ({
|
|
88 |
(x) => x?.doc_id === chunkItem?.doc_id,
|
89 |
);
|
90 |
const documentId = document?.doc_id;
|
|
|
|
|
|
|
91 |
return (
|
92 |
<Flex
|
93 |
key={chunkItem?.chunk_id}
|
94 |
gap={10}
|
95 |
className={styles.referencePopoverWrapper}
|
96 |
>
|
97 |
-
|
98 |
-
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
<Image
|
101 |
-
id={
|
102 |
-
className={styles.
|
103 |
></Image>
|
104 |
-
|
105 |
-
|
106 |
-
<Image
|
107 |
-
id={chunkItem?.img_id}
|
108 |
-
className={styles.referenceChunkImage}
|
109 |
-
></Image>
|
110 |
-
</Popover>
|
111 |
<Space direction={'vertical'}>
|
112 |
<div
|
113 |
dangerouslySetInnerHTML={{
|
@@ -116,11 +126,23 @@ const MessageItem = ({
|
|
116 |
className={styles.chunkContentText}
|
117 |
></div>
|
118 |
{documentId && (
|
119 |
-
<Flex gap={'
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
<Button
|
122 |
type="link"
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
124 |
>
|
125 |
{document?.doc_name}
|
126 |
</Button>
|
@@ -224,17 +246,31 @@ const MessageItem = ({
|
|
224 |
<List
|
225 |
bordered
|
226 |
dataSource={referenceDocumentList}
|
227 |
-
renderItem={(item) =>
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
<
|
233 |
-
{
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
/>
|
239 |
)}
|
240 |
</Flex>
|
|
|
36 |
useSendMessage,
|
37 |
} from '../hooks';
|
38 |
|
39 |
+
import SvgIcon from '@/components/svg-icon';
|
40 |
+
import { getExtension, isPdf } from '@/utils/documentUtils';
|
41 |
import styles from './index.less';
|
42 |
|
43 |
const reg = /(#{2}\d+\${2})/g;
|
|
|
76 |
const isAssistant = item.role === MessageType.Assistant;
|
77 |
|
78 |
const handleDocumentButtonClick = useCallback(
|
79 |
+
(documentId: string, chunk: IChunk, isPdf: boolean) => () => {
|
80 |
+
if (!isPdf) {
|
81 |
+
return;
|
82 |
+
}
|
83 |
clickDocumentButton(documentId, chunk);
|
84 |
},
|
85 |
[clickDocumentButton],
|
|
|
93 |
(x) => x?.doc_id === chunkItem?.doc_id,
|
94 |
);
|
95 |
const documentId = document?.doc_id;
|
96 |
+
const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
|
97 |
+
const fileExtension = documentId ? getExtension(document?.doc_name) : '';
|
98 |
+
const imageId = chunkItem?.img_id;
|
99 |
return (
|
100 |
<Flex
|
101 |
key={chunkItem?.chunk_id}
|
102 |
gap={10}
|
103 |
className={styles.referencePopoverWrapper}
|
104 |
>
|
105 |
+
{imageId && (
|
106 |
+
<Popover
|
107 |
+
placement="left"
|
108 |
+
content={
|
109 |
+
<Image
|
110 |
+
id={imageId}
|
111 |
+
className={styles.referenceImagePreview}
|
112 |
+
></Image>
|
113 |
+
}
|
114 |
+
>
|
115 |
<Image
|
116 |
+
id={imageId}
|
117 |
+
className={styles.referenceChunkImage}
|
118 |
></Image>
|
119 |
+
</Popover>
|
120 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
121 |
<Space direction={'vertical'}>
|
122 |
<div
|
123 |
dangerouslySetInnerHTML={{
|
|
|
126 |
className={styles.chunkContentText}
|
127 |
></div>
|
128 |
{documentId && (
|
129 |
+
<Flex gap={'small'}>
|
130 |
+
{fileThumbnail ? (
|
131 |
+
<img src={fileThumbnail} alt="" />
|
132 |
+
) : (
|
133 |
+
<SvgIcon
|
134 |
+
name={`file-icon/${fileExtension}`}
|
135 |
+
width={24}
|
136 |
+
></SvgIcon>
|
137 |
+
)}
|
138 |
<Button
|
139 |
type="link"
|
140 |
+
className={styles.documentLink}
|
141 |
+
onClick={handleDocumentButtonClick(
|
142 |
+
documentId,
|
143 |
+
chunkItem,
|
144 |
+
fileExtension === 'pdf',
|
145 |
+
)}
|
146 |
>
|
147 |
{document?.doc_name}
|
148 |
</Button>
|
|
|
246 |
<List
|
247 |
bordered
|
248 |
dataSource={referenceDocumentList}
|
249 |
+
renderItem={(item) => {
|
250 |
+
const fileThumbnail = fileThumbnails[item.doc_id];
|
251 |
+
const fileExtension = getExtension(item.doc_name);
|
252 |
+
return (
|
253 |
+
<List.Item>
|
254 |
+
<Flex gap={'small'} align="center">
|
255 |
+
{fileThumbnail ? (
|
256 |
+
<img src={fileThumbnail}></img>
|
257 |
+
) : (
|
258 |
+
<SvgIcon
|
259 |
+
name={`file-icon/${fileExtension}`}
|
260 |
+
width={24}
|
261 |
+
></SvgIcon>
|
262 |
+
)}
|
263 |
+
|
264 |
+
<NewDocumentLink
|
265 |
+
documentId={item.doc_id}
|
266 |
+
preventDefault={!isPdf(item.doc_name)}
|
267 |
+
>
|
268 |
+
{item.doc_name}
|
269 |
+
</NewDocumentLink>
|
270 |
+
</Flex>
|
271 |
+
</List.Item>
|
272 |
+
);
|
273 |
+
}}
|
274 |
/>
|
275 |
)}
|
276 |
</Flex>
|
web/src/utils/documentUtils.ts
CHANGED
@@ -38,3 +38,7 @@ export const isFileUploadDone = (file: UploadFile) => file.status === 'done';
|
|
38 |
|
39 |
export const getExtension = (name: string) =>
|
40 |
name?.slice(name.lastIndexOf('.') + 1).toLowerCase() ?? '';
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
export const getExtension = (name: string) =>
|
40 |
name?.slice(name.lastIndexOf('.') + 1).toLowerCase() ?? '';
|
41 |
+
|
42 |
+
export const isPdf = (name: string) => {
|
43 |
+
return getExtension(name) === 'pdf';
|
44 |
+
};
|