lamgamepro1 commited on
Commit
320f523
·
verified ·
1 Parent(s): 7aaf5c0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -163
app.py CHANGED
@@ -7,17 +7,15 @@ import hashlib
7
  from datetime import datetime
8
 
9
  # Import an toàn
10
- try: from duckduckgo_search import DDGS
11
- except: DDGS = None
12
  try: from gtts import gTTS
13
  except: gTTS = None
14
 
15
- # 1. CẤU HÌNH HỆ THỐNG & MODEL
16
  HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN")
17
  client = InferenceClient(model="Qwen/Qwen2.5-7B-Instruct", token=HF_TOKEN)
18
  DB_FILE = "nodeai_pro_memory.json"
19
 
20
- # 2. HỆ THỐNG TRÍ NHỚ THEO USER
21
  def get_user_id(username):
22
  if not username: return "guest"
23
  return hashlib.md5(username.encode()).hexdigest()[:8]
@@ -33,251 +31,283 @@ def save_db(data):
33
  with open(DB_FILE, "w", encoding="utf-8") as f:
34
  json.dump(data, f, ensure_ascii=False, indent=4)
35
 
36
- # 3. GIAO DIỆN SIÊU ĐẸP - GRADIO 6.0 COMPATIBLE
37
  custom_css = """
38
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;800&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
 
 
39
 
40
- * { box-sizing: border-box; }
41
- body { margin: 0; font-family: 'Plus Jakarta Sans', sans-serif !important; }
42
  .gradio-container {
43
  background: linear-gradient(135deg, #0a0a0f 0%, #1a0d1a 50%, #0f0a1a 100%) !important;
44
- min-height: 100vh !important; position: relative !important; overflow-x: hidden !important;
45
  }
46
 
47
- /* MOBILE HAMBURGER */
48
- .hamburger {
49
- position: fixed !important; top: 20px !important; left: 20px !important; z-index: 3000 !important;
50
- background: rgba(20,20,30,0.95) !important; border: 1px solid rgba(0,242,254,0.3) !important;
51
- border-radius: 12px !important; width: 56px !important; height: 56px !important;
52
- backdrop-filter: blur(20px) !important; transition: all 0.3s ease !important;
 
 
 
53
  }
54
- .hamburger:hover { box-shadow: 0 8px 25px rgba(0,242,254,0.3) !important; }
55
 
56
- /* SIDEBAR MOBILE */
57
- .sidebar-mobile {
58
- position: fixed !important; top: 0 !important; left: -320px !important; width: 320px !important;
59
- height: 100vh !important; z-index: 2500 !important; background: rgba(15,15,25,0.98) !important;
60
- backdrop-filter: blur(25px) !important; border-right: 1px solid rgba(255,255,255,0.08) !important;
61
- transition: left 0.4s cubic-bezier(0.25,0.46,0.45,0.94) !important; padding: 80px 25px 25px !important;
 
62
  overflow-y: auto !important;
63
  }
64
- .sidebar-mobile.open { left: 0 !important; }
65
 
66
  /* LOGO */
67
- .logo-text {
68
- font-family: 'Orbitron', monospace !important; font-weight: 800 !important;
69
- background: linear-gradient(135deg, #00f2fe 0%, #4facfe 50%, #ff6b6b 100%) !important;
70
- background-size: 300% 300% !important; -webkit-background-clip: text !important;
71
- -webkit-text-fill-color: transparent !important; font-size: 28px !important;
72
- text-align: center !important; margin-bottom: 35px !important;
73
- animation: gradientShift 4s ease infinite !important;
74
  }
75
- @keyframes gradientShift { 0%,100%{background-position:0%50%} 50%{background-position:100%50%} }
76
-
77
- /* USER CARD */
78
- .user-card { background: rgba(255,107,107,0.15) !important; border: 1px solid rgba(255,107,107,0.3) !important;
79
- border-radius: 16px !important; padding: 20px !important; margin-bottom: 25px !important;
80
- text-align: center !important; backdrop-filter: blur(15px) !important;
81
- }
82
- .user-card h3 { color: #ff8e53 !important; margin: 0 0 8px 0 !important; font-size: 18px !important; }
83
 
84
  /* BUTTONS */
85
- .orange-btn, .hist-btn {
86
- width: 100% !important; border-radius: 12px !important; border: none !important; font-weight: 600 !important;
87
- cursor: pointer !important; transition: all 0.3s ease !important; margin-bottom: 12px !important;
88
- padding: 14px 18px !important; font-size: 15px !important;
 
89
  }
90
- .orange-btn {
91
- background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 50%, #FF6B6B 100%) !important;
92
- box-shadow: 0 6px 20px rgba(255,107,107,0.4) !important; color: white !important;
93
  }
94
- .orange-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 10px 30px rgba(255,107,107,0.6) !important; }
95
- .hist-btn { background: rgba(255,255,255,0.05) !important; color: #a0aec0 !important;
96
- border: 1px solid rgba(255,255,255,0.1) !important; text-align: left !important;
97
- }
98
- .hist-btn:hover { background: rgba(0,242,254,0.15) !important; color: #00f2fe !important;
99
- border-color: rgba(0,242,254,0.4) !important; box-shadow: 0 4px 15px rgba(0,242,254,0.2) !important;
100
  }
 
101
 
102
- /* CHAT */
103
- .chat-container { padding: 20px !important; max-width: 100% !important; margin: 0 auto !important;
104
- min-height: 100vh !important; display: flex !important; flex-direction: column !important;
 
 
105
  }
106
- .chatbot-box { flex: 1 !important; background: rgba(20,20,30,0.6) !important; border: 1px solid rgba(0,242,254,0.2) !important;
107
- border-radius: 24px !important; padding: 25px !important; margin-bottom: 120px !important;
108
- max-height: 70vh !important; overflow-y: auto !important; backdrop-filter: blur(20px) !important;
109
- box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;
 
 
 
 
110
  }
111
- .input-bar { position: fixed !important; bottom: 20px !important; left: 20px !important; right: 20px !important;
112
- z-index: 1000 !important; background: rgba(20,20,30,0.95) !important; backdrop-filter: blur(25px) !important;
113
- border: 1px solid rgba(0,242,254,0.3) !important; border-radius: 24px !important; padding: 12px 20px !important;
114
- box-shadow: 0 15px 50px rgba(0,0,0,0.6) !important;
115
  }
116
- footer, .gr-button-secondary { display: none !important; }
117
 
118
- @media (min-width: 1024px) {
119
- .hamburger { display: none !important; }
120
- .sidebar-mobile { position: static !important; left: 0 !important; width: 340px !important; }
121
- .chat-container { margin-left: 340px !important; max-width: calc(100% - 360px) !important; }
122
  }
123
- @media (max-width: 768px) {
124
- .chatbot-box { margin-bottom: 140px !important; padding: 20px !important; border-radius: 20px !important; }
125
- .input-bar { padding: 10px 16px !important; bottom: 15px !important; }
 
 
126
  }
 
 
 
 
 
 
 
 
 
127
  """
