import { useEffect, useRef, useState } from "react"; import type { AgentLogEntry } from "@kb/core"; interface AgentLogViewerProps { entries: AgentLogEntry[]; loading: boolean; } /** * Renders agent log entries in a scrollable, monospace container. * Auto-scrolls to the bottom as new entries arrive, but pauses * auto-scroll when the user scrolls up (scroll-lock). */ const SCROLL_THRESHOLD = 40; export function AgentLogViewer({ entries, loading }: AgentLogViewerProps) { const containerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); // Auto-scroll to bottom when new entries arrive (if scroll-lock is not active) useEffect(() => { if (autoScroll && containerRef.current) { containerRef.current.scrollTop = containerRef.current.scrollHeight; } }, [entries, autoScroll]); const handleScroll = () => { const el = containerRef.current; if (!el) return; // Enable auto-scroll only when user is near the bottom of the container const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - SCROLL_THRESHOLD; setAutoScroll(nearBottom); }; if (loading && entries.length === 0) { return (
Loading agent logs…
); } if (entries.length === 0) { return (
No agent output yet.
); } return (
{entries.map((entry, i) => { const agentBadge = entry.agent ? ( [{entry.agent}] ) : null; if (entry.type === "tool") { return (
{agentBadge}⚡ {entry.text} {entry.detail && ( — {entry.detail} )}
); } if (entry.type === "thinking") { return ( {agentBadge}{entry.text} ); } if (entry.type === "tool_result") { return (
{agentBadge}✓ {entry.text} {entry.detail && ( — {entry.detail} )}
); } if (entry.type === "tool_error") { return (
{agentBadge}✗ {entry.text} {entry.detail && ( — {entry.detail} )}
); } // Default: text entries return ( {agentBadge}{entry.text} ); })}
); }