@agenticmail/enterprise 0.5.201 → 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.
|
@@ -2,6 +2,13 @@ import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCa
|
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { HelpButton } from '../components/help-button.js';
|
|
4
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
|
+
|
|
5
12
|
// ─── Colors ──────────────────────────────────────────────
|
|
6
13
|
const STATUS_COLORS = {
|
|
7
14
|
created: '#6366f1',
|
|
@@ -12,34 +19,115 @@ const STATUS_COLORS = {
|
|
|
12
19
|
cancelled: '#6b7394',
|
|
13
20
|
};
|
|
14
21
|
const PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
|
|
15
|
-
const 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' };
|
|
16
|
-
const BG = '#0a0c14';
|
|
17
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';
|
|
18
26
|
|
|
19
|
-
// ───
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
});
|
|
23
40
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
];
|
|
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
|
+
});
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
41
128
|
}
|
|
42
129
|
|
|
130
|
+
// ─── Helpers ─────────────────────────────────────────────
|
|
43
131
|
function timeAgo(ts) {
|
|
44
132
|
if (!ts) return '-';
|
|
45
133
|
var diff = Date.now() - new Date(ts).getTime();
|
|
@@ -50,51 +138,57 @@ function timeAgo(ts) {
|
|
|
50
138
|
return Math.floor(diff / 86400000) + 'd ago';
|
|
51
139
|
}
|
|
52
140
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
92
|
-
)
|
|
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)
|
|
93
179
|
);
|
|
94
180
|
}
|
|
95
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
|
+
|
|
96
187
|
// ─── Task Detail Modal ───────────────────────────────────
|
|
97
|
-
function TaskDetail(
|
|
188
|
+
function TaskDetail(props) {
|
|
189
|
+
var task = props.task;
|
|
190
|
+
var onClose = props.onClose;
|
|
191
|
+
var onCancel = props.onCancel;
|
|
98
192
|
if (!task) return null;
|
|
99
193
|
var statusColor = STATUS_COLORS[task.status] || '#6b7394';
|
|
100
194
|
|
|
@@ -105,34 +199,18 @@ function TaskDetail({ task, onClose, onCancel }) {
|
|
|
105
199
|
h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
|
|
106
200
|
),
|
|
107
201
|
h('div', { className: 'modal-body', style: { padding: 20 } },
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12,
|
|
111
|
-
|
|
112
|
-
),
|
|
113
|
-
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } },
|
|
114
|
-
task.priority.toUpperCase()
|
|
115
|
-
),
|
|
116
|
-
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: 'var(--bg-tertiary)', color: 'var(--text-muted)' } },
|
|
117
|
-
(CATEGORY_ICONS[task.category] || '') + ' ' + task.category
|
|
118
|
-
)
|
|
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)
|
|
119
206
|
),
|
|
120
|
-
|
|
121
|
-
// Description
|
|
122
207
|
task.description && h('div', { style: { marginBottom: 16, fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)' } }, task.description),
|
|
123
|
-
|
|
124
|
-
// Progress
|
|
125
208
|
task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
|
|
126
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } },
|
|
127
|
-
h('span', null, 'Progress'),
|
|
128
|
-
h('span', null, task.progress + '%')
|
|
129
|
-
),
|
|
209
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, task.progress + '%')),
|
|
130
210
|
h('div', { style: { height: 6, background: 'var(--border)', borderRadius: 3, overflow: 'hidden' } },
|
|
131
211
|
h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3 } })
|
|
132
212
|
)
|
|
133
213
|
),
|
|
134
|
-
|
|
135
|
-
// Metadata grid
|
|
136
214
|
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 24px', fontSize: 13, marginBottom: 16 } },
|
|
137
215
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
|
|
138
216
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
|
|
@@ -143,21 +221,11 @@ function TaskDetail({ task, onClose, onCancel }) {
|
|
|
143
221
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Model'), h('div', null, task.modelUsed || task.model || '-')),
|
|
144
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)))
|
|
145
223
|
),
|
|
146
|
-
|
|
147
|
-
// Error
|
|
148
|
-
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' } },
|
|
149
|
-
h('strong', null, 'Error: '), task.error
|
|
150
|
-
),
|
|
151
|
-
|
|
152
|
-
// Result
|
|
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),
|
|
153
225
|
task.result && h('div', { style: { marginBottom: 16 } },
|
|
154
226
|
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Result'),
|
|
155
|
-
h('pre', { style: { fontSize: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 200 } },
|
|
156
|
-
JSON.stringify(task.result, null, 2)
|
|
157
|
-
)
|
|
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))
|
|
158
228
|
),
|
|
159
|
-
|
|
160
|
-
// Actions
|
|
161
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 } },
|
|
162
230
|
h('button', { className: 'btn btn-danger btn-sm', onClick: function() { onCancel(task.id); } }, 'Cancel Task')
|
|
163
231
|
)
|
|
@@ -166,290 +234,512 @@ function TaskDetail({ task, onClose, onCancel }) {
|
|
|
166
234
|
);
|
|
167
235
|
}
|
|
168
236
|
|
|
169
|
-
// ───
|
|
170
|
-
function StatsBar({ stats }) {
|
|
171
|
-
var items = [
|
|
172
|
-
{ label: 'Queued', value: stats.created, color: STATUS_COLORS.created },
|
|
173
|
-
{ label: 'Assigned', value: stats.assigned, color: STATUS_COLORS.assigned },
|
|
174
|
-
{ label: 'In Progress', value: stats.inProgress, color: STATUS_COLORS.in_progress },
|
|
175
|
-
{ label: 'Completed', value: stats.completed, color: STATUS_COLORS.completed },
|
|
176
|
-
{ label: 'Failed', value: stats.failed, color: STATUS_COLORS.failed },
|
|
177
|
-
];
|
|
178
|
-
return h('div', { style: { display: 'flex', gap: 16, marginBottom: 20, flexWrap: 'wrap' } },
|
|
179
|
-
items.map(function(it) {
|
|
180
|
-
return h('div', { key: it.label, style: { background: 'var(--bg-secondary)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', padding: '12px 20px', minWidth: 120, textAlign: 'center' } },
|
|
181
|
-
h('div', { style: { fontSize: 28, fontWeight: 700, color: it.color, lineHeight: 1 } }, it.value),
|
|
182
|
-
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, it.label)
|
|
183
|
-
);
|
|
184
|
-
})
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ─── Main Page Component ─────────────────────────────────
|
|
237
|
+
// ─── Main Page ───────────────────────────────────────────
|
|
189
238
|
export function TaskPipelinePage() {
|
|
190
|
-
var
|
|
191
|
-
var
|
|
192
|
-
var
|
|
193
|
-
var [
|
|
194
|
-
var
|
|
195
|
-
var
|
|
196
|
-
var
|
|
197
|
-
var
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
|
|
200
265
|
var loadData = useCallback(function() {
|
|
201
266
|
setLoading(true);
|
|
202
267
|
Promise.all([
|
|
203
268
|
engineCall('/task-pipeline?limit=200'),
|
|
204
269
|
engineCall('/task-pipeline/stats'),
|
|
205
|
-
]).then(function(
|
|
206
|
-
setTasks(
|
|
207
|
-
setStats(
|
|
270
|
+
]).then(function(res) {
|
|
271
|
+
setTasks(res[0]?.tasks || []);
|
|
272
|
+
setStats(res[1] || stats);
|
|
208
273
|
}).catch(function(err) {
|
|
209
|
-
console.error('[TaskPipeline] load
|
|
274
|
+
console.error('[TaskPipeline] load:', err);
|
|
210
275
|
}).finally(function() { setLoading(false); });
|
|
211
276
|
}, []);
|
|
212
277
|
|
|
213
|
-
// SSE
|
|
278
|
+
// SSE
|
|
214
279
|
useEffect(function() {
|
|
215
280
|
loadData();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
event.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
setStats(function(prev) {
|
|
247
|
-
var s = Object.assign({}, prev);
|
|
248
|
-
if (event.type === 'task_created') { s.created++; s.total++; }
|
|
249
|
-
else if (event.type === 'task_completed') { s.completed++; s.inProgress = Math.max(0, s.inProgress - 1); }
|
|
250
|
-
else if (event.type === 'task_failed') { s.failed++; s.inProgress = Math.max(0, s.inProgress - 1); }
|
|
251
|
-
return s;
|
|
252
|
-
});
|
|
253
|
-
// Update selected task if viewing it
|
|
254
|
-
setSelectedTask(function(prev) {
|
|
255
|
-
if (prev && prev.id === event.task.id) return event.task;
|
|
256
|
-
return prev;
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
} catch (err) { /* ignore parse errors */ }
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
es.onerror = function() {
|
|
263
|
-
// Reconnect handled by browser
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return function() {
|
|
267
|
-
if (es) es.close();
|
|
268
|
-
};
|
|
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(); };
|
|
269
311
|
}, []);
|
|
270
312
|
|
|
271
|
-
// Periodic stats
|
|
313
|
+
// Periodic stats
|
|
272
314
|
useEffect(function() {
|
|
273
|
-
var
|
|
315
|
+
var iv = setInterval(function() {
|
|
274
316
|
engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
|
|
275
317
|
}, 30000);
|
|
276
|
-
return function() { clearInterval(
|
|
318
|
+
return function() { clearInterval(iv); };
|
|
277
319
|
}, []);
|
|
278
320
|
|
|
279
321
|
var cancelTask = useCallback(function(taskId) {
|
|
280
|
-
engineCall('/task-pipeline/' + taskId + '/cancel', { method: 'POST' })
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
})
|
|
286
|
-
.catch(function(err) { toast(err.message || 'Failed to cancel', 'error'); });
|
|
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'); });
|
|
287
327
|
}, []);
|
|
288
328
|
|
|
289
|
-
// Filter
|
|
290
|
-
var
|
|
329
|
+
// Filter
|
|
330
|
+
var filtered = tasks.filter(function(t) {
|
|
291
331
|
if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
|
|
292
332
|
if (filter === 'completed') return t.status === 'completed';
|
|
293
333
|
if (filter === 'failed') return t.status === 'failed' || t.status === 'cancelled';
|
|
294
334
|
return true;
|
|
295
335
|
});
|
|
296
336
|
|
|
297
|
-
//
|
|
298
|
-
var
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}, []);
|
|
304
350
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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]);
|
|
308
358
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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'),
|
|
317
434
|
h('ul', { style: _ul },
|
|
318
|
-
h('li', null, '
|
|
319
|
-
h('li', null, '
|
|
320
|
-
h('li', null, '
|
|
321
|
-
h('li', null, '
|
|
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')
|
|
322
439
|
),
|
|
323
440
|
h('h4', { style: _h4 }, 'Task Lifecycle'),
|
|
324
441
|
h('ul', { style: _ul },
|
|
325
|
-
h('li', null, h('strong', null, 'Queued'), ' —
|
|
442
|
+
h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
|
|
326
443
|
h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
|
|
327
|
-
h('li', null, h('strong', null, 'In Progress'), ' —
|
|
328
|
-
h('li', null, h('strong', null, 'Completed'), ' —
|
|
329
|
-
h('li', null, h('strong', null, 'Failed'), ' —
|
|
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')
|
|
330
447
|
),
|
|
331
|
-
h('div', { style: _tip }, 'Tip:
|
|
448
|
+
h('div', { style: _tip }, 'Tip: Sub-tasks appear as children of their parent task. The arrow system shows task delegation flow.')
|
|
332
449
|
)
|
|
333
450
|
),
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
h('div', { style: {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
});
|
|
344
516
|
})
|
|
345
517
|
),
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
+
})
|
|
359
598
|
)
|
|
360
599
|
),
|
|
361
600
|
|
|
362
|
-
//
|
|
363
|
-
h(
|
|
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
|
+
),
|
|
364
620
|
|
|
365
|
-
//
|
|
366
|
-
h('div', { style: {
|
|
367
|
-
|
|
368
|
-
'
|
|
369
|
-
|
|
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)
|
|
370
630
|
),
|
|
371
631
|
|
|
372
|
-
//
|
|
373
|
-
|
|
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); },
|
|
374
697
|
style: {
|
|
375
|
-
display: '
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
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 = ''; },
|
|
380
704
|
},
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
colTasks.length === 0
|
|
392
|
-
? h('div', { style: { padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 12 } }, 'No tasks')
|
|
393
|
-
: colTasks.map(function(t) {
|
|
394
|
-
return h(TaskCard, { key: t.id, task: t, onSelect: setSelectedTask });
|
|
395
|
-
})
|
|
396
|
-
)
|
|
397
|
-
);
|
|
398
|
-
})
|
|
399
|
-
),
|
|
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
|
+
}
|
|
400
715
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
h('th', null, 'Priority'),
|
|
411
|
-
h('th', null, 'Category'),
|
|
412
|
-
h('th', null, 'Created'),
|
|
413
|
-
h('th', null, 'Duration'),
|
|
414
|
-
h('th', null, 'Model')
|
|
415
|
-
)
|
|
416
|
-
),
|
|
417
|
-
h('tbody', null,
|
|
418
|
-
filteredTasks.length === 0
|
|
419
|
-
? h('tr', null, h('td', { colSpan: 8, style: { textAlign: 'center', color: 'var(--text-muted)', padding: 40 } }, 'No tasks found'))
|
|
420
|
-
: filteredTasks.map(function(t) {
|
|
421
|
-
var statusColor = STATUS_COLORS[t.status] || '#6b7394';
|
|
422
|
-
return h('tr', {
|
|
423
|
-
key: t.id,
|
|
424
|
-
onClick: function() { setSelectedTask(t); },
|
|
425
|
-
style: { cursor: 'pointer' }
|
|
426
|
-
},
|
|
427
|
-
h('td', { style: { maxWidth: 250, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 500 } }, t.title),
|
|
428
|
-
h('td', { style: { fontSize: 13 } }, t.assignedToName || '-'),
|
|
429
|
-
h('td', null,
|
|
430
|
-
h('span', { style: { padding: '2px 8px', borderRadius: 10, fontSize: 11, fontWeight: 600, background: statusColor + '22', color: statusColor } },
|
|
431
|
-
t.status.replace('_', ' ')
|
|
432
|
-
)
|
|
433
|
-
),
|
|
434
|
-
h('td', null,
|
|
435
|
-
h('span', { style: { fontSize: 11, color: PRIORITY_COLORS[t.priority] || '#6366f1' } }, t.priority)
|
|
436
|
-
),
|
|
437
|
-
h('td', { style: { fontSize: 12 } }, (CATEGORY_ICONS[t.category] || '') + ' ' + t.category),
|
|
438
|
-
h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, timeAgo(t.createdAt)),
|
|
439
|
-
h('td', { style: { fontSize: 12 } }, formatDuration(t.actualDurationMs)),
|
|
440
|
-
h('td', { style: { fontSize: 12, fontFamily: 'var(--font-mono)' } }, t.modelUsed || t.model || '-')
|
|
441
|
-
);
|
|
442
|
-
})
|
|
443
|
-
)
|
|
444
|
-
)
|
|
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)
|
|
445
725
|
)
|
|
446
726
|
),
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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 })
|
|
454
744
|
);
|
|
455
745
|
}
|