lamgamepro1 commited on
Commit
7d37740
·
verified ·
1 Parent(s): f33a707

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +737 -236
app.py CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
3
  from supabase import create_client, Client
@@ -10,202 +18,558 @@ 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
@@ -213,42 +577,88 @@ class ChatManager:
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]:
@@ -265,7 +675,7 @@ def load_selected_chat(evt: gr.SelectData, db_data: List[Dict]) -> Tuple[List[Di
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
 
@@ -284,71 +694,139 @@ def save_chat_to_db(username: str, session_id: str, title: str,
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:
@@ -357,15 +835,19 @@ def chat_engine(message: Dict, history: List[Dict], session_id: str,
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,
@@ -373,104 +855,88 @@ def chat_engine(message: Dict, history: List[Dict], session_id: str,
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),
@@ -479,59 +945,72 @@ def create_interface():
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(
@@ -544,7 +1023,18 @@ def create_interface():
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],
@@ -559,7 +1049,7 @@ def create_interface():
559
  chatbot,
560
  session_id,
561
  selected_model,
562
- system_prompt,
563
  profile
564
  ],
565
  outputs=[msg_input, chatbot, session_id, status_display]
@@ -573,24 +1063,29 @@ def create_interface():
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],
@@ -600,15 +1095,21 @@ def create_interface():
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
- )
 
 
 
 
 
 
 
1
+ """
2
+ ╔══════════════════════════════════════════════════════════════════════════╗
3
+ ║ NodeAI Genesis Pro Ultra v3.0 ║
4
+ ║ The World's Most Advanced Open-Source AI Chat ║
5
+ ║ Built with Gradio, Supabase, HuggingFace ║
6
+ ╚══════════════════════════════════════════════════════════════════════════╝
7
+ """
8
+
9
  import gradio as gr
10
  from huggingface_hub import InferenceClient
11
  from supabase import create_client, Client
 
18
  import re
19
  from functools import lru_cache
20
  import logging
21
+ from collections import defaultdict
22
+ import time
23
+ import asyncio
24
+ from enum import Enum
25
 
26
  # ============================================================================
27
+ # 1. LOGGING SETUP
28
  # ============================================================================
29
+ logging.basicConfig(
30
+ level=logging.INFO,
31
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
32
+ handlers=[
33
+ logging.FileHandler('nodeai.log'),
34
+ logging.StreamHandler()
35
+ ]
36
+ )
37
  logger = logging.getLogger(__name__)
38
 
39
+ # ============================================================================
40
+ # 2. CONSTANTS & ENUMS
41
+ # ============================================================================
42
+ class ModelProvider(Enum):
43
+ QWEN = "qwen"
44
+ MISTRAL = "mistral"
45
+ LLAMA = "llama"
46
+ CUSTOM = "custom"
47
+
48
+ class ChatRole(Enum):
49
+ USER = "user"
50
+ ASSISTANT = "assistant"
51
+ SYSTEM = "system"
52
+
53
+ # ============================================================================
54
+ # 3. CONFIGURATION
55
+ # ============================================================================
56
+ # Environment Setup
57
  URL = os.environ.get("SUPABASE_URL")
58
  KEY = os.environ.get("SUPABASE_KEY")
59
  HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN")
60
 
61
+ # Initialize Supabase with error handling
62
  try:
63
  supabase: Client = create_client(URL, KEY) if URL and KEY else None
64
+ logger.info("✅ Supabase initialized successfully")
65
  except Exception as e:
66
+ logger.error(f"❌ Supabase initialization failed: {e}")
67
  supabase = None
68
 
69
  # ============================================================================
70
+ # 4. ADVANCED MODEL CONFIGURATION
71
  # ============================================================================
72
  AVAILABLE_MODELS = {
73
+ "🚀 Qwen 2.5 7B (Balanced)": {
74
+ "model_id": "Qwen/Qwen2.5-7B-Instruct",
75
+ "provider": ModelProvider.QWEN,
76
+ "speed": "Fast",
77
+ "quality": "High",
78
+ "context_window": 32768,
79
+ "max_tokens": 2048,
80
+ "temperature": 0.7,
81
+ "top_p": 0.95,
82
+ },
83
+ "⚡ Mistral 7B (Fastest)": {
84
+ "model_id": "mistralai/Mistral-7B-Instruct-v0.2",
85
+ "provider": ModelProvider.MISTRAL,
86
+ "speed": "Very Fast",
87
+ "quality": "Good",
88
+ "context_window": 32768,
89
+ "max_tokens": 2048,
90
+ "temperature": 0.7,
91
+ "top_p": 0.95,
92
+ },
93
+ "🔥 LLaMA 2 Chat 7B": {
94
+ "model_id": "meta-llama/Llama-2-7b-chat-hf",
95
+ "provider": ModelProvider.LLAMA,
96
+ "speed": "Fast",
97
+ "quality": "High",
98
+ "context_window": 4096,
99
+ "max_tokens": 2048,
100
+ "temperature": 0.7,
101
+ "top_p": 0.95,
102
+ },
103
+ "🎯 Qwen 72B (Ultra)": {
104
+ "model_id": "Qwen/Qwen2.5-72B-Instruct",
105
+ "provider": ModelProvider.QWEN,
106
+ "speed": "Slower",
107
+ "quality": "Premium",
108
+ "context_window": 32768,
109
+ "max_tokens": 4096,
110
+ "temperature": 0.7,
111
+ "top_p": 0.95,
112
+ },
113
  }
114
 
115
+ DEFAULT_MODEL = "🚀 Qwen 2.5 7B (Balanced)"
116
 
117
+ # ============================================================================
118
+ # 5. SYSTEM PROMPTS WITH PERSONALITY
119
+ # ============================================================================
120
+ SYSTEM_PROMPTS = {
121
+ "🤖 Default Assistant": {
122
+ "emoji": "🤖",
123
+ "description": "Helpful, creative, and concise",
124
+ "prompt": """You are NodeAI Genesis Pro, an advanced AI assistant created by the community.
125
+ You are helpful, creative, honest, and concise. You think step by step.
126
+ When you don't know something, you admit it clearly. You are good at:
127
+ - Answering questions with accuracy and clarity
128
+ - Helping with coding, writing, and analysis
129
+ - Providing thoughtful advice and insights
130
+ - Explaining complex topics in simple terms"""
131
+ },
132
+
133
+ "📚 Research Scholar": {
134
+ "emoji": "📚",
135
+ "description": "Expert analyst with citations",
136
+ "prompt": """You are an expert research analyst and scholar with deep knowledge across multiple domains.
137
+ Your expertise includes:
138
+ - Academic research and peer-reviewed literature
139
+ - Data analysis and statistical interpretation
140
+ - Historical context and evidence-based arguments
141
+ - Critical thinking and logical reasoning
142
+
143
+ Always provide well-structured analysis with proper citations where applicable.
144
+ Break down complex topics into digestible parts with examples."""
145
+ },
146
+
147
+ "💻 Senior Developer": {
148
+ "emoji": "💻",
149
+ "description": "Expert programmer and architect",
150
+ "prompt": """You are a senior software engineer and architect with expertise in:
151
+ - Full-stack development (frontend, backend, databases)
152
+ - Cloud architecture and DevOps
153
+ - System design and scalability
154
+ - Best practices and design patterns
155
+ - Code quality and performance optimization
156
+
157
+ Always provide:
158
+ - Clean, production-ready code
159
+ - Detailed explanations
160
+ - Security considerations
161
+ - Performance implications
162
+ - Testing strategies"""
163
+ },
164
+
165
+ "📝 Professional Writer": {
166
+ "emoji": "📝",
167
+ "description": "Content creation expert",
168
+ "prompt": """You are a professional writer and editor with expertise in:
169
+ - Academic writing and research papers
170
+ - Creative writing and storytelling
171
+ - Technical documentation
172
+ - Marketing and copywriting
173
+ - Grammar, style, and clarity
174
+
175
+ Create engaging, well-structured, grammatically perfect content.
176
+ Adapt tone and style to the target audience and purpose.
177
+ Provide constructive feedback and editing suggestions."""
178
+ },
179
+
180
+ "🎯 Life Coach": {
181
+ "emoji": "🎯",
182
+ "description": "Motivational mentor",
183
+ "prompt": """You are a supportive personal mentor and life coach.
184
+ Your approach is:
185
+ - Non-judgmental and empathetic
186
+ - Action-oriented with practical steps
187
+ - Motivating while realistic
188
+ - Focused on growth and self-improvement
189
+ - Evidence-based in your advice
190
+
191
+ Provide motivational, actionable advice that helps people achieve their goals.
192
+ Ask clarifying questions to understand the context better.
193
+ Celebrate progress and help overcome obstacles."""
194
+ },
195
+
196
+ "🔬 Data Scientist": {
197
+ "emoji": "🔬",
198
+ "description": "ML/AI and data expert",
199
+ "prompt": """You are an expert Data Scientist and Machine Learning engineer.
200
+ Your expertise includes:
201
+ - Statistical analysis and hypothesis testing
202
+ - Machine learning algorithms and models
203
+ - Data visualization and exploration
204
+ - Python, R, SQL for data work
205
+ - Data ethics and responsible AI
206
+
207
+ Provide clear explanations of complex concepts.
208
+ Show code examples when relevant.
209
+ Discuss trade-offs and limitations of approaches."""
210
+ },
211
+
212
+ "🎓 Teacher": {
213
+ "emoji": "🎓",
214
+ "description": "Education specialist",
215
+ "prompt": """You are an expert teacher and educator skilled at:
216
+ - Explaining complex concepts in simple terms
217
+ - Breaking down learning into manageable steps
218
+ - Using examples and analogies
219
+ - Assessing understanding and adjusting explanations
220
+ - Creating engaging educational content
221
+
222
+ Your approach is patient, encouraging, and adaptive.
223
+ You meet learners where they are and help them progress.
224
+ You explain the "why" behind concepts, not just the "how"."""
225
+ },
226
+ }
227
 
228
  # ============================================================================
229
+ # 6. PREMIUM CSS WITH ADVANCED THEMING
230
  # ============================================================================
231
  custom_css = """
232
+ * {
233
+ transition: all 0.3s ease;
234
+ box-sizing: border-box;
235
+ }
236
 
237
+ html, body {
238
+ margin: 0;
239
+ padding: 0;
240
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
241
+ }
242
+
243
+ footer, .built-with, header, #header {
244
+ display: none !important;
245
+ }
246
 
247
  .gradio-container {
248
  max-width: 100% !important;
249
  padding: 0 !important;
250
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
251
  min-height: 100vh;
252
  color: #e2e8f0;
253
  }
254
 
255
+ body {
256
+ background: #0f172a !important;
257
+ overflow: hidden;
258
+ }
259
 
260
+ /* ===== SIDEBAR ===== */
261
  .sidebar-node {
262
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.95), rgba(30, 41, 59, 0.95));
263
  border-right: 1px solid rgba(56, 189, 248, 0.2);
264
  padding: 24px;
265
  max-height: 100vh;
266
  overflow-y: auto;
267
+ box-shadow: inset -10px 0 30px rgba(0, 0, 0, 0.3);
268
  }
269
 
270
  .logo-area {
271
+ font-family: 'Courier New', 'Monaco', monospace;
272
+ background: linear-gradient(135deg, #38bdf8 0%, #8b5cf6 50%, #ec4899 100%);
273
  -webkit-background-clip: text;
274
  -webkit-text-fill-color: transparent;
275
  background-clip: text;
276
+ font-size: 26px;
277
  text-align: center;
278
+ font-weight: 900;
279
+ margin-bottom: 28px;
280
+ letter-spacing: 3px;
281
+ text-shadow: 0 0 30px rgba(56, 189, 248, 0.4);
282
+ animation: glow 3s ease-in-out infinite;
283
  }
284
 
285
+ @keyframes glow {
286
+ 0%, 100% { filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3)); }
287
+ 50% { filter: drop-shadow(0 0 20px rgba(139, 92, 246, 0.5)); }
288
+ }
289
+
290
+ /* ===== BUTTONS ===== */
291
+ button, .gradio-button {
292
  background: linear-gradient(135deg, #38bdf8, #8b5cf6) !important;
293
  border: none !important;
294
  color: white !important;
295
+ font-weight: 700 !important;
296
  padding: 12px 20px !important;
297
+ border-radius: 10px !important;
298
  cursor: pointer !important;
299
+ box-shadow: 0 4px 20px rgba(56, 189, 248, 0.3) !important;
300
+ position: relative;
301
+ overflow: hidden;
302
+ }
303
+
304
+ button::before {
305
+ content: '';
306
+ position: absolute;
307
+ top: 0;
308
+ left: -100%;
309
+ width: 100%;
310
+ height: 100%;
311
+ background: rgba(255, 255, 255, 0.1);
312
+ transition: left 0.3s;
313
+ }
314
+
315
+ button:hover::before {
316
+ left: 100%;
317
  }
318
 
319
+ button:hover {
320
+ transform: translateY(-3px) !important;
321
+ box-shadow: 0 8px 30px rgba(56, 189, 248, 0.5) !important;
322
  }
323
 
324
+ button:active {
325
+ transform: translateY(-1px) !important;
326
+ }
327
+
328
+ /* ===== CHATBOT ===== */
329
  .gradio-chatbot {
330
+ background: linear-gradient(135deg, rgba(30, 41, 59, 0.8), rgba(15, 23, 42, 0.8));
331
+ border: 1px solid rgba(56, 189, 248, 0.15) !important;
332
+ border-radius: 16px !important;
333
+ box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.3);
334
+ padding: 20px !important;
335
+ }
336
+
337
+ .message {
338
  border-radius: 12px !important;
339
+ padding: 16px !important;
340
+ margin: 12px 0 !important;
341
  }
342
 
343
+ .user-message {
344
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.15), rgba(139, 92, 246, 0.1));
345
+ border-left: 4px solid #38bdf8 !important;
346
+ }
347
+
348
+ .assistant-message {
349
+ background: rgba(15, 23, 42, 0.6);
350
+ border-left: 4px solid #8b5cf6 !important;
351
+ }
352
+
353
+ /* ===== INPUT ===== */
354
+ .gradio-textbox input,
355
+ .gradio-multimodalTextbox textarea {
356
+ background: rgba(30, 41, 59, 0.9) !important;
357
+ border: 1px solid rgba(56, 189, 248, 0.2) !important;
358
+ border-radius: 10px !important;
359
+ color: #e2e8f0 !important;
360
+ padding: 14px 16px !important;
361
+ font-size: 15px !important;
362
+ }
363
+
364
+ .gradio-multimodalTextbox {
365
+ background: rgba(30, 41, 59, 0.9) !important;
366
+ border: 1px solid rgba(56, 189, 248, 0.2) !important;
367
+ border-radius: 10px !important;
368
+ }
369
+
370
+ .gradio-textbox input:focus,
371
+ .gradio-multimodalTextbox textarea:focus {
372
+ border-color: #38bdf8 !important;
373
+ box-shadow: 0 0 10px rgba(56, 189, 248, 0.3) !important;
374
+ }
375
+
376
+ /* ===== DROPDOWNS ===== */
377
+ .gradio-dropdown select {
378
  background: rgba(30, 41, 59, 0.9) !important;
379
  border: 1px solid rgba(56, 189, 248, 0.2) !important;
 
380
  color: #e2e8f0 !important;
381
+ border-radius: 8px !important;
382
+ padding: 10px !important;
383
  }
384
 
385
+ .gradio-dropdown select:focus {
386
+ border-color: #38bdf8 !important;
387
+ box-shadow: 0 0 10px rgba(56, 189, 248, 0.3) !important;
388
+ }
389
+
390
+ /* ===== DATASETS & LISTS ===== */
391
  .gradio-dataset {
392
  background: rgba(30, 41, 59, 0.7) !important;
393
  border: 1px solid rgba(56, 189, 248, 0.1) !important;
394
+ border-radius: 10px !important;
395
+ max-height: 400px;
396
+ overflow-y: auto;
397
  }
398
 
399
  .dataset-item {
400
  background: rgba(15, 23, 42, 0.6) !important;
401
  border-left: 3px solid #38bdf8 !important;
402
+ padding: 14px !important;
403
+ margin: 8px !important;
404
  cursor: pointer !important;
405
  border-radius: 6px !important;
406
+ transition: all 0.2s;
407
  }
408
 
409
  .dataset-item:hover {
410
+ background: rgba(56, 189, 248, 0.15) !important;
411
+ transform: translateX(6px) !important;
412
+ box-shadow: 0 4px 15px rgba(56, 189, 248, 0.2) !important;
413
+ }
414
+
415
+ .dataset-item.selected {
416
+ background: rgba(56, 189, 248, 0.2) !important;
417
+ box-shadow: 0 0 20px rgba(56, 189, 248, 0.3) !important;
418
+ }
419
+
420
+ /* ===== STATUS CARDS ===== */
421
+ .stat-card {
422
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.1), rgba(139, 92, 246, 0.1));
423
+ border: 1px solid rgba(56, 189, 248, 0.2);
424
+ border-radius: 10px;
425
+ padding: 18px;
426
+ margin: 10px 0;
427
+ color: #e2e8f0;
428
+ backdrop-filter: blur(10px);
429
+ }
430
+
431
+ .stat-card-title {
432
+ font-weight: 600;
433
+ color: #38bdf8;
434
+ margin-bottom: 8px;
435
+ }
436
+
437
+ /* ===== MARKDOWN ===== */
438
+ .gradio-markdown {
439
+ color: #e2e8f0;
440
+ }
441
+
442
+ .gradio-markdown h1,
443
+ .gradio-markdown h2,
444
+ .gradio-markdown h3 {
445
+ color: #38bdf8;
446
+ margin-top: 16px;
447
+ margin-bottom: 12px;
448
+ }
449
+
450
+ .gradio-markdown code {
451
+ background: rgba(15, 23, 42, 0.8);
452
+ color: #38bdf8;
453
+ border-radius: 4px;
454
+ padding: 2px 6px;
455
+ font-family: 'Courier New', monospace;
456
+ }
457
+
458
+ /* ===== TABS ===== */
459
+ .gradio-tabs {
460
+ background: transparent !important;
461
+ }
462
+
463
+ .gradio-tabitem {
464
+ background: rgba(30, 41, 59, 0.8) !important;
465
+ border: 1px solid rgba(56, 189, 248, 0.1) !important;
466
+ border-radius: 10px !important;
467
+ }
468
+
469
+ /* ===== SCROLLBAR ===== */
470
+ ::-webkit-scrollbar {
471
+ width: 10px;
472
+ height: 10px;
473
+ }
474
+
475
+ ::-webkit-scrollbar-track {
476
+ background: rgba(56, 189, 248, 0.05);
477
+ border-radius: 5px;
478
+ }
479
+
480
+ ::-webkit-scrollbar-thumb {
481
+ background: linear-gradient(180deg, #38bdf8, #8b5cf6);
482
+ border-radius: 5px;
483
+ }
484
+
485
+ ::-webkit-scrollbar-thumb:hover {
486
+ background: linear-gradient(180deg, #8b5cf6, #ec4899);
487
+ box-shadow: 0 0 10px rgba(56, 189, 248, 0.3);
488
+ }
489
+
490
+ /* ===== ANIMATIONS ===== */
491
+ @keyframes slideIn {
492
+ from { transform: translateY(10px); opacity: 0; }
493
+ to { transform: translateY(0); opacity: 1; }
494
+ }
495
+
496
+ @keyframes fadeIn {
497
+ from { opacity: 0; }
498
+ to { opacity: 1; }
499
+ }
500
+
501
+ .message {
502
+ animation: slideIn 0.3s ease;
503
  }
504
 
505
+ /* ===== WARNING & INFO ===== */
506
  .custom-warning {
507
  text-align: center;
508
  color: #94a3b8;
509
  font-size: 12px;
510
+ margin: 16px 0;
511
  font-style: italic;
512
+ padding: 14px;
513
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.05), rgba(139, 92, 246, 0.05));
514
+ border-radius: 8px;
515
  border-left: 3px solid #38bdf8;
516
  }
517
 
518
+ .info-box {
519
+ background: rgba(56, 189, 248, 0.1);
520
  border: 1px solid rgba(56, 189, 248, 0.2);
521
  border-radius: 8px;
522
+ padding: 14px;
 
523
  color: #e2e8f0;
524
+ margin: 12px 0;
525
  }
526
 
527
+ /* ===== RESPONSIVE ===== */
528
+ @media (max-width: 768px) {
529
+ .sidebar-node {
530
+ max-height: 200px;
531
+ padding: 16px;
532
+ }
533
+
534
+ .logo-area {
535
+ font-size: 20px;
536
+ margin-bottom: 16px;
537
+ }
538
+
539
+ button {
540
+ padding: 10px 16px !important;
541
+ font-size: 14px !important;
542
+ }
543
  }
 
 
544
  """
545
 
546
  # ============================================================================
547
+ # 7. ADVANCED CHAT MANAGER WITH ANALYTICS
548
  # ============================================================================
549
+ class AdvancedChatManager:
550
+ """Enterprise-grade chat management with caching, analytics, and security"""
551
 
552
  def __init__(self):
553
  self.cache = {}
554
+ self.rate_limit = defaultdict(list)
555
+ self.analytics = defaultdict(list)
556
+ self.user_stats = defaultdict(lambda: {"messages": 0, "chats": 0, "tokens_used": 0})
557
+ self.performance_metrics = defaultdict(list)
558
+
559
+ def get_cache_key(self, username: str, session_id: str) -> str:
560
+ """Generate unique cache key"""
561
+ return f"user_{username}_session_{session_id}"
562
 
563
  def is_rate_limited(self, username: str, limit: int = 30, window: int = 60) -> bool:
564
+ """Check rate limiting with sliding window"""
565
  key = f"rate_{username}"
566
  now = datetime.now()
567
 
568
+ # Remove old timestamps outside window
569
+ self.rate_limit[key] = [
570
+ ts for ts in self.rate_limit[key]
571
+ if (now - ts).seconds < window
572
+ ]
 
573
 
574
  if len(self.rate_limit[key]) >= limit:
575
  return True
 
577
  self.rate_limit[key].append(now)
578
  return False
579
 
580
+ def record_metric(self, username: str, metric_type: str, value: float):
581
+ """Record performance metrics"""
582
+ self.performance_metrics[username].append({
583
+ "type": metric_type,
584
+ "value": value,
585
+ "timestamp": datetime.now()
586
+ })
587
+
588
+ # Keep only last 100 metrics per user
589
+ if len(self.performance_metrics[username]) > 100:
590
+ self.performance_metrics[username].pop(0)
591
+
592
+ def get_average_response_time(self, username: str) -> float:
593
+ """Calculate average response time"""
594
+ metrics = [
595
+ m["value"] for m in self.performance_metrics[username]
596
+ if m["type"] == "response_time"
597
+ ]
598
+ return sum(metrics) / len(metrics) if metrics else 0.0
599
+
600
  def generate_session_title(self, text: str, max_length: int = 50) -> str:
601
+ """Generate intelligent session title from first message"""
602
+ # Remove special characters and extra whitespace
603
+ clean = re.sub(r'[^a-zA-Z0-9\s]', '', text)
604
+ words = clean.split()[:8]
605
+ title = ' '.join(words) or "New Conversation"
606
  return title[:max_length]
607
+
608
+ def log_event(self, username: str, event_type: str, details: Dict = None):
609
+ """Log user events for analytics"""
610
+ event = {
611
+ "type": event_type,
612
+ "timestamp": datetime.now(),
613
+ "details": details or {}
614
+ }
615
+ self.analytics[username].append(event)
616
+
617
+ # Keep only last 500 events
618
+ if len(self.analytics[username]) > 500:
619
+ self.analytics[username].pop(0)
620
 
621
+ chat_manager = AdvancedChatManager()
622
 
623
  # ============================================================================
624
+ # 8. ENHANCED DATABASE OPERATIONS
625
  # ============================================================================
626
  def get_db_history(profile: gr.OAuthProfile | None) -> Tuple[List[Dict], gr.Dataset]:
627
+ """Retrieve user chat history with caching and formatting"""
628
  if not supabase or not profile:
629
  return [], gr.Dataset(samples=[])
630
 
631
  try:
632
  username = profile.username
633
+ logger.info(f"📂 Loading history for user: {username}")
634
+
635
+ res = supabase.table("chats").select("*").eq("username", username).order(
636
+ "last_updated", desc=True
637
+ ).limit(50).execute()
638
 
639
  data = res.data if res.data else []
640
 
641
+ # Format for display
642
+ samples = []
643
+ for item in data:
644
+ title = item.get('title', 'Untitled')[:35]
645
+ msg_count = item.get('message_count', 0)
646
+ created = item.get('created_at', '').split('T')[0]
647
+ model = item.get('model_used', 'N/A').split('/')[-1][:15]
648
+
649
+ samples.append([
650
+ f"💬 {title}",
651
+ f"📊 {msg_count} msgs | 🤖 {model}",
652
+ f"📅 {created}"
653
+ ])
654
 
655
+ chat_manager.log_event(username, "load_history", {"count": len(data)})
656
  logger.info(f"✅ Loaded {len(data)} chats for {username}")
657
+
658
  return data, gr.Dataset(samples=samples)
659
 
660
  except Exception as e:
661
+ logger.error(f"❌ DB load error: {e}")
662
  return [], gr.Dataset(samples=[])
663
 
664
  def load_selected_chat(evt: gr.SelectData, db_data: List[Dict]) -> Tuple[List[Dict], str]:
 
675
 
676
  def save_chat_to_db(username: str, session_id: str, title: str,
677
  history: List[Dict], model_used: str) -> bool:
678
+ """Save chat to database with error handling"""
679
  if not supabase or not username:
680
  return False
681
 
 
694
  }
