GuXSs commited on
Commit
7d3e98e
·
verified ·
1 Parent(s): 9d6afff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +601 -249
app.py CHANGED
@@ -20,6 +20,8 @@ import jwt
20
  from functools import wraps
21
  import hashlib
22
  import secrets
 
 
23
 
24
  # ----------------- Configuration & Models -----------------
25
  load_dotenv()
@@ -34,6 +36,7 @@ class Config:
34
  RATE_LIMIT_PER_HOUR: int = int(os.getenv("RATE_LIMIT_PER_HOUR", "100"))
35
  MAX_TOKENS: int = int(os.getenv("MAX_TOKENS", "500"))
36
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
 
37
 
38
  class GenerationRequest(BaseModel):
39
  prompt: str
@@ -41,6 +44,7 @@ class GenerationRequest(BaseModel):
41
  temperature: float = 0.7
42
  top_k: int = 50
43
  top_p: float = 0.95
 
44
 
45
  class UserCreate(BaseModel):
46
  name: str
@@ -80,19 +84,32 @@ class DatabaseManager:
80
  async def create_user(self, user_data: UserCreate, hf_user_id: str = None) -> Tuple[bool, str, str]:
81
  """Create user with enhanced validation and security"""
82
  try:
 
 
 
 
 
 
 
 
 
 
 
83
  # Generate secure API key
84
  api_key = self._generate_api_key()
85
 
86
  data = {
87
  "name": user_data.name.strip(),
88
  "email": user_data.email.strip(),
89
- "api_key": api_key, # Store plain API key (consider hashing in production)
90
  "hf_user_id": hf_user_id,
91
  "requests": 0,
92
  "plan": user_data.plan,
93
  "created_at": datetime.now().isoformat(),
94
  "last_request": None,
95
- "rate_limit_reset": (datetime.now() + timedelta(hours=1)).isoformat()
 
 
96
  }
97
 
98
  async with aiohttp.ClientSession() as session:
@@ -133,7 +150,7 @@ class DatabaseManager:
133
  logger.error(f"Error validating API key: {e}")
134
  return None
135
 
136
- async def check_rate_limit(self, user_id: int) -> bool:
137
  """Check if user has exceeded rate limit"""
138
  try:
139
  async with aiohttp.ClientSession() as session:
@@ -145,18 +162,19 @@ class DatabaseManager:
145
  data = await response.json()
146
  if data:
147
  user = data[0]
148
- reset_time = datetime.fromisoformat(user.get('rate_limit_reset', ''))
149
 
150
  if datetime.now() > reset_time:
151
  # Reset rate limit
152
  await self._reset_rate_limit(user_id)
153
- return True
154
 
155
- return user.get('requests_this_hour', 0) < Config().RATE_LIMIT_PER_HOUR
156
- return False
 
157
  except Exception as e:
158
  logger.error(f"Error checking rate limit: {e}")
159
- return False
160
 
161
  async def _reset_rate_limit(self, user_id: int):
162
  """Reset hourly rate limit"""
@@ -204,6 +222,21 @@ class DatabaseManager:
204
  )
205
  except Exception as e:
206
  logger.error(f"Error incrementing usage: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  # ----------------- Model Manager -----------------
209
  class ModelManager:
@@ -225,7 +258,8 @@ class ModelManager:
225
  None,
226
  lambda: AutoTokenizer.from_pretrained(
227
  self.config.MODEL_NAME,
228
- use_auth_token=self.config.HF_TOKEN
 
229
  )
230
  )
231
 
@@ -234,9 +268,10 @@ class ModelManager:
234
  None,
235
  lambda: AutoModelForCausalLM.from_pretrained(
236
  self.config.MODEL_NAME,
237
- use_auth_token=self.config.HF_TOKEN,
238
  device_map="auto",
239
- torch_dtype="auto"
 
240
  )
241
  )
242
 
@@ -268,8 +303,8 @@ class ModelManager:
268
  if len(request.prompt.strip()) == 0:
269
  return False, "⚠️ Prompt cannot be empty", 0
270
 
271
- if len(request.prompt) > 2000:
272
- return False, "⚠️ Prompt too long (max 2000 characters)", 0
273
 
274
  # Generate text
275
  loop = asyncio.get_event_loop()
@@ -282,6 +317,7 @@ class ModelManager:
282
  temperature=request.temperature,
283
  top_k=request.top_k,
284
  top_p=request.top_p,
 
285
  pad_token_id=self.tokenizer.eos_token_id,
286
  return_full_text=False
287
  )
@@ -296,12 +332,54 @@ class ModelManager:
296
  logger.error(f"Generation error: {e}")
297
  return False, f"❌ Generation failed: {str(e)}", 0
298
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  # ----------------- Service Layer -----------------
300
  class GemmaSaaSService:
301
  def __init__(self):
302
  self.config = Config()
303
  self.db = DatabaseManager(self.config)
304
  self.model_manager = ModelManager(self.config)
 
305
  self._validate_config()
306
 
307
  def _validate_config(self):
@@ -351,10 +429,15 @@ class GemmaSaaSService:
351
  )
352
 
353
  # Check rate limit
354
- if not await self.db.check_rate_limit(user['id']):
 
 
 
 
 
355
  return APIResponse(
356
  success=False,
357
- error="⚠️ Rate limit exceeded. Try again later."
358
  )
359
 
360
  # Generate text
@@ -370,7 +453,8 @@ class GemmaSaaSService:
370
  data={
371
  "generated_text": text,
372
  "tokens_used": tokens_used,
373
- "user_plan": user.get('plan', 'free')
 
374
  }
375
  )
376
  else:
@@ -396,6 +480,9 @@ class GemmaSaaSService:
396
  error="Invalid API key"
397
  )
398
 
 
 
 
399
  stats = {
400
  "name": user.get('name'),
401
  "email": user.get('email'),
@@ -405,7 +492,8 @@ class GemmaSaaSService:
405
  "requests_this_hour": user.get('requests_this_hour', 0),
406
  "rate_limit": self.config.RATE_LIMIT_PER_HOUR,
407
  "created_at": user.get('created_at'),
408
- "last_request": user.get('last_request')
 
409
  }
410
 
411
  return APIResponse(success=True, data=stats)
@@ -416,318 +504,431 @@ class GemmaSaaSService:
416
  success=False,
417
  error="Error retrieving stats"
418
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
  # ----------------- Enhanced UI -----------------
421
  class GradioInterface:
422
  def __init__(self, service: GemmaSaaSService):
423
  self.service = service
 
 
 
 
 
 
 
424
 
425
  def create_advanced_css(self):
