Multi_Agent_Model / src /server.py
sonthaiha's picture
Fix: Directory Error & Feedback DB
d3e87b1 verified
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import json
import uuid
import shutil
import time
import re
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
from sqlalchemy import text
from src.core.engine import ModelEngine
from src.core.memory import MemoryManager
from src.core.saas_api import SaasAPI
from src.core.integrations import IntegrationManager
from src.agents.manager import ManagerAgent
from src.agents.coder import CoderAgent
from src.agents.vision import VisionAgent
try:
if 'engine' not in globals(): engine = ModelEngine()
except: engine = None
memory = MemoryManager()
saas = SaasAPI()
integrations = IntegrationManager(memory)
manager = ManagerAgent(engine, memory)
coder = CoderAgent(engine, memory)
vision = VisionAgent()
app = FastAPI()
class ChatRequest(BaseModel):
user_id: int
store_id: int
message: str
class FeedbackRequest(BaseModel):
prompt: str
response: str
feedback: str
correction: str = ""
def clean_output(text):
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
return text.replace("</think>", "").replace("<think>", "").strip()
# --- HTML UI (Minified) ---
html_content = """
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Project A</title><script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"><style>:root{--primary:#0084ff;--bg:#ffffff;--chat-bg:#f0f2f5;--user-msg:#0084ff;--ai-msg:#e4e6eb}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:var(--bg);display:flex;justify-content:center;height:100vh;margin:0}.chat-widget{width:100%;max-width:700px;height:100vh;background:#fff;display:flex;flex-direction:column;box-shadow:0 0 20px rgba(0,0,0,0.1)}.header{padding:15px 20px;border-bottom:1px solid #eee;font-weight:700;font-size:18px;color:#333;display:flex;align-items:center;gap:10px}.status-dot{width:10px;height:10px;background:#31a24c;border-radius:50%}.messages{flex:1;padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:10px}.msg-row{display:flex;flex-direction:column;max-width:85%}.msg-row.user{align-self:flex-end;align-items:flex-end}.msg-row.ai{align-self:flex-start;align-items:flex-start}.msg{padding:10px 16px;border-radius:18px;font-size:15px;line-height:1.5}.msg.user{background:var(--user-msg);color:white;border-bottom-right-radius:4px}.msg.ai{background:var(--ai-msg);color:#050505;border-bottom-left-radius:4px}.attachment-area{display:flex;gap:10px;padding:10px 15px;background:#fff;overflow-x:auto;min-height:60px;display:none}.att-card{display:flex;align-items:center;gap:10px;background:#2b2d31;color:white;padding:8px 12px;border-radius:8px;font-size:13px;min-width:150px;box-shadow:0 2px 5px rgba(0,0,0,0.2)}.att-thumb{width:30px;height:30px;border-radius:4px;object-fit:cover;background:#444}.att-info{display:flex;flex-direction:column;flex:1;overflow:hidden}.att-name{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.att-size{font-size:10px;color:#aaa}.att-close{cursor:pointer;color:#aaa;padding:5px}.att-close:hover{color:white}.input-wrapper{padding:15px;border-top:1px solid #eee;background:#fff}.input-bar{display:flex;align-items:center;gap:10px;background:#f0f2f5;padding:8px 12px;border-radius:25px}.attach-btn{background:none;border:none;cursor:pointer;color:#65676b;font-size:20px}.attach-btn:hover{color:var(--primary)}input[type="text"]{flex:1;background:transparent;border:none;outline:none;font-size:15px}.send-btn{background:var(--primary);color:white;border:none;width:36px;height:36px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center}#typing{font-size:12px;color:#888;margin-left:20px;margin-bottom:5px;display:none}.feedback-actions{display:flex;gap:10px;margin-top:5px;margin-left:5px;font-size:12px;color:#65676b;opacity:0;transition:opacity 0.2s}.msg-row.ai:hover .feedback-actions{opacity:1}.fb-btn{cursor:pointer}.fb-btn:hover{color:var(--primary)}.correction-box{display:none;margin-top:5px;width:100%}.correction-box input{width:100%;padding:5px;border:1px solid #ddd;border-radius:5px;font-size:12px}</style>
</head>
<body>
<div class="chat-widget"><div class="header"><div class="status-dot"></div> Project A</div><div class="messages" id="messages"></div><div id="typing">Project A đang suy nghĩ...</div><div id="attachment-area" class="attachment-area"></div><div class="input-wrapper"><div class="input-bar"><input type="file" id="fileInput" accept="image/*" style="display:none;" onchange="handleFileSelect()"><button class="attach-btn" onclick="document.getElementById('fileInput').click()"><i class="fa-solid fa-paperclip"></i></button><input type="text" id="input" placeholder="Nhập tin nhắn..." onkeypress="handleEnter(event)"><button class="send-btn" onclick="sendMessage()"><i class="fa-solid fa-paper-plane"></i></button></div></div></div>
<script>
const API_URL="";let currentFile=null;function handleFileSelect(){const e=document.getElementById("fileInput").files[0];e&&(currentFile=e,(new FileReader).onload=function(t){showAttachmentCard(e.name,t.target.result)},(new FileReader).readAsDataURL(e))}function showAttachmentCard(e,t){const n=document.getElementById("attachment-area");n.style.display="flex",n.innerHTML=`<div class="att-card"><img src="${t}" class="att-thumb"><div class="att-info"><div class="att-name">${e}</div><div class="att-size">Ready to upload</div></div><div class="att-close" onclick="clearFile()">✕</div></div>`}function clearFile(){currentFile=null,document.getElementById("fileInput").value="",document.getElementById("attachment-area").style.display="none",document.getElementById("attachment-area").innerHTML=""}async function sendMessage(){const e=document.getElementById("input"),t=e.value.trim();if(!t&&!currentFile)return;t&&addMessage(t,"user"),currentFile&&addMessage(`📎 Đã gửi ảnh: ${currentFile.name}`,"user"),e.value="",clearFile(),document.getElementById("typing").style.display="block";try{if(currentFile){const e=new FormData;e.append("file",currentFile),await fetch(`${API_URL}/upload`,{method:"POST",body:e}),clearFile()}const n=t||"Hãy mô tả ảnh tôi vừa gửi.",a=await fetch(`${API_URL}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:1,store_id:1,message:n})}),s=await a.json();document.getElementById("typing").style.display="none",addAiMessage(s.response,n)}catch(e){document.getElementById("typing").style.display="none",addMessage("Lỗi: "+e.message,"ai")}}function addMessage(e,t){const n=document.createElement("div");n.className=`msg-row ${t}`;const a=document.createElement("div");a.className=`msg ${t}`,a.innerHTML=marked.parse(e),n.appendChild(a),document.getElementById("messages").appendChild(n),document.getElementById("messages").scrollTop=document.getElementById("messages").scrollHeight}function addAiMessage(e,t){const n=document.createElement("div");n.className="msg-row ai";const a=document.createElement("div");a.className="msg ai",a.innerHTML=marked.parse(e);const s=document.createElement("div");s.className="feedback-actions",s.innerHTML=`<span class="fb-btn" onclick="sendFeedback(this, 'up')">👍</span><span class="fb-btn" onclick="showCorrection(this)">👎</span>`;const i=document.createElement("div");i.className="correction-box",i.innerHTML=`<input placeholder="Góp ý sửa lỗi..." onkeypress="if(event.key==='Enter') submitCorrection('${t}', '${e.replace(/'/g,"\'").replace(/\n/g," ")}', this)">`,n.appendChild(a),n.appendChild(s),n.appendChild(i),document.getElementById("messages").appendChild(n),document.getElementById("messages").scrollTop=document.getElementById("messages").scrollHeight}function showCorrection(e){e.parentElement.nextElementSibling.style.display="block"}function sendFeedback(e,t){e.parentElement.innerHTML="<span style='color:green'>✓ Đã ghi nhận</span>"}async function submitCorrection(e,t,n){const a=n.value;n.parentElement.innerHTML="<span style='color:green; font-size:12px'>✓ Cảm ơn bạn!</span>",await fetch(`${API_URL}/feedback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt:e,response:t,feedback:"down",correction:a})})}function handleEnter(e){"Enter"===e.key&&sendMessage()}
</script></body></html>
"""
@app.get("/", response_class=HTMLResponse)
async def root():
return html_content
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
file_ext = file.filename.split(".")[-1].lower()
filename = f"{uuid.uuid4()}.{file_ext}"
save_path = f"src/data/{filename}"
os.makedirs("src/data", exist_ok=True)
with open(save_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer)
analysis = f"File {file.filename}"
if file_ext in ['jpg', 'png', 'jpeg', 'webp']:
analysis = vision.analyze_media(save_path)
memory.save_attachment(1, 1, file.filename, file_ext, analysis)
return {"status": "success", "vision_analysis": analysis}
@app.post("/feedback")
async def save_feedback(req: FeedbackRequest):
# --- SAVE TO NEON DB ---
try:
with memory.get_conn() as conn:
conn.execute(text("""
INSERT INTO feedback_logs (user_id, prompt, ai_response, user_correction, feedback_type)
VALUES (:uid, :p, :r, :c, :f)
"""), {
"uid": 1,
"p": req.prompt,
"r": req.response,
"c": req.correction,
"f": req.feedback
})
conn.commit()
return {"status": "recorded_in_db"}
except Exception as e:
print(f"Feedback Error: {e}")
return {"status": "error", "message": str(e)}
@app.post("/chat")
async def chat_endpoint(req: ChatRequest):
memory.add_message(req.user_id, req.store_id, "user", req.message)
history = memory.get_context_string(req.user_id)
decision = manager.analyze_task(req.message, history)
cat = decision.get("category", "GENERAL")
if "ảnh" in req.message.lower() or "hình" in req.message.lower():
cat = "GENERAL"
resp = ""
if cat == "TECHNICAL":
plan = manager.plan(req.message, history)
code = coder.write_code(req.message, plan)
match = re.search(r"```json\n(.*?)\n```", code, re.DOTALL)
if match:
integrations.deploy_internal(req.store_id, match.group(1))
resp = f"Đã thiết kế quy trình:\n{code}"
elif cat == "DATA_INTERNAL":
data = saas.get_sales_report(req.store_id)
resp = manager.consult(req.message, str(data), history)
else:
resp = manager.consult(req.message, "", history)
final = clean_output(resp)
memory.add_message(req.user_id, req.store_id, "assistant", final)
return {"response": final}