bolt.new/packages/bolt/app/components/chat/Markdown.tsx

75 lines
2.2 KiB
TypeScript
Raw Normal View History

import { memo, useMemo } from 'react';
import ReactMarkdown, { type Components } from 'react-markdown';
2024-07-10 16:44:39 +00:00
import type { BundledLanguage } from 'shiki';
import { createScopedLogger } from '~/utils/logger';
2024-08-22 13:06:51 +00:00
import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown';
2024-07-10 16:44:39 +00:00
import { Artifact } from './Artifact';
import { CodeBlock } from './CodeBlock';
2024-07-10 16:44:39 +00:00
import styles from './Markdown.module.scss';
const logger = createScopedLogger('MarkdownComponent');
interface MarkdownProps {
children: string;
2024-08-22 13:06:51 +00:00
html?: boolean;
limitedMarkdown?: boolean;
2024-07-10 16:44:39 +00:00
}
2024-08-22 13:06:51 +00:00
export const Markdown = memo(({ children, html = false, limitedMarkdown = false }: MarkdownProps) => {
2024-07-10 16:44:39 +00:00
logger.trace('Render');
2024-08-22 13:06:51 +00:00
const components = useMemo(() => {
return {
div: ({ className, children, node, ...props }) => {
if (className?.includes('__boltArtifact__')) {
const messageId = node?.properties.dataMessageId as string;
2024-07-10 16:44:39 +00:00
if (!messageId) {
logger.error(`Invalid message id ${messageId}`);
2024-07-10 16:44:39 +00:00
}
return <Artifact messageId={messageId} />;
}
2024-07-10 16:44:39 +00:00
return (
<div className={className} {...props}>
{children}
</div>
);
},
pre: (props) => {
const { children, node, ...rest } = props;
2024-07-10 16:44:39 +00:00
const [firstChild] = node?.children ?? [];
2024-07-10 16:44:39 +00:00
if (
firstChild &&
firstChild.type === 'element' &&
firstChild.tagName === 'code' &&
firstChild.children[0].type === 'text'
) {
const { className, ...rest } = firstChild.properties;
const [, language = 'plaintext'] = /language-(\w+)/.exec(String(className) || '') ?? [];
2024-07-10 16:44:39 +00:00
return <CodeBlock code={firstChild.children[0].value} language={language as BundledLanguage} {...rest} />;
}
return <pre {...rest}>{children}</pre>;
},
2024-08-22 13:06:51 +00:00
} satisfies Components;
}, []);
return (
<ReactMarkdown
2024-08-22 13:06:51 +00:00
allowedElements={allowedHTMLElements}
className={styles.MarkdownContent}
components={components}
2024-08-22 13:06:51 +00:00
remarkPlugins={remarkPlugins(limitedMarkdown)}
rehypePlugins={rehypePlugins(html)}
2024-07-10 16:44:39 +00:00
>
{children}
</ReactMarkdown>
);
});