Initial commit
This commit is contained in:
101
dashboard/js/components/agent-timeline.js
Normal file
101
dashboard/js/components/agent-timeline.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Agent Timeline — visual execution timeline built from SSE events
|
||||
*/
|
||||
|
||||
let containerEl;
|
||||
let steps = [];
|
||||
|
||||
export function initTimeline() {
|
||||
// The timeline renders inside the inspector; this module manages the data
|
||||
containerEl = null;
|
||||
steps = [];
|
||||
}
|
||||
|
||||
export function addTimelineStep({ step, totalSteps, agent, description, status }) {
|
||||
steps.push({
|
||||
step,
|
||||
totalSteps,
|
||||
agent,
|
||||
description,
|
||||
status: status || 'executing',
|
||||
tools: [],
|
||||
});
|
||||
|
||||
renderTimeline();
|
||||
}
|
||||
|
||||
export function updateTimelineStep(agent, { tool, status }) {
|
||||
// Find the last step for this agent
|
||||
for (let i = steps.length - 1; i >= 0; i--) {
|
||||
if (steps[i].agent === agent) {
|
||||
if (tool) {
|
||||
steps[i].tools.push({ name: tool, status: status || 'completed' });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
renderTimeline();
|
||||
}
|
||||
|
||||
export function clearTimeline() {
|
||||
steps = [];
|
||||
renderTimeline();
|
||||
}
|
||||
|
||||
function renderTimeline() {
|
||||
// Find or create the timeline container in the inspector
|
||||
const inspector = document.getElementById('inspector');
|
||||
if (!inspector) return;
|
||||
|
||||
// Remove existing timeline section
|
||||
let existing = document.getElementById('live-timeline-section');
|
||||
if (existing) existing.remove();
|
||||
|
||||
if (steps.length === 0) return;
|
||||
|
||||
const section = document.createElement('div');
|
||||
section.className = 'inspector-section';
|
||||
section.id = 'live-timeline-section';
|
||||
|
||||
let html = '<div class="inspector-section-title">Live Timeline</div>';
|
||||
html += '<div class="timeline">';
|
||||
|
||||
for (const s of steps) {
|
||||
const stepClass = s.status === 'executing' ? 'active' : s.status === 'completed' ? 'completed' : s.status === 'failed' ? 'failed' : '';
|
||||
|
||||
html += `
|
||||
<div class="timeline-step ${stepClass}">
|
||||
<div class="timeline-step-header">
|
||||
<span class="role-badge ${s.agent}">${s.agent}</span>
|
||||
<span class="text-sm text-muted">Step ${s.step}/${s.totalSteps}</span>
|
||||
</div>
|
||||
<div class="timeline-step-desc">${escapeHtml(s.description)}</div>
|
||||
`;
|
||||
|
||||
if (s.tools.length > 0) {
|
||||
html += '<div class="timeline-tools">';
|
||||
for (const t of s.tools) {
|
||||
html += `<span class="tool-chip">${escapeHtml(t.name)}</span>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
section.innerHTML = html;
|
||||
|
||||
// Insert at the top of inspector (after session info if present)
|
||||
const firstSection = inspector.querySelector('.inspector-section');
|
||||
if (firstSection && firstSection.nextSibling) {
|
||||
inspector.insertBefore(section, firstSection.nextSibling);
|
||||
} else {
|
||||
inspector.appendChild(section);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
Reference in New Issue
Block a user