426
  return """
427
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
428
-
429
  :root {
430
- --primary-gradient: linear-gradient(135deg, #566981 0%, #3A415A 100%);
431
- --secondary-gradient: linear-gradient(135deg, #3A415A 0%, #34344E 100%);
432
- --success-gradient: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
433
- --error-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
434
- --card-shadow: 0 10px 30px rgba(0,0,0,0.3);
435
- --hover-shadow: 0 15px 40px rgba(0,0,0,0.4);
436
- --bg-color: #566981;
437
- --text-color: #ffffff;
438
- --card-bg: #3A415A;
439
- --accent-color: #34344E;
440
- }
441
-
442
- * {
443
- scrollbar-width: thin;
444
- scrollbar-color: var(--card-bg) var(--bg-color);
445
- }
446
-
447
- *::-webkit-scrollbar {
448
- width: 8px;
449
- }
450
-
451
- *::-webkit-scrollbar-track {
452
- background: var(--bg-color);
453
- }
454
-
455
- *::-webkit-scrollbar-thumb {
456
- background-color: var(--card-bg);
457
- border-radius: 4px;
458
  }
459
 
460
  body, .gradio-container {
461
- background: var(--bg-color) !important;
462
- color: var(--text-color) !important;
463
- font-family: 'Inter', sans-serif !important;
464
- margin: 0 !important;
465
- padding: 0 !important;
466
  }
467
 
468
  .gradio-container {
469
  max-width: 1400px !important;
470
  margin: 0 auto !important;
471
- padding: 2rem !important;
472
  }
473
 
474
- .main-header {
475
  text-align: center;
476
- margin-bottom: 3rem;
477
- padding: 2rem;
478
- background: var(--card-bg) !important;
479
- border-radius: 20px;
480
- box-shadow: var(--card-shadow);
481
- border: 1px solid rgba(255,255,255,0.1);
 
482
  }
483
 
484
- .main-title {
485
  font-size: 3.5rem;
486
- font-weight: 700;
487
- background: linear-gradient(135deg, #ffffff 0%, #e2e8f0 100%);
488
  -webkit-background-clip: text;
489
  -webkit-text-fill-color: transparent;
490
- margin-bottom: 1rem;
491
  }
492
 
493
- .main-subtitle {
494
- font-size: 1.3rem;
495
- color: #cbd5e1;
496
- font-weight: 400;
 
 
497
  }
498
 
499
- .feature-card, .gr-form, .gr-box {
500
  background: var(--card-bg) !important;
501
- border-radius: 15px !important;
502
- padding: 2rem !important;
503
- box-shadow: var(--card-shadow) !important;
 
504
  transition: all 0.3s ease !important;
505
- border: 1px solid rgba(255,255,255,0.1) !important;
506
- color: var(--text-color) !important;
507
  }
508
 
509
- .feature-card:hover {
510
  transform: translateY(-5px);
511
- box-shadow: var(--hover-shadow);
512
  }
513
 
514
- .gr-button {
515
  border-radius: 12px !important;
 
516
  font-weight: 600 !important;
517
- padding: 0.8rem 2rem !important;
518
- transition: all 0.3s ease !important;
519
  border: none !important;
520
- text-transform: uppercase !important;
521
- letter-spacing: 1px !important;
522
- font-size: 0.9rem !important;
523
  }
524
 
525
- .gr-button-primary {
526
- background: var(--primary-gradient) !important;
527
  color: white !important;
528
  }
529
 
530
- .gr-button-secondary {
531
- background: var(--secondary-gradient) !important;
532
- color: white !important;
533
  }
534
 
535
- .gr-button:hover {
536
- transform: translateY(-2px) !important;
537
- box-shadow: 0 8px 25px rgba(0,0,0,0.4) !important;
 
538
  }
539
 
540
- .stats-card {
541
- background: var(--primary-gradient) !important;
542
- color: white;
543
- border-radius: 15px;
544
- padding: 2rem;
545
- margin: 1rem 0;
546
- box-shadow: var(--card-shadow);
547
  }
548
 
549
- .metric {
 
 
 
 
550
  text-align: center;
551
- padding: 1rem;
552
  }
553
 
554
- .metric-value {
555
  font-size: 2.5rem;
556
  font-weight: 700;
557
- color: #ffffff;
558
  }
559
 
560
- .metric-label {
561
  font-size: 0.9rem;
562
  opacity: 0.8;
563
  text-transform: uppercase;
564
  letter-spacing: 1px;
565
  }
566
 
567
- .alert-success {
568
- background: var(--success-gradient) !important;
569
- color: white;
570
- padding: 1rem;
571
- border-radius: 10px;
572
  margin: 1rem 0;
573
- box-shadow: var(--card-shadow);
574
  }
575
 
576
- .alert-error {
577
- background: var(--error-gradient) !important;
578
  color: white;
579
- padding: 1rem;
580
- border-radius: 10px;
581
- margin: 1rem 0;
582
- box-shadow: var(--card-shadow);
583
  }
584
 
585
- .gr-textbox, .gr-dropdown, .gr-slider {
586
- background: var(--accent-color) !important;
587
- border: 1px solid rgba(255,255,255,0.1) !important;
588
- border-radius: 8px !important;
589
- color: var(--text-color) !important;
590
- }
591
-
592
- .gr-textbox:focus, .gr-dropdown:focus {
593
- border-color: rgba(255,255,255,0.3) !important;
594
- box-shadow: 0 0 0 3px rgba(255,255,255,0.1) !important;
595
  }
596
 
597
- .gr-tab-nav {
598
  background: var(--card-bg) !important;
599
  border-radius: 12px !important;
600
  padding: 0.5rem !important;
 
601
  }
602
 
603
- .gr-tab-nav button {
604
- background: transparent !important;
605
- color: var(--text-color) !important;
606
- border: none !important;
607
  border-radius: 8px !important;
608
- padding: 0.8rem 1.5rem !important;
609
- margin: 0 0.25rem !important;
610
- transition: all 0.3s ease !important;
611
  }
612
 
613
- .gr-tab-nav button.selected, .gr-tab-nav button:hover {
614
- background: var(--accent-color) !important;
615
  color: white !important;
616
  }
617
 
618
- .gr-accordion {
619
- background: var(--accent-color) !important;
620
- border-radius: 10px !important;
621
- border: 1px solid rgba(255,255,255,0.1) !important;
 
 
622
  }
623
 
624
- .gr-accordion summary {
625
- color: var(--text-color) !important;
626
- font-weight: 600 !important;
627
  }
628
 
629
  label {
630
- color: var(--text-color) !important;
631
  font-weight: 600 !important;
 
632
  }
633
 
634
- .gr-markdown {
635
- color: var(--text-color) !important;
 
 
 
 
 
636
  }
637
 
638
- .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
639
- color: #ffffff !important;
 
 
 
 
640
  }
641
 
642
- .dark-coming-soon {
643
- background: var(--card-bg) !important;
644
- border-radius: 20px !important;
645
- padding: 4rem 2rem !important;
646
- text-align: center !important;
647
- border: 2px dashed rgba(255,255,255,0.2) !important;
648
  }
649
 
650
- .dark-coming-soon h3 {
651
- color: #ffffff !important;
652
- font-size: 2rem !important;
653
- margin-bottom: 1rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  }
655
 
656
- .dark-coming-soon p {
657
- color: #cbd5e1 !important;
658
- font-size: 1.1rem !important;
659
  }
660
  """
661
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  async def create_interface(self):
663
  """Create the enhanced Gradio interface"""
664
- with gr.Blocks(css=self.create_advanced_css(), title="Gemma SaaS Platform") as app:
 
 
 
 
665
 
666
  # Header
667
- with gr.Row():
668
- gr.HTML("""
669
- <div class="main-header">
670
- <h1 class="main-title">🚀 Gemma SaaS Platform</h1>
671
- <p class="main-subtitle">Professional AI Text Generation with Advanced Analytics</p>
672
- </div>
673
- """)
674
 
675
  # Main Content
676
- with gr.Tabs():
677
 
678
  # Playground Tab
