|
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; |
|
import { MessageType } from '@/constants/chat'; |
|
import { useSetModalState } from '@/hooks/common-hooks'; |
|
import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; |
|
import classNames from 'classnames'; |
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'; |
|
|
|
import { |
|
useFetchDocumentInfosByIds, |
|
useFetchDocumentThumbnailsByIds, |
|
} from '@/hooks/document-hooks'; |
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; |
|
import { IMessage } from '@/pages/chat/interface'; |
|
import MarkdownContent from '@/pages/chat/markdown-content'; |
|
import { getExtension, isImage } from '@/utils/document-util'; |
|
import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; |
|
import FileIcon from '../file-icon'; |
|
import IndentedTreeModal from '../indented-tree/modal'; |
|
import NewDocumentLink from '../new-document-link'; |
|
import { useTheme } from '../theme-provider'; |
|
import { AssistantGroupButton, UserGroupButton } from './group-button'; |
|
import styles from './index.less'; |
|
|
|
const { Text } = Typography; |
|
|
|
interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage { |
|
item: IMessage; |
|
reference: IReference; |
|
loading?: boolean; |
|
sendLoading?: boolean; |
|
nickname?: string; |
|
avatar?: string; |
|
avatardialog?: string | null; |
|
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void; |
|
index: number; |
|
showLikeButton?: boolean; |
|
showLoudspeaker?: boolean; |
|
} |
|
|
|
const MessageItem = ({ |
|
item, |
|
reference, |
|
loading = false, |
|
avatar, |
|
avatardialog, |
|
sendLoading = false, |
|
clickDocumentButton, |
|
index, |
|
removeMessageById, |
|
regenerateMessage, |
|
showLikeButton = true, |
|
showLoudspeaker = true, |
|
}: IProps) => { |
|
const { theme } = useTheme(); |
|
const isAssistant = item.role === MessageType.Assistant; |
|
const isUser = item.role === MessageType.User; |
|
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); |
|
const { data: documentThumbnails, setDocumentIds: setIds } = |
|
useFetchDocumentThumbnailsByIds(); |
|
const { visible, hideModal, showModal } = useSetModalState(); |
|
const [clickedDocumentId, setClickedDocumentId] = useState(''); |
|
|
|
const referenceDocumentList = useMemo(() => { |
|
return reference?.doc_aggs ?? []; |
|
}, [reference?.doc_aggs]); |
|
|
|
const handleUserDocumentClick = useCallback( |
|
(id: string) => () => { |
|
setClickedDocumentId(id); |
|
showModal(); |
|
}, |
|
[showModal], |
|
); |
|
|
|
const handleRegenerateMessage = useCallback(() => { |
|
regenerateMessage?.(item); |
|
}, [regenerateMessage, item]); |
|
|
|
useEffect(() => { |
|
const ids = item?.doc_ids ?? []; |
|
if (ids.length) { |
|
setDocumentIds(ids); |
|
const documentIds = ids.filter((x) => !(x in documentThumbnails)); |
|
if (documentIds.length) { |
|
setIds(documentIds); |
|
} |
|
} |
|
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]); |
|
|
|
return ( |
|
<div |
|
className={classNames(styles.messageItem, { |
|
[styles.messageItemLeft]: item.role === MessageType.Assistant, |
|
[styles.messageItemRight]: item.role === MessageType.User, |
|
})} |
|
> |
|
<section |
|
className={classNames(styles.messageItemSection, { |
|
[styles.messageItemSectionLeft]: item.role === MessageType.Assistant, |
|
[styles.messageItemSectionRight]: item.role === MessageType.User, |
|
})} |
|
> |
|
<div |
|
className={classNames(styles.messageItemContent, { |
|
[styles.messageItemContentReverse]: item.role === MessageType.User, |
|
})} |
|
> |
|
{item.role === MessageType.User ? ( |
|
<Avatar size={40} src={avatar ?? '/logo.svg'} /> |
|
) : avatardialog ? ( |
|
<Avatar size={40} src={avatardialog} /> |
|
) : ( |
|
<AssistantIcon /> |
|
)} |
|
<Flex vertical gap={8} flex={1}> |
|
<Space> |
|
{isAssistant ? ( |
|
index !== 0 && ( |
|
<AssistantGroupButton |
|
messageId={item.id} |
|
content={item.content} |
|
prompt={item.prompt} |
|
showLikeButton={showLikeButton} |
|
audioBinary={item.audio_binary} |
|
showLoudspeaker={showLoudspeaker} |
|
></AssistantGroupButton> |
|
) |
|
) : ( |
|
<UserGroupButton |
|
content={item.content} |
|
messageId={item.id} |
|
removeMessageById={removeMessageById} |
|
regenerateMessage={ |
|
regenerateMessage && handleRegenerateMessage |
|
} |
|
sendLoading={sendLoading} |
|
></UserGroupButton> |
|
)} |
|
|
|
{/* <b>{isAssistant ? '' : nickname}</b> */} |
|
</Space> |
|
<div |
|
className={ |
|
isAssistant |
|
? theme === 'dark' |
|
? styles.messageTextDark |
|
: styles.messageText |
|
: styles.messageUserText |
|
} |
|
> |
|
<MarkdownContent |
|
loading={loading} |
|
content={item.content} |
|
reference={reference} |
|
clickDocumentButton={clickDocumentButton} |
|
></MarkdownContent> |
|
</div> |
|
{isAssistant && referenceDocumentList.length > 0 && ( |
|
<List |
|
bordered |
|
dataSource={referenceDocumentList} |
|
renderItem={(item) => { |
|
return ( |
|
<List.Item> |
|
<Flex gap={'small'} align="center"> |
|
<FileIcon |
|
id={item.doc_id} |
|
name={item.doc_name} |
|
></FileIcon> |
|
|
|
<NewDocumentLink |
|
documentId={item.doc_id} |
|
documentName={item.doc_name} |
|
prefix="document" |
|
> |
|
{item.doc_name} |
|
</NewDocumentLink> |
|
</Flex> |
|
</List.Item> |
|
); |
|
}} |
|
/> |
|
)} |
|
{isUser && documentList.length > 0 && ( |
|
<List |
|
bordered |
|
dataSource={documentList} |
|
renderItem={(item) => { |
|
// TODO: |
|
// const fileThumbnail = |
|
// documentThumbnails[item.id] || documentThumbnails[item.id]; |
|
const fileExtension = getExtension(item.name); |
|
return ( |
|
<List.Item> |
|
<Flex gap={'small'} align="center"> |
|
<FileIcon id={item.id} name={item.name}></FileIcon> |
|
|
|
{isImage(fileExtension) ? ( |
|
<NewDocumentLink |
|
documentId={item.id} |
|
documentName={item.name} |
|
prefix="document" |
|
> |
|
{item.name} |
|
</NewDocumentLink> |
|
) : ( |
|
<Button |
|
type={'text'} |
|
onClick={handleUserDocumentClick(item.id)} |
|
> |
|
<Text |
|
style={{ maxWidth: '40vw' }} |
|
ellipsis={{ tooltip: item.name }} |
|
> |
|
{item.name} |
|
</Text> |
|
</Button> |
|
)} |
|
</Flex> |
|
</List.Item> |
|
); |
|
}} |
|
/> |
|
)} |
|
</Flex> |
|
</div> |
|
</section> |
|
{visible && ( |
|
<IndentedTreeModal |
|
visible={visible} |
|
hideModal={hideModal} |
|
documentId={clickedDocumentId} |
|
></IndentedTreeModal> |
|
)} |
|
</div> |
|
); |
|
}; |
|
|
|
export default memo(MessageItem); |
|
|