diff --git a/README.md b/README.md
index bab58c2..8cac223 100644
--- a/README.md
+++ b/README.md
@@ -7,12 +7,9 @@ English | [中文](README_zh.md)
# 👋 OpenManus
-[Official Website](https://openmanus.github.io/)
-
Manus is incredible, but OpenManus can achieve any idea without an *Invite Code* 🛫!
-Our team
-members [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) [@stellaHSR](https://github.com/stellaHSR), we are from [@MetaGPT](https://github.com/geekan/MetaGPT). The prototype is launched within 3 hours and we are keeping building!
+Our team members [@Xinbin Liang](https://github.com/mannaandpoem) and [@Jinyu Xiang](https://github.com/XiangJinyu) (core authors), along with [@Zhaoyang Yu](https://github.com/MoshiQAQ), [@Jiayi Zhang](https://github.com/didiforgithub), and [@Sirui Hong](https://github.com/stellaHSR), we are from [@MetaGPT](https://github.com/geekan/MetaGPT). The prototype is launched within 3 hours and we are keeping building!
It's a simple implementation, so we welcome any suggestions, contributions, and feedback!
@@ -130,10 +127,6 @@ We welcome any friendly suggestions and helpful contributions! Just create issue
Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com
-## Roadmap
-
-- [ ] Improve the UI’s visual appeal to create a more intuitive and seamless user experience.
-
## Community Group
Join our networking group on Feishu and share your experience with other developers!
@@ -162,4 +155,4 @@ OpenManus is built by contributors from MetaGPT. Huge thanks to this agent commu
journal = {GitHub repository},
howpublished = {\url{https://github.com/mannaandpoem/OpenManus}},
}
-```
+```
diff --git a/README_zh.md b/README_zh.md
index fb50333..13bb768 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -7,8 +7,6 @@
# 👋 OpenManus
-[官方网站](https://openmanus.github.io/)
-
Manus 非常棒,但 OpenManus 无需邀请码即可实现任何创意 🛫!
我们的团队成员 [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) https://github.com/stellaHSR 来自 [@MetaGPT](https://github.com/geekan/MetaGPT) 组织,我们在 3
@@ -130,10 +128,6 @@ python run_flow.py
或通过 📧 邮件联系 @mannaandpoem:mannaandpoem@gmail.com
-## 发展路线
-
-- [ ] 提高用户界面的视觉吸引力,以创建更直观和无缝的用户体验。
-
## 交流群
加入我们的飞书交流群,与其他开发者分享经验!
diff --git a/app.py b/app.py
index a855f03..7fcac28 100644
--- a/app.py
+++ b/app.py
@@ -249,3 +249,4 @@ async def generic_exception_handler(request: Request, exc: Exception):
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=5172)
+
diff --git a/app/flow/base.py b/app/flow/base.py
index 13b3e17..13066ce 100644
--- a/app/flow/base.py
+++ b/app/flow/base.py
@@ -60,3 +60,32 @@ class BaseFlow(BaseModel, ABC):
@abstractmethod
async def execute(self, input_text: str) -> str:
"""Execute the flow with given input"""
+
+
+class PlanStepStatus(str, Enum):
+ """Enum class defining possible statuses of a plan step"""
+
+ NOT_STARTED = "not_started"
+ IN_PROGRESS = "in_progress"
+ COMPLETED = "completed"
+ BLOCKED = "blocked"
+
+ @classmethod
+ def get_all_statuses(cls) -> list[str]:
+ """Return a list of all possible step status values"""
+ return [status.value for status in cls]
+
+ @classmethod
+ def get_active_statuses(cls) -> list[str]:
+ """Return a list of values representing active statuses (not started or in progress)"""
+ return [cls.NOT_STARTED.value, cls.IN_PROGRESS.value]
+
+ @classmethod
+ def get_status_marks(cls) -> Dict[str, str]:
+ """Return a mapping of statuses to their marker symbols"""
+ return {
+ cls.COMPLETED.value: "[✓]",
+ cls.IN_PROGRESS.value: "[→]",
+ cls.BLOCKED.value: "[!]",
+ cls.NOT_STARTED.value: "[ ]",
+ }
diff --git a/app/flow/planning.py b/app/flow/planning.py
index b3df48a..808949f 100644
--- a/app/flow/planning.py
+++ b/app/flow/planning.py
@@ -5,7 +5,7 @@ from typing import Dict, List, Optional, Union
from pydantic import Field
from app.agent.base import BaseAgent
-from app.flow.base import BaseFlow
+from app.flow.base import BaseFlow, PlanStepStatus
from app.llm import LLM
from app.logger import logger
from app.schema import AgentState, Message
@@ -183,11 +183,11 @@ class PlanningFlow(BaseFlow):
# Find first non-completed step
for i, step in enumerate(steps):
if i >= len(step_statuses):
- status = "not_started"
+ status = PlanStepStatus.NOT_STARTED.value
else:
status = step_statuses[i]
- if status in ["not_started", "in_progress"]:
+ if status in PlanStepStatus.get_active_statuses():
# Extract step type/category if available
step_info = {"text": step}
@@ -204,17 +204,17 @@ class PlanningFlow(BaseFlow):
command="mark_step",
plan_id=self.active_plan_id,
step_index=i,
- step_status="in_progress",
+ step_status=PlanStepStatus.IN_PROGRESS.value,
)
except Exception as e:
logger.warning(f"Error marking step as in_progress: {e}")
# Update step status directly if needed
if i < len(step_statuses):
- step_statuses[i] = "in_progress"
+ step_statuses[i] = PlanStepStatus.IN_PROGRESS.value
else:
while len(step_statuses) < i:
- step_statuses.append("not_started")
- step_statuses.append("in_progress")
+ step_statuses.append(PlanStepStatus.NOT_STARTED.value)
+ step_statuses.append(PlanStepStatus.IN_PROGRESS.value)
plan_data["step_statuses"] = step_statuses
@@ -266,7 +266,7 @@ class PlanningFlow(BaseFlow):
command="mark_step",
plan_id=self.active_plan_id,
step_index=self.current_step_index,
- step_status="completed",
+ step_status=PlanStepStatus.COMPLETED.value,
)
logger.info(
f"Marked step {self.current_step_index} as completed in plan {self.active_plan_id}"
@@ -280,10 +280,10 @@ class PlanningFlow(BaseFlow):
# Ensure the step_statuses list is long enough
while len(step_statuses) <= self.current_step_index:
- step_statuses.append("not_started")
+ step_statuses.append(PlanStepStatus.NOT_STARTED.value)
# Update the status
- step_statuses[self.current_step_index] = "completed"
+ step_statuses[self.current_step_index] = PlanStepStatus.COMPLETED.value
plan_data["step_statuses"] = step_statuses
async def _get_plan_text(self) -> str:
@@ -311,23 +311,18 @@ class PlanningFlow(BaseFlow):
# Ensure step_statuses and step_notes match the number of steps
while len(step_statuses) < len(steps):
- step_statuses.append("not_started")
+ step_statuses.append(PlanStepStatus.NOT_STARTED.value)
while len(step_notes) < len(steps):
step_notes.append("")
# Count steps by status
- status_counts = {
- "completed": 0,
- "in_progress": 0,
- "blocked": 0,
- "not_started": 0,
- }
+ status_counts = {status: 0 for status in PlanStepStatus.get_all_statuses()}
for status in step_statuses:
if status in status_counts:
status_counts[status] += 1
- completed = status_counts["completed"]
+ completed = status_counts[PlanStepStatus.COMPLETED.value]
total = len(steps)
progress = (completed / total) * 100 if total > 0 else 0
@@ -337,21 +332,19 @@ class PlanningFlow(BaseFlow):
plan_text += (
f"Progress: {completed}/{total} steps completed ({progress:.1f}%)\n"
)
- plan_text += f"Status: {status_counts['completed']} completed, {status_counts['in_progress']} in progress, "
- plan_text += f"{status_counts['blocked']} blocked, {status_counts['not_started']} not started\n\n"
+ plan_text += f"Status: {status_counts[PlanStepStatus.COMPLETED.value]} completed, {status_counts[PlanStepStatus.IN_PROGRESS.value]} in progress, "
+ plan_text += f"{status_counts[PlanStepStatus.BLOCKED.value]} blocked, {status_counts[PlanStepStatus.NOT_STARTED.value]} not started\n\n"
plan_text += "Steps:\n"
+ status_marks = PlanStepStatus.get_status_marks()
+
for i, (step, status, notes) in enumerate(
zip(steps, step_statuses, step_notes)
):
- if status == "completed":
- status_mark = "[✓]"
- elif status == "in_progress":
- status_mark = "[→]"
- elif status == "blocked":
- status_mark = "[!]"
- else: # not_started
- status_mark = "[ ]"
+ # Use status marks to indicate step status
+ status_mark = status_marks.get(
+ status, status_marks[PlanStepStatus.NOT_STARTED.value]
+ )
plan_text += f"{i}. {status_mark} {step}\n"
if notes:
diff --git a/pytest.ini b/pytest.ini
deleted file mode 100644
index 4bbeada..0000000
--- a/pytest.ini
+++ /dev/null
@@ -1,14 +0,0 @@
-[pytest]
-testpaths = tests
-python_files = test_*.py
-python_classes = Test*
-python_functions = test_*
-
-# Log settings
-log_cli = true
-log_cli_level = INFO
-log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
-log_cli_date_format = %Y-%m-%d %H:%M:%S
-
-# Make sure asyncio works properly
-asyncio_mode = auto
diff --git a/run.bat b/run.bat
deleted file mode 100644
index 4c48f2f..0000000
--- a/run.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-venv\Scripts\python.exe app.py
-pause
diff --git a/static/main.js b/static/main.js
deleted file mode 100644
index 49d5dd7..0000000
--- a/static/main.js
+++ /dev/null
@@ -1,694 +0,0 @@
-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();
- });
- }
-});
diff --git a/static/style.css b/static/style.css
deleted file mode 100644
index 776304e..0000000
--- a/static/style.css
+++ /dev/null
@@ -1,338 +0,0 @@
-:root {
- --primary-color: #007bff;
- --primary-hover: #0056b3;
- --success-color: #28a745;
- --error-color: #dc3545;
- --warning-color: #ff9800;
- --info-color: #2196f3;
- --text-color: #333;
- --text-light: #666;
- --bg-color: #f8f9fa;
- --border-color: #ddd;
-}
-
-* {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-
-body {
- font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
- margin: 0;
- padding: 0;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-.container {
- display: flex;
- min-height: 100vh;
- width: 90%;
- margin: 0 auto;
- padding: 20px;
- gap: 20px;
-}
-
-.card {
- background-color: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-}
-
-.history-panel {
- width: 300px;
-}
-
-.main-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 20px;
-}
-
-.task-list {
- margin-top: 10px;
- max-height: calc(100vh - 160px);
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-.task-container {
- @extend .card;
- width: 100%;
- position: relative;
- min-height: 300px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: auto;
- height: 100%;
-}
-
-.welcome-message {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- text-align: center;
- color: var(--text-light);
- background: white;
- z-index: 1;
- padding: 20px;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- width: 100%;
- height: 100%;
-}
-
-.welcome-message h1 {
- font-size: 2rem;
- margin-bottom: 10px;
- color: var(--text-color);
-}
-
-.input-container {
- @extend .card;
- display: flex;
- gap: 10px;
-}
-
-#prompt-input {
- flex: 1;
- padding: 12px;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 1rem;
-}
-
-button {
- padding: 12px 24px;
- background-color: var(--primary-color);
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 1rem;
- transition: background-color 0.2s;
-}
-
-button:hover {
- background-color: var(--primary-hover);
-}
-
-.task-item {
- padding: 10px;
- margin-bottom: 10px;
- background-color: #f8f9fa;
- border-radius: 4px;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-.task-item:hover {
- background-color: #e9ecef;
-}
-
-.task-item.active {
- background-color: var(--primary-color);
- color: white;
-}
-
-#input-container.bottom {
- margin-top: auto;
-}
-
-.task-card {
- background: #fff;
- padding: 15px;
- margin-bottom: 10px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.task-card:hover {
- transform: translateX(5px);
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-}
-
-.status-pending {
- color: var(--text-light);
-}
-
-.status-running {
- color: var(--primary-color);
-}
-
-.status-completed {
- color: var(--success-color);
-}
-
-.status-failed {
- color: var(--error-color);
-}
-
-.step-container {
- display: block;
- padding: 15px;
- width: 100%;
- max-height: calc(100vh - 300px);
- overflow-y: auto;
-}
-
-.step-item {
- padding: 15px;
- background: white;
- border-radius: 8px;
- width: 100%;
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
- margin-bottom: 10px;
- opacity: 1;
- transform: none;
-}
-
-.step-item .log-line:not(.result) {
- opacity: 0.7;
- color: #666;
- font-size: 0.9em;
-}
-
-.step-item .log-line.result {
- opacity: 1;
- color: #333;
- font-size: 1em;
- background: #e8f5e9;
- border-left: 4px solid #4caf50;
- padding: 10px;
- border-radius: 4px;
-}
-
-.step-item.show {
- opacity: 1;
- transform: none;
-}
-
-.log-line {
- padding: 10px;
- border-radius: 4px;
- margin-bottom: 10px;
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.log-line.think,
-.step-item pre.think {
- background: var(--info-color-light);
- border-left: 4px solid var(--info-color);
-}
-
-.log-line.tool,
-.step-item pre.tool {
- background: var(--warning-color-light);
- border-left: 4px solid var(--warning-color);
-}
-
-.log-line.result,
-.step-item pre.result {
- background: var(--success-color-light);
- border-left: 4px solid var(--success-color);
-}
-
-.log-line.error,
-.step-item pre.error {
- background: var(--error-color-light);
- border-left: 4px solid var(--error-color);
-}
-
-.log-line.info,
-.step-item pre.info {
- background: var(--bg-color);
- border-left: 4px solid var(--text-light);
-}
-
-/* 删除重复的样式定义 */
-
-.log-prefix {
- font-weight: bold;
- margin-bottom: 5px;
- color: #666;
-}
-
-.step-item pre {
- padding: 10px;
- border-radius: 4px;
- margin: 10px 0;
- overflow-x: auto;
- padding: 10px;
- border-radius: 4px;
- margin: 10px 0;
- overflow-x: auto;
- font-family: 'Courier New', monospace;
- font-size: 0.9em;
- line-height: 1.4;
- white-space: pre-wrap;
- color: var(--text-color);
- background: var(--bg-color);
-
- &.log {
- background: var(--bg-color);
- border-left: 4px solid var(--text-light);
- }
- &.think {
- background: var(--info-color-light);
- border-left: 4px solid var(--info-color);
- }
- &.tool {
- background: var(--warning-color-light);
- border-left: 4px solid var(--warning-color);
- }
- &.result {
- background: var(--success-color-light);
- border-left: 4px solid var(--success-color);
- }
-}
-
-.step-item strong {
- display: block;
- margin-bottom: 8px;
- color: #007bff;
- font-size: 0.9em;
-}
-
-.step-item div {
- color: #444;
- line-height: 1.6;
-}
-
-.loading {
- padding: 15px;
- color: #666;
- text-align: center;
-}
-
-.ping {
- color: #ccc;
- text-align: center;
- margin: 5px 0;
-}
-
-.error {
- color: #dc3545;
- padding: 10px;
- background: #ffe6e6;
- border-radius: 4px;
- margin: 10px 0;
-}
-
-.complete {
- color: #28a745;
- padding: 10px;
- background: #e6ffe6;
- border-radius: 4px;
- margin: 10px 0;
-}
diff --git a/templates/index.html b/templates/index.html
deleted file mode 100644
index d3245d9..0000000
--- a/templates/index.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
- OpenManus Local Version
-
-
-
-
-
-
-
-
-
-
Welcome to OpenManus Local Version
-
Please enter a task prompt to start a new task
-
-
-
-
-
-
-
-
-
-
-
-
-
-