/* ============================================================================= AUTO TASK JAVASCRIPT - Sentient Theme Intelligent Self-Executing Task Interface ============================================================================= */ // ============================================================================= // STATE MANAGEMENT // ============================================================================= const AutoTaskState = { currentFilter: "active", selectedIntentId: null, intents: [], compiledPlan: null, pendingDecisions: [], pendingApprovals: [], refreshInterval: null, wsConnection: null, progressWsConnection: null, activeTaskProgress: {}, llmOutputStream: [], }; // ============================================================================= // INITIALIZATION // ============================================================================= document.addEventListener("DOMContentLoaded", function () { initAutoTask(); }); function initAutoTask() { // Initialize WebSocket for real-time updates initWebSocket(); // Initialize task progress WebSocket initTaskProgressWebSocket(); // Start auto-refresh startAutoRefresh(); // Setup event listeners setupEventListeners(); // Load initial stats updateStats(); // Setup keyboard shortcuts setupKeyboardShortcuts(); // Initialize floating panel (hidden by default) initFloatingPanel(); console.log("AutoTask Sentient initialized"); } // ============================================================================= // FLOATING PANEL // ============================================================================= function initFloatingPanel() { const panel = document.getElementById("floating-progress"); if (panel) { panel.style.display = "none"; } } function showFloatingPanel(taskId, taskName) { const panel = document.getElementById("floating-progress"); if (!panel) return; panel.style.display = "block"; panel.dataset.taskId = taskId; const nameEl = document.getElementById("floating-task-name"); if (nameEl) nameEl.textContent = taskName || "Processing..."; updateFloatingProgress(0, 0, 0, "Initializing..."); } function updateFloatingProgress(progress, current, total, statusText) { const fill = document.getElementById("floating-progress-fill"); const pct = document.getElementById("floating-percentage"); const steps = document.getElementById("floating-status-steps"); const status = document.getElementById("floating-status-text"); if (fill) fill.style.width = `${progress}%`; if (pct) pct.textContent = `${progress}%`; if (steps) steps.textContent = `${current}/${total}`; if (status) status.textContent = statusText; } function addFloatingLog(icon, message, type = "info") { const log = document.getElementById("floating-log"); if (!log) return; const entry = document.createElement("div"); entry.className = `floating-log-entry ${type}`; entry.innerHTML = ` ${icon} ${escapeHtml(message)} ${formatTime(new Date())} `; log.appendChild(entry); log.scrollTop = log.scrollHeight; } function addFloatingTerminalOutput(text) { const terminal = document.getElementById("floating-terminal"); if (!terminal) return; const line = document.createElement("div"); line.className = "llm-output"; line.textContent = text; terminal.appendChild(line); terminal.scrollTop = terminal.scrollHeight; // Keep only last 50 lines while (terminal.children.length > 50) { terminal.removeChild(terminal.firstChild); } } function minimizeFloatingPanel() { const panel = document.getElementById("floating-progress"); if (panel) { panel.classList.toggle("minimized"); } } function closeFloatingPanel() { const panel = document.getElementById("floating-progress"); if (panel) { panel.style.display = "none"; } } function completeFloatingPanel(message) { updateFloatingProgress(100, 0, 0, message || "Complete!"); addFloatingLog("βœ…", message || "Task completed successfully", "success"); // Auto-hide after delay setTimeout(() => { closeFloatingPanel(); }, 5000); } // ============================================================================= // WEBSOCKET CONNECTION // ============================================================================= function initWebSocket() { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${protocol}//${window.location.host}/ws/autotask`; try { AutoTaskState.wsConnection = new WebSocket(wsUrl); AutoTaskState.wsConnection.onopen = function () { console.log("AutoTask WebSocket connected"); }; AutoTaskState.wsConnection.onmessage = function (event) { handleWebSocketMessage(JSON.parse(event.data)); }; AutoTaskState.wsConnection.onclose = function () { console.log("AutoTask WebSocket disconnected, reconnecting..."); setTimeout(initWebSocket, 5000); }; AutoTaskState.wsConnection.onerror = function (error) { console.error("AutoTask WebSocket error:", error); }; } catch (e) { console.warn("WebSocket not available, using polling"); } } function initTaskProgressWebSocket() { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${protocol}//${window.location.host}/ws/task-progress`; try { AutoTaskState.progressWsConnection = new WebSocket(wsUrl); AutoTaskState.progressWsConnection.onopen = function () { console.log("Task Progress WebSocket connected"); }; AutoTaskState.progressWsConnection.onmessage = function (event) { handleTaskProgressMessage(JSON.parse(event.data)); }; AutoTaskState.progressWsConnection.onclose = function () { console.log("Task Progress WebSocket disconnected, reconnecting..."); setTimeout(initTaskProgressWebSocket, 3000); }; AutoTaskState.progressWsConnection.onerror = function (error) { console.error("Task Progress WebSocket error:", error); }; } catch (e) { console.warn("Task Progress WebSocket not available"); } } function handleTaskProgressMessage(data) { // Forward to ProgressPanel if available if (typeof ProgressPanel !== "undefined" && ProgressPanel.manifest) { ProgressPanel.handleProgressUpdate(data); } console.log("Task progress:", data); switch (data.type) { case "connected": console.log("Connected to task progress stream"); break; case "task_started": onTaskStarted(data); break; case "task_progress": onTaskProgress(data); break; case "task_completed": onTaskProgressCompleted(data); break; case "task_error": onTaskProgressError(data); break; case "llm_stream": onLLMStream(data); break; default: console.log("Unknown progress event:", data.type); } } function onTaskStarted(data) { AutoTaskState.activeTaskProgress[data.task_id] = { started: new Date(), totalSteps: data.total_steps, currentStep: 0, progress: 0, logs: [], }; // Show floating panel showFloatingPanel(data.task_id, data.message); addFloatingLog("πŸš€", data.message, "started"); // Also update detail panel if viewing this intent if (AutoTaskState.selectedIntentId === data.task_id) { updateDetailPanelProgress(data); } } function onTaskProgress(data) { const taskProgress = AutoTaskState.activeTaskProgress[data.task_id]; if (taskProgress) { taskProgress.currentStep = data.current_step; taskProgress.progress = data.progress; taskProgress.logs.push({ time: new Date(), step: data.step, message: data.message, details: data.details, }); } // Update floating panel updateFloatingProgress( data.progress, data.current_step, data.total_steps, data.message, ); const stepIcons = { llm_request: "πŸ€–", llm_response: "✨", parse_structure: "πŸ“‹", create_tables: "πŸ—„οΈ", tables_synced: "βœ…", write_files: "πŸ“", write_file: "πŸ“„", write_designer: "🎨", write_tools: "πŸ”§", sync_site: "πŸ”„", }; const icon = stepIcons[data.step] || "πŸ“Œ"; addFloatingLog(icon, data.message, "progress"); // Update detail panel if viewing this intent if (AutoTaskState.selectedIntentId === data.task_id) { updateDetailPanelProgress(data); addTerminalLine(data.message); } } function onTaskProgressCompleted(data) { const taskProgress = AutoTaskState.activeTaskProgress[data.task_id]; if (taskProgress) { taskProgress.progress = 100; taskProgress.completed = new Date(); } completeFloatingPanel(data.message); // Refresh intent list setTimeout(() => { refreshIntents(); updateStats(); }, 1000); } function onTaskProgressError(data) { addFloatingLog("❌", data.error, "error"); updateFloatingProgress( AutoTaskState.activeTaskProgress[data.task_id]?.progress || 0, 0, 0, `Error: ${data.error}`, ); } function onLLMStream(data) { // Stream LLM output to terminal addFloatingTerminalOutput(data.text); if (AutoTaskState.selectedIntentId === data.task_id) { addTerminalLine(data.text, true); } } function addTerminalLine(text, isLLM = false) { const terminal = document.querySelector( `#terminal-${AutoTaskState.selectedIntentId}`, ); if (!terminal) return; const line = document.createElement("div"); line.className = `terminal-line${isLLM ? " highlight" : ""}`; line.textContent = text; terminal.appendChild(line); terminal.scrollTop = terminal.scrollHeight; } function updateDetailPanelProgress(data) { // Update progress bar in detail panel const progressFill = document.querySelector( ".detail-progress .progress-fill", ); const progressSteps = document.querySelector( ".detail-progress .progress-steps", ); const progressPct = document.querySelector( ".detail-progress .progress-percent", ); if (progressFill) progressFill.style.width = `${data.progress}%`; if (progressSteps) progressSteps.textContent = `${data.current_step}/${data.total_steps} Steps`; if (progressPct) progressPct.textContent = `${data.progress}%`; } // ============================================================================= // INTENT SELECTION & DETAIL PANEL // ============================================================================= function selectIntent(intentId) { // Update selected state document.querySelectorAll(".intent-card").forEach((card) => { card.classList.remove("selected"); }); const selectedCard = document.querySelector( `.intent-card[data-intent-id="${intentId}"]`, ); if (selectedCard) { selectedCard.classList.add("selected"); } AutoTaskState.selectedIntentId = intentId; // Load detail panel loadIntentDetail(intentId); } function loadIntentDetail(intentId) { const panel = document.getElementById("intent-detail-panel"); if (!panel) return; // Show loading state panel.innerHTML = `
Loading intent details...
`; // Fetch intent details and manifest in parallel Promise.all([ fetch(`/api/autotask/${intentId}`).then((r) => r.json()), fetch(`/api/autotask/${intentId}/manifest`) .then((r) => r.json()) .catch(() => null), ]) .then(([taskData, manifestData]) => { renderIntentDetail(taskData); // If manifest exists, initialize ProgressPanel if (manifestData && manifestData.success && manifestData.manifest) { if (typeof ProgressPanel !== "undefined") { ProgressPanel.manifest = manifestData.manifest; ProgressPanel.init(intentId); ProgressPanel.render(); } } }) .catch((error) => { console.error("Failed to load intent detail:", error); panel.innerHTML = `
⚠️