128
 
129
- # 4. CORE FUNCTIONS
130
  def chat_fn(message, history, session_id, store, username):
131
- user_text = message.get("text", "")
132
- if not user_text and not message.get("files", []):
133
- yield {"text": ""}, history, session_id, store, ""
134
- return
135
 
136
  user_id = get_user_id(username)
137
- if user_id not in store:
138
- store[user_id] = {"workspaces": {}}
139
 
140
  if session_id not in store[user_id]["workspaces"]:
141
  store[user_id]["workspaces"][session_id] = {
142
- "title": user_text[:30] if user_text else "Nova Workspace",
143
- "history": [],
144
- "created": datetime.now().isoformat()
145
  }
146
 
147
- api_messages = [{"role": "system", "content": f"Bạn là NodeAI Genesis. Master: Trần Đình Phúc. User: {username}"}]
148
- for msg in history:
149
- api_messages.append({"role": msg["role"], "content": msg["content"]})
150
- api_messages.append({"role": "user", "content": user_text})
151
 
152
  try:
153
- ans = ""
154
- new_h = history + [{"role": "user", "content": user_text}]
155
- for token in client.chat_completion(messages=api_messages, max_tokens=2000, stream=True):
156
- if token.choices[0].delta.content:
157
- ans += token.choices[0].delta.content
158
- yield {"text": ""}, new_h + [{"role": "assistant", "content": ans}], session_id, store, ""
 
