"use client"; import * as React from "react"; import { Sparkles, User } from "lucide-react"; import { cn } from "@/lib/utils"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; export type AIMessageProps = { role: "user" | "assistant" | "system"; content: string; timestamp?: Date; }; /** Minimal markdown: **bold**, *italic*, `code`, lists */ function renderAssistantMarkdown(content: string): React.ReactNode { const lines = content.split("\n"); const blocks: React.ReactNode[] = []; let listBuf: { ordered: boolean; items: string[] } | null = null; let blockKey = 0; const parseInline = (text: string, keyPrefix: string): React.ReactNode[] => { const nodes: React.ReactNode[] = []; let remaining = text; let i = 0; while (remaining.length > 0) { const tick = remaining.match(/^`([^`]+)`/); if (tick) { nodes.push( {tick[1]} , ); remaining = remaining.slice(tick[0].length); continue; } const bold = remaining.match(/^\*\*([^*]+)\*\*/); if (bold) { nodes.push( {bold[1]} , ); remaining = remaining.slice(bold[0].length); continue; } const italic = remaining.match(/^\*([^*]+)\*/); if (italic) { nodes.push( {italic[1]} , ); remaining = remaining.slice(italic[0].length); continue; } const nextSpecial = remaining.search(/[`\*]/); if (nextSpecial === -1) { nodes.push(remaining); break; } if (nextSpecial > 0) { nodes.push(remaining.slice(0, nextSpecial)); remaining = remaining.slice(nextSpecial); continue; } nodes.push(remaining[0]); remaining = remaining.slice(1); } return nodes; }; const flushList = () => { if (!listBuf || listBuf.items.length === 0) return; const { ordered, items } = listBuf; listBuf = null; const lis = items.map((line, i) => (
  • {parseInline(line, `li-${i}`)}
  • )); if (ordered) { blocks.push(
      {lis}
    , ); } else { blocks.push( , ); } }; for (const line of lines) { const ul = line.match(/^\s*[-*]\s+(.*)$/); const ol = line.match(/^\s*\d+\.\s+(.*)$/); if (ul) { if (listBuf?.ordered) flushList(); if (!listBuf) listBuf = { ordered: false, items: [] }; listBuf.items.push(ul[1] ?? ""); continue; } if (ol) { if (listBuf && !listBuf.ordered) flushList(); if (!listBuf) listBuf = { ordered: true, items: [] }; listBuf.items.push(ol[1] ?? ""); continue; } flushList(); if (line.trim() === "") { blocks.push(
    ); } else { blocks.push(

    {parseInline(line, `p-${blockKey}`)}

    , ); } } flushList(); return
    {blocks}
    ; } export function AIMessage({ role, content, timestamp }: AIMessageProps) { if (role === "system") { return (

    {content}

    ); } const isUser = role === "user"; return (
    {isUser ? : }
    {isUser ? (

    {content}

    ) : (
    {renderAssistantMarkdown(content)}
    )}
    {timestamp ? ( ) : null}
    ); }