"use client"; import { useEffect, useRef, useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Loader2, List, FileText, GitBranch } from "lucide-react"; import { getElectronAPI } from "@/lib/electron"; import { LogViewer } from "@/components/ui/log-viewer"; import { GitDiffPanel } from "@/components/ui/git-diff-panel"; import { useAppStore } from "@/store/app-store"; import type { AutoModeEvent } from "@/types/electron"; interface AgentOutputModalProps { open: boolean; onClose: () => void; featureDescription: string; featureId: string; /** The status of the feature - used to determine if spinner should be shown */ featureStatus?: string; /** Called when a number key (0-9) is pressed while the modal is open */ onNumberKeyPress?: (key: string) => void; } type ViewMode = "parsed" | "raw" | "changes"; export function AgentOutputModal({ open, onClose, featureDescription, featureId, featureStatus, onNumberKeyPress, }: AgentOutputModalProps) { const [output, setOutput] = useState(""); const [isLoading, setIsLoading] = useState(true); const [viewMode, setViewMode] = useState("parsed"); const [projectPath, setProjectPath] = useState(""); const scrollRef = useRef(null); const autoScrollRef = useRef(true); const projectPathRef = useRef(""); const useWorktrees = useAppStore((state) => state.useWorktrees); // Auto-scroll to bottom when output changes useEffect(() => { if (autoScrollRef.current && scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [output]); // Load existing output from file useEffect(() => { if (!open) return; const loadOutput = async () => { const api = getElectronAPI(); if (!api) return; setIsLoading(true); try { // Get current project path from store (we'll need to pass this) const currentProject = (window as any).__currentProject; if (!currentProject?.path) { setIsLoading(false); return; } projectPathRef.current = currentProject.path; setProjectPath(currentProject.path); // Use features API to get agent output if (api.features) { const result = await api.features.getAgentOutput( currentProject.path, featureId ); if (result.success) { setOutput(result.content || ""); } else { setOutput(""); } } else { setOutput(""); } } catch (error) { console.error("Failed to load output:", error); setOutput(""); } finally { setIsLoading(false); } }; loadOutput(); }, [open, featureId]); // Save output to file const saveOutput = async (newContent: string) => { if (!projectPathRef.current) return; const api = getElectronAPI(); if (!api) return; try { // Use features API - agent output is stored in features/{id}/agent-output.md // We need to write it directly since there's no updateAgentOutput method // The context-manager handles this on the backend, but for frontend edits we write directly const outputPath = `${projectPathRef.current}/.automaker/features/${featureId}/agent-output.md`; await api.writeFile(outputPath, newContent); } catch (error) { console.error("Failed to save output:", error); } }; // Listen to auto mode events and update output useEffect(() => { if (!open) return; const api = getElectronAPI(); if (!api?.autoMode) return; const unsubscribe = api.autoMode.onEvent((event) => { // Filter events for this specific feature only (skip events without featureId) if ("featureId" in event && event.featureId !== featureId) { return; } let newContent = ""; switch (event.type) { case "auto_mode_progress": newContent = event.content || ""; break; case "auto_mode_tool": const toolName = event.tool || "Unknown Tool"; const toolInput = event.input ? JSON.stringify(event.input, null, 2) : ""; newContent = `\n🔧 Tool: ${toolName}\n${ toolInput ? `Input: ${toolInput}` : "" }`; break; case "auto_mode_phase": const phaseEmoji = event.phase === "planning" ? "📋" : event.phase === "action" ? "⚡" : "✅"; newContent = `\n${phaseEmoji} ${event.message}\n`; break; case "auto_mode_error": newContent = `\n❌ Error: ${event.error}\n`; break; case "auto_mode_ultrathink_preparation": // Format thinking level preparation information let prepContent = `\n🧠 Ultrathink Preparation\n`; if (event.warnings && event.warnings.length > 0) { prepContent += `\n⚠️ Warnings:\n`; event.warnings.forEach((warning: string) => { prepContent += ` • ${warning}\n`; }); } if (event.recommendations && event.recommendations.length > 0) { prepContent += `\n💡 Recommendations:\n`; event.recommendations.forEach((rec: string) => { prepContent += ` • ${rec}\n`; }); } if (event.estimatedCost !== undefined) { prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed( 2 )} per execution\n`; } if (event.estimatedTime) { prepContent += `\n⏱️ Estimated Time: ${event.estimatedTime}\n`; } newContent = prepContent; break; case "auto_mode_feature_complete": const emoji = event.passes ? "✅" : "⚠️"; newContent = `\n${emoji} Task completed: ${event.message}\n`; // Close the modal when the feature is verified (passes = true) if (event.passes) { // Small delay to show the completion message before closing setTimeout(() => { onClose(); }, 1500); } break; } if (newContent) { setOutput((prev) => { const updated = prev + newContent; saveOutput(updated); return updated; }); } }); return () => { unsubscribe(); }; }, [open, featureId]); // Handle scroll to detect if user scrolled up const handleScroll = () => { if (!scrollRef.current) return; const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const isAtBottom = scrollHeight - scrollTop - clientHeight < 50; autoScrollRef.current = isAtBottom; }; // Handle number key presses while modal is open useEffect(() => { if (!open || !onNumberKeyPress) return; const handleKeyDown = (event: KeyboardEvent) => { // Check if a number key (0-9) was pressed without modifiers if ( !event.ctrlKey && !event.altKey && !event.metaKey && /^[0-9]$/.test(event.key) ) { event.preventDefault(); onNumberKeyPress(event.key); } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [open, onNumberKeyPress]); return (
{featureStatus !== "verified" && featureStatus !== "waiting_approval" && ( )} Agent Output
{featureDescription}
{viewMode === "changes" ? (
{projectPath ? ( ) : (
Loading...
)}
) : ( <>
{isLoading && !output ? (
Loading output...
) : !output ? (
No output yet. The agent will stream output here as it works.
) : viewMode === "parsed" ? ( ) : (
{output}
)}
{autoScrollRef.current ? "Auto-scrolling enabled" : "Scroll to bottom to enable auto-scroll"}
)}
); }