159
 
160
- final_h = new_h + [{"role": "assistant", "content": ans}]
161
- store[user_id]["workspaces"][session_id]["history"] = final_h
162
- store[user_id]["workspaces"][session_id]["title"] = user_text[:30] if user_text else store[user_id]["workspaces"][session_id]["title"]
 
163
  save_db(store)
164
- yield {"text": ""}, final_h, session_id, store, ans
 
165
  except Exception as e:
166
- yield {"text": f"Lỗi: {str(e)}"}, history + [{"role": "assistant", "content": str(e)}], session_id, store, ""
167
 
168
- def tts_engine(text):
169
- if not text or len(text) < 2 or not gTTS: return None
170
  try:
171
- tts = gTTS(text=text[:300], lang='vi')
172
- tts.save("voice.mp3")
173
- return "voice.mp3"
174
  except: return None
175
 
176
  def toggle_sidebar():
177
- return gr.update(visible=True, elem_classes="sidebar-mobile open")
178
 
179
  def handle_login(profile):
180
- """Xử lý đăng nhập từ LoginButton"""
181
- if profile and hasattr(profile, 'name') and profile.name:
182
  username = profile.name
183
  user_id = get_user_id(username)
184
- user_html = f"""
185
- <div class='user-card'>
186
  <h3>👋 Xin chào</h3>
187
- <p style='color: #ff8e53; font-weight: 600;'>@{username}</p>
188
  <small>ID: {user_id}</small>
189
  </div>
190
- """
191
- return gr.update(value=user_html), username
192
- return gr.update(value="<div class='user-card'><h3>Guest</h3><p>Chưa đăng nhập</p></div>"), "guest"
 
 
 
 
193
 
194
- def load_history_buttons(store, username):
195
  user_id = get_user_id(username)
196
  if user_id in store and "workspaces" in store[user_id]:
197
- html_buttons = []
198
- for sid, data in sorted(store[user_id]["workspaces"].items(), key=lambda x: x[1]["created"], reverse=True):
199
- html_buttons.append(f"""
200
- <div class="hist-btn" style="cursor: pointer; margin-bottom: 8px;">
201
- 💬 {data['title']} ({len(data['history'])//2} tin)
 
 
 
 
 
202
  </div>
203
- """)
204
- return gr.update(value="".join(html_buttons))
205
- return gr.update(value="<div>Chưa có lịch sử chat</div>")
206
 
207
- # 5. GIAO DIỆN CHÍNH - FIX LOGINBUTTON
208
- with gr.Blocks(title="NodeAI Genesis Pro") as demo:
209
- chat_store = gr.State(load_db())
 
210
  session_id = gr.State(str(uuid.uuid4()))
211
  current_user = gr.State("guest")
212
- tts_buffer = gr.Textbox(visible=False)
213
 
214
- # HAMBURGER MENU
215
- hamburger = gr.Button("☰", elem_classes="hamburger", scale=1)
 
 
216
 
217
  # SIDEBAR
218
- with gr.Column(elem_classes="sidebar-mobile", visible=False) as sidebar:
219
- gr.HTML("<div class='logo-text'>NODEAI GENESIS</div>")
220
 
221
- # ✅ FIX: LoginButton dùng .click() thay vì .change()
222
- login_btn = gr.LoginButton(value="🔐 ĐĂNG NHẬP HF", elem_classes="orange-btn", scale=1)
223
 
224
- # USER INFO
225
- gr.HTML("<h3 style='color: #a0aec0; margin: 25px 0 15px 0;'>👤 NGƯỜI DÙNG</h3>")
226
- user_card = gr.HTML(value="<div class='user-card'><h3>Guest</h3><p>Chưa đăng nhập</p></div>")
 
 
 
227
 
228
- # TẠO WORKSPACE MỚI
229
- new_btn = gr.Button("+ TẠO KHÔNG GIAN MỚI", elem_classes="orange-btn", scale=1)
230
 