679
- with gr.Tab("🎮 Playground", elem_classes=["feature-card"]):
680
  with gr.Row():
681
  with gr.Column(scale=1):
682
- gr.Markdown("### 📋 Generation Parameters")
683
  api_key_playground = gr.Textbox(
684
  label="🔑 API Key",
685
  type="password",
686
- placeholder="Enter your API key..."
 
687
  )
688
 
689
- with gr.Accordion("⚙️ Advanced Settings", open=False):
690
  max_tokens_input = gr.Slider(
691
- minimum=50, maximum=500, value=200,
692
- label="Max Tokens"
 
693
  )
694
  temperature_input = gr.Slider(
695
  minimum=0.1, maximum=2.0, value=0.7,
696
- label="Temperature"
 
697
  )
698
  top_k_input = gr.Slider(
699
  minimum=1, maximum=100, value=50,
700
- label="Top K"
 
701
  )
702
  top_p_input = gr.Slider(
703
  minimum=0.1, maximum=1.0, value=0.95,
704
- label="Top P"
 
 
 
 
 
 
705
  )
706
 
707
  with gr.Column(scale=2):
708
- gr.Markdown("### 💭 Text Generation")
 
 
 
709
  prompt_input = gr.Textbox(
710
- label="✍️ Prompt",
711
- lines=8,
712
- placeholder="Enter your prompt here... (e.g., 'Write a short story about a robot discovering emotions')"
 
 
713
  )
714
 
715
  with gr.Row():
716
  generate_btn = gr.Button(
717
  "🚀 Generate",
718
- elem_classes=["gr-button-primary"],
719
  variant="primary"
720
  )
721
- clear_btn = gr.ClearButton(
722
- components=[prompt_input],
723
- value="🗑️ Clear",
724
- elem_classes=["gr-button-secondary"]
725
  )
726
 
727
  output_text = gr.Textbox(
728
  label="📝 Generated Text",
729
- lines=12,
730
- interactive=False
 
731
  )
732
 
733
  # Stats display
@@ -737,21 +938,28 @@ class GradioInterface:
737
  )
738
 
739
  # Profile Tab
740
- with gr.Tab("👤 Profile", elem_classes=["feature-card"]):
741
  with gr.Row():
742
- with gr.Column():
743
  gr.Markdown("### 🆕 Create Account")
744
- name_input = gr.Textbox(label="👤 Full Name")
745
- email_input = gr.Textbox(label="📧 Email Address")
 
 
 
 
 
 
746
  plan_input = gr.Dropdown(
747
  choices=["free", "pro", "enterprise"],
748
  value="free",
749
- label="📋 Plan"
 
750
  )
751
 
752
  create_btn = gr.Button(
753
  "✨ Create API Key",
754
- elem_classes=["gr-button-primary"],
755
  variant="primary"
756
  )
757
 
@@ -759,52 +967,70 @@ class GradioInterface:
759
  api_key_display = gr.Textbox(
760
  label="🔑 Your API Key",
761
  interactive=False,
762
- visible=False
 
763
  )
764
 
765
- with gr.Column():
766
  gr.Markdown("### 📊 Account Statistics")
767
  stats_api_key = gr.Textbox(
768
  label="🔑 API Key",
769
  type="password",
770
- placeholder="Enter API key to view stats"
 
771
  )
772
 
773
  refresh_stats_btn = gr.Button(
774
  "🔄 Refresh Stats",
775
- elem_classes=["gr-button-secondary"]
776
  )
777
 
778
  user_stats_display = gr.HTML()
779
 
780
  # Analytics Tab
781
- with gr.Tab("📈 Analytics", elem_classes=["feature-card"]):
782
- gr.Markdown("### 📊 Platform Analytics")
783
 
784
- # Enhanced dark theme analytics placeholder
785
- gr.HTML("""
786
- <div class="dark-coming-soon">
787
- <h3>📈 Advanced Analytics Coming Soon</h3>
788
- <p>Real-time usage metrics, performance insights, and detailed reporting.</p>
789
- <div style="margin-top: 2rem; display: flex; justify-content: center; gap: 2rem;">
790
- <div style="text-align: center;">
791
- <div style="font-size: 3rem;">⚡</div>
792
- <div>Real-time Metrics</div>
793
- </div>
794
- <div style="text-align: center;">
795
- <div style="font-size: 3rem;">📊</div>
796
- <div>Usage Analytics</div>
797
- </div>
798
- <div style="text-align: center;">
799
- <div style="font-size: 3rem;">🎯</div>
800
- <div>Performance Insights</div>
801
- </div>
802
- </div>
803
- </div>
804
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
 
806
  # Event Handlers
807
- async def handle_generation(prompt, api_key, max_tokens, temperature, top_k, top_p):
808
  if not api_key.strip():
809
  return "⚠️ Please enter your API key", {}, False
810
 
@@ -814,29 +1040,50 @@ class GradioInterface:
814
  max_tokens=max_tokens,
815
  temperature=temperature,
816
  top_k=top_k,
817
- top_p=top_p
 
818
  )
819
 
820
  if response.success:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
  return (
822
  response.data["generated_text"],
823
- response.data,
824
  True
825
  )
826
  else:
827
- return response.error, {}, False
 
 
 
 
828
 
829
  async def handle_user_creation(name, email, plan):
830
  if not name or not name.strip():
831
  return (
832
- f'<div class="alert-error">❌ Name is required</div>',
833
  "",
834
  False
835
  )
836
 
837
  if not email or not email.strip():
838
  return (
839
- f'<div class="alert-error">❌ Email is required</div>',
840
  "",
841
  False
842
  )
@@ -845,13 +1092,13 @@ class GradioInterface:
845
 
846
  if response.success:
847
  return (
848
- f'<div class="alert-success">✅ Account created successfully!</div>',
849
  response.data["api_key"],
850
  True
851
  )
852
  else:
853
  return (
854
- f'<div class="alert-error">❌ {response.error}</div>',
855
  "",
856
  False
857
  )
@@ -862,43 +1109,136 @@ class GradioInterface:
862
  if response.success:
863
  stats = response.data
864
  return f"""
865
- <div class="stats-card">
866
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
867
- <div class="metric">
868
- <div class="metric-value">{stats['total_requests']}</div>
869
- <div class="metric-label">Total Requests</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  </div>
871
- <div class="metric">
872
- <div class="metric-value">{stats['tokens_used']:,}</div>
873
- <div class="metric-label">Tokens Used</div>
874
  </div>
875
- <div class="metric">
876
- <div class="metric-value">{stats['requests_this_hour']}/{stats['rate_limit']}</div>
877
- <div class="metric-label">Hourly Usage</div>
878
  </div>
879
- <div class="metric">
880
- <div class="metric-value">{stats['plan'].title()}</div>
881
- <div class="metric-label">Current Plan</div>
882
  </div>
883
  </div>
884
- <hr style="margin: 2rem 0; opacity: 0.3;">
885
- <p><strong>Account:</strong> {stats['name']} ({stats['email']})</p>
886
- <p><strong>Member since:</strong> {stats['created_at'][:10] if stats['created_at'] else 'N/A'}</p>
887
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
888
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889
  else:
890
- return f'<div class="alert-error">❌ {response.error}</div>'
 
 
 
 
 
891
 
892
  # Wire up events
893
  generate_btn.click(
894
  fn=handle_generation,
895
  inputs=[
896
  prompt_input, api_key_playground, max_tokens_input,
897
- temperature_input, top_k_input, top_p_input
898
  ],
899
  outputs=[output_text, generation_stats, generation_stats]
900
  )
901
 
 
 
 
 
 
 
902
  create_btn.click(
903
  fn=handle_user_creation,
904
  inputs=[name_input, email_input, plan_input],
@@ -910,6 +1250,18 @@ class GradioInterface:
910
  inputs=[stats_api_key],
911
  outputs=[user_stats_display]
912
  )
 
 
 
 
 
 
 
 
 
 
 
 
913
 
914
  return app
915
 
@@ -936,7 +1288,7 @@ async def main():
936
  favicon_path=None,
937
  ssl_keyfile=None,
938
  ssl_certfile=None,
939
- auth=None, # Add your auth function here if needed
940
  max_threads=10
941
  )