Failed to load intent details

`; }); } function renderIntentDetail(intent) { const panel = document.getElementById("intent-detail-panel"); if (!panel) return; const statusIcons = { active: "⚑", complete: "βœ“", paused: "⏸", blocked: "⚠", awaiting: "⏳", }; const healthClass = intent.health >= 80 ? "good" : intent.health >= 50 ? "warning" : "bad"; panel.innerHTML = `

${escapeHtml(intent.title || intent.intent)}

${statusIcons[intent.status] || "⚑"} ${intent.status_display || intent.status}
${intent.current_step || 0}/${intent.total_steps || 0} Steps ${intent.progress || 0}%
${ intent.decision_required ? `
DECISION REQUIRED ${escapeHtml(intent.decision_title || "Decision needed")}
Context:

${escapeHtml(intent.decision_context || "")}

` : "" }
STATUS Runtime: ${intent.runtime || "0 min"}
${escapeHtml(intent.current_task_name || "Processing...")} Estimated: ${intent.estimated_time || "calculating..."}
${renderStepsPreview(intent.steps || [])}
PROGRESS LOG
${renderProgressLog(intent.logs || [])}
TERMINAL (LIVE AGENT ACTIVITY) Processed: ${intent.processed_count || 0} data points / Processing speed: ${intent.processing_speed || "~8 sources/s"} / Estimated completion: ${intent.estimated_completion || "calculating"}
Initializing agent...
`; } function renderStepsPreview(steps) { if (!steps || steps.length === 0) { return '
No steps yet
'; } return steps .slice(0, 3) .map( (step) => `
${escapeHtml(step.name)} ${step.note ? `${escapeHtml(step.note)}` : ""}
`, ) .join(""); } function renderProgressLog(logs) { if (!logs || logs.length === 0) { return '
●Waiting for activity...
'; } return logs .map( (log) => `
● ${escapeHtml(log.title)} β–Ά ${log.step_label ? `${escapeHtml(log.step_label)}` : ""}
${(log.sub_entries || []) .map( (sub) => `
● ${escapeHtml(sub.text)} Duration: ${sub.duration || "< 1 min"} ${sub.status === "complete" ? "βœ“" : ""}
`, ) .join("")}
`, ) .join(""); } function toggleLogEntry(header) { const entry = header.closest(".log-entry"); if (entry) { entry.classList.toggle("expanded"); } } function closeDetailPanel() { // Clean up ProgressPanel if active if (typeof ProgressPanel !== "undefined") { ProgressPanel.destroy(); } AutoTaskState.selectedIntentId = null; document.querySelectorAll(".intent-card").forEach((card) => { card.classList.remove("selected"); }); const panel = document.getElementById("intent-detail-panel"); if (panel) { panel.innerHTML = `
πŸ“‹