695
 
696
  supabase.table("chats").upsert(data, on_conflict="session_id").execute()
697
+ logger.info(f"💾 Chat saved: {session_id} ({message_count} messages)")
698
  return True
699
  except Exception as e:
700
  logger.error(f"❌ Save error: {e}")
701
  return False
702
 
703
+ def get_user_stats(profile: gr.OAuthProfile | None) -> str:
704
+ """Generate detailed user statistics"""
705
+ if not supabase or not profile:
706
+ return "📊 No data available"
707
+
708
+ try:
709
+ username = profile.username
710
+ res = supabase.table("chats").select(
711
+ "id,message_count,created_at,model_used"
712
+ ).eq("username", username).execute()
713
+
714
+ data = res.data if res.data else []
715
+
716
+ if not data:
717
+ return """
718
+ 📊 **Your Statistics**
719
+ - Total Chats: **0**
720
+ - Total Messages: **0**
721
+ - Average: **0** messages/chat
722
+ - Most Used Model: **None yet**
723
+ """
724
+
725
+ total_chats = len(data)
726
+ total_messages = sum(d.get('message_count', 0) for d in data)
727
+ avg_messages = total_messages // max(total_chats, 1)
728
+
729
+ # Find most used model
730
+ models = [d.get('model_used', 'Unknown') for d in data]
731
+ most_used = max(set(models), key=models.count).split('/')[-1][:20]
732
+
733
+ # Calculate streak (days with chats)
734
+ dates = set(d.get('created_at', '').split('T')[0] for d in data)
735
+
736
+ return f"""
737
+ 📊 **Your Statistics**
738
+ - **Total Chats**: {total_chats}
739
+ - **Total Messages**: {total_messages}
740
+ - **Avg Messages/Chat**: {avg_messages}
741
+ - **Most Used Model**: {most_used}
742
+ - **Unique Days**: {len(dates)}
743
+ - **Last Chat**: {data[0].get('last_updated', 'N/A').split('T')[0]}
744
+ """
745
+ except Exception as e:
746
+ logger.error(f"Stats error: {e}")
747
+ return "📊 Error loading stats"
748
 
