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 { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; 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, } from 'antd'; import classNames from 'classnames'; import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; import Markdown from 'react-markdown'; import reactStringReplace from 'react-string-replace'; import remarkGfm from 'remark-gfm'; import { visitParents } from 'unist-util-visit-parents'; import { useClickDrawer, useFetchConversationOnMount, useGetFileIcon, 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) => { if (ancestors.at(-1).tagName !== 'custom-typography') { 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), } as any } > {item.content} ) : ( )}
{isAssistant && referenceDocumentList.length > 0 && ( ( {/* */} {item.doc_name} )} /> )}
); }; const ChatContainer = () => { const [value, setValue] = useState(''); const { ref, currentConversation: conversation, addNewestConversation, } = useFetchConversationOnMount(); const { sendMessage } = useSendMessage(); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); const loading = useOneNamespaceEffectsLoading('chatModel', [ 'completeConversation', ]); useGetFileIcon(); const handlePressEnter = () => { if (!loading) { setValue(''); addNewestConversation(value); sendMessage(value); } }; const handleInputChange: ChangeEventHandler = (e) => { const value = e.target.value.trim(); const nextValue = value.replaceAll('\\n', '\n'); setValue(nextValue); }; 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;