agentflow-dashboard 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YDFLDRWO.js → chunk-RWNVLZU7.js} +69 -0
- package/dist/cli.cjs +69 -0
- package/dist/cli.js +1 -1
- package/dist/index.cjs +69 -0
- package/dist/index.js +1 -1
- package/dist/public/dashboard.js +164 -2
- package/dist/public/index.html +12 -0
- package/dist/server.cjs +69 -0
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/public/dashboard.js +164 -2
- package/public/index.html +12 -0
|
@@ -1667,6 +1667,75 @@ var DashboardServer = class {
|
|
|
1667
1667
|
res.status(500).json({ error: "Failed to load statistics" });
|
|
1668
1668
|
}
|
|
1669
1669
|
});
|
|
1670
|
+
this.app.get("/api/agents/:agentId/timeline", (req, res) => {
|
|
1671
|
+
try {
|
|
1672
|
+
const agentId = req.params.agentId;
|
|
1673
|
+
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
1674
|
+
const rawTraces = this.watcher.getTracesByAgent(agentId);
|
|
1675
|
+
if (rawTraces.length === 0) {
|
|
1676
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1677
|
+
}
|
|
1678
|
+
const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
|
|
1679
|
+
const executions = traces.map((t) => {
|
|
1680
|
+
const serialized = serializeTrace(t);
|
|
1681
|
+
const nodes = serialized.nodes || {};
|
|
1682
|
+
const events = serialized.sessionEvents || [];
|
|
1683
|
+
const activities = [];
|
|
1684
|
+
if (events.length > 0) {
|
|
1685
|
+
for (let i = 0; i < events.length; i++) {
|
|
1686
|
+
const evt = events[i];
|
|
1687
|
+
if (evt.type === "system" || evt.type === "model_change") continue;
|
|
1688
|
+
const dur = evt.duration || 0;
|
|
1689
|
+
const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
|
|
1690
|
+
const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
|
|
1691
|
+
const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
|
|
1692
|
+
activities.push({
|
|
1693
|
+
id: evt.id || `evt-${i}`,
|
|
1694
|
+
name: evt.toolName || evt.name || evt.type,
|
|
1695
|
+
type: evt.type,
|
|
1696
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1697
|
+
startTime: startTs,
|
|
1698
|
+
endTime: endTs,
|
|
1699
|
+
parentId: evt.parentId
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
} else {
|
|
1703
|
+
const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1704
|
+
for (const node of sorted) {
|
|
1705
|
+
activities.push({
|
|
1706
|
+
id: node.id,
|
|
1707
|
+
name: node.name || node.type || node.id,
|
|
1708
|
+
type: node.type || "unknown",
|
|
1709
|
+
status: node.status || "completed",
|
|
1710
|
+
startTime: node.startTime || t.startTime,
|
|
1711
|
+
endTime: node.endTime || node.startTime || t.startTime,
|
|
1712
|
+
parentId: node.parentId
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return {
|
|
1717
|
+
id: serialized.id || serialized.filename,
|
|
1718
|
+
filename: serialized.filename,
|
|
1719
|
+
name: serialized.name || serialized.filename,
|
|
1720
|
+
agentId: serialized.agentId,
|
|
1721
|
+
trigger: serialized.trigger,
|
|
1722
|
+
status: serialized.status || "completed",
|
|
1723
|
+
sourceType: serialized.sourceType,
|
|
1724
|
+
startTime: serialized.startTime,
|
|
1725
|
+
endTime: serialized.endTime || serialized.startTime,
|
|
1726
|
+
tokenUsage: serialized.tokenUsage,
|
|
1727
|
+
activities
|
|
1728
|
+
};
|
|
1729
|
+
});
|
|
1730
|
+
const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
|
|
1731
|
+
const minTime = Math.min(...allTimes);
|
|
1732
|
+
const maxTime = Math.max(...allTimes);
|
|
1733
|
+
res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
console.error("Agent timeline error:", error);
|
|
1736
|
+
res.status(500).json({ error: "Failed to build agent timeline" });
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1670
1739
|
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1671
1740
|
try {
|
|
1672
1741
|
const agentId = req.params.agentId;
|
package/dist/cli.cjs
CHANGED
|
@@ -1561,6 +1561,75 @@ var DashboardServer = class {
|
|
|
1561
1561
|
res.status(500).json({ error: "Failed to load statistics" });
|
|
1562
1562
|
}
|
|
1563
1563
|
});
|
|
1564
|
+
this.app.get("/api/agents/:agentId/timeline", (req, res) => {
|
|
1565
|
+
try {
|
|
1566
|
+
const agentId = req.params.agentId;
|
|
1567
|
+
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
1568
|
+
const rawTraces = this.watcher.getTracesByAgent(agentId);
|
|
1569
|
+
if (rawTraces.length === 0) {
|
|
1570
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1571
|
+
}
|
|
1572
|
+
const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
|
|
1573
|
+
const executions = traces.map((t) => {
|
|
1574
|
+
const serialized = serializeTrace(t);
|
|
1575
|
+
const nodes = serialized.nodes || {};
|
|
1576
|
+
const events = serialized.sessionEvents || [];
|
|
1577
|
+
const activities = [];
|
|
1578
|
+
if (events.length > 0) {
|
|
1579
|
+
for (let i = 0; i < events.length; i++) {
|
|
1580
|
+
const evt = events[i];
|
|
1581
|
+
if (evt.type === "system" || evt.type === "model_change") continue;
|
|
1582
|
+
const dur = evt.duration || 0;
|
|
1583
|
+
const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
|
|
1584
|
+
const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
|
|
1585
|
+
const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
|
|
1586
|
+
activities.push({
|
|
1587
|
+
id: evt.id || `evt-${i}`,
|
|
1588
|
+
name: evt.toolName || evt.name || evt.type,
|
|
1589
|
+
type: evt.type,
|
|
1590
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1591
|
+
startTime: startTs,
|
|
1592
|
+
endTime: endTs,
|
|
1593
|
+
parentId: evt.parentId
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
} else {
|
|
1597
|
+
const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1598
|
+
for (const node of sorted) {
|
|
1599
|
+
activities.push({
|
|
1600
|
+
id: node.id,
|
|
1601
|
+
name: node.name || node.type || node.id,
|
|
1602
|
+
type: node.type || "unknown",
|
|
1603
|
+
status: node.status || "completed",
|
|
1604
|
+
startTime: node.startTime || t.startTime,
|
|
1605
|
+
endTime: node.endTime || node.startTime || t.startTime,
|
|
1606
|
+
parentId: node.parentId
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return {
|
|
1611
|
+
id: serialized.id || serialized.filename,
|
|
1612
|
+
filename: serialized.filename,
|
|
1613
|
+
name: serialized.name || serialized.filename,
|
|
1614
|
+
agentId: serialized.agentId,
|
|
1615
|
+
trigger: serialized.trigger,
|
|
1616
|
+
status: serialized.status || "completed",
|
|
1617
|
+
sourceType: serialized.sourceType,
|
|
1618
|
+
startTime: serialized.startTime,
|
|
1619
|
+
endTime: serialized.endTime || serialized.startTime,
|
|
1620
|
+
tokenUsage: serialized.tokenUsage,
|
|
1621
|
+
activities
|
|
1622
|
+
};
|
|
1623
|
+
});
|
|
1624
|
+
const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
|
|
1625
|
+
const minTime = Math.min(...allTimes);
|
|
1626
|
+
const maxTime = Math.max(...allTimes);
|
|
1627
|
+
res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
console.error("Agent timeline error:", error);
|
|
1630
|
+
res.status(500).json({ error: "Failed to build agent timeline" });
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1564
1633
|
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1565
1634
|
try {
|
|
1566
1635
|
const agentId = req.params.agentId;
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1706,6 +1706,75 @@ var DashboardServer = class {
|
|
|
1706
1706
|
res.status(500).json({ error: "Failed to load statistics" });
|
|
1707
1707
|
}
|
|
1708
1708
|
});
|
|
1709
|
+
this.app.get("/api/agents/:agentId/timeline", (req, res) => {
|
|
1710
|
+
try {
|
|
1711
|
+
const agentId = req.params.agentId;
|
|
1712
|
+
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
1713
|
+
const rawTraces = this.watcher.getTracesByAgent(agentId);
|
|
1714
|
+
if (rawTraces.length === 0) {
|
|
1715
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1716
|
+
}
|
|
1717
|
+
const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
|
|
1718
|
+
const executions = traces.map((t) => {
|
|
1719
|
+
const serialized = serializeTrace(t);
|
|
1720
|
+
const nodes = serialized.nodes || {};
|
|
1721
|
+
const events = serialized.sessionEvents || [];
|
|
1722
|
+
const activities = [];
|
|
1723
|
+
if (events.length > 0) {
|
|
1724
|
+
for (let i = 0; i < events.length; i++) {
|
|
1725
|
+
const evt = events[i];
|
|
1726
|
+
if (evt.type === "system" || evt.type === "model_change") continue;
|
|
1727
|
+
const dur = evt.duration || 0;
|
|
1728
|
+
const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
|
|
1729
|
+
const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
|
|
1730
|
+
const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
|
|
1731
|
+
activities.push({
|
|
1732
|
+
id: evt.id || `evt-${i}`,
|
|
1733
|
+
name: evt.toolName || evt.name || evt.type,
|
|
1734
|
+
type: evt.type,
|
|
1735
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1736
|
+
startTime: startTs,
|
|
1737
|
+
endTime: endTs,
|
|
1738
|
+
parentId: evt.parentId
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
} else {
|
|
1742
|
+
const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1743
|
+
for (const node of sorted) {
|
|
1744
|
+
activities.push({
|
|
1745
|
+
id: node.id,
|
|
1746
|
+
name: node.name || node.type || node.id,
|
|
1747
|
+
type: node.type || "unknown",
|
|
1748
|
+
status: node.status || "completed",
|
|
1749
|
+
startTime: node.startTime || t.startTime,
|
|
1750
|
+
endTime: node.endTime || node.startTime || t.startTime,
|
|
1751
|
+
parentId: node.parentId
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return {
|
|
1756
|
+
id: serialized.id || serialized.filename,
|
|
1757
|
+
filename: serialized.filename,
|
|
1758
|
+
name: serialized.name || serialized.filename,
|
|
1759
|
+
agentId: serialized.agentId,
|
|
1760
|
+
trigger: serialized.trigger,
|
|
1761
|
+
status: serialized.status || "completed",
|
|
1762
|
+
sourceType: serialized.sourceType,
|
|
1763
|
+
startTime: serialized.startTime,
|
|
1764
|
+
endTime: serialized.endTime || serialized.startTime,
|
|
1765
|
+
tokenUsage: serialized.tokenUsage,
|
|
1766
|
+
activities
|
|
1767
|
+
};
|
|
1768
|
+
});
|
|
1769
|
+
const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
|
|
1770
|
+
const minTime = Math.min(...allTimes);
|
|
1771
|
+
const maxTime = Math.max(...allTimes);
|
|
1772
|
+
res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
console.error("Agent timeline error:", error);
|
|
1775
|
+
res.status(500).json({ error: "Failed to build agent timeline" });
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1709
1778
|
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1710
1779
|
try {
|
|
1711
1780
|
const agentId = req.params.agentId;
|
package/dist/index.js
CHANGED
package/dist/public/dashboard.js
CHANGED
|
@@ -307,11 +307,15 @@ class AgentFlowDashboard {
|
|
|
307
307
|
this.selectedTrace = trace;
|
|
308
308
|
this.selectedTraceData = trace;
|
|
309
309
|
|
|
310
|
-
// Reset
|
|
310
|
+
// Reset agent-level caches when agent changes
|
|
311
311
|
if (this._processMapAgent !== trace.agentId) {
|
|
312
312
|
this._processMapAgent = null;
|
|
313
313
|
if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
|
|
314
314
|
}
|
|
315
|
+
if (this._agentTimelineAgent !== trace.agentId) {
|
|
316
|
+
this._agentTimelineAgent = null;
|
|
317
|
+
this._agentTimelineRendered = false;
|
|
318
|
+
}
|
|
315
319
|
|
|
316
320
|
// Update sidebar selection
|
|
317
321
|
document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
|
|
@@ -856,6 +860,7 @@ class AgentFlowDashboard {
|
|
|
856
860
|
case 'state': this.renderStateMachine(); break;
|
|
857
861
|
case 'summary': this.renderSummary(); break;
|
|
858
862
|
case 'transcript': this.renderTranscript(); break;
|
|
863
|
+
case 'agenttimeline': this.renderAgentTimeline(); break;
|
|
859
864
|
case 'processmap': this.renderProcessMap(); break;
|
|
860
865
|
}
|
|
861
866
|
this.updateToolbarInfo();
|
|
@@ -1915,7 +1920,164 @@ class AgentFlowDashboard {
|
|
|
1915
1920
|
}
|
|
1916
1921
|
|
|
1917
1922
|
// ---------------------------------------------------------------------------
|
|
1918
|
-
// Tab 8:
|
|
1923
|
+
// Tab 8: Agent Timeline (Gantt Chart)
|
|
1924
|
+
// ---------------------------------------------------------------------------
|
|
1925
|
+
renderAgentTimeline() {
|
|
1926
|
+
var trace = this.selectedTraceData || this.selectedTrace;
|
|
1927
|
+
if (!trace || !trace.agentId) {
|
|
1928
|
+
document.getElementById('agentTimelineEmpty').style.display = '';
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
var agentId = trace.agentId;
|
|
1933
|
+
var self = this;
|
|
1934
|
+
|
|
1935
|
+
if (this._agentTimelineAgent === agentId && this._agentTimelineRendered) return;
|
|
1936
|
+
this._agentTimelineAgent = agentId;
|
|
1937
|
+
|
|
1938
|
+
var container = document.getElementById('agentTimelineContent');
|
|
1939
|
+
container.innerHTML =
|
|
1940
|
+
'<div class="empty-state"><div class="empty-state-icon" style="animation:spin 1s linear infinite">⚙</div>' +
|
|
1941
|
+
'<div class="empty-state-text">Loading timeline for ' + escapeHtml(agentId) + '...</div></div>';
|
|
1942
|
+
|
|
1943
|
+
fetch('/api/agents/' + encodeURIComponent(agentId) + '/timeline?limit=50')
|
|
1944
|
+
.then(function(r) { return r.json(); })
|
|
1945
|
+
.then(function(data) {
|
|
1946
|
+
if (data.error || !data.executions || data.executions.length === 0) {
|
|
1947
|
+
container.innerHTML =
|
|
1948
|
+
'<div class="empty-state"><div class="empty-state-text">No timeline data for ' + escapeHtml(agentId) + '</div></div>';
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
self._agentTimelineRendered = true;
|
|
1952
|
+
self._renderGantt(container, data);
|
|
1953
|
+
})
|
|
1954
|
+
.catch(function() {
|
|
1955
|
+
container.innerHTML =
|
|
1956
|
+
'<div class="empty-state"><div class="empty-state-text">Failed to load agent timeline.</div></div>';
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
_renderGantt(container, data) {
|
|
1961
|
+
var execs = data.executions;
|
|
1962
|
+
var minTime = data.minTime;
|
|
1963
|
+
var maxTime = data.maxTime;
|
|
1964
|
+
var timeSpan = maxTime - minTime || 1;
|
|
1965
|
+
var self = this;
|
|
1966
|
+
|
|
1967
|
+
// Layout constants
|
|
1968
|
+
var labelW = 220;
|
|
1969
|
+
var chartW = 900;
|
|
1970
|
+
var rowH = 28;
|
|
1971
|
+
var subRowH = 20;
|
|
1972
|
+
var headerH = 36;
|
|
1973
|
+
var totalW = labelW + chartW + 20;
|
|
1974
|
+
|
|
1975
|
+
// Build HTML
|
|
1976
|
+
var html = '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' + totalW + 'px;">';
|
|
1977
|
+
|
|
1978
|
+
// Header with time axis
|
|
1979
|
+
html += '<div class="gantt-header" style="display:flex;height:' + headerH + 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
|
|
1980
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
|
|
1981
|
+
html += '<div style="flex:1;position:relative;">';
|
|
1982
|
+
// Time ticks
|
|
1983
|
+
var tickCount = 6;
|
|
1984
|
+
for (var t = 0; t <= tickCount; t++) {
|
|
1985
|
+
var pct = (t / tickCount) * 100;
|
|
1986
|
+
var tickTime = minTime + (t / tickCount) * timeSpan;
|
|
1987
|
+
var d = new Date(tickTime);
|
|
1988
|
+
var label = d.getMonth() + 1 + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
|
1989
|
+
html += '<div style="position:absolute;left:' + pct + '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' + label + '</div>';
|
|
1990
|
+
}
|
|
1991
|
+
html += '</div></div>';
|
|
1992
|
+
|
|
1993
|
+
// Rows
|
|
1994
|
+
html += '<div class="gantt-body">';
|
|
1995
|
+
for (var i = 0; i < execs.length; i++) {
|
|
1996
|
+
var exec = execs[i];
|
|
1997
|
+
var execStart = ((exec.startTime - minTime) / timeSpan) * 100;
|
|
1998
|
+
var execWidth = Math.max(0.3, ((exec.endTime - exec.startTime) / timeSpan) * 100);
|
|
1999
|
+
var statusColor = exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
|
|
2000
|
+
var hasActivities = exec.activities && exec.activities.length > 0;
|
|
2001
|
+
var execId = 'gantt-exec-' + i;
|
|
2002
|
+
|
|
2003
|
+
// Main execution row
|
|
2004
|
+
html += '<div class="gantt-row" style="display:flex;height:' + rowH + 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
|
|
2005
|
+
'onclick="(function(){var el=document.getElementById(\'' + execId + '\');if(el)el.style.display=el.style.display===\'none\'?\'block\':\'none\';})()" ' +
|
|
2006
|
+
'title="Click to ' + (hasActivities ? 'expand' : 'view') + '">';
|
|
2007
|
+
|
|
2008
|
+
// Label
|
|
2009
|
+
var execName = exec.name || exec.filename || exec.id;
|
|
2010
|
+
if (execName.length > 28) execName = execName.slice(0, 28) + '...';
|
|
2011
|
+
var dur = this.computeDuration(exec.startTime, exec.endTime);
|
|
2012
|
+
var triggerBadge = exec.trigger ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' + escapeHtml(exec.trigger) + '</span>' : '';
|
|
2013
|
+
var expandIcon = hasActivities ? '<span style="color:#6b7280;margin-right:4px;">▶</span>' : '<span style="width:14px;display:inline-block;"></span>';
|
|
2014
|
+
|
|
2015
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' + (rowH - 8) + 'px;">' +
|
|
2016
|
+
expandIcon + escapeHtml(execName) + triggerBadge + '</div>';
|
|
2017
|
+
|
|
2018
|
+
// Bar
|
|
2019
|
+
html += '<div style="flex:1;position:relative;padding:4px 0;">';
|
|
2020
|
+
html += '<div style="position:absolute;left:' + execStart + '%;width:' + execWidth + '%;top:4px;height:' + (rowH - 12) + 'px;' +
|
|
2021
|
+
'background:' + statusColor + ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
|
|
2022
|
+
'title="' + escapeHtml(exec.name || '') + ' | ' + dur + ' | ' + escapeHtml(exec.status) + '"></div>';
|
|
2023
|
+
html += '</div></div>';
|
|
2024
|
+
|
|
2025
|
+
// Sub-activities (collapsed by default)
|
|
2026
|
+
if (hasActivities) {
|
|
2027
|
+
html += '<div id="' + execId + '" style="display:none;background:#0a0e14;">';
|
|
2028
|
+
// Filter to top-level activities (no parentId or parentId is root)
|
|
2029
|
+
var rootIds = new Set();
|
|
2030
|
+
if (exec.activities.length > 0) {
|
|
2031
|
+
var firstAct = exec.activities[0];
|
|
2032
|
+
rootIds.add(firstAct.id);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
for (var j = 0; j < exec.activities.length; j++) {
|
|
2036
|
+
var act = exec.activities[j];
|
|
2037
|
+
var actStart = ((Math.max(act.startTime, exec.startTime) - minTime) / timeSpan) * 100;
|
|
2038
|
+
var actEnd = act.endTime || act.startTime;
|
|
2039
|
+
var actWidth = Math.max(0.2, ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100);
|
|
2040
|
+
var actColor = act.status === 'failed' ? '#f87171' :
|
|
2041
|
+
act.type === 'user' ? '#60a5fa' :
|
|
2042
|
+
act.type === 'assistant' ? '#34d399' :
|
|
2043
|
+
act.type === 'thinking' ? '#a78bfa' :
|
|
2044
|
+
act.type === 'tool_call' ? '#fb923c' :
|
|
2045
|
+
act.type === 'tool_result' ? '#4ade80' :
|
|
2046
|
+
act.type === 'agent' ? '#38bdf8' :
|
|
2047
|
+
'#6b7280';
|
|
2048
|
+
var actName = act.name || act.type;
|
|
2049
|
+
if (actName.length > 30) actName = actName.slice(0, 30) + '...';
|
|
2050
|
+
var isChild = act.parentId && !rootIds.has(act.id);
|
|
2051
|
+
|
|
2052
|
+
html += '<div style="display:flex;height:' + subRowH + 'px;border-bottom:1px solid #0d1117;">';
|
|
2053
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:2px 10px 2px ' + (isChild ? '30' : '20') + 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' + (subRowH - 4) + 'px;">' +
|
|
2054
|
+
'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + actColor + ';margin-right:6px;vertical-align:middle;"></span>' +
|
|
2055
|
+
escapeHtml(actName) + '</div>';
|
|
2056
|
+
html += '<div style="flex:1;position:relative;">';
|
|
2057
|
+
html += '<div style="position:absolute;left:' + actStart + '%;width:' + actWidth + '%;top:3px;height:' + (subRowH - 8) + 'px;' +
|
|
2058
|
+
'background:' + actColor + ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
|
|
2059
|
+
'title="' + escapeHtml(act.name || act.type) + ' | ' + escapeHtml(act.status) + '"></div>';
|
|
2060
|
+
html += '</div></div>';
|
|
2061
|
+
}
|
|
2062
|
+
html += '</div>';
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
html += '</div>';
|
|
2067
|
+
|
|
2068
|
+
// Summary bar
|
|
2069
|
+
html += '<div style="padding:10px;border-top:1px solid #30363d;color:#8b949e;font-size:10px;">';
|
|
2070
|
+
html += escapeHtml(data.agentId) + ' — ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
|
|
2071
|
+
var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
|
|
2072
|
+
html += ' — ' + timeRange;
|
|
2073
|
+
html += '</div>';
|
|
2074
|
+
|
|
2075
|
+
html += '</div>';
|
|
2076
|
+
container.innerHTML = html;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// ---------------------------------------------------------------------------
|
|
2080
|
+
// Tab 9: Process Map (Process Mining Graph)
|
|
1919
2081
|
// ---------------------------------------------------------------------------
|
|
1920
2082
|
renderProcessMap() {
|
|
1921
2083
|
var trace = this.selectedTraceData || this.selectedTrace;
|
package/dist/public/index.html
CHANGED
|
@@ -1235,6 +1235,7 @@
|
|
|
1235
1235
|
<div class="tab" data-tab="state">State Machine</div>
|
|
1236
1236
|
<div class="tab" data-tab="summary">Summary</div>
|
|
1237
1237
|
<div class="tab" data-tab="transcript">Transcript</div>
|
|
1238
|
+
<div class="tab" data-tab="agenttimeline">Agent Timeline</div>
|
|
1238
1239
|
<div class="tab" data-tab="processmap">Process Map</div>
|
|
1239
1240
|
</div>
|
|
1240
1241
|
|
|
@@ -1334,6 +1335,17 @@
|
|
|
1334
1335
|
</div>
|
|
1335
1336
|
</div>
|
|
1336
1337
|
|
|
1338
|
+
<!-- Agent Timeline (Gantt) tab -->
|
|
1339
|
+
<div class="tab-panel" id="panel-agenttimeline">
|
|
1340
|
+
<div class="timeline-container" id="agentTimelineContent" style="overflow:auto;">
|
|
1341
|
+
<div class="empty-state" id="agentTimelineEmpty">
|
|
1342
|
+
<div class="empty-state-icon">☰</div>
|
|
1343
|
+
<div class="empty-state-title">Agent Timeline</div>
|
|
1344
|
+
<div class="empty-state-text">Select a trace to view all executions for its agent as a Gantt chart.</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
1348
|
+
|
|
1337
1349
|
<!-- Process Map tab -->
|
|
1338
1350
|
<div class="tab-panel" id="panel-processmap">
|
|
1339
1351
|
<div id="cyProcessMap" style="width:100%;height:100%;min-height:500px;">
|
package/dist/server.cjs
CHANGED
|
@@ -1701,6 +1701,75 @@ var DashboardServer = class {
|
|
|
1701
1701
|
res.status(500).json({ error: "Failed to load statistics" });
|
|
1702
1702
|
}
|
|
1703
1703
|
});
|
|
1704
|
+
this.app.get("/api/agents/:agentId/timeline", (req, res) => {
|
|
1705
|
+
try {
|
|
1706
|
+
const agentId = req.params.agentId;
|
|
1707
|
+
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
1708
|
+
const rawTraces = this.watcher.getTracesByAgent(agentId);
|
|
1709
|
+
if (rawTraces.length === 0) {
|
|
1710
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1711
|
+
}
|
|
1712
|
+
const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
|
|
1713
|
+
const executions = traces.map((t) => {
|
|
1714
|
+
const serialized = serializeTrace(t);
|
|
1715
|
+
const nodes = serialized.nodes || {};
|
|
1716
|
+
const events = serialized.sessionEvents || [];
|
|
1717
|
+
const activities = [];
|
|
1718
|
+
if (events.length > 0) {
|
|
1719
|
+
for (let i = 0; i < events.length; i++) {
|
|
1720
|
+
const evt = events[i];
|
|
1721
|
+
if (evt.type === "system" || evt.type === "model_change") continue;
|
|
1722
|
+
const dur = evt.duration || 0;
|
|
1723
|
+
const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
|
|
1724
|
+
const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
|
|
1725
|
+
const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
|
|
1726
|
+
activities.push({
|
|
1727
|
+
id: evt.id || `evt-${i}`,
|
|
1728
|
+
name: evt.toolName || evt.name || evt.type,
|
|
1729
|
+
type: evt.type,
|
|
1730
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1731
|
+
startTime: startTs,
|
|
1732
|
+
endTime: endTs,
|
|
1733
|
+
parentId: evt.parentId
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
} else {
|
|
1737
|
+
const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1738
|
+
for (const node of sorted) {
|
|
1739
|
+
activities.push({
|
|
1740
|
+
id: node.id,
|
|
1741
|
+
name: node.name || node.type || node.id,
|
|
1742
|
+
type: node.type || "unknown",
|
|
1743
|
+
status: node.status || "completed",
|
|
1744
|
+
startTime: node.startTime || t.startTime,
|
|
1745
|
+
endTime: node.endTime || node.startTime || t.startTime,
|
|
1746
|
+
parentId: node.parentId
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
id: serialized.id || serialized.filename,
|
|
1752
|
+
filename: serialized.filename,
|
|
1753
|
+
name: serialized.name || serialized.filename,
|
|
1754
|
+
agentId: serialized.agentId,
|
|
1755
|
+
trigger: serialized.trigger,
|
|
1756
|
+
status: serialized.status || "completed",
|
|
1757
|
+
sourceType: serialized.sourceType,
|
|
1758
|
+
startTime: serialized.startTime,
|
|
1759
|
+
endTime: serialized.endTime || serialized.startTime,
|
|
1760
|
+
tokenUsage: serialized.tokenUsage,
|
|
1761
|
+
activities
|
|
1762
|
+
};
|
|
1763
|
+
});
|
|
1764
|
+
const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
|
|
1765
|
+
const minTime = Math.min(...allTimes);
|
|
1766
|
+
const maxTime = Math.max(...allTimes);
|
|
1767
|
+
res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
console.error("Agent timeline error:", error);
|
|
1770
|
+
res.status(500).json({ error: "Failed to build agent timeline" });
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1704
1773
|
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1705
1774
|
try {
|
|
1706
1775
|
const agentId = req.params.agentId;
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
package/public/dashboard.js
CHANGED
|
@@ -307,11 +307,15 @@ class AgentFlowDashboard {
|
|
|
307
307
|
this.selectedTrace = trace;
|
|
308
308
|
this.selectedTraceData = trace;
|
|
309
309
|
|
|
310
|
-
// Reset
|
|
310
|
+
// Reset agent-level caches when agent changes
|
|
311
311
|
if (this._processMapAgent !== trace.agentId) {
|
|
312
312
|
this._processMapAgent = null;
|
|
313
313
|
if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
|
|
314
314
|
}
|
|
315
|
+
if (this._agentTimelineAgent !== trace.agentId) {
|
|
316
|
+
this._agentTimelineAgent = null;
|
|
317
|
+
this._agentTimelineRendered = false;
|
|
318
|
+
}
|
|
315
319
|
|
|
316
320
|
// Update sidebar selection
|
|
317
321
|
document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
|
|
@@ -856,6 +860,7 @@ class AgentFlowDashboard {
|
|
|
856
860
|
case 'state': this.renderStateMachine(); break;
|
|
857
861
|
case 'summary': this.renderSummary(); break;
|
|
858
862
|
case 'transcript': this.renderTranscript(); break;
|
|
863
|
+
case 'agenttimeline': this.renderAgentTimeline(); break;
|
|
859
864
|
case 'processmap': this.renderProcessMap(); break;
|
|
860
865
|
}
|
|
861
866
|
this.updateToolbarInfo();
|
|
@@ -1915,7 +1920,164 @@ class AgentFlowDashboard {
|
|
|
1915
1920
|
}
|
|
1916
1921
|
|
|
1917
1922
|
// ---------------------------------------------------------------------------
|
|
1918
|
-
// Tab 8:
|
|
1923
|
+
// Tab 8: Agent Timeline (Gantt Chart)
|
|
1924
|
+
// ---------------------------------------------------------------------------
|
|
1925
|
+
renderAgentTimeline() {
|
|
1926
|
+
var trace = this.selectedTraceData || this.selectedTrace;
|
|
1927
|
+
if (!trace || !trace.agentId) {
|
|
1928
|
+
document.getElementById('agentTimelineEmpty').style.display = '';
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
var agentId = trace.agentId;
|
|
1933
|
+
var self = this;
|
|
1934
|
+
|
|
1935
|
+
if (this._agentTimelineAgent === agentId && this._agentTimelineRendered) return;
|
|
1936
|
+
this._agentTimelineAgent = agentId;
|
|
1937
|
+
|
|
1938
|
+
var container = document.getElementById('agentTimelineContent');
|
|
1939
|
+
container.innerHTML =
|
|
1940
|
+
'<div class="empty-state"><div class="empty-state-icon" style="animation:spin 1s linear infinite">⚙</div>' +
|
|
1941
|
+
'<div class="empty-state-text">Loading timeline for ' + escapeHtml(agentId) + '...</div></div>';
|
|
1942
|
+
|
|
1943
|
+
fetch('/api/agents/' + encodeURIComponent(agentId) + '/timeline?limit=50')
|
|
1944
|
+
.then(function(r) { return r.json(); })
|
|
1945
|
+
.then(function(data) {
|
|
1946
|
+
if (data.error || !data.executions || data.executions.length === 0) {
|
|
1947
|
+
container.innerHTML =
|
|
1948
|
+
'<div class="empty-state"><div class="empty-state-text">No timeline data for ' + escapeHtml(agentId) + '</div></div>';
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
self._agentTimelineRendered = true;
|
|
1952
|
+
self._renderGantt(container, data);
|
|
1953
|
+
})
|
|
1954
|
+
.catch(function() {
|
|
1955
|
+
container.innerHTML =
|
|
1956
|
+
'<div class="empty-state"><div class="empty-state-text">Failed to load agent timeline.</div></div>';
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
_renderGantt(container, data) {
|
|
1961
|
+
var execs = data.executions;
|
|
1962
|
+
var minTime = data.minTime;
|
|
1963
|
+
var maxTime = data.maxTime;
|
|
1964
|
+
var timeSpan = maxTime - minTime || 1;
|
|
1965
|
+
var self = this;
|
|
1966
|
+
|
|
1967
|
+
// Layout constants
|
|
1968
|
+
var labelW = 220;
|
|
1969
|
+
var chartW = 900;
|
|
1970
|
+
var rowH = 28;
|
|
1971
|
+
var subRowH = 20;
|
|
1972
|
+
var headerH = 36;
|
|
1973
|
+
var totalW = labelW + chartW + 20;
|
|
1974
|
+
|
|
1975
|
+
// Build HTML
|
|
1976
|
+
var html = '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' + totalW + 'px;">';
|
|
1977
|
+
|
|
1978
|
+
// Header with time axis
|
|
1979
|
+
html += '<div class="gantt-header" style="display:flex;height:' + headerH + 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
|
|
1980
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
|
|
1981
|
+
html += '<div style="flex:1;position:relative;">';
|
|
1982
|
+
// Time ticks
|
|
1983
|
+
var tickCount = 6;
|
|
1984
|
+
for (var t = 0; t <= tickCount; t++) {
|
|
1985
|
+
var pct = (t / tickCount) * 100;
|
|
1986
|
+
var tickTime = minTime + (t / tickCount) * timeSpan;
|
|
1987
|
+
var d = new Date(tickTime);
|
|
1988
|
+
var label = d.getMonth() + 1 + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
|
1989
|
+
html += '<div style="position:absolute;left:' + pct + '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' + label + '</div>';
|
|
1990
|
+
}
|
|
1991
|
+
html += '</div></div>';
|
|
1992
|
+
|
|
1993
|
+
// Rows
|
|
1994
|
+
html += '<div class="gantt-body">';
|
|
1995
|
+
for (var i = 0; i < execs.length; i++) {
|
|
1996
|
+
var exec = execs[i];
|
|
1997
|
+
var execStart = ((exec.startTime - minTime) / timeSpan) * 100;
|
|
1998
|
+
var execWidth = Math.max(0.3, ((exec.endTime - exec.startTime) / timeSpan) * 100);
|
|
1999
|
+
var statusColor = exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
|
|
2000
|
+
var hasActivities = exec.activities && exec.activities.length > 0;
|
|
2001
|
+
var execId = 'gantt-exec-' + i;
|
|
2002
|
+
|
|
2003
|
+
// Main execution row
|
|
2004
|
+
html += '<div class="gantt-row" style="display:flex;height:' + rowH + 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
|
|
2005
|
+
'onclick="(function(){var el=document.getElementById(\'' + execId + '\');if(el)el.style.display=el.style.display===\'none\'?\'block\':\'none\';})()" ' +
|
|
2006
|
+
'title="Click to ' + (hasActivities ? 'expand' : 'view') + '">';
|
|
2007
|
+
|
|
2008
|
+
// Label
|
|
2009
|
+
var execName = exec.name || exec.filename || exec.id;
|
|
2010
|
+
if (execName.length > 28) execName = execName.slice(0, 28) + '...';
|
|
2011
|
+
var dur = this.computeDuration(exec.startTime, exec.endTime);
|
|
2012
|
+
var triggerBadge = exec.trigger ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' + escapeHtml(exec.trigger) + '</span>' : '';
|
|
2013
|
+
var expandIcon = hasActivities ? '<span style="color:#6b7280;margin-right:4px;">▶</span>' : '<span style="width:14px;display:inline-block;"></span>';
|
|
2014
|
+
|
|
2015
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' + (rowH - 8) + 'px;">' +
|
|
2016
|
+
expandIcon + escapeHtml(execName) + triggerBadge + '</div>';
|
|
2017
|
+
|
|
2018
|
+
// Bar
|
|
2019
|
+
html += '<div style="flex:1;position:relative;padding:4px 0;">';
|
|
2020
|
+
html += '<div style="position:absolute;left:' + execStart + '%;width:' + execWidth + '%;top:4px;height:' + (rowH - 12) + 'px;' +
|
|
2021
|
+
'background:' + statusColor + ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
|
|
2022
|
+
'title="' + escapeHtml(exec.name || '') + ' | ' + dur + ' | ' + escapeHtml(exec.status) + '"></div>';
|
|
2023
|
+
html += '</div></div>';
|
|
2024
|
+
|
|
2025
|
+
// Sub-activities (collapsed by default)
|
|
2026
|
+
if (hasActivities) {
|
|
2027
|
+
html += '<div id="' + execId + '" style="display:none;background:#0a0e14;">';
|
|
2028
|
+
// Filter to top-level activities (no parentId or parentId is root)
|
|
2029
|
+
var rootIds = new Set();
|
|
2030
|
+
if (exec.activities.length > 0) {
|
|
2031
|
+
var firstAct = exec.activities[0];
|
|
2032
|
+
rootIds.add(firstAct.id);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
for (var j = 0; j < exec.activities.length; j++) {
|
|
2036
|
+
var act = exec.activities[j];
|
|
2037
|
+
var actStart = ((Math.max(act.startTime, exec.startTime) - minTime) / timeSpan) * 100;
|
|
2038
|
+
var actEnd = act.endTime || act.startTime;
|
|
2039
|
+
var actWidth = Math.max(0.2, ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100);
|
|
2040
|
+
var actColor = act.status === 'failed' ? '#f87171' :
|
|
2041
|
+
act.type === 'user' ? '#60a5fa' :
|
|
2042
|
+
act.type === 'assistant' ? '#34d399' :
|
|
2043
|
+
act.type === 'thinking' ? '#a78bfa' :
|
|
2044
|
+
act.type === 'tool_call' ? '#fb923c' :
|
|
2045
|
+
act.type === 'tool_result' ? '#4ade80' :
|
|
2046
|
+
act.type === 'agent' ? '#38bdf8' :
|
|
2047
|
+
'#6b7280';
|
|
2048
|
+
var actName = act.name || act.type;
|
|
2049
|
+
if (actName.length > 30) actName = actName.slice(0, 30) + '...';
|
|
2050
|
+
var isChild = act.parentId && !rootIds.has(act.id);
|
|
2051
|
+
|
|
2052
|
+
html += '<div style="display:flex;height:' + subRowH + 'px;border-bottom:1px solid #0d1117;">';
|
|
2053
|
+
html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:2px 10px 2px ' + (isChild ? '30' : '20') + 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' + (subRowH - 4) + 'px;">' +
|
|
2054
|
+
'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + actColor + ';margin-right:6px;vertical-align:middle;"></span>' +
|
|
2055
|
+
escapeHtml(actName) + '</div>';
|
|
2056
|
+
html += '<div style="flex:1;position:relative;">';
|
|
2057
|
+
html += '<div style="position:absolute;left:' + actStart + '%;width:' + actWidth + '%;top:3px;height:' + (subRowH - 8) + 'px;' +
|
|
2058
|
+
'background:' + actColor + ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
|
|
2059
|
+
'title="' + escapeHtml(act.name || act.type) + ' | ' + escapeHtml(act.status) + '"></div>';
|
|
2060
|
+
html += '</div></div>';
|
|
2061
|
+
}
|
|
2062
|
+
html += '</div>';
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
html += '</div>';
|
|
2067
|
+
|
|
2068
|
+
// Summary bar
|
|
2069
|
+
html += '<div style="padding:10px;border-top:1px solid #30363d;color:#8b949e;font-size:10px;">';
|
|
2070
|
+
html += escapeHtml(data.agentId) + ' — ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
|
|
2071
|
+
var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
|
|
2072
|
+
html += ' — ' + timeRange;
|
|
2073
|
+
html += '</div>';
|
|
2074
|
+
|
|
2075
|
+
html += '</div>';
|
|
2076
|
+
container.innerHTML = html;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// ---------------------------------------------------------------------------
|
|
2080
|
+
// Tab 9: Process Map (Process Mining Graph)
|
|
1919
2081
|
// ---------------------------------------------------------------------------
|
|
1920
2082
|
renderProcessMap() {
|
|
1921
2083
|
var trace = this.selectedTraceData || this.selectedTrace;
|
package/public/index.html
CHANGED
|
@@ -1235,6 +1235,7 @@
|
|
|
1235
1235
|
<div class="tab" data-tab="state">State Machine</div>
|
|
1236
1236
|
<div class="tab" data-tab="summary">Summary</div>
|
|
1237
1237
|
<div class="tab" data-tab="transcript">Transcript</div>
|
|
1238
|
+
<div class="tab" data-tab="agenttimeline">Agent Timeline</div>
|
|
1238
1239
|
<div class="tab" data-tab="processmap">Process Map</div>
|
|
1239
1240
|
</div>
|
|
1240
1241
|
|
|
@@ -1334,6 +1335,17 @@
|
|
|
1334
1335
|
</div>
|
|
1335
1336
|
</div>
|
|
1336
1337
|
|
|
1338
|
+
<!-- Agent Timeline (Gantt) tab -->
|
|
1339
|
+
<div class="tab-panel" id="panel-agenttimeline">
|
|
1340
|
+
<div class="timeline-container" id="agentTimelineContent" style="overflow:auto;">
|
|
1341
|
+
<div class="empty-state" id="agentTimelineEmpty">
|
|
1342
|
+
<div class="empty-state-icon">☰</div>
|
|
1343
|
+
<div class="empty-state-title">Agent Timeline</div>
|
|
1344
|
+
<div class="empty-state-text">Select a trace to view all executions for its agent as a Gantt chart.</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
1348
|
+
|
|
1337
1349
|
<!-- Process Map tab -->
|
|
1338
1350
|
<div class="tab-panel" id="panel-processmap">
|
|
1339
1351
|
<div id="cyProcessMap" style="width:100%;height:100%;min-height:500px;">
|