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.8KB

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