942
 
 
20
  from functools import wraps
21
  import hashlib
22
  import secrets
23
+ import plotly.graph_objects as go
24
+ from plotly.subplots import make_subplots
25
 
26
  # ----------------- Configuration & Models -----------------
27
  load_dotenv()
 
36
  RATE_LIMIT_PER_HOUR: int = int(os.getenv("RATE_LIMIT_PER_HOUR", "100"))
37
  MAX_TOKENS: int = int(os.getenv("MAX_TOKENS", "500"))
38
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
39
+ ADMIN_EMAIL: str = os.getenv("ADMIN_EMAIL", "")
40
 
41
  class GenerationRequest(BaseModel):
42
  prompt: str
 
44
  temperature: float = 0.7
45
  top_k: int = 50
46
  top_p: float = 0.95
47
+ repetition_penalty: float = 1.0
48
 
49
  class UserCreate(BaseModel):
50
  name: str
 
84
  async def create_user(self, user_data: UserCreate, hf_user_id: str = None) -> Tuple[bool, str, str]:
85
  """Create user with enhanced validation and security"""
86
  try:
87
+ # Check if user already exists
88
+ async with aiohttp.ClientSession() as session:
89
+ async with session.get(
90
+ f"{self.config.SUPABASE_URL}/rest/v1/users?email=eq.{user_data.email}",
91
+ headers=self.headers
92
+ ) as response:
93
+ if response.status == 200:
94
+ existing_users = await response.json()
95
+ if existing_users:
96
+ return False, "❌ User with this email already exists", ""
97
+
98
  # Generate secure API key
99
  api_key = self._generate_api_key()
100
 
101
  data = {
102
  "name": user_data.name.strip(),
103
  "email": user_data.email.strip(),
104
+ "api_key": api_key,
105
  "hf_user_id": hf_user_id,
106
  "requests": 0,
107
  "plan": user_data.plan,
108
  "created_at": datetime.now().isoformat(),
109
  "last_request": None,
110
+ "requests_this_hour": 0,
111
+ "rate_limit_reset": (datetime.now() + timedelta(hours=1)).isoformat(),
112
+ "tokens_used": 0
113
  }
114
 
115
  async with aiohttp.ClientSession() as session:
 
150
  logger.error(f"Error validating API key: {e}")
151
  return None
152
 
153
+ async def check_rate_limit(self, user_id: int) -> Tuple[bool, int]:
154
  """Check if user has exceeded rate limit"""
155
  try:
156
  async with aiohttp.ClientSession() as session:
 
162
  data = await response.json()
163
  if data:
164
  user = data[0]
165
+ reset_time = datetime.fromisoformat(user.get('rate_limit_reset', datetime.now().isoformat()))
166
 
167
  if datetime.now() > reset_time:
168
  # Reset rate limit
169
  await self._reset_rate_limit(user_id)
170
+ return True, 0
171
 
172
+ requests_this_hour = user.get('requests_this_hour', 0)
173
+ return requests_this_hour < self.config.RATE_LIMIT_PER_HOUR, requests_this_hour
174
+ return False, 0
175
  except Exception as e:
176
  logger.error(f"Error checking rate limit: {e}")
177
+ return False, 0
178
 
179
  async def _reset_rate_limit(self, user_id: int):
180
  """Reset hourly rate limit"""
 
222
  )
223
  except Exception as e:
224
  logger.error(f"Error incrementing usage: {e}")
225
+
226
+ async def get_all_users_stats(self):
227
+ """Get statistics for all users (admin only)"""
228
+ try:
229
+ async with aiohttp.ClientSession() as session:
230
+ async with session.get(
231
+ f"{self.config.SUPABASE_URL}/rest/v1/users?select=*",
232
+ headers=self.headers
233
+ ) as response:
234
+ if response.status == 200:
235
+ return await response.json()
236
+ return []
237
+ except Exception as e:
238
+ logger.error(f"Error getting all users stats: {e}")
239
+ return []
240
 
241
  # ----------------- Model Manager -----------------
242
  class ModelManager:
 
258
  None,
259
  lambda: AutoTokenizer.from_pretrained(
260
  self.config.MODEL_NAME,
261
+ token=self.config.HF_TOKEN,
262
+ trust_remote_code=True
263
  )
264
  )
265
 
 
268
  None,
269
  lambda: AutoModelForCausalLM.from_pretrained(
270
  self.config.MODEL_NAME,
271
+ token=self.config.HF_TOKEN,
272
  device_map="auto",
273
+ torch_dtype="auto",
274
+ trust_remote_code=True
275
  )
276
  )
277
 
 
303
  if len(request.prompt.strip()) == 0:
304
  return False, "⚠️ Prompt cannot be empty", 0
305
 
306
+ if len(request.prompt) > 4000:
307
+ return False, "⚠️ Prompt too long (max 4000 characters)", 0
308
 
309
  # Generate text
310
  loop = asyncio.get_event_loop()
 
317
  temperature=request.temperature,
318
  top_k=request.top_k,
319
  top_p=request.top_p,
320
+ repetition_penalty=request.repetition_penalty,
321
  pad_token_id=self.tokenizer.eos_token_id,
322
  return_full_text=False
323
  )
 
332
  logger.error(f"Generation error: {e}")
333
  return False, f"❌ Generation failed: {str(e)}", 0
334
 
