UNPKG

@plteam/chat-ui

Version:

CUI Kit is a free and open-source library for creating AI assistant chat interfaces, built with React, Material UI, and TypeScript

171 lines (170 loc) 7.42 kB
import * as React from 'react'; import MarkdownToJsx from 'markdown-to-jsx'; import { useChatSlots } from '../../core/ChatSlotsContext'; import MarkdownParagraphParser from './MessageMarkdownParagraphParser'; import { useSmoothManager } from './smooth/useSmoothManager'; import MarkdownComponentSmoother from './smooth/MarkdownComponentSmoother'; import { AccentComponent, BoldComponent, ItalicComponent, LiComponent, StrongComponent } from './SimpleMarkdownComponents'; import MarkdownLazyComponentSmoother from './smooth/MarkdownLazyComponentSmoother'; import clsx from 'clsx'; import { chatClassNames } from '../../core/chatClassNames'; import { useInProgressStateCache } from './useInProgressStateCache'; import Skeleton from '@mui/material/Skeleton'; const MessageMarkdown = ({ text, inProgress: inProgressProp, processAssistantText, customMarkdownComponents }) => { const { slots, slotProps } = useChatSlots(); const inProgress = useInProgressStateCache(inProgressProp); if (processAssistantText) { text = processAssistantText(text); } const getLazySmoothComponent = React.useCallback((componentKey) => { return ({ component: slots[componentKey], props: { ...slotProps[componentKey], className: clsx(slotProps?.[componentKey]?.className, { [chatClassNames.markdownSmoothedPending]: inProgress }) }, }); }, [inProgress]); const customOverrides = React.useMemo(() => { const obj = {}; customMarkdownComponents?.forEach(({ name, component }) => { const data = { [name]: { component: component, } }; Object.assign(obj, data); }); return obj; }, [customMarkdownComponents]); const paragraphSettings = React.useMemo(() => ({ component: MarkdownParagraphParser, props: { pSlot: slots.markdownP, pSlotProps: slotProps.markdownP, inProgress: inProgress, } }), [inProgress, slots, slotProps]); useSmoothManager(text, inProgress); const markdownText = React.useMemo(() => { if (!customMarkdownComponents?.length) return text; const replacedText = inProgressProp ? text.replace(/<([A-Z][A-Za-z0-9]*)([^>]*)>?/g, (match) => { const isSelfClosing = match.trim().endsWith('/>'); if (!isSelfClosing) { const userHeight = customMarkdownComponents.find(({ name }) => match.startsWith(`<${name} `))?.skeletonHeight; const height = `${userHeight || 60}px`; return `<Skeleton height={${height}} />`; } return match; }) : text; return replacedText; }, [inProgressProp, customMarkdownComponents, text]); return (React.createElement(MarkdownToJsx, { options: { forceBlock: true, forceWrapper: true, wrapper: slots.markdownWrapper, overrides: { ...customOverrides, a: { component: slots.markdownA, props: { ...slotProps.markdownA, className: clsx(slotProps.markdownA?.className, { [chatClassNames.markdownSmoothedPending]: inProgress }) }, }, table: getLazySmoothComponent('markdownTable'), thead: { component: slots.markdownThead, props: { ...slotProps.markdownThead, }, }, tbody: { component: slots.markdownTbody, props: { ...slotProps.markdownTbody, }, }, th: { component: slots.markdownTh, props: { ...slotProps.markdownTh, textComponent: slots.markdownTdText, textComponentProps: slotProps.markdownTdText, }, }, td: { component: slots.markdownTd, props: { ...slotProps.markdownTd, textComponent: slots.markdownTdText, textComponentProps: slotProps.markdownTdText, }, }, tr: { component: slots.markdownTr, props: { ...slotProps.markdownTr, }, }, ul: { component: slots.markdownUl, props: { ...slotProps.markdownUl, }, }, ol: { component: slots.markdownOl, props: { ...slotProps.markdownOl, }, }, li: { // Looks like this is also needed in slots component: MarkdownComponentSmoother, props: { inProgress, component: LiComponent }, }, b: { component: MarkdownLazyComponentSmoother, props: { inProgress, component: BoldComponent }, }, i: { component: MarkdownLazyComponentSmoother, props: { inProgress, component: ItalicComponent }, }, strong: { component: MarkdownLazyComponentSmoother, props: { inProgress, component: StrongComponent }, }, h1: getLazySmoothComponent('markdownH1'), h2: getLazySmoothComponent('markdownH2'), h3: getLazySmoothComponent('markdownH3'), h4: getLazySmoothComponent('markdownH4'), h5: getLazySmoothComponent('markdownH5'), h6: getLazySmoothComponent('markdownH6'), pre: getLazySmoothComponent('markdownCodeWrapper'), code: getLazySmoothComponent('markdownCode'), hr: getLazySmoothComponent('markdownHr'), blockquote: getLazySmoothComponent('markdownBlockquote'), img: { component: slots.markdownImg, props: { ...slotProps.markdownImg, rootClassName: clsx(slotProps.markdownImg?.rootClassName, { [chatClassNames.markdownSmoothedPending]: inProgress }) }, }, em: { component: MarkdownLazyComponentSmoother, props: { inProgress, component: AccentComponent }, }, p: paragraphSettings, span: paragraphSettings, Skeleton: { component: Skeleton, props: { variant: "rectangular" }, }, }, } }, markdownText)); }; export default MessageMarkdown;