diff --git a/app.py b/app.py
index b827484..2e1959a 100644
--- a/app.py
+++ b/app.py
@@ -1,5 +1,7 @@
import asyncio
+import threading
import uuid
+import webbrowser
from datetime import datetime
from json import dumps
@@ -10,6 +12,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
+
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@@ -241,7 +244,12 @@ async def generic_exception_handler(request: Request, exc: Exception):
)
+def open_local_browser():
+ webbrowser.open_new_tab("http://localhost:5172")
+
+
if __name__ == "__main__":
import uvicorn
+ threading.Timer(3, open_local_browser).start()
uvicorn.run(app, host="localhost", port=5172)
diff --git a/static/main.js b/static/main.js
index 49d5dd7..c219d20 100644
--- a/static/main.js
+++ b/static/main.js
@@ -50,12 +50,12 @@ function setupSSE(taskId) {
const maxRetries = 3;
const retryDelay = 2000;
+ const container = document.getElementById('task-container');
+
function connect() {
const eventSource = new EventSource(`/tasks/${taskId}/events`);
currentEventSource = eventSource;
- const container = document.getElementById('task-container');
-
let heartbeatTimer = setInterval(() => {
container.innerHTML += '
¡
';
}, 5000);
@@ -71,94 +71,20 @@ function setupSSE(taskId) {
});
}, 10000);
- if (!eventSource._listenersAdded) {
- eventSource._listenersAdded = true;
-
- let lastResultContent = '';
- eventSource.addEventListener('status', (event) => {
+ const handleEvent = (event, type) => {
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}
-
- `;
+ const stepContainer = ensureStepContainer(container);
+ const { formattedContent, timestamp } = formatStepContent(data, type);
+ const step = createStepElement(type, formattedContent, timestamp);
stepContainer.appendChild(step);
- container.scrollTo({
- top: container.scrollHeight,
- behavior: 'smooth'
- });
+ autoScroll(stepContainer);
- // Update task status
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
@@ -168,236 +94,16 @@ function setupSSE(taskId) {
console.error('Status update failed:', error);
});
} catch (e) {
- console.error('Think event handling failed:', 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));
});
- // 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 += `
@@ -408,7 +114,6 @@ function setupSSE(taskId) {
`;
eventSource.close();
currentEventSource = null;
- lastResultContent = ''; // Clear result content
});
eventSource.addEventListener('error', (event) => {
@@ -427,17 +132,9 @@ function setupSSE(taskId) {
console.error('Error handling failed:', e);
}
});
- }
-
- container.scrollTo({
- top: container.scrollHeight,
- behavior: 'smooth'
- });
eventSource.onerror = (err) => {
- if (isTaskComplete) {
- return;
- }
+ if (eventSource.readyState === EventSource.CLOSED) return;
console.error('SSE connection error:', err);
clearInterval(heartbeatTimer);
@@ -465,32 +162,105 @@ function setupSSE(taskId) {
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 loadHistory() {
+ fetch('/tasks')
+ .then(response => {
+ if (!response.ok) {
+ return response.text().then(text => {
+ throw new Error(`诡ćąĺ¤ąč´Ľ: ${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 || 'ćŞçĽçść'}
+
+
+
+ `).join('');
+ })
+ .catch(error => {
+ console.error('ĺ č˝˝ĺĺ˛čްĺ˝ĺ¤ąč´Ľ:', error);
+ const listContainer = document.getElementById('task-list');
+ listContainer.innerHTML = `ĺ 载夹贼: ${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');
+ 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) {
- 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';
- }
+ 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) {
@@ -506,164 +276,9 @@ function updateTaskStatus(task) {
}
}
-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();
@@ -671,7 +286,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
- // Show history button functionality
const historyToggle = document.getElementById('history-toggle');
if (historyToggle) {
historyToggle.addEventListener('click', () => {
@@ -683,7 +297,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // Clear button functionality
const clearButton = document.getElementById('clear-btn');
if (clearButton) {
clearButton.addEventListener('click', () => {
diff --git a/static/style.css b/static/style.css
index 776304e..5aaec28 100644
--- a/static/style.css
+++ b/static/style.css
@@ -28,6 +28,7 @@ body {
.container {
display: flex;
min-height: 100vh;
+ min-width: 0;
width: 90%;
margin: 0 auto;
padding: 20px;
@@ -62,13 +63,13 @@ body {
.task-container {
@extend .card;
width: 100%;
+ max-width: 100%;
position: relative;
min-height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
- padding: 20px;
overflow: auto;
height: 100%;
}
@@ -177,11 +178,15 @@ button:hover {
}
.step-container {
- display: block;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
padding: 15px;
width: 100%;
- max-height: calc(100vh - 300px);
+ max-height: calc(100vh - 200px);
overflow-y: auto;
+ max-width: 100%;
+ overflow-x: hidden;
}
.step-item {
@@ -255,10 +260,9 @@ button:hover {
border-left: 4px solid var(--text-light);
}
-/* ĺ é¤éĺ¤çć ˇĺźĺŽäš */
-
.log-prefix {
font-weight: bold;
+ white-space: nowrap;
margin-bottom: 5px;
color: #666;
}
@@ -267,15 +271,14 @@ button:hover {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
- overflow-x: auto;
- padding: 10px;
- border-radius: 4px;
- margin: 10px 0;
- overflow-x: auto;
+ overflow-x: hidden;
font-family: 'Courier New', monospace;
font-size: 0.9em;
line-height: 1.4;
white-space: pre-wrap;
+ word-wrap: break-word;
+ word-break: break-all;
+ max-width: 100%;
color: var(--text-color);
background: var(--bg-color);
@@ -336,3 +339,15 @@ button:hover {
border-radius: 4px;
margin: 10px 0;
}
+
+pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.complete pre {
+ max-width: 100%;
+ white-space: pre-wrap;
+ word-break: break-word;
+}