remove front-end code
This commit is contained in:
parent
2c0b2d1fb3
commit
35de1f9e15
255
app.py
255
app.py
@ -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)
|
|
14
pytest.ini
14
pytest.ini
@ -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
|
|
307
static/main.js
307
static/main.js
@ -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 = '<div class="loading">Initializing task...</div>';
|
|
||||||
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 = `<div class="error">Error: ${error.message}</div>`;
|
|
||||||
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 += '<div class="ping">·</div>';
|
|
||||||
}, 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 += `
|
|
||||||
<div class="complete">
|
|
||||||
<div>✅ Task completed</div>
|
|
||||||
<pre>${lastResultContent}</pre>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
eventSource.close();
|
|
||||||
currentEventSource = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
eventSource.addEventListener('error', (event) => {
|
|
||||||
clearInterval(heartbeatTimer);
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
container.innerHTML += `
|
|
||||||
<div class="error">
|
|
||||||
❌ Error: ${data.message}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
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 += `
|
|
||||||
<div class="warning">
|
|
||||||
⚠ Connection lost, retrying in ${retryDelay/1000} seconds (${retryCount}/${maxRetries})...
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
setTimeout(connect, retryDelay);
|
|
||||||
} else {
|
|
||||||
container.innerHTML += `
|
|
||||||
<div class="error">
|
|
||||||
⚠ Connection lost, please try refreshing the page
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 => `
|
|
||||||
<div class="task-card" data-task-id="${task.id}">
|
|
||||||
<div>${task.prompt}</div>
|
|
||||||
<div class="task-meta">
|
|
||||||
${new Date(task.created_at).toLocaleString()} -
|
|
||||||
<span class="status status-${task.status ? task.status.toLowerCase() : 'unknown'}">
|
|
||||||
${task.status || '未知状态'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('加载历史记录失败:', error);
|
|
||||||
const listContainer = document.getElementById('task-list');
|
|
||||||
listContainer.innerHTML = `<div class="error">加载失败: ${error.message}</div>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function ensureStepContainer(container) {
|
|
||||||
let stepContainer = container.querySelector('.step-container');
|
|
||||||
if (!stepContainer) {
|
|
||||||
container.innerHTML = '<div class="step-container"></div>';
|
|
||||||
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 = `
|
|
||||||
<div class="log-line">
|
|
||||||
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span>
|
|
||||||
<pre>${content}</pre>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
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 = `<span class="status-complete">✅ Task completed</span>`;
|
|
||||||
} else if (task.status === 'failed') {
|
|
||||||
statusBar.innerHTML = `<span class="status-error">❌ Task failed: ${task.error || 'Unknown error'}</span>`;
|
|
||||||
} else {
|
|
||||||
statusBar.innerHTML = `<span class="status-running">⚙️ Task running: ${task.status}</span>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
353
static/style.css
353
static/style.css
@ -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;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>OpenManus Local Version</title>
|
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="history-panel">
|
|
||||||
<h2>History Tasks</h2>
|
|
||||||
<div id="task-list" class="task-list"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main-panel">
|
|
||||||
<div id="task-container" class="task-container">
|
|
||||||
<div class="welcome-message">
|
|
||||||
<h1>Welcome to OpenManus Local Version</h1>
|
|
||||||
<p>Please enter a task prompt to start a new task</p>
|
|
||||||
</div>
|
|
||||||
<div id="log-container" class="step-container"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="input-container" class="input-container">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="prompt-input"
|
|
||||||
placeholder="Enter task prompt..."
|
|
||||||
onkeypress="if(event.keyCode === 13) createTask()"
|
|
||||||
>
|
|
||||||
<button onclick="createTask()">Create Task</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/static/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
x
Reference in New Issue
Block a user