balibabu commited on
Commit
8e222fd
·
1 Parent(s): 2edbd4b

feat: add file icon and add message popover content (#77)

Browse files

* feat: add message popover content

* feat: add file icon

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. web/src/assets/svg/file-icon/aep.svg +10 -0
  2. web/src/assets/svg/file-icon/ai.svg +10 -0
  3. web/src/assets/svg/file-icon/avi.svg +10 -0
  4. web/src/assets/svg/file-icon/css.svg +10 -0
  5. web/src/assets/svg/file-icon/csv.svg +10 -0
  6. web/src/assets/svg/file-icon/dmg.svg +10 -0
  7. web/src/assets/svg/file-icon/doc.svg +10 -0
  8. web/src/assets/svg/file-icon/docx.svg +10 -0
  9. web/src/assets/svg/file-icon/eps.svg +10 -0
  10. web/src/assets/svg/file-icon/exe.svg +10 -0
  11. web/src/assets/svg/file-icon/fig.svg +10 -0
  12. web/src/assets/svg/file-icon/gif.svg +10 -0
  13. web/src/assets/svg/file-icon/html.svg +10 -0
  14. web/src/assets/svg/file-icon/indd.svg +10 -0
  15. web/src/assets/svg/file-icon/java.svg +10 -0
  16. web/src/assets/svg/file-icon/jpeg.svg +10 -0
  17. web/src/assets/svg/file-icon/jpg.svg +10 -0
  18. web/src/assets/svg/file-icon/js.svg +10 -0
  19. web/src/assets/svg/file-icon/json.svg +10 -0
  20. web/src/assets/svg/file-icon/mkv.svg +10 -0
  21. web/src/assets/svg/file-icon/mp3.svg +10 -0
  22. web/src/assets/svg/file-icon/mp4.svg +10 -0
  23. web/src/assets/svg/file-icon/mpeg.svg +10 -0
  24. web/src/assets/svg/file-icon/pdf.svg +10 -0
  25. web/src/assets/svg/file-icon/png.svg +10 -0
  26. web/src/assets/svg/file-icon/ppt.svg +10 -0
  27. web/src/assets/svg/file-icon/pptx.svg +10 -0
  28. web/src/assets/svg/file-icon/psd.svg +10 -0
  29. web/src/assets/svg/file-icon/rss.svg +10 -0
  30. web/src/assets/svg/file-icon/sql.svg +10 -0
  31. web/src/assets/svg/file-icon/svg.svg +10 -0
  32. web/src/assets/svg/file-icon/tiff.svg +10 -0
  33. web/src/assets/svg/file-icon/txt.svg +10 -0
  34. web/src/assets/svg/file-icon/wav.svg +10 -0
  35. web/src/assets/svg/file-icon/webp.svg +0 -0
  36. web/src/assets/svg/file-icon/xls.svg +10 -0
  37. web/src/assets/svg/file-icon/xlsx.svg +10 -0
  38. web/src/assets/svg/file-icon/xml.svg +10 -0
  39. web/src/components/new-document-link.tsx +20 -0
  40. web/src/components/svg-icon.tsx +34 -0
  41. web/src/constants/common.ts +40 -0
  42. web/src/hooks/commonHooks.ts +36 -0
  43. web/src/interfaces/database/chat.ts +1 -1
  44. web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx +0 -6
  45. web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx +3 -7
  46. web/src/pages/add-knowledge/index.tsx +5 -5
  47. web/src/pages/chat/chat-container/index.less +8 -0
  48. web/src/pages/chat/chat-container/index.tsx +107 -24
  49. web/src/pages/chat/hooks.ts +37 -1
  50. web/src/utils/domUtils.ts +3 -0
web/src/assets/svg/file-icon/aep.svg ADDED
web/src/assets/svg/file-icon/ai.svg ADDED
web/src/assets/svg/file-icon/avi.svg ADDED
web/src/assets/svg/file-icon/css.svg ADDED
web/src/assets/svg/file-icon/csv.svg ADDED
web/src/assets/svg/file-icon/dmg.svg ADDED
web/src/assets/svg/file-icon/doc.svg ADDED
web/src/assets/svg/file-icon/docx.svg ADDED
web/src/assets/svg/file-icon/eps.svg ADDED
web/src/assets/svg/file-icon/exe.svg ADDED
web/src/assets/svg/file-icon/fig.svg ADDED
web/src/assets/svg/file-icon/gif.svg ADDED
web/src/assets/svg/file-icon/html.svg ADDED
web/src/assets/svg/file-icon/indd.svg ADDED
web/src/assets/svg/file-icon/java.svg ADDED
web/src/assets/svg/file-icon/jpeg.svg ADDED
web/src/assets/svg/file-icon/jpg.svg ADDED
web/src/assets/svg/file-icon/js.svg ADDED
web/src/assets/svg/file-icon/json.svg ADDED
web/src/assets/svg/file-icon/mkv.svg ADDED
web/src/assets/svg/file-icon/mp3.svg ADDED
web/src/assets/svg/file-icon/mp4.svg ADDED
web/src/assets/svg/file-icon/mpeg.svg ADDED
web/src/assets/svg/file-icon/pdf.svg ADDED
web/src/assets/svg/file-icon/png.svg ADDED
web/src/assets/svg/file-icon/ppt.svg ADDED
web/src/assets/svg/file-icon/pptx.svg ADDED
web/src/assets/svg/file-icon/psd.svg ADDED
web/src/assets/svg/file-icon/rss.svg ADDED
web/src/assets/svg/file-icon/sql.svg ADDED
web/src/assets/svg/file-icon/svg.svg ADDED
web/src/assets/svg/file-icon/tiff.svg ADDED
web/src/assets/svg/file-icon/txt.svg ADDED
web/src/assets/svg/file-icon/wav.svg ADDED
web/src/assets/svg/file-icon/webp.svg ADDED
web/src/assets/svg/file-icon/xls.svg ADDED
web/src/assets/svg/file-icon/xlsx.svg ADDED
web/src/assets/svg/file-icon/xml.svg ADDED
web/src/components/new-document-link.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api_host } from '@/utils/api';
2
+ import React from 'react';
3
+
4
+ interface IProps extends React.PropsWithChildren {
5
+ documentId: string;
6
+ }
7
+
8
+ const NewDocumentLink = ({ children, documentId }: IProps) => {
9
+ return (
10
+ <a
11
+ target="_blank"
12
+ href={`${api_host}/document/get/${documentId}`}
13
+ rel="noreferrer"
14
+ >
15
+ {children}
16
+ </a>
17
+ );
18
+ };
19
+
20
+ export default NewDocumentLink;
web/src/components/svg-icon.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ UseDynamicSVGImportOptions,
3
+ useDynamicSVGImport,
4
+ } from '@/hooks/commonHooks';
5
+
6
+ interface IconProps extends React.SVGProps<SVGSVGElement> {
7
+ name: string;
8
+ onCompleted?: UseDynamicSVGImportOptions['onCompleted'];
9
+ onError?: UseDynamicSVGImportOptions['onError'];
10
+ }
11
+
12
+ const SvgIcon: React.FC<IconProps> = ({
13
+ name,
14
+ onCompleted,
15
+ onError,
16
+ ...rest
17
+ }): React.ReactNode | null => {
18
+ const { error, loading, SvgIcon } = useDynamicSVGImport(name, {
19
+ onCompleted,
20
+ onError,
21
+ });
22
+ if (error) {
23
+ return error.message;
24
+ }
25
+ if (loading) {
26
+ return 'Loading...';
27
+ }
28
+ if (SvgIcon) {
29
+ return <SvgIcon {...rest} />;
30
+ }
31
+ return null;
32
+ };
33
+
34
+ export default SvgIcon;
web/src/constants/common.ts CHANGED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const fileIconMap = {
2
+ aep: 'aep.svg',
3
+ ai: 'ai.svg',
4
+ avi: 'avi.svg',
5
+ css: 'css.svg',
6
+ csv: 'csv.svg',
7
+ dmg: 'dmg.svg',
8
+ doc: 'doc.svg',
9
+ docx: 'docx.svg',
10
+ eps: 'eps.svg',
11
+ exe: 'exe.svg',
12
+ fig: 'fig.svg',
13
+ gif: 'gif.svg',
14
+ html: 'html.svg',
15
+ indd: 'indd.svg',
16
+ java: 'java.svg',
17
+ jpeg: 'jpeg.svg',
18
+ jpg: 'jpg.svg',
19
+ js: 'js.svg',
20
+ json: 'json.svg',
21
+ mkv: 'mkv.svg',
22
+ mp3: 'mp3.svg',
23
+ mp4: 'mp4.svg',
24
+ mpeg: 'mpeg.svg',
25
+ pdf: 'pdf.svg',
26
+ png: 'png.svg',
27
+ ppt: 'ppt.svg',
28
+ pptx: 'pptx.svg',
29
+ psd: 'psd.svg',
30
+ rss: 'rss.svg',
31
+ sql: 'sql.svg',
32
+ svg: 'svg.svg',
33
+ tiff: 'tiff.svg',
34
+ txt: 'txt.svg',
35
+ wav: 'wav.svg',
36
+ webp: 'webp.svg',
37
+ xls: 'xls.svg',
38
+ xlsx: 'xlsx.svg',
39
+ xml: 'xml.svg',
40
+ };
web/src/hooks/commonHooks.ts CHANGED
@@ -32,3 +32,39 @@ export const useDeepCompareEffect = (
32
  };
