| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 | 
							- import Image from '@/components/image';
 - import SvgIcon from '@/components/svg-icon';
 - import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
 - import { getExtension } from '@/utils/document-util';
 - import { InfoCircleOutlined } from '@ant-design/icons';
 - import { Button, Flex, Popover, Space } from 'antd';
 - import DOMPurify from 'dompurify';
 - import { useCallback, useEffect, useMemo } from 'react';
 - import Markdown from 'react-markdown';
 - import reactStringReplace from 'react-string-replace';
 - import SyntaxHighlighter from 'react-syntax-highlighter';
 - import rehypeKatex from 'rehype-katex';
 - import rehypeRaw from 'rehype-raw';
 - import remarkGfm from 'remark-gfm';
 - import remarkMath from 'remark-math';
 - import { visitParents } from 'unist-util-visit-parents';
 - 
 - import { useFetchDocumentThumbnailsByIds } from '@/hooks/document-hooks';
 - import { useTranslation } from 'react-i18next';
 - 
 - import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
 - 
 - import { preprocessLaTeX, replaceThinkToSection } from '@/utils/chat';
 - import { replaceTextByOldReg } from '../utils';
 - 
 - import { pipe } from 'lodash/fp';
 - import styles from './index.less';
 - 
 - const reg = /(~{2}\d+={2})/g;
 - const curReg = /(~{2}\d+\${2})/g;
 - 
 - const getChunkIndex = (match: string) => Number(match.slice(2, -2));
 - // TODO: The display of the table is inconsistent with the display previously placed in the MessageItem.
 - const MarkdownContent = ({
 -   reference,
 -   clickDocumentButton,
 -   content,
 -   loading,
 - }: {
 -   content: string;
 -   loading: boolean;
 -   reference: IReference;
 -   clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
 - }) => {
 -   const { t } = useTranslation();
 -   const { setDocumentIds, data: fileThumbnails } =
 -     useFetchDocumentThumbnailsByIds();
 -   const contentWithCursor = useMemo(() => {
 -     let text = content;
 -     if (text === '') {
 -       text = t('chat.searching');
 -     }
 -     const nextText = replaceTextByOldReg(text);
 -     return loading
 -       ? nextText?.concat('~~2$$')
 -       : pipe(replaceThinkToSection, preprocessLaTeX)(nextText);
 -   }, [content, loading, t]);
 - 
 -   useEffect(() => {
 -     const docAggs = reference?.doc_aggs;
 -     setDocumentIds(Array.isArray(docAggs) ? docAggs.map((x) => x.doc_id) : []);
 -   }, [reference, setDocumentIds]);
 - 
 -   const handleDocumentButtonClick = useCallback(
 -     (documentId: string, chunk: IReferenceChunk, isPdf: boolean) => () => {
 -       if (!isPdf) {
 -         return;
 -       }
 -       clickDocumentButton?.(documentId, chunk);
 -     },
 -     [clickDocumentButton],
 -   );
 - 
 -   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 getPopoverContent = useCallback(
 -     (chunkIndex: number) => {
 -       const chunks = reference?.chunks ?? [];
 -       const chunkItem = chunks[chunkIndex];
 -       const document = reference?.doc_aggs?.find(
 -         (x) => x?.doc_id === chunkItem?.document_id,
 -       );
 -       const documentId = document?.doc_id;
 -       const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
 -       const fileExtension = documentId ? getExtension(document?.doc_name) : '';
 -       const imageId = chunkItem?.image_id;
 -       return (
 -         <Flex
 -           key={chunkItem?.id}
 -           gap={10}
 -           className={styles.referencePopoverWrapper}
 -         >
 -           {imageId && (
 -             <Popover
 -               placement="left"
 -               content={
 -                 <Image
 -                   id={imageId}
 -                   className={styles.referenceImagePreview}
 -                 ></Image>
 -               }
 -             >
 -               <Image
 -                 id={imageId}
 -                 className={styles.referenceChunkImage}
 -               ></Image>
 -             </Popover>
 -           )}
 -           <Space direction={'vertical'}>
 -             <div
 -               dangerouslySetInnerHTML={{
 -                 __html: DOMPurify.sanitize(chunkItem?.content ?? ''),
 -               }}
 -               className={styles.chunkContentText}
 -             ></div>
 -             {documentId && (
 -               <Flex gap={'small'}>
 -                 {fileThumbnail ? (
 -                   <img
 -                     src={fileThumbnail}
 -                     alt=""
 -                     className={styles.fileThumbnail}
 -                   />
 -                 ) : (
 -                   <SvgIcon
 -                     name={`file-icon/${fileExtension}`}
 -                     width={24}
 -                   ></SvgIcon>
 -                 )}
 -                 <Button
 -                   type="link"
 -                   className={styles.documentLink}
 -                   onClick={handleDocumentButtonClick(
 -                     documentId,
 -                     chunkItem,
 -                     fileExtension === 'pdf',
 -                   )}
 -                 >
 -                   {document?.doc_name}
 -                 </Button>
 -               </Flex>
 -             )}
 -           </Space>
 -         </Flex>
 -       );
 -     },
 -     [reference, fileThumbnails, handleDocumentButtonClick],
 -   );
 - 
 -   const renderReference = useCallback(
 -     (text: string) => {
 -       let replacedText = reactStringReplace(text, reg, (match, i) => {
 -         const chunkIndex = getChunkIndex(match);
 -         return (
 -           <Popover content={getPopoverContent(chunkIndex)} key={i}>
 -             <InfoCircleOutlined className={styles.referenceIcon} />
 -           </Popover>
 -         );
 -       });
 - 
 -       replacedText = reactStringReplace(replacedText, curReg, (match, i) => (
 -         <span className={styles.cursor} key={i}></span>
 -       ));
 - 
 -       return replacedText;
 -     },
 -     [getPopoverContent],
 -   );
 - 
 -   return (
 -     <Markdown
 -       rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
 -       remarkPlugins={[remarkGfm, remarkMath]}
 -       components={
 -         {
 -           'custom-typography': ({ children }: { children: string }) =>
 -             renderReference(children),
 -           code(props: any) {
 -             const { children, className, node, ...rest } = props;
 -             const match = /language-(\w+)/.exec(className || '');
 -             return match ? (
 -               <SyntaxHighlighter {...rest} PreTag="div" language={match[1]}>
 -                 {String(children).replace(/\n$/, '')}
 -               </SyntaxHighlighter>
 -             ) : (
 -               <code {...rest} className={className}>
 -                 {children}
 -               </code>
 -             );
 -           },
 -         } as any
 -       }
 -     >
 -       {contentWithCursor}
 -     </Markdown>
 -   );
 - };
 - 
 - export default MarkdownContent;
 
 
  |