botui/ui/suite/tasks/tasks.html

1030 lines
44 KiB
HTML
Raw Normal View History

<!-- =============================================================================
TASKS APP - Autonomous Task Management
Bot Database Architecture with Shared Resources
============================================================================= -->
<div class="tasks-app">
<!-- Main Content Area -->
<main class="tasks-main">
<!-- Task List Panel (Left) -->
<section class="tasks-list-panel">
<div class="tasks-list-header">
<div class="tasks-list-title">
<h1>Autonomous Tasks</h1>
<span
class="tasks-count"
id="tasks-total-count"
hx-get="/api/tasks/stats"
hx-trigger="load, taskCreated from:body"
hx-swap="innerHTML"
>Loading...</span
></h1>
<button
class="btn-new-intent"
onclick="showNewIntentModal()"
title="Create new intent"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
New Intent
</button>
</div>
<!-- Status Filter Pills -->
<div class="status-filters" id="status-filters">
<button
class="status-pill complete active"
data-filter="all"
hx-get="/api/tasks?filter=all"
hx-target="#task-list"
hx-swap="innerHTML"
>
All <span class="pill-count" id="count-all">-</span>
</button>
<button
class="status-pill active-intents"
data-filter="active"
hx-get="/api/tasks?filter=active"
hx-target="#task-list"
hx-swap="innerHTML"
>
Active
<span class="pill-count" id="count-active">-</span>
</button>
<button
class="status-pill complete"
data-filter="completed"
hx-get="/api/tasks?filter=completed"
hx-target="#task-list"
hx-swap="innerHTML"
>
Completed
<span class="pill-count" id="count-completed">-</span>
</button>
<button
class="status-pill blocked"
data-filter="priority"
hx-get="/api/tasks?filter=priority"
hx-target="#task-list"
hx-swap="innerHTML"
>
High Priority
<span class="pill-count" id="count-priority">-</span>
</button>
<button
class="status-pill pending-info"
data-filter="pending"
hx-get="/api/pending-info"
hx-target="#task-list"
hx-swap="innerHTML"
>
🔑 Pending Info
<span class="pill-count" id="count-pending">-</span>
</button>
<button
class="status-pill goals"
data-filter="goals"
hx-get="/api/goals"
hx-target="#task-list"
hx-swap="innerHTML"
>
🎯 Goals
<span class="pill-count" id="count-goals">-</span>
</button>
<button
class="status-pill schedulers"
data-filter="schedulers"
hx-get="/api/schedulers"
hx-target="#task-list"
hx-swap="innerHTML"
>
⏰ Schedulers
<span class="pill-count" id="count-schedulers">-</span>
</button>
<button
class="status-pill monitors"
data-filter="monitors"
hx-get="/api/monitors"
hx-target="#task-list"
hx-swap="innerHTML"
>
👁️ Monitors
<span class="pill-count" id="count-monitors">-</span>
</button>
<button
class="status-pill tables"
data-filter="tables"
hx-get="/api/tables"
hx-target="#task-list"
hx-swap="innerHTML"
>
🗃️ Tables
<span class="pill-count" id="count-tables">-</span>
</button>
<button
class="status-pill apps"
data-filter="apps"
hx-get="/api/apps"
hx-target="#task-list"
hx-swap="innerHTML"
>
📱 Apps
<span class="pill-count" id="count-apps">-</span>
</button>
</div>
</div>
<!-- Task List -->
<div
class="tasks-list-scroll"
id="task-list"
hx-get="/api/tasks?filter=all"
hx-trigger="load, taskCreated from:body"
2025-12-03 18:42:22 -03:00
hx-swap="innerHTML"
>
<!-- Loading indicator - will be replaced by HTMX -->
<div class="loading-state">
<div class="loading-spinner"></div>
<p>Loading tasks...</p>
</div>
</div>
</section>
<!-- Task Detail Panel (Right) -->
<aside class="task-detail-panel" id="task-detail-panel">
<!-- Empty state shown when no task selected -->
<div class="task-detail-empty" id="task-detail-empty">
<div class="empty-state">
<svg
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path
d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"
/>
<rect x="9" y="3" width="6" height="4" rx="2" />
<path d="M9 12h6" />
<path d="M9 16h6" />
</svg>
<h3>Select a task</h3>
<p>Click on a task from the list to view details</p>
<div class="empty-state-info">
<p class="info-text">
<strong>Bot Database:</strong> All apps share the same database tables.<br>
<strong>Shared Resources:</strong> Schedulers, tools, and monitors work across all apps.
</p>
</div>
</div>
</div>
<!-- Dynamic task detail content loaded via HTMX -->
<div id="task-detail-content" style="display: none">
<div class="task-detail-header">
<div class="task-detail-title-row">
<h2 class="task-detail-title" id="detail-title">-</h2>
<div class="task-detail-actions">
<button
class="task-detail-action-btn"
title="Edit"
onclick="editSelectedTask()"
>
✏️
</button>
<button
class="task-detail-action-btn"
title="Delete"
onclick="deleteSelectedTask()"
>
🗑️
</button>
<button class="task-detail-action-btn" title="More">
</button>
</div>
</div>
<div class="task-detail-meta">
<span class="task-detail-meta-item" id="detail-status">
<span class="icon">📊</span>
<span id="detail-status-text">-</span>
</span>
<span
class="task-detail-meta-item"
id="detail-priority"
>
<span class="icon">🎯</span>
<span id="detail-priority-text">-</span>
</span>
<span
class="task-detail-meta-item"
id="detail-due-date"
>
<span class="icon">📅</span>
<span id="detail-due-date-text">-</span>
</span>
</div>
</div>
<div class="task-detail-scroll">
<!-- Task Description Section -->
<section class="task-description-section">
<div class="section-header">
<span class="section-title">Description</span>
</div>
<p class="task-description" id="detail-description">
No description provided.
</p>
</section>
<!-- Task Actions Section -->
<section class="task-actions-section">
<div class="section-header">
<span class="section-title">Quick Actions</span>
</div>
<div class="task-action-buttons">
<button
class="task-action-btn"
onclick="updateTaskStatus('in_progress')"
>
▶️ Start
</button>
<button
class="task-action-btn"
onclick="updateTaskStatus('done')"
>
✓ Complete
</button>
<button
class="task-action-btn"
onclick="updateTaskStatus('blocked')"
>
⏸️ Block
</button>
</div>
</section>
<!-- Task Activity Log -->
<section class="progress-log-section">
<div class="progress-log-header">
<span class="progress-log-title">Activity</span>
</div>
<div class="step-list" id="detail-activity">
<div class="step-item completed">
<div class="step-icon"></div>
<div class="step-content">
<div class="step-name">Task created</div>
<div
class="step-detail"
id="detail-created-at"
>
-
</div>
</div>
</div>
</div>
</section>
<!-- Goal Progress (shown for GOAL type) -->
<section
class="goal-progress-section"
id="goal-progress-section"
style="display: none"
>
<div class="section-header">
<span class="section-title">🎯 Goal Progress</span>
</div>
<div class="goal-progress-content">
<div class="goal-progress-bar">
<div
class="goal-progress-fill"
id="goal-progress-fill"
style="width: 0%"
></div>
</div>
<div class="goal-progress-stats">
<span id="goal-current-value">0</span> /
<span id="goal-target-value">0</span> (<span
id="goal-percent"
>0</span
>%)
</div>
<div class="goal-last-action" id="goal-last-action">
Last action: -
</div>
</div>
</section>
<!-- Pending Info Fill Form (shown for PENDING type) -->
<section
class="pending-fill-section"
id="pending-fill-section"
style="display: none"
>
<div class="section-header">
<span class="section-title"
>🔑 Fill Information</span
>
</div>
<div class="pending-fill-content">
<p class="pending-reason" id="pending-reason">-</p>
<form
id="pending-fill-form"
hx-post="/api/pending-info/fill"
hx-swap="none"
>
<input
type="hidden"
name="id"
id="pending-fill-id"
/>
<div class="form-group">
<label id="pending-fill-label">Value</label>
<input
type="password"
class="form-input"
name="value"
id="pending-fill-value"
required
/>
</div>
<button type="submit" class="task-action-btn">
Save to Bot Config
</button>
</form>
</div>
</section>
<!-- Scheduler Info (shown for SCHEDULER type) -->
<section
class="scheduler-info-section"
id="scheduler-info-section"
style="display: none"
>
<div class="section-header">
<span class="section-title">⏰ Schedule (Shared)</span>
</div>
<div class="scheduler-info-content">
<div class="scheduler-cron" id="scheduler-cron">
Cron: -
</div>
<div class="scheduler-next" id="scheduler-next">
Next run: -
</div>
<div class="scheduler-file" id="scheduler-file">
File: .gbdialog/schedulers/-
</div>
<p class="shared-resource-note">
This scheduler runs for all apps in this bot
</p>
</div>
</section>
<!-- Monitor Info (shown for MONITOR type) -->
<section
class="monitor-info-section"
id="monitor-info-section"
style="display: none"
>
<div class="section-header">
<span class="section-title">👁️ Monitor (Shared)</span>
</div>
<div class="monitor-info-content">
<div class="monitor-target" id="monitor-target">
Target: -
</div>
<div class="monitor-interval" id="monitor-interval">
Interval: -
</div>
<div
class="monitor-last-check"
id="monitor-last-check"
>
Last check: -
</div>
<div
class="monitor-last-value"
id="monitor-last-value"
>
Last value: -
</div>
<p class="shared-resource-note">
This monitor triggers for all apps in this bot
</p>
</div>
</section>
<!-- Table Info (shown for TABLE type) -->
<section
class="table-info-section"
id="table-info-section"
style="display: none"
>
<div class="section-header">
<span class="section-title">🗃️ Table (Shared)</span>
</div>
<div class="table-info-content">
<div class="table-name" id="table-name">
Table: -
</div>
<div class="table-fields" id="table-fields">
Fields: -
</div>
<div class="table-records" id="table-records">
Records: -
</div>
<div class="table-source">
Defined in: <code>.gbdialog/tables.bas</code>
</div>
<p class="shared-resource-note">
All apps in this bot access this table via FIND keyword
</p>
<div class="table-query-example">
<strong>Example:</strong>
<code id="table-query-example">FIND * IN table_name</code>
</div>
</div>
</section>
2025-12-03 18:42:22 -03:00
<!-- App Info (shown for APP type) -->
<section
class="app-info-section"
id="app-info-section"
style="display: none"
>
<div class="section-header">
<span class="section-title">📱 App</span>
</div>
<div class="app-info-content">
<div class="app-name" id="app-name">
App: -
</div>
<div class="app-url" id="app-url">
URL: /apps/-
</div>
<div class="app-tables" id="app-tables">
Uses tables: -
</div>
<p class="shared-resource-note">
This app uses the bot's shared database
</p>
<div class="app-actions">
<button class="task-action-btn" onclick="openApp()">
🚀 Open App
</button>
<button class="task-action-btn" onclick="editAppTables()">
🗃️ Edit Tables
</button>
</div>
</div>
</section>
</div>
</div>
</aside>
</main>
</div>
2025-12-03 18:42:22 -03:00
<!-- Bot Database Summary Panel -->
<div
class="bot-db-summary-panel"
id="bot-db-summary-panel"
hx-get="/api/bot/database/summary"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="bot-db-loading">Loading bot database info...</div>
</div>
<!-- Pending Info Summary Panel -->
<div
class="pending-summary-panel"
id="pending-summary-panel"
hx-get="/api/pending-info/summary"
hx-trigger="load"
hx-swap="innerHTML"
></div>
<!-- New Intent Modal -->
<div class="modal-overlay" id="newIntentModal">
<div class="modal">
<div class="modal-header">
<h3>Create New Intent</h3>
<button class="modal-close" onclick="closeNewIntentModal()">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div class="modal-body">
<form
id="newIntentForm"
hx-post="/api/tasks"
hx-ext="json-enc"
hx-target="#task-list"
hx-swap="afterbegin"
hx-on::after-request="if(event.detail.successful) { closeNewIntentModal(); htmx.trigger(document.body, 'taskCreated'); }"
>
<div class="form-group">
<label class="form-label" for="intentTitle"
>Intent (Natural Language)</label
>
<input
type="text"
class="form-input"
id="intentTitle"
name="title"
placeholder="e.g., cria app de agendamento, quando IBM mudar avisa, todo dia 9h resumo..."
required
/>
<p class="form-hint">
Describe what you want: create apps, schedule tasks, monitor changes, or set goals.
</p>
</div>
<div class="form-group">
<label class="form-label" for="intentDescription"
>Additional Details (Optional)</label
>
<textarea
class="form-input"
id="intentDescription"
name="description"
rows="3"
placeholder="Add more context: tables needed, fields, triggers, etc."
></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="intentDueDate"
>Due Date (Optional)</label
>
<input
type="datetime-local"
class="form-input"
id="intentDueDate"
name="due_date"
/>
</div>
<div class="form-group">
<label class="form-label" for="intentPriority"
>Priority</label
>
<select
class="form-input"
id="intentPriority"
name="priority"
>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label" for="intentType"
>Intent Type (Auto-detected)</label
>
<select
class="form-input"
id="intentType"
name="intent_type"
>
<option value="auto">Auto-detect</option>
<option value="app">📱 App - Create HTMX app</option>
<option value="table">🗃️ Table - Define new table</option>
<option value="todo">✅ Todo - Simple task</option>
<option value="goal">🎯 Goal - Autonomous goal</option>
<option value="schedule">⏰ Schedule - Recurring task</option>
<option value="monitor">👁️ Monitor - Watch for changes</option>
<option value="tool">🔧 Tool - Voice/chat command</option>
<option value="action">⚡ Action - Execute immediately</option>
</select>
</div>
<div class="form-info-box">
<strong>🗄️ Bot Database Architecture:</strong>
<ul>
<li>All apps share the same database tables</li>
<li>Tables are defined in <code>.gbdialog/tables.bas</code></li>
<li>Schedulers, tools, and monitors are shared resources</li>
<li>Use <code>FIND x IN table</code> to query data</li>
</ul>
</div>
</form>
</div>
<div class="modal-footer">
<button
class="decision-btn decision-btn-secondary</strong>"
onclick="closeNewIntentModal()"
>
Cancel
</button>
<button
class="decision-btn decision-btn-primary"
type="submit"
form="newIntentForm"
>
Create Intent
</button>
</div>
</div>
</div>
<!-- Edit Tables Modal -->
<div class="modal-overlay" id="editTablesModal">
<div class="modal modal-large">
<div class="modal-header">
<h3>🗃️ Edit Tables (tables.bas)</h3>
<button class="modal-close" onclick="closeEditTablesModal()">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" /></h3>
</svg>
</button>
</div>
<div class="modal-body">
<p class="modal-description">
Define tables using the TABLE keyword. Changes auto-sync to the bot database.
</p>
<div
id="tables-editor"
hx-get="/api/tables/source"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-state">Loading tables.bas...</div>
</div>
</div>
<div class="modal-footer">
<button
class="decision-btn decision-btn-secondary"
onclick="closeEditTablesModal()"
>
Cancel
</button>
<button
class="decision-btn decision-btn-primary"
onclick="saveTablesFile()"
>
Save & Sync
</button>
</div>
</div>
</div>
<script>
// Conditionally load tasks.js only if not already loaded
if (typeof TasksState === "undefined") {
const script = document.createElement("script");
script.src = "tasks/tasks.js";
document.head.appendChild(script);
}
</script>
<script>
// New Intent Modal functions
function showNewIntentModal() {
document.getElementById("newIntentModal").classList.add("show");
document.getElementById("intentTitle").focus();
}
function closeNewIntentModal() {
document.getElementById("newIntentModal").classList.remove("show");
document.getElementById("newIntentForm").reset();
}
// Edit Tables Modal functions
function showEditTablesModal() {
document.getElementById("editTablesModal").classList.add("show");
htmx.trigger(document.getElementById("tables-editor"), "load");
}
function closeEditTablesModal() {
document.getElementById("editTablesModal").classList.remove("show");
}
function editAppTables() {
showEditTablesModal();
}
function saveTablesFile() {
const editor = document.getElementById("tables-editor-content");
if (!editor) return;
fetch("/api/tables/source", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: editor.value }),
})
.then((response) => {
if (response.ok) {
closeEditTablesModal();
htmx.trigger(document.body, "taskCreated");
showToast("Tables saved and synced to database", "success");
} else {
showToast("Failed to save tables", "error");
}
})
.catch((err) => {
console.error("Failed to save tables:", err);
showToast("Failed to save tables", "error");
});
}
function showToast(message, type) {
// Simple toast notification
const toast = document.createElement("div");
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// Transform form data before sending to API - using global listener
document.body.addEventListener("htmx:configRequest", function (event) {
const form = event.detail.elt;
if (form.id !== "newIntentForm") return;
const formData = event.detail.parameters;
// Convert due_date to ISO 8601 format if present
if (formData.due_date && formData.due_date.trim() !== "") {
const date = new Date(formData.due_date);
formData.due_date = date.toISOString();
} else {
delete formData.due_date;
}
// Remove empty optional fields
if (!formData.description || formData.description.trim() === "") {
delete formData.description;
}
// Handle auto-detect intent type
if (formData.intent_type === "auto") {
delete formData.intent_type;
}
});
// Currently selected task
window.selectedTaskId = null;
window.selectedTaskType = null;
// Select a task and load its details (global for HTMX onclick)
window.selectTask = function (taskId, taskType) {
window.selectedTaskId = taskId;
window.selectedTaskType = taskType || "task";
// Update UI selection state
document.querySelectorAll(".task-item, .task-card").forEach((el) => {
el.classList.remove("selected");
});
const selectedEl = document.querySelector(`[data-task-id="${taskId}"]`);
if (selectedEl) {
selectedEl.classList.add("selected");
}
// Show detail panel content
document.getElementById("task-detail-empty").style.display = "none";
document.getElementById("task-detail-content").style.display = "block";
// Hide all type-specific sections
document.getElementById("goal-progress-section").style.display = "none";
document.getElementById("pending-fill-section").style.display = "none";
document.getElementById("scheduler-info-section").style.display = "none";
document.getElementById("monitor-info-section").style.display = "none";
document.getElementById("table-info-section").style.display = "none";
document.getElementById("app-info-section").style.display = "none";
// Load details based on type
const endpoint = getEndpointForType(taskType, taskId);
fetch(endpoint)
.then((response) => response.json())
.then((data) => {
populateDetails(data, taskType);
})
.catch((err) => {
console.error("Failed to load details:", err);
});
};
function getEndpointForType(type, id) {
switch (type) {
case "goal":
return `/api/goals/${id}`;
case "pending":
return `/api/pending-info/${id}`;
case "scheduler":
return `/api/schedulers/${id}`;
case "monitor":
return `/api/monitors/${id}`;
case "table":
return `/api/tables/${id}`;
case "app":
return `/api/apps/${id}`;
default:
return `/api/tasks/${id}`;
}
}
function populateDetails(data, type) {
document.getElementById("detail-title").textContent =
data.title || data.name || "Untitled";
document.getElementById("detail-status-text").textContent =
data.status || "unknown";
document.getElementById("detail-priority-text").textContent =
data.priority || "normal";
document.getElementById("detail-description").textContent =
data.description || "No description provided.";
const dueDateText = data.due_date
? new Date(data.due_date).toLocaleDateString()
: "No due date";
document.getElementById("detail-due-date-text").textContent = dueDateText;
const createdText = data.created_at
? new Date(data.created_at).toLocaleString()
: "-";
document.getElementById("detail-created-at").textContent = createdText;
// Show type-specific sections
switch (type) {
case "goal":
document.getElementById("goal-progress-section").style.display = "block";
document.getElementById("goal-current-value").textContent = data.current_value || 0;
document.getElementById("goal-target-value").textContent = data.target_value || 0;
const percent = data.target_value ? Math.round((data.current_value / data.target_value) * 100) : 0;
document.getElementById("goal-percent").textContent = percent;
document.getElementById("goal-progress-fill").style.width = percent + "%";
document.getElementById("goal-last-action").textContent = "Last action: " + (data.last_action || "-");
break;
case "pending":
document.getElementById("pending-fill-section").style.display = "block";
document.getElementById("pending-fill-id").value = data.id;
document.getElementById("pending-fill-label").textContent = data.field_label || "Value";
document.getElementById("pending-reason").textContent = data.reason || "Required for app functionality";
break;
case "scheduler":
document.getElementById("scheduler-info-section").style.display = "block";
document.getElementById("scheduler-cron").textContent = "Cron: " + (data.cron || "-");
document.getElementById("scheduler-next").textContent = "Next run: " + (data.next_run || "-");
document.getElementById("scheduler-file").textContent = "File: .gbdialog/schedulers/" + (data.file || "-");
break;
case "monitor":
document.getElementById("monitor-info-section").style.display = "block";
document.getElementById("monitor-target").textContent = "Target: " + (data.target || "-");
document.getElementById("monitor-interval").textContent = "Interval: " + (data.interval || "-");
document.getElementById("monitor-last-check").textContent = "Last check: " + (data.last_check || "-");
document.getElementById("monitor-last-value").textContent = "Last value: " + (data.last_value || "-");
break;
case "table":
document.getElementById("table-info-section").style.display = "block";
document.getElementById("table-name").textContent = "Table: " + (data.name || "-");
document.getElementById("table-fields").textContent = "Fields: " + (data.fields?.join(", ") || "-");
document.getElementById("table-records").textContent = "Records: " + (data.record_count || 0);
document.getElementById("table-query-example").textContent = "FIND * IN " + (data.name || "table_name");
break;
case "app":
document.getElementById("app-info-section").style.display = "block";
document.getElementById("app-name").textContent = "App: " + (data.name || "-");
document.getElementById("app-url").textContent = "URL: /apps/" + (data.name || "-");
document.getElementById("app-tables").textContent = "Uses tables: " + (data.tables?.join(", ") || "-");
break;
}
}
// Update task status (global for onclick)
window.updateTaskStatus = function (newStatus) {
if (!window.selectedTaskId) return;
fetch(`/api/tasks/${window.selectedTaskId}/status`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: newStatus }),
})
.then((response) => {
if (response.ok) {
htmx.trigger(document.body, "taskCreated");
window.selectTask(window.selectedTaskId, window.selectedTaskType);
}
})
.catch((err) => console.error("Failed to update status:", err));
};
// Delete selected task (global for onclick)
window.deleteSelectedTask = function () {
if (!window.selectedTaskId) return;
if (!confirm("Are you sure you want to delete this?")) return;
const endpoint = getEndpointForType(window.selectedTaskType, window.selectedTaskId);
fetch(endpoint, { method: "DELETE" })
.then((response) => {
if (response.ok) {
window.selectedTaskId = null;
window.selectedTaskType = null;
document.getElementById("task-detail-empty").style.display = "flex";
document.getElementById("task-detail-content").style.display = "none";
htmx.trigger(document.body, "taskCreated");
showToast("Deleted successfully", "success");
}
})
.catch((err) => console.error("Failed to delete:", err));
};
// Edit selected task (global for onclick)
window.editSelectedTask = function () {
if (!window.selectedTaskId) return;
if (window.selectedTaskType === "table") {
showEditTablesModal();
} else {
alert("Edit functionality for " + window.selectedTaskType + ": " + window.selectedTaskId);
}
};
// Open app in new tab
window.openApp = function () {
if (window.selectedTaskType === "app" && window.selectedTaskId) {
window.open("/apps/" + window.selectedTaskId, "_blank");
}
};
// Close modal on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeNewIntentModal();
closeEditTablesModal();
}
});
// Close modal when clicking overlay
document.querySelectorAll(".modal-overlay").forEach((overlay) => {
overlay.addEventListener("click", (e) => {
if (e.target.classList.contains("modal-overlay")) {
closeNewIntentModal();
closeEditTablesModal();
}
});
});
// Close modal after successful form submission
document.body.addEventListener("htmx:afterRequest", (e) => {
if (e.detail.elt.id === "newIntentForm" && e.detail.successful) {
closeNewIntentModal();
showToast("Intent created successfully", "success");
}
});
// Update stats counts on load
document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target.id === "tasks-total-count") {
// Stats loaded, update pill counts
try {
const stats = JSON.parse(e.detail.target.textContent);
if (stats.total !== undefined) {
document.getElementById("count-all").textContent = stats.total;
document.getElementById("count-active").textContent = stats.running || 0;
document.getElementById("count-completed").textContent = stats.completed || 0;
document.getElementById("count-pending").textContent = stats.pending_count || 0;
document.getElementById("count-goals").textContent = stats.goals_count || 0;
document.getElementById("count-schedulers").textContent = stats.schedulers_count || 0;
document.getElementById("count-monitors").textContent = stats.monitors_count || 0;
document.getElementById("count-tables").textContent = stats.tables_count || 0;
document.getElementById("count-apps").textContent = stats.apps_count || 0;
e.detail.target.textContent = stats.total + " items";
}
} catch (err) {
// Not JSON, leave as is
}
}
});
</script>