2025-12-15 16:33:23 -03:00
|
|
|
|
/* =============================================================================
|
|
|
|
|
|
TASKS APP JAVASCRIPT
|
|
|
|
|
|
Automated Intelligent Task Management Interface
|
|
|
|
|
|
============================================================================= */
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// STATE MANAGEMENT
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
2025-12-15 23:16:09 -03:00
|
|
|
|
// Prevent duplicate declaration when script is reloaded via HTMX
|
|
|
|
|
|
if (typeof TasksState === "undefined") {
|
|
|
|
|
|
var TasksState = {
|
|
|
|
|
|
selectedTaskId: 2, // Default selected task
|
|
|
|
|
|
currentFilter: "complete",
|
|
|
|
|
|
tasks: [],
|
|
|
|
|
|
wsConnection: null,
|
|
|
|
|
|
agentLogPaused: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-12-15 16:33:23 -03:00
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// INITIALIZATION
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
|
|
initTasksApp();
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
function initTasksApp() {
|
|
|
|
|
|
// Initialize WebSocket for real-time updates
|
|
|
|
|
|
initWebSocket();
|
|
|
|
|
|
|
|
|
|
|
|
// Setup event listeners
|
|
|
|
|
|
setupEventListeners();
|
|
|
|
|
|
|
|
|
|
|
|
// Setup keyboard shortcuts
|
|
|
|
|
|
setupKeyboardShortcuts();
|
|
|
|
|
|
|
|
|
|
|
|
// Auto-scroll agent log to bottom
|
|
|
|
|
|
scrollAgentLogToBottom();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("[Tasks] Initialized");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// WEBSOCKET CONNECTION
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function initWebSocket() {
|
|
|
|
|
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
|
|
|
|
const wsUrl = `${protocol}//${window.location.host}/ws/tasks`;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
TasksState.wsConnection = new WebSocket(wsUrl);
|
|
|
|
|
|
|
|
|
|
|
|
TasksState.wsConnection.onopen = function () {
|
|
|
|
|
|
console.log("[Sentient Tasks] WebSocket connected");
|
|
|
|
|
|
addAgentLog("info", "[SYSTEM] Connected to task orchestrator");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
TasksState.wsConnection.onmessage = function (event) {
|
|
|
|
|
|
handleWebSocketMessage(JSON.parse(event.data));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
TasksState.wsConnection.onclose = function () {
|
|
|
|
|
|
console.log("[Sentient Tasks] WebSocket disconnected, reconnecting...");
|
|
|
|
|
|
setTimeout(initWebSocket, 5000);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
TasksState.wsConnection.onerror = function (error) {
|
|
|
|
|
|
console.error("[Sentient Tasks] WebSocket error:", error);
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("[Sentient Tasks] WebSocket not available");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleWebSocketMessage(data) {
|
|
|
|
|
|
switch (data.type) {
|
|
|
|
|
|
case "task_update":
|
|
|
|
|
|
updateTaskCard(data.task);
|
|
|
|
|
|
if (data.task.id === TasksState.selectedTaskId) {
|
|
|
|
|
|
updateTaskDetail(data.task);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "step_progress":
|
|
|
|
|
|
updateStepProgress(data.taskId, data.step);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "agent_log":
|
|
|
|
|
|
addAgentLog(data.level, data.message);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "decision_required":
|
|
|
|
|
|
showDecisionRequired(data.decision);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "task_completed":
|
|
|
|
|
|
onTaskCompleted(data.task);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "task_failed":
|
|
|
|
|
|
onTaskFailed(data.task, data.error);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// EVENT LISTENERS
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function setupEventListeners() {
|
|
|
|
|
|
// Filter pills
|
|
|
|
|
|
document.querySelectorAll(".status-pill").forEach((pill) => {
|
|
|
|
|
|
pill.addEventListener("click", function (e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const filter = this.dataset.filter;
|
|
|
|
|
|
setActiveFilter(filter, this);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Search input
|
|
|
|
|
|
const searchInput = document.querySelector(".topbar-search-input");
|
|
|
|
|
|
if (searchInput) {
|
|
|
|
|
|
searchInput.addEventListener(
|
|
|
|
|
|
"input",
|
|
|
|
|
|
debounce(function (e) {
|
|
|
|
|
|
searchTasks(e.target.value);
|
|
|
|
|
|
}, 300),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Nav items
|
|
|
|
|
|
document.querySelectorAll(".topbar-nav-item").forEach((item) => {
|
|
|
|
|
|
item.addEventListener("click", function () {
|
|
|
|
|
|
document
|
|
|
|
|
|
.querySelectorAll(".topbar-nav-item")
|
|
|
|
|
|
.forEach((i) => i.classList.remove("active"));
|
|
|
|
|
|
this.classList.add("active");
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Progress log toggle
|
|
|
|
|
|
const logToggle = document.querySelector(".progress-log-toggle");
|
|
|
|
|
|
if (logToggle) {
|
|
|
|
|
|
logToggle.addEventListener("click", toggleProgressLog);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setupKeyboardShortcuts() {
|
|
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
|
|
|
|
// Escape: Deselect task
|
|
|
|
|
|
if (e.key === "Escape") {
|
|
|
|
|
|
deselectTask();
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// Cmd/Ctrl + K: Focus search
|
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
document.querySelector(".topbar-search-input")?.focus();
|
|
|
|
|
|
}
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// Arrow keys: Navigate tasks
|
|
|
|
|
|
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
navigateTasks(e.key === "ArrowDown" ? 1 : -1);
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// Enter: Submit decision if in decision mode
|
|
|
|
|
|
if (
|
|
|
|
|
|
e.key === "Enter" &&
|
|
|
|
|
|
document.querySelector(".decision-option.selected")
|
|
|
|
|
|
) {
|
|
|
|
|
|
submitDecision();
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// 1-5: Quick filter
|
|
|
|
|
|
if (e.key >= "1" && e.key <= "5" && !e.target.matches("input, textarea")) {
|
|
|
|
|
|
const pills = document.querySelectorAll(".status-pill");
|
|
|
|
|
|
const index = parseInt(e.key) - 1;
|
|
|
|
|
|
if (pills[index]) {
|
|
|
|
|
|
pills[index].click();
|
|
|
|
|
|
}
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
2025-12-15 16:33:23 -03:00
|
|
|
|
});
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// TASK SELECTION & FILTERING
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function selectTask(taskId) {
|
|
|
|
|
|
TasksState.selectedTaskId = taskId;
|
|
|
|
|
|
|
|
|
|
|
|
// Update selected state in list
|
|
|
|
|
|
document.querySelectorAll(".task-card").forEach((card) => {
|
|
|
|
|
|
card.classList.toggle("selected", card.dataset.taskId == taskId);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Load task details (in real app, this would fetch from API)
|
|
|
|
|
|
loadTaskDetails(taskId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function deselectTask() {
|
|
|
|
|
|
TasksState.selectedTaskId = null;
|
|
|
|
|
|
document.querySelectorAll(".task-card").forEach((card) => {
|
|
|
|
|
|
card.classList.remove("selected");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function navigateTasks(direction) {
|
|
|
|
|
|
const cards = Array.from(document.querySelectorAll(".task-card"));
|
|
|
|
|
|
if (cards.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentIndex = cards.findIndex((c) => c.classList.contains("selected"));
|
|
|
|
|
|
let newIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if (currentIndex === -1) {
|
|
|
|
|
|
newIndex = direction === 1 ? 0 : cards.length - 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newIndex = currentIndex + direction;
|
|
|
|
|
|
if (newIndex < 0) newIndex = cards.length - 1;
|
|
|
|
|
|
if (newIndex >= cards.length) newIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const taskId = cards[newIndex].dataset.taskId;
|
|
|
|
|
|
selectTask(taskId);
|
|
|
|
|
|
cards[newIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setActiveFilter(filter, button) {
|
|
|
|
|
|
TasksState.currentFilter = filter;
|
|
|
|
|
|
|
|
|
|
|
|
// Update active pill
|
|
|
|
|
|
document.querySelectorAll(".status-pill").forEach((pill) => {
|
|
|
|
|
|
pill.classList.remove("active");
|
|
|
|
|
|
});
|
|
|
|
|
|
button.classList.add("active");
|
|
|
|
|
|
|
|
|
|
|
|
// Filter will be handled by HTMX, but we track state
|
|
|
|
|
|
addAgentLog("info", `[FILTER] Showing ${filter} tasks`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function searchTasks(query) {
|
|
|
|
|
|
if (query.length > 0) {
|
|
|
|
|
|
addAgentLog("info", `[SEARCH] Searching: "${query}"`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// In real app, this would filter via API
|
|
|
|
|
|
// For demo, we'll do client-side filtering
|
|
|
|
|
|
const cards = document.querySelectorAll(".task-card");
|
|
|
|
|
|
cards.forEach((card) => {
|
|
|
|
|
|
const title =
|
|
|
|
|
|
card.querySelector(".task-card-title")?.textContent.toLowerCase() || "";
|
|
|
|
|
|
const subtitle =
|
|
|
|
|
|
card.querySelector(".task-card-subtitle")?.textContent.toLowerCase() ||
|
|
|
|
|
|
"";
|
|
|
|
|
|
const matches =
|
|
|
|
|
|
title.includes(query.toLowerCase()) ||
|
|
|
|
|
|
subtitle.includes(query.toLowerCase());
|
|
|
|
|
|
card.style.display = matches || query === "" ? "block" : "none";
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// TASK DETAILS
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function loadTaskDetails(taskId) {
|
|
|
|
|
|
// In real app, fetch from API
|
|
|
|
|
|
// htmx.ajax('GET', `/api/tasks/${taskId}`, {target: '#task-detail-panel', swap: 'innerHTML'});
|
|
|
|
|
|
|
|
|
|
|
|
addAgentLog("info", `[LOAD] Loading task #${taskId} details`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateTaskCard(task) {
|
|
|
|
|
|
const card = document.querySelector(`[data-task-id="${task.id}"]`);
|
|
|
|
|
|
if (!card) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Update progress
|
|
|
|
|
|
const progressFill = card.querySelector(".task-progress-fill");
|
|
|
|
|
|
const progressPercent = card.querySelector(".task-progress-percent");
|
|
|
|
|
|
const progressSteps = card.querySelector(".task-progress-steps");
|
|
|
|
|
|
|
|
|
|
|
|
if (progressFill) progressFill.style.width = `${task.progress}%`;
|
|
|
|
|
|
if (progressPercent) progressPercent.textContent = `${task.progress}%`;
|
|
|
|
|
|
if (progressSteps)
|
|
|
|
|
|
progressSteps.textContent = `${task.currentStep}/${task.totalSteps} steps`;
|
|
|
|
|
|
|
|
|
|
|
|
// Update status badge
|
|
|
|
|
|
const statusBadge = card.querySelector(".task-card-status");
|
|
|
|
|
|
if (statusBadge) {
|
|
|
|
|
|
statusBadge.className = `task-card-status ${task.status}`;
|
|
|
|
|
|
statusBadge.textContent = formatStatus(task.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateTaskDetail(task) {
|
|
|
|
|
|
// Update detail panel with task data
|
|
|
|
|
|
const detailTitle = document.querySelector(".task-detail-title");
|
|
|
|
|
|
if (detailTitle) detailTitle.textContent = task.title;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// DECISION HANDLING
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function selectDecision(element, value) {
|
|
|
|
|
|
// Remove selected from all options
|
|
|
|
|
|
document.querySelectorAll(".decision-option").forEach((opt) => {
|
|
|
|
|
|
opt.classList.remove("selected");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Add selected to clicked option
|
|
|
|
|
|
element.classList.add("selected");
|
|
|
|
|
|
|
|
|
|
|
|
// Store selected value
|
|
|
|
|
|
TasksState.selectedDecision = value;
|
|
|
|
|
|
|
|
|
|
|
|
addAgentLog("info", `[DECISION] Selected: ${value}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function submitDecision() {
|
|
|
|
|
|
const selectedOption = document.querySelector(".decision-option.selected");
|
|
|
|
|
|
if (!selectedOption) {
|
|
|
|
|
|
showToast("Please select an option", "warning");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const value = TasksState.selectedDecision;
|
|
|
|
|
|
const taskId = TasksState.selectedTaskId;
|
|
|
|
|
|
|
|
|
|
|
|
addAgentLog("accent", `[AGENT] Applying decision: ${value}`);
|
|
|
|
|
|
addAgentLog("info", `[TASK] Resuming task #${taskId}...`);
|
|
|
|
|
|
|
|
|
|
|
|
// In real app, send to API
|
|
|
|
|
|
fetch(`/api/tasks/${taskId}/decide`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
|
body: JSON.stringify({ decision: value }),
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((result) => {
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
showToast("Decision applied successfully", "success");
|
|
|
|
|
|
addAgentLog("success", `[OK] Decision applied, task resuming`);
|
|
|
|
|
|
|
|
|
|
|
|
// Hide decision section (in real app, would update via HTMX)
|
|
|
|
|
|
const decisionSection = document.querySelector(
|
|
|
|
|
|
".decision-required-section",
|
|
|
|
|
|
);
|
|
|
|
|
|
if (decisionSection) {
|
|
|
|
|
|
decisionSection.style.display = "none";
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showToast("Failed to apply decision", "error");
|
|
|
|
|
|
addAgentLog(
|
|
|
|
|
|
"error",
|
|
|
|
|
|
`[ERROR] Failed to apply decision: ${result.error}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
// For demo, simulate success
|
|
|
|
|
|
showToast("Decision applied successfully", "success");
|
|
|
|
|
|
addAgentLog("success", `[OK] Decision applied, task resuming`);
|
|
|
|
|
|
|
|
|
|
|
|
const decisionSection = document.querySelector(
|
|
|
|
|
|
".decision-required-section",
|
|
|
|
|
|
);
|
|
|
|
|
|
if (decisionSection) {
|
|
|
|
|
|
decisionSection.style.opacity = "0.5";
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
decisionSection.style.display = "none";
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update step status
|
|
|
|
|
|
const activeStep = document.querySelector(".step-item.active");
|
|
|
|
|
|
if (activeStep) {
|
|
|
|
|
|
activeStep.classList.remove("active");
|
|
|
|
|
|
activeStep.classList.add("completed");
|
|
|
|
|
|
activeStep.querySelector(".step-icon").textContent = "✓";
|
|
|
|
|
|
activeStep.querySelector(".step-detail").textContent =
|
|
|
|
|
|
"Completed with merge strategy";
|
|
|
|
|
|
|
|
|
|
|
|
const nextStep = activeStep.nextElementSibling;
|
|
|
|
|
|
if (nextStep && nextStep.classList.contains("pending")) {
|
|
|
|
|
|
nextStep.classList.remove("pending");
|
|
|
|
|
|
nextStep.classList.add("active");
|
|
|
|
|
|
nextStep.querySelector(".step-icon").textContent = "●";
|
|
|
|
|
|
nextStep.querySelector(".step-time").textContent = "Now";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showDecisionRequired(decision) {
|
|
|
|
|
|
addAgentLog("warning", `[ALERT] Decision required: ${decision.title}`);
|
|
|
|
|
|
showToast(`Decision required: ${decision.title}`, "warning");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// PROGRESS LOG
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function toggleProgressLog() {
|
|
|
|
|
|
const stepList = document.querySelector(".step-list");
|
|
|
|
|
|
const toggle = document.querySelector(".progress-log-toggle");
|
|
|
|
|
|
|
|
|
|
|
|
if (stepList.style.display === "none") {
|
|
|
|
|
|
stepList.style.display = "flex";
|
|
|
|
|
|
toggle.textContent = "Collapse";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
stepList.style.display = "none";
|
|
|
|
|
|
toggle.textContent = "Expand";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateStepProgress(taskId, step) {
|
|
|
|
|
|
if (taskId !== TasksState.selectedTaskId) return;
|
|
|
|
|
|
|
|
|
|
|
|
const stepItems = document.querySelectorAll(".step-item");
|
|
|
|
|
|
stepItems.forEach((item, index) => {
|
|
|
|
|
|
if (index < step.index) {
|
|
|
|
|
|
item.classList.remove("active", "pending");
|
|
|
|
|
|
item.classList.add("completed");
|
|
|
|
|
|
item.querySelector(".step-icon").textContent = "✓";
|
|
|
|
|
|
} else if (index === step.index) {
|
|
|
|
|
|
item.classList.remove("completed", "pending");
|
|
|
|
|
|
item.classList.add("active");
|
|
|
|
|
|
item.querySelector(".step-icon").textContent = "●";
|
|
|
|
|
|
item.querySelector(".step-name").textContent = step.name;
|
|
|
|
|
|
item.querySelector(".step-detail").textContent = step.detail;
|
|
|
|
|
|
item.querySelector(".step-time").textContent = "Now";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.classList.remove("completed", "active");
|
|
|
|
|
|
item.classList.add("pending");
|
|
|
|
|
|
item.querySelector(".step-icon").textContent = "○";
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
2025-12-15 16:33:23 -03:00
|
|
|
|
});
|
|
|
|
|
|
}
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// AGENT ACTIVITY LOG
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function addAgentLog(level, message) {
|
|
|
|
|
|
if (TasksState.agentLogPaused) return;
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
const log = document.getElementById("agent-log");
|
|
|
|
|
|
if (!log) return;
|
|
|
|
|
|
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const timestamp = now.toTimeString().split(" ")[0].substring(0, 8);
|
|
|
|
|
|
|
|
|
|
|
|
const line = document.createElement("div");
|
|
|
|
|
|
line.className = `activity-line ${level}`;
|
|
|
|
|
|
line.innerHTML = `
|
|
|
|
|
|
<span class="activity-timestamp">${timestamp}</span>
|
|
|
|
|
|
<span class="activity-message">${message}</span>
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
// Insert at the top
|
|
|
|
|
|
log.insertBefore(line, log.firstChild);
|
|
|
|
|
|
|
|
|
|
|
|
// Limit log entries
|
|
|
|
|
|
while (log.children.length > 100) {
|
|
|
|
|
|
log.removeChild(log.lastChild);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scrollAgentLogToBottom() {
|
|
|
|
|
|
const log = document.getElementById("agent-log");
|
|
|
|
|
|
if (log) {
|
|
|
|
|
|
log.scrollTop = 0; // Since newest is at top
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearAgentLog() {
|
|
|
|
|
|
const log = document.getElementById("agent-log");
|
|
|
|
|
|
if (log) {
|
|
|
|
|
|
log.innerHTML = "";
|
|
|
|
|
|
addAgentLog("info", "[SYSTEM] Log cleared");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleAgentLogPause() {
|
|
|
|
|
|
TasksState.agentLogPaused = !TasksState.agentLogPaused;
|
|
|
|
|
|
const pauseBtn = document.querySelector(".agent-activity-btn:last-child");
|
|
|
|
|
|
if (pauseBtn) {
|
|
|
|
|
|
pauseBtn.textContent = TasksState.agentLogPaused ? "Resume" : "Pause";
|
|
|
|
|
|
}
|
|
|
|
|
|
addAgentLog(
|
|
|
|
|
|
"info",
|
|
|
|
|
|
TasksState.agentLogPaused ? "[SYSTEM] Log paused" : "[SYSTEM] Log resumed",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// TASK LIFECYCLE
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function onTaskCompleted(task) {
|
|
|
|
|
|
showToast(`Task completed: ${task.title}`, "success");
|
|
|
|
|
|
addAgentLog("success", `[COMPLETE] Task #${task.id}: ${task.title}`);
|
|
|
|
|
|
updateTaskCard(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onTaskFailed(task, error) {
|
|
|
|
|
|
showToast(`Task failed: ${task.title}`, "error");
|
|
|
|
|
|
addAgentLog("error", `[FAILED] Task #${task.id}: ${error}`);
|
|
|
|
|
|
updateTaskCard(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// TOAST NOTIFICATIONS
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function showToast(message, type = "info") {
|
|
|
|
|
|
let container = document.getElementById("toast-container");
|
|
|
|
|
|
if (!container) {
|
|
|
|
|
|
container = document.createElement("div");
|
|
|
|
|
|
container.id = "toast-container";
|
|
|
|
|
|
container.style.cssText = `
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 24px;
|
|
|
|
|
|
right: 24px;
|
|
|
|
|
|
z-index: 10000;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
`;
|
|
|
|
|
|
document.body.appendChild(container);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const toast = document.createElement("div");
|
|
|
|
|
|
const bgColors = {
|
|
|
|
|
|
success: "rgba(34, 197, 94, 0.95)",
|
|
|
|
|
|
error: "rgba(239, 68, 68, 0.95)",
|
|
|
|
|
|
warning: "rgba(245, 158, 11, 0.95)",
|
|
|
|
|
|
info: "rgba(59, 130, 246, 0.95)",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const icons = {
|
|
|
|
|
|
success: "✓",
|
|
|
|
|
|
error: "✕",
|
|
|
|
|
|
warning: "⚠",
|
|
|
|
|
|
info: "ℹ",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
toast.style.cssText = `
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
background: ${bgColors[type] || bgColors.info};
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
animation: slideIn 0.3s ease;
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
toast.innerHTML = `
|
|
|
|
|
|
<span style="font-size: 16px;">${icons[type] || icons.info}</span>
|
|
|
|
|
|
<span>${message}</span>
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
container.appendChild(toast);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
toast.style.animation = "fadeOut 0.3s ease forwards";
|
|
|
|
|
|
setTimeout(() => toast.remove(), 300);
|
|
|
|
|
|
}, 4000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// UTILITY FUNCTIONS
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
function debounce(func, wait) {
|
|
|
|
|
|
let timeout;
|
|
|
|
|
|
return function executedFunction(...args) {
|
|
|
|
|
|
const later = () => {
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
func(...args);
|
|
|
|
|
|
};
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
timeout = setTimeout(later, wait);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatStatus(status) {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
complete: "Complete",
|
|
|
|
|
|
running: "Running",
|
|
|
|
|
|
awaiting: "Awaiting",
|
|
|
|
|
|
paused: "Paused",
|
|
|
|
|
|
blocked: "Blocked",
|
|
|
|
|
|
};
|
|
|
|
|
|
return statusMap[status] || status;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatTime(seconds) {
|
|
|
|
|
|
if (seconds < 60) return `${seconds}s`;
|
|
|
|
|
|
if (seconds < 3600) {
|
|
|
|
|
|
const mins = Math.floor(seconds / 60);
|
|
|
|
|
|
return `${mins}m`;
|
|
|
|
|
|
}
|
|
|
|
|
|
const hours = Math.floor(seconds / 3600);
|
|
|
|
|
|
const mins = Math.floor((seconds % 3600) / 60);
|
|
|
|
|
|
return `${hours}h ${mins}m`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// GLOBAL STYLES FOR TOAST ANIMATIONS
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const style = document.createElement("style");
|
|
|
|
|
|
style.textContent = `
|
|
|
|
|
|
@keyframes slideIn {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(20px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 16:33:23 -03:00
|
|
|
|
@keyframes fadeOut {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(20px);
|
refactor: Extract inline CSS/JS to separate files for monitoring module
- Create individual CSS files: monitoring.css, alerts.css, health.css, logs.css, metrics.css, resources.css
- Create individual JS files: monitoring.js, alerts.js, health.js, logs.js, metrics.js, resources.js
- Update HTML files to reference external CSS/JS files
- Add CSS/JS files for other modules (analytics, chat, mail, meet, tasks, etc.)
- Remove obsolete implementation plan files
2025-12-07 09:56:27 -03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-15 16:33:23 -03:00
|
|
|
|
`;
|
|
|
|
|
|
document.head.appendChild(style);
|
|
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// DEMO: Simulate real-time activity
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
// Simulate agent activity for demo
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
if (Math.random() > 0.7) {
|
|
|
|
|
|
const messages = [
|
|
|
|
|
|
{ level: "info", msg: "[SCAN] Monitoring task queues..." },
|
|
|
|
|
|
{ level: "info", msg: "[AGENT] Processing next batch..." },
|
|
|
|
|
|
{ level: "success", msg: "[OK] Checkpoint saved" },
|
|
|
|
|
|
{ level: "info", msg: "[SYNC] Synchronizing state..." },
|
|
|
|
|
|
];
|
|
|
|
|
|
const { level, msg } =
|
|
|
|
|
|
messages[Math.floor(Math.random() * messages.length)];
|
|
|
|
|
|
addAgentLog(level, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 5000);
|