749
  # ============================================================================
750
+ # 9. ULTRA CHAT ENGINE WITH STREAMING & OPTIMIZATION
751
  # ============================================================================
752
+ def chat_engine(
753
+ message: Dict,
754
+ history: List[Dict],
755
+ session_id: str,
756
+ selected_model: str,
757
+ system_prompt_key: str,
758
+ profile: gr.OAuthProfile | None
759
+ ) -> Tuple:
760
+ """Advanced chat engine with streaming, caching, analytics"""
761
 
762
+ start_time = time.time()
763
  text = message.get("text", "").strip() if isinstance(message, dict) else str(message).strip()
764
 
765
  if not text:
766
+ yield (
767
+ gr.update(value={"text": "", "files": []}, interactive=True),
768
+ history,
769
+ session_id,
770
+ "⚠️ Please enter a message"
771
+ )
772
  return
773
 
 
774
  username = profile.username if profile else "guest"
775
+
776
+ # Rate limiting check
777
+ if chat_manager.is_rate_limited(username, limit=30, window=60):
778
+ logger.warning(f"⏱️ Rate limit hit for {username}")
779
+ yield (
780
+ gr.update(value={"text": "", "files": []}, interactive=True),
781
+ history,
782
+ session_id,
783
+ "⏱️ Rate limited. Max 30 requests/minute. Please wait."
784
+ )
785
  return
