@agenticmail/enterprise 0.5.209 → 0.5.211
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,129 +2,154 @@ 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
|
-
// ───
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
5
|
+
// ─── Constants ───────────────────────────────────────────
|
|
6
|
+
var NODE_W = 200;
|
|
7
|
+
var NODE_H = 64;
|
|
8
|
+
var AGENT_W = 160;
|
|
9
|
+
var AGENT_H = 52;
|
|
10
|
+
var H_GAP = 48; // horizontal gap (left→right flow)
|
|
11
|
+
var V_GAP = 24; // vertical gap between lanes
|
|
12
|
+
var PAD = 40;
|
|
13
|
+
|
|
14
|
+
var STATUS_COLORS = { created: '#6366f1', assigned: '#f59e0b', in_progress: '#06b6d4', completed: '#22c55e', failed: '#ef4444', cancelled: '#6b7394' };
|
|
15
|
+
var PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
|
|
16
|
+
var DELEGATION_COLORS = { delegation: '#6366f1', review: '#f59e0b', revision: '#f97316', escalation: '#ef4444', return: '#22c55e' };
|
|
17
|
+
var BG = '#0a0c14';
|
|
18
|
+
var EDGE_COLOR = 'rgba(255,255,255,0.18)';
|
|
19
|
+
var EDGE_HL = 'rgba(99,102,241,0.7)';
|
|
20
|
+
|
|
21
|
+
// ─── CSS Keyframes (injected once) ──────────────────────
|
|
22
|
+
var _injected = false;
|
|
23
|
+
function injectCSS() {
|
|
24
|
+
if (_injected) return; _injected = true;
|
|
25
|
+
var style = document.createElement('style');
|
|
26
|
+
style.textContent = `
|
|
27
|
+
@keyframes flowDash { to { stroke-dashoffset: -24; } }
|
|
28
|
+
@keyframes flowPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
|
|
29
|
+
@keyframes taskPulse { 0%,100% { box-shadow: 0 0 0 0 rgba(6,182,212,0.3); } 50% { box-shadow: 0 0 8px 2px rgba(6,182,212,0.2); } }
|
|
30
|
+
.tp-flow-active { animation: flowDash 1.2s linear infinite; }
|
|
31
|
+
.tp-node-active { animation: taskPulse 2s ease-in-out infinite; }
|
|
32
|
+
.tp-node:hover { transform: scale(1.03); z-index: 10; }
|
|
33
|
+
.tp-customer-badge { background: linear-gradient(135deg, #6366f1, #a855f7); color: #fff; font-size: 9px; padding: 1px 6px; border-radius: 8px; font-weight: 600; }
|
|
34
|
+
.tp-chain-tag { font-size: 9px; padding: 1px 5px; border-radius: 4px; font-weight: 600; letter-spacing: 0.02em; }
|
|
35
|
+
`;
|
|
36
|
+
document.head.appendChild(style);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Layout: Left-to-Right Chain Flow ────────────────────
|
|
40
|
+
// Tasks flow horizontally. Each chain = a horizontal row.
|
|
41
|
+
// Multiple chains stack vertically. Circular flows curve back.
|
|
42
|
+
|
|
43
|
+
function layoutChains(tasks) {
|
|
44
|
+
if (!tasks.length) return { nodes: [], edges: [], width: 0, height: 0, chains: [] };
|
|
45
|
+
|
|
46
|
+
// Group by chainId
|
|
47
|
+
var chainMap = new Map();
|
|
48
|
+
var orphans = [];
|
|
35
49
|
tasks.forEach(function(t) {
|
|
50
|
+
if (t.chainId) {
|
|
51
|
+
if (!chainMap.has(t.chainId)) chainMap.set(t.chainId, []);
|
|
52
|
+
chainMap.get(t.chainId).push(t);
|
|
53
|
+
} else {
|
|
54
|
+
orphans.push(t);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Sort each chain by chainSeq
|
|
59
|
+
chainMap.forEach(function(arr) { arr.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); }); });
|
|
60
|
+
|
|
61
|
+
// Also group orphans by agent for a simpler layout
|
|
62
|
+
var orphansByAgent = new Map();
|
|
63
|
+
orphans.forEach(function(t) {
|
|
36
64
|
var key = t.assignedTo || 'unassigned';
|
|
37
|
-
if (!
|
|
38
|
-
|
|
65
|
+
if (!orphansByAgent.has(key)) orphansByAgent.set(key, []);
|
|
66
|
+
orphansByAgent.get(key).push(t);
|
|
39
67
|
});
|
|
40
68
|
|
|
41
|
-
// Build task nodes under each agent
|
|
42
69
|
var allNodes = [];
|
|
43
|
-
var
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
var allEdges = [];
|
|
71
|
+
var chainInfos = [];
|
|
72
|
+
var y = PAD;
|
|
73
|
+
|
|
74
|
+
// Layout each chain as a horizontal row
|
|
75
|
+
chainMap.forEach(function(chainTasks, chainId) {
|
|
76
|
+
var x = PAD;
|
|
77
|
+
var rowNodes = [];
|
|
78
|
+
var maxH = NODE_H;
|
|
79
|
+
|
|
80
|
+
chainTasks.forEach(function(t, i) {
|
|
81
|
+
var node = { id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: chainId };
|
|
82
|
+
rowNodes.push(node);
|
|
83
|
+
allNodes.push(node);
|
|
84
|
+
|
|
85
|
+
if (i > 0) {
|
|
86
|
+
var prev = rowNodes[i - 1];
|
|
87
|
+
var dtype = t.delegationType || 'delegation';
|
|
88
|
+
var isReturn = dtype === 'return' || dtype === 'revision';
|
|
89
|
+
// Check for circular: does this task go back to an agent already seen?
|
|
90
|
+
var seenAgents = chainTasks.slice(0, i).map(function(ct) { return ct.assignedTo; });
|
|
91
|
+
var isCircular = seenAgents.indexOf(t.assignedTo) !== -1 && isReturn;
|
|
92
|
+
|
|
93
|
+
allEdges.push({
|
|
94
|
+
from: prev, to: node,
|
|
95
|
+
delegationType: dtype,
|
|
96
|
+
isCircular: isCircular,
|
|
97
|
+
isActive: prev.task.status === 'in_progress' || t.status === 'in_progress',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
x += NODE_W + H_GAP;
|
|
56
102
|
});
|
|
57
103
|
|
|
58
|
-
//
|
|
59
|
-
var
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
// Customer context badge on first node
|
|
105
|
+
var firstTask = chainTasks[0];
|
|
106
|
+
chainInfos.push({
|
|
107
|
+
chainId: chainId,
|
|
108
|
+
y: y,
|
|
109
|
+
taskCount: chainTasks.length,
|
|
110
|
+
customer: firstTask.customerContext,
|
|
111
|
+
status: chainTasks[chainTasks.length - 1].status,
|
|
112
|
+
title: firstTask.title,
|
|
66
113
|
});
|
|
67
114
|
|
|
68
|
-
|
|
69
|
-
agentRoots.push(agent);
|
|
115
|
+
y += maxH + V_GAP + 16; // extra space between chains
|
|
70
116
|
});
|
|
71
117
|
|
|
72
|
-
//
|
|
73
|
-
function
|
|
74
|
-
var
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
118
|
+
// Layout orphans as simple rows per agent
|
|
119
|
+
orphansByAgent.forEach(function(agentTasks, agentId) {
|
|
120
|
+
var x = PAD;
|
|
121
|
+
agentTasks.forEach(function(t) {
|
|
122
|
+
allNodes.push({ id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: null });
|
|
123
|
+
x += NODE_W + H_GAP;
|
|
94
124
|
});
|
|
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;
|
|
125
|
+
y += NODE_H + V_GAP;
|
|
103
126
|
});
|
|
104
127
|
|
|
105
|
-
|
|
106
|
-
|
|
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); });
|
|
128
|
+
var maxX = 0;
|
|
129
|
+
allNodes.forEach(function(n) { maxX = Math.max(maxX, n.x + n.w); });
|
|
116
130
|
|
|
117
|
-
return {
|
|
131
|
+
return { nodes: allNodes, edges: allEdges, width: maxX + PAD, height: y + PAD, chains: chainInfos };
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
// ─── SVG Edge
|
|
121
|
-
function
|
|
122
|
-
var x1 =
|
|
123
|
-
var y1 =
|
|
124
|
-
var x2 =
|
|
125
|
-
var y2 =
|
|
126
|
-
var
|
|
127
|
-
return 'M ' + x1 + ' ' + y1 + ' C ' +
|
|
134
|
+
// ─── SVG Edge Paths ──────────────────────────────────────
|
|
135
|
+
function horizontalPath(from, to) {
|
|
136
|
+
var x1 = from.x + from.w;
|
|
137
|
+
var y1 = from.y + from.h / 2;
|
|
138
|
+
var x2 = to.x;
|
|
139
|
+
var y2 = to.y + to.h / 2;
|
|
140
|
+
var midX = x1 + (x2 - x1) * 0.5;
|
|
141
|
+
return 'M ' + x1 + ' ' + y1 + ' C ' + midX + ' ' + y1 + ', ' + midX + ' ' + y2 + ', ' + x2 + ' ' + y2;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function circularPath(from, to) {
|
|
145
|
+
// Arc back: goes up and curves back left
|
|
146
|
+
var x1 = from.x + from.w;
|
|
147
|
+
var y1 = from.y + from.h / 2;
|
|
148
|
+
var x2 = to.x;
|
|
149
|
+
var y2 = to.y + to.h / 2;
|
|
150
|
+
var lift = 40;
|
|
151
|
+
var topY = Math.min(y1, y2) - lift;
|
|
152
|
+
return 'M ' + x1 + ' ' + y1 + ' C ' + (x1 + 50) + ' ' + topY + ', ' + (x2 - 50) + ' ' + topY + ', ' + x2 + ' ' + y2;
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
// ─── Helpers ─────────────────────────────────────────────
|
|
@@ -137,7 +162,6 @@ function timeAgo(ts) {
|
|
|
137
162
|
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
|
|
138
163
|
return Math.floor(diff / 86400000) + 'd ago';
|
|
139
164
|
}
|
|
140
|
-
|
|
141
165
|
function formatDuration(ms) {
|
|
142
166
|
if (!ms) return '-';
|
|
143
167
|
var s = Math.floor(ms / 1000);
|
|
@@ -146,86 +170,136 @@ function formatDuration(ms) {
|
|
|
146
170
|
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
|
147
171
|
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
|
148
172
|
}
|
|
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
|
-
}
|
|
173
|
+
function tag(color, text) { return h('span', { className: 'tp-chain-tag', style: { background: color + '22', color: color } }, text); }
|
|
155
174
|
|
|
156
175
|
var toolbarBtnStyle = {
|
|
157
|
-
background: 'rgba(255,255,255,0.08)',
|
|
158
|
-
|
|
159
|
-
borderRadius: 6,
|
|
160
|
-
color: '#fff',
|
|
161
|
-
fontSize: 14,
|
|
162
|
-
fontWeight: 600,
|
|
163
|
-
padding: '4px 8px',
|
|
164
|
-
cursor: 'pointer',
|
|
165
|
-
lineHeight: '1.2',
|
|
176
|
+
background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)',
|
|
177
|
+
borderRadius: 6, color: '#fff', fontSize: 12, fontWeight: 600, padding: '4px 10px', cursor: 'pointer',
|
|
166
178
|
};
|
|
167
|
-
|
|
168
179
|
function legendDot(color, label) {
|
|
169
180
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
170
181
|
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: color } }),
|
|
171
|
-
h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize:
|
|
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)
|
|
182
|
+
h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11 } }, label)
|
|
179
183
|
);
|
|
180
184
|
}
|
|
181
|
-
|
|
182
|
-
// ─── Help tooltip styles ─────────────────────────────────
|
|
183
185
|
var _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
|
|
184
186
|
var _ul = { paddingLeft: 20, margin: '4px 0 8px' };
|
|
185
187
|
var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
|
|
186
188
|
|
|
189
|
+
// ─── Customer Profile Mini-Card ──────────────────────────
|
|
190
|
+
function CustomerBadge(props) {
|
|
191
|
+
var c = props.customer;
|
|
192
|
+
if (!c) return null;
|
|
193
|
+
return h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px', background: 'rgba(99,102,241,0.08)', border: '1px solid rgba(99,102,241,0.2)', borderRadius: 8, fontSize: 11, marginBottom: 4 } },
|
|
194
|
+
h('div', { style: { width: 20, height: 20, borderRadius: '50%', background: 'linear-gradient(135deg, #6366f1, #a855f7)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 9, fontWeight: 700, flexShrink: 0 } }, (c.name || '?').charAt(0).toUpperCase()),
|
|
195
|
+
h('div', { style: { overflow: 'hidden' } },
|
|
196
|
+
h('div', { style: { fontWeight: 600, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, c.name || 'Unknown'),
|
|
197
|
+
h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 9 } },
|
|
198
|
+
c.isNew ? 'New customer' : (c.company || c.email || c.channel || '')
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
// ─── Task Detail Modal ───────────────────────────────────
|
|
188
205
|
function TaskDetail(props) {
|
|
189
206
|
var task = props.task;
|
|
207
|
+
var chain = props.chain;
|
|
190
208
|
var onClose = props.onClose;
|
|
191
209
|
var onCancel = props.onCancel;
|
|
192
210
|
if (!task) return null;
|
|
193
211
|
var statusColor = STATUS_COLORS[task.status] || '#6b7394';
|
|
194
212
|
|
|
195
213
|
return h('div', { className: 'modal-overlay', onClick: onClose },
|
|
196
|
-
h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width:
|
|
214
|
+
h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 640, maxHeight: '85vh', overflow: 'auto' } },
|
|
197
215
|
h('div', { className: 'modal-header' },
|
|
198
|
-
h('h2',
|
|
216
|
+
h('h2', { style: { fontSize: 16 } }, task.title),
|
|
199
217
|
h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
|
|
200
218
|
),
|
|
201
219
|
h('div', { className: 'modal-body', style: { padding: 20 } },
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize:
|
|
205
|
-
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize:
|
|
220
|
+
// Status badges
|
|
221
|
+
h('div', { style: { display: 'flex', gap: 6, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' } },
|
|
222
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } }, task.status.replace('_', ' ').toUpperCase()),
|
|
223
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, task.priority.toUpperCase()),
|
|
224
|
+
task.chainId && h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: 'rgba(99,102,241,0.1)', color: '#6366f1', fontFamily: 'var(--font-mono)' } }, 'Chain #' + task.chainId.slice(0, 8)),
|
|
225
|
+
task.delegationType && tag(DELEGATION_COLORS[task.delegationType] || '#6b7394', task.delegationType)
|
|
206
226
|
),
|
|
227
|
+
|
|
228
|
+
// Customer context
|
|
229
|
+
task.customerContext && h('div', { style: { padding: 12, background: 'rgba(99,102,241,0.06)', border: '1px solid rgba(99,102,241,0.15)', borderRadius: 'var(--radius)', marginBottom: 16 } },
|
|
230
|
+
h('div', { style: { fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.5)', marginBottom: 8 } }, 'CUSTOMER'),
|
|
231
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px 16px', fontSize: 13 } },
|
|
232
|
+
task.customerContext.name && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Name'), h('div', null, task.customerContext.name)),
|
|
233
|
+
task.customerContext.email && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Email'), h('div', null, task.customerContext.email)),
|
|
234
|
+
task.customerContext.company && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Company'), h('div', null, task.customerContext.company)),
|
|
235
|
+
task.customerContext.channel && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Channel'), h('div', null, task.customerContext.channel)),
|
|
236
|
+
h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Type'), h('div', null, task.customerContext.isNew ? 'New Customer' : 'Returning'))
|
|
237
|
+
)
|
|
238
|
+
),
|
|
239
|
+
|
|
207
240
|
task.description && h('div', { style: { marginBottom: 16, fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)' } }, task.description),
|
|
241
|
+
|
|
242
|
+
// Progress
|
|
208
243
|
task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
|
|
209
244
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, task.progress + '%')),
|
|
210
245
|
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 } })
|
|
246
|
+
h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3, transition: 'width 0.3s' } })
|
|
212
247
|
)
|
|
213
248
|
),
|
|
214
|
-
|
|
249
|
+
|
|
250
|
+
// Grid details
|
|
251
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 24px', fontSize: 13, marginBottom: 16 } },
|
|
215
252
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
|
|
216
253
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
|
|
217
254
|
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
255
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Duration'), h('div', null, formatDuration(task.actualDurationMs))),
|
|
221
256
|
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Model'), h('div', null, task.modelUsed || task.model || '-')),
|
|
222
257
|
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
258
|
),
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
h('
|
|
259
|
+
|
|
260
|
+
// Task chain timeline (if part of a chain)
|
|
261
|
+
chain && chain.length > 1 && h('div', { style: { marginBottom: 16 } },
|
|
262
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'DELEGATION CHAIN'),
|
|
263
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 0, overflow: 'auto', padding: '8px 0' } },
|
|
264
|
+
chain.map(function(ct, i) {
|
|
265
|
+
var isMe = ct.id === task.id;
|
|
266
|
+
var sc = STATUS_COLORS[ct.status] || '#6b7394';
|
|
267
|
+
return h(Fragment, { key: ct.id },
|
|
268
|
+
i > 0 && h('div', { style: { display: 'flex', alignItems: 'center', flexShrink: 0 } },
|
|
269
|
+
h('div', { style: { width: 32, height: 2, background: (DELEGATION_COLORS[ct.delegationType] || '#6366f1') + '66' } }),
|
|
270
|
+
h('div', { style: { fontSize: 8, color: 'rgba(255,255,255,0.4)', position: 'relative', top: -8 } }, ct.delegationType || '')
|
|
271
|
+
),
|
|
272
|
+
h('div', { style: {
|
|
273
|
+
padding: '6px 10px', borderRadius: 8, fontSize: 11, flexShrink: 0,
|
|
274
|
+
background: isMe ? sc + '22' : 'rgba(255,255,255,0.03)',
|
|
275
|
+
border: '1px solid ' + (isMe ? sc : 'rgba(255,255,255,0.1)'),
|
|
276
|
+
fontWeight: isMe ? 700 : 400, color: isMe ? sc : 'rgba(255,255,255,0.6)',
|
|
277
|
+
} },
|
|
278
|
+
h('div', { style: { fontWeight: 600 } }, ct.assignedToName || ct.assignedTo),
|
|
279
|
+
h('div', { style: { fontSize: 9, marginTop: 2, opacity: 0.6 } }, ct.status.replace('_', ' '))
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
})
|
|
283
|
+
)
|
|
284
|
+
),
|
|
285
|
+
|
|
286
|
+
// Activity log
|
|
287
|
+
task.activityLog && task.activityLog.length > 0 && h('div', { style: { marginBottom: 16 } },
|
|
288
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'ACTIVITY LOG'),
|
|
289
|
+
h('div', { style: { maxHeight: 180, overflow: 'auto', border: '1px solid var(--border)', borderRadius: 'var(--radius)' } },
|
|
290
|
+
task.activityLog.map(function(entry, i) {
|
|
291
|
+
return h('div', { key: i, style: { display: 'flex', gap: 8, padding: '6px 10px', borderBottom: '1px solid var(--border)', fontSize: 11 } },
|
|
292
|
+
h('span', { style: { color: 'var(--text-muted)', flexShrink: 0, fontFamily: 'var(--font-mono)', fontSize: 10 } }, entry.ts ? new Date(entry.ts).toLocaleTimeString() : ''),
|
|
293
|
+
h('span', { style: { fontWeight: 600, flexShrink: 0, minWidth: 60 } }, entry.type),
|
|
294
|
+
h('span', { style: { color: 'var(--text-secondary)' } }, entry.detail)
|
|
295
|
+
);
|
|
296
|
+
})
|
|
297
|
+
)
|
|
228
298
|
),
|
|
299
|
+
|
|
300
|
+
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),
|
|
301
|
+
|
|
302
|
+
// Actions
|
|
229
303
|
(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
304
|
h('button', { className: 'btn btn-danger btn-sm', onClick: function() { onCancel(task.id); } }, 'Cancel Task')
|
|
231
305
|
)
|
|
@@ -234,22 +308,152 @@ function TaskDetail(props) {
|
|
|
234
308
|
);
|
|
235
309
|
}
|
|
236
310
|
|
|
311
|
+
// ─── Metrics Bar ─────────────────────────────────────────
|
|
312
|
+
function MetricsBar(props) {
|
|
313
|
+
var s = props.stats;
|
|
314
|
+
var metricStyle = { display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '8px 16px', minWidth: 80 };
|
|
315
|
+
var numStyle = function(color) { return { fontSize: 20, fontWeight: 700, color: color, lineHeight: 1 }; };
|
|
316
|
+
var lblStyle = { fontSize: 9, color: 'rgba(255,255,255,0.4)', marginTop: 3, textTransform: 'uppercase', letterSpacing: '0.06em' };
|
|
317
|
+
var divider = h('div', { style: { width: 1, height: 32, background: 'rgba(255,255,255,0.08)', flexShrink: 0 } });
|
|
318
|
+
|
|
319
|
+
return h('div', { style: { display: 'flex', alignItems: 'center', padding: '6px 16px', borderBottom: '1px solid rgba(255,255,255,0.06)', background: 'rgba(0,0,0,0.15)', flexShrink: 0, overflowX: 'auto', gap: 0 } },
|
|
320
|
+
h('div', metricStyle, h('div', numStyle('#22c55e'), s.todayCompleted || 0), h('div', lblStyle, 'Done Today')),
|
|
321
|
+
divider,
|
|
322
|
+
h('div', metricStyle, h('div', numStyle('#06b6d4'), s.inProgress || 0), h('div', lblStyle, 'In Progress')),
|
|
323
|
+
divider,
|
|
324
|
+
h('div', metricStyle, h('div', numStyle('#f59e0b'), s.todayCreated || 0), h('div', lblStyle, 'Created Today')),
|
|
325
|
+
divider,
|
|
326
|
+
h('div', metricStyle, h('div', numStyle('#ef4444'), s.todayFailed || 0), h('div', lblStyle, 'Failed Today')),
|
|
327
|
+
divider,
|
|
328
|
+
h('div', metricStyle, h('div', numStyle('#fff'), formatDuration(s.avgDurationMs)), h('div', lblStyle, 'Avg Duration')),
|
|
329
|
+
divider,
|
|
330
|
+
h('div', metricStyle, h('div', numStyle('#a855f7'), (s.totalTokens || 0) > 999999 ? ((s.totalTokens / 1000000).toFixed(1) + 'M') : (s.totalTokens || 0) > 999 ? ((s.totalTokens / 1000).toFixed(1) + 'K') : (s.totalTokens || 0)), h('div', lblStyle, 'Tokens')),
|
|
331
|
+
divider,
|
|
332
|
+
h('div', metricStyle, h('div', numStyle('#22c55e'), '$' + (s.totalCost || 0).toFixed(2)), h('div', lblStyle, 'Total Cost')),
|
|
333
|
+
divider,
|
|
334
|
+
h('div', metricStyle, h('div', numStyle('rgba(255,255,255,0.6)'), s.total || 0), h('div', lblStyle, 'All Time')),
|
|
335
|
+
// Top agents mini
|
|
336
|
+
s.topAgents && s.topAgents.length > 0 && h(Fragment, null,
|
|
337
|
+
divider,
|
|
338
|
+
h('div', { style: { display: 'flex', gap: 6, padding: '4px 12px', alignItems: 'center' } },
|
|
339
|
+
h('div', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginRight: 4 } }, 'TOP'),
|
|
340
|
+
s.topAgents.slice(0, 3).map(function(a) {
|
|
341
|
+
return h('div', { key: a.agent, style: { display: 'flex', alignItems: 'center', gap: 4, padding: '2px 8px', background: 'rgba(255,255,255,0.04)', borderRadius: 6, fontSize: 10 } },
|
|
342
|
+
h('div', { style: { width: 14, height: 14, borderRadius: '50%', background: '#6366f1' + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: '#6366f1' } }, (a.name || '?').charAt(0).toUpperCase()),
|
|
343
|
+
h('span', { style: { color: '#fff', fontWeight: 600 } }, a.name),
|
|
344
|
+
h('span', { style: { color: 'rgba(255,255,255,0.3)' } }, a.completed + '/' + a.active)
|
|
345
|
+
);
|
|
346
|
+
})
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ─── Inline Chain Flowchart (appears below canvas when task expanded) ─
|
|
353
|
+
function ChainFlowInline(props) {
|
|
354
|
+
var chain = props.chain;
|
|
355
|
+
var taskId = props.taskId;
|
|
356
|
+
var onClose = props.onClose;
|
|
357
|
+
var onClickTask = props.onClickTask;
|
|
358
|
+
if (!chain || chain.length === 0) return null;
|
|
359
|
+
|
|
360
|
+
var MINI_W = 180;
|
|
361
|
+
var MINI_H = 56;
|
|
362
|
+
var MINI_GAP = 40;
|
|
363
|
+
var totalW = chain.length * MINI_W + (chain.length - 1) * MINI_GAP + 40;
|
|
364
|
+
|
|
365
|
+
return h('div', { style: { borderTop: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.25)', padding: '12px 16px', flexShrink: 0, overflow: 'auto' } },
|
|
366
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 } },
|
|
367
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
368
|
+
h('span', { style: { fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.06em' } }, 'TASK FLOW'),
|
|
369
|
+
h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.3)', fontFamily: 'var(--font-mono)' } }, chain[0].chainId ? '#' + chain[0].chainId.slice(0, 8) : ''),
|
|
370
|
+
h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.3)' } }, chain.length + ' step' + (chain.length > 1 ? 's' : ''))
|
|
371
|
+
),
|
|
372
|
+
h('button', { onClick: onClose, style: { background: 'none', border: 'none', color: 'rgba(255,255,255,0.3)', cursor: 'pointer', fontSize: 16, padding: '0 4px' } }, '\u00D7')
|
|
373
|
+
),
|
|
374
|
+
h('div', { style: { position: 'relative', height: MINI_H + 20, minWidth: totalW } },
|
|
375
|
+
// SVG arrows
|
|
376
|
+
h('svg', { width: totalW, height: MINI_H + 20, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
|
|
377
|
+
h('defs', null,
|
|
378
|
+
h('marker', { id: 'cf-arr', markerWidth: 6, markerHeight: 4, refX: 6, refY: 2, orient: 'auto' },
|
|
379
|
+
h('polygon', { points: '0 0, 6 2, 0 4', fill: 'rgba(99,102,241,0.5)' })
|
|
380
|
+
)
|
|
381
|
+
),
|
|
382
|
+
chain.map(function(ct, i) {
|
|
383
|
+
if (i === 0) return null;
|
|
384
|
+
var x1 = 20 + (i - 1) * (MINI_W + MINI_GAP) + MINI_W;
|
|
385
|
+
var x2 = 20 + i * (MINI_W + MINI_GAP);
|
|
386
|
+
var y = 10 + MINI_H / 2;
|
|
387
|
+
var dType = ct.delegationType || 'delegation';
|
|
388
|
+
var color = DELEGATION_COLORS[dType] || 'rgba(99,102,241,0.5)';
|
|
389
|
+
var isActive = chain[i - 1].status === 'in_progress' || ct.status === 'in_progress';
|
|
390
|
+
return h(Fragment, { key: 'e' + i },
|
|
391
|
+
h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: color + '88', strokeWidth: 2, markerEnd: 'url(#cf-arr)' }),
|
|
392
|
+
isActive && h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, strokeDasharray: '4 12', className: 'tp-flow-active', style: { opacity: 0.7 } }),
|
|
393
|
+
dType !== 'delegation' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: color, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, dType)
|
|
394
|
+
);
|
|
395
|
+
})
|
|
396
|
+
),
|
|
397
|
+
// Task nodes
|
|
398
|
+
chain.map(function(ct, i) {
|
|
399
|
+
var x = 20 + i * (MINI_W + MINI_GAP);
|
|
400
|
+
var sc = STATUS_COLORS[ct.status] || '#6b7394';
|
|
401
|
+
var isMe = ct.id === taskId;
|
|
402
|
+
var isActive = ct.status === 'in_progress';
|
|
403
|
+
return h('div', {
|
|
404
|
+
key: ct.id,
|
|
405
|
+
onClick: function() { if (onClickTask) onClickTask(ct); },
|
|
406
|
+
style: {
|
|
407
|
+
position: 'absolute', left: x, top: 10, width: MINI_W, height: MINI_H,
|
|
408
|
+
background: isMe ? sc + '15' : 'rgba(255,255,255,0.02)',
|
|
409
|
+
border: '1.5px solid ' + (isMe ? sc : 'rgba(255,255,255,0.1)'),
|
|
410
|
+
borderLeft: '3px solid ' + sc,
|
|
411
|
+
borderRadius: 8, padding: '6px 10px', cursor: 'pointer',
|
|
412
|
+
display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3,
|
|
413
|
+
transition: 'all 0.15s',
|
|
414
|
+
},
|
|
415
|
+
onMouseEnter: function(e) { e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; },
|
|
416
|
+
onMouseLeave: function(e) { e.currentTarget.style.background = isMe ? sc + '15' : 'rgba(255,255,255,0.02)'; },
|
|
417
|
+
},
|
|
418
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
|
|
419
|
+
h('div', { style: { width: 16, height: 16, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: sc, flexShrink: 0 } },
|
|
420
|
+
(ct.assignedToName || '?').charAt(0).toUpperCase()
|
|
421
|
+
),
|
|
422
|
+
h('span', { style: { fontSize: 10, fontWeight: 600, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 } }, ct.assignedToName || ct.assignedTo)
|
|
423
|
+
),
|
|
424
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
425
|
+
tag(sc, ct.status.replace('_', ' ')),
|
|
426
|
+
h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)' } }, timeAgo(ct.createdAt)),
|
|
427
|
+
ct.actualDurationMs && h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, formatDuration(ct.actualDurationMs))
|
|
428
|
+
),
|
|
429
|
+
isActive && ct.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden' } },
|
|
430
|
+
h('div', { style: { height: '100%', width: ct.progress + '%', background: sc, borderRadius: 1 } })
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
})
|
|
434
|
+
)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
237
438
|
// ─── Main Page ───────────────────────────────────────────
|
|
238
439
|
export function TaskPipelinePage() {
|
|
440
|
+
injectCSS();
|
|
239
441
|
var app = useApp();
|
|
240
442
|
var toast = app.toast;
|
|
241
443
|
var _tasks = useState([]);
|
|
242
444
|
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 });
|
|
445
|
+
var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] });
|
|
244
446
|
var stats = _stats[0]; var setStats = _stats[1];
|
|
447
|
+
var _expandedTaskId = useState(null);
|
|
448
|
+
var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
|
|
245
449
|
var _loading = useState(true);
|
|
246
450
|
var loading = _loading[0]; var setLoading = _loading[1];
|
|
247
451
|
var _selectedTask = useState(null);
|
|
248
452
|
var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
|
|
453
|
+
var _selectedChain = useState(null);
|
|
454
|
+
var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
|
|
249
455
|
var _hoveredId = useState(null);
|
|
250
456
|
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
457
|
var _zoom = useState(1);
|
|
254
458
|
var zoom = _zoom[0]; var setZoom = _zoom[1];
|
|
255
459
|
var _pan = useState({ x: 0, y: 0 });
|
|
@@ -260,6 +464,8 @@ export function TaskPipelinePage() {
|
|
|
260
464
|
var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
|
|
261
465
|
var _filter = useState('active');
|
|
262
466
|
var filter = _filter[0]; var setFilter = _filter[1];
|
|
467
|
+
var _mousePos = useState({ x: 0, y: 0 });
|
|
468
|
+
var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
|
|
263
469
|
var containerRef = useRef(null);
|
|
264
470
|
|
|
265
471
|
var loadData = useCallback(function() {
|
|
@@ -270,9 +476,8 @@ export function TaskPipelinePage() {
|
|
|
270
476
|
]).then(function(res) {
|
|
271
477
|
setTasks(res[0]?.tasks || []);
|
|
272
478
|
setStats(res[1] || stats);
|
|
273
|
-
}).catch(function(err) {
|
|
274
|
-
|
|
275
|
-
}).finally(function() { setLoading(false); });
|
|
479
|
+
}).catch(function(err) { console.error('[TaskPipeline]', err); })
|
|
480
|
+
.finally(function() { setLoading(false); });
|
|
276
481
|
}, []);
|
|
277
482
|
|
|
278
483
|
// SSE
|
|
@@ -299,10 +504,9 @@ export function TaskPipelinePage() {
|
|
|
299
504
|
if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
|
|
300
505
|
return [event.task].concat(prev);
|
|
301
506
|
});
|
|
302
|
-
setSelectedTask(function(prev) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
});
|
|
507
|
+
setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
|
|
508
|
+
// Refresh stats on every task event for real-time metrics
|
|
509
|
+
engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
|
|
306
510
|
}
|
|
307
511
|
} catch (err) {}
|
|
308
512
|
};
|
|
@@ -310,11 +514,10 @@ export function TaskPipelinePage() {
|
|
|
310
514
|
return function() { if (es) es.close(); };
|
|
311
515
|
}, []);
|
|
312
516
|
|
|
313
|
-
// Periodic stats
|
|
314
517
|
useEffect(function() {
|
|
315
518
|
var iv = setInterval(function() {
|
|
316
519
|
engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
|
|
317
|
-
},
|
|
520
|
+
}, 15000);
|
|
318
521
|
return function() { clearInterval(iv); };
|
|
319
522
|
}, []);
|
|
320
523
|
|
|
@@ -326,6 +529,34 @@ export function TaskPipelinePage() {
|
|
|
326
529
|
}).catch(function(err) { toast(err.message || 'Failed', 'error'); });
|
|
327
530
|
}, []);
|
|
328
531
|
|
|
532
|
+
function openTaskDetail(t) {
|
|
533
|
+
setSelectedTask(t);
|
|
534
|
+
if (t.chainId) {
|
|
535
|
+
var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
|
|
536
|
+
chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
|
|
537
|
+
setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
|
|
538
|
+
} else {
|
|
539
|
+
setSelectedChain(null);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Toggle inline chain flowchart (single click on node)
|
|
544
|
+
function toggleExpand(t) {
|
|
545
|
+
if (expandedTaskId === t.id) {
|
|
546
|
+
setExpandedTaskId(null);
|
|
547
|
+
setSelectedChain(null);
|
|
548
|
+
} else {
|
|
549
|
+
setExpandedTaskId(t.id);
|
|
550
|
+
if (t.chainId) {
|
|
551
|
+
var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
|
|
552
|
+
chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
|
|
553
|
+
setSelectedChain(chainTasks.length > 0 ? chainTasks : [t]);
|
|
554
|
+
} else {
|
|
555
|
+
setSelectedChain([t]);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
329
560
|
// Filter
|
|
330
561
|
var filtered = tasks.filter(function(t) {
|
|
331
562
|
if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
|
|
@@ -335,34 +566,28 @@ export function TaskPipelinePage() {
|
|
|
335
566
|
});
|
|
336
567
|
|
|
337
568
|
// Layout
|
|
338
|
-
var layout =
|
|
339
|
-
var
|
|
569
|
+
var layout = layoutChains(filtered);
|
|
570
|
+
var nodes = layout.nodes;
|
|
340
571
|
var edges = layout.edges;
|
|
341
572
|
var treeW = layout.width;
|
|
342
573
|
var treeH = layout.height;
|
|
574
|
+
var chainInfos = layout.chains;
|
|
343
575
|
|
|
344
|
-
// Zoom
|
|
576
|
+
// Zoom/Pan handlers
|
|
345
577
|
var handleWheel = useCallback(function(e) {
|
|
346
578
|
e.preventDefault();
|
|
347
|
-
|
|
348
|
-
setZoom(function(z) { return Math.min(3, Math.max(0.15, z + delta)); });
|
|
579
|
+
setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
|
|
349
580
|
}, []);
|
|
350
|
-
|
|
351
|
-
// Pan
|
|
352
581
|
var handleMouseDown = useCallback(function(e) {
|
|
353
|
-
if (e.button !== 0) return;
|
|
354
|
-
if (e.target.closest('.task-node')) return;
|
|
582
|
+
if (e.button !== 0 || e.target.closest('.tp-node')) return;
|
|
355
583
|
setDragging(true);
|
|
356
584
|
setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
|
|
357
585
|
}, [pan]);
|
|
358
|
-
|
|
359
586
|
var handleMouseMove = useCallback(function(e) {
|
|
360
587
|
if (!dragging) return;
|
|
361
588
|
setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
|
|
362
589
|
}, [dragging, dragStart]);
|
|
363
|
-
|
|
364
590
|
var handleMouseUp = useCallback(function() { setDragging(false); }, []);
|
|
365
|
-
|
|
366
591
|
useEffect(function() {
|
|
367
592
|
if (dragging) {
|
|
368
593
|
window.addEventListener('mousemove', handleMouseMove);
|
|
@@ -371,7 +596,6 @@ export function TaskPipelinePage() {
|
|
|
371
596
|
}
|
|
372
597
|
}, [dragging, handleMouseMove, handleMouseUp]);
|
|
373
598
|
|
|
374
|
-
// Fit
|
|
375
599
|
var fitToView = useCallback(function() {
|
|
376
600
|
if (!containerRef.current || !treeW || !treeH) return;
|
|
377
601
|
var rect = containerRef.current.getBoundingClientRect();
|
|
@@ -379,128 +603,96 @@ export function TaskPipelinePage() {
|
|
|
379
603
|
var scaleY = (rect.height - 40) / treeH;
|
|
380
604
|
var scale = Math.min(scaleX, scaleY, 1.5);
|
|
381
605
|
setZoom(scale);
|
|
382
|
-
setPan({ x: (rect.width - treeW * scale) / 2, y:
|
|
606
|
+
setPan({ x: (rect.width - treeW * scale) / 2, y: 20 });
|
|
383
607
|
}, [treeW, treeH]);
|
|
608
|
+
useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
|
|
384
609
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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;
|
|
610
|
+
// Highlight connected chain on hover
|
|
611
|
+
var hoveredChainId = null;
|
|
612
|
+
if (hoveredId) {
|
|
613
|
+
var hn = nodes.find(function(n) { return n.id === hoveredId; });
|
|
614
|
+
if (hn) hoveredChainId = hn.chainId;
|
|
615
|
+
}
|
|
408
616
|
|
|
409
|
-
|
|
410
|
-
|
|
617
|
+
var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
|
|
618
|
+
|
|
619
|
+
// ─── Toolbar ─────────────────────────────────────────
|
|
620
|
+
var toolbar = h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)', flexShrink: 0, flexWrap: 'wrap' } },
|
|
621
|
+
h('div', { style: { fontWeight: 700, fontSize: 14, color: '#fff', display: 'flex', alignItems: 'center', gap: 6 } },
|
|
622
|
+
I.workflow(), 'Task Pipeline',
|
|
623
|
+
h(HelpButton, { label: 'Task Pipeline' },
|
|
624
|
+
h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
|
|
625
|
+
h('h4', { style: _h4 }, 'Features'),
|
|
626
|
+
h('ul', { style: _ul },
|
|
627
|
+
h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
|
|
628
|
+
h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
|
|
629
|
+
h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
|
|
630
|
+
h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
|
|
631
|
+
h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
|
|
632
|
+
h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
|
|
633
|
+
),
|
|
634
|
+
h('h4', { style: _h4 }, 'Interactions'),
|
|
635
|
+
h('ul', { style: _ul },
|
|
636
|
+
h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
|
|
637
|
+
h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
|
|
638
|
+
h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
|
|
639
|
+
h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
|
|
640
|
+
),
|
|
641
|
+
h('div', { style: _tip }, h('strong', null, 'Tip: '), 'Each task has a unique chain ID linking all delegation steps. Even circular review loops (agent A \u2192 B \u2192 A) are tracked.')
|
|
642
|
+
)
|
|
643
|
+
),
|
|
644
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
645
|
+
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'flowPulse 2s infinite' } }),
|
|
646
|
+
h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } }, 'Live')
|
|
647
|
+
),
|
|
648
|
+
h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } },
|
|
649
|
+
(stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
|
|
650
|
+
),
|
|
651
|
+
h('div', { style: { flex: 1 } }),
|
|
652
|
+
// Filters
|
|
653
|
+
['active', 'all', 'completed', 'failed'].map(function(f) {
|
|
654
|
+
return h('button', { key: f, onClick: function() { setFilter(f); }, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, background: filter === f ? 'rgba(99,102,241,0.3)' : toolbarBtnStyle.background }) }, f.charAt(0).toUpperCase() + f.slice(1));
|
|
655
|
+
}),
|
|
656
|
+
h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
|
|
657
|
+
legendDot(STATUS_COLORS.in_progress, 'Active'),
|
|
658
|
+
legendDot(STATUS_COLORS.completed, 'Done'),
|
|
659
|
+
legendDot(STATUS_COLORS.failed, 'Failed'),
|
|
660
|
+
h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
|
|
661
|
+
h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
|
|
662
|
+
h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11, minWidth: 36, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
663
|
+
h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
|
|
664
|
+
h('button', { onClick: fitToView, style: toolbarBtnStyle }, 'Fit'),
|
|
665
|
+
h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
|
|
666
|
+
);
|
|
411
667
|
|
|
412
668
|
if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
|
|
413
669
|
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
h(
|
|
417
|
-
h('div', { style: { fontWeight: 700, fontSize: 16, color: '#fff', display: 'flex', alignItems: 'center', gap: 8 } }, I.workflow(), ' Task Pipeline',
|
|
418
|
-
h(HelpButton, { label: 'Task Pipeline' },
|
|
419
|
-
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.'),
|
|
420
|
-
h('h4', { style: _h4 }, 'How It Works'),
|
|
421
|
-
h('ul', { style: _ul },
|
|
422
|
-
h('li', null, 'Tasks are automatically recorded when agents are spawned for work'),
|
|
423
|
-
h('li', null, 'The pipeline tracks task status from creation through completion'),
|
|
424
|
-
h('li', null, 'Real-time updates via SSE — no need to refresh'),
|
|
425
|
-
h('li', null, 'Smart metadata extraction categorizes tasks automatically')
|
|
426
|
-
),
|
|
427
|
-
h('h4', { style: _h4 }, 'Task Lifecycle'),
|
|
428
|
-
h('ul', { style: _ul },
|
|
429
|
-
h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
|
|
430
|
-
h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
|
|
431
|
-
h('li', null, h('strong', null, 'In Progress'), ' — Actively executing'),
|
|
432
|
-
h('li', null, h('strong', null, 'Completed'), ' — Finished successfully'),
|
|
433
|
-
h('li', null, h('strong', null, 'Failed'), ' — Encountered an error')
|
|
434
|
-
),
|
|
435
|
-
h('div', { style: _tip }, 'Tip: Tasks will appear here as agents are assigned work. Sub-tasks show as children connected by arrows.')
|
|
436
|
-
)
|
|
437
|
-
),
|
|
438
|
-
),
|
|
670
|
+
if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
|
|
671
|
+
toolbar,
|
|
672
|
+
h(MetricsBar, { stats: stats }),
|
|
439
673
|
h('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' } },
|
|
440
|
-
h('div', { style: {
|
|
441
|
-
h('div', { style: { fontSize:
|
|
442
|
-
h('div', { style: { color: 'rgba(255,255,255,0.5)' } }, 'Tasks will appear here as agents are assigned work.')
|
|
674
|
+
h('div', { style: { width: 48, height: 48, borderRadius: 12, background: 'rgba(99,102,241,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16, color: '#6366f1' } }, I.workflow()),
|
|
675
|
+
h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
|
|
676
|
+
h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
|
|
443
677
|
)
|
|
444
678
|
);
|
|
445
679
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
h('li', null, h('strong', null, 'Scroll'), ' — Zoom in/out'),
|
|
458
|
-
h('li', null, h('strong', null, 'Drag'), ' — Pan the canvas')
|
|
459
|
-
),
|
|
460
|
-
h('h4', { style: _h4 }, 'Task Lifecycle'),
|
|
461
|
-
h('ul', { style: _ul },
|
|
462
|
-
h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
|
|
463
|
-
h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
|
|
464
|
-
h('li', null, h('strong', null, 'In Progress'), ' — Actively executing'),
|
|
465
|
-
h('li', null, h('strong', null, 'Completed'), ' — Finished successfully'),
|
|
466
|
-
h('li', null, h('strong', null, 'Failed'), ' — Encountered an error')
|
|
467
|
-
),
|
|
468
|
-
h('div', { style: _tip }, 'Tip: Sub-tasks appear as children of their parent task. The arrow system shows task delegation flow.')
|
|
469
|
-
)
|
|
470
|
-
),
|
|
471
|
-
// Live dot
|
|
472
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
473
|
-
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'pulse 2s infinite' } }),
|
|
474
|
-
h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } }, 'Live')
|
|
475
|
-
),
|
|
476
|
-
// Stats
|
|
477
|
-
h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } },
|
|
478
|
-
(stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
|
|
479
|
-
),
|
|
480
|
-
h('div', { style: { flex: 1 } }),
|
|
481
|
-
// Filter
|
|
482
|
-
['active', 'all', 'completed', 'failed'].map(function(f) {
|
|
483
|
-
return h('button', {
|
|
484
|
-
key: f,
|
|
485
|
-
onClick: function() { setFilter(f); },
|
|
486
|
-
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 })
|
|
487
|
-
}, f.charAt(0).toUpperCase() + f.slice(1));
|
|
488
|
-
}),
|
|
489
|
-
h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
|
|
490
|
-
// Legend
|
|
491
|
-
legendDot(STATUS_COLORS.in_progress, 'Active'),
|
|
492
|
-
legendDot(STATUS_COLORS.assigned, 'Assigned'),
|
|
493
|
-
legendDot(STATUS_COLORS.completed, 'Done'),
|
|
494
|
-
legendDot(STATUS_COLORS.failed, 'Failed'),
|
|
495
|
-
h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
|
|
496
|
-
// Zoom
|
|
497
|
-
h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
|
|
498
|
-
h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 12, minWidth: 40, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
499
|
-
h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
|
|
500
|
-
h('button', { onClick: fitToView, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Fit'),
|
|
501
|
-
h('button', { onClick: loadData, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Refresh'),
|
|
502
|
-
),
|
|
680
|
+
// Build expanded chain for inline flowchart
|
|
681
|
+
var expandedChain = null;
|
|
682
|
+
if (expandedTaskId) {
|
|
683
|
+
var et = tasks.find(function(t) { return t.id === expandedTaskId; });
|
|
684
|
+
if (et && et.chainId) {
|
|
685
|
+
expandedChain = tasks.filter(function(ct) { return ct.chainId === et.chainId; });
|
|
686
|
+
expandedChain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
|
|
687
|
+
} else if (et) {
|
|
688
|
+
expandedChain = [et];
|
|
689
|
+
}
|
|
690
|
+
}
|
|
503
691
|
|
|
692
|
+
return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
|
|
693
|
+
toolbar,
|
|
694
|
+
// Metrics bar
|
|
695
|
+
h(MetricsBar, { stats: stats }),
|
|
504
696
|
// Canvas
|
|
505
697
|
h('div', {
|
|
506
698
|
ref: containerRef,
|
|
@@ -509,109 +701,110 @@ export function TaskPipelinePage() {
|
|
|
509
701
|
onWheel: handleWheel,
|
|
510
702
|
},
|
|
511
703
|
h('div', { style: { transform: 'translate(' + pan.x + 'px, ' + pan.y + 'px) scale(' + zoom + ')', transformOrigin: '0 0', position: 'absolute', top: 0, left: 0 } },
|
|
512
|
-
|
|
513
|
-
|
|
704
|
+
|
|
705
|
+
// Chain labels (left side)
|
|
706
|
+
chainInfos.map(function(ci, i) {
|
|
707
|
+
return h('div', { key: ci.chainId, style: { position: 'absolute', left: 4, top: ci.y - 2, fontSize: 9, color: 'rgba(255,255,255,0.25)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', maxWidth: PAD - 8, overflow: 'hidden' } },
|
|
708
|
+
ci.customer && h(CustomerBadge, { customer: ci.customer })
|
|
709
|
+
);
|
|
710
|
+
}),
|
|
711
|
+
|
|
712
|
+
// SVG edges with animated flow
|
|
713
|
+
h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
|
|
514
714
|
h('defs', null,
|
|
515
|
-
h('marker', { id: '
|
|
516
|
-
h('polygon', { points: '0 0,
|
|
715
|
+
h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
|
|
716
|
+
h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
|
|
717
|
+
),
|
|
718
|
+
h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
|
|
719
|
+
h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
|
|
517
720
|
),
|
|
518
|
-
|
|
519
|
-
|
|
721
|
+
// Animated glow filter
|
|
722
|
+
h('filter', { id: 'tp-glow' },
|
|
723
|
+
h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
|
|
724
|
+
h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
|
|
520
725
|
)
|
|
521
726
|
),
|
|
522
727
|
edges.map(function(e, i) {
|
|
523
|
-
var
|
|
524
|
-
var
|
|
525
|
-
var isHl =
|
|
526
|
-
var dim =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
728
|
+
var fromId = e.from.id;
|
|
729
|
+
var toId = e.to.id;
|
|
730
|
+
var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
|
|
731
|
+
var dim = hoveredChainId && !isHl;
|
|
732
|
+
var dType = e.delegationType || 'delegation';
|
|
733
|
+
var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
|
|
734
|
+
var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
|
|
735
|
+
|
|
736
|
+
return h(Fragment, { key: i },
|
|
737
|
+
// Base path
|
|
738
|
+
h('path', {
|
|
739
|
+
d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
|
|
740
|
+
markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
|
|
741
|
+
style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
|
|
742
|
+
}),
|
|
743
|
+
// Animated flow dash overlay for active tasks
|
|
744
|
+
e.isActive && h('path', {
|
|
745
|
+
d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
|
|
746
|
+
strokeDasharray: '6 18',
|
|
747
|
+
className: 'tp-flow-active',
|
|
748
|
+
filter: 'url(#tp-glow)',
|
|
749
|
+
style: { opacity: dim ? 0.1 : 0.7 },
|
|
750
|
+
}),
|
|
751
|
+
// Delegation type label on edge
|
|
752
|
+
!dim && dType !== 'delegation' && h('text', {
|
|
753
|
+
x: (e.from.x + e.from.w + e.to.x) / 2,
|
|
754
|
+
y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
|
|
755
|
+
fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
|
|
756
|
+
fontSize: 8, textAnchor: 'middle', fontWeight: 600,
|
|
757
|
+
}, dType)
|
|
758
|
+
);
|
|
536
759
|
})
|
|
537
760
|
),
|
|
538
761
|
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
var nodeId = node.id || node.agentId;
|
|
542
|
-
var isHovered = hoveredId === nodeId;
|
|
543
|
-
var dim = connected && !connected.has(nodeId);
|
|
544
|
-
|
|
545
|
-
if (node.isAgent) {
|
|
546
|
-
// Agent node (top-level, like org-chart node)
|
|
547
|
-
var taskCount = (node.children || []).length;
|
|
548
|
-
return h('div', {
|
|
549
|
-
key: nodeId,
|
|
550
|
-
className: 'task-node',
|
|
551
|
-
onMouseEnter: function(ev) { setHoveredId(nodeId); setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
552
|
-
onMouseMove: function(ev) { if (isHovered) setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
553
|
-
onMouseLeave: function() { setHoveredId(null); },
|
|
554
|
-
style: {
|
|
555
|
-
position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
|
|
556
|
-
background: isHovered ? 'rgba(99,102,241,0.12)' : 'rgba(255,255,255,0.03)',
|
|
557
|
-
border: '1.5px solid ' + (isHovered ? 'rgba(99,102,241,0.5)' : 'rgba(255,255,255,0.12)'),
|
|
558
|
-
borderRadius: 12, padding: '10px 14px', cursor: 'default',
|
|
559
|
-
transition: 'all 0.2s', opacity: dim ? 0.2 : 1,
|
|
560
|
-
backdropFilter: 'blur(8px)',
|
|
561
|
-
display: 'flex', alignItems: 'center', gap: 10, userSelect: 'none',
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
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 } },
|
|
565
|
-
(node.name || '?').charAt(0).toUpperCase()
|
|
566
|
-
),
|
|
567
|
-
h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
|
|
568
|
-
h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, node.name),
|
|
569
|
-
h('div', { style: { fontSize: 11, color: 'rgba(255,255,255,0.45)', marginTop: 2 } }, 'Agent'),
|
|
570
|
-
h('div', { style: { display: 'flex', gap: 4, marginTop: 4 } },
|
|
571
|
-
h('span', { style: tagStyle(ACCENT) }, taskCount + ' task' + (taskCount !== 1 ? 's' : ''))
|
|
572
|
-
)
|
|
573
|
-
)
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Task node
|
|
762
|
+
// Task nodes
|
|
763
|
+
nodes.map(function(node) {
|
|
578
764
|
var t = node.task;
|
|
579
|
-
var
|
|
580
|
-
var
|
|
581
|
-
var
|
|
765
|
+
var sc = STATUS_COLORS[t.status] || '#6b7394';
|
|
766
|
+
var isHovered = hoveredId === node.id;
|
|
767
|
+
var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
|
|
768
|
+
var dim = hoveredChainId && !isChainHl;
|
|
769
|
+
var isActive = t.status === 'in_progress';
|
|
582
770
|
|
|
771
|
+
var isExpanded = expandedTaskId === node.id;
|
|
583
772
|
return h('div', {
|
|
584
|
-
key:
|
|
585
|
-
className: '
|
|
586
|
-
onClick: function() {
|
|
587
|
-
|
|
588
|
-
|
|
773
|
+
key: node.id,
|
|
774
|
+
className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
|
|
775
|
+
onClick: function() { toggleExpand(t); },
|
|
776
|
+
onDoubleClick: function() { openTaskDetail(t); },
|
|
777
|
+
onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
778
|
+
onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
|
|
589
779
|
onMouseLeave: function() { setHoveredId(null); },
|
|
590
780
|
style: {
|
|
591
|
-
position: 'absolute', left: node.x, top: node.y, width:
|
|
781
|
+
position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
|
|
592
782
|
background: isHovered ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
|
|
593
|
-
border: '
|
|
594
|
-
borderLeft: '3px solid ' +
|
|
595
|
-
borderRadius:
|
|
596
|
-
transition: 'all 0.
|
|
597
|
-
backdropFilter: 'blur(
|
|
598
|
-
display: 'flex', flexDirection: 'column', justifyContent: 'center', gap:
|
|
783
|
+
border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
|
|
784
|
+
borderLeft: '3px solid ' + sc,
|
|
785
|
+
borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
|
|
786
|
+
transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
|
|
787
|
+
backdropFilter: 'blur(6px)',
|
|
788
|
+
display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
|
|
599
789
|
},
|
|
600
790
|
},
|
|
601
|
-
//
|
|
602
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap:
|
|
603
|
-
h('
|
|
604
|
-
|
|
791
|
+
// Agent + title row
|
|
792
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
|
|
793
|
+
h('div', { style: { width: 18, height: 18, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, color: sc, flexShrink: 0, border: '1px solid ' + sc + '44' } },
|
|
794
|
+
(t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
|
|
795
|
+
),
|
|
796
|
+
h('span', { style: { fontSize: 11, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
|
|
605
797
|
),
|
|
606
|
-
// Status +
|
|
798
|
+
// Status + agent name + time
|
|
607
799
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
|
|
608
|
-
|
|
609
|
-
h('span', { style:
|
|
610
|
-
|
|
800
|
+
tag(sc, t.status.replace('_', ' ')),
|
|
801
|
+
h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.4)' } }, t.assignedToName || t.assignedTo),
|
|
802
|
+
t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
|
|
803
|
+
h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
|
|
611
804
|
),
|
|
612
805
|
// Progress bar
|
|
613
|
-
|
|
614
|
-
h('div', { style: { height: '100%', width: t.progress + '%', background:
|
|
806
|
+
isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
|
|
807
|
+
h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
|
|
615
808
|
)
|
|
616
809
|
);
|
|
617
810
|
})
|
|
@@ -619,43 +812,37 @@ export function TaskPipelinePage() {
|
|
|
619
812
|
),
|
|
620
813
|
|
|
621
814
|
// Hover tooltip
|
|
622
|
-
hoveredNode &&
|
|
815
|
+
hoveredNode && hoveredNode.task && h('div', { style: {
|
|
623
816
|
position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
|
|
624
817
|
background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
|
|
625
818
|
border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
|
|
626
|
-
padding: '
|
|
819
|
+
padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
|
|
627
820
|
}},
|
|
628
|
-
h(
|
|
629
|
-
h('div', { style: {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
hoveredNode.task.
|
|
635
|
-
hoveredNode.task.
|
|
636
|
-
hoveredNode.task.actualDurationMs && tooltipRow('Duration', formatDuration(hoveredNode.task.actualDurationMs)),
|
|
637
|
-
hoveredNode.task.progress > 0 && tooltipRow('Progress', hoveredNode.task.progress + '%', STATUS_COLORS.in_progress)
|
|
821
|
+
hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
|
|
822
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.task.title),
|
|
823
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
|
|
824
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Agent'), h('span', { style: { fontWeight: 600 } }, hoveredNode.task.assignedToName || '-')),
|
|
825
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Status'), h('span', { style: { color: STATUS_COLORS[hoveredNode.task.status] } }, hoveredNode.task.status.replace('_', ' '))),
|
|
826
|
+
hoveredNode.task.chainId && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Chain Step'), h('span', null, '#' + ((hoveredNode.task.chainSeq || 0) + 1))),
|
|
827
|
+
hoveredNode.task.delegationType && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Type'), h('span', { style: { color: DELEGATION_COLORS[hoveredNode.task.delegationType] } }, hoveredNode.task.delegationType)),
|
|
828
|
+
hoveredNode.task.progress > 0 && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Progress'), h('span', { style: { color: STATUS_COLORS.in_progress } }, hoveredNode.task.progress + '%'))
|
|
638
829
|
)
|
|
639
830
|
),
|
|
640
831
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.name),
|
|
649
|
-
tooltipRow('Tasks', String((hoveredNode.children || []).length), ACCENT)
|
|
650
|
-
),
|
|
832
|
+
// Inline chain flowchart (shown when a task is clicked)
|
|
833
|
+
expandedChain && expandedChain.length > 0 && h(ChainFlowInline, {
|
|
834
|
+
chain: expandedChain,
|
|
835
|
+
taskId: expandedTaskId,
|
|
836
|
+
onClose: function() { setExpandedTaskId(null); },
|
|
837
|
+
onClickTask: function(t) { openTaskDetail(t); }
|
|
838
|
+
}),
|
|
651
839
|
|
|
652
|
-
//
|
|
653
|
-
selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
|
|
840
|
+
// Detail modal (double-click)
|
|
841
|
+
selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
|
|
654
842
|
);
|
|
655
843
|
}
|
|
656
844
|
|
|
657
|
-
// ─── Agent Task Pipeline (for agent-detail workforce tab) ─
|
|
658
|
-
// Reusable mini version scoped to a single agent
|
|
845
|
+
// ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
|
|
659
846
|
export function AgentTaskPipeline(props) {
|
|
660
847
|
var agentId = props.agentId;
|
|
661
848
|
var _tasks = useState([]);
|
|
@@ -672,8 +859,6 @@ export function AgentTaskPipeline(props) {
|
|
|
672
859
|
engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
|
|
673
860
|
setTasks(res?.tasks || []);
|
|
674
861
|
}).catch(function() {}).finally(function() { setLoading(false); });
|
|
675
|
-
|
|
676
|
-
// SSE
|
|
677
862
|
var baseUrl = window.__ENGINE_BASE || '/api/engine';
|
|
678
863
|
var es;
|
|
679
864
|
try {
|
|
@@ -709,57 +894,46 @@ export function AgentTaskPipeline(props) {
|
|
|
709
894
|
var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
|
|
710
895
|
|
|
711
896
|
function renderTaskRow(t) {
|
|
712
|
-
var
|
|
713
|
-
var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
|
|
897
|
+
var sc = STATUS_COLORS[t.status] || '#6b7394';
|
|
714
898
|
return h('div', {
|
|
715
899
|
key: t.id,
|
|
716
900
|
onClick: function() { setSelectedTask(t); },
|
|
717
|
-
style: {
|
|
718
|
-
display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px',
|
|
719
|
-
borderBottom: '1px solid var(--border)', cursor: 'pointer',
|
|
720
|
-
transition: 'background 0.15s',
|
|
721
|
-
},
|
|
901
|
+
style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
|
|
722
902
|
onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
|
|
723
903
|
onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
|
|
724
904
|
},
|
|
725
|
-
h('
|
|
905
|
+
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: sc, flexShrink: 0 } }),
|
|
726
906
|
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
727
|
-
h('div', { style: { fontSize:
|
|
728
|
-
h('div', { style: { fontSize:
|
|
907
|
+
h('div', { style: { fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
|
|
908
|
+
h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 4, alignItems: 'center' } },
|
|
909
|
+
t.category,
|
|
910
|
+
t.delegationType && h('span', { style: { color: DELEGATION_COLORS[t.delegationType] || '#6b7394' } }, '\u2192 ' + t.delegationType),
|
|
911
|
+
h('span', null, '\u00B7 ' + timeAgo(t.createdAt))
|
|
912
|
+
)
|
|
729
913
|
),
|
|
730
|
-
t.status === 'in_progress' && t.progress > 0 && h('
|
|
731
|
-
h('span', { style: { padding: '2px
|
|
732
|
-
t.actualDurationMs && h('span', { style: { fontSize:
|
|
914
|
+
t.status === 'in_progress' && t.progress > 0 && h('span', { style: { fontSize: 10, color: sc, fontWeight: 600 } }, t.progress + '%'),
|
|
915
|
+
h('span', { style: { padding: '2px 6px', borderRadius: 8, fontSize: 9, fontWeight: 600, background: sc + '22', color: sc, flexShrink: 0 } }, t.status.replace('_', ' ')),
|
|
916
|
+
t.actualDurationMs && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
|
|
733
917
|
);
|
|
734
918
|
}
|
|
735
919
|
|
|
736
920
|
return h(Fragment, null,
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'pulse 2s infinite' } }),
|
|
921
|
+
active.length > 0 && h('div', { style: { marginBottom: 12 } },
|
|
922
|
+
h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
|
|
923
|
+
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
|
|
741
924
|
'Active (' + active.length + ')'
|
|
742
925
|
),
|
|
743
|
-
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
744
|
-
active.map(renderTaskRow)
|
|
745
|
-
)
|
|
926
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, active.map(renderTaskRow))
|
|
746
927
|
),
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
h('div', { style: {
|
|
750
|
-
h('div', { style: {
|
|
751
|
-
completed.slice(0, 10).map(renderTaskRow)
|
|
752
|
-
),
|
|
753
|
-
completed.length > 10 && h('div', { style: { padding: 8, textAlign: 'center', fontSize: 12, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
|
|
928
|
+
completed.length > 0 && h('div', { style: { marginBottom: 12 } },
|
|
929
|
+
h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 6 } }, 'Completed (' + completed.length + ')'),
|
|
930
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, completed.slice(0, 10).map(renderTaskRow)),
|
|
931
|
+
completed.length > 10 && h('div', { style: { padding: 6, textAlign: 'center', fontSize: 11, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
|
|
754
932
|
),
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
h('div', { style: {
|
|
758
|
-
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
759
|
-
failed.slice(0, 5).map(renderTaskRow)
|
|
760
|
-
)
|
|
933
|
+
failed.length > 0 && h('div', { style: { marginBottom: 12 } },
|
|
934
|
+
h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
|
|
935
|
+
h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
|
|
761
936
|
),
|
|
762
|
-
|
|
763
|
-
selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
|
|
937
|
+
selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
|
|
764
938
|
);
|
|
765
939
|
}
|