Initial commit
This commit is contained in:
252
dashboard/js/app.js
Normal file
252
dashboard/js/app.js
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user