import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; import Image from '@/components/image'; import NewDocumentLink from '@/components/new-document-link'; import DocumentPreviewer from '@/components/pdf-previewer'; import { MessageType } from '@/constants/chat'; import { useSelectFileThumbnails } from '@/hooks/knowledgeHook'; import { useSelectUserInfo } from '@/hooks/userSettingHook'; import { IReference, Message } from '@/interfaces/database/chat'; import { IChunk } from '@/interfaces/database/knowledge'; import { InfoCircleOutlined } from '@ant-design/icons'; import { Avatar, Button, Drawer, Flex, Input, List, Popover, Skeleton, Space, Spin, } from 'antd'; import classNames from 'classnames'; import { useCallback, useMemo } from 'react'; import Markdown from 'react-markdown'; import reactStringReplace from 'react-string-replace'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import remarkGfm from 'remark-gfm'; import { visitParents } from 'unist-util-visit-parents'; import { useClickDrawer, useFetchConversationOnMount, useGetFileIcon, useGetSendButtonDisabled, useSelectConversationLoading, useSendMessage, } from '../hooks'; import styles from './index.less'; const reg = /(#{2}\d+\${2})/g; const getChunkIndex = (match: string) => Number(match.slice(2, -2)); const rehypeWrapReference = () => { return function wrapTextTransform(tree: any) { visitParents(tree, 'text', (node, ancestors) => { const latestAncestor = ancestors.at(-1); if ( latestAncestor.tagName !== 'custom-typography' && latestAncestor.tagName !== 'code' ) { node.type = 'element'; node.tagName = 'custom-typography'; node.properties = {}; node.children = [{ type: 'text', value: node.value }]; } }); }; }; const MessageItem = ({ item, reference, clickDocumentButton, }: { item: Message; reference: IReference; clickDocumentButton: (documentId: string, chunk: IChunk) => void; }) => { const userInfo = useSelectUserInfo(); const fileThumbnails = useSelectFileThumbnails(); const isAssistant = item.role === MessageType.Assistant; const handleDocumentButtonClick = useCallback( (documentId: string, chunk: IChunk) => () => { clickDocumentButton(documentId, chunk); }, [clickDocumentButton], ); const getPopoverContent = useCallback( (chunkIndex: number) => { const chunks = reference?.chunks ?? []; const chunkItem = chunks[chunkIndex]; const document = reference?.doc_aggs.find( (x) => x?.doc_id === chunkItem?.doc_id, ); const documentId = document?.doc_id; return ( } >
{documentId && ( )}
); }, [reference, fileThumbnails, handleDocumentButtonClick], ); const renderReference = useCallback( (text: string) => { return reactStringReplace(text, reg, (match, i) => { const chunkIndex = getChunkIndex(match); return ( ); }); }, [getPopoverContent], ); const referenceDocumentList = useMemo(() => { return reference?.doc_aggs ?? []; }, [reference?.doc_aggs]); return (
{item.role === MessageType.User ? ( ) : ( )} {isAssistant ? '' : userInfo.nickname}
{item.content !== '' ? ( renderReference(children), code(props: any) { const { children, className, node, ...rest } = props; const match = /language-(\w+)/.exec(className || ''); return match ? ( {String(children).replace(/\n$/, '')} ) : ( {children} ); }, } as any } > {item.content} ) : ( )}
{isAssistant && referenceDocumentList.length > 0 && ( ( {/* */} {item.doc_name} )} /> )}
); }; const ChatContainer = () => { const { ref, currentConversation: conversation, addNewestConversation, removeLatestMessage, } = useFetchConversationOnMount(); const { handleInputChange, handlePressEnter, value, loading: sendLoading, } = useSendMessage(conversation, addNewestConversation, removeLatestMessage); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); const disabled = useGetSendButtonDisabled(); useGetFileIcon(); const loading = useSelectConversationLoading(); return ( <>
{conversation?.message?.map((message) => { const assistantMessages = conversation?.message ?.filter((x) => x.role === MessageType.Assistant) .slice(1); const referenceIndex = assistantMessages.findIndex( (x) => x.id === message.id, ); const reference = conversation.reference[referenceIndex]; return ( ); })}
Send } onPressEnter={handlePressEnter} onChange={handleInputChange} /> ); }; export default ChatContainer;