lamgamepro1 commited on
Commit
48b048c
·
verified ·
1 Parent(s): 5ae4eec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +218 -143
app.py CHANGED
@@ -3,6 +3,8 @@ from huggingface_hub import InferenceClient
3
  import os
4
  import uuid
5
  import json
 
 
6
 
7
  # Import an toàn
8
  try: from duckduckgo_search import DDGS
@@ -15,7 +17,12 @@ 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 TRÍ NHỚ (MEMORY CACHE)
 
 
 
 
 
19
  def load_db():
20
  if os.path.exists(DB_FILE):
21
  try:
@@ -27,140 +34,153 @@ def save_db(data):
27
  with open(DB_FILE, "w", encoding="utf-8") as f:
28
  json.dump(data, f, ensure_ascii=False, indent=4)
29
 
30
- # 3. GIAO DIỆN SIÊU ĐẸP (GLASSMORPHISM & ORANGE GLOW)
31
  custom_css = """
32
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@800&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap');
33
 
34
- /* Nền vũ trụ tối */
 
 
35
  .gradio-container {
36
- background: radial-gradient(circle at 50% 0%, #151522 0%, #050508 100%) !important;
37
- font-family: 'Plus Jakarta Sans', sans-serif !important;
38
- color: #e2e8f0 !important;
39
  overflow-x: hidden !important;
40
  }
41
 
42
- /* Sidebar Kính mờ (Glassmorphism) */
43
- .sidebar-node {
44
- position: fixed !important;
45
- left: 0 !important; top: 0 !important;
46
- width: 280px !important; height: 100vh !important;
47
- background: rgba(15, 15, 20, 0.4) !important;
48
- backdrop-filter: blur(20px) !important;
49
- -webkit-backdrop-filter: blur(20px) !important;
50
- border-right: 1px solid rgba(255, 255, 255, 0.05) !important;
51
- z-index: 1000 !important;
52
- padding: 25px 20px !important;
53
- box-shadow: 5px 0 30px rgba(0,0,0,0.5) !important;
54
  }
 
55
 
56
- /* Logo Cyberpunk */
 
 
 
 
 
 
 
 
 
 
 
57
  .logo-text {
58
- font-family: 'Orbitron', sans-serif !important;
59
- background: linear-gradient(90deg, #00f2fe, #4facfe, #00f2fe);
60
- background-size: 200% auto;
61
- -webkit-background-clip: text;
62
- -webkit-text-fill-color: transparent;
63
- font-size: 26px; text-align: center; margin-bottom: 30px;
64
- animation: shine 3s linear infinite;
 
 
 
65
  }
66
- @keyframes shine { to { background-position: 200% center; } }
67
 
68
- /* Nút New Workspace Màu Cam Rực Rỡ */
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  .orange-btn {
70
- background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%) !important;
71
- border: none !important; color: white !important; font-weight: 600 !important;
72
- border-radius: 12px !important; padding: 10px !important;
73
- transition: all 0.3s ease !important;
74
- box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3) !important;
75
- margin-bottom: 20px !important;
 
76
  }
77
- .orange-btn:hover {
78
- transform: translateY(-2px) !important;
79
- box-shadow: 0 6px 20px rgba(255, 107, 107, 0.5) !important;
80
  }
81
 
82
- /* Nút Lịch sử Chat */
83
- .hist-btn {
84
- background: transparent !important; border: 1px solid transparent !important;
85
- color: #a0aec0 !important; text-align: left !important;
86
- border-radius: 8px !important; transition: 0.2s !important;
87
  }
88
- .hist-btn:hover {
89
- background: rgba(255, 255, 255, 0.05) !important;
90
- border: 1px solid rgba(255, 255, 255, 0.1) !important;
91
- color: #fff !important;
 
 
92
  }
93
 
94
- /* Khu vực Chat Chính */
95
- .chat-main {
96
- margin-left: 280px !important;
97
- width: calc(100% - 280px) !important;
98
- min-height: 100vh !important;
99
- padding: 20px 0 !important;
100
- display: flex !important;
101
- flex-direction: column !important;
102
- align-items: center !important;
103
- }
104
-
105
- /* Chatbot Box */
106
- .gr-chatbot {
107
- background: transparent !important;
108
- border: none !important;
109
- width: 100% !important;
110
- max-width: 900px !important;
111
- height: 78vh !important;
112
- margin-bottom: 100px !important;
113
- }
114
-
115
- /* Thanh Nhập Liệu Cố Định Chống Tràn */
116
- .input-fixed-bottom {
117
- position: fixed !important;
118
- bottom: 30px !important;
119
- left: 50% !important;
120
- transform: translateX(calc(-50% + 140px)) !important; /* Cân bằng với sidebar */
121
- width: 100% !important;
122
- max-width: 850px !important;
123
- background: rgba(20, 20, 28, 0.8) !important;
124
- backdrop-filter: blur(15px) !important;
125
- border: 1px solid rgba(0, 242, 254, 0.2) !important;
126
- border-radius: 20px !important;
127
- padding: 8px 15px !important;
128
- z-index: 2000 !important;
129
- box-shadow: 0 10px 40px rgba(0,0,0,0.5) !important;
130
- transition: 0.3s !important;
131
- }
132
- .input-fixed-bottom:focus-within {
133
- border-color: rgba(0, 242, 254, 0.6) !important;
134
- box-shadow: 0 10px 40px rgba(0, 242, 254, 0.15) !important;
135
- }
136
-
137
- /* Ẩn các thành phần thừa */
138
  footer, .gr-button-secondary { display: none !important; }
139
 
140
- /* Responsive Mobile */
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  @media screen and (max-width: 768px) {
142
- .sidebar-node { display: none !important; }
143
- .chat-main { margin-left: 0 !important; width: 100% !important; }
144
- .input-fixed-bottom {
145
- transform: translateX(-50%) !important;
146
- width: 90% !important;
147
- bottom: 15px !important;
148
- }
149
  }
150
  """