Select an intent to view details

`; } } function viewDetailedIntent(intentId) { selectIntent(intentId); } function togglePause(intentId) { const intent = AutoTaskState.intents.find((i) => i.id === intentId); if (!intent) return; const action = intent.status === "paused" ? "resume" : "pause"; fetch(`/api/autotask/${intentId}/${action}`, { method: "POST" }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast(`Intent ${action}d`, "success"); refreshIntents(); if (AutoTaskState.selectedIntentId === intentId) { loadIntentDetail(intentId); } } else { showToast(`Failed to ${action} intent`, "error"); } }) .catch((error) => { console.error(`Failed to ${action} intent:`, error); showToast(`Failed to ${action} intent`, "error"); }); } function formatTime(date) { return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); } function escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } function handleWebSocketMessage(data) { switch (data.type) { case "task_update": case "intent_update": updateIntentInList(data.task || data.intent); break; case "step_progress": updateStepProgress(data.taskId, data.step, data.progress); break; case "decision_required": showDecisionNotification(data.decision); break; case "approval_required": showApprovalNotification(data.approval); break; case "task_completed": case "intent_completed": onIntentCompleted(data.task || data.intent); break; case "task_failed": case "intent_failed": onIntentFailed(data.task || data.intent, data.error); break; case "stats_update": updateStatsFromData(data.stats); break; } } function updateIntentInList(intent) { const card = document.querySelector( `.intent-card[data-intent-id="${intent.id}"]`, ); if (card) { // Update progress const progressFill = card.querySelector(".progress-fill"); const progressSteps = card.querySelector(".progress-steps"); const progressPct = card.querySelector(".progress-percent"); if (progressFill) progressFill.style.width = `${intent.progress}%`; if (progressSteps) progressSteps.textContent = `${intent.current_step}/${intent.total_steps} Steps`; if (progressPct) progressPct.textContent = `${intent.progress}%`; // Update status indicator const statusIndicator = card.querySelector(".intent-status-indicator"); if (statusIndicator) { statusIndicator.className = `intent-status-indicator ${intent.status}`; } } } function onIntentCompleted(intent) { showToast(`Intent completed: ${intent.title || intent.id}`, "success"); updateIntentInList(intent); updateStats(); } function onIntentFailed(intent, error) { showToast(`Intent failed: ${intent.title || intent.id} - ${error}`, "error"); updateIntentInList(intent); updateStats(); } // ============================================================================= // EVENT LISTENERS // ============================================================================= function setupEventListeners() { // Intent form submission const intentForm = document.getElementById("intent-form"); if (intentForm) { intentForm.addEventListener("htmx:afterSwap", function (event) { if (event.detail.target.id === "compilation-result") { onCompilationComplete(event); } }); } // Task list updates const taskList = document.getElementById("task-list"); if (taskList) { taskList.addEventListener("htmx:afterSwap", function () { updateStats(); highlightPendingItems(); }); } // Expand log entries on details open document.addEventListener( "toggle", function (event) { if ( event.target.classList.contains("execution-log") && event.target.open ) { const taskId = event.target.closest(".autotask-item")?.dataset.taskId; if (taskId) { loadExecutionLogs(taskId); } } }, true, ); } function setupKeyboardShortcuts() { document.addEventListener("keydown", function (e) { // Alt + N: Focus on intent input if (e.altKey && e.key === "n") { e.preventDefault(); document.getElementById("intent-input")?.focus(); } // Alt + R: Refresh tasks if (e.altKey && e.key === "r") { e.preventDefault(); refreshTasks(); } // Escape: Close any open modal if (e.key === "Escape") { closeAllModals(); } // Alt + 1-4: Switch filters if (e.altKey && e.key >= "1" && e.key <= "4") { e.preventDefault(); const filters = ["all", "running", "approval", "decision"]; const index = parseInt(e.key) - 1; const tabs = document.querySelectorAll(".filter-tab"); if (tabs[index]) { tabs[index].click(); } } }); } // ============================================================================= // AUTO REFRESH // ============================================================================= function startAutoRefresh() { // Refresh every 5 seconds AutoTaskState.refreshInterval = setInterval(function () { if (!document.hidden) { updateStats(); } }, 5000); } function stopAutoRefresh() { if (AutoTaskState.refreshInterval) { clearInterval(AutoTaskState.refreshInterval); AutoTaskState.refreshInterval = null; } } // ============================================================================= // STATS MANAGEMENT // ============================================================================= function updateStats() { fetch("/api/autotask/stats") .then((response) => response.json()) .then((stats) => { updateStatsFromData(stats); }) .catch((error) => { console.error("Failed to fetch stats:", error); }); } function updateStatsFromData(stats) { // Sentient filter counts const countComplete = document.getElementById("count-complete"); const countActive = document.getElementById("count-active"); const countAwaiting = document.getElementById("count-awaiting"); const countPaused = document.getElementById("count-paused"); const countBlocked = document.getElementById("count-blocked"); const timeSaved = document.getElementById("time-saved"); if (countComplete) countComplete.textContent = stats.completed || 0; if (countActive) countActive.textContent = stats.running || stats.active || 0; if (countAwaiting) countAwaiting.textContent = stats.pending_decision || stats.awaiting || 0; if (countPaused) countPaused.textContent = stats.paused || 0; if (countBlocked) countBlocked.textContent = stats.blocked || stats.failed || 0; if (timeSaved) timeSaved.textContent = stats.time_saved || "0 hrs this week"; // Legacy support const statRunning = document.getElementById("stat-running"); const statPending = document.getElementById("stat-pending"); const statCompleted = document.getElementById("stat-completed"); const statApproval = document.getElementById("stat-approval"); if (statRunning) statRunning.textContent = stats.running || 0; if (statPending) statPending.textContent = stats.pending || 0; if (statCompleted) statCompleted.textContent = stats.completed || 0; if (statApproval) statApproval.textContent = stats.pending_approval || 0; } // ============================================================================= // TASK FILTERING // ============================================================================= function filterTasks(filter, button) { AutoTaskState.currentFilter = filter; // Update active tab document.querySelectorAll(".filter-tab").forEach((tab) => { tab.classList.remove("active"); }); button.classList.add("active"); // Trigger HTMX request htmx.ajax("GET", `/api/autotask/list?filter=${filter}`, { target: "#task-list", swap: "innerHTML", }); } // Sentient status filter function filterByStatus(status, button) { AutoTaskState.currentFilter = status; // Update active filter document.querySelectorAll(".status-filter").forEach((filter) => { filter.classList.remove("active"); }); button.classList.add("active"); // Trigger HTMX request for intent list htmx.ajax("GET", `/api/autotask/list?status=${status}`, { target: "#intent-list", swap: "innerHTML", }); } function refreshTasks() { const filter = AutoTaskState.currentFilter; htmx.ajax("GET", `/api/autotask/list?filter=${filter}`, { target: "#task-list", swap: "innerHTML", }); updateStats(); } function refreshIntents() { const status = AutoTaskState.currentFilter; htmx.ajax("GET", `/api/autotask/list?status=${status}`, { target: "#intent-list", swap: "innerHTML", }); updateStats(); } // ============================================================================= // MODAL FUNCTIONS - SENTIENT // ============================================================================= function showNewIntentModal() { const modal = document.getElementById("new-intent-modal"); if (modal) { modal.style.display = "flex"; document.body.classList.add("modal-open"); setTimeout(() => { document.getElementById("intent-input")?.focus(); }, 100); } } function closeNewIntentModal() { const modal = document.getElementById("new-intent-modal"); if (modal) { modal.style.display = "none"; document.body.classList.remove("modal-open"); } } function openDecisionModal(intentId) { const modal = document.getElementById("decision-modal"); if (!modal) return; modal.style.display = "flex"; document.body.classList.add("modal-open"); const content = document.getElementById("decision-content"); if (content) { content.innerHTML = `
Loading decision options...
`; } // Fetch decision details fetch(`/api/autotask/${intentId}/decisions`) .then((response) => response.json()) .then((decisions) => { renderDecisionContent(intentId, decisions); }) .catch((error) => { console.error("Failed to load decisions:", error); if (content) { content.innerHTML = `
⚠️

