lamgamepro1 commited on
Commit
f33a707
·
verified ·
1 Parent(s): 5ea04b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +585 -67
app.py CHANGED
@@ -1,96 +1,614 @@
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
 
3
  import os
 
 
 
 
 
 
 
 
4
 
5
- # 1. CẤU HÌNH AI
 
 
 
 
 
 
 
6
  HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN")
7
- client = InferenceClient(model="Qwen/Qwen2.5-7B-Instruct", token=HF_TOKEN)
8
 
9
- # 2. GIAO DIỆN CSS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  custom_css = """
 
 
11
  footer, .built-with, header, #header { display: none !important; }
12
- .gradio-container { max-width: 100% !important; padding: 0 !important; }
13
- .sidebar-node { background: rgba(15, 23, 42, 0.9) !important; border-right: 1px solid #38bdf833; padding: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  .logo-area {
15
- font-family: 'Orbitron', sans-serif;
16
- background: linear-gradient(90deg, #38bdf8, #818cf8);
17
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
18
- font-size: 22px; text-align: center; font-weight: bold; margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
- .custom-warning { text-align: center; color: #94a3b8; font-size: 12px; margin: 10px 0; font-style: italic; }
21
- """
22
 
23
- js_cleaner = """
24
- <img src="x" onerror="
25
- const observer = new MutationObserver(() => {
26
- ['footer', '.built-with', 'header', '#header'].forEach(s => {
27
- const el = document.querySelector(s); if (el) el.remove();
28
- });
29
- });
30
- observer.observe(document.body, { childList: true, subtree: true });
31
- " style="display:none;">
32
  """
33
 
34
- # 3. CHAT ENGINE (Sử dụng định dạng {'role': '...', 'content': '...'})
35
- def chat_engine(message, history, profile: gr.OAuthProfile | None):
36
- text = message.get("text", "").strip()
37
- if not text:
38
- yield gr.update(value={"text": "", "files": []}), history
39
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- user_name = profile.name if profile else "Master"
 
 
 
 
 
 
42
 
43
- # Tạo danh sách tin nhắn gửi đi cho AI (Bao gồm System Prompt)
44
- messages = [{"role": "system", "content": f"Bạn là NodeAI Genesis. Master: {user_name}."}]
 
 
 
45
 
46
- # history bây giờ là danh sách các dict, chỉ cần cộng vào là xong
47
- messages.extend(history)
 
 
 
 
 
 
 
 
 
 
 
 
48
  messages.append({"role": "user", "content": text})
49
 
50
- # Cập nhật history cho chatbot hiển thị
51
  new_history = history + [{"role": "user", "content": text}]
52
  ans = ""
53
 
54
- # Gửi trạng thái đang chờ
55
- yield gr.update(value={"text": "", "files": []}), new_history + [{"role": "assistant", "content": "..."}]
 
 
 
 
 
56
 
57
  try:
58
- for token in client.chat_completion(messages=messages, max_tokens=2048, stream=True):
 
 
 
 
 
 
 
59
  if token.choices[0].delta.content:
60
  ans += token.choices[0].delta.content
61
- # Gửi history kèm tin nhắn assistant đang được cập nhật
62
- yield gr.update(), new_history + [{"role": "assistant", "content": ans}]
63
- except Exception as e:
64
- yield gr.update(), new_history + [{"role": "assistant", "content": f"Lỗi: {str(e)}"}]
65
-
66
- # 4. GIAO DIỆN UI
67
- with gr.Blocks() as demo:
68
- gr.HTML(js_cleaner, visible=False)
69
-
70
- with gr.Row():
71
- with gr.Column(scale=1, elem_classes="sidebar-node"):
72
- gr.HTML("<div class='logo-area'>NODEAI GENESIS</div>")
73
- gr.LoginButton(value="Đăng nhập", size="sm")
74
- new_chat_btn = gr.Button("+ CLEAR MEMORY", variant="stop")
75
- gr.Markdown("---")
76
- gr.Markdown("💡 **Status**: Script Memory (Messages Format)")
77
-
78
- with gr.Column(scale=4):
79
- # Không đặt type="messages" nữa vì bản này tự hiểu khi nhận list dict
80
- chatbot = gr.Chatbot(show_label=False, height="78vh")
81
-
82
- gr.HTML("<div class='custom-warning'>NodeAI không thể chắc chắn 100%, hãy rà soát lại khi bạn có chuyện gì quan trọng!</div>")
83
-
84
- msg_input = gr.MultimodalTextbox(
85
- show_label=False,
86
- placeholder="Nhập nội dung Master cần hỏi...",
87
- container=False
88
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- # Xử lý sự kiện
91
- msg_input.submit(chat_engine, [msg_input, chatbot], [msg_input, chatbot])
92
- new_chat_btn.click(lambda: [], None, chatbot) # Trả về mảng rỗng để xóa chat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- # 5. LAUNCH
 
 
95
  if __name__ == "__main__":
96
- demo.queue().launch(css=custom_css)
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
3
+ from supabase import create_client, Client
4
  import os
5
+ import uuid
6
+ import json
7
+ from datetime import datetime, timedelta
8
+ from typing import Optional, List, Dict, Tuple, Any
9
+ import hashlib
10
+ import re
11
+ from functools import lru_cache
12
+ import logging
13
 
14
+ # ============================================================================
15
+ # 1. SETUP LOGGING & CONFIGURATION
16
+ # ============================================================================
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ URL = os.environ.get("SUPABASE_URL")
21
+ KEY = os.environ.get("SUPABASE_KEY")
22
  HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN")
 
23
 
24
+ # Initialize Supabase
25
+ try:
26
+ supabase: Client = create_client(URL, KEY) if URL and KEY else None
27
+ logger.info("✅ Supabase connected")
28
+ except Exception as e:
29
+ logger.error(f"❌ Supabase error: {e}")
30
+ supabase = None
31
+
32
+ # ============================================================================
33
+ # 2. MODEL CONFIGURATION & CLIENTS
34
+ # ============================================================================
35
+ AVAILABLE_MODELS = {
36
+ "🚀 Qwen 2.5 (Balanced)": "Qwen/Qwen2.5-7B-Instruct",
37
+ "⚡ Mistral (Fast)": "mistralai/Mistral-7B-Instruct-v0.2",
38
+ "🔥 LLaMA 2 (Powerful)": "meta-llama/Llama-2-7b-chat-hf",
39
+ "🎯 Qwen 72B (Pro)": "Qwen/Qwen2.5-72B-Instruct",
40
+ }
41
+
42
+ DEFAULT_MODEL = "Qwen/Qwen2.5-7B-Instruct"
43
+
44
+ clients = {}
45
+ for name, model_id in AVAILABLE_MODELS.items():
46
+ try:
47
+ clients[name] = InferenceClient(model=model_id, token=HF_TOKEN)
48
+ except:
49
+ logger.warning(f"Could not load {model_id}")
50
+
51
+ # ============================================================================
52
+ # 3. ADVANCED CSS & STYLING (Dark Mode + Animations)
53
+ # ============================================================================
54
  custom_css = """
55
+ * { transition: all 0.3s ease; }
56
+
57
  footer, .built-with, header, #header { display: none !important; }
58
+
59
+ .gradio-container {
60
+ max-width: 100% !important;
61
+ padding: 0 !important;
62
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
63
+ min-height: 100vh;
64
+ color: #e2e8f0;
65
+ }
66
+
67
+ body { background: #0f172a !important; }
68
+
69
+ /* Sidebar */
70
+ .sidebar-node {
71
+ background: rgba(15, 23, 42, 0.95) !important;
72
+ border-right: 1px solid rgba(56, 189, 248, 0.2);
73
+ padding: 24px;
74
+ max-height: 100vh;
75
+ overflow-y: auto;
76
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.3);
77
+ }
78
+
79
  .logo-area {
80
+ font-family: 'Courier New', monospace;
81
+ background: linear-gradient(135deg, #38bdf8, #8b5cf6, #ec4899);
82
+ -webkit-background-clip: text;
83
+ -webkit-text-fill-color: transparent;
84
+ background-clip: text;
85
+ font-size: 24px;
86
+ text-align: center;
87
+ font-weight: bold;
88
+ margin-bottom: 24px;
89
+ letter-spacing: 2px;
90
+ text-shadow: 0 0 20px rgba(56, 189, 248, 0.3);
91
+ }
92
+
93
+ /* Buttons */
94
+ .btn-primary {
95
+ background: linear-gradient(135deg, #38bdf8, #8b5cf6) !important;
96
+ border: none !important;
97
+ color: white !important;
98
+ font-weight: 600 !important;
99
+ padding: 12px 20px !important;
100
+ border-radius: 8px !important;
101
+ cursor: pointer !important;
102
+ box-shadow: 0 4px 15px rgba(56, 189, 248, 0.3) !important;
103
+ }
104
+
105
+ .btn-primary:hover {
106
+ transform: translateY(-2px) !important;
107
+ box-shadow: 0 6px 20px rgba(56, 189, 248, 0.5) !important;
108
+ }
109
+
110
+ /* Chatbot */
111
+ .gradio-chatbot {
112
+ background: rgba(30, 41, 59, 0.8) !important;
113
+ border: 1px solid rgba(56, 189, 248, 0.1) !important;
114
+ border-radius: 12px !important;
115
+ }
116
+
117
+ .message-input {
118
+ background: rgba(30, 41, 59, 0.9) !important;
119
+ border: 1px solid rgba(56, 189, 248, 0.2) !important;
120
+ border-radius: 8px !important;
121
+ color: #e2e8f0 !important;
122
+ }
123
+
124
+ /* Datasets & Lists */
125
+ .gradio-dataset {
126
+ background: rgba(30, 41, 59, 0.7) !important;
127
+ border: 1px solid rgba(56, 189, 248, 0.1) !important;
128
+ border-radius: 8px !important;
129
+ }
130
+
131
+ .dataset-item {
132
+ background: rgba(15, 23, 42, 0.6) !important;
133
+ border-left: 3px solid #38bdf8 !important;
134
+ padding: 12px !important;
135
+ margin: 8px 0 !important;
136
+ cursor: pointer !important;
137
+ border-radius: 6px !important;
138
+ }
139
+
140
+ .dataset-item:hover {
141
+ background: rgba(56, 189, 248, 0.1) !important;
142
+ transform: translateX(4px) !important;
143
+ }
144
+
145
+ /* Text Elements */
146
+ .custom-warning {
147
+ text-align: center;
148
+ color: #94a3b8;
149
+ font-size: 12px;
150
+ margin: 15px 0;
151
+ font-style: italic;
152
+ padding: 12px;
153
+ background: rgba(56, 189, 248, 0.05);
154
+ border-radius: 6px;
155
+ border-left: 3px solid #38bdf8;
156
+ }
157
+
158
+ .stat-card {
159
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.1), rgba(139, 92, 246, 0.1));
160
+ border: 1px solid rgba(56, 189, 248, 0.2);
161
+ border-radius: 8px;
162
+ padding: 16px;
163
+ margin: 8px 0;
164
+ color: #e2e8f0;
165
+ }
166
+
167
+ /* Tabs & Accordion */
168
+ .gradio-tabs { background: transparent !important; }
169
+ .gradio-tabitem { background: rgba(30, 41, 59, 0.8) !important; }
170
+
171
+ /* Scrollbar */
172
+ ::-webkit-scrollbar { width: 8px; }
173
+ ::-webkit-scrollbar-track { background: rgba(56, 189, 248, 0.1); }
174
+ ::-webkit-scrollbar-thumb { background: #38bdf8; border-radius: 4px; }
175
+
176
+ /* Loading Animation */
177
+ @keyframes pulse-glow {
178
+ 0%, 100% { text-shadow: 0 0 10px rgba(56, 189, 248, 0.5); }
179
+ 50% { text-shadow: 0 0 20px rgba(139, 92, 246, 0.8); }
180
  }
 
 
181
 
182
+ .loading { animation: pulse-glow 1.5s ease-in-out infinite; }
 
 
 
 
 
 
 
 
183
  """
184
 
185
+ # ============================================================================
186
+ # 4. UTILITY FUNCTIONS
187
+ # ============================================================================
188
+ class ChatManager:
189
+ """Quản lý lịch sử chat, caching, và analytics"""
190
+
191
+ def __init__(self):
192
+ self.cache = {}
193
+ self.rate_limit = {}
194
+
195
+ def get_cache_key(self, username: str) -> str:
196
+ return f"user_{username}_cache"
197
+
198
+ def is_rate_limited(self, username: str, limit: int = 30, window: int = 60) -> bool:
199
+ """Check rate limiting (30 requests per 60 seconds)"""
200
+ key = f"rate_{username}"
201
+ now = datetime.now()
202
+
203
+ if key not in self.rate_limit:
204
+ self.rate_limit[key] = []
205
+
206
+ # Remove old timestamps
207
+ self.rate_limit[key] = [ts for ts in self.rate_limit[key]
208
+ if (now - ts).seconds < window]
209
+
210
+ if len(self.rate_limit[key]) >= limit:
211
+ return True
212
+
213
+ self.rate_limit[key].append(now)
214
+ return False
215
+
216
+ def generate_session_title(self, text: str, max_length: int = 50) -> str:
217
+ """Generate meaningful session title from first message"""
218
+ clean_text = re.sub(r'[^a-zA-Z0-9\s]', '', text)
219
+ words = clean_text.split()[:8]
220
+ title = ' '.join(words) or "New Chat"
221
+ return title[:max_length]
222
+
223
+ chat_manager = ChatManager()
224
+
225
+ # ============================================================================
226
+ # 5. DATABASE OPERATIONS (Enhanced)
227
+ # ============================================================================
228
+ def get_db_history(profile: gr.OAuthProfile | None) -> Tuple[List[Dict], gr.Dataset]:
229
+ """Get chat history filtered by username"""
230
+ if not supabase or not profile:
231
+ return [], gr.Dataset(samples=[])
232
+
233
+ try:
234
+ username = profile.username
235
+ res = supabase.table("chats").select("*").eq("username", username).order("last_updated", desc=True).limit(50).execute()
236
+
237
+ data = res.data if res.data else []
238
+
239
+ # Format for dataset display
240
+ samples = [
241
+ [f"💬 {item.get('title', 'Untitled')[:40]}",
242
+ f"{item.get('message_count', 0)} msgs",
243
+ item.get('created_at', '').split('T')[0]]
244
+ for item in data
245
+ ]
246
+
247
+ logger.info(f"✅ Loaded {len(data)} chats for {username}")
248
+ return data, gr.Dataset(samples=samples)
249
+
250
+ except Exception as e:
251
+ logger.error(f"❌ DB error: {e}")
252
+ return [], gr.Dataset(samples=[])
253
+
254
+ def load_selected_chat(evt: gr.SelectData, db_data: List[Dict]) -> Tuple[List[Dict], str]:
255
+ """Load selected chat session"""
256
+ try:
257
+ selected_item = db_data[evt.index]
258
+ history = selected_item.get('history', [])
259
+ session_id = selected_item.get('session_id')
260
+ logger.info(f"📂 Loaded session: {session_id}")
261
+ return history, session_id
262
+ except Exception as e:
263
+ logger.error(f"Error loading chat: {e}")
264
+ return [], str(uuid.uuid4())
265
+
266
+ def save_chat_to_db(username: str, session_id: str, title: str,
267
+ history: List[Dict], model_used: str) -> bool:
268
+ """Save or update chat in database"""
269
+ if not supabase or not username:
270
+ return False
271
+
272
+ try:
273
+ message_count = len([m for m in history if m.get('role') == 'user'])
274
+
275
+ data = {
276
+ "username": username,
277
+ "session_id": session_id,
278
+ "title": title,
279
+ "history": history,
280
+ "message_count": message_count,
281
+ "model_used": model_used,
282
+ "last_updated": datetime.utcnow().isoformat(),
283
+ "created_at": datetime.utcnow().isoformat(),
284
+ }
285
+
286
+ supabase.table("chats").upsert(data, on_conflict="session_id").execute()
287
+ logger.info(f"💾 Chat saved: {session_id}")
288
+ return True
289
+ except Exception as e:
290
+ logger.error(f"❌ Save error: {e}")
291
+ return False
292
+
293
+ # ============================================================================
294
+ # 6. SYSTEM PROMPTS & TEMPLATES
295
+ # ============================================================================
296
+ SYSTEM_PROMPTS = {
297
+ "🤖 Default": "You are NodeAI Genesis, an advanced AI assistant. Be helpful, creative, and concise.",
298
+ "📚 Researcher": "You are an expert researcher. Provide detailed, well-structured analysis with sources.",
299
+ "💻 Developer": "You are an expert programmer. Provide clean, optimized code with explanations.",
300
+ "📝 Writer": "You are a professional writer. Create engaging, well-written content.",
301
+ "🎯 Coach": "You are a personal mentor. Provide motivational, actionable advice.",
302
+ }
303
 
304
+ # ============================================================================
305
+ # 7. ADVANCED CHAT ENGINE
306
+ # ============================================================================
307
+ def chat_engine(message: Dict, history: List[Dict], session_id: str,
308
+ selected_model: str, system_prompt: str,
309
+ profile: gr.OAuthProfile | None) -> Tuple:
310
+ """Advanced chat engine with streaming, caching, and analytics"""
311
 
312
+ text = message.get("text", "").strip() if isinstance(message, dict) else str(message).strip()
313
+
314
+ if not text:
315
+ yield gr.update(value={"text": "", "files": []}), history, session_id, "⚠️ Empty message"
316
+ return
317
 
318
+ # Rate limiting check
319
+ username = profile.username if profile else "guest"
320
+ if chat_manager.is_rate_limited(username):
321
+ yield gr.update(value={"text": "", "files": []}), history, session_id, "⏱️ Rate limited. Wait a moment."
322
+ return
323
+
324
+ # Get the selected client
325
+ client = clients.get(selected_model)
326
+ if not client:
327
+ client = InferenceClient(model=DEFAULT_MODEL, token=HF_TOKEN)
328
+
329
+ # Build message context
330
+ messages = [{"role": "system", "content": system_prompt}]
331
+ messages.extend(history[-10:]) # Keep last 10 messages for context
332
  messages.append({"role": "user", "content": text})
333
 
 
334
  new_history = history + [{"role": "user", "content": text}]
335
  ans = ""
336
 
337
+ # Show loading state
338
+ yield (
339
+ gr.update(value={"text": "", "files": []}, interactive=False),
340
+ new_history + [{"role": "assistant", "content": "⏳ Thinking..."}],
341
+ session_id,
342
+ "🔄 Processing..."
343
+ )
344
 
345
  try:
346
+ # Stream response
347
+ for token in client.chat_completion(
348
+ messages=messages,
349
+ max_tokens=2048,
350
+ temperature=0.7,
351
+ top_p=0.95,
352
+ stream=True
353
+ ):
354
  if token.choices[0].delta.content:
355
  ans += token.choices[0].delta.content
356
+ yield (
357
+ gr.update(value={"text": "", "files": []}, interactive=True),
358
+ new_history + [{"role": "assistant", "content": ans}],
359
+ session_id,
360
+ f"✅ Complete ({len(ans)} chars)"
361
+ )
362
+
363
+ # Save to database
364
+ final_history = new_history + [{"role": "assistant", "content": ans}]
365
+ session_title = chat_manager.generate_session_title(text)
366
+
367
+ if profile:
368
+ save_chat_to_db(
369
+ profile.username,
370
+ session_id,
371
+ session_title,
372
+ final_history,
373
+ selected_model
 
 
 
 
 
 
 
 
 
374
  )
375
+
376
+ yield (
377
+ gr.update(value={"text": "", "files": []}, interactive=True),
378
+ final_history,
379
+ session_id,
380
+ "✅ Saved to database"
381
+ )
382
+
383
+ except Exception as e:
384
+ error_msg = f"❌ Error: {str(e)[:100]}"
385
+ logger.error(error_msg)
386
+ yield (
387
+ gr.update(value={"text": "", "files": []}, interactive=True),
388
+ new_history + [{"role": "assistant", "content": f"Error occurred: {str(e)}"}],
389
+ session_id,
390
+ error_msg
391
+ )
392
 
393
+ # ============================================================================
394
+ # 8. ANALYTICS & STATS
395
+ # ============================================================================
396
+ def get_user_stats(profile: gr.OAuthProfile | None) -> str:
397
+ """Get user statistics"""
398
+ if not supabase or not profile:
399
+ return "📊 No data"
400
+
401
+ try:
402
+ res = supabase.table("chats").select("id,message_count").eq("username", profile.username).execute()
403
+ data = res.data if res.data else []
404
+
405
+ total_chats = len(data)
406
+ total_messages = sum(d.get('message_count', 0) for d in data)
407
+
408
+ return f"""
409
+ 📊 **Your Statistics**
410
+ - Total Chats: **{total_chats}**
411
+ - Total Messages: **{total_messages}**
412
+ - Avg per Chat: **{total_messages // max(total_chats, 1)}**
413
+ """
414
+ except:
415
+ return "📊 Unable to load stats"
416
+
417
+ def clear_chat_history(session_id: str, profile: gr.OAuthProfile | None) -> Tuple[List, str]:
418
+ """Clear current session"""
419
+ if supabase and profile and session_id:
420
+ try:
421
+ supabase.table("chats").delete().eq("session_id", session_id).execute()
422
+ logger.info(f"🗑️ Cleared session: {session_id}")
423
+ except Exception as e:
424
+ logger.error(f"Error clearing: {e}")
425
+
426
+ return [], str(uuid.uuid4())
427
+
428
+ # ============================================================================
429
+ # 9. UI INTERFACE (Gradio)
430
+ # ============================================================================
431
+ def create_interface():
432
+ with gr.Blocks(theme=gr.themes.Slate()) as demo:
433
+
434
+ # States
435
+ session_id = gr.State(str(uuid.uuid4()))
436
+ db_state = gr.State([])
437
+ status_message = gr.State("Ready")
438
+
439
+ with gr.Row(equal_height=False):
440
+ # ===== SIDEBAR =====
441
+ with gr.Column(scale=1, elem_classes="sidebar-node"):
442
+ gr.HTML("<div class='logo-area'>⚡ NODEAI</div>")
443
+
444
+ # Auth Button
445
+ profile = gr.LoginButton(
446
+ value="🔐 Login with HF",
447
+ size="sm",
448
+ scale=1
449
+ )
450
+
451
+ # Action Buttons
452
+ with gr.Row(scale=1):
453
+ new_chat_btn = gr.Button("➕ New Chat", variant="primary", scale=1)
454
+ delete_chat_btn = gr.Button("🗑️ Delete", scale=1)
455
+
456
+ # Model & System Prompt Selection
457
+ gr.Markdown("### ⚙️ Settings")
458
+ selected_model = gr.Dropdown(
459
+ choices=list(AVAILABLE_MODELS.keys()),
460
+ value=list(AVAILABLE_MODELS.keys())[0],
461
+ label="🤖 AI Model",
462
+ interactive=True
463
+ )
464
+
465
+ system_prompt = gr.Dropdown(
466
+ choices=list(SYSTEM_PROMPTS.keys()),
467
+ value=list(SYSTEM_PROMPTS.keys())[0],
468
+ label="📝 Personality",
469
+ interactive=True
470
+ )
471
+
472
+ # History Section
473
+ gr.Markdown("### 📚 Chat History")
474
+ history_list = gr.Dataset(
475
+ components=[
476
+ gr.Textbox(visible=False),
477
+ gr.Textbox(visible=False),
478
+ gr.Textbox(visible=False),
479
+ ],
480
+ samples=[],
481
+ type="index",
482
+ label="Your Conversations"
483
+ )
484
+
485
+ # Statistics
486
+ gr.Markdown("### 📊 Analytics")
487
+ stats_display = gr.Markdown("📊 Loading stats...")
488
+
489
+ # Warning
490
+ gr.HTML("<div class='custom-warning'>🔒 Data saved securely with your HF account</div>")
491
+
492
+ # ===== MAIN CHAT AREA =====
493
+ with gr.Column(scale=4):
494
+ # Header
495
+ gr.HTML("""
496
+ <div style='text-align: center; margin-bottom: 20px;'>
497
+ <h1 style='color: #38bdf8; margin: 0;'>🚀 NodeAI Genesis Pro</h1>
498
+ <p style='color: #94a3b8; margin: 5px 0;'>Advanced AI Chat with Multi-Model Support</p>
499
+ </div>
500
+ """)
501
+
502
+ # Chatbot
503
+ chatbot = gr.Chatbot(
504
+ show_label=False,
505
+ height="70vh",
506
+ bubble_full_width=False,
507
+ scale=1
508
+ )
509
+
510
+ # Status & Info
511
+ with gr.Row(scale=1):
512
+ status_display = gr.Textbox(
513
+ value="✅ Ready",
514
+ label="Status",
515
+ interactive=False,
516
+ scale=1
517
+ )
518
+ model_info = gr.Textbox(
519
+ value=list(AVAILABLE_MODELS.keys())[0],
520
+ label="Current Model",
521
+ interactive=False,
522
+ scale=1
523
+ )
524
+
525
+ # Input Area
526
+ with gr.Row(scale=1):
527
+ msg_input = gr.MultimodalTextbox(
528
+ show_label=False,
529
+ placeholder="💬 Ask me anything... (Ctrl+Enter to send)",
530
+ container=False,
531
+ scale=1
532
+ )
533
+
534
+ # ===== EVENT HANDLERS =====
535
+
536
+ # Load history on page load
537
+ demo.load(
538
+ fn=lambda profile: get_db_history(profile),
539
+ inputs=[profile],
540
+ outputs=[db_state, history_list]
541
+ ).then(
542
+ fn=lambda profile: get_user_stats(profile),
543
+ inputs=[profile],
544
+ outputs=[stats_display]
545
+ )
546
+
547
+ # Select chat from history
548
+ history_list.select(
549
+ fn=lambda evt, db_data: load_selected_chat(evt, db_data),
550
+ inputs=[history_list, db_state],
551
+ outputs=[chatbot, session_id]
552
+ )
553
+
554
+ # Send message
555
+ msg_input.submit(
556
+ fn=chat_engine,
557
+ inputs=[
558
+ msg_input,
559
+ chatbot,
560
+ session_id,
561
+ selected_model,
562
+ system_prompt,
563
+ profile
564
+ ],
565
+ outputs=[msg_input, chatbot, session_id, status_display]
566
+ ).then(
567
+ fn=lambda profile: get_db_history(profile),
568
+ inputs=[profile],
569
+ outputs=[db_state, history_list]
570
+ ).then(
571
+ fn=lambda profile: get_user_stats(profile),
572
+ inputs=[profile],
573
+ outputs=[stats_display]
574
+ )
575
+
576
+ # Update model info display
577
+ selected_model.change(
578
+ fn=lambda model: gr.update(value=model),
579
+ inputs=[selected_model],
580
+ outputs=[model_info]
581
+ )
582
+
583
+ # New chat button
584
+ new_chat_btn.click(
585
+ fn=lambda: ([], str(uuid.uuid4()), "✅ Ready"),
586
+ outputs=[chatbot, session_id, status_display]
587
+ )
588
+
589
+ # Delete chat button
590
+ delete_chat_btn.click(
591
+ fn=lambda session_id, profile: clear_chat_history(session_id, profile),
592
+ inputs=[session_id, profile],
593
+ outputs=[chatbot, session_id]
594
+ ).then(
595
+ fn=lambda profile: get_db_history(profile),
596
+ inputs=[profile],
597
+ outputs=[db_state, history_list]
598
+ )
599
+
600
+ return demo
601
 
602
+ # ============================================================================
603
+ # 10. MAIN EXECUTION
604
+ # ============================================================================
605
  if __name__ == "__main__":
606
+ demo = create_interface()
607
+ demo.queue(max_size=20, default_concurrency_limit=3).launch(
608
+ css=custom_css,
609
+ share=False,
610
+ server_name="0.0.0.0",
611
+ server_port=7860,
612
+ show_error=True,
613
+ analytics_enabled=False
614
+ )