151
 
152
- # 4. CORE LOGIC CHAT
153
- def chat_fn(message, history, session_id, store):
154
  user_text = message.get("text", "")
155
  if not user_text and not message.get("files", []):
156
  yield {"text": ""}, history, session_id, store, ""
157
  return
158
 
159
- if session_id not in store:
160
- store[session_id] = {"title": user_text[:25] if user_text else "Nova Workspace", "history": []}
 
161
 
162
- api_messages = [{"role": "system", "content": "Bạn là NodeAI Genesis. Master: Trần Đình Phúc."}]
163
- for msg in history: api_messages.append({"role": msg["role"], "content": msg["content"]})
 
 
 
 
 
 
 
 
164
  api_messages.append({"role": "user", "content": user_text})
165
 
166
  try:
@@ -169,17 +189,16 @@ def chat_fn(message, history, session_id, store):
169
  for token in client.chat_completion(messages=api_messages, max_tokens=2000, stream=True):
170
  if token.choices[0].delta.content:
171
  ans += token.choices[0].delta.content
172
- # Yield text rỗng cho tts_buffer trong lúc stream để tránh lỗi
173
  yield {"text": ""}, new_h + [{"role": "assistant", "content": ans}], session_id, store, ""
174
 
175
  final_h = new_h + [{"role": "assistant", "content": ans}]
176
- store[session_id]["history"] = final_h
 
177
  save_db(store)
178
 
179
- # Chỉ khi hoàn thành mới gửi ans vào tts_buffer
180
  yield {"text": ""}, final_h, session_id, store, ans
181
  except Exception as e:
182
- yield {"text": ""}, history + [{"role": "assistant", "content": str(e)}], session_id, store, ""
183
 
184
  def tts_engine(text):
185
  if not text or len(text) < 2 or not gTTS: return None
@@ -189,48 +208,104 @@ def tts_engine(text):
189
  return "voice.mp3"
190
  except: return None
191
 