335
+ # ----------------- Analytics Manager -----------------
336
+ class AnalyticsManager:
337
+ def __init__(self, db: DatabaseManager):
338
+ self.db = db
339
+
340
+ async def generate_usage_plot(self, user_data: Dict) -> go.Figure:
341
+ """Generate a usage plot for the user"""
342
+ fig = make_subplots(
343
+ rows=1, cols=2,
344
+ subplot_titles=('Requests Over Time', 'Token Usage Distribution'),
345
+ specs=[[{"type": "scatter"}, {"type": "pie"}]]
346
+ )
347
+
348
+ # Mock data for the chart (in a real app, you'd get this from the database)
349
+ dates = [datetime.now() - timedelta(days=i) for i in range(7, 0, -1)]
350
+ requests = [user_data.get('requests', 0) // 7] * 7
351
+
352
+ fig.add_trace(
353
+ go.Scatter(x=dates, y=requests, mode='lines+markers', name='Requests'),
354
+ row=1, col=1
355
+ )
356
+
357
+ # Token usage pie chart
358
+ token_categories = ['Generated', 'Prompt', 'Other']
359
+ token_values = [user_data.get('tokens_used', 0), user_data.get('tokens_used', 0) // 3, user_data.get('tokens_used', 0) // 5]
360
+
361
+ fig.add_trace(
362
+ go.Pie(labels=token_categories, values=token_values, name="Token Usage"),
363
+ row=1, col=2
364
+ )
365
+
366
+ fig.update_layout(
367
+ height=400,
368
+ showlegend=True,
369
+ paper_bgcolor='rgba(0,0,0,0)',
370
+ plot_bgcolor='rgba(0,0,0,0)',
371
+ font=dict(color='white')
372
+ )
373
+
374
+ return fig
375
+
376
  # ----------------- Service Layer -----------------
377
  class GemmaSaaSService:
378
  def __init__(self):
379
  self.config = Config()
380
  self.db = DatabaseManager(self.config)
381
  self.model_manager = ModelManager(self.config)
382
+ self.analytics_manager = AnalyticsManager(self.db)
383
  self._validate_config()
384
 
385
  def _validate_config(self):
 
429
  )
430
 
431
  # Check rate limit
432
+ can_make_request, requests_used = await self.db.check_rate_limit(user['id'])
433
+ if not can_make_request:
434
+ reset_time = datetime.fromisoformat(user.get('rate_limit_reset', ''))
435
+ time_remaining = reset_time - datetime.now()
436
+ mins_remaining = max(0, int(time_remaining.total_seconds() / 60))
437
+
438
  return APIResponse(
439
  success=False,
440
+ error=f"⚠️ Rate limit exceeded ({requests_used}/{self.config.RATE_LIMIT_PER_HOUR}). Try again in {mins_remaining} minutes."
441
  )
442
 
443
  # Generate text
 
453
  data={
454
  "generated_text": text,
455
  "tokens_used": tokens_used,
456
+ "user_plan": user.get('plan', 'free'),
457
+ "requests_remaining": self.config.RATE_LIMIT_PER_HOUR - requests_used - 1
458
  }
459
  )
460
  else:
 
480
  error="Invalid API key"
481
  )
482
 
483
+ # Generate analytics plot
484
+ plot = await self.analytics_manager.generate_usage_plot(user)
485
+
486
  stats = {
487
  "name": user.get('name'),
488
  "email": user.get('email'),
 
492
  "requests_this_hour": user.get('requests_this_hour', 0),
493
  "rate_limit": self.config.RATE_LIMIT_PER_HOUR,
494
  "created_at": user.get('created_at'),
495
+ "last_request": user.get('last_request'),
496
+ "plot": plot
497
  }
498
 
499
  return APIResponse(success=True, data=stats)
 
504
  success=False,
505
  error="Error retrieving stats"
506
  )
507
+
508
+ async def get_admin_stats(self, api_key: str) -> APIResponse:
509
+ """Get admin statistics (only for admin users)"""
510
+ try:
511
+ user = await self.db.validate_api_key(api_key)
512
+ if not user or user.get('email') != self.config.ADMIN_EMAIL:
513
+ return APIResponse(
514
+ success=False,
515
+ error="Unauthorized: Admin access required"
516
+ )
517
+
518
+ all_users = await self.db.get_all_users_stats()
519
+
520
+ total_requests = sum(user.get('requests', 0) for user in all_users)
521
+ total_tokens = sum(user.get('tokens_used', 0) for user in all_users)
522
+ active_users = len([user for user in all_users if user.get('last_request')])
523
+
524
+ # Generate admin dashboard plot
525
+ fig = go.Figure()
526
+
527
+ user_names = [user.get('name', 'Unknown') for user in all_users]
528
+ user_requests = [user.get('requests', 0) for user in all_users]
529
+
530
+ fig.add_trace(go.Bar(
531
+ x=user_names,
532
+ y=user_requests,
533
+ name="User Requests",
534
+ marker_color='indianred'
535
+ ))
536
+
537
+ fig.update_layout(
538
+ title='User Activity',
539
+ xaxis_title='Users',
540
+ yaxis_title='Number of Requests',
541
+ paper_bgcolor='rgba(0,0,0,0)',
542
+ plot_bgcolor='rgba(0,0,0,0)',
543
+ font=dict(color='white')
544
+ )
545
+
546
+ stats = {
547
+ "total_users": len(all_users),
548
+ "active_users": active_users,
549
+ "total_requests": total_requests,
550
+ "total_tokens": total_tokens,
551
+ "users": all_users,
552
+ "plot": fig
553
+ }
554
+
555
+ return APIResponse(success=True, data=stats)
556
+
557
+ except Exception as e:
558
+ logger.error(f"Error getting admin stats: {e}")
559
+ return APIResponse(
560
+ success=False,
561
+ error="Error retrieving admin stats"
562
+ )
563
 
564
  # ----------------- Enhanced UI -----------------
565
  class GradioInterface:
566
  def __init__(self, service: GemmaSaaSService):
567
  self.service = service
568
+ self.examples = [
569
+ ["Write a short story about a robot discovering emotions"],
570
+ ["Explain quantum computing in simple terms"],
571
+ ["Create a recipe for chocolate chip cookies"],
572
+ ["Write a poem about the changing seasons"],
573
+ ["How can I improve my time management skills?"]
574
+ ]
575
 
576
  def create_advanced_css(self):
