102 lines
2.7 KiB
JavaScript
102 lines
2.7 KiB
JavaScript
/**
|
|
* 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, '>');
|
|
}
|