"use client";
import { useMemo } from "react";
interface ChatMessageProps {
role: "user" | "assistant";
content: string;
}
/**
* Lightweight inline markdown renderer — handles bold, italic, code,
* bullet lists, and numbered lists without a full markdown library.
*/
function renderMarkdown(text: string) {
const lines = text.split("\n");
const elements: React.ReactNode[] = [];
let listItems: React.ReactNode[] = [];
let listType: "ul" | "ol" | null = null;
const flushList = () => {
if (listItems.length > 0 && listType) {
const Tag = listType;
elements.push(
{inlineFormat(line)}
); } flushList(); return elements; } /** Parse inline formatting: **bold**, *italic*, `code` */ function inlineFormat(text: string): React.ReactNode { // Split by inline patterns, preserving delimiters const parts: React.ReactNode[] = []; // Regex: **bold**, *italic*, `code` const regex = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`)/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(text)) !== null) { // Text before this match if (match.index > lastIndex) { parts.push(text.slice(lastIndex, match.index)); } if (match[2]) { // **bold** parts.push({match[2]}); } else if (match[3]) { // *italic* parts.push({match[3]}); } else if (match[4]) { // `code` parts.push(
{match[4]}
,
);
}
lastIndex = match.index + match[0].length;
}
// Remaining text
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts.length === 1 ? parts[0] : <>{parts}>;
}
export function ChatMessage({ role, content }: ChatMessageProps) {
const isUser = role === "user";
const rendered = useMemo(() => (isUser ? null : renderMarkdown(content)), [isUser, content]);
return (