231
- # LỊCH SỬ CHAT
232
- gr.HTML("<h3 style='color: #a0aec0; margin: 25px 0 15px 0;'>📚 LỊCH SỬ</h3>")
233
- history_display = gr.HTML(value="Chưa có lịch sử chat")
234
 
235
  # MAIN CHAT
236
- with gr.Column(elem_classes="chat-container"):
237
  chatbot = gr.Chatbot(
238
- show_label=False,
239
- avatar_images=(None, "https://cdn-icons-png.flaticon.com/512/4712/4712139.png"),
240
- elem_classes="chatbot-box"
 
241
  )
242
- audio_out = gr.Audio(autoplay=True, visible=False)
243
 
244
- with gr.Row(elem_classes="input-bar"):
245
- msg_input = gr.MultimodalTextbox(
246
- show_label=False,
247
- placeholder="✨ Nhập lệnh cho NodeAI Genesis...",
248
- container=False
249
  )
 
250
 
251
- # ✅ EVENTS - FIX LOGINBUTTON
252
  hamburger.click(toggle_sidebar, outputs=sidebar)
253
 
254
- # LoginButton chỉ có .click()
255
  login_btn.click(
256
  handle_login,
257
- outputs=[user_card, current_user]
258
  ).then(
259
- load_history_buttons,
260
- inputs=[chat_store, current_user],
261
- outputs=history_display
262
  )
263
 
264
- # TẠO MỚI
265
- new_btn.click(
266
  lambda: ([], str(uuid.uuid4())),
267
  outputs=[chatbot, session_id]
268
  )
269
 
270
- # CHAT
 
 
271
  msg_input.submit(
272
- chat_fn,
273
- inputs=[msg_input, chatbot, session_id, chat_store, current_user],
274
- outputs=[msg_input, chatbot, session_id, chat_store, tts_buffer]
275
- ).then(
276
- tts_engine, tts_buffer, audio_out
277
- )
 
 
 
 
278
 
279
  if __name__ == "__main__":
280
  demo.launch(
281
- share=True, server_name="0.0.0.0", server_port=7860,
282
- theme=gr.themes.Base(), css=custom_css
 
 
 
 
283
  )
 
7
  from datetime import datetime
8
 
9
  # Import an toàn
 
 
10
  try: from gtts import gTTS
11
  except: gTTS = None
12
 
13
+ # 1. CẤU HÌNH
14
  HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN")
15
  client = InferenceClient(model="Qwen/Qwen2.5-7B-Instruct", token=HF_TOKEN)
16
  DB_FILE = "nodeai_pro_memory.json"
17
 
18
+ # 2. HỆ THỐNG USER
19
  def get_user_id(username):
20
  if not username: return "guest"
21
  return hashlib.md5(username.encode()).hexdigest()[:8]
 
31
  with open(DB_FILE, "w", encoding="utf-8") as f:
32
  json.dump(data, f, ensure_ascii=False, indent=4)
33
 