33
  }, []);
34
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  };
33
  }, []);
34
  };
35
+
36
+ export interface UseDynamicSVGImportOptions {
37
+ onCompleted?: (
38
+ name: string,
39
+ SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined,
40
+ ) => void;
41
+ onError?: (err: Error) => void;
42
+ }
43
+
44
+ export function useDynamicSVGImport(
45
+ name: string,
46
+ options: UseDynamicSVGImportOptions = {},
47
+ ) {
48
+ const ImportedIconRef = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
49
+ const [loading, setLoading] = useState(false);
50
+ const [error, setError] = useState<Error>();
51
+
52
+ const { onCompleted, onError } = options;
53
+ useEffect(() => {
54
+ setLoading(true);
55
+ const importIcon = async (): Promise<void> => {
56
+ try {
57
+ ImportedIconRef.current = (await import(name)).ReactComponent;
58
+ onCompleted?.(name, ImportedIconRef.current);
59
+ } catch (err: any) {
60
+ onError?.(err);
61
+ setError(err);
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ };
66
+ importIcon();
67
+ }, [name, onCompleted, onError]);
68
+
69
+ return { error, loading, SvgIcon: ImportedIconRef.current };
70
+ }
web/src/interfaces/database/chat.ts CHANGED
@@ -71,7 +71,7 @@ export interface IReference {
71
  total: number;
72
  }
73
 
74
- interface Docagg {
75
  count: number;
76
  doc_id: string;
77
  doc_name: string;
 
71
  total: number;
72
  }
73
 
74
+ export interface Docagg {
75
  count: number;
76
  doc_id: string;
77
  doc_name: string;
web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx CHANGED
@@ -1,6 +1,5 @@
1
  import Image from '@/components/image';
2
  import { IChunk } from '@/interfaces/database/knowledge';
3
- import { api_host } from '@/utils/api';
4
  import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
5
  import { useState } from 'react';
6
  import styles from './index.less';
@@ -48,11 +47,6 @@ const ChunkCard = ({
48
  <Image id={item.img_id} className={styles.imagePreview}></Image>
49
  }
50
  >
51
- <img
52
- src={`${api_host}/document/image/${item.img_id}`}
53
- alt=""
54
- className={styles.image}
55
- />
56
  <Image id={item.img_id} className={styles.image}></Image>
57
  </Popover>
58
  )}
 
1
  import Image from '@/components/image';
2
  import { IChunk } from '@/interfaces/database/knowledge';
 
3
  import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
4
  import { useState } from 'react';
5
  import styles from './index.less';
 
47
  <Image id={item.img_id} className={styles.imagePreview}></Image>
48
  }
