"use client" import { useEffect, useState, useRef } from "react" import { fetchApi } from "@/lib/api-config" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { ScrollArea } from "@/components/ui/scroll-area" import { Loader2, CheckCircle2, XCircle, AlertCircle } from "lucide-react" interface HybridScriptMonitorProps { sessionId: string | null title?: string description?: string onClose: () => void onComplete?: (success: boolean) => void } interface ScriptInteraction { type: "msgbox" | "yesno" | "inputbox" | "menu" id: string title: string text: string data?: string } interface LogEntry { timestamp: string message: string type: "info" | "error" | "warning" | "success" } export function HybridScriptMonitor({ sessionId, title = "Script Execution", description = "Monitoring script execution...", onClose, onComplete, }: HybridScriptMonitorProps) { const [logs, setLogs] = useState([]) const [interaction, setInteraction] = useState(null) const [status, setStatus] = useState<"running" | "completed" | "failed">("running") const [inputValue, setInputValue] = useState("") const [selectedMenuItem, setSelectedMenuItem] = useState("") const [isResponding, setIsResponding] = useState(false) const scrollRef = useRef(null) const pollingIntervalRef = useRef(null) const lastLogPositionRef = useRef(0) const decodeBase64 = (str: string): string => { try { return atob(str) } catch (e) { console.error("[v0] Failed to decode base64:", str, e) return str } } useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight } }, [logs]) useEffect(() => { if (!sessionId) return console.log("[v0] Setting up EventSource for session:", sessionId) const eventSource = new EventSource(`/api/scripts/logs/${sessionId}`) eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data) console.log("[v0] Received SSE event:", data) if (data.type === "init") { // Initial message, add to logs setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: `Starting script: ${data.script}`, type: "info", }, ]) } else if (data.type === "raw") { // Raw log line from script const message = data.message // Check if it contains a WEB_INTERACTION if (message.includes("WEB_INTERACTION:")) { const interactionPart = message.split("WEB_INTERACTION:")[1] if (interactionPart) { const parts = interactionPart.split(":") if (parts.length >= 4) { const [type, id, titleB64, textB64, ...dataParts] = parts const dataB64 = dataParts.join(":") console.log("[v0] Detected interaction:", { type, id, titleB64, textB64, dataB64 }) setInteraction({ type: type as ScriptInteraction["type"], id, title: decodeBase64(titleB64), text: decodeBase64(textB64), data: dataB64 ? decodeBase64(dataB64) : undefined, }) } } } else { // Regular log line setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message, type: message.toLowerCase().includes("error") ? "error" : message.toLowerCase().includes("warning") ? "warning" : message.toLowerCase().includes("success") || message.toLowerCase().includes("complete") ? "success" : "info", }, ]) } } else if (data.type === "error") { // Error from script_runner setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: `Error: ${data.message}`, type: "error", }, ]) } else { // Unknown type, display as-is setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: JSON.stringify(data), type: "info", }, ]) } } catch (e) { console.error("[v0] Error parsing SSE event:", e, "Raw data:", event.data) // If not JSON, display as plain text setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: event.data, type: "info", }, ]) } } eventSource.onerror = (error) => { console.error("[v0] EventSource error:", error) setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: "Connection to log stream lost", type: "error", }, ]) eventSource.close() } const pollStatus = async () => { try { const statusData = await fetchApi(`/api/scripts/status/${sessionId}`) console.log("[v0] Status data:", statusData) if (statusData.status === "completed" || statusData.exit_code === 0) { console.log("[v0] Script execution completed") setStatus("completed") eventSource.close() if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current) } onComplete?.(true) } else if (statusData.status === "failed" || (statusData.exit_code !== null && statusData.exit_code !== 0)) { console.log("[v0] Script execution failed") setStatus("failed") eventSource.close() if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current) } onComplete?.(false) } // Check for pending interactions in status if (statusData.pending_interaction) { const parts = statusData.pending_interaction.split(":") if (parts.length >= 4) { const [type, id, titleB64, textB64, ...dataParts] = parts const dataB64 = dataParts.join(":") setInteraction({ type: type as ScriptInteraction["type"], id, title: decodeBase64(titleB64), text: decodeBase64(textB64), data: dataB64 ? decodeBase64(dataB64) : undefined, }) } } } catch (error) { console.error("[v0] Error polling status:", error) } } pollStatus() pollingIntervalRef.current = setInterval(pollStatus, 2000) return () => { console.log("[v0] Cleaning up EventSource and polling") eventSource.close() if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current) } } }, [sessionId, onComplete]) const handleInteractionResponse = async (response: string) => { if (!interaction || !sessionId) return setIsResponding(true) try { console.log("[v0] Sending interaction response:", { session_id: sessionId, interaction_id: interaction.id, value: response, }) await fetchApi("/api/scripts/respond", { method: "POST", body: JSON.stringify({ session_id: sessionId, interaction_id: interaction.id, value: response, }), }) console.log("[v0] Response sent successfully") setInteraction(null) setInputValue("") setSelectedMenuItem("") } catch (error) { console.error("[v0] Error responding to interaction:", error) setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: `Error responding: ${error}`, type: "error", }, ]) } finally { setIsResponding(false) } } const renderInteractionModal = () => { if (!interaction) return null switch (interaction.type) { case "msgbox": return ( {}}> {interaction.title} {interaction.text}
) case "yesno": return ( {}}> {interaction.title} {interaction.text}
) case "inputbox": return ( {}}> {interaction.title} {interaction.text}
setInputValue(e.target.value)} placeholder={interaction.data || "Enter value..."} />
) case "menu": const menuItems = interaction.data?.split("|").filter(Boolean) || [] return ( {}}> {interaction.title} {interaction.text}
{menuItems.map((item, index) => ( ))}
) default: return null } } if (!sessionId) return null return ( <> {status === "running" && } {status === "completed" && } {status === "failed" && } {title} {description}
Execution Logs
{logs.length === 0 ? (
Waiting for logs...
) : ( logs.map((log, index) => (
[{log.timestamp}] {log.message}
)) )}
Session ID: {sessionId}
{status !== "running" && }
{renderInteractionModal()} ) }