Spaces:
Running
Running
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 = '<div class="loading">Initializing task...</div>'; | |
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(); | |
promptInput.value = ''; | |
}) | |
.catch(error => { | |
container.innerHTML = `<div class="error">Error: ${error.message}</div>`; | |
console.error('Failed to create task:', error); | |
}); | |
} | |
function setupSSE(taskId) { | |
let retryCount = 0; | |
const maxRetries = 3; | |
const retryDelay = 2000; | |
let lastResultContent = ''; | |
const container = document.getElementById('task-container'); | |
function connect() { | |
const eventSource = new EventSource(`/tasks/${taskId}/events`); | |
currentEventSource = eventSource; | |
let heartbeatTimer = setInterval(() => { | |
container.innerHTML += '<div class="ping">Β·</div>'; | |
}, 5000); | |
// Initial polling | |
fetch(`/tasks/${taskId}`) | |
.then(response => response.json()) | |
.then(task => { | |
updateTaskStatus(task); | |
}) | |
.catch(error => { | |
console.error('Initial status fetch failed:', error); | |
}); | |
const handleEvent = (event, type) => { | |
clearInterval(heartbeatTimer); | |
try { | |
const data = JSON.parse(event.data); | |
container.querySelector('.loading')?.remove(); | |
container.classList.add('active'); | |
const stepContainer = ensureStepContainer(container); | |
const { formattedContent, timestamp } = formatStepContent(data, type); | |
const step = createStepElement(type, formattedContent, timestamp); | |
stepContainer.appendChild(step); | |
autoScroll(stepContainer); | |
fetch(`/tasks/${taskId}`) | |
.then(response => response.json()) | |
.then(task => { | |
updateTaskStatus(task); | |
}) | |
.catch(error => { | |
console.error('Status update failed:', error); | |
}); | |
} catch (e) { | |
console.error(`Error handling ${type} event:`, e); | |
} | |
}; | |
const eventTypes = ['think', 'tool', 'act', 'log', 'run', 'message']; | |
eventTypes.forEach(type => { | |
eventSource.addEventListener(type, (event) => handleEvent(event, type)); | |
}); | |
eventSource.addEventListener('complete', (event) => { | |
clearInterval(heartbeatTimer); | |
try { | |
const data = JSON.parse(event.data); | |
lastResultContent = data.result || ''; | |
container.innerHTML += ` | |
<div class="complete"> | |
<div>β Task completed</div> | |
<pre>${lastResultContent}</pre> | |
</div> | |
`; | |
fetch(`/tasks/${taskId}`) | |
.then(response => response.json()) | |
.then(task => { | |
updateTaskStatus(task); | |
}) | |
.catch(error => { | |
console.error('Final status update failed:', error); | |
}); | |
eventSource.close(); | |
currentEventSource = null; | |
} catch (e) { | |
console.error('Error handling complete event:', e); | |
} | |
}); | |
eventSource.addEventListener('error', (event) => { | |
clearInterval(heartbeatTimer); | |
try { | |
const data = JSON.parse(event.data); | |
container.innerHTML += ` | |
<div class="error"> | |
β Error: ${data.message} | |
</div> | |
`; | |
eventSource.close(); | |
currentEventSource = null; | |
} catch (e) { | |
console.error('Error handling failed:', e); | |
} | |
}); | |
eventSource.onerror = (err) => { | |
if (eventSource.readyState === EventSource.CLOSED) return; | |
console.error('SSE connection error:', err); | |
clearInterval(heartbeatTimer); | |
eventSource.close(); | |
fetch(`/tasks/${taskId}`) | |
.then(response => response.json()) | |
.then(task => { | |
if (task.status === 'completed' || task.status === 'failed') { | |
updateTaskStatus(task); | |
if (task.status === 'completed') { | |
container.innerHTML += ` | |
<div class="complete"> | |
<div>β Task completed</div> | |
</div> | |
`; | |
} else { | |
container.innerHTML += ` | |
<div class="error"> | |
β Error: ${task.error || 'Task failed'} | |
</div> | |
`; | |
} | |
} else if (retryCount < maxRetries) { | |
retryCount++; | |
container.innerHTML += ` | |
<div class="warning"> | |
β Connection lost, retrying in ${retryDelay/1000} seconds (${retryCount}/${maxRetries})... | |
</div> | |
`; | |
setTimeout(connect, retryDelay); | |
} else { | |
container.innerHTML += ` | |
<div class="error"> | |
β Connection lost, please try refreshing the page | |
</div> | |
`; | |
} | |
}) | |
.catch(error => { | |
console.error('Task status check failed:', error); | |
if (retryCount < maxRetries) { | |
retryCount++; | |
setTimeout(connect, retryDelay); | |
} | |
}); | |
}; | |
} | |
connect(); | |
} | |
function loadHistory() { | |
fetch('/tasks') | |
.then(response => { | |
if (!response.ok) { | |
return response.text().then(text => { | |
throw new Error(`request failure: ${response.status} - ${text.substring(0, 100)}`); | |
}); | |
} | |
return response.json(); | |
}) | |
.then(tasks => { | |
const listContainer = document.getElementById('task-list'); | |
listContainer.innerHTML = tasks.map(task => ` | |
<div class="task-card" data-task-id="${task.id}"> | |
<div>${task.prompt}</div> | |
<div class="task-meta"> | |
${new Date(task.created_at).toLocaleString()} - | |
<span class="status status-${task.status ? task.status.toLowerCase() : 'unknown'}"> | |
${task.status || 'Unknown state'} | |
</span> | |
</div> | |
</div> | |
`).join(''); | |
}) | |
.catch(error => { | |
console.error('Failed to load history records:', error); | |
const listContainer = document.getElementById('task-list'); | |
listContainer.innerHTML = `<div class="error">Load Fail: ${error.message}</div>`; | |
}); | |
} | |
function ensureStepContainer(container) { | |
let stepContainer = container.querySelector('.step-container'); | |
if (!stepContainer) { | |
container.innerHTML = '<div class="step-container"></div>'; | |
stepContainer = container.querySelector('.step-container'); | |
} | |
return stepContainer; | |
} | |
function formatStepContent(data, eventType) { | |
return { | |
formattedContent: data.result, | |
timestamp: new Date().toLocaleTimeString() | |
}; | |
} | |
function createStepElement(type, content, timestamp) { | |
const step = document.createElement('div'); | |
// Executing step | |
const stepRegex = /Executing step (\d+)\/(\d+)/; | |
if (type === 'log' && stepRegex.test(content)) { | |
const match = content.match(stepRegex); | |
const currentStep = parseInt(match[1]); | |
const totalSteps = parseInt(match[2]); | |
step.className = 'step-divider'; | |
step.innerHTML = ` | |
<div class="step-circle">${currentStep}</div> | |
<div class="step-line"></div> | |
<div class="step-info">${currentStep}/${totalSteps}</div> | |
`; | |
} else if (type === 'act') { | |
// Check if it contains information about file saving | |
const saveRegex = /Content successfully saved to (.+)/; | |
const match = content.match(saveRegex); | |
step.className = `step-item ${type}`; | |
if (match && match[1]) { | |
const filePath = match[1].trim(); | |
const fileName = filePath.split('/').pop(); | |
const fileExtension = fileName.split('.').pop().toLowerCase(); | |
// Handling different types of files | |
let fileInteractionHtml = ''; | |
if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(fileExtension)) { | |
fileInteractionHtml = ` | |
<div class="file-interaction image-preview"> | |
<img src="${filePath}" alt="${fileName}" class="preview-image" onclick="showFullImage('${filePath}')"> | |
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">β¬οΈ δΈθ½½εΎη</a> | |
</div> | |
`; | |
} else if (['mp3', 'wav', 'ogg'].includes(fileExtension)) { | |
fileInteractionHtml = ` | |
<div class="file-interaction audio-player"> | |
<audio controls src="${filePath}"></audio> | |
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">β¬οΈ δΈθ½½ι³ι’</a> | |
</div> | |
`; | |
} else if (['html', 'js', 'py'].includes(fileExtension)) { | |
fileInteractionHtml = ` | |
<div class="file-interaction code-file"> | |
<button onclick="simulateRunPython('${filePath}')" class="run-button">βΆοΈ 樑ζθΏθ‘</button> | |
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">β¬οΈ δΈθ½½ζδ»Ά</a> | |
</div> | |
`; | |
} else { | |
fileInteractionHtml = ` | |
<div class="file-interaction"> | |
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">β¬οΈ δΈθ½½ζδ»Ά: ${fileName}</a> | |
</div> | |
`; | |
} | |
step.innerHTML = ` | |
<div class="log-line"> | |
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span> | |
<pre>${content}</pre> | |
${fileInteractionHtml} | |
</div> | |
`; | |
} else { | |
step.innerHTML = ` | |
<div class="log-line"> | |
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span> | |
<pre>${content}</pre> | |
</div> | |
`; | |
} | |
} else { | |
step.className = `step-item ${type}`; | |
step.innerHTML = ` | |
<div class="log-line"> | |
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span> | |
<pre>${content}</pre> | |
</div> | |
`; | |
} | |
return step; | |
} | |
function autoScroll(element) { | |
requestAnimationFrame(() => { | |
element.scrollTo({ | |
top: element.scrollHeight, | |
behavior: 'smooth' | |
}); | |
}); | |
setTimeout(() => { | |
element.scrollTop = element.scrollHeight; | |
}, 100); | |
} | |
function getEventIcon(eventType) { | |
const icons = { | |
'think': 'π€', | |
'tool': 'π οΈ', | |
'act': 'π', | |
'result': 'π', | |
'error': 'β', | |
'complete': 'β ', | |
'log': 'π', | |
'run': 'βοΈ' | |
}; | |
return icons[eventType] || 'βΉοΈ'; | |
} | |
function getEventLabel(eventType) { | |
const labels = { | |
'think': 'Thinking', | |
'tool': 'Using Tool', | |
'act': 'Action', | |
'result': 'Result', | |
'error': 'Error', | |
'complete': 'Complete', | |
'log': 'Log', | |
'run': 'Running' | |
}; | |
return labels[eventType] || 'Info'; | |
} | |
function updateTaskStatus(task) { | |
const statusBar = document.getElementById('status-bar'); | |
if (!statusBar) return; | |
if (task.status === 'completed') { | |
statusBar.innerHTML = `<span class="status-complete">β Task completed</span>`; | |
if (currentEventSource) { | |
currentEventSource.close(); | |
currentEventSource = null; | |
} | |
} else if (task.status === 'failed') { | |
statusBar.innerHTML = `<span class="status-error">β Task failed: ${task.error || 'Unknown error'}</span>`; | |
if (currentEventSource) { | |
currentEventSource.close(); | |
currentEventSource = null; | |
} | |
} else { | |
statusBar.innerHTML = `<span class="status-running">βοΈ Task running: ${task.status}</span>`; | |
} | |
} | |
// Display full screen image | |
function showFullImage(imageSrc) { | |
const modal = document.getElementById('image-modal'); | |
if (!modal) { | |
const modalDiv = document.createElement('div'); | |
modalDiv.id = 'image-modal'; | |
modalDiv.className = 'image-modal'; | |
modalDiv.innerHTML = ` | |
<span class="close-modal">×</span> | |
<img src="${imageSrc}" class="modal-content" id="full-image"> | |
`; | |
document.body.appendChild(modalDiv); | |
const closeBtn = modalDiv.querySelector('.close-modal'); | |
closeBtn.addEventListener('click', () => { | |
modalDiv.classList.remove('active'); | |
}); | |
modalDiv.addEventListener('click', (e) => { | |
if (e.target === modalDiv) { | |
modalDiv.classList.remove('active'); | |
} | |
}); | |
setTimeout(() => modalDiv.classList.add('active'), 10); | |
} else { | |
document.getElementById('full-image').src = imageSrc; | |
modal.classList.add('active'); | |
} | |
} | |
// Simulate running Python files | |
function simulateRunPython(filePath) { | |
let modal = document.getElementById('python-modal'); | |
if (!modal) { | |
modal = document.createElement('div'); | |
modal.id = 'python-modal'; | |
modal.className = 'python-modal'; | |
modal.innerHTML = ` | |
<div class="python-console"> | |
<div class="close-modal">×</div> | |
<div class="python-output">Loading Python file contents...</div> | |
</div> | |
`; | |
document.body.appendChild(modal); | |
const closeBtn = modal.querySelector('.close-modal'); | |
closeBtn.addEventListener('click', () => { | |
modal.classList.remove('active'); | |
}); | |
} | |
modal.classList.add('active'); | |
// Load Python file content | |
fetch(filePath) | |
.then(response => response.text()) | |
.then(code => { | |
const outputDiv = modal.querySelector('.python-output'); | |
outputDiv.innerHTML = ''; | |
const codeElement = document.createElement('pre'); | |
codeElement.textContent = code; | |
codeElement.style.marginBottom = '20px'; | |
codeElement.style.padding = '10px'; | |
codeElement.style.borderBottom = '1px solid #444'; | |
outputDiv.appendChild(codeElement); | |
// Add simulation run results | |
const resultElement = document.createElement('div'); | |
resultElement.innerHTML = ` | |
<div style="color: #4CAF50; margin-top: 10px; margin-bottom: 10px;"> | |
> Simulated operation output:</div> | |
<pre style="color: #f8f8f8;"> | |
#This is the result of Python code simulation run | |
#The actual operational results may vary | |
# Running ${filePath.split('/').pop()}... | |
print("Hello from Python Simulated environment!") | |
# Code execution completed | |
</pre> | |
`; | |
outputDiv.appendChild(resultElement); | |
}) | |
.catch(error => { | |
console.error('Error loading Python file:', error); | |
const outputDiv = modal.querySelector('.python-output'); | |
outputDiv.innerHTML = `Error loading file: ${error.message}`; | |
}); | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
loadHistory(); | |
document.getElementById('prompt-input').addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
createTask(); | |
} | |
}); | |
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'); | |
} | |
}); | |
} | |
const clearButton = document.getElementById('clear-btn'); | |
if (clearButton) { | |
clearButton.addEventListener('click', () => { | |
document.getElementById('prompt-input').value = ''; | |
document.getElementById('prompt-input').focus(); | |
}); | |
} | |
// Add keyboard event listener to close modal boxes | |
document.addEventListener('keydown', (e) => { | |
if (e.key === 'Escape') { | |
const imageModal = document.getElementById('image-modal'); | |
if (imageModal && imageModal.classList.contains('active')) { | |
imageModal.classList.remove('active'); | |
} | |
const pythonModal = document.getElementById('python-modal'); | |
if (pythonModal && pythonModal.classList.contains('active')) { | |
pythonModal.classList.remove('active'); | |
} | |
} | |
}); | |
}); | |