Failed to load decision options

`; } }); } function renderDecisionContent(intentId, decisions) { const content = document.getElementById("decision-content"); if (!content || !decisions || decisions.length === 0) { if (content) { content.innerHTML = `
βœ“

No pending decisions

`; } return; } const decision = decisions[0]; content.innerHTML = `

${escapeHtml(decision.title)}

${escapeHtml(decision.description)}

${decision.options .map( (opt, idx) => ` `, ) .join("")}
`; } function submitDecisionFromModal(intentId, decisionId) { const selectedOption = document.querySelector( 'input[name="decision_option"]:checked', )?.value; if (!selectedOption) { showToast("Please select an option", "warning"); return; } fetch(`/api/autotask/${intentId}/decide`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ decision_id: decisionId, option_id: selectedOption, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Decision submitted", "success"); closeDecisionModal(); refreshIntents(); if (AutoTaskState.selectedIntentId === intentId) { loadIntentDetail(intentId); } } else { showToast(`Failed: ${result.error || "Unknown error"}`, "error"); } }) .catch((error) => { console.error("Failed to submit decision:", error); showToast("Failed to submit decision", "error"); }); } function closeDecisionModal() { const modal = document.getElementById("decision-modal"); if (modal) { modal.style.display = "none"; document.body.classList.remove("modal-open"); } } function viewDecisionContext(intentId) { openDecisionModal(intentId); } function closeAllModals() { closeNewIntentModal(); closeDecisionModal(); document.body.classList.remove("modal-open"); } // ============================================================================= // COMPILATION HANDLING // ============================================================================= function onCompilationComplete(event) { const result = event.detail.target.querySelector(".compiled-plan"); if (result) { // Scroll to result result.scrollIntoView({ behavior: "smooth", block: "start" }); // Store compiled plan const planId = result.dataset?.planId; if (planId) { AutoTaskState.compiledPlan = planId; } // Syntax highlight the code highlightBasicCode(); } } function highlightBasicCode() { const codeBlocks = document.querySelectorAll(".code-preview code"); codeBlocks.forEach((block) => { // Basic syntax highlighting for BASIC keywords let html = block.innerHTML; // Keywords const keywords = [ "PLAN_START", "PLAN_END", "STEP", "SET", "GET", "IF", "THEN", "ELSE", "END IF", "FOR EACH", "NEXT", "WHILE", "WEND", "TALK", "HEAR", "LLM", "CREATE_TASK", "RUN_PYTHON", "RUN_JAVASCRIPT", "RUN_BASH", "USE_MCP", "POST", "GET", "PUT", "PATCH", "DELETE HTTP", "REQUIRE_APPROVAL", "SIMULATE_IMPACT", "AUDIT_LOG", "SEND_MAIL", "SAVE", "UPDATE", "INSERT", "DELETE", "FIND", ]; keywords.forEach((keyword) => { const regex = new RegExp(`\\b${keyword}\\b`, "g"); html = html.replace(regex, `${keyword}`); }); // Comments html = html.replace(/(\'[^\n]*)/g, '$1'); // Strings html = html.replace(/("[^"]*")/g, '$1'); // Numbers html = html.replace(/\b(\d+)\b/g, '$1'); block.innerHTML = html; }); } function copyGeneratedCode() { const code = document.querySelector(".code-preview code")?.textContent; if (code) { navigator.clipboard .writeText(code) .then(() => { showToast("Code copied to clipboard", "success"); }) .catch(() => { showToast("Failed to copy code", "error"); }); } } function discardPlan() { if (confirm("Are you sure you want to discard this plan?")) { document.getElementById("compilation-result").innerHTML = ""; AutoTaskState.compiledPlan = null; document.getElementById("intent-input").value = ""; document.getElementById("intent-input").focus(); } } function editPlan() { if (!AutoTaskState.compiledPlan) { showToast("No plan to edit", "warning"); return; } const modal = document.createElement("div"); modal.className = "modal-overlay"; modal.id = "plan-editor-modal"; modal.innerHTML = ` `; document.body.appendChild(modal); } function closePlanEditor() { const modal = document.getElementById("plan-editor-modal"); if (modal) { modal.remove(); } } function savePlanEdits() { const name = document.getElementById("plan-name").value; const description = document.getElementById("plan-description").value; const stepsJson = document.getElementById("plan-steps").value; const priority = document.getElementById("plan-priority").value; let steps; try { steps = JSON.parse(stepsJson); } catch (e) { showToast("Invalid JSON in steps", "error"); return; } AutoTaskState.compiledPlan = { ...AutoTaskState.compiledPlan, name: name, description: description, steps: steps, priority: priority, }; closePlanEditor(); showToast("Plan updated successfully", "success"); const resultDiv = document.getElementById("compilation-result"); if (resultDiv && AutoTaskState.compiledPlan) { renderCompiledPlan(AutoTaskState.compiledPlan); } } function renderCompiledPlan(plan) { const resultDiv = document.getElementById("compilation-result"); if (!resultDiv) return; const stepsHtml = (plan.steps || []) .map( (step, i) => `
${i + 1} ${step.action || step.type || "Action"} ${step.target || step.description || ""}
`, ) .join(""); resultDiv.innerHTML = `

