diff --git a/app.py b/app.py
deleted file mode 100644
index 2e1959a..0000000
--- a/app.py
+++ /dev/null
@@ -1,255 +0,0 @@
-import asyncio
-import threading
-import uuid
-import webbrowser
-from datetime import datetime
-from json import dumps
-
-from fastapi import Body, FastAPI, HTTPException, Request
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from pydantic import BaseModel
-
-
-app = FastAPI()
-
-app.mount("/static", StaticFiles(directory="static"), name="static")
-templates = Jinja2Templates(directory="templates")
-
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-
-class Task(BaseModel):
- id: str
- prompt: str
- created_at: datetime
- status: str
- steps: list = []
-
- def model_dump(self, *args, **kwargs):
- data = super().model_dump(*args, **kwargs)
- data["created_at"] = self.created_at.isoformat()
- return data
-
-
-class TaskManager:
- def __init__(self):
- self.tasks = {}
- self.queues = {}
-
- def create_task(self, prompt: str) -> Task:
- task_id = str(uuid.uuid4())
- task = Task(
- id=task_id, prompt=prompt, created_at=datetime.now(), status="pending"
- )
- self.tasks[task_id] = task
- self.queues[task_id] = asyncio.Queue()
- return task
-
- async def update_task_step(
- self, task_id: str, step: int, result: str, step_type: str = "step"
- ):
- if task_id in self.tasks:
- task = self.tasks[task_id]
- task.steps.append({"step": step, "result": result, "type": step_type})
- await self.queues[task_id].put(
- {"type": step_type, "step": step, "result": result}
- )
- await self.queues[task_id].put(
- {"type": "status", "status": task.status, "steps": task.steps}
- )
-
- async def complete_task(self, task_id: str):
- if task_id in self.tasks:
- task = self.tasks[task_id]
- task.status = "completed"
- await self.queues[task_id].put(
- {"type": "status", "status": task.status, "steps": task.steps}
- )
- await self.queues[task_id].put({"type": "complete"})
-
- async def fail_task(self, task_id: str, error: str):
- if task_id in self.tasks:
- self.tasks[task_id].status = f"failed: {error}"
- await self.queues[task_id].put({"type": "error", "message": error})
-
-
-task_manager = TaskManager()
-
-
-@app.get("/", response_class=HTMLResponse)
-async def index(request: Request):
- return templates.TemplateResponse("index.html", {"request": request})
-
-
-@app.post("/tasks")
-async def create_task(prompt: str = Body(..., embed=True)):
- task = task_manager.create_task(prompt)
- asyncio.create_task(run_task(task.id, prompt))
- return {"task_id": task.id}
-
-
-from app.agent.manus import Manus
-
-
-async def run_task(task_id: str, prompt: str):
- try:
- task_manager.tasks[task_id].status = "running"
-
- agent = Manus(
- name="Manus",
- description="A versatile agent that can solve various tasks using multiple tools",
- max_steps=30,
- )
-
- async def on_think(thought):
- await task_manager.update_task_step(task_id, 0, thought, "think")
-
- async def on_tool_execute(tool, input):
- await task_manager.update_task_step(
- task_id, 0, f"Executing tool: {tool}\nInput: {input}", "tool"
- )
-
- async def on_action(action):
- await task_manager.update_task_step(
- task_id, 0, f"Executing action: {action}", "act"
- )
-
- async def on_run(step, result):
- await task_manager.update_task_step(task_id, step, result, "run")
-
- from app.logger import logger
-
- class SSELogHandler:
- def __init__(self, task_id):
- self.task_id = task_id
-
- async def __call__(self, message):
- import re
-
- # 提取 - 后面的内容
- cleaned_message = re.sub(r"^.*? - ", "", message)
-
- event_type = "log"
- if "✨ Manus's thoughts:" in cleaned_message:
- event_type = "think"
- elif "🛠️ Manus selected" in cleaned_message:
- event_type = "tool"
- elif "🎯 Tool" in cleaned_message:
- event_type = "act"
- elif "📝 Oops!" in cleaned_message:
- event_type = "error"
- elif "🏁 Special tool" in cleaned_message:
- event_type = "complete"
-
- await task_manager.update_task_step(
- self.task_id, 0, cleaned_message, event_type
- )
-
- sse_handler = SSELogHandler(task_id)
- logger.add(sse_handler)
-
- result = await agent.run(prompt)
- await task_manager.update_task_step(task_id, 1, result, "result")
- await task_manager.complete_task(task_id)
- except Exception as e:
- await task_manager.fail_task(task_id, str(e))
-
-
-@app.get("/tasks/{task_id}/events")
-async def task_events(task_id: str):
- async def event_generator():
- if task_id not in task_manager.queues:
- yield f"event: error\ndata: {dumps({'message': 'Task not found'})}\n\n"
- return
-
- queue = task_manager.queues[task_id]
-
- task = task_manager.tasks.get(task_id)
- if task:
- yield f"event: status\ndata: {dumps({'type': 'status', 'status': task.status, 'steps': task.steps})}\n\n"
-
- while True:
- try:
- event = await queue.get()
- formatted_event = dumps(event)
-
- yield ": heartbeat\n\n"
-
- if event["type"] == "complete":
- yield f"event: complete\ndata: {formatted_event}\n\n"
- break
- elif event["type"] == "error":
- yield f"event: error\ndata: {formatted_event}\n\n"
- break
- elif event["type"] == "step":
- task = task_manager.tasks.get(task_id)
- if task:
- yield f"event: status\ndata: {dumps({'type': 'status', 'status': task.status, 'steps': task.steps})}\n\n"
- yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
- elif event["type"] in ["think", "tool", "act", "run"]:
- yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
- else:
- yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
-
- except asyncio.CancelledError:
- print(f"Client disconnected for task {task_id}")
- break
- except Exception as e:
- print(f"Error in event stream: {str(e)}")
- yield f"event: error\ndata: {dumps({'message': str(e)})}\n\n"
- break
-
- return StreamingResponse(
- event_generator(),
- media_type="text/event-stream",
- headers={
- "Cache-Control": "no-cache",
- "Connection": "keep-alive",
- "X-Accel-Buffering": "no",
- },
- )
-
-
-@app.get("/tasks")
-async def get_tasks():
- sorted_tasks = sorted(
- task_manager.tasks.values(), key=lambda task: task.created_at, reverse=True
- )
- return JSONResponse(
- content=[task.model_dump() for task in sorted_tasks],
- headers={"Content-Type": "application/json"},
- )
-
-
-@app.get("/tasks/{task_id}")
-async def get_task(task_id: str):
- if task_id not in task_manager.tasks:
- raise HTTPException(status_code=404, detail="Task not found")
- return task_manager.tasks[task_id]
-
-
-@app.exception_handler(Exception)
-async def generic_exception_handler(request: Request, exc: Exception):
- return JSONResponse(
- status_code=500, content={"message": f"Server error: {str(exc)}"}
- )
-
-
-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/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 c219d20..0000000
--- a/static/main.js
+++ /dev/null
@@ -1,307 +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;
-
- const container = document.getElementById('task-container');
-
- function connect() {
- const eventSource = new EventSource(`/tasks/${taskId}/events`);
- currentEventSource = eventSource;
-
- 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);
-
- 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);
- clearInterval(pollInterval);
- container.innerHTML += `
-
-
✅ Task completed
-
${lastResultContent}
-
- `;
- eventSource.close();
- currentEventSource = null;
- });
-
- 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);
- }
- });
-
- eventSource.onerror = (err) => {
- if (eventSource.readyState === EventSource.CLOSED) 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 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) {
- 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`;
- } else if (task.status === 'failed') {
- statusBar.innerHTML = `❌ Task failed: ${task.error || 'Unknown error'}`;
- } else {
- statusBar.innerHTML = `⚙️ Task running: ${task.status}`;
- }
-}
-
-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();
- });
- }
-});
diff --git a/static/style.css b/static/style.css
deleted file mode 100644
index 5aaec28..0000000
--- a/static/style.css
+++ /dev/null
@@ -1,353 +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;
- min-width: 0;
- 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%;
- max-width: 100%;
- position: relative;
- min-height: 300px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- 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: flex;
- flex-direction: column;
- gap: 10px;
- padding: 15px;
- width: 100%;
- max-height: calc(100vh - 200px);
- overflow-y: auto;
- max-width: 100%;
- overflow-x: hidden;
-}
-
-.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;
- white-space: nowrap;
- margin-bottom: 5px;
- color: #666;
-}
-
-.step-item pre {
- padding: 10px;
- border-radius: 4px;
- margin: 10px 0;
- 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);
-
- &.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;
-}
-
-pre {
- margin: 0;
- white-space: pre-wrap;
- word-break: break-word;
-}
-
-.complete pre {
- max-width: 100%;
- white-space: pre-wrap;
- word-break: break-word;
-}
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
-
-
-
-
-
-
-
-
-
-
-
-
-
-