Spaces:
Paused
Paused
Fix: Add Root UI and Agentic Routes
Browse files- src/server.py +85 -2
src/server.py
CHANGED
|
@@ -8,6 +8,7 @@ import uuid
|
|
| 8 |
import shutil
|
| 9 |
import re
|
| 10 |
from fastapi import FastAPI, UploadFile, File
|
|
|
|
| 11 |
from pydantic import BaseModel
|
| 12 |
from src.core.engine import ModelEngine
|
| 13 |
from src.core.memory import MemoryManager
|
|
@@ -17,6 +18,7 @@ from src.agents.manager import ManagerAgent
|
|
| 17 |
from src.agents.coder import CoderAgent
|
| 18 |
from src.agents.vision import VisionAgent
|
| 19 |
|
|
|
|
| 20 |
try:
|
| 21 |
if 'engine' not in globals(): engine = ModelEngine()
|
| 22 |
except: engine = None
|
|
@@ -39,6 +41,89 @@ def clean_output(text):
|
|
| 39 |
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
|
| 40 |
return text.replace("</think>", "").replace("<think>", "").strip()
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
@app.post("/upload")
|
| 43 |
async def upload_file(file: UploadFile = File(...)):
|
| 44 |
file_ext = file.filename.split(".")[-1].lower()
|
|
@@ -50,7 +135,6 @@ async def upload_file(file: UploadFile = File(...)):
|
|
| 50 |
analysis = f"File {file.filename}"
|
| 51 |
if file_ext in ['jpg', 'png']: analysis = vision.analyze_media(save_path)
|
| 52 |
|
| 53 |
-
# Save to Memory (Hardcoded User 1 for now, should come from Form)
|
| 54 |
memory.save_attachment(1, 1, file.filename, file_ext, analysis)
|
| 55 |
return {"status": "success", "vision_analysis": analysis}
|
| 56 |
|
|
@@ -62,7 +146,6 @@ async def chat_endpoint(req: ChatRequest):
|
|
| 62 |
decision = manager.analyze_task(req.message, history)
|
| 63 |
cat = decision.get("category", "GENERAL")
|
| 64 |
|
| 65 |
-
# Vision Override
|
| 66 |
if "ảnh" in req.message.lower(): cat = "GENERAL"
|
| 67 |
|
| 68 |
resp = ""
|
|
|
|
| 8 |
import shutil
|
| 9 |
import re
|
| 10 |
from fastapi import FastAPI, UploadFile, File
|
| 11 |
+
from fastapi.responses import HTMLResponse
|
| 12 |
from pydantic import BaseModel
|
| 13 |
from src.core.engine import ModelEngine
|
| 14 |
from src.core.memory import MemoryManager
|
|
|
|
| 18 |
from src.agents.coder import CoderAgent
|
| 19 |
from src.agents.vision import VisionAgent
|
| 20 |
|
| 21 |
+
# Initialize Components
|
| 22 |
try:
|
| 23 |
if 'engine' not in globals(): engine = ModelEngine()
|
| 24 |
except: engine = None
|
|
|
|
| 41 |
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
|
| 42 |
return text.replace("</think>", "").replace("<think>", "").strip()
|
| 43 |
|
| 44 |
+
# --- ROOT ROUTE (THE FIX) ---
|
| 45 |
+
@app.get("/", response_class=HTMLResponse)
|
| 46 |
+
async def root():
|
| 47 |
+
return """
|
| 48 |
+
<!DOCTYPE html>
|
| 49 |
+
<html lang="vi">
|
| 50 |
+
<head>
|
| 51 |
+
<meta charset="UTF-8">
|
| 52 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 53 |
+
<title>Project A - Agentic Console</title>
|
| 54 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 55 |
+
<style>
|
| 56 |
+
:root { --primary: #0084ff; --bg: #f0f2f5; --chat-bg: #ffffff; --user-msg: #0084ff; --ai-msg: #f0f0f0; }
|
| 57 |
+
body { font-family: sans-serif; background: var(--bg); display: flex; justify-content: center; height: 100vh; margin: 0; }
|
| 58 |
+
.chat-widget { width: 100%; max-width: 600px; height: 100vh; background: var(--chat-bg); display: flex; flex-direction: column; }
|
| 59 |
+
.header { background: white; padding: 15px; border-bottom: 1px solid #eee; font-weight: bold; display: flex; justify-content: space-between; }
|
| 60 |
+
.messages { flex: 1; padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; }
|
| 61 |
+
.msg { padding: 10px 15px; border-radius: 15px; max-width: 80%; line-height: 1.5; word-wrap: break-word; }
|
| 62 |
+
.msg.user { background: var(--user-msg); color: white; align-self: flex-end; }
|
| 63 |
+
.msg.ai { background: var(--ai-msg); color: black; align-self: flex-start; }
|
| 64 |
+
.input-area { padding: 15px; border-top: 1px solid #eee; display: flex; gap: 10px; background: white; }
|
| 65 |
+
input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 20px; outline: none; }
|
| 66 |
+
button { padding: 10px 20px; background: var(--primary); color: white; border: none; border-radius: 20px; cursor: pointer; }
|
| 67 |
+
#typing { display: none; margin-left: 20px; color: #888; font-size: 12px; }
|
| 68 |
+
</style>
|
| 69 |
+
</head>
|
| 70 |
+
<body>
|
| 71 |
+
<div class="chat-widget">
|
| 72 |
+
<div class="header">
|
| 73 |
+
Project A (Agentic Mode)
|
| 74 |
+
<select id="user-select"><option value="1">User A</option></select>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="messages" id="messages">
|
| 77 |
+
<div class="msg ai">System Online. Connected to Cloud DB.<br>I can see your tables: sales, products, workflows.<br>Try: "Create a workflow to email vip customers."</div>
|
| 78 |
+
</div>
|
| 79 |
+
<div id="typing">Thinking...</div>
|
| 80 |
+
<div class="input-area">
|
| 81 |
+
<input type="text" id="input" placeholder="Type a command..." onkeypress="handleEnter(event)">
|
| 82 |
+
<button onclick="sendMessage()">Send</button>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
<script>
|
| 86 |
+
// Empty string means "Use current domain" (Relative path)
|
| 87 |
+
// This makes it work automatically on Hugging Face Spaces
|
| 88 |
+
const API_URL = "";
|
| 89 |
+
|
| 90 |
+
async function sendMessage() {
|
| 91 |
+
const input = document.getElementById("input");
|
| 92 |
+
const text = input.value.trim();
|
| 93 |
+
if (!text) return;
|
| 94 |
+
|
| 95 |
+
addMessage(text, "user");
|
| 96 |
+
input.value = "";
|
| 97 |
+
document.getElementById("typing").style.display = "block";
|
| 98 |
+
|
| 99 |
+
try {
|
| 100 |
+
const res = await fetch(`${API_URL}/chat`, {
|
| 101 |
+
method: "POST",
|
| 102 |
+
headers: { "Content-Type": "application/json" },
|
| 103 |
+
body: JSON.stringify({ user_id: 1, store_id: 1, message: text })
|
| 104 |
+
});
|
| 105 |
+
const data = await res.json();
|
| 106 |
+
document.getElementById("typing").style.display = "none";
|
| 107 |
+
addMessage(data.response, "ai");
|
| 108 |
+
} catch (e) {
|
| 109 |
+
document.getElementById("typing").style.display = "none";
|
| 110 |
+
addMessage("Error: " + e.message, "ai");
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function addMessage(text, role) {
|
| 115 |
+
const div = document.createElement("div");
|
| 116 |
+
div.className = `msg ${role}`;
|
| 117 |
+
div.innerHTML = marked.parse(text);
|
| 118 |
+
document.getElementById("messages").appendChild(div);
|
| 119 |
+
document.getElementById("messages").scrollTop = document.getElementById("messages").scrollHeight;
|
| 120 |
+
}
|
| 121 |
+
function handleEnter(e) { if (e.key === "Enter") sendMessage(); }
|
| 122 |
+
</script>
|
| 123 |
+
</body>
|
| 124 |
+
</html>
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
@app.post("/upload")
|
| 128 |
async def upload_file(file: UploadFile = File(...)):
|
| 129 |
file_ext = file.filename.split(".")[-1].lower()
|
|
|
|
| 135 |
analysis = f"File {file.filename}"
|
| 136 |
if file_ext in ['jpg', 'png']: analysis = vision.analyze_media(save_path)
|
| 137 |
|
|
|
|
| 138 |
memory.save_attachment(1, 1, file.filename, file_ext, analysis)
|
| 139 |
return {"status": "success", "vision_analysis": analysis}
|
| 140 |
|
|
|
|
| 146 |
decision = manager.analyze_task(req.message, history)
|
| 147 |
cat = decision.get("category", "GENERAL")
|
| 148 |
|
|
|
|
| 149 |
if "ảnh" in req.message.lower(): cat = "GENERAL"
|
| 150 |
|
| 151 |
resp = ""
|