Spaces:
Paused
Paused
File size: 11,106 Bytes
2e91995 1804a7a 2e91995 a0b166b 1804a7a 2e91995 fbc4549 1804a7a e0c7727 1804a7a 2e91995 1804a7a 2e91995 1804a7a a0b166b 1804a7a d3e87b1 e0c7727 fbc4549 e0c7727 fbc4549 e0c7727 fbc4549 e0c7727 fbc4549 e0c7727 2e91995 1804a7a 2e91995 a0b166b 1804a7a 2e91995 a0b166b d3e87b1 e0c7727 d3e87b1 e0c7727 d3e87b1 e0c7727 a0b166b 2e91995 1804a7a 2e91995 1804a7a a0b166b 2e91995 1804a7a 2e91995 a0b166b 2e91995 1804a7a 2e91995 1804a7a 2e91995 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
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}
|