let currentEventSource = null;
let exampleApiKey = '';
function checkConfigStatus() {
fetch('/config/status')
.then(response => response.json())
.then(data => {
if (data.status === 'missing') {
showConfigModal(data.example_config);
} else if (data.status === 'no_example') {
alert('Error: Missing configuration example file! Please ensure that the config/config.example.toml file exists.');
} else if (data.status === 'error') {
alert('Configuration check error:' + data.message);
}
})
.catch(error => {
console.error('Configuration check failed:', error);
});
}
// Display configuration pop-up and fill in sample configurations
function showConfigModal(exampleConfig) {
const configModal = document.getElementById('config-modal');
if (!configModal) return;
configModal.classList.add('active');
if (exampleConfig) {
fillConfigForm(exampleConfig);
}
const saveButton = document.getElementById('save-config-btn');
if (saveButton) {
saveButton.onclick = saveConfig;
}
}
// Use example configuration to fill in the form
function fillConfigForm(exampleConfig) {
if (exampleConfig.llm) {
const llm = exampleConfig.llm;
setInputValue('llm-model', llm.model);
setInputValue('llm-base-url', llm.base_url);
setInputValue('llm-api-key', llm.api_key);
// Save example API key for later verification
exampleApiKey = llm.api_key || '';
setInputValue('llm-max-tokens', llm.max_tokens);
setInputValue('llm-temperature', llm.temperature);
}
if (exampleConfig.server) {
setInputValue('server-host', exampleConfig.server.host);
setInputValue('server-port', exampleConfig.server.port);
}
}
function setInputValue(id, value) {
const input = document.getElementById(id);
if (input && value !== undefined) {
input.value = value;
}
}
function saveConfig() {
const configData = collectFormData();
const requiredFields = [
{ id: 'llm-model', name: 'Model Name' },
{ id: 'llm-base-url', name: 'API Base URL' },
{ id: 'llm-api-key', name: 'API Key' },
{ id: 'server-host', name: 'Server Host' },
{ id: 'server-port', name: 'Server Port' }
];
let missingFields = [];
requiredFields.forEach(field => {
if (!document.getElementById(field.id).value.trim()) {
missingFields.push(field.name);
}
});
if (missingFields.length > 0) {
document.getElementById('config-error').textContent =
`Please fill in the necessary configuration information: ${missingFields.join(', ')}`;
return;
}
// Check if the API key is the same as the example configuration
const apiKey = document.getElementById('llm-api-key').value.trim();
if (apiKey === exampleApiKey && exampleApiKey.includes('sk-')) {
document.getElementById('config-error').textContent =
`Please enter your own API key`;
// Highlight the API key input box
document.getElementById('llm-api-key').parentElement.classList.add('error');
return;
} else {
document.getElementById('llm-api-key').parentElement.classList.remove('error');
}
// Send configuration to server
fetch('/config/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(configData)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// Close pop-up
document.getElementById('config-modal').classList.remove('active');
// Show success message
alert('Configuration saved successfully! The application will use the new configuration on next startup.');
// Refresh page
window.location.reload();
} else {
document.getElementById('config-error').textContent =
`Save failed: ${data.message}`;
}
})
.catch(error => {
document.getElementById('config-error').textContent =
`Request error: ${error.message}`;
});
}
// Collect form data
function collectFormData() {
const configData = {
llm: {
model: document.getElementById('llm-model').value,
base_url: document.getElementById('llm-base-url').value,
api_key: document.getElementById('llm-api-key').value
},
server: {
host: document.getElementById('server-host').value,
port: parseInt(document.getElementById('server-port').value || '5172')
}
};
// Add optional fields
const maxTokens = document.getElementById('llm-max-tokens').value;
if (maxTokens) {
configData.llm.max_tokens = parseInt(maxTokens);
}
const temperature = document.getElementById('llm-temperature').value;
if (temperature) {
configData.llm.temperature = parseFloat(temperature);
}
return configData;
}
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();
promptInput.value = '';
})
.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;
let lastResultContent = '';
const container = document.getElementById('task-container');
function connect() {
const eventSource = new EventSource(`/tasks/${taskId}/events`);
currentEventSource = eventSource;
let heartbeatTimer = setInterval(() => {
container.innerHTML += '·
';
}, 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 += `
✅ Task completed
${lastResultContent}
`;
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 += `
❌ Error: ${data.message}
`;
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 += `
`;
} else {
container.innerHTML += `
❌ Error: ${task.error || 'Task failed'}
`;
}
} else 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
`;
}
})
.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 => `
${task.prompt}
${new Date(task.created_at).toLocaleString()} -
${task.status || 'Unknown state'}
`).join('');
})
.catch(error => {
console.error('Failed to load history records:', error);
const listContainer = document.getElementById('task-list');
listContainer.innerHTML = `Load Fail: ${error.message}
`;
});
}
function ensureStepContainer(container) {
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '';
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 = `
${currentStep}
${currentStep}/${totalSteps}
`;
} 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 = `
`;
} else if (['mp3', 'wav', 'ogg'].includes(fileExtension)) {
fileInteractionHtml = `
`;
} else if (['html', 'js', 'py'].includes(fileExtension)) {
fileInteractionHtml = `
`;
} else {
fileInteractionHtml = `
`;
}
step.innerHTML = `
${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:
${content}
${fileInteractionHtml}
`;
} else {
step.innerHTML = `
${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:
${content}
`;
}
} else {
step.className = `step-item ${type}`;
step.innerHTML = `
${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:
${content}
`;
}
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 = `✅ Task completed`;
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
} else if (task.status === 'failed') {
statusBar.innerHTML = `❌ Task failed: ${task.error || 'Unknown error'}`;
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
} else {
statusBar.innerHTML = `⚙️ Task running: ${task.status}`;
}
}
// 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 = `
×
`;
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 = `
×
Loading Python file contents...
`;
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 = `
> Simulated operation output:
#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
`;
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', () => {
// Check configuration status
checkConfigStatus();
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');
}
// Do not close the configuration pop-up, because the configuration is required
}
});
});