Skip to content

Commit 03a9f20

Browse files
committed
realtime display logs with front-end support, using fastapi & jquery & tailwind.css
1 parent f04beec commit 03a9f20

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ aiofiles~=24.1.0
1919
pydantic_core~=2.27.2
2020
colorama~=0.4.6
2121
playwright~=1.49.1
22+
23+
fastapi~=0.115.11
24+
starlette~=0.46.0

templates/web.html

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Manus Web Interface</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
9+
</head>
10+
<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center py-10 px-4">
11+
<div class="w-full max-w-3xl bg-white shadow-lg rounded-xl p-8">
12+
<h1 class="text-2xl font-semibold text-gray-800 mb-6 text-center">Manus Web Interface</h1>
13+
14+
<textarea id="prompt"
15+
class="w-full h-14 resize-none rounded-md border border-gray-300 focus:ring-2 focus:ring-blue-300 outline-none p-4 text-gray-700 bg-gray-50"
16+
placeholder="Enter your prompt here..."></textarea>
17+
18+
<button id="submitBtn"
19+
class="mt-4 w-full py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md font-medium transition-colors duration-200">
20+
Submit
21+
</button>
22+
23+
<pre id="result"
24+
class="mt-6 bg-gray-50 rounded-md border border-gray-200 p-4 h-96 overflow-auto text-gray-700 whitespace-pre-wrap"></pre>
25+
</div>
26+
27+
<script>
28+
$(document).ready(function() {
29+
$('#submitBtn').click(async function() {
30+
const prompt = $('#prompt').val();
31+
const response = await fetch('/run', {
32+
method: 'POST',
33+
headers: {
34+
'Content-Type': 'application/json'
35+
},
36+
body: JSON.stringify({ prompt })
37+
});
38+
const data = await response.json();
39+
if (response.ok) {
40+
$('#result').text(data.result);
41+
} else {
42+
$('#result').text(`Error: ${data.error}`);
43+
}
44+
});
45+
46+
function startLogStream() {
47+
const eventSource = new EventSource("/logs");
48+
eventSource.onmessage = function(event) {
49+
$('#result').append(event.data + "\n");
50+
$('#result').scrollTop($('#result')[0].scrollHeight);
51+
};
52+
53+
eventSource.onerror = function() {
54+
console.error("SSE connection closed.");
55+
eventSource.close();
56+
};
57+
}
58+
59+
startLogStream();
60+
});
61+
</script>
62+
</body>
63+
</html>

web_server.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import uvicorn
2+
import asyncio
3+
from fastapi import FastAPI, Request
4+
from fastapi.responses import HTMLResponse, StreamingResponse
5+
from fastapi.templating import Jinja2Templates
6+
from pydantic import BaseModel
7+
from loguru import logger as _logger
8+
from app.agent.manus import Manus
9+
from app.logger import logger
10+
11+
app = FastAPI()
12+
agent = Manus()
13+
log_queue = asyncio.Queue() # using `asyncio.Queue` prevents blocking
14+
templates = Jinja2Templates(directory="templates")
15+
16+
# Log Interceptor: Asynchronously store logs in the queue
17+
async def log_intercept(message: str):
18+
message = message.strip()
19+
if message:
20+
await log_queue.put(message)
21+
22+
# Append log interceptor to `loguru` without affecting existing log handling
23+
_logger.add(lambda msg: asyncio.create_task(log_intercept(msg)), format="{message}")
24+
25+
# Asynchronous log stream using `async for` for efficient real-time streaming
26+
async def log_stream():
27+
while True:
28+
message = await log_queue.get() # ✅ Waits for new log messages
29+
yield f"data: {message}\n\n"
30+
31+
@app.get("/logs")
32+
async def stream_logs():
33+
"""Frontend can access the real-time log stream via `/logs`"""
34+
return StreamingResponse(log_stream(), media_type="text/event-stream")
35+
36+
@app.get("/", response_class=HTMLResponse)
37+
async def index(request: Request):
38+
return templates.TemplateResponse("web.html", {"request": request})
39+
40+
class PromptRequest(BaseModel):
41+
prompt: str
42+
43+
@app.post("/run")
44+
async def run(request: PromptRequest):
45+
logger.warning("Processing your request...")
46+
result = await agent.run(request.prompt) # ✅ Ensures `agent.run()` executes asynchronously
47+
return {"result": result}
48+
49+
if __name__ == "__main__":
50+
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")

0 commit comments

Comments
 (0)