balibabu
feat: Delete the file from the upload control of the message input box #1880 (#1946)
d35be0d
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; | |
import { MessageType } from '@/constants/chat'; | |
import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; | |
import { useSelectFileThumbnails } from '@/hooks/knowledge-hooks'; | |
import { IReference, Message } from '@/interfaces/database/chat'; | |
import { IChunk } from '@/interfaces/database/knowledge'; | |
import classNames from 'classnames'; | |
import { memo, useCallback, useEffect, useMemo, useState } from 'react'; | |
import { | |
useFetchDocumentInfosByIds, | |
useFetchDocumentThumbnailsByIds, | |
} from '@/hooks/document-hooks'; | |
import MarkdownContent from '@/pages/chat/markdown-content'; | |
import { getExtension, isImage } from '@/utils/document-util'; | |
import { Avatar, Button, Flex, List, Typography } from 'antd'; | |
import IndentedTreeModal from '../indented-tree/modal'; | |
import NewDocumentLink from '../new-document-link'; | |
import SvgIcon from '../svg-icon'; | |
import styles from './index.less'; | |
const { Text } = Typography; | |
interface IProps { | |
item: Message; | |
reference: IReference; | |
loading?: boolean; | |
nickname?: string; | |
avatar?: string; | |
clickDocumentButton?: (documentId: string, chunk: IChunk) => void; | |
} | |
const MessageItem = ({ | |
item, | |
reference, | |
loading = false, | |
avatar = '', | |
nickname = '', | |
clickDocumentButton, | |
}: IProps) => { | |
const isAssistant = item.role === MessageType.Assistant; | |
const isUser = item.role === MessageType.User; | |
const { t } = useTranslate('chat'); | |
const fileThumbnails = useSelectFileThumbnails(); | |
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 content = useMemo(() => { | |
let text = item.content; | |
if (text === '') { | |
text = t('searching'); | |
} | |
return loading ? text?.concat('~~2$$') : text; | |
}, [item.content, loading, t]); | |
const handleUserDocumentClick = useCallback( | |
(id: string) => () => { | |
setClickedDocumentId(id); | |
showModal(); | |
}, | |
[showModal], | |
); | |
useEffect(() => { | |
const ids = item?.doc_ids ?? []; | |
if (ids.length) { | |
setDocumentIds(ids); | |
const documentIds = ids.filter((x) => !(x in fileThumbnails)); | |
if (documentIds.length) { | |
setIds(documentIds); | |
} | |
} | |
}, [item.doc_ids, setDocumentIds, setIds, fileThumbnails]); | |
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 ?? | |
'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' | |
} | |
/> | |
) : ( | |
<AssistantIcon></AssistantIcon> | |
)} | |
<Flex vertical gap={8} flex={1}> | |
<b>{isAssistant ? '' : nickname}</b> | |
<div | |
className={ | |
isAssistant ? styles.messageText : styles.messageUserText | |
} | |
> | |
<MarkdownContent | |
content={content} | |
reference={reference} | |
clickDocumentButton={clickDocumentButton} | |
></MarkdownContent> | |
</div> | |
{isAssistant && referenceDocumentList.length > 0 && ( | |
<List | |
bordered | |
dataSource={referenceDocumentList} | |
renderItem={(item) => { | |
const fileThumbnail = fileThumbnails[item.doc_id]; | |
const fileExtension = getExtension(item.doc_name); | |
return ( | |
<List.Item> | |
<Flex gap={'small'} align="center"> | |
{fileThumbnail ? ( | |
<img | |
src={fileThumbnail} | |
className={styles.thumbnailImg} | |
></img> | |
) : ( | |
<SvgIcon | |
name={`file-icon/${fileExtension}`} | |
width={24} | |
></SvgIcon> | |
)} | |
<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) => { | |
const fileThumbnail = | |
documentThumbnails[item.id] || fileThumbnails[item.id]; | |
const fileExtension = getExtension(item.name); | |
return ( | |
<List.Item> | |
<Flex gap={'small'} align="center"> | |
{fileThumbnail ? ( | |
<img | |
src={fileThumbnail} | |
className={styles.thumbnailImg} | |
></img> | |
) : ( | |
<SvgIcon | |
name={`file-icon/${fileExtension}`} | |
width={24} | |
></SvgIcon> | |
)} | |
{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); | |