577
  return """
 
 
578
  :root {
579
+ --primary: #6366f1;
580
+ --primary-dark: #4338ca;
581
+ --secondary: #10b981;
582
+ --accent: #f59e0b;
583
+ --danger: #ef4444;
584
+ --dark: #1f2937;
585
+ --darker: #111827;
586
+ --light: #f3f4f6;
587
+ --lighter: #f9fafb;
588
+ --card-bg: rgba(255, 255, 255, 0.05);
589
+ --card-border: rgba(255, 255, 255, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  }
591
 
592
  body, .gradio-container {
593
+ background: linear-gradient(135deg, var(--darker) 0%, var(--dark) 100%) !important;
594
+ color: var(--lighter) !important;
595
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
 
 
596
  }
597
 
598
  .gradio-container {
599
  max-width: 1400px !important;
600
  margin: 0 auto !important;
601
+ padding: 1rem !important;
602
  }
603
 
604
+ .header {
605
  text-align: center;
606
+ padding: 2rem 1rem;
607
+ margin-bottom: 2rem;
608
+ background: var(--card-bg);
609
+ backdrop-filter: blur(10px);
610
+ border-radius: 16px;
611
+ border: 1px solid var(--card-border);
612
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
613
  }
614
 
615
+ .header h1 {
616
  font-size: 3.5rem;
617
+ font-weight: 800;
618
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
619
  -webkit-background-clip: text;
620
  -webkit-text-fill-color: transparent;
621
+ margin-bottom: 0.5rem;
622
  }
623
 
624
+ .header p {
625
+ font-size: 1.2rem;
626
+ color: var(--light);
627
+ opacity: 0.8;
628
+ max-width: 600px;
629
+ margin: 0 auto;
630
  }
631
 
632
+ .card {
633
  background: var(--card-bg) !important;
634
+ backdrop-filter: blur(10px);
635
+ border-radius: 16px !important;
636
+ border: 1px solid var(--card-border) !important;
637
+ padding: 1.5rem !important;
638
  transition: all 0.3s ease !important;
639
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15) !important;
 
640
  }
641
 
642
+ .card:hover {
643
  transform: translateY(-5px);
644
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25) !important;
645
  }
646
 
647
+ .btn {
648
  border-radius: 12px !important;
649
+ padding: 0.75rem 1.5rem !important;
650
  font-weight: 600 !important;
651
+ transition: all 0.2s ease !important;
 
652
  border: none !important;
 
 
 
653
  }
654
 
655
+ .btn-primary {
656
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%) !important;
657
  color: white !important;
658
  }
659
 
660
+ .btn-primary:hover {
661
+ transform: translateY(-2px);
662
+ box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4) !important;
663
  }
664
 
665
+ .btn-secondary {
666
+ background: rgba(255, 255, 255, 0.1) !important;
667
+ color: white !important;
668
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
669
  }
670
 
671
+ .btn-secondary:hover {
672
+ background: rgba(255, 255, 255, 0.2) !important;
673
+ transform: translateY(-2px);
 
 
 
 
674
  }
675
 
676
+ .stat-card {
677
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
678
+ color: white;
679
+ border-radius: 12px;
680
+ padding: 1.5rem;
681
  text-align: center;
 
682
  }
683
 
684
+ .stat-value {
685
  font-size: 2.5rem;
686
  font-weight: 700;
687
+ margin-bottom: 0.5rem;
688
  }
689
 
690
+ .stat-label {
691
  font-size: 0.9rem;
692
  opacity: 0.8;
693
  text-transform: uppercase;
694
  letter-spacing: 1px;
695
  }
696
 
697
+ .alert {
698
+ padding: 1rem 1.5rem;
699
+ border-radius: 12px;
 
 
700
  margin: 1rem 0;
 
701
  }
702
 
703
+ .alert-success {
704
+ background: linear-gradient(135deg, var(--secondary) 0%, #059669 100%);
705
  color: white;
 
 
 
 
706
  }
707
 
708
+ .alert-error {
709
+ background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);
710
+ color: white;
 
 
 
 
 
 
 
711
  }
712
 
713
+ .tab-nav {
714
  background: var(--card-bg) !important;
715
  border-radius: 12px !important;
716
  padding: 0.5rem !important;
717
+ margin-bottom: 1.5rem !important;
718
  }
719
 
720
+ .tab-nav button {
 
 
 
721
  border-radius: 8px !important;
722
+ padding: 0.75rem 1.5rem !important;
723
+ font-weight: 600 !important;
 
724
  }
725
 
726
+ .tab-nav button.selected {
727
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%) !important;
728
  color: white !important;
729
  }
730
 
731
+ input, textarea, select {
732
+ background: rgba(255, 255, 255, 0.05) !important;
733
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
734
+ border-radius: 12px !important;
735
+ color: white !important;
736
+ padding: 0.75rem 1rem !important;
737
  }
738
 
739
+ input:focus, textarea:focus, select:focus {
740
+ border-color: var(--primary) !important;
741
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2) !important;
742
  }
743
 
744
  label {
745
+ color: var(--light) !important;
746
  font-weight: 600 !important;
747
+ margin-bottom: 0.5rem !important;
748
  }
749
 
750
+ .footer {
751
+ text-align: center;
752
+ padding: 2rem 1rem;
753
+ margin-top: 3rem;
754
+ color: var(--light);
755
+ opacity: 0.7;
756
+ font-size: 0.9rem;
757
  }
758
 
759
+ .badge {
760
+ display: inline-block;
761
+ padding: 0.25rem 0.75rem;
762
+ border-radius: 9999px;
763
+ font-size: 0.75rem;
764
+ font-weight: 700;
765
  }
766
 
767
+ .badge-free {
768
+ background: var(--secondary);
769
+ color: white;
 
 
 
770
  }
771
 
772
+ .badge-pro {
773
+ background: var(--primary);
774
+ color: white;
775
+ }
776
+
777
+ .badge-enterprise {
778
+ background: var(--accent);
779
+ color: white;
780
+ }
781
+
782
+ .example-card {
783
+ cursor: pointer;
784
+ transition: all 0.2s ease;
785
+ }
786
+
787
+ .example-card:hover {
788
+ transform: translateY(-3px);
789
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2) !important;
790
+ }
791
+
792
+ .copy-btn {
793
+ position: absolute;
794
+ top: 0.5rem;
795
+ right: 0.5rem;
796
+ background: rgba(255, 255, 255, 0.1);
797
+ border: none;
798
+ border-radius: 8px;
799
+ padding: 0.5rem;
800
+ cursor: pointer;
801
+ opacity: 0.7;
802
+ transition: all 0.2s ease;
803
+ }
804
+
805
+ .copy-btn:hover {
806
+ opacity: 1;
807
+ background: rgba(255, 255, 255, 0.2);
808
  }
809
 
810
+ .dark-mode-plot {
811
+ background: transparent !important;
 
812
  }
813
  """
814
 
815
+ def create_header(self):
816
+ return gr.HTML("""
817
+ <div class="header">
818
+ <h1>🚀 Gemma AI Platform</h1>
819
+ <p>Advanced text generation powered by state-of-the-art AI models. Create content, analyze text, and unlock new possibilities.</p>
820
+ </div>
821
+ """)
822
+
823
+ def create_footer(self):
824
+ return gr.HTML("""
825
+ <div class="footer">
826
+ <p>Gemma AI Platform © 2023 | Built with ❤️ using Hugging Face, Gradio, and Supabase</p>
827
+ <p>Version 2.0 | <a href="#" style="color: #ccc; text-decoration: none;">Terms of Service</a> | <a href="#" style="color: #ccc; text-decoration: none;">Privacy Policy</a></p>
828
+ </div>
829
+ """)
830
+
831
+ def create_examples_component(self):
832
+ with gr.Column(elem_classes=["card"]):
833
+ gr.Markdown("### 💡 Example Prompts")
834
+
835
+ examples_html = """
836
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;">
837
+ """
838
+
839
+ for i, example in enumerate(self.examples):
840
+ examples_html += f"""
841
+ <div class="card example-card" onclick="document.getElementById('prompt-input').value = `{example[0]}`">
842
+ <div style="font-size: 0.9rem; opacity: 0.9;">{example[0]}</div>
843
+ </div>
844
+ """
845
+
846
+ examples_html += "</div>"
847
+
848
+ return gr.HTML(examples_html)
849
+
850
  async def create_interface(self):
851
  """Create the enhanced Gradio interface"""
852
+ with gr.Blocks(
853
+ css=self.create_advanced_css(),
854
+ title="Gemma AI Platform",
855
+ theme=gr.themes.Default(primary_hue="indigo", secondary_hue="emerald")
856
+ ) as app:
857
 
858
  # Header
859
+ self.create_header()
 
 
 
 
 
 
860
 
861
  # Main Content
862
+ with gr.Tabs(elem_classes=["tab-nav"]):
863
 
864
  # Playground Tab
865
+ with gr.Tab("🎮 Playground", elem_classes=["card"]):
866
  with gr.Row():
867
  with gr.Column(scale=1):
868
+ gr.Markdown("### ⚙️ Configuration")
869
  api_key_playground = gr.Textbox(
870
  label="🔑 API Key",
871
  type="password",
872
+ placeholder="Enter your API key...",
873
+ elem_classes=["input"]
874
  )
875
 
