"use client"; import { useMemo } from "react"; import { clsx } from "clsx"; interface AssistantInsightMetric { label: string; value: string; tone?: "neutral" | "good" | "warn" | "danger" | "info"; } interface AssistantInsightSection { title: string; metrics: AssistantInsightMetric[]; } interface AssistantInsight { kind: "chargeability" | "resource_match" | "holiday_region" | "resource_holidays"; title: string; subtitle?: string; metrics: AssistantInsightMetric[]; sections?: AssistantInsightSection[]; } interface ChatMessageProps { role: "user" | "assistant"; content: string; insights?: AssistantInsight[]; } 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( {listItems} , ); listItems = []; listType = null; } }; for (const [i, line] of lines.entries()) { const bulletMatch = line.match(/^[\s]*[-*]\s+(.*)/); if (bulletMatch?.[1]) { if (listType !== "ul") flushList(); listType = "ul"; listItems.push(
  • {inlineFormat(bulletMatch[1])}
  • ); continue; } const numMatch = line.match(/^[\s]*\d+\.\s+(.*)/); if (numMatch?.[1]) { if (listType !== "ol") flushList(); listType = "ol"; listItems.push(
  • {inlineFormat(numMatch[1])}
  • ); continue; } flushList(); if (line.trim() === "") { elements.push(
    ); continue; } elements.push(

    {inlineFormat(line)}

    ); } flushList(); return elements; } function inlineFormat(text: string): React.ReactNode { const parts: React.ReactNode[] = []; const regex = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`)/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(text)) !== null) { if (match.index > lastIndex) { parts.push(text.slice(lastIndex, match.index)); } if (match[2]) { parts.push({match[2]}); } else if (match[3]) { parts.push({match[3]}); } else if (match[4]) { parts.push( {match[4]} , ); } lastIndex = match.index + match[0].length; } if (lastIndex < text.length) { parts.push(text.slice(lastIndex)); } return parts.length === 1 ? parts[0] : <>{parts}; } function metricToneClasses(tone: AssistantInsightMetric["tone"] | undefined): string { switch (tone) { case "good": return "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-950/30 dark:text-emerald-300"; case "warn": return "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/30 dark:text-amber-300"; case "danger": return "border-red-200 bg-red-50 text-red-700 dark:border-red-900/60 dark:bg-red-950/30 dark:text-red-300"; case "info": return "border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-900/60 dark:bg-sky-950/30 dark:text-sky-300"; default: return "border-gray-200 bg-white text-gray-700 dark:border-slate-700 dark:bg-slate-900/60 dark:text-gray-200"; } } function InsightMetric({ metric }: { metric: AssistantInsightMetric }) { return (
    {metric.label}
    {metric.value}
    ); } function InsightCard({ insight }: { insight: AssistantInsight }) { return (
    {insight.title}
    {insight.subtitle && (
    {insight.subtitle}
    )}
    {insight.kind.replace("_", " ")}
    {insight.metrics.map((metric, index) => ( ))}
    {insight.sections && insight.sections.length > 0 && (
    {insight.sections.map((section, sectionIndex) => (
    {section.title}
    {section.metrics.map((metric, metricIndex) => ( ))}
    ))}
    )}
    ); } export function ChatMessage({ role, content, insights }: ChatMessageProps) { const isUser = role === "user"; const rendered = useMemo(() => (isUser ? null : renderMarkdown(content)), [isUser, content]); return (
    {isUser ? ( {content} ) : ( <> AI Generated {insights && insights.length > 0 && (
    {insights.map((insight, index) => ( ))}
    )}
    {rendered}
    )}
    ); } export function TypingIndicator() { return (
    ); }