Files
agenticSystem/dashboard/js/app.js
2026-04-01 23:16:45 +01:00

253 lines
6.9 KiB
JavaScript

/**
* Application bootstrap — state management, health polling, routing
*/
import { api } from './api.js';
import { initSidebar, renderSessions } from './components/sidebar.js';
import { initSessionForm } from './components/session-form.js';
import { initToolbar, updateToolbar } from './components/toolbar.js';
import { initChat, addMessage, setStreamingContent, clearChat, setInputEnabled, showExecutionIndicator, hideExecutionIndicator } from './components/chat.js';
import { initEventLog, addEvent, clearEvents } from './components/event-log.js';
import { initInspector, updateInspector, clearInspector } from './components/session-inspector.js';
import { initTimeline, addTimelineStep, updateTimelineStep, clearTimeline } from './components/agent-timeline.js';
// --- Global state ---
export const state = {
sessions: JSON.parse(localStorage.getItem('agentic_sessions') || '[]'),
activeSessionId: null,
sessionState: null,
health: null,
isExecuting: false,
streamingContent: '',
currentAgent: '',
};
function saveSessions() {
localStorage.setItem('agentic_sessions', JSON.stringify(state.sessions));
}
// --- Session management ---
export async function selectSession(sessionId) {
api.unsubscribeAll();
state.activeSessionId = sessionId;
state.streamingContent = '';
state.currentAgent = '';
clearChat();
clearEvents();
clearTimeline();
clearInspector();
if (!sessionId) {
updateToolbar(null);
renderSessions();
return;
}
location.hash = `session=${sessionId}`;
renderSessions();
try {
const session = await api.getSession(sessionId);
state.sessionState = session;
updateToolbar(session);
updateInspector(session);
const events = await api.getEvents(sessionId);
for (const ev of events) addEvent(ev);
api.subscribeSSE(sessionId);
setInputEnabled(true);
} catch (e) {
addMessage('system', `Error loading session: ${e.message}`);
}
}
export async function createSession(body) {
const data = await api.createSession(body);
if (!state.sessions.includes(data.session_id)) {
state.sessions.unshift(data.session_id);
saveSessions();
}
await selectSession(data.session_id);
return data;
}
export async function deleteSession(sessionId) {
try {
await api.deleteSession(sessionId);
} catch { /* ignore 404 */ }
api.unsubscribeSSE(sessionId);
state.sessions = state.sessions.filter(s => s !== sessionId);
saveSessions();
if (state.activeSessionId === sessionId) {
state.activeSessionId = null;
state.sessionState = null;
location.hash = '';
clearChat();
clearEvents();
clearTimeline();
clearInspector();
updateToolbar(null);
}
renderSessions();
}
export async function sendMessage(message, stream = false) {
if (!state.activeSessionId || state.isExecuting) return;
state.isExecuting = true;
state.streamingContent = '';
setInputEnabled(false);
addMessage('user', message);
showExecutionIndicator();
try {
if (stream) {
await api.sendMessage(state.activeSessionId, message, true);
// Response comes via SSE — handled by event listeners
} else {
const result = await api.sendMessage(state.activeSessionId, message, false);
hideExecutionIndicator();
addMessage('assistant', result.content || JSON.stringify(result, null, 2));
state.isExecuting = false;
setInputEnabled(true);
// Refresh state
const session = await api.getSession(state.activeSessionId);
state.sessionState = session;
updateToolbar(session);
updateInspector(session);
}
} catch (e) {
hideExecutionIndicator();
addMessage('system', `Error: ${e.message}`);
state.isExecuting = false;
setInputEnabled(true);
}
}
// --- SSE event handlers ---
function setupSSEListeners() {
api.on('sse', (e) => {
addEvent(e.detail);
});
api.on('sse:execution.started', () => {
state.isExecuting = true;
showExecutionIndicator('Starting execution...');
});
api.on('sse:subagent.assigned', (e) => {
const d = e.detail.data || e.detail;
state.currentAgent = d.agent || '';
showExecutionIndicator(`Step ${d.step}/${d.total_steps}${d.agent}`);
addTimelineStep({
step: d.step,
totalSteps: d.total_steps,
agent: d.agent,
description: d.description,
status: 'executing',
});
});
api.on('sse:agent.delta', (e) => {
const d = e.detail.data || e.detail;
state.streamingContent += d.delta || '';
setStreamingContent(state.streamingContent, state.currentAgent);
});
api.on('sse:tool.started', (e) => {
const d = e.detail.data || e.detail;
showExecutionIndicator(`Running tool: ${d.tool}`);
});
api.on('sse:tool.completed', (e) => {
const d = e.detail.data || e.detail;
if (state.currentAgent) {
updateTimelineStep(state.currentAgent, { tool: d.tool, status: d.status });
}
});
api.on('sse:execution.completed', async (e) => {
hideExecutionIndicator();
if (state.streamingContent) {
// Finalize the streaming message
addMessage('assistant', state.streamingContent);
state.streamingContent = '';
}
state.isExecuting = false;
state.currentAgent = '';
setInputEnabled(true);
// Refresh session state
if (state.activeSessionId) {
try {
const session = await api.getSession(state.activeSessionId);
state.sessionState = session;
updateToolbar(session);
updateInspector(session);
} catch { /* ignore */ }
}
});
api.on('sse:error', (e) => {
const d = e.detail.data || e.detail;
addMessage('system', `Error: ${JSON.stringify(d)}`);
hideExecutionIndicator();
state.isExecuting = false;
setInputEnabled(true);
});
}
// --- Health polling ---
let healthInterval;
function startHealthPolling() {
const poll = async () => {
state.health = await api.checkHealth();
};
poll();
healthInterval = setInterval(poll, 10000);
}
// --- Theme ---
export function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('agentic_theme', next);
}
// --- Init ---
function init() {
// Restore theme
const savedTheme = localStorage.getItem('agentic_theme') || 'dark';
if (savedTheme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
}
// Init components
initToolbar();
initSidebar();
initSessionForm();
initChat();
initEventLog();
initInspector();
initTimeline();
// SSE listeners
setupSSEListeners();
// Health
startHealthPolling();
// Restore session from hash
const hash = location.hash;
const match = hash.match(/session=([a-f0-9]+)/);
if (match && state.sessions.includes(match[1])) {
selectSession(match[1]);
}
renderSessions();
}
document.addEventListener('DOMContentLoaded', init);