diff --git a/web/components/chat/CitedChunks.tsx b/web/components/chat/CitedChunks.tsx index df57ac7..25333ad 100644 --- a/web/components/chat/CitedChunks.tsx +++ b/web/components/chat/CitedChunks.tsx @@ -3,6 +3,8 @@ import { useState } from "react" import { Badge } from "@/components/ui/badge" import { ChevronDown, ChevronRight, FileText } from "lucide-react" +import { cn } from "@/lib/utils" +import { levelTone } from "@/lib/log-levels" export type CitedChunk = { chunk_id: string @@ -62,7 +64,7 @@ export function CitedChunks({ )} {c.level && ( - + {c.level} )} diff --git a/web/components/chat/Timeline.tsx b/web/components/chat/Timeline.tsx index 1414d0d..eb5505d 100644 --- a/web/components/chat/Timeline.tsx +++ b/web/components/chat/Timeline.tsx @@ -4,6 +4,7 @@ import { useState } from "react" import { Badge } from "@/components/ui/badge" import { ChevronDown, ChevronRight, Clock } from "lucide-react" import { cn } from "@/lib/utils" +import { levelTone } from "@/lib/log-levels" export type TimelineEntry = { service: string | null @@ -24,22 +25,6 @@ function formatRange(first: string, last: string): string { return `${formatTs(first)}–${formatTs(last)}` } -function levelTone(level: string | null): string { - switch ((level || "").toUpperCase()) { - case "ERROR": - case "CRITICAL": - case "FATAL": - return "text-red-500 border-red-500/30" - case "WARNING": - case "WARN": - return "text-amber-500 border-amber-500/30" - case "INFO": - return "text-blue-500 border-blue-500/30" - default: - return "text-muted-foreground border-border" - } -} - // Optional controlled `open` so a parent (e.g. a quick-action button on the // assistant turn) can force the panel open. Falls back to uncontrolled // internal state when omitted. diff --git a/web/components/investigation-step.tsx b/web/components/investigation-step.tsx index 3973128..7fffae1 100644 --- a/web/components/investigation-step.tsx +++ b/web/components/investigation-step.tsx @@ -7,8 +7,18 @@ import { Spinner } from "@/components/ui/spinner" import { Terminal, Brain, Search, ChevronDown, ChevronRight, Sparkles, Lightbulb, Flag } from "lucide-react" import { useState } from "react" import ReactMarkdown from "react-markdown" -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" -import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism" + +// Neutral JSON rendering — matches CitedChunks' raw-text styling so every +// monospace panel in the app reads the same. Syntax-highlighting themes +// (vscDarkPlus etc.) brought a full colour scheme into an otherwise +// two-tone UI and ignored light/dark mode. +function JsonBlock({ value }: { value: unknown }) { + return ( +
+      {JSON.stringify(value, null, 2)}
+    
+ ) +} export function InvestigationStepCard({ step }: { step: Step }) { const [showTool, setShowTool] = useState(false) @@ -79,7 +89,7 @@ export function InvestigationStepCard({ step }: { step: Step }) { {/* Action/Tool Call */} {step.action && ( -
+
{showTool && (
- - {JSON.stringify(step.action.args, null, 2)} - +
)}
@@ -107,7 +111,7 @@ export function InvestigationStepCard({ step }: { step: Step }) { {/* Observation */} {step.observation && ( -
+
{showObservation && (
- - {JSON.stringify(step.observation, null, 2)} - +
)}
diff --git a/web/lib/log-levels.ts b/web/lib/log-levels.ts new file mode 100644 index 0000000..15c97ed --- /dev/null +++ b/web/lib/log-levels.ts @@ -0,0 +1,19 @@ +// Single source of truth for log-level badge styling, shared by every panel +// that renders a level (Timeline, CitedChunks, …) so they can't drift apart. +// +// Deliberately minimal palette: theme `destructive` for error-class levels, +// one amber accent for warnings, neutral for INFO and everything else — +// INFO is the overwhelming majority of rows, so colouring it is pure noise. +export function levelTone(level: string | null | undefined): string { + switch ((level || "").toUpperCase()) { + case "ERROR": + case "CRITICAL": + case "FATAL": + return "text-destructive border-destructive/30" + case "WARNING": + case "WARN": + return "text-amber-600 dark:text-amber-500 border-amber-500/30" + default: + return "text-muted-foreground border-border" + } +}