145 lines
6.1 KiB
JavaScript
145 lines
6.1 KiB
JavaScript
// ==========================================
|
|
// Chat Switchboard — Task Sidebar Section
|
|
// ==========================================
|
|
// Sidebar section showing user's scheduled tasks with status indicators.
|
|
// Click opens the task's output channel. Same pattern as WorkflowQueue.
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
const TaskSidebar = {
|
|
|
|
_loaded: false,
|
|
|
|
// ── Init ────────────────────────────
|
|
|
|
async init() {
|
|
if (this._loaded) return;
|
|
this._loaded = true;
|
|
|
|
// Insert after Queue section (or after Channels if no queue)
|
|
var anchor = document.getElementById('sbSectionQueue') ||
|
|
document.getElementById('sbSectionChannels');
|
|
if (!anchor) return;
|
|
|
|
var section = document.createElement('div');
|
|
section.className = 'sb-section';
|
|
section.id = 'sbSectionTasks';
|
|
section.innerHTML =
|
|
'<div class="sb-section-header" data-action="UI.toggleSidebarSection" data-args=\'["tasks"]\'>' +
|
|
'<span class="sb-section-arrow" id="sbArrowTasks">\u25be</span>' +
|
|
'<span class="sb-section-label">Tasks</span>' +
|
|
'<span class="sb-queue-badge" id="sbTaskBadge" style="display:none">0</span>' +
|
|
'</div>' +
|
|
'<div class="sb-section-body" id="sbBodyTasks"></div>';
|
|
|
|
anchor.parentNode.insertBefore(section, anchor.nextSibling);
|
|
this.refresh();
|
|
},
|
|
|
|
// ── Refresh ─────────────────────────
|
|
|
|
async refresh() {
|
|
var body = document.getElementById('sbBodyTasks');
|
|
var badge = document.getElementById('sbTaskBadge');
|
|
if (!body) return;
|
|
|
|
try {
|
|
// Personal tasks
|
|
var resp = await API._get('/api/v1/tasks');
|
|
var tasks = (resp.data || []).filter(function(t) { return t.is_active; });
|
|
|
|
// Team tasks (v0.27.5)
|
|
try {
|
|
var teamsResp = await API._get('/api/v1/teams/mine');
|
|
var teams = teamsResp.data || teamsResp || [];
|
|
var seen = {};
|
|
tasks.forEach(function(t) { seen[t.id] = true; });
|
|
for (var i = 0; i < teams.length; i++) {
|
|
try {
|
|
var tr = await API._get('/api/v1/teams/' + teams[i].id + '/tasks');
|
|
(tr.data || []).forEach(function(t) {
|
|
if (!seen[t.id] && t.is_active) {
|
|
t._teamName = teams[i].name;
|
|
tasks.push(t);
|
|
seen[t.id] = true;
|
|
}
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
} catch (_) {}
|
|
|
|
if (tasks.length === 0) {
|
|
body.innerHTML = '';
|
|
if (badge) badge.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
if (badge) {
|
|
badge.textContent = tasks.length;
|
|
badge.style.display = '';
|
|
}
|
|
|
|
var html = '';
|
|
tasks.forEach(function(t) {
|
|
// Status indicator
|
|
var icon = '\u25f7'; // default: clock
|
|
var statusTip = 'Scheduled';
|
|
if (t.last_run_at) {
|
|
// Check if most recent run was recent (within 2x schedule interval)
|
|
icon = '\u2713'; // checkmark
|
|
statusTip = 'Last: ' + new Date(t.last_run_at).toLocaleString();
|
|
}
|
|
var nextTip = t.next_run_at
|
|
? 'Next: ' + new Date(t.next_run_at).toLocaleString()
|
|
: '';
|
|
|
|
var channelId = t.output_channel_id || '';
|
|
var onclick = channelId
|
|
? 'selectChannel(\'' + channelId + '\')'
|
|
: 'UI.toast(\'No output channel yet — task has not run\',\'info\')';
|
|
|
|
var label = _esc(t.name);
|
|
if (t._teamName) label = '<span style="opacity:0.6;font-size:10px">[' + _esc(t._teamName) + ']</span> ' + label;
|
|
|
|
html += '<div class="sb-queue-item" onclick="' + onclick + '" title="' +
|
|
_esc(statusTip + (nextTip ? ' \u00b7 ' + nextTip : '')) + '">' +
|
|
'<span class="sb-queue-icon">' + icon + '</span>' +
|
|
'<span class="sb-queue-label">' + label + '</span>' +
|
|
'<button class="btn-small" style="padding:0 4px;font-size:10px" ' +
|
|
'data-action="TaskSidebar.runNow" data-args=\'' + JSON.stringify([t.id]) + '\' title="Run Now">\u25b6</button>' +
|
|
'</div>';
|
|
});
|
|
|
|
body.innerHTML = html;
|
|
} catch (err) {
|
|
body.innerHTML = '';
|
|
if (badge) badge.style.display = 'none';
|
|
}
|
|
},
|
|
|
|
// ── Run Now ─────────────────────────
|
|
|
|
async runNow(taskId) {
|
|
try {
|
|
await API._post('/api/v1/tasks/' + taskId + '/run', {});
|
|
UI.toast('Task triggered', 'success');
|
|
} catch (err) {
|
|
UI.toast(err.message || 'Failed to run task', 'error');
|
|
}
|
|
}
|
|
};
|
|
sb.ns('TaskSidebar', TaskSidebar);
|
|
|
|
function _esc(s) { return s ? s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') : ''; }
|
|
|
|
// Auto-init when chat surface loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(function() {
|
|
if (typeof API !== 'undefined' && API.accessToken) {
|
|
TaskSidebar.init();
|
|
}
|
|
}, 2200); // After WorkflowQueue (2000ms)
|
|
});
|
|
})();
|