You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
  2. import { MessageType } from '@/constants/chat';
  3. import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
  4. import { useSelectFileThumbnails } from '@/hooks/knowledge-hooks';
  5. import { IReference, Message } from '@/interfaces/database/chat';
  6. import { IChunk } from '@/interfaces/database/knowledge';
  7. import classNames from 'classnames';
  8. import { memo, useCallback, useEffect, useMemo, useState } from 'react';
  9. import {
  10. useFetchDocumentInfosByIds,
  11. useFetchDocumentThumbnailsByIds,
  12. } from '@/hooks/document-hooks';
  13. import MarkdownContent from '@/pages/chat/markdown-content';
  14. import { getExtension, isImage } from '@/utils/document-util';
  15. import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
  16. import FileIcon from '../file-icon';
  17. import IndentedTreeModal from '../indented-tree/modal';
  18. import NewDocumentLink from '../new-document-link';
  19. import { AssistantGroupButton, UserGroupButton } from './group-button';
  20. import styles from './index.less';
  21. const { Text } = Typography;
  22. interface IProps {
  23. item: Message;
  24. reference: IReference;
  25. loading?: boolean;
  26. nickname?: string;
  27. avatar?: string;
  28. clickDocumentButton?: (documentId: string, chunk: IChunk) => void;
  29. }
  30. const MessageItem = ({
  31. item,
  32. reference,
  33. loading = false,
  34. avatar = '',
  35. nickname = '',
  36. clickDocumentButton,
  37. }: IProps) => {
  38. const isAssistant = item.role === MessageType.Assistant;
  39. const isUser = item.role === MessageType.User;
  40. const { t } = useTranslate('chat');
  41. const fileThumbnails = useSelectFileThumbnails();
  42. const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
  43. const { data: documentThumbnails, setDocumentIds: setIds } =
  44. useFetchDocumentThumbnailsByIds();
  45. const { visible, hideModal, showModal } = useSetModalState();
  46. const [clickedDocumentId, setClickedDocumentId] = useState('');
  47. const referenceDocumentList = useMemo(() => {
  48. return reference?.doc_aggs ?? [];
  49. }, [reference?.doc_aggs]);
  50. const content = useMemo(() => {
  51. let text = item.content;
  52. if (text === '') {
  53. text = t('searching');
  54. }
  55. return loading ? text?.concat('~~2$$') : text;
  56. }, [item.content, loading, t]);
  57. const handleUserDocumentClick = useCallback(
  58. (id: string) => () => {
  59. setClickedDocumentId(id);
  60. showModal();
  61. },
  62. [showModal],
  63. );
  64. useEffect(() => {
  65. const ids = item?.doc_ids ?? [];
  66. if (ids.length) {
  67. setDocumentIds(ids);
  68. const documentIds = ids.filter((x) => !(x in fileThumbnails));
  69. if (documentIds.length) {
  70. setIds(documentIds);
  71. }
  72. }
  73. }, [item.doc_ids, setDocumentIds, setIds, fileThumbnails]);
  74. return (
  75. <div
  76. className={classNames(styles.messageItem, {
  77. [styles.messageItemLeft]: item.role === MessageType.Assistant,
  78. [styles.messageItemRight]: item.role === MessageType.User,
  79. })}
  80. >
  81. <section
  82. className={classNames(styles.messageItemSection, {
  83. [styles.messageItemSectionLeft]: item.role === MessageType.Assistant,
  84. [styles.messageItemSectionRight]: item.role === MessageType.User,
  85. })}
  86. >
  87. <div
  88. className={classNames(styles.messageItemContent, {
  89. [styles.messageItemContentReverse]: item.role === MessageType.User,
  90. })}
  91. >
  92. {item.role === MessageType.User ? (
  93. <Avatar
  94. size={40}
  95. src={
  96. avatar ??
  97. 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
  98. }
  99. />
  100. ) : (
  101. <AssistantIcon></AssistantIcon>
  102. )}
  103. <Flex vertical gap={8} flex={1}>
  104. <Space>
  105. {isAssistant ? (
  106. <AssistantGroupButton></AssistantGroupButton>
  107. ) : (
  108. <UserGroupButton></UserGroupButton>
  109. )}
  110. {/* <b>{isAssistant ? '' : nickname}</b> */}
  111. </Space>
  112. <div
  113. className={
  114. isAssistant ? styles.messageText : styles.messageUserText
  115. }
  116. >
  117. <MarkdownContent
  118. content={content}
  119. reference={reference}
  120. clickDocumentButton={clickDocumentButton}
  121. ></MarkdownContent>
  122. </div>
  123. {isAssistant && referenceDocumentList.length > 0 && (
  124. <List
  125. bordered
  126. dataSource={referenceDocumentList}
  127. renderItem={(item) => {
  128. return (
  129. <List.Item>
  130. <Flex gap={'small'} align="center">
  131. <FileIcon
  132. id={item.doc_id}
  133. name={item.doc_name}
  134. ></FileIcon>
  135. <NewDocumentLink
  136. documentId={item.doc_id}
  137. documentName={item.doc_name}
  138. prefix="document"
  139. >
  140. {item.doc_name}
  141. </NewDocumentLink>
  142. </Flex>
  143. </List.Item>
  144. );
  145. }}
  146. />
  147. )}
  148. {isUser && documentList.length > 0 && (
  149. <List
  150. bordered
  151. dataSource={documentList}
  152. renderItem={(item) => {
  153. // TODO:
  154. const fileThumbnail =
  155. documentThumbnails[item.id] || fileThumbnails[item.id];
  156. const fileExtension = getExtension(item.name);
  157. return (
  158. <List.Item>
  159. <Flex gap={'small'} align="center">
  160. <FileIcon id={item.id} name={item.name}></FileIcon>
  161. {isImage(fileExtension) ? (
  162. <NewDocumentLink
  163. documentId={item.id}
  164. documentName={item.name}
  165. prefix="document"
  166. >
  167. {item.name}
  168. </NewDocumentLink>
  169. ) : (
  170. <Button
  171. type={'text'}
  172. onClick={handleUserDocumentClick(item.id)}
  173. >
  174. <Text
  175. style={{ maxWidth: '40vw' }}
  176. ellipsis={{ tooltip: item.name }}
  177. >
  178. {item.name}
  179. </Text>
  180. </Button>
  181. )}
  182. </Flex>
  183. </List.Item>
  184. );
  185. }}
  186. />
  187. )}
  188. </Flex>
  189. </div>
  190. </section>
  191. {visible && (
  192. <IndentedTreeModal
  193. visible={visible}
  194. hideModal={hideModal}
  195. documentId={clickedDocumentId}
  196. ></IndentedTreeModal>
  197. )}
  198. </div>
  199. );
  200. };
  201. export default memo(MessageItem);