876
+ with gr.Accordion("Advanced Settings", open=False):
877
  max_tokens_input = gr.Slider(
878
+ minimum=50, maximum=1000, value=200,
879
+ label="Max Tokens",
880
+ info="Maximum number of tokens to generate"
881
  )
882
  temperature_input = gr.Slider(
883
  minimum=0.1, maximum=2.0, value=0.7,
884
+ label="Temperature",
885
+ info="Higher values = more creative, lower values = more focused"
886
  )
887
  top_k_input = gr.Slider(
888
  minimum=1, maximum=100, value=50,
889
+ label="Top K",
890
+ info="Consider only the top K tokens"
891
  )
892
  top_p_input = gr.Slider(
893
  minimum=0.1, maximum=1.0, value=0.95,
894
+ label="Top P",
895
+ info="Nucleus sampling: consider only tokens with cumulative probability"
896
+ )
897
+ repetition_penalty_input = gr.Slider(
898
+ minimum=0.1, maximum=2.0, value=1.0,
899
+ label="Repetition Penalty",
900
+ info="Penalize repeated tokens (1.0 = no penalty)"
901
  )
902
 
903
  with gr.Column(scale=2):
904
+ # Examples
905
+ self.create_examples_component()
906
+
907
+ gr.Markdown("### 💬 Text Generation")
908
  prompt_input = gr.Textbox(
909
+ label="✍️ Your Prompt",
910
+ lines=6,
911
+ placeholder="Enter your prompt here... (e.g., 'Write a short story about a robot discovering emotions')",
912
+ elem_id="prompt-input",
913
+ elem_classes=["input"]
914
  )
915
 
916
  with gr.Row():
917
  generate_btn = gr.Button(
918
  "🚀 Generate",
919
+ elem_classes=["btn", "btn-primary"],
920
  variant="primary"
921
  )
922
+ clear_btn = gr.Button(
923
+ "🗑️ Clear",
924
+ elem_classes=["btn", "btn-secondary"]
 
925
  )
926
 
927
  output_text = gr.Textbox(
928
  label="📝 Generated Text",
929
+ lines=8,
930
+ interactive=False,
931
+ elem_classes=["input"]
932
  )
933
 
934
  # Stats display
 
938
  )
939
 
940
  # Profile Tab
941
+ with gr.Tab("👤 Profile", elem_classes=["card"]):
942
  with gr.Row():
943
+ with gr.Column(scale=1):
944
  gr.Markdown("### 🆕 Create Account")
945
+ name_input = gr.Textbox(
946
+ label="👤 Full Name",
947
+ elem_classes=["input"]
948
+ )
949
+ email_input = gr.Textbox(
950
+ label="📧 Email Address",
951
+ elem_classes=["input"]
952
+ )
953
  plan_input = gr.Dropdown(
954
  choices=["free", "pro", "enterprise"],
955
  value="free",
956
+ label="📋 Plan",
957
+ elem_classes=["input"]
958
  )
959
 
960
  create_btn = gr.Button(
961
  "✨ Create API Key",
962
+ elem_classes=["btn", "btn-primary"],
963
  variant="primary"
964
  )
965
 
 
967
  api_key_display = gr.Textbox(
968
  label="🔑 Your API Key",
969
  interactive=False,
970
+ visible=False,
971
+ elem_classes=["input"]
972
  )
973
 
974
+ with gr.Column(scale=1):
975
  gr.Markdown("### 📊 Account Statistics")
976
  stats_api_key = gr.Textbox(
977
  label="🔑 API Key",
978
  type="password",
979
+ placeholder="Enter API key to view stats",
980
+ elem_classes=["input"]
981
  )
982
 
983
  refresh_stats_btn = gr.Button(
984
  "🔄 Refresh Stats",
985
+ elem_classes=["btn", "btn-secondary"]
986
  )
987
 
988
  user_stats_display = gr.HTML()
989
 
990
  # Analytics Tab
991
+ with gr.Tab("📈 Analytics", elem_classes=["card"]):
992
+ gr.Markdown("### 📊 Usage Analytics")
993
 
994
+ with gr.Row():
995
+ with gr.Column(scale=1):
996
+ analytics_api_key = gr.Textbox(
997
+ label="🔑 API Key",
998
+ type="password",
999
+ placeholder="Enter API key to view analytics",
1000
+ elem_classes=["input"]
1001
+ )
1002
+ refresh_analytics_btn = gr.Button(
1003
+ "📈 Generate Analytics",
1004
+ elem_classes=["btn", "btn-primary"]
1005
+ )
1006
+
1007
+ analytics_plot = gr.Plot(label="Usage Analytics")
1008
+
1009
+ # Admin Tab (only visible to admin)
1010
+ with gr.Tab("👑 Admin", elem_classes=["card"], visible=bool(self.service.config.ADMIN_EMAIL)):
1011
+ gr.Markdown("### 👑 Admin Dashboard")
1012
+
1013
+ with gr.Row():
1014
+ with gr.Column(scale=1):
1015
+ admin_api_key = gr.Textbox(
1016
+ label="🔑 Admin API Key",
1017
+ type="password",
1018
+ placeholder="Enter admin API key",
1019
+ elem_classes=["input"]
1020
+ )
1021
+ refresh_admin_btn = gr.Button(
1022
+ "🔄 Refresh Admin Stats",
1023
+ elem_classes=["btn", "btn-primary"]
1024
+ )
1025
+
1026
+ admin_plot = gr.Plot(label="Platform Analytics")
1027
+ admin_stats_display = gr.HTML()
1028
+
1029
+ # Footer
1030
+ self.create_footer()
1031
 
1032
  # Event Handlers
1033
+ async def handle_generation(prompt, api_key, max_tokens, temperature, top_k, top_p, repetition_penalty):
1034
  if not api_key.strip():
1035
  return "⚠️ Please enter your API key", {}, False
1036
 
 
1040
  max_tokens=max_tokens,
1041
  temperature=temperature,
1042
  top_k=top_k,
1043
+ top_p=top_p,
1044
+ repetition_penalty=repetition_penalty
1045
  )
1046
 
1047
  if response.success:
1048
+ stats_html = f"""
1049
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-top: 1rem;">
1050
+ <div class="stat-card">
1051
+ <div class="stat-value">{response.data['tokens_used']}</div>
1052
+ <div class="stat-label">Tokens Used</div>
1053
+ </div>
1054
+ <div class="stat-card">
1055
+ <div class="stat-value">{response.data['requests_remaining']}</div>
1056
+ <div class="stat-label">Requests Left</div>
1057
+ </div>
1058
+ <div class="stat-card">
1059
+ <div class="stat-value">{response.data['user_plan'].title()}</div>
1060
+ <div class="stat-label">Current Plan</div>
1061
+ </div>
1062
+ </div>
1063
+ """
1064
  return (
1065
  response.data["generated_text"],
1066
+ stats_html,
1067
  True
1068
  )
1069
  else:
1070
+ return (
1071
+ response.error,
1072
+ f'<div class="alert alert-error">{response.error}</div>',
1073
+ False
1074
+ )
1075
 
1076
  async def handle_user_creation(name, email, plan):
1077
  if not name or not name.strip():
1078
  return (
1079
+ f'<div class="alert alert-error">❌ Name is required</div>',
1080
  "",
1081
  False
1082
  )
1083
 
1084
  if not email or not email.strip():