786
 
787
+ # Log event
788
+ chat_manager.log_event(username, "message_sent", {"model": selected_model, "text_length": len(text)})
789
+
790
+ # Get model configuration
791
+ model_config = AVAILABLE_MODELS.get(selected_model, {})
792
+ model_id = model_config.get("model_id", "Qwen/Qwen2.5-7B-Instruct")
793
 
794
+ # Get system prompt
795
+ prompt_config = SYSTEM_PROMPTS.get(system_prompt_key, SYSTEM_PROMPTS[list(SYSTEM_PROMPTS.keys())[0]])
796
+ system_message = prompt_config.get("prompt")
797
+
798
+ # Initialize client
799
+ try:
800
+ client = InferenceClient(model=model_id, token=HF_TOKEN)
801
+ except:
802
+ client = InferenceClient(model="Qwen/Qwen2.5-7B-Instruct", token=HF_TOKEN)
803
+
804
+ # Build message context with optimization
805
+ messages = [{"role": "system", "content": system_message}]
806
+
807
+ # Keep relevant context (last 15 messages or 4000 tokens equivalent)
808
+ context_messages = history[-15:] if len(history) > 15 else history
809
+ messages.extend(context_messages)
810
  messages.append({"role": "user", "content": text})
811
 
812
  new_history = history + [{"role": "user", "content": text}]