192
- # 5. DỰNG GIAO DIỆN
193
- with gr.Blocks(title="NodeAI Genesis", theme=gr.themes.Base()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  chat_store = gr.State(load_db())
195
  session_id = gr.State(str(uuid.uuid4()))
196
- tts_buffer = gr.Textbox(visible=False) # Chìa khóa chống lỗi Audio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
- with gr.Row():
199
- # SIDEBAR
200
- with gr.Column(elem_classes="sidebar-node"):
201
- gr.HTML("<div class='logo-text'>NODEAI GENESIS</div>")
202
- gr.LoginButton(value="Xác thực Hugging Face")
203
- new_btn = gr.Button("+ TẠO KHÔNG GIAN MỚI", elem_classes="orange-btn")
204
-
205
- with gr.Column():
206
- @gr.render(inputs=chat_store)
207
- def render_history(store):
208
- for sid in reversed(list(store.keys())):
209
- btn = gr.Button(f"💬 {store[sid]['title']}", elem_classes="hist-btn")
210
- btn.click(lambda s=sid: (store[s]["history"], s), None, [chatbot, session_id])
211
-
212
- # MAIN CHAT
213
- with gr.Column(elem_classes="chat-main"):
214
- chatbot = gr.Chatbot(show_label=False, avatar_images=(None, "https://cdn-icons-png.flaticon.com/512/4712/4712139.png"))
215
- audio_out = gr.Audio(autoplay=True, visible=False)
216
-
217
- with gr.Row(elem_classes="input-fixed-bottom"):
218
- msg_input = gr.MultimodalTextbox(
219
- show_label=False,
220
- placeholder="Nhập lệnh cho NodeAI...",
221
- container=False
222
- )
223
-
224
- # XỬ LÝ SỰ KIỆN KHÔNG LỖI
225
  msg_input.submit(
226
  chat_fn,
227
- [msg_input, chatbot, session_id, chat_store],
228
  [msg_input, chatbot, session_id, chat_store, tts_buffer]
229
  ).then(
230
  tts_engine, tts_buffer, audio_out
231
  )
232
-
233
- new_btn.click(lambda: ([], str(uuid.uuid4())), None, [chatbot, session_id])
234
 
235
  if __name__ == "__main__":
236
- demo.launch(css=custom_css)
 
 
 
 
 
 
3
  import os
4
  import uuid
5
  import json
6
+ import hashlib
7
+ from datetime import datetime
8
 
9
  # Import an toàn
10
  try: from duckduckgo_search import DDGS
 
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
+ """Tạo ID unique cho mỗi user dựa trên username"""
23
+ if not username: return "guest"
24
+ return hashlib.md5(username.encode()).hexdigest()[:8]
25
+
26
  def load_db():
27
  if os.path.exists(DB_FILE):
28
  try:
 
34
  with open(DB_FILE, "w", encoding="utf-8") as f:
35
  json.dump(data, f, ensure_ascii=False, indent=4)
36
 
37
+ # 3. GIAO DIỆN SIÊU ĐẸP - MOBILE FIRST
38
  custom_css = """
39
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;800&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
40
 
41
+ /* RESET & BASE */
42
+ * { box-sizing: border-box; }
43
+ body { margin: 0; font-family: 'Plus Jakarta Sans', sans-serif !important; }
44
  .gradio-container {
45
+ background: linear-gradient(135deg, #0a0a0f 0%, #1a0d1a 50%, #0f0a1a 100%) !important;
46
+ min-height: 100vh !important;
47
+ position: relative !important;
48
  overflow-x: hidden !important;
49
  }
50
 
51
+ /* MOBILE HAMBURGER MENU */
52
+ .hamburger {
53
+ position: fixed !important; top: 20px !important; left: 20px !important;
54
+ z-index: 3000 !important; background: rgba(20,20,30,0.95) !important;
55
+ border: 1px solid rgba(0,242,254,0.3) !important; border-radius: 12px !important;
56
+ width: 56px !important; height: 56px !important; cursor: pointer !important;
57
+ backdrop-filter: blur(20px) !important; transition: all 0.3s ease !important;
 
 
 
 
 
58
  }
59
+ .hamburger:hover { box-shadow: 0 8px 25px rgba(0,242,254,0.3) !important; }
60
 
61
+ /* SIDEBAR - MOBILE SLIDE IN */
62
+ .sidebar-mobile {
63
+ position: fixed !important; top: 0 !important; left: -320px !important;
64
+ width: 320px !important; height: 100vh !important; z-index: 2500 !important;
65
+ background: rgba(15,15,25,0.98) !important; backdrop-filter: blur(25px) !important;
66
+ border-right: 1px solid rgba(255,255,255,0.08) !important;
67
+ transition: left 0.4s cubic-bezier(0.25,0.46,0.45,0.94) !important;
68
+ padding: 80px 25px 25px !important; overflow-y: auto !important;
69
+ }
70
+ .sidebar-mobile.open { left: 0 !important; }
71
+
72
+ /* LOGO CYBERPUNK */
73
  .logo-text {
74
+ font-family: 'Orbitron', monospace !important; font-weight: 800 !important;
75
+ background: linear-gradient(135deg, #00f2fe 0%, #4facfe 50%, #ff6b6b 100%) !important;
76
+ background-size: 300% 300% !important; -webkit-background-clip: text !important;
77
+ -webkit-text-fill-color: transparent !important; font-size: 28px !important;
78
+ text-align: center !important; margin-bottom: 35px !important;
79
+ animation: gradientShift 4s ease infinite !important;
80
+ }
81
+ @keyframes gradientShift {
82
+ 0%, 100% { background-position: 0% 50%; }
83
+ 50% { background-position: 100% 50%; }
84
  }
 
85
 
86
+ /* USER INFO CARD */
87
+ .user-card {
88
+ background: rgba(255,107,107,0.15) !important; border: 1px solid rgba(255,107,107,0.3) !important;
89
+ border-radius: 16px !important; padding: 20px !important; margin-bottom: 25px !important;
90
+ text-align: center !important; backdrop-filter: blur(15px) !important;
91
+ }
92
+ .user-card h3 { color: #ff8e53 !important; margin: 0 0 8px 0 !important; font-size: 18px !important; }
93
+
94
+ /* BUTTONS */
95
+ .orange-btn, .hist-btn {
96
+ width: 100% !important; border-radius: 12px !important; border: none !important;
97
+ font-weight: 600 !important; cursor: pointer !important; transition: all 0.3s ease !important;
98
+ margin-bottom: 12px !important; padding: 14px 18px !important; font-size: 15px !important;
99
+ }
100
  .orange-btn {
101
+ background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 50%, #FF6B6B 100%) !important;
102
+ box-shadow: 0 6px 20px rgba(255,107,107,0.4) !important; color: white !important;
103
+ }
104
+ .orange-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 10px 30px rgba(255,107,107,0.6) !important; }
105
+ .hist-btn {
106
+ background: rgba(255,255,255,0.05) !important; color: #a0aec0 !important;
107
+ border: 1px solid rgba(255,255,255,0.1) !important; text-align: left !important;
108
  }
109
+ .hist-btn:hover, .hist-btn.active {
110
+ background: rgba(0,242,254,0.15) !important; color: #00f2fe !important;
111
+ border-color: rgba(0,242,254,0.4) !important; box-shadow: 0 4px 15px rgba(0,242,254,0.2) !important;
112
  }
113
 
114
+ /* MAIN CHAT - FULLSCREEN MOBILE */
115
+ .chat-container {
116
+ padding: 20px !important; max-width: 100% !important; margin: 0 auto !important;
117
+ min-height: 100vh !important; display: flex !important; flex-direction: column !important;
 
118
  }
119
+ .chatbot-box {
120
+ flex: 1 !important; background: rgba(20,20,30,0.6) !important;
121
+ border: 1px solid rgba(0,242,254,0.2) !important; border-radius: 24px !important;
122
+ padding: 25px !important; margin-bottom: 120px !important; max-height: 70vh !important;
123
+ overflow-y: auto !important; backdrop-filter: blur(20px) !important;
124
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;
125
  }
126
 
127
+ /* FIXED INPUT BAR */
128
+ .input-bar {
129
+ position: fixed !important; bottom: 20px !important; left: 20px !important; right: 20px !important;
130
+ z-index: 1000 !important; background: rgba(20,20,30,0.95) !important;
131
+ backdrop-filter: blur(25px) !important; border: 1px solid rgba(0,242,254,0.3) !important;
132
+ border-radius: 24px !important; padding: 12px 20px !important;
133
+ box-shadow: 0 15px 50px rgba(0,0,0,0.6) !important; transition: all 0.3s ease !important;
134
+ }
135
+ .input-bar:focus-within {
136
+ border-color: rgba(0,242,254,0.6) !important; box-shadow: 0 20px 60px rgba(0,242,254,0.2) !important;
137
+ }
138
+
139
+ /* HIDE DEFAULT ELEMENTS */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  footer, .gr-button-secondary { display: none !important; }
141
 
142
+ /* DESKTOP SIDEBAR */
143
+ @media screen and (min-width: 1024px) {
144
+ .hamburger { display: none !important; }
145
+ .sidebar-mobile { position: static !important; left: 0 !important; width: 340px !important; }
146
+ .chat-container { margin-left: 340px !important; max-width: calc(100% - 360px) !important; }
147
+ }
148
+
149
+ /* TABLET */
150
+ @media screen and (max-width: 1023px) and (min-width: 769px) {
151
+ .sidebar-mobile { width: 300px !important; }
152
+ .chat-container { margin-left: 0 !important; }
153
+ }
154
+
155
+ /* PERFECT MOBILE */
156
  @media screen and (max-width: 768px) {
157
+ .chatbot-box { margin-bottom: 140px !important; padding: 20px !important; border-radius: 20px !important; }
158
+ .input-bar { padding: 10px 16px !important; bottom: 15px !important; }
159
+ .logo-text { font-size: 24px !important; }
 
 
 
 
160
  }
161
  """
162
 
163
+ # 4. CORE LOGIC CHAT - PER USER
164
+ def chat_fn(message, history, session_id, store, username):
165
  user_text = message.get("text", "")
166
  if not user_text and not message.get("files", []):
167
  yield {"text": ""}, history, session_id, store, ""
168
  return
169
 
170
+ user_id = get_user_id(username)
171
+ if user_id not in store:
172
+ store[user_id] = {"workspaces": {}}
173
 
174
+ if session_id not in store[user_id]["workspaces"]:
175
+ store[user_id]["workspaces"][session_id] = {
176
+ "title": user_text[:30] if user_text else "Nova Workspace",
177
+ "history": [],
178
+ "created": datetime.now().isoformat()
179
+ }
180
+
181
+ api_messages = [{"role": "system", "content": f"Bạn là NodeAI Genesis. Master: Trần Đình Phúc. User: {username}"}]
182
+ for msg in history:
183
+ api_messages.append({"role": msg["role"], "content": msg["content"]})
184
  api_messages.append({"role": "user", "content": user_text})
185
 
186
  try:
 
189
  for token in client.chat_completion(messages=api_messages, max_tokens=2000, stream=True):
190
  if token.choices[0].delta.content:
191
  ans += token.choices[0].delta.content
 
192
  yield {"text": ""}, new_h + [{"role": "assistant", "content": ans}], session_id, store, ""
193
 
194
  final_h = new_h + [{"role": "assistant", "content": ans}]
195
+ store[user_id]["workspaces"][session_id]["history"] = final_h
196
+ store[user_id]["workspaces"][session_id]["title"] = user_text[:30] if user_text else store[user_id]["workspaces"][session_id]["title"]
197
  save_db(store)
198
 
 
199
  yield {"text": ""}, final_h, session_id, store, ans
200
  except Exception as e:
201
+ yield {"text": f"Lỗi: {str(e)}"}, history + [{"role": "assistant", "content": str(e)}], session_id, store, ""
202
 
203
  def tts_engine(text):
204
  if not text or len(text) < 2 or not gTTS: return None
 
208
  return "voice.mp3"
209
  except: return None
210
 
211
+ def toggle_sidebar():
212
+ return gr.update(visible=True, elem_classes="sidebar-mobile open")
213
+
214
+ def load_user_workspaces(store, username):
215
+ user_id = get_user_id(username)
216
+ if user_id in store and "workspaces" in store[user_id]:
217
+ workspaces = store[user_id]["workspaces"]
218
+ buttons = []
219
+ for sid, data in sorted(workspaces.items(), key=lambda x: x[1]["created"], reverse=True):
220
+ btn = gr.Button(
221
+ f"💬 {data['title']} ({len(data['history'])//2} tin)",
222
+ elem_classes="hist-btn"
223
+ )
224
+ buttons.append(btn)
225
+ return buttons
226
+ return []
227
+
228
+ # 5. GIAO DIỆN CHÍNH
229
+ with gr.Blocks(title="NodeAI Genesis Pro", theme=gr.themes.Base(), css=custom_css) as demo:
230
  chat_store = gr.State(load_db())
231
  session_id = gr.State(str(uuid.uuid4()))
232
+ current_user = gr.State("guest")
233
+ tts_buffer = gr.Textbox(visible=False)
234
+
235
+ # HAMBURGER MENU MOBILE
236
+ hamburger = gr.Button("☰", elem_classes="hamburger").style(size="lg")
237
+
238
+ # SIDEBAR - MOBILE SLIDE
239
+ with gr.Column(elem_classes="sidebar-mobile", visible=False) as sidebar:
240
+ gr.HTML("<div class='logo-text'>NODEAI GENESIS</div>")
241
+
242
+ with gr.Row():
243
+ gr.HTML("<h3>👤 NGƯỜI DÙNG</h3>")
244
+ user_card = gr.HTML(value="<div class='user-card'><h3>Guest</h3><p>Chưa đăng nhập</p></div>")
245
+
246
+ new_btn = gr.Button("+ TẠO KHÔNG GIAN MỚI", elem_classes="orange-btn").style(size="lg")
247
+
248
+ gr.HTML("<h3>📚 LỊCH SỬ</h3>")
249
+ history_container = gr.Column()
250
+
251
+ gr.HTML("<div style='height: 200px;'></div>") # Spacer
252
+
253
+ # MAIN CHAT
254
+ with gr.Column(elem_classes="chat-container"):
255
+ chatbot = gr.Chatbot(
256
+ show_label=False,
257
+ avatar_images=(None, "https://cdn-icons-png.flaticon.com/512/4712/4712139.png"),
258
+ elem_classes="chatbot-box"
259
+ )
260
+ audio_out = gr.Audio(autoplay=True, visible=False)
261
+
262
+ with gr.Row(elem_classes="input-bar"):
263
+ msg_input = gr.MultimodalTextbox(
264
+ show_label=False,
265
+ placeholder="✨ Nhập lệnh cho NodeAI Genesis...",
266
+ container=False
267
+ )
268
+
269
+ # EVENTS
270
+ def login_event(username):
271
+ if username:
272
+ user_id = get_user_id(username)
273
+ workspaces = load_user_workspaces(chat_store.value, username)
274
+ user_html = f"""
275
+ <div class='user-card'>
276
+ <h3>👋 Xin chào</h3>
277
+ <p style='color: #ff8e53; font-weight: 600;'>@{username}</p>
278
+ <small>ID: {user_id}</small>
279
+ </div>
280
+ """
281
+ return gr.update(value=user_html), gr.update(visible=True)
282
+ return gr.update(value="<div class='user-card'><h3>Guest</h3><p>Chưa đăng nhập</p></div>"), gr.update(visible=False)
283
+
284
+ gr.LoginButton(value="🔐 ĐĂNG NHẬP", elem_classes="orange-btn").change(
285
+ login_event, current_user, [user_card, history_container]
286
+ )
287
+
288
+ hamburger.click(toggle_sidebar, None, sidebar)
289
+ new_btn.click(
290
+ lambda: ([], str(uuid.uuid4())),
291
+ None, [chatbot, session_id]
292
+ ).then(
293
+ lambda store, user: load_user_workspaces(store, user),
294
+ [chat_store, current_user], history_container
295
+ )
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  msg_input.submit(
298
  chat_fn,
299
+ [msg_input, chatbot, session_id, chat_store, current_user],
300
  [msg_input, chatbot, session_id, chat_store, tts_buffer]
301
  ).then(
302
  tts_engine, tts_buffer, audio_out
303
  )
 
 
304
 
305
  if __name__ == "__main__":
306
+ demo.launch(
307
+ share=True,
308
+ server_name="0.0.0.0",
309
+ server_port=7860,
310
+ favicon_path="favicon.ico" # Thêm favicon nếu có
311
+ )