Spaces:
Sleeping
Sleeping
| import sys | |
| import os | |
| import io | |
| from flask import Flask, request, jsonify, render_template_string | |
| # Add project root to path | |
| sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) | |
| from src.model.rag_runner import RAGRunner | |
| app = Flask(__name__) | |
| # Initialize RAG Runner (Global) | |
| # Note: This will load the model, which takes time. | |
| print("Initializing Chatbot Engine...") | |
| chatbot = RAGRunner() | |
| print("Chatbot Engine Ready!") | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="vi"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NIHE AI Smart Assistant</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #0062ff; | |
| --primary-light: #e6f0ff; | |
| --bg: #f4f7f6; | |
| --glass: rgba(255, 255, 255, 0.85); | |
| --text: #2d3436; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 0; | |
| color: var(--text); | |
| } | |
| .container { | |
| width: 95%; | |
| max-width: 1000px; | |
| height: 85vh; | |
| background: var(--glass); | |
| backdrop-filter: blur(20px); | |
| border-radius: 30px; | |
| box-shadow: 0 20px 50px rgba(0,0,0,0.2); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| border: 1px solid rgba(255,255,255,0.4); | |
| } | |
| .header { | |
| padding: 25px; | |
| background: rgba(255, 255, 255, 0.5); | |
| border-bottom: 1px solid rgba(0,0,0,0.05); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .header-left { display: flex; align-items: center; gap: 15px; } | |
| .header h1 { margin: 0; font-size: 1.3rem; font-weight: 600; color: #1a1a1a; } | |
| .status-badge { | |
| background: #00d1b2; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| display: inline-block; | |
| margin-right: 5px; | |
| box-shadow: 0 0 10px #00d1b2; | |
| } | |
| .chat-box { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 30px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| scrollbar-width: thin; | |
| } | |
| .message { | |
| max-width: 75%; | |
| padding: 15px 22px; | |
| border-radius: 20px; | |
| font-size: 0.95rem; | |
| line-height: 1.6; | |
| position: relative; | |
| animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } | |
| .user { | |
| align-self: flex-end; | |
| background: var(--primary); | |
| color: white; | |
| border-bottom-right-radius: 4px; | |
| box-shadow: 0 4px 15px rgba(0, 98, 255, 0.3); | |
| } | |
| .bot { | |
| align-self: flex-start; | |
| background: white; | |
| color: var(--text); | |
| border-bottom-left-radius: 4px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.05); | |
| } | |
| .message p { margin: 0 0 10px 0; } | |
| .message p:last-child { margin-bottom: 0; } | |
| .message ul, .message ol { padding-left: 20px; margin: 10px 0; } | |
| .footer { | |
| padding: 25px; | |
| background: white; | |
| display: flex; | |
| gap: 15px; | |
| border-top: 1px solid #f0f0f0; | |
| } | |
| input { | |
| flex: 1; | |
| padding: 15px 25px; | |
| border: 2px solid #f0f0f0; | |
| border-radius: 15px; | |
| outline: none; | |
| transition: all 0.3s; | |
| font-size: 1rem; | |
| } | |
| input:focus { border-color: var(--primary); background: #fafafa; } | |
| button { | |
| padding: 0 30px; | |
| background: var(--primary); | |
| color: white; | |
| border: none; | |
| border-radius: 15px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: all 0.2s; | |
| box-shadow: 0 4px 10px rgba(0, 98, 255, 0.2); | |
| } | |
| button:hover { background: #0056e0; transform: translateY(-2px); } | |
| button:active { transform: translateY(0); } | |
| .typing { | |
| font-size: 0.85rem; | |
| color: #666; | |
| margin-top: -15px; | |
| margin-left: 35px; | |
| padding-bottom: 10px; | |
| display: none; | |
| font-style: italic; | |
| } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <div class="header-left"> | |
| <div style="width: 45px; height: 45px; background: var(--primary); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 1.2rem; box-shadow: 0 4px 12px rgba(0,98,255,0.4);">N</div> | |
| <h1>NIHE Assistant <span style="font-size: 0.8rem; opacity: 0.6; font-weight: 400;">v2.5</span></h1> | |
| </div> | |
| <div style="display: flex; align-items: center; font-size: 0.9rem; color: #666;"> | |
| <span class="status-badge"></span> Trực tuyến | |
| </div> | |
| </div> | |
| <div class="chat-box" id="chatbox"> | |
| <div class="message bot">Xin chào! Tôi là **trợ lý thông minh của NIHE**. Tôi có thể hỗ trợ bạn về thông tin dịch bệnh, tiêm chủng và các quy trình y tế. Hôm nay tôi có thể giúp gì cho bạn?</div> | |
| </div> | |
| <div class="typing" id="typing">Trợ lý đang soạn câu trả lời...</div> | |
| <div class="footer"> | |
| <input type="text" id="msg" placeholder="Nhập câu hỏi của bạn tại đây..." onkeydown="if(event.key==='Enter') send()"> | |
| <button onclick="send()">Gửi đi</button> | |
| </div> | |
| </div> | |
| <script> | |
| const chatbox = document.getElementById('chatbox'); | |
| const typing = document.getElementById('typing'); | |
| marked.setOptions({ | |
| breaks: true, | |
| gfm: true | |
| }); | |
| // Initialize bot welcoming message with marked | |
| const firstBotMsg = chatbox.querySelector('.bot'); | |
| firstBotMsg.innerHTML = marked.parse(firstBotMsg.innerHTML); | |
| async function send() { | |
| const input = document.getElementById('msg'); | |
| const msg = input.value.trim(); | |
| if(!msg) return; | |
| addMsg(msg, "user"); | |
| input.value = ""; | |
| typing.style.display = "block"; | |
| try { | |
| const res = await fetch('/chat', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({query: msg}) | |
| }); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const data = await res.json(); | |
| addMsg(data.response, "bot"); | |
| } catch (e) { | |
| console.error("Chat Error:", e); | |
| addMsg(`⚠️ **Lỗi kết nối**: Hệ thống không phản hồi kịp thời hoặc mất kết nối.\n\n*Nguyên nhân có thể do:* \n1. Server đang xử lý quá tải (CPU Mode chậm).\n2. Đường truyền mạng (Tunnel) bị ngắt.\n\n(Error details: ${e.message})`, "bot"); | |
| } finally { | |
| typing.style.display = "none"; | |
| } | |
| } | |
| function addMsg(text, cls) { | |
| const div = document.createElement('div'); | |
| div.className = `message ${cls}`; | |
| if(cls === "bot") { | |
| div.innerHTML = marked.parse(text); | |
| } else { | |
| div.innerText = text; | |
| } | |
| chatbox.appendChild(div); | |
| // Smooth scroll | |
| chatbox.scrollTo({ | |
| top: chatbox.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def home(): | |
| return render_template_string(HTML_TEMPLATE) | |
| def chat(): | |
| """API endpoint for chat.""" | |
| print("Received chat request", flush=True) | |
| data = request.json | |
| print(f"Data received: {data}", flush=True) | |
| query = data.get('query', '') | |
| if not query: | |
| return jsonify({'response': 'Vui lòng nhập câu hỏi.'}) | |
| try: | |
| response = chatbot.run(query) | |
| return jsonify({'response': response}) | |
| except Exception as e: | |
| print(f"Error executing RAG: {e}", flush=True) | |
| return jsonify({'response': f"Lỗi hệ thống: {str(e)}"}), 500 | |
| if __name__ == '__main__': | |
| # Threaded=True to handle multiple requests (and keep-alives) better | |
| app.run(host='0.0.0.0', port=5000, threaded=True) | |