49
  >
 
 
 
 
 
50
  <Image id={item.img_id} className={styles.image}></Image>
51
  </Popover>
52
  )}
web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
 
2
  import { ITestingDocument } from '@/interfaces/database/knowledge';
3
- import { api_host } from '@/utils/api';
4
  import { Table, TableProps } from 'antd';
5
  import { useDispatch, useSelector } from 'umi';
6
 
@@ -34,13 +34,9 @@ const SelectFiles = ({ handleTesting }: IProps) => {
34
  key: 'view',
35
  width: 50,
36
  render: (_, { doc_id }) => (
37
- <a
38
- href={`${api_host}/document/get/${doc_id}`}
39
- target="_blank"
40
- rel="noreferrer"
41
- >
42
  <NavigationPointerIcon />
43
- </a>
44
  ),
45
  },
46
  ];
 
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
 
 
34
  key: 'view',
35
  width: 50,
36
  render: (_, { doc_id }) => (
37
+ <NewDocumentLink documentId={doc_id}>
 
 
 
 
38
  <NavigationPointerIcon />
39
+ </NewDocumentLink>
40
  ),
41
  },
42
  ];
web/src/pages/add-knowledge/index.tsx CHANGED
@@ -2,7 +2,7 @@ import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
2
  import { useSecondPathName, useThirdPathName } from '@/hooks/routeHook';
