"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 const pollLogsAndStatus = async () => { try { console.log("[v0] Polling logs and status for session:", sessionId) 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:", statusData.status) setStatus("completed") 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:", statusData) setStatus("failed") if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current) } onComplete?.(false) } 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(":") console.log("[v0] Detected pending interaction from status:", { type, id, titleB64, textB64, dataB64 }) setInteraction({ type: type as ScriptInteraction["type"], id, title: decodeBase64(titleB64), text: decodeBase64(textB64), data: dataB64 ? decodeBase64(dataB64) : undefined, }) } } try { const logsData = await fetchApi(`/api/scripts/session/${sessionId}/logs?from=${lastLogPositionRef.current}`) if (logsData.logs && Array.isArray(logsData.logs)) { const newLogs = logsData.logs .map((line: string) => { if (line.includes("WEB_INTERACTION:")) { const interactionPart = line.split("WEB_INTERACTION:")[1] const parts = interactionPart.split(":") if (parts.length >= 4) { const [type, id, titleB64, textB64, ...dataParts] = parts const dataB64 = dataParts.join(":") console.log("[v0] Detected interaction in log:", { type, id, titleB64, textB64, dataB64 }) const decodedTitle = decodeBase64(titleB64) const decodedText = decodeBase64(textB64) const decodedData = dataB64 ? decodeBase64(dataB64) : undefined console.log("[v0] Decoded interaction:", { type, id, title: decodedTitle, text: decodedText, data: decodedData, }) setInteraction({ type: type as ScriptInteraction["type"], id, title: decodedTitle, text: decodedText, data: decodedData, }) } return null } return { timestamp: new Date().toLocaleTimeString(), message: line, type: line.toLowerCase().includes("error") ? "error" : line.toLowerCase().includes("warning") ? "warning" : line.toLowerCase().includes("success") || line.toLowerCase().includes("complete") ? "success" : "info", } as LogEntry }) .filter(Boolean) if (newLogs.length > 0) { setLogs((prev) => [...prev, ...newLogs]) lastLogPositionRef.current = logsData.position || lastLogPositionRef.current + newLogs.length } } } catch (logError) { console.error("[v0] Error fetching logs:", logError) } } catch (error) { console.error("[v0] Error polling:", error) setLogs((prev) => [ ...prev, { timestamp: new Date().toLocaleTimeString(), message: `Error: ${error}`, type: "error", }, ]) } } pollLogsAndStatus() pollingIntervalRef.current = setInterval(pollLogsAndStatus, 1000) return () => { console.log("[v0] Cleaning up polling") 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()} ) }