${plan.name || "Compiled Plan"}

${plan.priority || "medium"}
${plan.description ? `

${plan.description}

` : ""}
${stepsHtml}
`; } // ============================================================================= // PLAN EXECUTION // ============================================================================= function simulatePlan(planId) { showSimulationModal(); fetch(`/api/autotask/simulate/${planId}`, { method: "POST", }) .then((response) => response.json()) .then((result) => { renderSimulationResult(result); }) .catch((error) => { document.getElementById("simulation-content").innerHTML = `
❌

Failed to simulate plan: ${error.message}

`; }); } function executePlan(planId) { const executionMode = document.querySelector('[name="execution_mode"]')?.value || "semi-automatic"; const priority = document.querySelector('[name="priority"]')?.value || "medium"; if (!confirm("Are you sure you want to execute this plan?")) { return; } fetch("/api/autotask/execute", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ plan_id: planId, execution_mode: executionMode, priority: priority, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Task execution started!", "success"); document.getElementById("compilation-result").innerHTML = ""; document.getElementById("intent-input").value = ""; refreshTasks(); } else { showToast(`Failed to start execution: ${result.error}`, "error"); } }) .catch((error) => { showToast(`Failed to execute plan: ${error.message}`, "error"); }); } // ============================================================================= // TASK ACTIONS // ============================================================================= function viewTaskDetails(taskId) { window.location.href = `/suite/tasks/detail/${taskId}`; } function simulateTask(taskId) { showSimulationModal(); fetch(`/api/autotask/${taskId}/simulate`, { method: "POST", }) .then((response) => response.json()) .then((result) => { result.task_id = taskId; renderSimulationResult(result); }) .catch((error) => { document.getElementById("simulation-content").innerHTML = `
❌