813
  ans = ""
814
 
815
+ # Initial status
816
  yield (
817
  gr.update(value={"text": "", "files": []}, interactive=False),
818
  new_history + [{"role": "assistant", "content": "⏳ Thinking..."}],
819
  session_id,
820
+ f"🔄 Processing with {selected_model}"
821
  )
822
 
823
  try:
824
  # Stream response
825
  for token in client.chat_completion(
826
  messages=messages,
827
+ max_tokens=model_config.get("max_tokens", 2048),
828
+ temperature=model_config.get("temperature", 0.7),
829
+ top_p=model_config.get("top_p", 0.95),
830
  stream=True
831
  ):
832
  if token.choices[0].delta.content:
 
835
  gr.update(value={"text": "", "files": []}, interactive=True),
836
  new_history + [{"role": "assistant", "content": ans}],
837
  session_id,
838
+ f"✅ {len(ans)} chars generated"
839
  )
840
 
841
+ # Calculate metrics
842
+ response_time = time.time() - start_time
843
+ chat_manager.record_metric(username, "response_time", response_time)
844
+
845
  # Save to database
846
  final_history = new_history + [{"role": "assistant", "content": ans}]
847
  session_title = chat_manager.generate_session_title(text)
848
 
849
  if profile:
850
+ save_success = save_chat_to_db(
851
  profile.username,
852
  session_id,
853
  session_title,
 
855
  selected_model
856
  )
