import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import { repairMarkdownTables, splitStreaming } from "../utils/streamRender.js";
// Full CommonMark + GFM (tables, strikethrough, task lists, autolinks) via
// react-markdown β replaces the old hand-rolled regex renderer, so any
// standard markdown the model emits renders correctly without per-syntax
// patches. Links open in a new tab.
const MD_PLUGINS = [remarkGfm, [remarkMath, { singleDollarTextMath: false }]];
const REHYPE_PLUGINS = [[rehypeKatex, { strict: false, throwOnError: false }]];
const MD_COMPONENTS = {
a: (props) => ,
table: ({ node, ...props }) => (
),
pre: ({ node, ...props }) => ,
};
function Markdown({ children }) {
return (
{repairMarkdownTables(children || "")}
);
}
/**
* One message β Claude Desktop style.
*
* user message: right-aligned, subtle warm-gray bubble
* assistant message: full-width prose, no bubble (just clean markdown)
*
* Assistant messages carry response_id + rendered_format. The feedback row
* (π/π) appears only on the most recent assistant message and routes to
* Path B with that exact response_id.
*/
export default function Message({ message, isLastAssistant, showMeta, onFeedback, onRegenerate, ratedSignal }) {
const isUser = message.role === "user";
const isPlaceholder = !!message._placeholder;
const thumbsLocked = !!ratedSignal;
// User messages: plain text in the bubble (React escapes automatically).
// Assistant messages: full markdown via react-markdown.
//
// While streaming we render LINE BY LINE: every complete line is safe to
// markdown-render, while the partially-typed last line is held back and
// sanitized (see splitStreaming). That keeps raw syntax off the screen
// without freezing a whole table as plain text until it finishes.
return (
);
}
/**
* The in-flight assistant bubble. splitStreaming decides which prefix of the
* buffer is safe to parse (buffering half-open tables/fences) and sanitizes
* the still-typing line; this component just renders those two pieces.
*/
function StreamingContent({ content }) {
const { thinking, committed, tail, liveCode } = splitStreaming(content);
if (thinking) {
return (