import { useState, useCallback } from "react"; import type { GraphIssue } from "@understand-anything/core/schema"; interface WarningBannerProps { issues: GraphIssue[]; } function buildCopyText(issues: GraphIssue[]): string { const hasFatal = issues.some((i) => i.level === "fatal"); // Fatal issues are dashboard rendering bugs (e.g. ELK layout failures), not // LLM generation errors — route the user to file a bug report instead of // asking their agent to "fix" the knowledge-graph.json. const lines = hasFatal ? [ "Some of these issues look like dashboard rendering bugs.", "Please file an issue at github.com/Lum1104/Understand-Anything/issues with the text below.", "", ] : [ "The following issues were found in your knowledge-graph.json.", "These are LLM generation errors — not a system bug.", "You can ask your agent to fix these specific issues in the knowledge-graph.json file:", "", ]; // Show fatal first (most actionable for bug reports), then dropped, then auto-corrected. const sorted = [...issues].sort((a, b) => { const order: Record = { fatal: 0, dropped: 1, "auto-corrected": 2 }; return (order[a.level] ?? 3) - (order[b.level] ?? 3); }); for (const issue of sorted) { const label = issue.level === "auto-corrected" ? "Auto-corrected" : issue.level === "dropped" ? "Dropped" : "Fatal"; lines.push(`[${label}] ${issue.message}`); } return lines.join("\n"); } export default function WarningBanner({ issues }: WarningBannerProps) { const [expanded, setExpanded] = useState(false); const [copied, setCopied] = useState(false); const fatal = issues.filter((i) => i.level === "fatal"); const autoCorrected = issues.filter((i) => i.level === "auto-corrected"); const dropped = issues.filter((i) => i.level === "dropped"); const hasFatal = fatal.length > 0; // Build summary text — only mention counts > 0 const parts: string[] = []; if (fatal.length > 0) { parts.push(`${fatal.length} fatal error${fatal.length !== 1 ? "s" : ""}`); } if (autoCorrected.length > 0) { parts.push(`${autoCorrected.length} auto-correction${autoCorrected.length !== 1 ? "s" : ""}`); } if (dropped.length > 0) { parts.push(`${dropped.length} dropped item${dropped.length !== 1 ? "s" : ""}`); } const summary = hasFatal ? `Dashboard hit ${parts.join(", ")}` : `Knowledge graph loaded with ${parts.join(" and ")}`; const handleCopy = useCallback(async () => { const text = buildCopyText(issues); try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { console.warn("Clipboard write failed — copy text manually from the expanded issue list"); } }, [issues]); if (issues.length === 0) return null; // Fatal issues escalate the banner from amber (warning) to red (error). const containerClasses = hasFatal ? "bg-red-900/25 border-b border-red-700 text-red-200 text-sm" : "bg-amber-900/20 border-b border-amber-700 text-amber-200 text-sm"; const hoverClasses = hasFatal ? "hover:bg-red-900/15" : "hover:bg-amber-900/10"; const iconClasses = hasFatal ? "text-red-400" : "text-amber-400"; const hintClasses = hasFatal ? "text-red-400/60" : "text-amber-400/60"; const dividerClasses = hasFatal ? "border-red-700/50" : "border-amber-700/50"; const footerTextClasses = hasFatal ? "text-red-200/70" : "text-amber-200/60"; const buttonClasses = hasFatal ? "bg-red-800/40 text-red-200 hover:bg-red-800/60" : "bg-amber-800/40 text-amber-200 hover:bg-amber-800/60"; const footerCopy = hasFatal ? "Copy these issues and file a bug report on GitHub" : "Copy these issues and ask your agent to fix them in knowledge-graph.json"; return (
{/* Collapsed summary row */} {/* Expanded detail panel */} {expanded && (
{/* Issue list */}
{/* Fatal issues — top of list, red, most prominent */} {fatal.length > 0 && (

Fatal ({fatal.length})

{fatal.map((issue, i) => (
{issue.message}
))}
)} {/* Auto-corrected issues */} {autoCorrected.length > 0 && (
0 ? "mt-2" : ""}>

Auto-corrected ({autoCorrected.length})

{autoCorrected.map((issue, i) => (
{issue.message}
))}
)} {/* Dropped issues */} {dropped.length > 0 && (
0 || autoCorrected.length > 0 ? "mt-2" : ""}>

Dropped ({dropped.length})

{dropped.map((issue, i) => (
{issue.message}
))}
)}
{/* Footer with copy button and actionable message */}

{footerCopy}

)}
); }