2025-12-15 16:33:23 -03:00
|
|
|
|
<!-- =============================================================================
|
2025-12-27 22:38:37 -03:00
|
|
|
|
TASKS APP - Autonomous Task Management
|
2025-12-30 22:42:54 -03:00
|
|
|
|
Respects Theme Manager - No hardcoded theme
|
2025-12-15 16:33:23 -03:00
|
|
|
|
============================================================================= -->
|
|
|
|
|
|
|
2025-12-15 18:48:40 -03:00
|
|
|
|
<div class="tasks-app">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Hidden element to load stats on page load -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
hx-get="/api/tasks/stats"
|
|
|
|
|
|
hx-trigger="load, taskCreated from:body"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
style="display: none"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Status Filter Pills Row -->
|
|
|
|
|
|
<div class="status-filter-row">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill"
|
|
|
|
|
|
data-filter="complete"
|
|
|
|
|
|
hx-get="/api/tasks?filter=complete"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">✓</span>
|
|
|
|
|
|
<span class="pill-label">Complete</span>
|
|
|
|
|
|
<span class="pill-count" id="count-complete">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill active"
|
|
|
|
|
|
data-filter="all"
|
|
|
|
|
|
hx-get="/api/tasks?filter=all"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">📋</span>
|
|
|
|
|
|
<span class="pill-label">All Tasks</span>
|
|
|
|
|
|
<span class="pill-count" id="count-all">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill"
|
|
|
|
|
|
data-filter="active"
|
|
|
|
|
|
hx-get="/api/tasks?filter=active"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">⚡</span>
|
|
|
|
|
|
<span class="pill-label">Active Intents</span>
|
|
|
|
|
|
<span class="pill-count" id="count-active">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill"
|
|
|
|
|
|
data-filter="awaiting"
|
|
|
|
|
|
hx-get="/api/tasks?filter=awaiting"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">⏳</span>
|
|
|
|
|
|
<span class="pill-label">Awaiting Decision</span>
|
|
|
|
|
|
<span class="pill-count" id="count-awaiting">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill"
|
|
|
|
|
|
data-filter="paused"
|
|
|
|
|
|
hx-get="/api/tasks?filter=paused"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">⏸</span>
|
|
|
|
|
|
<span class="pill-label">Paused</span>
|
|
|
|
|
|
<span class="pill-count" id="count-paused">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="filter-pill"
|
|
|
|
|
|
data-filter="blocked"
|
|
|
|
|
|
hx-get="/api/tasks?filter=blocked"
|
|
|
|
|
|
hx-target="#task-list"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="pill-icon">⚠</span>
|
|
|
|
|
|
<span class="pill-label">Blocked/Issues</span>
|
|
|
|
|
|
<span class="pill-count" id="count-blocked">-</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div class="time-saved-badge">
|
|
|
|
|
|
<span class="time-label">Active Time Saved:</span>
|
|
|
|
|
|
<span class="time-value" id="time-saved-value">-</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Quick Intent Input -->
|
|
|
|
|
|
<div class="quick-intent-bar">
|
|
|
|
|
|
<div class="intent-input-wrapper">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="quick-intent-input"
|
|
|
|
|
|
name="intent"
|
|
|
|
|
|
class="quick-intent-input"
|
|
|
|
|
|
placeholder="What would you like to do? e.g., 'create a CRM app' or 'remind me to call John tomorrow'"
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
id="quick-intent-btn"
|
|
|
|
|
|
class="btn-create-run"
|
|
|
|
|
|
hx-post="/api/autotask/create"
|
|
|
|
|
|
hx-ext="json-enc"
|
|
|
|
|
|
hx-include="#quick-intent-input"
|
2025-12-31 12:38:54 -03:00
|
|
|
|
hx-target="#intent-result-hidden"
|
|
|
|
|
|
hx-swap="none"
|
2025-12-30 22:42:54 -03:00
|
|
|
|
hx-indicator="#intent-spinner"
|
2025-12-31 12:38:54 -03:00
|
|
|
|
hx-timeout="300000"
|
2025-12-30 22:42:54 -03:00
|
|
|
|
>
|
|
|
|
|
|
<span class="btn-text">Create & Run</span>
|
|
|
|
|
|
<span class="spinner" id="intent-spinner"></span>
|
|
|
|
|
|
</button>
|
2025-12-27 23:33:18 -03:00
|
|
|
|
</div>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<div id="intent-result" class="intent-result"></div>
|
2025-12-31 12:38:54 -03:00
|
|
|
|
<div id="intent-result-hidden" style="display: none"></div>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
</div>
|
2025-12-27 23:33:18 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Main Two-Column Layout -->
|
2025-12-15 16:33:23 -03:00
|
|
|
|
<main class="tasks-main">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Left Panel: Task Cards List -->
|
2025-12-15 16:33:23 -03:00
|
|
|
|
<section class="tasks-list-panel">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="tasks-list-scroll"
|
|
|
|
|
|
id="task-list"
|
2025-12-27 22:38:37 -03:00
|
|
|
|
hx-get="/api/tasks?filter=all"
|
|
|
|
|
|
hx-trigger="load, taskCreated from:body"
|
2025-12-03 18:42:22 -03:00
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Loading state - replaced by HTMX -->
|
2025-12-27 22:38:37 -03:00
|
|
|
|
<div class="loading-state">
|
|
|
|
|
|
<div class="loading-spinner"></div>
|
|
|
|
|
|
<p>Loading tasks...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Right Panel: Task Detail -->
|
2025-12-27 22:38:37 -03:00
|
|
|
|
<aside class="task-detail-panel" id="task-detail-panel">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Detail content loaded dynamically -->
|
|
|
|
|
|
<div class="detail-empty" id="detail-empty">
|
|
|
|
|
|
<div class="empty-icon">📋</div>
|
|
|
|
|
|
<h3 class="empty-title">Select a task</h3>
|
|
|
|
|
|
<p class="empty-description">
|
|
|
|
|
|
Click on a task from the list to view details
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="empty-info">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Bot Database:</strong> All apps share the same
|
|
|
|
|
|
database tables.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Shared Resources:</strong> Schedulers, tools,
|
|
|
|
|
|
and monitors work across all apps.
|
|
|
|
|
|
</p>
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Dynamic detail content -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
id="task-detail-content"
|
|
|
|
|
|
style="display: none"
|
|
|
|
|
|
hx-get=""
|
|
|
|
|
|
hx-trigger="taskSelected from:body"
|
|
|
|
|
|
hx-swap="innerHTML"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- Loaded via HTMX when task selected -->
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</div>
|
2025-12-15 16:33:23 -03:00
|
|
|
|
</aside>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
2025-12-03 18:42:22 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Floating Progress Panel - Shows live task generation progress -->
|
2025-12-27 22:38:37 -03:00
|
|
|
|
<div
|
2025-12-30 22:42:54 -03:00
|
|
|
|
class="floating-progress-panel"
|
|
|
|
|
|
id="floating-progress"
|
|
|
|
|
|
style="display: none"
|
2025-12-27 22:38:37 -03:00
|
|
|
|
>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<div class="floating-progress-header">
|
|
|
|
|
|
<div class="floating-progress-title">
|
|
|
|
|
|
<span class="progress-dot"></span>
|
|
|
|
|
|
<span id="floating-task-name">Processing...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="floating-progress-actions">
|
|
|
|
|
|
<button class="btn-minimize" onclick="minimizeFloatingProgress()">
|
|
|
|
|
|
—
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn-close-float" onclick="closeFloatingProgress()">
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="floating-progress-body">
|
|
|
|
|
|
<div class="floating-progress-bar">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="floating-progress-fill"
|
|
|
|
|
|
id="floating-progress-fill"
|
|
|
|
|
|
style="width: 0%"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="floating-progress-info">
|
|
|
|
|
|
<span id="floating-progress-step">Starting...</span>
|
|
|
|
|
|
<span id="floating-progress-percent">0%</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="floating-progress-log" id="floating-progress-log">
|
|
|
|
|
|
<!-- Live log entries appear here -->
|
|
|
|
|
|
</div>
|
2025-12-31 12:38:54 -03:00
|
|
|
|
<div class="floating-llm-terminal" id="floating-llm-terminal">
|
|
|
|
|
|
<!-- LLM streaming output appears here -->
|
|
|
|
|
|
</div>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
</div>
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<!-- Toast Container -->
|
|
|
|
|
|
<div class="toast-container" id="toast-container"></div>
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-15 23:16:09 -03:00
|
|
|
|
<!-- New Intent Modal -->
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<div class="modal" id="new-intent-modal" style="display: none">
|
|
|
|
|
|
<div class="modal-backdrop" onclick="closeNewIntentModal()"></div>
|
|
|
|
|
|
<div class="modal-content">
|
2025-12-27 22:38:37 -03:00
|
|
|
|
<div class="modal-header">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<h3>Create New Intent</h3>
|
|
|
|
|
|
<button class="btn-close" onclick="closeNewIntentModal()">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
|
<form id="new-intent-form">
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="intent-text"
|
|
|
|
|
|
>What would you like to accomplish?</label
|
|
|
|
|
|
>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
id="intent-text"
|
|
|
|
|
|
name="intent"
|
|
|
|
|
|
rows="4"
|
|
|
|
|
|
placeholder="Describe what you want to create or automate..."
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-row">
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="intent-priority">Priority</label>
|
|
|
|
|
|
<select id="intent-priority" name="priority">
|
|
|
|
|
|
<option value="normal">Normal</option>
|
|
|
|
|
|
<option value="high">High</option>
|
|
|
|
|
|
<option value="low">Low</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="intent-mode">Execution Mode</label>
|
|
|
|
|
|
<select id="intent-mode" name="mode">
|
|
|
|
|
|
<option value="auto">Automatic</option>
|
|
|
|
|
|
<option value="supervised">Supervised</option>
|
|
|
|
|
|
<option value="manual">Manual Approval</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
|
<button class="btn-secondary" onclick="closeNewIntentModal()">
|
|
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn-primary" onclick="submitNewIntent()">
|
|
|
|
|
|
Create & Run
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Decision Modal -->
|
|
|
|
|
|
<div class="modal" id="decision-modal" style="display: none">
|
|
|
|
|
|
<div class="modal-backdrop" onclick="closeDecisionModal()"></div>
|
|
|
|
|
|
<div class="modal-content modal-lg">
|
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
|
<h3>Make Decision</h3>
|
|
|
|
|
|
<button class="btn-close" onclick="closeDecisionModal()">×</button>
|
|
|
|
|
|
</div>
|
2025-12-27 22:38:37 -03:00
|
|
|
|
<div class="modal-body">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<div class="decision-question" id="decision-question">
|
|
|
|
|
|
<h4>Decision Required</h4>
|
|
|
|
|
|
<p>Loading decision details...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="decision-options" id="decision-options">
|
|
|
|
|
|
<!-- Options loaded dynamically -->
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<button class="btn-secondary" onclick="closeDecisionModal()">
|
2025-12-27 22:38:37 -03:00
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
<button class="btn-secondary" onclick="skipDecision()">
|
|
|
|
|
|
Skip for Now
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn-primary" onclick="submitDecision()">
|
|
|
|
|
|
Confirm Decision
|
2025-12-27 22:38:37 -03:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-15 23:16:09 -03:00
|
|
|
|
<script>
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Initialize on load
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
|
|
if (typeof initTasksApp === "function") {
|
|
|
|
|
|
initTasksApp();
|
|
|
|
|
|
}
|
|
|
|
|
|
// Load stats
|
|
|
|
|
|
loadTaskStats();
|
2025-12-31 12:38:54 -03:00
|
|
|
|
|
|
|
|
|
|
// Handle create task response - auto-select new task and show progress
|
|
|
|
|
|
document.body.addEventListener("htmx:afterRequest", function (evt) {
|
|
|
|
|
|
// Check if this is the create task response
|
|
|
|
|
|
if (
|
|
|
|
|
|
evt.detail.pathInfo &&
|
|
|
|
|
|
evt.detail.pathInfo.requestPath === "/api/autotask/create"
|
|
|
|
|
|
) {
|
|
|
|
|
|
const xhr = evt.detail.xhr;
|
|
|
|
|
|
const intentResult = document.getElementById("intent-result");
|
|
|
|
|
|
|
|
|
|
|
|
if (xhr && xhr.status === 202) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = JSON.parse(xhr.responseText);
|
|
|
|
|
|
if (response.success && response.task_id) {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
"[TASK] Created task:",
|
|
|
|
|
|
response.task_id,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Show success feedback
|
|
|
|
|
|
intentResult.innerHTML = `<span class="intent-success">✓ Task created - running...</span>`;
|
|
|
|
|
|
intentResult.style.display = "block";
|
|
|
|
|
|
|
|
|
|
|
|
// Clear the input
|
|
|
|
|
|
document.getElementById(
|
|
|
|
|
|
"quick-intent-input",
|
|
|
|
|
|
).value = "";
|
|
|
|
|
|
|
|
|
|
|
|
// Trigger task list refresh
|
|
|
|
|
|
htmx.trigger(document.body, "taskCreated");
|
|
|
|
|
|
|
|
|
|
|
|
// After a short delay to let the list reload, select the new task
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
|
selectTask(response.task_id);
|
|
|
|
|
|
// Start polling for updates
|
|
|
|
|
|
startTaskPolling(response.task_id);
|
|
|
|
|
|
// Hide success message after task is selected
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
|
intentResult.style.display = "none";
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
intentResult.innerHTML = `<span class="intent-error">✗ ${response.message || "Failed to create task"}</span>`;
|
|
|
|
|
|
intentResult.style.display = "block";
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("Failed to parse create response:", e);
|
|
|
|
|
|
intentResult.innerHTML = `<span class="intent-error">✗ Failed to parse response</span>`;
|
|
|
|
|
|
intentResult.style.display = "block";
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (xhr && xhr.status >= 400) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = JSON.parse(xhr.responseText);
|
|
|
|
|
|
intentResult.innerHTML = `<span class="intent-error">✗ ${response.error || response.message || "Error creating task"}</span>`;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
intentResult.innerHTML = `<span class="intent-error">✗ Error: ${xhr.status}</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
intentResult.style.display = "block";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-12-30 22:42:54 -03:00
|
|
|
|
});
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-31 12:38:54 -03:00
|
|
|
|
// Poll for task status updates
|
|
|
|
|
|
let taskPollingInterval = null;
|
|
|
|
|
|
function startTaskPolling(taskId) {
|
|
|
|
|
|
// Clear any existing polling
|
|
|
|
|
|
if (taskPollingInterval) {
|
|
|
|
|
|
clearInterval(taskPollingInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
taskPollingInterval = setInterval(function () {
|
2025-12-31 23:45:38 -03:00
|
|
|
|
fetch(`/api/tasks/${taskId}`, {
|
|
|
|
|
|
headers: { Accept: "application/json" },
|
|
|
|
|
|
})
|
2025-12-31 12:38:54 -03:00
|
|
|
|
.then((r) => r.json())
|
|
|
|
|
|
.then((task) => {
|
|
|
|
|
|
console.log("[TASK] Poll status:", task.status);
|
|
|
|
|
|
|
|
|
|
|
|
// Refresh the detail panel
|
|
|
|
|
|
htmx.ajax("GET", `/api/tasks/${taskId}`, {
|
|
|
|
|
|
target: "#task-detail-content",
|
|
|
|
|
|
swap: "innerHTML",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Refresh task list to update status badges
|
|
|
|
|
|
htmx.trigger(document.body, "taskCreated");
|
|
|
|
|
|
|
|
|
|
|
|
// Stop polling if task is complete or failed
|
|
|
|
|
|
if (
|
|
|
|
|
|
task.status === "completed" ||
|
|
|
|
|
|
task.status === "failed" ||
|
|
|
|
|
|
task.status === "cancelled"
|
|
|
|
|
|
) {
|
|
|
|
|
|
clearInterval(taskPollingInterval);
|
|
|
|
|
|
taskPollingInterval = null;
|
|
|
|
|
|
loadTaskStats();
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
|
console.warn("Failed to poll task:", e);
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 2000); // Poll every 2 seconds
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stopTaskPolling() {
|
|
|
|
|
|
if (taskPollingInterval) {
|
|
|
|
|
|
clearInterval(taskPollingInterval);
|
|
|
|
|
|
taskPollingInterval = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Load task statistics
|
|
|
|
|
|
function loadTaskStats() {
|
2025-12-31 12:38:54 -03:00
|
|
|
|
fetch("/api/tasks/stats/json")
|
2025-12-30 22:42:54 -03:00
|
|
|
|
.then((r) => r.json())
|
|
|
|
|
|
.then((stats) => {
|
|
|
|
|
|
if (stats.complete !== undefined)
|
|
|
|
|
|
document.getElementById("count-complete").textContent =
|
|
|
|
|
|
stats.complete;
|
|
|
|
|
|
if (stats.active !== undefined)
|
|
|
|
|
|
document.getElementById("count-active").textContent =
|
|
|
|
|
|
stats.active;
|
|
|
|
|
|
if (stats.awaiting !== undefined)
|
|
|
|
|
|
document.getElementById("count-awaiting").textContent =
|
|
|
|
|
|
stats.awaiting;
|
|
|
|
|
|
if (stats.paused !== undefined)
|
|
|
|
|
|
document.getElementById("count-paused").textContent =
|
|
|
|
|
|
stats.paused;
|
|
|
|
|
|
if (stats.blocked !== undefined)
|
|
|
|
|
|
document.getElementById("count-blocked").textContent =
|
|
|
|
|
|
stats.blocked;
|
|
|
|
|
|
if (stats.time_saved !== undefined)
|
|
|
|
|
|
document.getElementById("time-saved-value").textContent =
|
|
|
|
|
|
stats.time_saved;
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((e) => console.warn("Failed to load stats:", e));
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Filter pill click handler
|
|
|
|
|
|
document.querySelectorAll(".filter-pill").forEach((pill) => {
|
|
|
|
|
|
pill.addEventListener("click", function () {
|
|
|
|
|
|
document
|
|
|
|
|
|
.querySelectorAll(".filter-pill")
|
|
|
|
|
|
.forEach((p) => p.classList.remove("active"));
|
|
|
|
|
|
this.classList.add("active");
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Modal functions
|
|
|
|
|
|
function showNewIntentModal() {
|
|
|
|
|
|
document.getElementById("new-intent-modal").style.display = "flex";
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function closeNewIntentModal() {
|
|
|
|
|
|
document.getElementById("new-intent-modal").style.display = "none";
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function showDecisionModal(decision) {
|
|
|
|
|
|
if (decision) {
|
|
|
|
|
|
document.getElementById("decision-question").innerHTML = `
|
|
|
|
|
|
<h4>${decision.title || "Decision Required"}</h4>
|
|
|
|
|
|
<p>${decision.description || ""}</p>
|
|
|
|
|
|
`;
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
2025-12-30 22:42:54 -03:00
|
|
|
|
document.getElementById("decision-modal").style.display = "flex";
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function closeDecisionModal() {
|
|
|
|
|
|
document.getElementById("decision-modal").style.display = "none";
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function submitNewIntent() {
|
|
|
|
|
|
const form = document.getElementById("new-intent-form");
|
|
|
|
|
|
const intent = form.querySelector('[name="intent"]').value;
|
|
|
|
|
|
if (intent.trim()) {
|
|
|
|
|
|
document.getElementById("quick-intent-input").value = intent;
|
|
|
|
|
|
htmx.trigger(document.getElementById("quick-intent-btn"), "click");
|
|
|
|
|
|
closeNewIntentModal();
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
2025-12-30 22:42:54 -03:00
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function submitDecision() {
|
|
|
|
|
|
// Implementation for submitting decision
|
|
|
|
|
|
closeDecisionModal();
|
|
|
|
|
|
htmx.trigger(document.body, "taskCreated");
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function skipDecision() {
|
|
|
|
|
|
closeDecisionModal();
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Task selection
|
|
|
|
|
|
function selectTask(taskId) {
|
|
|
|
|
|
// Update selection UI
|
|
|
|
|
|
document.querySelectorAll(".task-card").forEach((card) => {
|
|
|
|
|
|
card.classList.remove("selected");
|
2025-12-27 22:38:37 -03:00
|
|
|
|
});
|
2025-12-30 22:42:54 -03:00
|
|
|
|
const selectedCard = document.querySelector(
|
|
|
|
|
|
`[data-task-id="${taskId}"]`,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (selectedCard) {
|
|
|
|
|
|
selectedCard.classList.add("selected");
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Show detail panel
|
|
|
|
|
|
document.getElementById("detail-empty").style.display = "none";
|
|
|
|
|
|
const detailContent = document.getElementById("task-detail-content");
|
|
|
|
|
|
detailContent.style.display = "block";
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Load task details
|
|
|
|
|
|
htmx.ajax("GET", `/api/tasks/${taskId}`, {
|
|
|
|
|
|
target: "#task-detail-content",
|
|
|
|
|
|
swap: "innerHTML",
|
|
|
|
|
|
});
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function deselectTask() {
|
|
|
|
|
|
document.querySelectorAll(".task-card").forEach((card) => {
|
|
|
|
|
|
card.classList.remove("selected");
|
|
|
|
|
|
});
|
|
|
|
|
|
document.getElementById("detail-empty").style.display = "flex";
|
|
|
|
|
|
document.getElementById("task-detail-content").style.display = "none";
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Floating Progress Panel Functions
|
|
|
|
|
|
function showFloatingProgress(taskName) {
|
|
|
|
|
|
let panel = document.getElementById("floating-progress");
|
|
|
|
|
|
document.getElementById("floating-task-name").textContent =
|
|
|
|
|
|
taskName || "Processing...";
|
|
|
|
|
|
document.getElementById("floating-progress-fill").style.width = "0%";
|
|
|
|
|
|
document.getElementById("floating-progress-step").textContent =
|
|
|
|
|
|
"Starting...";
|
|
|
|
|
|
document.getElementById("floating-progress-percent").textContent = "0%";
|
|
|
|
|
|
document.getElementById("floating-progress-log").innerHTML = "";
|
|
|
|
|
|
const dot = panel.querySelector(".progress-dot");
|
|
|
|
|
|
if (dot) dot.classList.remove("completed", "error");
|
|
|
|
|
|
panel.style.display = "block";
|
|
|
|
|
|
panel.classList.remove("minimized");
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function updateFloatingProgress(step, message, current, total, details) {
|
|
|
|
|
|
const panel = document.getElementById("floating-progress");
|
|
|
|
|
|
if (panel.style.display === "none") {
|
|
|
|
|
|
showFloatingProgress(message);
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
const percent = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
|
|
|
|
document.getElementById("floating-progress-fill").style.width =
|
|
|
|
|
|
percent + "%";
|
|
|
|
|
|
document.getElementById("floating-progress-step").textContent = message;
|
|
|
|
|
|
document.getElementById("floating-progress-percent").textContent =
|
|
|
|
|
|
percent + "%";
|
|
|
|
|
|
|
|
|
|
|
|
// Add log entry
|
|
|
|
|
|
if (step) {
|
|
|
|
|
|
const log = document.getElementById("floating-progress-log");
|
|
|
|
|
|
const entry = document.createElement("div");
|
|
|
|
|
|
entry.className = "log-entry";
|
|
|
|
|
|
const time = new Date().toLocaleTimeString();
|
|
|
|
|
|
entry.innerHTML = `<span class="log-time">${time}</span> <span class="log-step">[${step}]</span> ${message}`;
|
|
|
|
|
|
if (details) {
|
|
|
|
|
|
entry.innerHTML += `<br><span class="log-details">→ ${details}</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
log.appendChild(entry);
|
|
|
|
|
|
log.scrollTop = log.scrollHeight;
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
2025-12-30 22:42:54 -03:00
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function completeFloatingProgress(message) {
|
|
|
|
|
|
document.getElementById("floating-progress-fill").style.width = "100%";
|
|
|
|
|
|
document.getElementById("floating-progress-step").textContent =
|
|
|
|
|
|
message || "Completed!";
|
|
|
|
|
|
document.getElementById("floating-progress-percent").textContent =
|
|
|
|
|
|
"100%";
|
|
|
|
|
|
const panel = document.getElementById("floating-progress");
|
|
|
|
|
|
const dot = panel.querySelector(".progress-dot");
|
|
|
|
|
|
if (dot) dot.classList.add("completed");
|
|
|
|
|
|
|
|
|
|
|
|
// Refresh task list
|
|
|
|
|
|
htmx.trigger(document.body, "taskCreated");
|
|
|
|
|
|
loadTaskStats();
|
|
|
|
|
|
|
|
|
|
|
|
// Auto-hide after 5 seconds
|
|
|
|
|
|
setTimeout(closeFloatingProgress, 5000);
|
|
|
|
|
|
}
|
2025-12-15 23:16:09 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function errorFloatingProgress(errorMessage) {
|
|
|
|
|
|
document.getElementById("floating-progress-step").textContent =
|
|
|
|
|
|
"Error: " + errorMessage;
|
|
|
|
|
|
const panel = document.getElementById("floating-progress");
|
|
|
|
|
|
const dot = panel.querySelector(".progress-dot");
|
|
|
|
|
|
if (dot) dot.classList.add("error");
|
|
|
|
|
|
}
|
2025-12-15 23:16:09 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
function minimizeFloatingProgress() {
|
|
|
|
|
|
document
|
|
|
|
|
|
.getElementById("floating-progress")
|
|
|
|
|
|
.classList.toggle("minimized");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeFloatingProgress() {
|
|
|
|
|
|
const panel = document.getElementById("floating-progress");
|
|
|
|
|
|
panel.style.display = "none";
|
|
|
|
|
|
const dot = panel.querySelector(".progress-dot");
|
|
|
|
|
|
if (dot) dot.classList.remove("completed", "error");
|
|
|
|
|
|
}
|
2025-12-27 22:38:37 -03:00
|
|
|
|
|
2025-12-30 22:42:54 -03:00
|
|
|
|
// Listen for HTMX events to refresh stats
|
|
|
|
|
|
document.body.addEventListener("htmx:afterSwap", function (e) {
|
|
|
|
|
|
if (e.detail.target.id === "task-list") {
|
|
|
|
|
|
loadTaskStats();
|
|
|
|
|
|
// Show empty state if no tasks
|
|
|
|
|
|
const taskList = document.getElementById("task-list");
|
|
|
|
|
|
const emptyState = document.getElementById("empty-state");
|
|
|
|
|
|
if (taskList && emptyState) {
|
|
|
|
|
|
const hasTasks = taskList.querySelector(".task-card");
|
|
|
|
|
|
emptyState.style.display = hasTasks ? "none" : "flex";
|
2025-12-27 22:38:37 -03:00
|
|
|
|
}
|
2025-12-15 23:16:09 -03:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|