let currentEventSource = null;
function createTask() {
const promptInput = document.getElementById('prompt-input');
const prompt = promptInput.value.trim();
if (!prompt) {
alert("Please enter a valid prompt");
promptInput.focus();
return;
}
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
const container = document.getElementById('task-container');
container.innerHTML = '
Initializing task...
';
document.getElementById('input-container').classList.add('bottom');
fetch('/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.detail || 'Request failed') });
}
return response.json();
})
.then(data => {
if (!data.task_id) {
throw new Error('Invalid task ID');
}
setupSSE(data.task_id);
loadHistory();
})
.catch(error => {
container.innerHTML = `Error: ${error.message}
`;
console.error('Failed to create task:', error);
});
}
function setupSSE(taskId) {
let retryCount = 0;
const maxRetries = 3;
const retryDelay = 2000;
function connect() {
const eventSource = new EventSource(`/tasks/${taskId}/events`);
currentEventSource = eventSource;
const container = document.getElementById('task-container');
let heartbeatTimer = setInterval(() => {
container.innerHTML += '·
';
}, 5000);
const pollInterval = setInterval(() => {
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Polling failed:', error);
});
}, 10000);
if (!eventSource._listenersAdded) {
eventSource._listenersAdded = true;
let lastResultContent = '';
eventSource.addEventListener('status', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
container.classList.add('active');
const welcomeMessage = document.querySelector('.welcome-message');
if (welcomeMessage) {
welcomeMessage.style.display = 'none';
}
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
// Save result content
if (data.steps && data.steps.length > 0) {
// Iterate through all steps, find the last result type
for (let i = data.steps.length - 1; i >= 0; i--) {
if (data.steps[i].type === 'result') {
lastResultContent = data.steps[i].result;
break;
}
}
}
// Parse and display each step with proper formatting
stepContainer.innerHTML = data.steps.map(step => {
const content = step.result;
const timestamp = new Date().toLocaleTimeString();
return `
${getEventIcon(step.type)} [${timestamp}] ${getEventLabel(step.type)}:
${content}
`;
}).join('');
// Auto-scroll to bottom
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
} catch (e) {
console.error('Status update failed:', e);
}
});
// Add handler for think event
eventSource.addEventListener('think', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
const step = document.createElement('div');
step.className = 'step-item think';
step.innerHTML = `
${getEventIcon('think')} [${timestamp}] ${getEventLabel('think')}:
${content}
`;
stepContainer.appendChild(step);
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
// Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error('Think event handling failed:', e);
}
});
// Add handler for tool event
eventSource.addEventListener('tool', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
const step = document.createElement('div');
step.className = 'step-item tool';
step.innerHTML = `
${getEventIcon('tool')} [${timestamp}] ${getEventLabel('tool')}:
${content}
`;
stepContainer.appendChild(step);
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
// Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error('Tool event handling failed:', e);
}
});
// Add handler for act event
eventSource.addEventListener('act', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
const step = document.createElement('div');
step.className = 'step-item act';
step.innerHTML = `
${getEventIcon('act')} [${timestamp}] ${getEventLabel('act')}:
${content}
`;
stepContainer.appendChild(step);
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
// Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error('Act event handling failed:', e);
}
});
// Add handler for log event
eventSource.addEventListener('log', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
const step = document.createElement('div');
step.className = 'step-item log';
step.innerHTML = `
${getEventIcon('log')} [${timestamp}] ${getEventLabel('log')}:
${content}
`;
stepContainer.appendChild(step);
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
// Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error('Log event handling failed:', e);
}
});
eventSource.addEventListener('run', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
const step = document.createElement('div');
step.className = 'step-item run';
step.innerHTML = `
${getEventIcon('run')} [${timestamp}] ${getEventLabel('run')}:
${content}
`;
stepContainer.appendChild(step);
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
// Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error('Run event handling failed:', e);
}
});
eventSource.addEventListener('message', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
stepContainer = container.querySelector('.step-container');
}
// Create new step element
const step = document.createElement('div');
step.className = `step-item ${data.type || 'step'}`;
// Format content and timestamp
const content = data.result;
const timestamp = new Date().toLocaleTimeString();
step.innerHTML = `
${getEventIcon(data.type)} [${timestamp}] ${getEventLabel(data.type)}:
${content}
`;
// Add step to container with animation
stepContainer.prepend(step);
setTimeout(() => {
step.classList.add('show');
}, 10);
// Auto-scroll to bottom
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
} catch (e) {
console.error('Message handling failed:', e);
}
});
let isTaskComplete = false;
eventSource.addEventListener('complete', (event) => {
isTaskComplete = true;
clearInterval(heartbeatTimer);
clearInterval(pollInterval);
container.innerHTML += `
✅ Task completed
${lastResultContent}
`;
eventSource.close();
currentEventSource = null;
lastResultContent = ''; // Clear result content
});
eventSource.addEventListener('error', (event) => {
clearInterval(heartbeatTimer);
clearInterval(pollInterval);
try {
const data = JSON.parse(event.data);
container.innerHTML += `
❌ Error: ${data.message}
`;
eventSource.close();
currentEventSource = null;
} catch (e) {
console.error('Error handling failed:', e);
}
});
}
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
});
eventSource.onerror = (err) => {
if (isTaskComplete) {
return;
}
console.error('SSE connection error:', err);
clearInterval(heartbeatTimer);
clearInterval(pollInterval);
eventSource.close();
if (retryCount < maxRetries) {
retryCount++;
container.innerHTML += `
⚠ Connection lost, retrying in ${retryDelay/1000} seconds (${retryCount}/${maxRetries})...
`;
setTimeout(connect, retryDelay);
} else {
container.innerHTML += `
⚠ Connection lost, please try refreshing the page
`;
}
};
}
connect();
}
function getEventIcon(eventType) {
switch(eventType) {
case 'think': return '🤔';
case 'tool': return '🛠️';
case 'act': return '🚀';
case 'result': return '🏁';
case 'error': return '❌';
case 'complete': return '✅';
case 'log': return '📝';
case 'run': return '⚙️';
default: return 'ℹ️';
}
}
function getEventLabel(eventType) {
switch(eventType) {
case 'think': return 'Thinking';
case 'tool': return 'Using Tool';
case 'act': return 'Action';
case 'result': return 'Result';
case 'error': return 'Error';
case 'complete': return 'Complete';
case 'log': return 'Log';
case 'run': return 'Running';
default: return 'Info';
}
}
function updateTaskStatus(task) {
const statusBar = document.getElementById('status-bar');
if (!statusBar) return;
if (task.status === 'completed') {
statusBar.innerHTML = `✅ Task completed`;
} else if (task.status === 'failed') {
statusBar.innerHTML = `❌ Task failed: ${task.error || 'Unknown error'}`;
} else {
statusBar.innerHTML = `⚙️ Task running: ${task.status}`;
}
}
function loadHistory() {
fetch('/tasks')
.then(response => {
if (!response.ok) {
throw new Error('Failed to load history');
}
return response.json();
})
.then(tasks => {
const historyContainer = document.getElementById('history-container');
if (!historyContainer) return;
historyContainer.innerHTML = '';
if (tasks.length === 0) {
historyContainer.innerHTML = 'No recent tasks
';
return;
}
const historyList = document.createElement('div');
historyList.className = 'history-list';
tasks.forEach(task => {
const taskItem = document.createElement('div');
taskItem.className = `history-item ${task.status}`;
taskItem.innerHTML = `
${task.prompt}
${new Date(task.created_at).toLocaleString()}
${getStatusIcon(task.status)}
`;
taskItem.addEventListener('click', () => {
loadTask(task.id);
});
historyList.appendChild(taskItem);
});
historyContainer.appendChild(historyList);
})
.catch(error => {
console.error('Failed to load history:', error);
const historyContainer = document.getElementById('history-container');
if (historyContainer) {
historyContainer.innerHTML = `Failed to load history: ${error.message}
`;
}
});
}
function getStatusIcon(status) {
switch(status) {
case 'completed': return '✅';
case 'failed': return '❌';
case 'running': return '⚙️';
default: return '⏳';
}
}
function loadTask(taskId) {
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
const container = document.getElementById('task-container');
container.innerHTML = 'Loading task...
';
document.getElementById('input-container').classList.add('bottom');
fetch(`/tasks/${taskId}`)
.then(response => {
if (!response.ok) {
throw new Error('Failed to load task');
}
return response.json();
})
.then(task => {
if (task.status === 'running') {
setupSSE(taskId);
} else {
displayTask(task);
}
})
.catch(error => {
console.error('Failed to load task:', error);
container.innerHTML = `Failed to load task: ${error.message}
`;
});
}
function displayTask(task) {
const container = document.getElementById('task-container');
container.innerHTML = '';
container.classList.add('active');
const welcomeMessage = document.querySelector('.welcome-message');
if (welcomeMessage) {
welcomeMessage.style.display = 'none';
}
const stepContainer = document.createElement('div');
stepContainer.className = 'step-container';
if (task.steps && task.steps.length > 0) {
task.steps.forEach(step => {
const stepItem = document.createElement('div');
stepItem.className = `step-item ${step.type || 'step'}`;
const content = step.result;
const timestamp = new Date(step.timestamp || task.created_at).toLocaleTimeString();
stepItem.innerHTML = `
${getEventIcon(step.type)} [${timestamp}] ${getEventLabel(step.type)}:
${content}
`;
stepContainer.appendChild(stepItem);
});
} else {
stepContainer.innerHTML = 'No steps recorded for this task
';
}
container.appendChild(stepContainer);
if (task.status === 'completed') {
let lastResultContent = '';
if (task.steps && task.steps.length > 0) {
for (let i = task.steps.length - 1; i >= 0; i--) {
if (task.steps[i].type === 'result') {
lastResultContent = task.steps[i].result;
break;
}
}
}
container.innerHTML += `
✅ Task completed
${lastResultContent}
`;
} else if (task.status === 'failed') {
container.innerHTML += `
❌ Error: ${task.error || 'Unknown error'}
`;
}
updateTaskStatus(task);
}
// Initialize the app when the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
loadHistory();
// Set up event listeners
document.getElementById('create-task-btn').addEventListener('click', createTask);
document.getElementById('prompt-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
createTask();
}
});
// Show history button functionality
const historyToggle = document.getElementById('history-toggle');
if (historyToggle) {
historyToggle.addEventListener('click', () => {
const historyPanel = document.getElementById('history-panel');
if (historyPanel) {
historyPanel.classList.toggle('open');
historyToggle.classList.toggle('active');
}
});
}
// Clear button functionality
const clearButton = document.getElementById('clear-btn');
if (clearButton) {
clearButton.addEventListener('click', () => {
document.getElementById('prompt-input').value = '';
document.getElementById('prompt-input').focus();
});
}
});