import { useState, useEffect, useCallback } from "react"; import { useTaskSubscription } from "../../hooks/useTaskSubscription"; import type { TaskOutputEvent } from "../../hooks/useTaskSubscription"; import { TaskOutput } from "../mesh/TaskOutput"; import { WorktreeFilesPanel } from "../mesh/WorktreeFilesPanel"; import { getTaskOutput } from "../../lib/api"; interface TaskSlideOutPanelProps { taskId: string; taskName?: string; isOpen: boolean; onClose: () => void; } export function TaskSlideOutPanel({ taskId, taskName, isOpen, onClose, }: TaskSlideOutPanelProps) { const [entries, setEntries] = useState([]); const [isStreaming, setIsStreaming] = useState(false); const [loadingHistory, setLoadingHistory] = useState(false); // Escape key handler useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; if (isOpen) document.addEventListener("keydown", handleEsc); return () => document.removeEventListener("keydown", handleEsc); }, [isOpen, onClose]); // Load historical output when panel opens with a taskId useEffect(() => { if (!isOpen || !taskId) { setEntries([]); setIsStreaming(false); return; } let cancelled = false; setLoadingHistory(true); getTaskOutput(taskId) .then((res) => { if (cancelled) return; // Map TaskOutputEntry to TaskOutputEvent const mapped: TaskOutputEvent[] = res.entries.map((e) => ({ taskId: e.taskId, messageType: e.messageType, content: e.content, toolName: e.toolName, toolInput: e.toolInput, isError: e.isError, costUsd: e.costUsd, durationMs: e.durationMs, isPartial: false, })); setEntries(mapped); }) .catch((err) => { if (cancelled) return; console.error("Failed to load task output history:", err); }) .finally(() => { if (!cancelled) setLoadingHistory(false); }); return () => { cancelled = true; }; }, [isOpen, taskId]); // Handle live output events const handleOutput = useCallback( (event: TaskOutputEvent) => { if (event.isPartial) return; setEntries((prev) => [...prev, event]); setIsStreaming(true); }, [] ); // Handle task updates (to detect completion) const handleUpdate = useCallback( (event: { status: string }) => { if ( event.status === "completed" || event.status === "failed" || event.status === "cancelled" ) { setIsStreaming(false); } else if (event.status === "running") { setIsStreaming(true); } }, [] ); // Subscribe to live output useTaskSubscription({ taskId: isOpen ? taskId : null, subscribeOutput: isOpen && !!taskId, onOutput: handleOutput, onUpdate: handleUpdate, }); return ( <> {/* Backdrop overlay */}
{/* Slide-out panel */}
{/* Header */}
Task {taskName || taskId} {isStreaming && ( Live )}
{/* Content */}
{/* Task Output section (~60% height) */}
{loadingHistory ? (
Loading output...
) : ( )}
{/* Worktree Changes section (~40% height) */}
{taskId && }
); }