lamgamepro1 commited on
Commit
4d6fec5
·
verified ·
1 Parent(s): f79897d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -1080
app.py CHANGED
@@ -1,1115 +1,191 @@
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
12
- import os
13
- import uuid
14
- import json
15
- from datetime import datetime, timedelta
16
- from typing import Optional, List, Dict, Tuple, Any
17
- import hashlib
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
576
-
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]:
665
- """Load selected chat session"""
666
- try:
667
- selected_item = db_data[evt.index]
668
- history = selected_item.get('history', [])
669
- session_id = selected_item.get('session_id')
670
- logger.info(f"📂 Loaded session: {session_id}")
671
- return history, session_id
672
- except Exception as e:
673
- logger.error(f"Error loading chat: {e}")
674
- return [], str(uuid.uuid4())
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
-
682
  try:
683
- message_count = len([m for m in history if m.get('role') == 'user'])
684
-
685
- data = {
686
- "username": username,
687
- "session_id": session_id,
688
- "title": title,
689
- "history": history,
690
- "message_count": message_count,
691
- "model_used": model_used,
692
- "last_updated": datetime.utcnow().isoformat(),
693
- "created_at": datetime.utcnow().isoformat(),
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:
833
- ans += token.choices[0].delta.content
834
- yield (
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,
854
- final_history,
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),
943
- gr.Textbox(visible=False),
944
- 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(
1017
- fn=lambda profile: get_db_history(profile),
1018
- inputs=[profile],
1019
- outputs=[db_state, history_list]
1020
- ).then(
1021
- fn=lambda profile: get_user_stats(profile),
1022
- inputs=[profile],
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],
1041
- outputs=[chatbot, session_id]
1042
- )
1043
-
1044
- # Send message
1045
- msg_input.submit(
1046
- fn=chat_engine,
1047
- inputs=[
1048
- msg_input,
1049
- chatbot,
1050
- session_id,
1051
- selected_model,
1052
- system_prompt_key,
1053
- profile
1054
- ],
1055
- outputs=[msg_input, chatbot, session_id, status_display]
1056
- ).then(
1057
- fn=lambda profile: get_db_history(profile),
1058
- inputs=[profile],
1059
- outputs=[db_state, history_list]
1060
- ).then(
1061
- fn=lambda profile: get_user_stats(profile),
1062
- inputs=[profile],
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],
1092
- outputs=[db_state, history_list]
1093
- )
1094
 
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
 
1
+ """NodeAI Genesis Pro - FULL Pro Version - Gradio 6.9.0 Compatible"""
 
 
 
 
 
 
 
2
  import gradio as gr
3
  from huggingface_hub import InferenceClient
4
  from supabase import create_client, Client
5
+ import os, uuid, json, logging, re, time
6
+ from datetime import datetime
7
+ from typing import List, Dict, Tuple
 
 
 
 
 
 
8
  from collections import defaultdict
 
 
 