34
+ # 3. CSS SIMPLIFIED - ĐẢM BẢO HIỂN THỊ
35
  custom_css = """
36
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@800&family=Inter:wght@400;500;600&display=swap');
37
+ * { box-sizing: border-box; margin: 0; padding: 0; }
38
+ body { font-family: 'Inter', sans-serif; }
39
 
40
+ /* CONTAINER CHÍNH */
 
41
  .gradio-container {
42
  background: linear-gradient(135deg, #0a0a0f 0%, #1a0d1a 50%, #0f0a1a 100%) !important;
43
+ color: white !important; min-height: 100vh !important;
44
  }
45
 
46
+ /* HAMBURGER - LUÔN HIỂN THỊ */
47
+ .hamburger-btn {
48
+ position: fixed !important; top: 20px !important; left: 20px !important;
49
+ z-index: 9999 !important; width: 60px !important; height: 60px !important;
50
+ background: linear-gradient(135deg, #FF6B6B, #FF8E53) !important;
51
+ border: none !important; border-radius: 15px !important;
52
+ color: white !important; font-size: 24px !important; font-weight: bold !important;
53
+ box-shadow: 0 8px 25px rgba(255,107,107,0.4) !important;
54
+ cursor: pointer !important;
55
  }
56
+ .hamburger-btn:hover { transform: scale(1.05) !important; }
57
 
58
+ /* SIDEBAR - SIÊU RÕ */
59
+ .sidebar {
60
+ position: fixed !important; top: 0 !important; left: -350px !important;
61
+ width: 350px !important; height: 100vh !important; z-index: 9998 !important;
62
+ background: rgba(20,20,30, 0.98) !important;
63
+ backdrop-filter: blur(20px) !important; border-right: 2px solid #00f2fe !important;
64
+ transition: left 0.4s ease !important; padding: 90px 25px 25px !important;
65
  overflow-y: auto !important;
66
  }
67
+ .sidebar.open { left: 0 !important; }
68
 
69
  /* LOGO */
70
+ .logo {
71
+ font-family: 'Orbitron', monospace !important; font-size: 28px !important;
72
+ font-weight: 800 !important; text-align: center !important; margin-bottom: 30px !important;
73
+ background: linear-gradient(90deg, #00f2fe, #4facfe, #ff6b6b) !important;
74
+ -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important;
75
+ background-clip: text !important;
 
76
  }
 
 
 
 
 
 
 
 
77
 
78
  /* BUTTONS */
79
+ .sidebar-btn {
80
+ width: 100% !important; height: 50px !important; margin-bottom: 15px !important;
81
+ border: none !important; border-radius: 12px !important; font-size: 16px !important;
82
+ font-weight: 600 !important; cursor: pointer !important; transition: all 0.3s !important;
83
+ display: flex !important; align-items: center !important; padding: 0 20px !important;
84
  }
85
+ .login-btn {
86
+ background: linear-gradient(135deg, #FF6B6B, #FF8E53) !important; color: white !important;
87
+ box-shadow: 0 6px 20px rgba(255,107,107,0.4) !important;
88
  }
89
+ .login-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 10px 30px rgba(255,107,107,0.6) !important; }
90
+ .new-chat-btn {
91
+ background: linear-gradient(135deg, #00f2fe, #4facfe) !important; color: white !important;
92
+ box-shadow: 0 6px 20px rgba(0,242,254,0.4) !important;
 
 
93
  }
94
+ .new-chat-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 10px 30px rgba(0,242,254,0.6) !important; }
95
 
96
+ /* USER INFO */
97
+ .user-info {
98
+ background: rgba(255,107,107,0.2) !important; border: 1px solid rgba(255,107,107,0.5) !important;
99
+ border-radius: 16px !important; padding: 20px !important; margin: 20px 0 !important;
100
+ text-align: center !important;
101
  }
102
+ .user-name { color: #ff8e53 !important; font-weight: 600 !important; font-size: 18px !important; }
103
+
104
+ /* HISTORY */
105
+ .history-item {
106
+ background: rgba(255,255,255,0.05) !important; border: 1px solid rgba(255,255,255,0.1) !important;
107
+ color: #a0aec0 !important; border-radius: 10px !important; padding: 15px !important;
108
+ margin-bottom: 10px !important; cursor: pointer !important; transition: all 0.3s !important;
109
+ text-align: left !important; font-size: 14px !important;
110
  }
111
+ .history-item:hover {
112
+ background: rgba(0,242,254,0.15) !important; color: #00f2fe !important;
113
+ border-color: #00f2fe !important; transform: translateX(5px) !important;
 
114
  }
 
115
 
116
+ /* MAIN CHAT */
117
+ .main-chat {
118
+ margin-left: 0 !important; padding: 80px 20px 150px 20px !important; max-width: 100% !important;
119
+ display: flex !important; flex-direction: column !important; min-height: 100vh !important;
120
  }
121
+ .chatbot {
122
+ flex: 1 !important; background: rgba(20,20,30,0.8) !important;
123
+ border: 1px solid rgba(0,242,254,0.3) !important; border-radius: 20px !important;
124
+ padding: 25px !important; max-height: 70vh !important; overflow-y: auto !important;
125
+ backdrop-filter: blur(20px) !important; margin-bottom: 20px !important;
126
  }
127
+
128
+ /* INPUT BAR FIXED */
129
+ .input-container {
130
+ position: fixed !important; bottom: 20px !important; left: 20px !important; right: 20px !important;
131
+ z-index: 1000 !important; background: rgba(20,20,30,0.95) !important;
132
+ backdrop-filter: blur(25px) !important; border: 1px solid rgba(0,242,254,0.4) !important;
133
+ border-radius: 25px !important; padding: 15px 20px !important;
134
+ }
135
+ footer { display: none !important; }
136
  """