Failed to simulate task: ${error.message}

`; }); } function pauseTask(taskId) { fetch(`/api/autotask/${taskId}/pause`, { method: "POST", }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Task paused", "success"); refreshTasks(); } else { showToast(`Failed to pause task: ${result.error}`, "error"); } }); } function resumeTask(taskId) { fetch(`/api/autotask/${taskId}/resume`, { method: "POST", }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Task resumed", "success"); refreshTasks(); } else { showToast(`Failed to resume task: ${result.error}`, "error"); } }); } function cancelTask(taskId) { if ( !confirm( "Are you sure you want to cancel this task? This may not be reversible.", ) ) { return; } fetch(`/api/autotask/${taskId}/cancel`, { method: "POST", }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Task cancelled", "success"); refreshTasks(); } else { showToast(`Failed to cancel task: ${result.error}`, "error"); } }); } function updateTaskInList(task) { const taskElement = document.querySelector(`[data-task-id="${task.id}"]`); if (taskElement) { // Update status badge const statusBadge = taskElement.querySelector(".task-status-badge"); if (statusBadge) { statusBadge.className = `task-status-badge status-${task.status}`; statusBadge.textContent = task.status.replace(/-/g, " "); } // Update progress const progressFill = taskElement.querySelector(".progress-fill"); const progressText = taskElement.querySelector(".progress-text"); if (progressFill && progressText) { progressFill.style.width = `${task.progress}%`; progressText.textContent = `${task.current_step}/${task.total_steps} steps (${Math.round(task.progress)}%)`; } // Update data attribute taskElement.dataset.status = task.status; } } function updateStepProgress(taskId, step, progress) { const taskElement = document.querySelector(`[data-task-id="${taskId}"]`); if (taskElement) { const currentStep = taskElement.querySelector(".current-step"); if (currentStep) { currentStep.querySelector(".step-name").textContent = `Step ${step.order}: ${step.name}`; currentStep.querySelector(".step-status").textContent = `${Math.round(progress)}%`; } } } // ============================================================================= // DECISIONS // ============================================================================= function viewDecisions(taskId) { showDecisionModal(); fetch(`/api/autotask/${taskId}/decisions`) .then((response) => response.json()) .then((decisions) => { renderDecisions(taskId, decisions); }) .catch((error) => { document.getElementById("decision-content").innerHTML = `
❌

Failed to load decisions: ${error.message}

`; }); } function renderDecisions(taskId, decisions) { const container = document.getElementById("decision-content"); if (!decisions || decisions.length === 0) { container.innerHTML = '

No pending decisions.

'; return; } let html = '
'; decisions.forEach((decision) => { html += `

${decision.title}

${decision.description}