3
  import { Breadcrumb } from 'antd';
4
  import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
5
- import { useEffect, useMemo } from 'react';
6
  import { Link, Outlet, useDispatch, useLocation, useNavigate } from 'umi';
7
  import Siderbar from './components/knowledge-sidebar';
8
  import {
@@ -25,9 +25,9 @@ const KnowledgeAdding = () => {
25
  const datasetActiveKey: KnowledgeDatasetRouteKey =
26
  useThirdPathName() as KnowledgeDatasetRouteKey;
27
 
28
- const gotoList = () => {
29
  navigate('/knowledge');
30
- };
31
 
32
  const breadcrumbItems: ItemType[] = useMemo(() => {
33
  const items: ItemType[] = [
@@ -54,7 +54,7 @@ const KnowledgeAdding = () => {
54
  }
55
 
56
  return items;
57
- }, [activeKey, datasetActiveKey]);
58
 
59
  useEffect(() => {
60
  const search: string = location.search.slice(1);
@@ -71,7 +71,7 @@ const KnowledgeAdding = () => {
71
  ...map,
72
  },
73
  });
74
- }, [location]);
75
 
76
  return (
77
  <>
 
2
  import { useSecondPathName, useThirdPathName } from '@/hooks/routeHook';
3
  import { Breadcrumb } from 'antd';
4
  import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
5
+ import { useCallback, useEffect, useMemo } from 'react';
6
  import { Link, Outlet, useDispatch, useLocation, useNavigate } from 'umi';
7
  import Siderbar from './components/knowledge-sidebar';
8
  import {
 
25
  const datasetActiveKey: KnowledgeDatasetRouteKey =
26
  useThirdPathName() as KnowledgeDatasetRouteKey;
27
 
28
+ const gotoList = useCallback(() => {
29
  navigate('/knowledge');
30
+ }, [navigate]);
31
 
32
  const breadcrumbItems: ItemType[] = useMemo(() => {
33
  const items: ItemType[] = [
 
54
  }
55
 
56
  return items;
57
+ }, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId]);
58
 
59
  useEffect(() => {
60
  const search: string = location.search.slice(1);
 
71
  ...map,
72
  },
73
  });
74
+ }, [location, dispatch]);
75
 