9
 
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
 
 
 
 
 
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
13
+ URL, KEY, HF_TOKEN = os.environ.get("SUPABASE_URL"), os.environ.get("SUPABASE_KEY"), os.environ.get("HUGGINGFACE_HUB_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  try:
15
+ supabase = create_client(URL, KEY) if URL and KEY else None
16
+ logger.info("✅ Supabase OK")
17
  except Exception as e:
18
+ logger.error(f"Supabase: {e}")
19
  supabase = None
20
 
21
+ MODELS = {
22
+ "🚀 Qwen 2.5 7B": {"id": "Qwen/Qwen2.5-7B-Instruct", "speed": "Fast", "quality": "High"},
23
+ "⚡ Mistral 7B": {"id": "mistralai/Mistral-7B-Instruct-v0.2", "speed": "Very Fast", "quality": "Good"},
24
+ "🔥 LLaMA 2 7B": {"id": "meta-llama/Llama-2-7b-chat-hf", "speed": "Fast", "quality": "High"},
25
+ "🎯 Qwen 72B": {"id": "Qwen/Qwen2.5-72B-Instruct", "speed": "Slow", "quality": "Premium"},
26
+ }
27
+
28
+ PROMPTS = {
29
+ "🤖 Default": "You are NodeAI Genesis Pro. Be helpful, creative, honest, and concise. Explain complex topics clearly.",
30
+ "📚 Researcher": "Expert analyst. Provide detailed analysis with sources. Break down complex topics. Think critically.",
31
+ "💻 Developer": "Senior programmer. Provide clean, optimized code. Explain approaches. Consider edge cases.",
32
+ "📝 Writer": "Professional writer. Create engaging, well-written, grammatically perfect content.",
33
+ "🎯 Coach": "Personal mentor. Provide motivational, actionable advice. Focus on growth.",
34
+ "🔬 Data Scientist": "ML expert. Explain statistics clearly. Provide code examples. Discuss ethics.",
35
+ "🎓 Teacher": "Educator. Explain clearly. Break into steps. Use examples. Encourage learning.",
36
+ }
37
+
38
+ CSS = """
39
+ * { transition: all 0.3s ease; }
40
+ footer, .built-with, header { display: none !important; }
41
+ .gradio-container { max-width: 100%; background: linear-gradient(135deg, #0f172a, #1e293b); color: #e2e8f0; }
42
+ body { background: #0f172a !important; }
43
+ .sidebar-node { background: rgba(15,23,42,0.95); border-right: 1px solid rgba(56,189,248,0.2); padding: 24px; max-height: 100vh; overflow-y: auto; }
44
+ .logo { background: linear-gradient(135deg, #38bdf8, #8b5cf6, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 28px; font-weight: 900; text-align: center; margin: 20px 0; letter-spacing: 2px; }
45
+ button { background: linear-gradient(135deg, #38bdf8, #8b5cf6) !important; border: none !important; color: white !important; padding: 12px 20px !important; border-radius: 10px !important; box-shadow: 0 4px 20px rgba(56,189,248,0.3) !important; }
46
+ button:hover { transform: translateY(-3px) !important; box-shadow: 0 8px 30px rgba(56,189,248,0.5) !important; }
47
+ .gradio-chatbot { background: rgba(30,41,59,0.8); border: 1px solid rgba(56,189,248,0.15); border-radius: 16px; padding: 20px; }
48
+ input, textarea { background: rgba(30,41,59,0.9) !important; border: 1px solid rgba(56,189,248,0.2) !important; color: #e2e8f0 !important; border-radius: 10px !important; padding: 14px !important; }
49
+ input:focus, textarea:focus { border-color: #38bdf8 !important; box-shadow: 0 0 15px rgba(56,189,248,0.3) !important; }
50
+ .gradio-dropdown select { background: rgba(30,41,59,0.9) !important; border: 1px solid rgba(56,189,248,0.2) !important; color: #e2e8f0 !important; }
51
+ .gradio-dataset { background: rgba(30,41,59,0.7); border: 1px solid rgba(56,189,248,0.1); border-radius: 10px; max-height: 400px; overflow-y: auto; }
52
+ .gradio-markdown h1, .gradio-markdown h2, .gradio-markdown h3 { color: #38bdf8; }
53
+ ::-webkit-scrollbar { width: 10px; }
54
+ ::-webkit-scrollbar-track { background: rgba(56,189,248,0.05); }
55
+ ::-webkit-scrollbar-thumb { background: linear-gradient(180deg, #38bdf8, #8b5cf6); border-radius: 5px; }
56
+ .warning { text-align: center; color: #94a3b8; font-size: 12px; margin: 16px 0; padding: 14px; background: rgba(56,189,248,0.05); border-radius: 8px; border-left: 3px solid #38bdf8; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  """
58
 
59
+ class ChatMgr:
 
 
 
 
 
60
  def __init__(self):
 
61
  self.rate_limit = defaultdict(list)
62
+ self.metrics = defaultdict(list)
 
 
 
 
 
 
63
 
64
+ def is_limited(self, user, limit=30, window=60):
 
 
65
  now = datetime.now()
66
+ key = f"r_{user}"
67
+ self.rate_limit[key] = [ts for ts in self.rate_limit[key] if (now - ts).seconds < window]
68
+ if len(self.rate_limit[key]) >= limit: return True
 
 
 
 
 
 
 
69
  self.rate_limit[key].append(now)
70
  return False
71
 
72
+ def record(self, user, metric, val):
73
+ self.metrics[user].append({"type": metric, "value": val})
74
+ if len(self.metrics[user]) > 100: self.metrics[user].pop(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ def title(self, text): return ' '.join(re.sub(r'[^a-zA-Z0-9\s]', '', text).split()[:8])[:50] or "Chat"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ mgr = ChatMgr()
79
 
80
+ def get_hist(profile):
81
+ if not supabase or not profile: return [], gr.Dataset(samples=[])
 
 
 
 
 
 
82
  try:
83
+ res = supabase.table("chats").select("*").eq("username", profile.username).order("last_updated", desc=True).limit(50).execute()
84
+ data = res.data or []
85
+ samples = [[f"💬 {d['title'][:35]}", f"{d.get('message_count', 0)} msgs | {d.get('model_used', 'N/A').split('/')[-1][:15]}"] for d in data]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  return data, gr.Dataset(samples=samples)
87
+ except: return [], gr.Dataset(samples=[])
 
 
 
88
 
89
+ def load_chat(evt, db):
90
+ try: return db[evt.index]['history'], db[evt.index]['session_id']
91
+ except: return [], str(uuid.uuid4())
 
 
 
 
 
 
 
 
92
 
93
+ def save_chat(user, sid, title, hist, model):
94
+ if not supabase or not user: return False
 
 
 
 
95
  try:
96
+ msg_count = len([m for m in hist if m['role'] == 'user'])
97
+ supabase.table("chats").upsert({
98
+ "username": user, "session_id": sid, "title": title, "history": hist,
99
+ "message_count": msg_count, "model_used": model,
100
+ "last_updated": datetime.utcnow().isoformat(), "created_at": datetime.utcnow().isoformat()
101
+ }, on_conflict="session_id").execute()
 
 
 
 
 
 
 
 
 
102
  return True
103
+ except: return False
 
 
104
 
105
+ def get_stats(profile):
106
+ if not supabase or not profile: return "📊 No data"
 
 
 
107
  try:
108
+ res = supabase.table("chats").select("message_count").eq("username", profile.username).execute()
109
+ data = res.data or []
110
+ total_msgs = sum(d.get('message_count', 0) for d in data)
111
+ return f"📊 Chats: **{len(data)}** | Messages: **{total_msgs}** | Avg: **{total_msgs // max(len(data), 1)}**"
112
+ except: return "📊 Error"
113
+
114
+ def chat_engine(msg, hist, sid, model, prompt, profile):
115
+ text = msg.get("text", "").strip() if isinstance(msg, dict) else str(msg).strip()
116
+ if not text: yield gr.update(value={"text": ""}), hist, sid, "Empty"
117
+ else:
118
+ user = profile.username if profile else "guest"
119
+ if mgr.is_limited(user):
120
+ yield gr.update(value={"text": ""}), hist, sid, "Rate limited"
121
+ else:
122
+ start = time.time()
123
+ try:
124
+ model_id = MODELS[model]["id"] if model in MODELS else list(MODELS.values())[0]["id"]
125
+ client = InferenceClient(model=model_id, token=HF_TOKEN)
126
+ except:
127
+ client = InferenceClient(model="Qwen/Qwen2.5-7B-Instruct", token=HF_TOKEN)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ msgs = [{"role": "system", "content": PROMPTS.get(prompt, list(PROMPTS.values())[0])}]
130
+ msgs.extend(hist[-15:])
131
+ msgs.append({"role": "user", "content": text})
132
+ new_hist = hist + [{"role": "user", "content": text}]
133
+ ans = ""
134
+
135
+ yield gr.update(value={"text": ""}, interactive=False), new_hist + [{"role": "assistant", "content": "..."}], sid, "..."
136
+ try:
137
+ for token in client.chat_completion(messages=msgs, max_tokens=2048, stream=True):
138
+ if token.choices[0].delta.content:
139
+ ans += token.choices[0].delta.content
140
+ yield gr.update(value={"text": ""}, interactive=True), new_hist + [{"role": "assistant", "content": ans}], sid, f"{len(ans)}"
141
+
142
+ final = new_hist + [{"role": "assistant", "content": ans}]
143
+ if profile: save_chat(profile.username, sid, mgr.title(text), final, model)
144
+ mgr.record(user, "response_time", time.time() - start)
145
+ yield gr.update(value={"text": ""}, interactive=True), final, sid, "✅"
146
+ except Exception as e:
147
+ yield gr.update(value={"text": ""}, interactive=True), new_hist + [{"role": "assistant", "content": f"Error: {str(e)[:50]}"}], sid, "❌"
148
+
149
+ with gr.Blocks(theme=gr.themes.Slate()) as demo:
150
+ sid = gr.State(str(uuid.uuid4()))
151
+ db = gr.State([])
152
+
153
+ with gr.Row(equal_height=False):
154
+ with gr.Column(scale=1, elem_classes="sidebar-node"):
155
+ gr.HTML("<div class='logo'>⚡ NODEAI PRO</div>")
156
+ profile = gr.LoginButton(value="🔐 Login", size="sm")
157
+
158
+ with gr.Row():
159
+ gr.Button("➕ New", variant="primary", scale=1, size="sm")
160
+ gr.Button("🗑️ Delete", scale=1, size="sm")
161
+
162
+ gr.Markdown("### 🤖 Model")
163
+ model = gr.Dropdown(list(MODELS.keys()), value=list(MODELS.keys())[0], show_label=False)
164
+
165
+ gr.Markdown("### 🎯 Personality")
166
+ prompt = gr.Dropdown(list(PROMPTS.keys()), value=list(PROMPTS.keys())[0], show_label=False)
167
+
168
+ gr.Markdown("### 📚 History")
169
+ hist_list = gr.Dataset(components=[gr.Textbox(visible=False)]*2, samples=[], type="index")
170
+
171
+ gr.Markdown("### 📊 Stats")
172
+ stats = gr.Markdown("Ready")
173
+ gr.HTML("<div class='warning'>🔒 Your data is private & encrypted</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ with gr.Column(scale=4):
176
+ gr.HTML("<div style='text-align:center;margin:20px;'><h1 style='color:#38bdf8;'>🚀 NodeAI Genesis Pro</h1><p style='color:#94a3b8;'>Enterprise AI Chat • Multi-Model • Real-time Streaming</p></div>")
177
+ chat = gr.Chatbot(show_label=False, height="65vh")
178
+
179
+ with gr.Row():
180
+ status = gr.Textbox(value="Ready", interactive=False, scale=2)
181
+ model_info = gr.Textbox(value="", interactive=False, scale=1)
182
+
183
+ inp = gr.MultimodalTextbox(show_label=False, placeholder="Ask anything... (Ctrl+Enter)")
184
+ gr.HTML("<div class='warning'>⚡ Streaming responses • Auto-saves</div>")
185
+
186
+ demo.load(lambda p: get_hist(p), [profile], [db, hist_list]).then(lambda p: get_stats(p), [profile], [stats])
187
+ hist_list.select(lambda e, d: load_chat(e, d), [hist_list, db], [chat, sid])
188
+ inp.submit(chat_engine, [inp, chat, sid, model, prompt, profile], [inp, chat, sid, status]).then(lambda p: get_hist(p), [profile], [db, hist_list]).then(lambda p: get_stats(p), [profile], [stats])
189
 
 
 
 
190
  if __name__ == "__main__":
191
+ demo.queue().launch(css=CSS, share=False)