${decision.options .map( (opt) => `

${opt.description}

πŸ’° ${opt.estimated_impact.cost_change >= 0 ? "+" : ""}$${opt.estimated_impact.cost_change} ⏱️ ${opt.estimated_impact.time_change_minutes >= 0 ? "+" : ""}${opt.estimated_impact.time_change_minutes}m ⚠️ ${opt.risk_level}
`, ) .join("")}
`; }); html += "
"; container.innerHTML = html; } function submitDecision(taskId, decisionId) { const selectedOption = document.querySelector( `input[name="decision_${decisionId}"]:checked`, )?.value; if (!selectedOption) { showToast("Please select an option", "warning"); return; } fetch(`/api/autotask/${taskId}/decide`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ decision_id: decisionId, option_id: selectedOption, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Decision submitted", "success"); closeDecisionModal(); refreshTasks(); } else { showToast(`Failed to submit decision: ${result.error}`, "error"); } }); } function skipDecision(taskId, decisionId) { if ( !confirm( "Are you sure you want to skip this decision? The default option will be used.", ) ) { return; } fetch(`/api/autotask/${taskId}/decide`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ decision_id: decisionId, skip: true, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Decision skipped", "info"); closeDecisionModal(); refreshTasks(); } else { showToast(`Failed to skip decision: ${result.error}`, "error"); } }); } function showDecisionNotification(decision) { showToast(`Decision required: ${decision.title}`, "warning", 10000); updateStats(); } // ============================================================================= // APPROVALS // ============================================================================= function viewApprovals(taskId) { showApprovalModal(); fetch(`/api/autotask/${taskId}/approvals`) .then((response) => response.json()) .then((approvals) => { renderApprovals(taskId, approvals); }) .catch((error) => { document.getElementById("approval-content").innerHTML = `
❌

Failed to load approvals: ${error.message}

`; }); } function renderApprovals(taskId, approvals) { const container = document.getElementById("approval-content"); if (!approvals || approvals.length === 0) { container.innerHTML = '

No pending approvals.

'; return; } let html = '
'; approvals.forEach((approval) => { html += `
${approval.approval_type.replace(/_/g, " ")} ${approval.risk_level} Risk

${approval.title}

${approval.description}

Impact Summary

${approval.impact_summary}

${ approval.simulation_result ? `
Simulation Result
Risk: ${approval.simulation_result.risk_level} Confidence: ${Math.round(approval.simulation_result.confidence * 100)}%
` : "" }
Step: ${approval.step_name || "N/A"} Expires: ${formatRelativeTime(approval.expires_at)} Default: ${approval.default_action}
`; }); html += "
"; container.innerHTML = html; } function approveApproval(taskId, approvalId) { submitApprovalDecision(taskId, approvalId, "approve"); } function rejectApproval(taskId, approvalId) { if (!confirm("Are you sure you want to reject this action?")) { return; } submitApprovalDecision(taskId, approvalId, "reject"); } function deferApproval(taskId, approvalId) { submitApprovalDecision(taskId, approvalId, "defer"); } function submitApprovalDecision(taskId, approvalId, action) { fetch(`/api/autotask/${taskId}/approve`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ approval_id: approvalId, action: action, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { const messages = { approve: "Approval granted", reject: "Approval rejected", defer: "Approval deferred", }; showToast(messages[action], "success"); closeApprovalModal(); refreshTasks(); } else { showToast(`Failed to ${action}: ${result.error}`, "error"); } }); } function showApprovalNotification(approval) { showToast(`Approval required: ${approval.title}`, "warning", 10000); updateStats(); } // ============================================================================= // SIMULATION // ============================================================================= function renderSimulationResult(result) { const container = document.getElementById("simulation-content"); const statusIcon = result.success ? "βœ…" : "⚠️"; const statusText = result.success ? "Simulation Successful" : "Simulation Found Issues"; let html = `
${statusIcon} ${statusText}
Confidence: ${Math.round(result.confidence * 100)}%

Impact Assessment

πŸ’Ύ Data Impact ${result.impact.data_impact.records_modified} records modified
πŸ’° Cost Impact $${result.impact.cost_impact.total_estimated_cost.toFixed(2)}
⏱️ Time Impact ${formatDuration(result.impact.time_impact.estimated_duration_seconds)}
πŸ”’ Security Impact ${result.impact.security_impact.risk_level}

Step-by-Step Predictions

${result.step_outcomes .map( (step) => `
${step.would_succeed ? "βœ…" : "⚠️"} ${step.step_name} ${Math.round(step.success_probability * 100)}% success
`, ) .join("")}
${ result.side_effects.length > 0 ? `

⚠️ Potential Side Effects

${result.side_effects .map( (effect) => `
${effect.description} ${effect.mitigation ? `Mitigation: ${effect.mitigation}` : ""}
`, ) .join("")}
` : "" } ${ result.recommendations.length > 0 ? `

πŸ’‘ Recommendations

${result.recommendations .map( (rec) => `
${rec.description} ${rec.action ? `` : ""}
`, ) .join("")}
` : "" }
`; container.innerHTML = html; } // ============================================================================= // MODAL FUNCTIONS // ============================================================================= function showSimulationModal() { const modal = document.getElementById("simulation-modal"); if (modal) { modal.style.display = "flex"; document.body.classList.add("modal-open"); // Show loading state document.getElementById("simulation-content").innerHTML = `

Running impact simulation...

`; } } function closeSimulationModal() { const modal = document.getElementById("simulation-modal"); if (modal) { modal.style.display = "none"; document.body.classList.remove("modal-open"); } } function showDecisionModal() { const modal = document.getElementById("decision-modal"); if (modal) { modal.style.display = "flex"; document.body.classList.add("modal-open"); // Show loading state document.getElementById("decision-content").innerHTML = `

Loading decisions...

