Files
ttstd-infinite-canvas/web/src/components/ai/ai-message-markdown.tsx
2026-09-14 01:11:56 +08:00

57 lines
3.4 KiB
TypeScript

import { Streamdown, type Components } from "streamdown";
import { memo } from "react";
import { AiMessageCodeBlock } from "./ai-message-code-block";
import "streamdown/styles.css";
type AIMessageMarkdownProps = {
children: string;
isStreaming?: boolean;
className?: string;
};
function buildComponents(isStreaming: boolean): Components {
return {
h1: ({ children, ...props }) => <h2 {...props} className="ai-message-markdown-heading ai-message-markdown-heading-1">{children}</h2>,
h2: ({ children, ...props }) => <h3 {...props} className="ai-message-markdown-heading ai-message-markdown-heading-2">{children}</h3>,
h3: ({ children, ...props }) => <h4 {...props} className="ai-message-markdown-heading ai-message-markdown-heading-3">{children}</h4>,
h4: ({ children, ...props }) => <h5 {...props} className="ai-message-markdown-heading ai-message-markdown-heading-4">{children}</h5>,
p: ({ children, ...props }) => <p {...props} className="ai-message-markdown-paragraph">{children}</p>,
ul: ({ children, ...props }) => <ul {...props} className="ai-message-markdown-list ai-message-markdown-list-unordered">{children}</ul>,
ol: ({ children, ...props }) => <ol {...props} className="ai-message-markdown-list ai-message-markdown-list-ordered">{children}</ol>,
li: ({ children, ...props }) => <li {...props} className="ai-message-markdown-list-item">{children}</li>,
blockquote: ({ children, ...props }) => <blockquote {...props} className="ai-message-markdown-blockquote">{children}</blockquote>,
pre: ({ children, ...props }) => <AiMessageCodeBlock isStreaming={isStreaming} {...props}>{children}</AiMessageCodeBlock>,
code: ({ children, className, ...props }) => <code {...props} className={`ai-message-markdown-code ${className || ""}`.trim()}>{children}</code>,
a: ({ children, ...props }) => <a {...props} className="ai-message-markdown-link" target="_blank" rel="noreferrer">{children}</a>,
hr: (props) => <hr {...props} className="ai-message-markdown-rule" />,
table: ({ children, ...props }) => <div className="ai-message-markdown-table-wrap"><table {...props} className="ai-message-markdown-table">{children}</table></div>,
th: ({ children, ...props }) => <th {...props} className="ai-message-markdown-table-cell ai-message-markdown-table-header">{children}</th>,
td: ({ children, ...props }) => <td {...props} className="ai-message-markdown-table-cell">{children}</td>,
input: ({ ...props }) => <input {...props} className="ai-message-markdown-task" disabled />,
};
}
const staticComponents = buildComponents(false);
const streamingComponents = buildComponents(true);
export const AIMessageMarkdown = memo(function AIMessageMarkdown({ children, isStreaming = false, className = "" }: AIMessageMarkdownProps) {
if (!children.trim()) return null;
return (
<Streamdown
className={`ai-message-markdown ${isStreaming ? "ai-message-markdown-streaming" : ""} ${className}`.trim()}
mode="streaming"
dir="auto"
isAnimating={isStreaming}
animated={isStreaming ? { animation: "fadeIn", duration: 140, sep: "word", stagger: 8 } : false}
parseIncompleteMarkdown
skipHtml
lineNumbers={false}
components={isStreaming ? streamingComponents : staticComponents}
>
{children}
</Streamdown>
);
});