76
  return (
77
  <>
web/src/pages/chat/chat-container/index.less CHANGED
@@ -39,3 +39,11 @@
39
  .messageItemRight {
40
  text-align: right;
41
  }
 
 
 
 
 
 
 
 
 
39
  .messageItemRight {
40
  text-align: right;
41
  }
42
+
43
+ .referencePopoverWrapper {
44
+ width: 50vw;
45
+ }
46
+
47
+ .referenceChunkImage {
48
+ width: 10vw;
49
+ }
web/src/pages/chat/chat-container/index.tsx CHANGED
@@ -3,18 +3,38 @@ 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 { Avatar, Button, Flex, Input, Popover } from 'antd';
 
 
 
 
 
 
 
 
 
7
  import classNames from 'classnames';
8
  import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
9
  import reactStringReplace from 'react-string-replace';
10
- import { useFetchConversation, useSendMessage } from '../hooks';
 
 
 
 
 
11
  import { IClientConversation } from '../interface';
12
 
 
 
13
  import { InfoCircleOutlined } from '@ant-design/icons';
14
  import Markdown from 'react-markdown';
15
  import { visitParents } from 'unist-util-visit-parents';
16
  import styles from './index.less';
17
 
 
 
 
 
18
  const rehypeWrapReference = () => {
19
  return function wrapTextTransform(tree: any) {
20
  visitParents(tree, 'text', (node, ancestors) => {
@@ -28,32 +48,69 @@ const rehypeWrapReference = () => {
28
  };
29
  };
30
 
31
- const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
 
 
 
 
 
 
32
  const userInfo = useSelectUserInfo();
33
 
34
- const popoverContent = useMemo(
35
- () => (
36
- <div>
37
- <p>Content</p>
38
- <p>Content</p>
39
- </div>
40
- ),
41
- [],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  );
43
 
44
  const renderReference = useCallback(
45
  (text: string) => {
46
- return reactStringReplace(text, /#{2}\d{1,}\${2}/g, (match, i) => {
 
47
  return (
48
- <Popover content={popoverContent}>
49
  <InfoCircleOutlined key={i} className={styles.referenceIcon} />
50
  </Popover>
51
  );
52
  });
53
  },
54
- [popoverContent],
55
  );
56
 
 
 
 
 
57
  return (
58
  <div
59
  className={classNames(styles.messageItem, {
@@ -86,9 +143,7 @@ const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
86
  <AssistantIcon></AssistantIcon>
87
  )}
88
  <Flex vertical gap={8} flex={1}>
89
- <b>
90
- {item.role === MessageType.Assistant ? 'Resume Assistant' : 'You'}
91
- </b>
92
  <div className={styles.messageText}>
93
  <Markdown
94
  rehypePlugins={[rehypeWrapReference]}
@@ -102,6 +157,22 @@ const MessageItem = ({ item }: { item: Message; references: IReference[] }) => {
102
  {item.content}
103
  </Markdown>
104
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </Flex>
106
  </div>
107
  </section>
@@ -117,6 +188,8 @@ const ChatContainer = () => {
117
  'completeConversation',
118
  'getConversation',
119
  ]);
 
 
120
 
121
  const handlePressEnter = () => {
122
  setValue('');
@@ -131,14 +204,24 @@ const ChatContainer = () => {
131
  <Flex flex={1} className={styles.chatContainer} vertical>
132
  <Flex flex={1} vertical className={styles.messageContainer}>
133
  <div>
134
- {conversation?.message?.map((message) => (
135
- <MessageItem
136
- key={message.id}
137
- item={message}
138
- references={conversation.reference}
139
- ></MessageItem>
140
- ))}
 
 
 
 
 
 
 
 
 
141
  </div>
 
142
  </Flex>
143
  <Input
144
  size="large"
 
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
+ Flex,
10
+ Input,
11
+ List,
12
+ Popover,
13
+ Space,
14
+ Typography,
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
+ useFetchConversation,
21
+ useGetFileIcon,
22
+ useScrollToBottom,
23
+ useSendMessage,
24
+ } from '../hooks';
25
  import { IClientConversation } from '../interface';
26
 
27
+ import Image from '@/components/image';
28
+ import NewDocumentLink from '@/components/new-document-link';
29
  import { InfoCircleOutlined } from '@ant-design/icons';
30
  import Markdown from 'react-markdown';
31
  import { visitParents } from 'unist-util-visit-parents';
32
  import styles from './index.less';
33
 
34
+ const reg = /(#{2}\d+\${2})/g;
35
+
36
+ const getChunkIndex = (match: string) => Number(match.slice(2, 3));
37
+
38
  const rehypeWrapReference = () => {
39
  return function wrapTextTransform(tree: any) {
40
  visitParents(tree, 'text', (node, ancestors) => {
 
48
  };
49
  };
50
 
51
+ const MessageItem = ({
52
+ item,
53
+ reference,
54
+ }: {
55
+ item: Message;
56
+ reference: IReference;
57
+ }) => {
58
  const userInfo = useSelectUserInfo();
59
 
60
+ const isAssistant = item.role === MessageType.Assistant;
61
+
62
+ const getFileIcon = useGetFileIcon();
63
+
64
+ const getPopoverContent = useCallback(
65
+ (chunkIndex: number) => {
66
+ const chunks = reference?.chunks ?? [];
67
+ const chunkItem = chunks[chunkIndex];
68
+ const document = reference?.doc_aggs.find(
69
+ (x) => x?.doc_id === chunkItem?.doc_id,
70
+ );
71
+ const documentId = document?.doc_id;
72
+ return (
73
+ <Flex
74
+ key={chunkItem?.chunk_id}
75
+ gap={10}
76
+ className={styles.referencePopoverWrapper}
77
+ >
78
+ <Image
79
+ id={chunkItem?.img_id}
80
+ className={styles.referenceChunkImage}
81
+ ></Image>
82
+ <Space direction={'vertical'}>
83
+ <div>{chunkItem?.content_with_weight}</div>
84
+ {documentId && (
85
+ <NewDocumentLink documentId={documentId}>
86
+ {document?.doc_name}
87
+ </NewDocumentLink>
88
+ )}
89
+ </Space>
90
+ </Flex>
91
+ );
92
+ },
93
+ [reference],
94
  );
95
 
96
  const renderReference = useCallback(
97
  (text: string) => {
98
+ return reactStringReplace(text, reg, (match, i) => {
99
+ const chunkIndex = getChunkIndex(match);
100
  return (
101
+ <Popover content={getPopoverContent(chunkIndex)}>
102
  <InfoCircleOutlined key={i} className={styles.referenceIcon} />
103
  </Popover>
104
  );
105
  });
106
  },
107
+ [getPopoverContent],
108
  );
109
 
110
+ const referenceDocumentList = useMemo(() => {
111
+ return reference?.doc_aggs ?? [];
112
+ }, [reference?.doc_aggs]);
113
+
114
  return (
115
  <div
116
  className={classNames(styles.messageItem, {
 
143
  <AssistantIcon></AssistantIcon>
144
  )}
145
  <Flex vertical gap={8} flex={1}>
146
+ <b>{isAssistant ? 'Resume Assistant' : 'You'}</b>
 
 
147
  <div className={styles.messageText}>
148
  <Markdown
149
  rehypePlugins={[rehypeWrapReference]}
 
157
  {item.content}
158
  </Markdown>
159
  </div>
160
+ {isAssistant && referenceDocumentList.length > 0 && (
161
+ <List
162
+ bordered
163
+ dataSource={referenceDocumentList}
164
+ renderItem={(item) => (
165
+ <List.Item>
166
+ <Typography.Text mark>
167
+ {/* <SvgIcon name={getFileIcon(item.doc_name)}></SvgIcon> */}
168
+ </Typography.Text>
169
+ <NewDocumentLink documentId={item.doc_id}>
170
+ {item.doc_name}
171
+ </NewDocumentLink>
172
+ </List.Item>
173
+ )}
174
+ />
175
+ )}
176
  </Flex>
177
  </div>
178
  </section>
 
188
  'completeConversation',
189
  'getConversation',
190
  ]);
191
+ const ref = useScrollToBottom();
192
+ useGetFileIcon();
193
 
194
  const handlePressEnter = () => {
195
  setValue('');
 
204
  <Flex flex={1} className={styles.chatContainer} vertical>
205
  <Flex flex={1} vertical className={styles.messageContainer}>
206
  <div>
207
+ {conversation?.message?.map((message) => {
208
+ const assistantMessages = conversation?.message
209
+ ?.filter((x) => x.role === MessageType.Assistant)
210
+ .slice(1);
211
+ const referenceIndex = assistantMessages.findIndex(
212
+ (x) => x.id === message.id,
213
+ );
214
+ const reference = conversation.reference[referenceIndex];
215
+ return (
216
+ <MessageItem
217
+ key={message.id}
218
+ item={message}
219
+ reference={reference}
220
+ ></MessageItem>
221
+ );
222
+ })}
223
  </div>
224
+ <div ref={ref} />
225
  </Flex>
226
  <Input
227
  size="large"
web/src/pages/chat/hooks.ts CHANGED
@@ -1,8 +1,10 @@
1
  import showDeleteConfirm from '@/components/deleting-confirm';
2
  import { MessageType } from '@/constants/chat';
 
3
  import { IConversation, IDialog } from '@/interfaces/database/chat';
 
4
  import omit from 'lodash/omit';
5
- import { useCallback, useEffect, useMemo, useState } from 'react';
6
  import { useDispatch, useSearchParams, useSelector } from 'umi';
7
  import { v4 as uuid } from 'uuid';
8
  import { ChatSearchParams, EmptyConversationId } from './constants';
@@ -441,4 +443,38 @@ export const useSendMessage = () => {
441
  return { sendMessage: handleSendMessage };
442
  };
443
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  //#endregion
 
1
  import showDeleteConfirm from '@/components/deleting-confirm';
2
  import { MessageType } from '@/constants/chat';
3
+ import { fileIconMap } from '@/constants/common';
4
  import { IConversation, IDialog } from '@/interfaces/database/chat';
5
+ import { getFileExtension } from '@/utils';
6
  import omit from 'lodash/omit';
7
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
  import { useDispatch, useSearchParams, useSelector } from 'umi';
9
  import { v4 as uuid } from 'uuid';
10
  import { ChatSearchParams, EmptyConversationId } from './constants';
 
443
  return { sendMessage: handleSendMessage };
444
  };
445
 
446
+ export const useScrollToBottom = () => {
447
+ const ref = useRef<HTMLDivElement>(null);
448
+ let chatModel: ChatModelState = useSelector((state: any) => state.chatModel);
449
+ const { currentConversation } = chatModel;
450
+
451
+ const scrollToBottom = useCallback(() => {
452
+ if (currentConversation.id) {
453
+ ref.current?.scrollIntoView({ behavior: 'instant' });
454
+ }
455
+ }, [currentConversation]);
456
+
457
+ useEffect(() => {
458
+ scrollToBottom();
459
+ }, [scrollToBottom]);
460
+
461
+ return ref;
462
+ };
463
+
464
+ export const useGetFileIcon = () => {
465
+ // const req = require.context('@/assets/svg/file-icon');
466
+ // const ret = req.keys().map(req);
467
+ // console.info(ret);
468
+ // useEffect(() => {}, []);
469
+
470
+ const getFileIcon = (filename: string) => {
471
+ const ext: string = getFileExtension(filename);
472
+ const iconPath = fileIconMap[ext as keyof typeof fileIconMap];
473
+ // const x = require(`@/assets/svg/file-icon/${iconPath}`);
474
+ return `@/assets/svg/file-icon/${iconPath}`;
475
+ };
476
+
477
+ return getFileIcon;
478
+ };
479
+
480
  //#endregion
web/src/utils/domUtils.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export const scrollToBottom = (element: HTMLElement) => {
2
+ element.scrollTo(0, element.scrollHeight);
3
+ };