1085
  return (
1086
+ f'<div class="alert alert-error">❌ Email is required</div>',
1087
  "",
1088
  False
1089
  )
 
1092
 
1093
  if response.success:
1094
  return (
1095
+ f'<div class="alert alert-success">✅ Account created successfully! Your API key is below.</div>',
1096
  response.data["api_key"],
1097
  True
1098
  )
1099
  else:
1100
  return (
1101
+ f'<div class="alert alert-error">❌ {response.error}</div>',
1102
  "",
1103
  False
1104
  )
 
1109
  if response.success:
1110
  stats = response.data
1111
  return f"""
1112
+ <div>
1113
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
1114
+ <div class="stat-card">
1115
+ <div class="stat-value">{stats['total_requests']}</div>
1116
+ <div class="stat-label">Total Requests</div>
1117
+ </div>
1118
+ <div class="stat-card">
1119
+ <div class="stat-value">{stats['tokens_used']:,}</div>
1120
+ <div class="stat-label">Tokens Used</div>
1121
+ </div>
1122
+ <div class="stat-card">
1123
+ <div class="stat-value">{stats['requests_this_hour']}/{stats['rate_limit']}</div>
1124
+ <div class="stat-label">Hourly Usage</div>
1125
+ </div>
1126
+ <div class="stat-card">
1127
+ <div class="stat-value">{stats['plan'].title()}</div>
1128
+ <div class="stat-label">Current Plan</div>
1129
+ </div>
1130
+ </div>
1131
+ <div style="background: var(--card-bg); padding: 1.5rem; border-radius: 12px; border: 1px solid var(--card-border);">
1132
+ <h3 style="margin-top: 0;">Account Details</h3>
1133
+ <p><strong>Name:</strong> {stats['name']}</p>
1134
+ <p><strong>Email:</strong> {stats['email']}</p>
1135
+ <p><strong>Member since:</strong> {stats['created_at'][:10] if stats['created_at'] else 'N/A'}</p>
1136
+ <p><strong>Last request:</strong> {stats['last_request'][:19] if stats['last_request'] else 'Never'}</p>
1137
+ </div>
1138
+ </div>
1139
+ """
1140
+ else:
1141
+ return f'<div class="alert alert-error">❌ {response.error}</div>'
1142
+
1143
+ async def handle_analytics_refresh(api_key):
1144
+ response = await self.service.get_user_stats(api_key)
1145
+
1146
+ if response.success:
1147
+ return response.data["plot"]
1148
+ else:
1149
+ return go.Figure().update_layout(
1150
+ title="Error loading analytics",
1151
+ paper_bgcolor='rgba(0,0,0,0)',
1152
+ plot_bgcolor='rgba(0,0,0,0)',
1153
+ font=dict(color='white')
1154
+ )
1155
+
1156
+ async def handle_admin_refresh(api_key):
1157
+ response = await self.service.get_admin_stats(api_key)
1158
+
1159
+ if response.success:
1160
+ stats = response.data
1161
+
1162
+ stats_html = f"""
1163
+ <div style="margin-bottom: 2rem;">
1164
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
1165
+ <div class="stat-card">
1166
+ <div class="stat-value">{stats['total_users']}</div>
1167
+ <div class="stat-label">Total Users</div>
1168
  </div>
1169
+ <div class="stat-card">
1170
+ <div class="stat-value">{stats['active_users']}</div>
1171
+ <div class="stat-label">Active Users</div>
1172
  </div>
1173
+ <div class="stat-card">
1174
+ <div class="stat-value">{stats['total_requests']}</div>
1175
+ <div class="stat-label">Total Requests</div>
1176
  </div>
1177
+ <div class="stat-card">
1178
+ <div class="stat-value">{stats['total_tokens']:,}</div>
1179
+ <div class="stat-label">Tokens Generated</div>
1180
  </div>
1181
  </div>
 
 
 
1182
  </div>
1183
+ <div>
1184
+ <h3>User List</h3>
1185
+ <div style="max-height: 300px; overflow-y: auto;">
1186
+ <table style="width: 100%; border-collapse: collapse;">
1187
+ <thead>
1188
+ <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
1189
+ <th style="text-align: left; padding: 0.75rem;">Name</th>
1190
+ <th style="text-align: left; padding: 0.75rem;">Email</th>
1191
+ <th style="text-align: left; padding: 0.75rem;">Plan</th>
1192
+ <th style="text-align: left; padding: 0.75rem;">Requests</th>
1193
+ <th style="text-align: left; padding: 0.75rem;">Tokens</th>
1194
+ </tr>
1195
+ </thead>
1196
+ <tbody>
1197
  """
1198
+
1199
+ for user in stats['users']:
1200
+ stats_html += f"""
1201
+ <tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">
1202
+ <td style="padding: 0.75rem;">{user.get('name', 'N/A')}</td>
1203
+ <td style="padding: 0.75rem;">{user.get('email', 'N/A')}</td>
1204
+ <td style="padding: 0.75rem;"><span class="badge badge-{user.get('plan', 'free')}">{user.get('plan', 'free').title()}</span></td>
1205
+ <td style="padding: 0.75rem;">{user.get('requests', 0)}</td>
1206
+ <td style="padding: 0.75rem;">{user.get('tokens_used', 0):,}</td>
1207
+ </tr>
1208
+ """
1209
+
1210
+ stats_html += """
1211
+ </tbody>
1212
+ </table>
1213
+ </div>
1214
+ </div>
1215
+ """
1216
+
1217
+ return stats["plot"], stats_html
1218
  else:
1219
+ return go.Figure().update_layout(
1220
+ title="Error loading admin data",
1221
+ paper_bgcolor='rgba(0,0,0,0)',
1222
+ plot_bgcolor='rgba(0,0,0,0)',
1223
+ font=dict(color='white')
1224
+ ), f'<div class="alert alert-error">❌ {response.error}</div>'
1225
 
1226
  # Wire up events
1227
  generate_btn.click(
1228
  fn=handle_generation,
1229
  inputs=[
1230
  prompt_input, api_key_playground, max_tokens_input,
1231
+ temperature_input, top_k_input, top_p_input, repetition_penalty_input
1232
  ],
1233
  outputs=[output_text, generation_stats, generation_stats]
1234
  )
1235
 
1236
+ clear_btn.click(
1237
+ fn=lambda: ("", "", False),
1238
+ inputs=[],
1239
+ outputs=[prompt_input, output_text, generation_stats]
1240
+ )
1241
+
1242
  create_btn.click(
1243
  fn=handle_user_creation,
1244
  inputs=[name_input, email_input, plan_input],
 
1250
  inputs=[stats_api_key],
1251
  outputs=[user_stats_display]
1252
  )
1253
+
1254
+ refresh_analytics_btn.click(
1255
+ fn=handle_analytics_refresh,
1256
+ inputs=[analytics_api_key],
1257
+ outputs=[analytics_plot]
1258
+ )
1259
+
1260
+ refresh_admin_btn.click(
1261
+ fn=handle_admin_refresh,
1262
+ inputs=[admin_api_key],
1263
+ outputs=[admin_plot, admin_stats_display]
1264
+ )
1265
 
1266
  return app
1267
 
 
1288
  favicon_path=None,
1289
  ssl_keyfile=None,
1290
  ssl_certfile=None,
1291
+ auth=None,
1292
  max_threads=10
1293
  )
1294