| import { Activity, Database, ListChecks, Wrench } from "lucide-react"; |
| import type { Observability } from "@/services/agenticApi"; |
| import { EmptyState, ErrorState, LoadingState } from "./AnalysisStates"; |
|
|
| interface ObservabilityPanelProps { |
| observability: Observability | null; |
| loading?: boolean; |
| error?: string | null; |
| } |
|
|
| export function ObservabilityPanel({ observability, loading, error }: ObservabilityPanelProps) { |
| if (loading) return <LoadingState label="Loading observability" />; |
| if (error) return <ErrorState title="Observability unavailable" message={error} />; |
| if (!observability) return <EmptyState title="No observability yet" description="It appears after an AI answer completes." />; |
|
|
| return ( |
| <section className="space-y-4"> |
| <div className="flex items-center gap-2"> |
| <Activity className="h-4 w-4 text-slate-600" /> |
| <h3 className="text-sm font-semibold text-slate-900">Observability</h3> |
| </div> |
| |
| {observability.planning && ( |
| <div className="rounded-lg border border-slate-200 bg-white p-3"> |
| <div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-slate-500"> |
| <ListChecks className="h-3.5 w-3.5" /> Planning |
| </div> |
| {observability.planning.goal_restated && <p className="text-sm leading-6 text-slate-700">{observability.planning.goal_restated}</p>} |
| {observability.planning.steps && observability.planning.steps.length > 0 && ( |
| <ol className="mt-2 space-y-1 text-xs text-slate-600"> |
| {observability.planning.steps.map((step, index) => ( |
| <li key={`${step.stage}-${index}`}>{step.step ?? index + 1}. {step.objective ?? step.stage}</li> |
| ))} |
| </ol> |
| )} |
| </div> |
| )} |
| |
| {observability.thinking && ( |
| <div className="rounded-lg border border-slate-200 bg-white p-3"> |
| <p className="text-xs font-semibold uppercase tracking-wide text-slate-500">Thinking summary</p> |
| <p className="mt-2 text-sm leading-6 text-slate-700">{observability.thinking}</p> |
| </div> |
| )} |
| |
| <div className="rounded-lg border border-slate-200 bg-white p-3"> |
| <div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-slate-500"> |
| <Wrench className="h-3.5 w-3.5" /> Tool calls |
| </div> |
| {observability.tool_calls.length === 0 ? ( |
| <p className="text-xs text-slate-500">No tools were used.</p> |
| ) : ( |
| <div className="space-y-2"> |
| {observability.tool_calls.map((tool, index) => ( |
| <div key={`${tool.name}-${index}`} className="rounded-md bg-slate-50 px-3 py-2"> |
| <p className="text-sm font-medium text-slate-800">{tool.name}</p> |
| <p className="text-xs text-slate-500">{tool.status ?? "completed"}</p> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| |
| <div className="rounded-lg border border-slate-200 bg-white p-3"> |
| <div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-slate-500"> |
| <Database className="h-3.5 w-3.5" /> Sources |
| </div> |
| {observability.sources.length === 0 ? ( |
| <p className="text-xs text-slate-500">No sources attached.</p> |
| ) : ( |
| <pre className="max-h-44 overflow-auto rounded-md bg-slate-950 p-3 text-[11px] leading-5 text-slate-100"> |
| {JSON.stringify(observability.sources, null, 2)} |
| </pre> |
| )} |
| </div> |
| </section> |
| ); |
| } |
|
|