@agenticmail/enterprise 0.5.200 → 0.5.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-heartbeat-EGMBRD3R.js +510 -0
- package/dist/chunk-5C3SCMY5.js +4457 -0
- package/dist/chunk-6OZYUTPL.js +1224 -0
- package/dist/chunk-ZR4Z42HT.js +3679 -0
- package/dist/cli-agent-PLMDHMRR.js +1602 -0
- package/dist/cli-serve-PLBAWN7N.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/app.js +3 -0
- package/dist/dashboard/components/icons.js +1 -0
- package/dist/dashboard/pages/agent-detail/workforce.js +14 -0
- package/dist/dashboard/pages/task-pipeline.js +745 -0
- package/dist/index.js +3 -3
- package/dist/routes-KHABOHOV.js +13273 -0
- package/dist/runtime-6WFHCG3N.js +45 -0
- package/dist/server-BENJQHTB.js +15 -0
- package/dist/setup-7RQIFV5Y.js +20 -0
- package/package.json +1 -1
- package/src/dashboard/app.js +3 -0
- package/src/dashboard/components/icons.js +1 -0
- package/src/dashboard/pages/agent-detail/workforce.js +14 -0
- package/src/dashboard/pages/task-pipeline.js +745 -0
- package/src/engine/model-fallback.ts +141 -0
- package/src/engine/routes.ts +5 -0
- package/src/engine/task-queue-after-spawn.ts +66 -0
- package/src/engine/task-queue-before-spawn.ts +109 -0
- package/src/engine/task-queue-routes.ts +133 -0
- package/src/engine/task-queue.ts +369 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
|
|
2
|
+
import { I } from '../components/icons.js';
|
|
3
|
+
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
|
|
5
|
+
// ─── Layout Constants (matches org-chart) ────────────────
|
|
6
|
+
const NODE_W = 240;
|
|
7
|
+
const NODE_H = 80;
|
|
8
|
+
const H_GAP = 32;
|
|
9
|
+
const V_GAP = 64;
|
|
10
|
+
const PAD = 60;
|
|
11
|
+
|
|
12
|
+
// ─── Colors ──────────────────────────────────────────────
|
|
13
|
+
const STATUS_COLORS = {
|
|
14
|
+
created: '#6366f1',
|
|
15
|
+
assigned: '#f59e0b',
|
|
16
|
+
in_progress: '#06b6d4',
|
|
17
|
+
completed: '#22c55e',
|
|
18
|
+
failed: '#ef4444',
|
|
19
|
+
cancelled: '#6b7394',
|
|
20
|
+
};
|
|
21
|
+
const PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
|
|
22
|
+
const ACCENT = '#6366f1';
|
|
23
|
+
const EDGE_COLOR = 'rgba(255,255,255,0.25)';
|
|
24
|
+
const EDGE_HIGHLIGHT = 'rgba(99,102,241,0.7)';
|
|
25
|
+
const BG = '#0a0c14';
|
|
26
|
+
|
|
27
|
+
// ─── Tree Layout ─────────────────────────────────────────
|
|
28
|
+
// Groups tasks by agent → builds hierarchy: agent nodes at top, task nodes as children
|
|
29
|
+
// Sub-tasks appear as children of their parent task
|
|
30
|
+
function layoutPipeline(tasks, agents) {
|
|
31
|
+
if (!tasks || !tasks.length) return { positioned: [], edges: [], width: 0, height: 0 };
|
|
32
|
+
|
|
33
|
+
// Group tasks by agent
|
|
34
|
+
var agentMap = new Map();
|
|
35
|
+
tasks.forEach(function(t) {
|
|
36
|
+
var key = t.assignedTo || 'unassigned';
|
|
37
|
+
if (!agentMap.has(key)) agentMap.set(key, { agentId: key, name: t.assignedToName || key, tasks: [], children: [], x: 0, y: 0, subtreeW: 0, isAgent: true });
|
|
38
|
+
agentMap.get(key).tasks.push(t);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Build task nodes under each agent
|
|
42
|
+
var allNodes = [];
|
|
43
|
+
var edgeList = [];
|
|
44
|
+
var agentRoots = [];
|
|
45
|
+
|
|
46
|
+
agentMap.forEach(function(agent) {
|
|
47
|
+
agent.subtreeW = 0;
|
|
48
|
+
var taskNodes = [];
|
|
49
|
+
var taskById = new Map();
|
|
50
|
+
|
|
51
|
+
// Create task nodes
|
|
52
|
+
agent.tasks.forEach(function(t) {
|
|
53
|
+
var node = { id: t.id, task: t, children: [], x: 0, y: 0, subtreeW: 0, isAgent: false, agentId: agent.agentId };
|
|
54
|
+
taskById.set(t.id, node);
|
|
55
|
+
taskNodes.push(node);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Link sub-tasks to parents
|
|
59
|
+
var rootTasks = [];
|
|
60
|
+
taskNodes.forEach(function(tn) {
|
|
61
|
+
if (tn.task.parentTaskId && taskById.has(tn.task.parentTaskId)) {
|
|
62
|
+
taskById.get(tn.task.parentTaskId).children.push(tn);
|
|
63
|
+
} else {
|
|
64
|
+
rootTasks.push(tn);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
agent.children = rootTasks;
|
|
69
|
+
agentRoots.push(agent);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Compute subtree widths
|
|
73
|
+
function computeW(node) {
|
|
74
|
+
var w = node.isAgent ? NODE_W : NODE_W;
|
|
75
|
+
if (node.children.length === 0) { node.subtreeW = w; return w; }
|
|
76
|
+
var total = 0;
|
|
77
|
+
node.children.forEach(function(c) { total += computeW(c); });
|
|
78
|
+
total += (node.children.length - 1) * H_GAP;
|
|
79
|
+
node.subtreeW = Math.max(w, total);
|
|
80
|
+
return node.subtreeW;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Assign positions
|
|
84
|
+
function assignPos(node, x, y) {
|
|
85
|
+
var w = node.isAgent ? NODE_W : NODE_W;
|
|
86
|
+
node.x = x + node.subtreeW / 2 - w / 2;
|
|
87
|
+
node.y = y;
|
|
88
|
+
if (node.children.length === 0) return;
|
|
89
|
+
var childrenW = node.children.reduce(function(s, c) { return s + c.subtreeW; }, 0) + (node.children.length - 1) * H_GAP;
|
|
90
|
+
var cx = node.x + w / 2 - childrenW / 2;
|
|
91
|
+
node.children.forEach(function(c) {
|
|
92
|
+
assignPos(c, cx, y + NODE_H + V_GAP);
|
|
93
|
+
cx += c.subtreeW + H_GAP;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Layout agent roots side by side
|
|
98
|
+
agentRoots.forEach(function(r) { computeW(r); });
|
|
99
|
+
var cx = PAD;
|
|
100
|
+
agentRoots.forEach(function(r) {
|
|
101
|
+
assignPos(r, cx, PAD);
|
|
102
|
+
cx += r.subtreeW + H_GAP * 2;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Flatten + collect edges
|
|
106
|
+
var maxX = 0, maxY = 0;
|
|
107
|
+
function flatten(node, parent) {
|
|
108
|
+
allNodes.push(node);
|
|
109
|
+
var w = NODE_W;
|
|
110
|
+
maxX = Math.max(maxX, node.x + w);
|
|
111
|
+
maxY = Math.max(maxY, node.y + NODE_H);
|
|
112
|
+
if (parent) edgeList.push({ parent: parent, child: node });
|
|
113
|
+
node.children.forEach(function(c) { flatten(c, node); });
|
|
114
|
+
}
|
|
115
|
+
agentRoots.forEach(function(r) { flatten(r, null); });
|
|
116
|
+
|
|
117
|
+
return { positioned: allNodes, edges: edgeList, width: maxX + PAD, height: maxY + PAD + 40 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── SVG Edge (curved, matches org-chart) ────────────────
|
|
121
|
+
function edgePath(parent, child) {
|
|
122
|
+
var x1 = parent.x + NODE_W / 2;
|
|
123
|
+
var y1 = parent.y + NODE_H;
|
|
124
|
+
var x2 = child.x + NODE_W / 2;
|
|
125
|
+
var y2 = child.y;
|
|
126
|
+
var midY = y1 + (y2 - y1) * 0.5;
|
|
127
|
+
return 'M ' + x1 + ' ' + y1 + ' C ' + x1 + ' ' + midY + ', ' + x2 + ' ' + midY + ', ' + x2 + ' ' + y2;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Helpers ─────────────────────────────────────────────
|
|
131
|
+
function timeAgo(ts) {
|
|
132
|
+
if (!ts) return '-';
|
|
133
|
+
var diff = Date.now() - new Date(ts).getTime();
|
|
134
|
+
if (diff < 5000) return 'just now';
|
|
135
|
+
if (diff < 60000) return Math.floor(diff / 1000) + 's ago';
|
|
136
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
|
|
137
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
|
|
138
|
+
return Math.floor(diff / 86400000) + 'd ago';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function formatDuration(ms) {
|
|
142
|
+
if (!ms) return '-';
|
|
143
|
+
var s = Math.floor(ms / 1000);
|
|
144
|
+
if (s < 60) return s + 's';
|
|
145
|
+
var m = Math.floor(s / 60);
|
|
146
|
+
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
|
147
|
+
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var CATEGORY_ICONS = { email: '\u2709', research: '\uD83D\uDD0D', meeting: '\uD83D\uDCC5', workflow: '\u2699', writing: '\u270F', deployment: '\uD83D\uDE80', review: '\u2714', monitoring: '\uD83D\uDCE1', custom: '\uD83D\uDCCB' };
|
|
151
|
+
|
|
152
|
+
function tagStyle(color) {
|
|
153
|
+
return { fontSize: 9, fontWeight: 600, padding: '1px 5px', borderRadius: 4, background: color + '22', color: color, letterSpacing: '0.02em' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var toolbarBtnStyle = {
|
|
157
|
+
background: 'rgba(255,255,255,0.08)',
|
|
158
|
+
border: '1px solid rgba(255,255,255,0.12)',
|
|
159
|
+
borderRadius: 6,
|
|
160
|
+
color: '#fff',
|
|
161
|
+
fontSize: 14,
|
|
162
|
+
fontWeight: 600,
|
|
163
|
+
padding: '4px 8px',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
lineHeight: '1.2',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function legendDot(color, label) {
|
|
169
|
+
return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
170
|
+
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: color } }),
|
|
171
|
+
h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 12 } }, label)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function tooltipRow(label, value, color) {
|
|
176
|
+
return h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 11 } },
|
|
177
|
+
h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, label),
|
|
178
|
+
h('span', { style: { fontWeight: 600, color: color || '#fff' } }, value)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─── Help tooltip styles ─────────────────────────────────
|
|
183
|
+
var _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
|
|
184
|
+
var _ul = { paddingLeft: 20, margin: '4px 0 8px' };
|
|
185
|
+
var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
|
|
186
|
+
|
|
187
|
+
// ─── Task Detail Modal ───────────────────────────────────
|
|
188
|
+
function TaskDetail(props) {
|
|
189
|
+
var task = props.task;
|
|
190
|
+
var onClose = props.onClose;
|
|
191
|
+
var onCancel = props.onCancel;
|
|
192
|
+
if (!task) return null;
|
|
193
|
+
var statusColor = STATUS_COLORS[task.status] || '#6b7394';
|
|
194
|
+
|
|
195
|
+
return h('div', { className: 'modal-overlay', onClick: onClose },
|
|
196
|
+
h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 560, maxHeight: '80vh', overflow: 'auto' } },
|
|
197
|
+
h('div', { className: 'modal-header' },
|
|
198
|
+
h('h2', null, task.title),
|
|
199
|
+
h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
|
|
200
|
+
),
|
|
201
|
+
h('div', { className: 'modal-body', style: { padding: 20 } },
|
|
202
|
+
h('div', { style: { display: 'flex', gap: 8, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' } },
|
|
203
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } }, task.status.replace('_', ' ').toUpperCase()),
|
|
204
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, task.priority.toUpperCase()),
|
|
205
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: 'var(--bg-tertiary)', color: 'var(--text-muted)' } }, (CATEGORY_ICONS[task.category] || '') + ' ' + task.category)
|
|
206
|
+
),
|
|
207
|
+
task.description && h('div', { style: { marginBottom: 16, fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)' } }, task.description),
|
|
208
|
+
task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
|
|
209
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, task.progress + '%')),
|
|
210
|
+
h('div', { style: { height: 6, background: 'var(--border)', borderRadius: 3, overflow: 'hidden' } },
|
|
211
|
+
h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3 } })
|
|
212
|
+
)
|
|
213
|
+
),
|
|
214
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 24px', fontSize: 13, marginBottom: 16 } },
|
|
215
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
|
|
216
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
|
|
217
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created'), h('div', null, task.createdAt ? new Date(task.createdAt).toLocaleString() : '-')),
|
|
218
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Started'), h('div', null, task.startedAt ? new Date(task.startedAt).toLocaleString() : '-')),
|
|
219
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Completed'), h('div', null, task.completedAt ? new Date(task.completedAt).toLocaleString() : '-')),
|
|
220
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Duration'), h('div', null, formatDuration(task.actualDurationMs))),
|
|
221
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Model'), h('div', null, task.modelUsed || task.model || '-')),
|
|
222
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Tokens / Cost'), h('div', null, (task.tokensUsed || 0).toLocaleString() + ' / $' + (task.costUsd || 0).toFixed(4)))
|
|
223
|
+
),
|
|
224
|
+
task.error && h('div', { style: { padding: 12, background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 'var(--radius)', marginBottom: 16, fontSize: 13, color: '#ef4444' } }, h('strong', null, 'Error: '), task.error),
|
|
225
|
+
task.result && h('div', { style: { marginBottom: 16 } },
|
|
226
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Result'),
|
|
227
|
+
h('pre', { style: { fontSize: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 200 } }, JSON.stringify(task.result, null, 2))
|
|
228
|
+
),
|
|
229
|
+
(task.status === 'created' || task.status === 'assigned' || task.status === 'in_progress') && h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 16, borderTop: '1px solid var(--border)', paddingTop: 16 } },
|
|
230
|
+
h('button', { className: 'btn btn-danger btn-sm', onClick: function() { onCancel(task.id); } }, 'Cancel Task')
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Main Page ───────────────────────────────────────────
|
|
238
|
+
export function TaskPipelinePage() {
|
|
239
|
+
var app = useApp();
|
|
240
|
+
var toast = app.toast;
|
|
241
|
+
var _tasks = useState([]);
|
|
242
|
+
var tasks = _tasks[0]; var setTasks = _tasks[1];
|
|
243
|
+
var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0 });
|
|
244
|
+
var stats = _stats[0]; var setStats = _stats[1];
|
|
245
|
+
var _loading = useState(true);
|
|
246
|
+
var loading = _loading[0]; var setLoading = _loading[1];
|
|
247
|
+
var _selectedTask = useState(null);
|
|
248
|
+
var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
|
|
249
|
+
var _hoveredId = useState(null);
|
|
250
|
+
var hoveredId = _hoveredId[0]; var setHoveredId = _hoveredId[1];
|
|
251
|
+
var _mousePos = useState({ x: 0, y: 0 });
|
|
252
|
+
var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
|
|
253
|
+
var _zoom = useState(1);
|
|
254
|
+
var zoom = _zoom[0]; var setZoom = _zoom[1];
|
|
255
|
+
var _pan = useState({ x: 0, y: 0 });
|
|
256
|
+
var pan = _pan[0]; var setPan = _pan[1];
|
|
257
|
+
var _dragging = useState(false);
|
|
258
|
+
var dragging = _dragging[0]; var setDragging = _dragging[1];
|
|
259
|
+
var _dragStart = useState({ x: 0, y: 0 });
|
|
260
|
+
var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
|
|
261
|
+
var _filter = useState('active');
|
|
262
|
+
var filter = _filter[0]; var setFilter = _filter[1];
|
|
263
|
+
var containerRef = useRef(null);
|
|
264
|
+
|
|
265
|
+
var loadData = useCallback(function() {
|
|
266
|
+
setLoading(true);
|
|
267
|
+
Promise.all([
|
|
268
|
+
engineCall('/task-pipeline?limit=200'),
|
|
269
|
+
engineCall('/task-pipeline/stats'),
|
|
270
|
+
]).then(function(res) {
|
|
271
|
+
setTasks(res[0]?.tasks || []);
|
|
272
|
+
setStats(res[1] || stats);
|
|
273
|
+
}).catch(function(err) {
|
|
274
|
+
console.error('[TaskPipeline] load:', err);
|
|
275
|
+
}).finally(function() { setLoading(false); });
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
// SSE
|
|
279
|
+
useEffect(function() {
|
|
280
|
+
loadData();
|
|
281
|
+
var baseUrl = window.__ENGINE_BASE || '/api/engine';
|
|
282
|
+
var es;
|
|
283
|
+
try {
|
|
284
|
+
es = new EventSource(baseUrl + '/task-pipeline/stream');
|
|
285
|
+
es.onmessage = function(e) {
|
|
286
|
+
try {
|
|
287
|
+
var event = JSON.parse(e.data);
|
|
288
|
+
if (event.type === 'init') {
|
|
289
|
+
if (event.tasks) setTasks(function(prev) {
|
|
290
|
+
var map = new Map();
|
|
291
|
+
prev.forEach(function(t) { map.set(t.id, t); });
|
|
292
|
+
event.tasks.forEach(function(t) { map.set(t.id, t); });
|
|
293
|
+
return Array.from(map.values()).sort(function(a, b) { return new Date(b.createdAt) - new Date(a.createdAt); });
|
|
294
|
+
});
|
|
295
|
+
if (event.stats) setStats(event.stats);
|
|
296
|
+
} else if (event.task) {
|
|
297
|
+
setTasks(function(prev) {
|
|
298
|
+
var idx = prev.findIndex(function(t) { return t.id === event.task.id; });
|
|
299
|
+
if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
|
|
300
|
+
return [event.task].concat(prev);
|
|
301
|
+
});
|
|
302
|
+
setSelectedTask(function(prev) {
|
|
303
|
+
if (prev && prev.id === event.task.id) return event.task;
|
|
304
|
+
return prev;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {}
|
|
308
|
+
};
|
|
309
|
+
} catch (err) {}
|
|
310
|
+
return function() { if (es) es.close(); };
|
|
311
|
+
}, []);
|
|
312
|
+
|
|
313
|
+
// Periodic stats
|
|
314
|
+
useEffect(function() {
|
|
315
|
+
var iv = setInterval(function() {
|
|
316
|
+
engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
|
|
317
|
+
}, 30000);
|
|
318
|
+
return function() { clearInterval(iv); };
|
|
319
|
+
}, []);
|
|
320
|
+
|
|
321
|
+
var cancelTask = useCallback(function(taskId) {
|
|
322
|
+
engineCall('/task-pipeline/' + taskId + '/cancel', { method: 'POST' }).then(function() {
|
|
323
|
+
toast('Task cancelled', 'success');
|
|
324
|
+
setSelectedTask(null);
|
|
325
|
+
loadData();
|
|
326
|
+
}).catch(function(err) { toast(err.message || 'Failed', 'error'); });
|
|
327
|
+
}, []);
|
|
328
|
+
|
|
329
|
+
// Filter
|
|
330
|
+
var filtered = tasks.filter(function(t) {
|
|
331
|
+
if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
|
|
332
|
+
if (filter === 'completed') return t.status === 'completed';
|
|
333
|
+
if (filter === 'failed') return t.status === 'failed' || t.status === 'cancelled';
|
|
334
|
+
return true;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Layout
|
|
338
|
+
var layout = layoutPipeline(filtered);
|
|
339
|
+
var positioned = layout.positioned;
|
|
340
|
+
var edges = layout.edges;
|
|
341
|
+
var treeW = layout.width;
|
|
342
|
+
var treeH = layout.height;
|
|
343
|
+
|
|
344
|
+
// Zoom
|
|
345
|
+
var handleWheel = useCallback(function(e) {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
var delta = e.deltaY > 0 ? -0.08 : 0.08;
|
|
348
|
+
setZoom(function(z) { return Math.min(3, Math.max(0.15, z + delta)); });
|
|
349
|
+
}, []);
|
|
350
|
+
|
|
351
|
+
// Pan
|
|
352
|
+
var handleMouseDown = useCallback(function(e) {
|
|
353
|
+
if (e.button !== 0) return;
|
|
354
|
+
if (e.target.closest('.task-node')) return;
|
|
355
|
+
setDragging(true);
|
|
356
|
+
setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
|
|
357
|
+
}, [pan]);
|
|
358
|
+
|
|
359
|
+
var handleMouseMove = useCallback(function(e) {
|
|
360
|
+
if (!dragging) return;
|
|
361
|
+
setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
|
|
362
|
+
}, [dragging, dragStart]);
|
|
363
|
+
|
|
364
|
+
var handleMouseUp = useCallback(function() { setDragging(false); }, []);
|
|
365
|
+
|
|
366
|
+
useEffect(function() {
|
|
367
|
+
if (dragging) {
|
|
368
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
369
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
370
|
+
return function() { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); };
|
|
371
|
+
}
|
|
372
|
+
}, [dragging, handleMouseMove, handleMouseUp]);
|
|
373
|
+
|
|
374
|
+
// Fit
|
|
375
|
+
var fitToView = useCallback(function() {
|
|
376
|
+
if (!containerRef.current || !treeW || !treeH) return;
|
|
377
|
+
var rect = containerRef.current.getBoundingClientRect();
|
|
378
|
+
var scaleX = (rect.width - 40) / treeW;
|
|
379
|
+
var scaleY = (rect.height - 40) / treeH;
|
|
380
|
+
var scale = Math.min(scaleX, scaleY, 1.5);
|
|
381
|
+
setZoom(scale);
|
|
382
|
+
setPan({ x: (rect.width - treeW * scale) / 2, y: (rect.height - treeH * scale) / 2 });
|
|
383
|
+
}, [treeW, treeH]);
|
|
384
|
+
|
|
385
|
+
useEffect(function() { if (positioned.length > 0) fitToView(); }, [positioned.length]);
|
|
386
|
+
|
|
387
|
+
// Connected highlight
|
|
388
|
+
var getConnected = useCallback(function(id) {
|
|
389
|
+
var connected = new Set([id]);
|
|
390
|
+
var byId = new Map();
|
|
391
|
+
positioned.forEach(function(n) { byId.set(n.id || n.agentId, n); });
|
|
392
|
+
// Children
|
|
393
|
+
function addDesc(node) {
|
|
394
|
+
(node.children || []).forEach(function(c) { connected.add(c.id || c.agentId); addDesc(c); });
|
|
395
|
+
}
|
|
396
|
+
var node = byId.get(id);
|
|
397
|
+
if (node) addDesc(node);
|
|
398
|
+
// Parent edges
|
|
399
|
+
edges.forEach(function(e) {
|
|
400
|
+
var cId = e.child.id || e.child.agentId;
|
|
401
|
+
var pId = e.parent.id || e.parent.agentId;
|
|
402
|
+
if (cId === id) connected.add(pId);
|
|
403
|
+
});
|
|
404
|
+
return connected;
|
|
405
|
+
}, [positioned, edges]);
|
|
406
|
+
|
|
407
|
+
var connected = hoveredId ? getConnected(hoveredId) : null;
|
|
408
|
+
|
|
409
|
+
// Hovered node for tooltip
|
|
410
|
+
var hoveredNode = hoveredId ? positioned.find(function(n) { return (n.id || n.agentId) === hoveredId; }) : null;
|
|
411
|
+
|
|
412
|
+
if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
|
|
413
|
+
|
|
414
|
+
if (positioned.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
|
|
415
|
+
// Toolbar even when empty
|
|
416
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '12px 20px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)' } },
|
|
417
|
+
h('div', { style: { fontWeight: 700, fontSize: 16, color: '#fff', display: 'flex', alignItems: 'center', gap: 8 } }, I.workflow(), ' Task Pipeline'),
|
|
418
|
+
),
|
|
419
|
+
h('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' } },
|
|
420
|
+
h('div', { style: { fontSize: 48, marginBottom: 16 } }, '\uD83D\uDCCB'),
|
|
421
|
+
h('div', { style: { fontSize: 18, fontWeight: 600, marginBottom: 8, color: '#fff' } }, 'No Tasks in Pipeline'),
|
|
422
|
+
h('div', { style: { color: 'rgba(255,255,255,0.5)' } }, 'Tasks will appear here as agents are assigned work.')
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
|
|
427
|
+
// Toolbar
|
|
428
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '12px 20px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)', flexShrink: 0, flexWrap: 'wrap' } },
|
|
429
|
+
h('div', { style: { fontWeight: 700, fontSize: 16, color: '#fff', display: 'flex', alignItems: 'center', gap: 8 } },
|
|
430
|
+
I.workflow(), ' Task Pipeline',
|
|
431
|
+
h(HelpButton, { label: 'Task Pipeline' },
|
|
432
|
+
h('p', null, 'Visual hierarchy of all agent tasks in your organization. Tasks flow from agents (top) to individual tasks and sub-tasks (below), connected by arrows.'),
|
|
433
|
+
h('h4', { style: _h4 }, 'Interactions'),
|
|
434
|
+
h('ul', { style: _ul },
|
|
435
|
+
h('li', null, h('strong', null, 'Hover'), ' — Highlights the task chain and shows detail tooltip'),
|
|
436
|
+
h('li', null, h('strong', null, 'Click'), ' — Opens full task detail modal'),
|
|
437
|
+
h('li', null, h('strong', null, 'Scroll'), ' — Zoom in/out'),
|
|
438
|
+
h('li', null, h('strong', null, 'Drag'), ' — Pan the canvas')
|
|
439
|
+
),
|
|
440
|
+
h('h4', { style: _h4 }, 'Task Lifecycle'),
|
|
441
|
+
h('ul', { style: _ul },
|
|
442
|
+
h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
|
|
443
|
+
h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
|
|
444
|
+
h('li', null, h('strong', null, 'In Progress'), ' — Actively executing'),
|
|
445
|
+
h('li', null, h('strong', null, 'Completed'), ' — Finished successfully'),
|
|
446
|
+
h('li', null, h('strong', null, 'Failed'), ' — Encountered an error')
|
|
447
|
+
),
|
|
448
|
+
h('div', { style: _tip }, 'Tip: Sub-tasks appear as children of their parent task. The arrow system shows task delegation flow.')
|
|
449
|
+
)
|
|
450
|
+
),
|
|
451
|
+
// Live dot
|
|
452
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
453
|
+
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'pulse 2s infinite' } }),
|
|
454
|
+
h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } }, 'Live')
|
|
455
|
+
),
|
|
456
|
+
// Stats
|
|
457
|
+
h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } },
|
|
458
|
+
(stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
|
|
459
|
+
),
|
|
460
|
+
h('div', { style: { flex: 1 } }),
|
|
461
|
+
// Filter
|
|
462
|
+
['active', 'all', 'completed', 'failed'].map(function(f) {
|
|
463
|
+
return h('button', {
|
|
464
|
+
key: f,
|
|
465
|
+
onClick: function() { setFilter(f); },
|
|
466
|
+
style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px', background: filter === f ? 'rgba(99,102,241,0.3)' : toolbarBtnStyle.background, borderColor: filter === f ? 'rgba(99,102,241,0.5)' : toolbarBtnStyle.border })
|
|
467
|
+
}, f.charAt(0).toUpperCase() + f.slice(1));
|
|
468
|
+
}),
|
|
469
|
+
h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
|
|
470
|
+
// Legend
|
|
471
|
+
legendDot(STATUS_COLORS.in_progress, 'Active'),
|
|
472
|
+
legendDot(STATUS_COLORS.assigned, 'Assigned'),
|
|
473
|
+
legendDot(STATUS_COLORS.completed, 'Done'),
|
|
474
|
+
legendDot(STATUS_COLORS.failed, 'Failed'),
|
|
475
|
+
h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
|
|
476
|
+
// Zoom
|
|
477
|
+
h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
|
|
478
|
+
h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 12, minWidth: 40, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
479
|
+
h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
|
|
480
|
+
h('button', { onClick: fitToView, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Fit'),
|
|
481
|
+
h('button', { onClick: loadData, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Refresh'),
|
|
482
|
+
),
|
|
483
|
+
|
|
484
|
+
// Canvas
|
|
485
|
+
h('div', {
|
|
486
|
+
ref: containerRef,
|
|
487
|
+
style: { flex: 1, overflow: 'hidden', cursor: dragging ? 'grabbing' : 'grab', position: 'relative' },
|
|
488
|
+
onMouseDown: handleMouseDown,
|
|
489
|
+
onWheel: handleWheel,
|
|
490
|
+
},
|
|
491
|
+
h('div', { style: { transform: 'translate(' + pan.x + 'px, ' + pan.y + 'px) scale(' + zoom + ')', transformOrigin: '0 0', position: 'absolute', top: 0, left: 0 } },
|
|
492
|
+
// SVG edges
|
|
493
|
+
h('svg', { width: treeW, height: treeH, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
|
|
494
|
+
h('defs', null,
|
|
495
|
+
h('marker', { id: 'task-arrow', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
|
|
496
|
+
h('polygon', { points: '0 0, 8 3, 0 6', fill: EDGE_COLOR })
|
|
497
|
+
),
|
|
498
|
+
h('marker', { id: 'task-arrow-hl', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
|
|
499
|
+
h('polygon', { points: '0 0, 8 3, 0 6', fill: EDGE_HIGHLIGHT })
|
|
500
|
+
)
|
|
501
|
+
),
|
|
502
|
+
edges.map(function(e, i) {
|
|
503
|
+
var pId = e.parent.id || e.parent.agentId;
|
|
504
|
+
var cId = e.child.id || e.child.agentId;
|
|
505
|
+
var isHl = connected && connected.has(pId) && connected.has(cId);
|
|
506
|
+
var dim = connected && !isHl;
|
|
507
|
+
return h('path', {
|
|
508
|
+
key: i,
|
|
509
|
+
d: edgePath(e.parent, e.child),
|
|
510
|
+
stroke: isHl ? EDGE_HIGHLIGHT : dim ? 'rgba(255,255,255,0.06)' : EDGE_COLOR,
|
|
511
|
+
strokeWidth: isHl ? 2.5 : 1.5,
|
|
512
|
+
fill: 'none',
|
|
513
|
+
markerEnd: isHl ? 'url(#task-arrow-hl)' : 'url(#task-arrow)',
|
|
514
|
+
style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.3 : 1 },
|
|
515
|
+
});
|
|
516
|
+
})
|
|
517
|
+
),
|
|
518
|
+
|
|
519
|
+
// Nodes
|
|
520
|
+
positioned.map(function(node) {
|
|
521
|
+
var nodeId = node.id || node.agentId;
|
|
522
|
+
var isHovered = hoveredId === nodeId;
|
|
523
|
+
var dim = connected && !connected.has(nodeId);
|
|
524
|
+
|
|
525
|
+
if (node.isAgent) {
|
|
526
|
+
// Agent node (top-level, like org-chart node)
|
|
527
|
+
var taskCount = (node.children || []).length;
|
|
528
|
+
return h('div', {
|
|
529
|
+
key: nodeId,
|
|
530
|
+
className: 'task-node',
|
|
531
|
+
onMouseEnter: function(ev) { setHoveredId(nodeId); setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
532
|
+
onMouseMove: function(ev) { if (isHovered) setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
533
|
+
onMouseLeave: function() { setHoveredId(null); },
|
|
534
|
+
style: {
|
|
535
|
+
position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
|
|
536
|
+
background: isHovered ? 'rgba(99,102,241,0.12)' : 'rgba(255,255,255,0.03)',
|
|
537
|
+
border: '1.5px solid ' + (isHovered ? 'rgba(99,102,241,0.5)' : 'rgba(255,255,255,0.12)'),
|
|
538
|
+
borderRadius: 12, padding: '10px 14px', cursor: 'default',
|
|
539
|
+
transition: 'all 0.2s', opacity: dim ? 0.2 : 1,
|
|
540
|
+
backdropFilter: 'blur(8px)',
|
|
541
|
+
display: 'flex', alignItems: 'center', gap: 10, userSelect: 'none',
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
h('div', { style: { width: 36, height: 36, borderRadius: '50%', background: 'linear-gradient(135deg, ' + ACCENT + '44, ' + ACCENT + '11)', border: '2px solid ' + ACCENT, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontWeight: 700, color: ACCENT, flexShrink: 0 } },
|
|
545
|
+
(node.name || '?').charAt(0).toUpperCase()
|
|
546
|
+
),
|
|
547
|
+
h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
|
|
548
|
+
h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, node.name),
|
|
549
|
+
h('div', { style: { fontSize: 11, color: 'rgba(255,255,255,0.45)', marginTop: 2 } }, 'Agent'),
|
|
550
|
+
h('div', { style: { display: 'flex', gap: 4, marginTop: 4 } },
|
|
551
|
+
h('span', { style: tagStyle(ACCENT) }, taskCount + ' task' + (taskCount !== 1 ? 's' : ''))
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Task node
|
|
558
|
+
var t = node.task;
|
|
559
|
+
var statusColor = STATUS_COLORS[t.status] || '#6b7394';
|
|
560
|
+
var priColor = PRIORITY_COLORS[t.priority] || '#6366f1';
|
|
561
|
+
var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
|
|
562
|
+
|
|
563
|
+
return h('div', {
|
|
564
|
+
key: nodeId,
|
|
565
|
+
className: 'task-node',
|
|
566
|
+
onClick: function() { setSelectedTask(t); },
|
|
567
|
+
onMouseEnter: function(ev) { setHoveredId(nodeId); setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
568
|
+
onMouseMove: function(ev) { if (isHovered) setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
569
|
+
onMouseLeave: function() { setHoveredId(null); },
|
|
570
|
+
style: {
|
|
571
|
+
position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
|
|
572
|
+
background: isHovered ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
|
|
573
|
+
border: '1.5px solid ' + (isHovered ? statusColor + '88' : 'rgba(255,255,255,0.12)'),
|
|
574
|
+
borderLeft: '3px solid ' + statusColor,
|
|
575
|
+
borderRadius: 12, padding: '8px 12px', cursor: 'pointer',
|
|
576
|
+
transition: 'all 0.2s', opacity: dim ? 0.2 : 1,
|
|
577
|
+
backdropFilter: 'blur(8px)',
|
|
578
|
+
display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 4, userSelect: 'none',
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
// Title row
|
|
582
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
583
|
+
h('span', { style: { fontSize: 12 } }, catIcon),
|
|
584
|
+
h('span', { style: { fontSize: 12, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
|
|
585
|
+
),
|
|
586
|
+
// Status + priority + time
|
|
587
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
|
|
588
|
+
h('span', { style: tagStyle(statusColor) }, t.status.replace('_', ' ')),
|
|
589
|
+
h('span', { style: tagStyle(priColor) }, t.priority),
|
|
590
|
+
h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.35)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
|
|
591
|
+
),
|
|
592
|
+
// Progress bar
|
|
593
|
+
t.status === 'in_progress' && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 1, overflow: 'hidden', marginTop: 2 } },
|
|
594
|
+
h('div', { style: { height: '100%', width: t.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 1 } })
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
})
|
|
598
|
+
)
|
|
599
|
+
),
|
|
600
|
+
|
|
601
|
+
// Hover tooltip
|
|
602
|
+
hoveredNode && !hoveredNode.isAgent && hoveredNode.task && h('div', { style: {
|
|
603
|
+
position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
|
|
604
|
+
background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
|
|
605
|
+
border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
|
|
606
|
+
padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 200, maxWidth: 300,
|
|
607
|
+
}},
|
|
608
|
+
h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', marginBottom: 8 } }, hoveredNode.task.title),
|
|
609
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } },
|
|
610
|
+
tooltipRow('Status', hoveredNode.task.status.replace('_', ' '), STATUS_COLORS[hoveredNode.task.status]),
|
|
611
|
+
tooltipRow('Priority', hoveredNode.task.priority, PRIORITY_COLORS[hoveredNode.task.priority]),
|
|
612
|
+
tooltipRow('Agent', hoveredNode.task.assignedToName || '-'),
|
|
613
|
+
tooltipRow('Category', hoveredNode.task.category),
|
|
614
|
+
hoveredNode.task.modelUsed && tooltipRow('Model', hoveredNode.task.modelUsed),
|
|
615
|
+
hoveredNode.task.startedAt && tooltipRow('Started', timeAgo(hoveredNode.task.startedAt)),
|
|
616
|
+
hoveredNode.task.actualDurationMs && tooltipRow('Duration', formatDuration(hoveredNode.task.actualDurationMs)),
|
|
617
|
+
hoveredNode.task.progress > 0 && tooltipRow('Progress', hoveredNode.task.progress + '%', STATUS_COLORS.in_progress)
|
|
618
|
+
)
|
|
619
|
+
),
|
|
620
|
+
|
|
621
|
+
// Agent hover tooltip
|
|
622
|
+
hoveredNode && hoveredNode.isAgent && h('div', { style: {
|
|
623
|
+
position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
|
|
624
|
+
background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
|
|
625
|
+
border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
|
|
626
|
+
padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 180,
|
|
627
|
+
}},
|
|
628
|
+
h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.name),
|
|
629
|
+
tooltipRow('Tasks', String((hoveredNode.children || []).length), ACCENT)
|
|
630
|
+
),
|
|
631
|
+
|
|
632
|
+
// Task detail modal
|
|
633
|
+
selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ─── Agent Task Pipeline (for agent-detail workforce tab) ─
|
|
638
|
+
// Reusable mini version scoped to a single agent
|
|
639
|
+
export function AgentTaskPipeline(props) {
|
|
640
|
+
var agentId = props.agentId;
|
|
641
|
+
var _tasks = useState([]);
|
|
642
|
+
var tasks = _tasks[0]; var setTasks = _tasks[1];
|
|
643
|
+
var _loading = useState(true);
|
|
644
|
+
var loading = _loading[0]; var setLoading = _loading[1];
|
|
645
|
+
var _selectedTask = useState(null);
|
|
646
|
+
var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
|
|
647
|
+
var app = useApp();
|
|
648
|
+
var toast = app.toast;
|
|
649
|
+
|
|
650
|
+
useEffect(function() {
|
|
651
|
+
setLoading(true);
|
|
652
|
+
engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
|
|
653
|
+
setTasks(res?.tasks || []);
|
|
654
|
+
}).catch(function() {}).finally(function() { setLoading(false); });
|
|
655
|
+
|
|
656
|
+
// SSE
|
|
657
|
+
var baseUrl = window.__ENGINE_BASE || '/api/engine';
|
|
658
|
+
var es;
|
|
659
|
+
try {
|
|
660
|
+
es = new EventSource(baseUrl + '/task-pipeline/stream');
|
|
661
|
+
es.onmessage = function(e) {
|
|
662
|
+
try {
|
|
663
|
+
var event = JSON.parse(e.data);
|
|
664
|
+
if (event.task && event.task.assignedTo === agentId) {
|
|
665
|
+
setTasks(function(prev) {
|
|
666
|
+
var idx = prev.findIndex(function(t) { return t.id === event.task.id; });
|
|
667
|
+
if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
|
|
668
|
+
return [event.task].concat(prev);
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
} catch (err) {}
|
|
672
|
+
};
|
|
673
|
+
} catch (err) {}
|
|
674
|
+
return function() { if (es) es.close(); };
|
|
675
|
+
}, [agentId]);
|
|
676
|
+
|
|
677
|
+
var cancelTask = useCallback(function(taskId) {
|
|
678
|
+
engineCall('/task-pipeline/' + taskId + '/cancel', { method: 'POST' }).then(function() {
|
|
679
|
+
toast('Task cancelled', 'success');
|
|
680
|
+
setSelectedTask(null);
|
|
681
|
+
}).catch(function(err) { toast(err.message, 'error'); });
|
|
682
|
+
}, []);
|
|
683
|
+
|
|
684
|
+
if (loading) return h('div', { style: { padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'Loading tasks...');
|
|
685
|
+
if (!tasks.length) return h('div', { style: { padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'No pipeline tasks for this agent yet.');
|
|
686
|
+
|
|
687
|
+
var active = tasks.filter(function(t) { return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress'; });
|
|
688
|
+
var completed = tasks.filter(function(t) { return t.status === 'completed'; });
|
|
689
|
+
var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
|
|
690
|
+
|
|
691
|
+
function renderTaskRow(t) {
|
|
692
|
+
var statusColor = STATUS_COLORS[t.status] || '#6b7394';
|
|
693
|
+
var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
|
|
694
|
+
return h('div', {
|
|
695
|
+
key: t.id,
|
|
696
|
+
onClick: function() { setSelectedTask(t); },
|
|
697
|
+
style: {
|
|
698
|
+
display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px',
|
|
699
|
+
borderBottom: '1px solid var(--border)', cursor: 'pointer',
|
|
700
|
+
transition: 'background 0.15s',
|
|
701
|
+
},
|
|
702
|
+
onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
|
|
703
|
+
onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
|
|
704
|
+
},
|
|
705
|
+
h('span', { style: { fontSize: 14, flexShrink: 0 } }, catIcon),
|
|
706
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
707
|
+
h('div', { style: { fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
|
|
708
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 2 } }, t.category + ' \u00B7 ' + timeAgo(t.createdAt))
|
|
709
|
+
),
|
|
710
|
+
t.status === 'in_progress' && t.progress > 0 && h('div', { style: { width: 40, fontSize: 11, color: STATUS_COLORS.in_progress, fontWeight: 600 } }, t.progress + '%'),
|
|
711
|
+
h('span', { style: { padding: '2px 8px', borderRadius: 10, fontSize: 10, fontWeight: 600, background: statusColor + '22', color: statusColor, flexShrink: 0 } }, t.status.replace('_', ' ')),
|
|
712
|
+
t.actualDurationMs && h('span', { style: { fontSize: 11, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return h(Fragment, null,
|
|
717
|
+
// Active
|
|
718
|
+
active.length > 0 && h('div', { style: { marginBottom: 16 } },
|
|
719
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
720
|
+
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'pulse 2s infinite' } }),
|
|
721
|
+
'Active (' + active.length + ')'
|
|
722
|
+
),
|
|
723
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
724
|
+
active.map(renderTaskRow)
|
|
725
|
+
)
|
|
726
|
+
),
|
|
727
|
+
// Completed
|
|
728
|
+
completed.length > 0 && h('div', { style: { marginBottom: 16 } },
|
|
729
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 8 } }, 'Completed (' + completed.length + ')'),
|
|
730
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
731
|
+
completed.slice(0, 10).map(renderTaskRow)
|
|
732
|
+
),
|
|
733
|
+
completed.length > 10 && h('div', { style: { padding: 8, textAlign: 'center', fontSize: 12, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
|
|
734
|
+
),
|
|
735
|
+
// Failed
|
|
736
|
+
failed.length > 0 && h('div', { style: { marginBottom: 16 } },
|
|
737
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 8 } }, 'Failed (' + failed.length + ')'),
|
|
738
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
739
|
+
failed.slice(0, 5).map(renderTaskRow)
|
|
740
|
+
)
|
|
741
|
+
),
|
|
742
|
+
// Detail modal
|
|
743
|
+
selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
|
|
744
|
+
);
|
|
745
|
+
}
|