/* =============================================================================
AUTO TASK JAVASCRIPT - Intelligent Self-Executing Task Interface
Premium VIP Mode Functionality
============================================================================= */
// =============================================================================
// STATE MANAGEMENT
// =============================================================================
const AutoTaskState = {
currentFilter: "all",
tasks: [],
compiledPlan: null,
pendingDecisions: [],
pendingApprovals: [],
refreshInterval: null,
wsConnection: null,
};
// =============================================================================
// INITIALIZATION
// =============================================================================
document.addEventListener("DOMContentLoaded", function () {
initAutoTask();
});
function initAutoTask() {
// Initialize WebSocket for real-time updates
initWebSocket();
// Start auto-refresh
startAutoRefresh();
// Setup event listeners
setupEventListeners();
// Load initial stats
updateStats();
// Setup keyboard shortcuts
setupKeyboardShortcuts();
console.log("AutoTask initialized");
}
// =============================================================================
// 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 handleWebSocketMessage(data) {
switch (data.type) {
case "task_update":
updateTaskInList(data.task);
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":
onTaskCompleted(data.task);
break;
case "task_failed":
onTaskFailed(data.task, data.error);
break;
case "stats_update":
updateStatsFromData(data.stats);
break;
}
}
// =============================================================================
// 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) {
// Header stats
document.getElementById("stat-running").textContent = stats.running || 0;
document.getElementById("stat-pending").textContent = stats.pending || 0;
document.getElementById("stat-completed").textContent = stats.completed || 0;
document.getElementById("stat-approval").textContent =
stats.pending_approval || 0;
// Filter counts
document.getElementById("count-all").textContent = stats.total || 0;
document.getElementById("count-running").textContent = stats.running || 0;
document.getElementById("count-approval").textContent =
stats.pending_approval || 0;
document.getElementById("count-decision").textContent =
stats.pending_decision || 0;
// Highlight if approvals needed
const approvalStat = document.querySelector(".stat-item.highlight");
if (approvalStat && stats.pending_approval > 0) {
approvalStat.classList.add("attention");
} else if (approvalStat) {
approvalStat.classList.remove("attention");
}
}
// =============================================================================
// 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",
});
}
function refreshTasks() {
const filter = AutoTaskState.currentFilter;
htmx.ajax("GET", `/api/autotask/list?filter=${filter}`, {
target: "#task-list",
swap: "innerHTML",
});
updateStats();
}
// =============================================================================
// 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, '');
// 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.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.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 = `
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 = `
`;
}
}
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 = `
`;
}
}
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 = `
`;
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}
`;
});
}