137
 
138
+ # 4. FUNCTIONS
139
  def chat_fn(message, history, session_id, store, username):
140
+ user_text = message.strip() if isinstance(message, str) else ""
141
+ if not user_text:
142
+ return "", history, session_id, store, ""
 
143
 
144
  user_id = get_user_id(username)
145
+ if user_id not in store: store[user_id] = {"workspaces": {}}
 
146
 
147
  if session_id not in store[user_id]["workspaces"]:
148
  store[user_id]["workspaces"][session_id] = {
149
+ "title": user_text[:30], "history": [], "created": datetime.now().isoformat()
 
 
150
  }
151
 
152
+ # Tạo messages cho API
153
+ messages = [{"role": "system", "content": f"Bạn là NodeAI Genesis. User: {username}"}]
154
+ for msg in history: messages.append({"role": msg["role"], "content": msg["content"]})
155
+ messages.append({"role": "user", "content": user_text})
156
 
157
  try:
158
+ # Stream response
159
+ response = ""
160
+ new_history = history + [{"role": "user", "content": user_text}]
161
+ for chunk in client.chat_completion(messages, max_tokens=1500, stream=True):
162
+ if chunk.choices[0].delta.content:
163
+ response += chunk.choices[0].delta.content
164
+ yield "", new_history + [{"role": "assistant", "content": response}], session_id, store, ""
165
 
166
+ # Lưu history
167
+ final_history = new_history + [{"role": "assistant", "content": response}]
168
+ store[user_id]["workspaces"][session_id]["history"] = final_history
169
+ store[user_id]["workspaces"][session_id]["title"] = user_text[:30]
170
  save_db(store)
171
+
172
+ yield "", final_history, session_id, store, response
173
  except Exception as e:
174
+ yield f"Lỗi: {str(e)}", history, session_id, store, ""
175
 
176
+ def tts_fn(text):
177
+ if not text or not gTTS: return None
178
  try:
179
+ tts = gTTS(text=text[:200], lang='vi', slow=False)
180
+ tts.save("response.mp3")
181
+ return "response.mp3"
182
  except: return None
183
 
184
  def toggle_sidebar():
185
+ return gr.update(visible=True, elem_classes="sidebar open")
186
 
187
  def handle_login(profile):
188
+ if profile and hasattr(profile, 'name'):
 
189
  username = profile.name
190
  user_id = get_user_id(username)
191
+ return f"""
192
+ <div class='user-info'>
193
  <h3>👋 Xin chào</h3>
194
+ <div class='user-name'>@{username}</div>
195
  <small>ID: {user_id}</small>
196
  </div>
197
+ """, username
198
+ return """
199
+ <div class='user-info'>
200
+ <h3>👤 Khách</h3>
201
+ <p>Vui lòng đăng nhập để lưu lịch sử</p>
202
+ </div>
203
+ """, "guest"
204
 
205
+ def get_history(store, username):
206
  user_id = get_user_id(username)
207
  if user_id in store and "workspaces" in store[user_id]:
208
+ html = ""
209
+ for sid, data in sorted(store[user_id]["workspaces"].items(),
210
+ key=lambda x: x[1].get("created", ""), reverse=True)[:10]:
211
+ msg_count = len(data["history"]) // 2
212
+ html += f"""
213
+ <div class='history-item'>
214
+ 💬 {data['title']}
215
+ <div style='font-size: 12px; opacity: 0.7;'>
216
+ {msg_count} tin nhắn
217
+ </div>
218
  </div>
219
+ """
220
+ return html or "<div>Chưa có lịch sử chat</div>"
221
+ return "<div>Chưa có lịch sử chat</div>"
222
 
