import * as React from "react"; import Markdown from "react-markdown"; import type { AgentEvent, AgentInfo, AgentStatusValue } from "../api/agents"; import { agentsApi, subscribeAgentStream } from "../api/agents"; import { api } from "../api/client"; const { useEffect, useRef, useState } = React; const STAGE_LABELS: Record = { upcoming: "Upcoming", current: "Current", qa: "QA", merge: "To Merge", done: "Done", archived: "Archived", }; const STATUS_COLORS: Record = { running: "#3fb950", pending: "#e3b341", completed: "#aaa", failed: "#f85149", }; interface WorkItemDetailPanelProps { storyId: string; onClose: () => void; } export function WorkItemDetailPanel({ storyId, onClose, }: WorkItemDetailPanelProps) { const [content, setContent] = useState(null); const [stage, setStage] = useState(""); const [name, setName] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [agentInfo, setAgentInfo] = useState(null); const [agentLog, setAgentLog] = useState([]); const [agentStatus, setAgentStatus] = useState(null); const panelRef = useRef(null); const cleanupRef = useRef<(() => void) | null>(null); useEffect(() => { setLoading(true); setError(null); api .getWorkItemContent(storyId) .then((data) => { setContent(data.content); setStage(data.stage); setName(data.name); }) .catch((err: unknown) => { setError(err instanceof Error ? err.message : "Failed to load content"); }) .finally(() => { setLoading(false); }); }, [storyId]); useEffect(() => { cleanupRef.current?.(); cleanupRef.current = null; setAgentInfo(null); setAgentLog([]); setAgentStatus(null); agentsApi .listAgents() .then((agents) => { const agent = agents.find((a) => a.story_id === storyId); if (!agent) return; setAgentInfo(agent); setAgentStatus(agent.status); if (agent.status === "running" || agent.status === "pending") { const cleanup = subscribeAgentStream( storyId, agent.agent_name, (event: AgentEvent) => { switch (event.type) { case "status": setAgentStatus((event.status as AgentStatusValue) ?? null); break; case "output": setAgentLog((prev) => [...prev, event.text ?? ""]); break; case "done": setAgentStatus("completed"); break; case "error": setAgentStatus("failed"); setAgentLog((prev) => [ ...prev, `[ERROR] ${event.message ?? "Unknown error"}`, ]); break; default: break; } }, ); cleanupRef.current = cleanup; } }) .catch((err: unknown) => { console.error("Failed to load agents:", err); }); return () => { cleanupRef.current?.(); cleanupRef.current = null; }; }, [storyId]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [onClose]); const stageLabel = STAGE_LABELS[stage] ?? stage; return (
{/* Header */}
{name ?? storyId}
{stage && (
{stageLabel}
)}
{/* Scrollable content area */}
{loading && (
Loading...
)} {error && (
{error}
)} {!loading && !error && content !== null && (
(

{children}

), // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props h2: ({ children }: any) => (

{children}

), // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props h3: ({ children }: any) => (

{children}

), }} > {content}
)}
{/* Agent Logs section */}
Agent Logs
{agentInfo && agentStatus && (
{agentInfo.agent_name} — {agentStatus}
)}
{agentInfo && agentLog.length > 0 ? (
{agentLog.join("")}
) : agentInfo ? (
{agentStatus === "running" || agentStatus === "pending" ? "Waiting for output..." : "No output."}
) : (
Coming soon
)}
{/* Placeholder sections for future content */} {( [ { id: "test-output", label: "Test Output" }, { id: "coverage", label: "Coverage" }, ] as { id: string; label: string }[] ).map(({ id, label }) => (
{label}
Coming soon
))}
); }