857
 
858
+ # Final status
859
  yield (
860
  gr.update(value={"text": "", "files": []}, interactive=True),
861
  final_history,
862
  session_id,
863
+ f"✅ Complete ({response_time:.2f}s, {len(ans)} chars)"
864
  )
865
 
866
+ logger.info(f"✅ Chat complete in {response_time:.2f}s for {username}")
867
+
868
  except Exception as e:
869
  error_msg = f"❌ Error: {str(e)[:100]}"
870
+ logger.error(f"{error_msg} (user: {username})")
871
  yield (
872
  gr.update(value={"text": "", "files": []}, interactive=True),
873
+ new_history + [{"role": "assistant", "content": f"⚠️ Error: {str(e)}"}],
874
  session_id,
875
  error_msg
876
  )
877
 
878
  # ============================================================================
879
+ # 10. PREMIUM UI INTERFACE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
880
  # ============================================================================
881
  def create_interface():
882
+ """Build the complete Gradio interface"""
883
+
884
  with gr.Blocks(theme=gr.themes.Slate()) as demo:
885
 
886
+ # Global states
887
  session_id = gr.State(str(uuid.uuid4()))
888
  db_state = gr.State([])
 
889
 
890
  with gr.Row(equal_height=False):
891
+ # ==================== SIDEBAR ====================
892
  with gr.Column(scale=1, elem_classes="sidebar-node"):