223
+ # 5. GIAO DIỆN
224
+ with gr.Blocks(title="NodeAI Genesis") as demo:
225
+ # States
226
+ store = gr.State(load_db())
227
  session_id = gr.State(str(uuid.uuid4()))
228
  current_user = gr.State("guest")
229
+ tts_buffer = gr.State("")
230
 
231
+ gr.HTML("<h1 style='text-align:center; color:#00f2fe; font-size:48px; margin:20px 0;'>🤖 NODEAI GENESIS</h1>")
232
+
233
+ # HAMBURGER
234
+ hamburger = gr.Button("☰ MENU", elem_classes="hamburger-btn")
235
 
236
  # SIDEBAR
237
+ with gr.Column(elem_classes="sidebar", visible=False) as sidebar:
238
+ gr.HTML("<div class='logo'>NODEAI GENESIS</div>")
239
 
240
+ login_btn = gr.LoginButton(value="🔐 ĐĂNG NHẬP", elem_classes="sidebar-btn login-btn")
 
241
 
242
+ user_info = gr.HTML(value="""
243
+ <div class='user-info'>
244
+ <h3>👤 Khách</h3>
245
+ <p>Vui lòng đăng nhập để lưu lịch sử</p>
246
+ </div>
247
+ """)
248
 
249
+ gr.HTML("<h3 style='color:#a0aec0; margin: 30px 0 20px 0;'>➕ KHÔNG GIAN MỚI</h3>")
250
+ new_chat = gr.Button("TẠO CHAT MỚI", elem_classes="sidebar-btn new-chat-btn")
251
 
252
+ gr.HTML("<h3 style='color:#a0aec0; margin: 30px 0 20px 0;'>📚 LỊCH SỬ</h3>")
253
+ history_list = gr.HTML(value="Chưa có lịch sử chat")
 
254
 
255
  # MAIN CHAT
256
+ with gr.Column(elem_classes="main-chat"):
257
  chatbot = gr.Chatbot(
258
+ height=500,
259
+ show_label=False,
260
+ avatar_images=("user.png", "https://cdn-icons-png.flaticon.com/512/4712/4712139.png"),
261
+ elem_classes="chatbot"
262
  )
263
+ audio_player = gr.Audio(visible=False)
264
 
265
+ with gr.Row(elem_classes="input-container"):
266
+ msg_input = gr.Textbox(
267
+ placeholder="✨ Gõ tin nhắn của bạn vào đây...",
268
+ show_label=False,
269
+ scale=8
270
  )
271
+ send_btn = gr.Button("GỬI", scale=1)
272
 
273
+ # EVENTS
274
  hamburger.click(toggle_sidebar, outputs=sidebar)
275
 
 
276
  login_btn.click(
277
  handle_login,
278
+ outputs=[user_info, current_user]
279
  ).then(
280
+ get_history,
281
+ inputs=[store, current_user],
282
+ outputs=history_list
283
  )
284
 
285
+ new_chat.click(
 
286
  lambda: ([], str(uuid.uuid4())),
287
  outputs=[chatbot, session_id]
288
  )
289
 
290
+ def send_message(msg, history, sid, store, user):
291
+ return chat_fn(msg, history, sid, store, user)
292
+
293
  msg_input.submit(
294
+ send_message,
295
+ inputs=[msg_input, chatbot, session_id, store, current_user],
296
+ outputs=[msg_input, chatbot, session_id, store, tts_buffer]
297
+ ).then(tts_fn, tts_buffer, audio_player)
298
+
299
+ send_btn.click(
300
+ send_message,
301
+ inputs=[msg_input, chatbot, session_id, store, current_user],
302
+ outputs=[msg_input, chatbot, session_id, store, tts_buffer]
303
+ ).then(tts_fn, tts_buffer, audio_player)
304
 
305
  if __name__ == "__main__":
306
  demo.launch(
307
+ share=True,
308
+ server_name="0.0.0.0",
309
+ server_port=7860,
310
+ theme=gr.themes.Base(),
311
+ css=custom_css,
312
+ auth=None # Tắt auth mặc định để LoginButton hoạt động
313
  )