| @@ -1,4 +1,3 @@ | |||
| import type { Components } from 'react-markdown' | |||
| import ReactMarkdown from 'react-markdown' | |||
| import ReactEcharts from 'echarts-for-react' | |||
| import 'katex/dist/katex.min.css' | |||
| @@ -9,8 +8,8 @@ import RemarkGfm from 'remark-gfm' | |||
| import RehypeRaw from 'rehype-raw' | |||
| import SyntaxHighlighter from 'react-syntax-highlighter' | |||
| import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs' | |||
| import { Component, createContext, memo, useContext, useMemo, useRef, useState } from 'react' | |||
| import { flow } from 'lodash/fp' | |||
| import { Component, memo, useMemo, useRef, useState } from 'react' | |||
| import { flow } from 'lodash-es' | |||
| import cn from '@/utils/classnames' | |||
| import CopyBtn from '@/app/components/base/copy-btn' | |||
| import SVGBtn from '@/app/components/base/svg' | |||
| @@ -22,7 +21,7 @@ import AudioGallery from '@/app/components/base/audio-gallery' | |||
| import SVGRenderer from '@/app/components/base/svg-gallery' | |||
| import MarkdownButton from '@/app/components/base/markdown-blocks/button' | |||
| import MarkdownForm from '@/app/components/base/markdown-blocks/form' | |||
| import type { ElementContentMap } from 'hast' | |||
| import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' | |||
| // Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD | |||
| const capitalizationLanguageNameMap: Record<string, string> = { | |||
| @@ -57,7 +56,7 @@ const getCorrectCapitalizationLanguageName = (language: string) => { | |||
| return language.charAt(0).toUpperCase() + language.substring(1) | |||
| } | |||
| const preprocessLaTeX = (content?: string) => { | |||
| const preprocessLaTeX = (content: string) => { | |||
| if (typeof content !== 'string') | |||
| return content | |||
| @@ -91,20 +90,6 @@ export function PreCode(props: { children: any }) { | |||
| ) | |||
| } | |||
| const PreContext = createContext({ | |||
| // if children not in PreContext, just leave inline true | |||
| inline: true, | |||
| }) | |||
| const PreBlock: Components['pre'] = (props) => { | |||
| const { ...rest } = props | |||
| return <PreContext.Provider value={{ | |||
| inline: false, | |||
| }}> | |||
| <pre {...rest} /> | |||
| </PreContext.Provider> | |||
| } | |||
| // **Add code block | |||
| // Avoid error #185 (Maximum update depth exceeded. | |||
| // This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. | |||
| @@ -118,8 +103,7 @@ const PreBlock: Components['pre'] = (props) => { | |||
| // visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message | |||
| // or use the non-minified dev environment for full errors and additional helpful warnings. | |||
| const CodeBlock: Components['code'] = memo(({ className, children, ...props }) => { | |||
| const { inline } = useContext(PreContext) | |||
| const CodeBlock: any = memo(({ inline, className, children, ...props }) => { | |||
| const [isSVG, setIsSVG] = useState(true) | |||
| const match = /language-(\w+)/.exec(className || '') | |||
| const language = match?.[1] | |||
| @@ -158,7 +142,7 @@ const CodeBlock: Components['code'] = memo(({ className, children, ...props }) = | |||
| else { | |||
| return ( | |||
| <SyntaxHighlighter | |||
| {...props as any} | |||
| {...props} | |||
| style={atelierHeathLight} | |||
| customStyle={{ | |||
| paddingLeft: 12, | |||
| @@ -199,23 +183,23 @@ const CodeBlock: Components['code'] = memo(({ className, children, ...props }) = | |||
| </div> | |||
| ) | |||
| }) | |||
| // CodeBlock.displayName = 'CodeBlock' | |||
| CodeBlock.displayName = 'CodeBlock' | |||
| const VideoBlock: Components['video'] = memo(({ node }) => { | |||
| const srcs = node!.children.filter(child => 'properties' in child).map(child => (child as any).properties.src) | |||
| const VideoBlock: any = memo(({ node }) => { | |||
| const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src) | |||
| if (srcs.length === 0) | |||
| return null | |||
| return <VideoGallery key={srcs.join()} srcs={srcs} /> | |||
| }) | |||
| // VideoBlock.displayName = 'VideoBlock' | |||
| VideoBlock.displayName = 'VideoBlock' | |||
| const AudioBlock: Components['audio'] = memo(({ node }) => { | |||
| const srcs = node!.children.filter(child => 'properties' in child).map(child => (child as any).properties.src) | |||
| const AudioBlock: any = memo(({ node }) => { | |||
| const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src) | |||
| if (srcs.length === 0) | |||
| return null | |||
| return <AudioGallery key={srcs.join()} srcs={srcs} /> | |||
| }) | |||
| // AudioBlock.displayName = 'AudioBlock' | |||
| AudioBlock.displayName = 'AudioBlock' | |||
| const ScriptBlock = memo(({ node }: any) => { | |||
| const scriptContent = node.children[0]?.value || '' | |||
| @@ -223,32 +207,34 @@ const ScriptBlock = memo(({ node }: any) => { | |||
| }) | |||
| ScriptBlock.displayName = 'ScriptBlock' | |||
| const Paragraph: Components['p'] = ({ node, children }) => { | |||
| const children_node = node!.children | |||
| if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') | |||
| return <ImageGallery srcs={[children_node?.[0]?.properties?.src as string]} /> | |||
| return <p>{children}</p> | |||
| const Paragraph = (paragraph: any) => { | |||
| const { node }: any = paragraph | |||
| const children_node = node.children | |||
| if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') { | |||
| return ( | |||
| <> | |||
| <ImageGallery srcs={[children_node[0].properties.src]} /> | |||
| <p>{paragraph.children.slice(1)}</p> | |||
| </> | |||
| ) | |||
| } | |||
| return <p>{paragraph.children}</p> | |||
| } | |||
| const Img: Components['img'] = ({ src }) => { | |||
| return (<ImageGallery srcs={[src!]} />) | |||
| const Img = ({ src }: any) => { | |||
| return (<ImageGallery srcs={[src]} />) | |||
| } | |||
| const Link: Components['a'] = ({ node, ...props }) => { | |||
| if (node!.properties?.href && node!.properties.href?.toString().startsWith('abbr')) { | |||
| const Link = ({ node, ...props }: any) => { | |||
| if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) { | |||
| // eslint-disable-next-line react-hooks/rules-of-hooks | |||
| const { onSend } = useChatContext() | |||
| const hidden_text = decodeURIComponent(node!.properties.href.toString().split('abbr:')[1]) | |||
| const title = (node!.children[0] as ElementContentMap['text'])?.value | |||
| return <abbr className="underline decoration-dashed !decoration-primary-700 cursor-pointer" onClick={() => onSend?.(hidden_text)} title={title}>{title}</abbr> | |||
| const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1]) | |||
| return <abbr className="underline decoration-dashed !decoration-primary-700 cursor-pointer" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value}>{node.children[0]?.value}</abbr> | |||
| } | |||
| else { | |||
| const firstChild = node?.children?.[0] as ElementContentMap['text'] | undefined | |||
| return <a {...props} target="_blank" className="underline decoration-dashed !decoration-primary-700 cursor-pointer">{ | |||
| firstChild | |||
| ? firstChild.value | |||
| : 'Download' | |||
| }</a> | |||
| return <a {...props} target="_blank" className="underline decoration-dashed !decoration-primary-700 cursor-pointer">{node.children[0] ? node.children[0]?.value : 'Download'}</a> | |||
| } | |||
| } | |||
| @@ -258,7 +244,7 @@ export function Markdown(props: { content: string; className?: string }) { | |||
| preprocessLaTeX, | |||
| ])(props.content) | |||
| return ( | |||
| <div className={cn('markdown-body', props.className)}> | |||
| <div className={cn(props.className, 'markdown-body')}> | |||
| <ReactMarkdown | |||
| remarkPlugins={[ | |||
| RemarkGfm, | |||
| @@ -284,7 +270,6 @@ export function Markdown(props: { content: string; className?: string }) { | |||
| ]} | |||
| disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body']} | |||
| components={{ | |||
| pre: PreBlock, | |||
| code: CodeBlock, | |||
| img: Img, | |||
| video: VideoBlock, | |||