893
+ # Logo
894
+ gr.HTML("<div class='logo-area'>⚡ NODEAI PRO</div>")
895
 
896
+ # Authentication
897
  profile = gr.LoginButton(
898
  value="🔐 Login with HF",
899
  size="sm",
900
  scale=1
901
  )
902
 
903
+ gr.Markdown("---")
904
+
905
+ # Chat Controls
906
+ gr.Markdown("### 🎮 Controls")
907
  with gr.Row(scale=1):
908
+ new_chat_btn = gr.Button("➕ New Chat", variant="primary", scale=1, size="sm")
909
+ delete_chat_btn = gr.Button("🗑️ Delete", scale=1, size="sm")
910
+
911
+ gr.Markdown("---")
912
 
913
+ # Model Selection
914
+ gr.Markdown("### 🤖 AI Model")
915
  selected_model = gr.Dropdown(
916
  choices=list(AVAILABLE_MODELS.keys()),
917
+ value=DEFAULT_MODEL,
918
+ label="Choose Model",
919
+ interactive=True,
920
+ show_label=False
921
  )
922
 
923
+ # System Prompt Selection
924
+ gr.Markdown("### 🎯 Personality")
925
+ system_prompt_key = gr.Dropdown(
926
  choices=list(SYSTEM_PROMPTS.keys()),
927
  value=list(SYSTEM_PROMPTS.keys())[0],
928
+ label="Persona",
929
+ interactive=True,
930
+ show_label=False
931
  )
932
 
933
+ # Show prompt description
934
+ prompt_desc = gr.Markdown("*Select a persona*")
935
+
936
+ gr.Markdown("---")
937
+
938
+ # Chat History
939
+ gr.Markdown("### 📚 History")
940
  history_list = gr.Dataset(
941
  components=[
942
  gr.Textbox(visible=False),
 
945
  ],
946
  samples=[],
947
  type="index",
948
+ label="Conversations"
949
  )
950
 
951
+ gr.Markdown("---")
952
+
953
  # Statistics