`; } } function closeDecisionModal() { const modal = document.getElementById("decision-modal"); if (modal) { modal.style.display = "none"; document.body.classList.remove("modal-open"); } } function showApprovalModal() { const modal = document.getElementById("approval-modal"); if (modal) { modal.style.display = "flex"; document.body.classList.add("modal-open"); // Show loading state document.getElementById("approval-content").innerHTML = `

Loading approvals...

`; } } function closeApprovalModal() { const modal = document.getElementById("approval-modal"); if (modal) { modal.style.display = "none"; document.body.classList.remove("modal-open"); } } function closeAllModals() { closeSimulationModal(); closeDecisionModal(); closeApprovalModal(); } // ============================================================================= // SIMULATION ACTIONS // ============================================================================= function proceedAfterSimulation(taskId) { closeSimulationModal(); if (!taskId) { showToast("No task ID provided", "error"); return; } fetch(`/api/autotask/${taskId}/execute`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ confirmed: true, }), }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Task execution started!", "success"); refreshTasks(); } else { showToast(result.error || "Failed to start execution", "error"); } }) .catch((error) => { console.error("Failed to proceed after simulation:", error); showToast("Failed to start execution", "error"); }); } function applyRecommendation(recId) { fetch(`/api/autotask/recommendations/${recId}/apply`, { method: "POST", }) .then((response) => response.json()) .then((result) => { if (result.success) { showToast("Recommendation applied", "success"); // Re-run simulation to show updated results const taskId = document.querySelector(".simulation-result")?.dataset?.taskId; if (taskId) { simulateTask(taskId); } } else { showToast(result.error || "Failed to apply recommendation", "error"); } }) .catch((error) => { console.error("Failed to apply recommendation:", error); showToast("Failed to apply recommendation", "error"); }); } // ============================================================================= // TOAST NOTIFICATIONS // ============================================================================= function showToast(message, type = "info") { // Get or create toast container let container = document.getElementById("toast-container"); if (!container) { container = document.createElement("div"); container.id = "toast-container"; container.className = "toast-container"; document.body.appendChild(container); } // Create toast element const toast = document.createElement("div"); toast.className = `toast toast-${type}`; const icons = { success: "βœ…", error: "❌", warning: "⚠️", info: "ℹ️", }; toast.innerHTML = ` ${icons[type] || icons.info} ${message} `; container.appendChild(toast); // Auto-remove after 5 seconds setTimeout(() => { toast.classList.add("toast-fade-out"); setTimeout(() => { toast.remove(); }, 300); }, 5000); } // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= function formatDuration(seconds) { if (seconds < 60) { return `${Math.round(seconds)}s`; } else if (seconds < 3600) { const minutes = Math.floor(seconds / 60); const secs = Math.round(seconds % 60); return `${minutes}m ${secs}s`; } else { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours}h ${minutes}m`; } } function formatRelativeTime(dateString) { if (!dateString) return "N/A"; const date = new Date(dateString); const now = new Date(); const diffMs = date - now; const diffSeconds = Math.floor(diffMs / 1000); const diffMinutes = Math.floor(diffSeconds / 60); const diffHours = Math.floor(diffMinutes / 60); const diffDays = Math.floor(diffHours / 24); if (diffMs < 0) { // Past const absDays = Math.abs(diffDays); const absHours = Math.abs(diffHours); const absMinutes = Math.abs(diffMinutes); if (absDays > 0) return `${absDays}d ago`; if (absHours > 0) return `${absHours}h ago`; if (absMinutes > 0) return `${absMinutes}m ago`; return "just now"; } else { // Future if (diffDays > 0) return `in ${diffDays}d`; if (diffHours > 0) return `in ${diffHours}h`; if (diffMinutes > 0) return `in ${diffMinutes}m`; return "soon"; } } // ============================================================================= // TASK LIFECYCLE HANDLERS // ============================================================================= function onTaskCompleted(task) { showToast(`Task completed: ${task.title || task.id}`, "success"); updateTaskInList(task); updateStats(); } function onTaskFailed(task, error) { showToast(`Task failed: ${task.title || task.id} - ${error}`, "error"); updateTaskInList(task); updateStats(); } function highlightPendingItems() { // Highlight tasks requiring attention document.querySelectorAll(".autotask-item").forEach((item) => { const status = item.dataset.status; if (status === "pending-approval" || status === "pending-decision") { item.classList.add("attention-required"); } else { item.classList.remove("attention-required"); } }); } function loadExecutionLogs(taskId) { const logContainer = document.querySelector( `[data-task-id="${taskId}"] .log-entries`, ); if (!logContainer || logContainer.dataset.loaded === "true") { return; } logContainer.innerHTML = `

Loading logs...

`; fetch(`/api/autotask/${taskId}/logs`) .then((response) => response.json()) .then((logs) => { if (!logs || logs.length === 0) { logContainer.innerHTML = "

No execution logs yet.

"; } else { logContainer.innerHTML = logs .map( (log) => `
${new Date(log.timestamp).toLocaleTimeString()} ${log.level} ${log.message}
`, ) .join(""); } logContainer.dataset.loaded = "true"; }) .catch((error) => { logContainer.innerHTML = `
❌

Failed to load logs: ${error.message}

`; }); }