From ecdbb3b26aae165faa8293acf88dfefe7484cf20 Mon Sep 17 00:00:00 2001 From: Feige-cn Date: Mon, 10 Mar 2025 12:17:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?OpenManus=E7=AE=80=E6=98=93web=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 247 ++++++++++++++++++ config/config.toml | 7 + run.bat | 3 + static/main.js | 585 +++++++++++++++++++++++++++++++++++++++++++ static/style.css | 338 +++++++++++++++++++++++++ templates/index.html | 39 +++ 6 files changed, 1219 insertions(+) create mode 100644 app.py create mode 100644 config/config.toml create mode 100644 run.bat create mode 100644 static/main.js create mode 100644 static/style.css create mode 100644 templates/index.html diff --git a/app.py b/app.py new file mode 100644 index 0000000..fba1d83 --- /dev/null +++ b/app.py @@ -0,0 +1,247 @@ +from fastapi import FastAPI, Request, Body, HTTPException +from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from datetime import datetime +import asyncio +import uuid +from json import dumps + +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.toolcall import ToolCallAgent + +async def run_task(task_id: str, prompt: str): + try: + task_manager.tasks[task_id].status = "running" + + agent = ToolCallAgent( + name="TaskAgent", + description="Agent for handling task execution", + 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"执行工具: {tool}\n输入: {input}", "tool") + + async def on_action(action): + await task_manager.update_task_step(task_id, 0, f"执行动作: {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 "✨ TaskAgent's thoughts:" in cleaned_message: + event_type = "think" + elif "🛠️ TaskAgent 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"服务器内部错误: {str(exc)}"} + ) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/config/config.toml b/config/config.toml new file mode 100644 index 0000000..1ae6743 --- /dev/null +++ b/config/config.toml @@ -0,0 +1,7 @@ +# Global LLM configuration +[llm] +model = "qwen-plus" +base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" +api_key = "sk-e8dcd91a089f4a71993b7043f84790f4" +max_tokens = 8192 +temperature = 1.0 diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..4c48f2f --- /dev/null +++ b/run.bat @@ -0,0 +1,3 @@ +@echo off +venv\Scripts\python.exe app.py +pause diff --git a/static/main.js b/static/main.js new file mode 100644 index 0000000..fbf61ff --- /dev/null +++ b/static/main.js @@ -0,0 +1,585 @@ +let currentEventSource = null; + +function createTask() { + const promptInput = document.getElementById('prompt-input'); + const prompt = promptInput.value.trim(); + + if (!prompt) { + alert("请输入有效的提示内容"); + promptInput.focus(); + return; + } + + if (currentEventSource) { + currentEventSource.close(); + currentEventSource = null; + } + + const container = document.getElementById('task-container'); + container.innerHTML = '
任务初始化中...
'; + 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 || '请求失败') }); + } + return response.json(); + }) + .then(data => { + if (!data.task_id) { + throw new Error('无效的任务ID'); + } + setupSSE(data.task_id); + loadHistory(); + }) + .catch(error => { + container.innerHTML = `
错误: ${error.message}
`; + console.error('创建任务失败:', 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('轮询失败:', 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'); + } + + // 保存result内容 + if (data.steps && data.steps.length > 0) { + // 遍历所有步骤,找到最后一个result类型 + 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('状态更新失败:', e); + } + }); + + // 添加对think事件的处理 + 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' + }); + + // 更新任务状态 + fetch(`/tasks/${taskId}`) + .then(response => response.json()) + .then(task => { + updateTaskStatus(task); + }) + .catch(error => { + console.error('状态更新失败:', error); + }); + } catch (e) { + console.error('思考事件处理失败:', e); + } + }); + + // 添加对tool事件的处理 + 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' + }); + + // 更新任务状态 + fetch(`/tasks/${taskId}`) + .then(response => response.json()) + .then(task => { + updateTaskStatus(task); + }) + .catch(error => { + console.error('状态更新失败:', error); + }); + } catch (e) { + console.error('工具事件处理失败:', e); + } + }); + + // 添加对act事件的处理 + 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' + }); + + // 更新任务状态 + fetch(`/tasks/${taskId}`) + .then(response => response.json()) + .then(task => { + updateTaskStatus(task); + }) + .catch(error => { + console.error('状态更新失败:', error); + }); + } catch (e) { + console.error('执行事件处理失败:', e); + } + }); + + // 添加对run事件的处理 + // 添加对log事件的处理 + 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' + }); + + // 更新任务状态 + fetch(`/tasks/${taskId}`) + .then(response => response.json()) + .then(task => { + updateTaskStatus(task); + }) + .catch(error => { + console.error('状态更新失败:', error); + }); + } catch (e) { + console.error('日志事件处理失败:', 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' + }); + + // 更新任务状态 + fetch(`/tasks/${taskId}`) + .then(response => response.json()) + .then(task => { + updateTaskStatus(task); + }) + .catch(error => { + console.error('状态更新失败:', error); + }); + } catch (e) { + console.error('运行事件处理失败:', 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('消息处理失败:', e); + } + }); + + let isTaskComplete = false; + + eventSource.addEventListener('complete', (event) => { + isTaskComplete = true; + clearInterval(heartbeatTimer); + clearInterval(pollInterval); + container.innerHTML += ` +
+
✅ 任务完成
+
${lastResultContent}
+
+ `; + eventSource.close(); + currentEventSource = null; + lastResultContent = ''; // 清空结果内容 + }); + + eventSource.addEventListener('error', (event) => { + clearInterval(heartbeatTimer); + clearInterval(pollInterval); + try { + const data = JSON.parse(event.data); + container.innerHTML += ` +
+ ❌ 错误: ${data.message} +
+ `; + eventSource.close(); + currentEventSource = null; + } catch (e) { + console.error('错误处理失败:', e); + } + }); + } + + container.scrollTo({ + top: container.scrollHeight, + behavior: 'smooth' + }); + + eventSource.onerror = (err) => { + if (isTaskComplete) { + return; + } + + console.error('SSE连接错误:', err); + clearInterval(heartbeatTimer); + clearInterval(pollInterval); + eventSource.close(); + + if (retryCount < maxRetries) { + retryCount++; + container.innerHTML += ` +
+ ⚠ 连接中断,${retryDelay/1000}秒后重试 (${retryCount}/${maxRetries})... +
+ `; + setTimeout(connect, retryDelay); + } else { + container.innerHTML += ` +
+ ⚠ 连接中断,请尝试刷新页面 +
+ `; + } + }; + } + + 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 'warning': return '⚠️'; + case 'log': return '📝'; + default: return '⚡'; + } +} + +function getEventLabel(eventType) { + switch(eventType) { + case 'think': return '思考'; + case 'tool': return '工具执行'; + case 'act': return '执行'; + case 'result': return '结果'; + case 'error': return '错误'; + case 'complete': return '完成'; + case 'warning': return '警告'; + case 'log': return '日志'; + default: return '步骤'; + } +} + +function formatContent(content) { + // Remove timestamp and log level prefixes + content = content.replace(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} \| [A-Z]+\s*\| /gm, ''); + // Format the remaining content + return content + .replace(/\n/g, '
') + .replace(/ /g, '  ') + .replace(/✨ Manus's thoughts:/g, '') + .replace(/🛠️ Manus selected/g, '') + .replace(/🧰 Tools being prepared:/g, '') + .replace(/🔧 Activating tool:/g, '') + .replace(/🎯 Tool/g, '') + .replace(/📝 Oops!/g, '') + .replace(/🏁 Special tool/g, ''); +} + +function updateTaskStatus(task) { + const taskCard = document.querySelector(`.task-card[data-task-id="${task.id}"]`); + if (taskCard) { + const statusEl = taskCard.querySelector('.task-meta .status'); + if (statusEl) { + statusEl.className = `status-${task.status ? task.status.toLowerCase() : 'unknown'}`; + statusEl.textContent = task.status || '未知状态'; + } + } +} + +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}
`; + }); +} + +document.addEventListener('DOMContentLoaded', function() { + const welcomeMessage = document.querySelector('.welcome-message'); + if (welcomeMessage) { + welcomeMessage.style.display = 'flex'; + } + + // 监听任务容器显示状态 + const taskContainer = document.getElementById('task-container'); + if (taskContainer) { + const observer = new MutationObserver((mutations) => { + mutations.forEach(mutation => { + if (mutation.attributeName === 'class') { + const welcomeMessage = document.querySelector('.welcome-message'); + if (taskContainer.classList.contains('active')) { + if (welcomeMessage) { + welcomeMessage.style.display = 'none'; + } + } else { + if (welcomeMessage) { + welcomeMessage.style.display = 'block'; + } + } + } + }); + }); + + observer.observe(taskContainer, { + attributes: true + }); + } +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..a1173fb --- /dev/null +++ b/static/style.css @@ -0,0 +1,338 @@ +: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 new file mode 100644 index 0000000..af097f9 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,39 @@ + + + + + + OpenManus本地版 + + + +
+
+

历史任务

+
+
+ +
+
+
+

欢迎使用OpenManus本地版

+

请输入任务提示开始新的任务

+
+
+
+ +
+ + +
+
+
+ + + + From 825855d13ab356e6ec8c784515b2d27378505995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=9E=E6=88=88?= <65696105+Feige-cn@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:20:21 +0800 Subject: [PATCH 2/2] Delete config/config.toml --- config/config.toml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 config/config.toml diff --git a/config/config.toml b/config/config.toml deleted file mode 100644 index 1ae6743..0000000 --- a/config/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Global LLM configuration -[llm] -model = "qwen-plus" -base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" -api_key = "sk-e8dcd91a089f4a71993b7043f84790f4" -max_tokens = 8192 -temperature = 1.0