954
+ gr.Markdown("### 📊 Stats")
955
+ stats_display = gr.Markdown("Loading...")
956
+
957
+ gr.Markdown("---")
958
 
959
+ # Footer
960
+ gr.HTML("<div class='custom-warning'>🔒 End-to-end encrypted. Your data, your control.</div>")
961
 
962
+ # ==================== MAIN AREA ====================
963
  with gr.Column(scale=4):
964
  # Header
965
  gr.HTML("""
966
+ <div style='text-align: center; margin-bottom: 20px; padding: 20px;'>
967
+ <h1 style='color: #38bdf8; margin: 0; font-size: 32px;'>🚀 NodeAI Genesis Pro Ultra</h1>
968
+ <p style='color: #94a3b8; margin: 8px 0; font-size: 14px;'>
969
+ Enterprise AI Chat • Multi-Model • Persistent Storage • Real-time Streaming
970
+ </p>
971
  </div>
972
  """)
973
 
974
  # Chatbot
975
  chatbot = gr.Chatbot(
976
  show_label=False,
977
+ height="65vh",
978
  bubble_full_width=False,
979
+ scale=1,
980
+ avatar_images=(None, "https://img.icons8.com/color/96/000000/robot.png")
981
  )
982
 
983
+ # Status Bar
984
+ with gr.Row(scale=1, equal_height=True):
985
  status_display = gr.Textbox(
986
  value="✅ Ready",
987
  label="Status",
988
  interactive=False,
989
+ scale=2,
990
+ container=False
991
  )
992
  model_info = gr.Textbox(
993
+ value="",
994
+ label="Model",
995
  interactive=False,
996
+ scale=1,
997
+ container=False
998
  )
999
 
1000
  # Input Area
1001
  with gr.Row(scale=1):
1002
  msg_input = gr.MultimodalTextbox(
1003
  show_label=False,
1004
+ placeholder="💬 Ask anything... (Ctrl+Enter to send)",
1005
  container=False,
1006
+ scale=1,
1007
+ min_width=300
1008
  )
1009
+
1010
+ # Info
1011
+ gr.HTML("<div class='info-box'>⚡ Streaming responses in real-time • Saves to your account automatically</div>")
1012
 
1013
+ # ==================== EVENT HANDLERS ====================
1014
 
1015
  # Load history on page load
1016
  demo.load(
 
1023
  outputs=[stats_display]
1024
  )
1025
 
1026
+ # Update prompt description
1027
+ def update_prompt_desc(key):
1028
+ config = SYSTEM_PROMPTS.get(key, {})
1029
+ return f"**{config.get('description', 'N/A')}**"
1030
+
1031
+ system_prompt_key.change(
1032
+ fn=update_prompt_desc,
1033
+ inputs=[system_prompt_key],
1034
+ outputs=[prompt_desc]
1035
+ )
1036
+
1037
+ # Load selected chat
1038
  history_list.select(
1039
  fn=lambda evt, db_data: load_selected_chat(evt, db_data),
1040
  inputs=[history_list, db_state],
 
1049
  chatbot,
1050
  session_id,
1051
  selected_model,
1052
+ system_prompt_key,
1053
  profile
1054
  ],
1055
  outputs=[msg_input, chatbot, session_id, status_display]
 
1063
  outputs=[stats_display]
1064
  )
1065
 
1066
+ # Update model info
1067
  selected_model.change(
1068
+ fn=lambda model: gr.update(value=model.split("(")[0].strip()),
1069
  inputs=[selected_model],
1070
  outputs=[model_info]
1071
  )
1072
 
1073
+ # New chat
1074
  new_chat_btn.click(
1075
+ fn=lambda: ([], str(uuid.uuid4()), "✅ New chat started"),
1076
  outputs=[chatbot, session_id, status_display]
1077
  )
1078
 
1079
+ # Delete chat
1080
  delete_chat_btn.click(
1081
+ fn=lambda session_id, profile: (
1082
+ supabase.table("chats").delete().eq("session_id", session_id).execute() if supabase and profile else None,
1083
+ [],
1084
+ str(uuid.uuid4()),
1085
+ "✅ Chat deleted"
1086
+ )[1:],
1087
  inputs=[session_id, profile],
1088
+ outputs=[chatbot, session_id, status_display]
1089
  ).then(
1090
  fn=lambda profile: get_db_history(profile),
1091
  inputs=[profile],
 
1095
  return demo
1096
 
1097
  # ============================================================================
1098
+ # 11. MAIN EXECUTION
1099
  # ============================================================================
1100
  if __name__ == "__main__":
1101
+ logger.info("🚀 Starting NodeAI Genesis Pro Ultra v3.0...")
1102
+
1103
+ try:
1104
+ demo = create_interface()
1105
+ demo.queue(max_size=30, default_concurrency_limit=5).launch(
1106
+ css=custom_css,
1107
+ share=False,
1108
+ server_name="0.0.0.0",
1109
+ server_port=7860,
1110
+ show_error=True,
1111
+ analytics_enabled=False,
1112
+ )
1113
+ except Exception as e:
1114
+ logger.error(f"❌ Failed to start app: {e}")
1115
+ raise