Keeby-smilyai commited on
Commit
76a9ef3
·
verified ·
1 Parent(s): 43d65a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +541 -624
app.py CHANGED
@@ -37,7 +37,7 @@ def init_database():
37
  conn = sqlite3.connect('sam_users.db', check_same_thread=False)
38
  c = conn.cursor()
39
 
40
- # Users table with coins
41
  c.execute('''CREATE TABLE IF NOT EXISTS users
42
  (id INTEGER PRIMARY KEY AUTOINCREMENT,
43
  username TEXT UNIQUE NOT NULL,
@@ -50,11 +50,7 @@ def init_database():
50
  messages_used_nano INTEGER DEFAULT 0,
51
  messages_used_mini INTEGER DEFAULT 0,
52
  messages_used_fast INTEGER DEFAULT 0,
53
- messages_used_large INTEGER DEFAULT 0,
54
- coins INTEGER DEFAULT 0,
55
- total_coins_earned INTEGER DEFAULT 0,
56
- total_messages_sent INTEGER DEFAULT 0,
57
- last_daily_claim TIMESTAMP)''')
58
 
59
  # Upgrade requests table
60
  c.execute('''CREATE TABLE IF NOT EXISTS upgrade_requests
@@ -75,21 +71,11 @@ def init_database():
75
  timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
76
  FOREIGN KEY (user_id) REFERENCES users(id))''')
77
 
78
- # Coin transactions table
79
- c.execute('''CREATE TABLE IF NOT EXISTS coin_transactions
80
- (id INTEGER PRIMARY KEY AUTOINCREMENT,
81
- user_id INTEGER,
82
- amount INTEGER,
83
- transaction_type TEXT,
84
- description TEXT,
85
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
86
- FOREIGN KEY (user_id) REFERENCES users(id))''')
87
-
88
  # Create admin account if not exists
89
  admin_pass = hashlib.sha256("admin123".encode()).hexdigest()
90
  try:
91
- c.execute("INSERT INTO users (username, password_hash, email, plan, is_admin, coins) VALUES (?, ?, ?, ?, ?, ?)",
92
- ("admin", admin_pass, "admin@samx1.ai", "pro", 1, 999999))
93
  conn.commit()
94
  print("✅ Admin account created (username: admin, password: admin123)")
95
  except sqlite3.IntegrityError:
@@ -102,7 +88,7 @@ def init_database():
102
  db_conn = init_database()
103
  db_lock = threading.Lock()
104
 
105
- # Plan limits with rolling window
106
  PLAN_LIMITS = {
107
  'free': {
108
  'nano_messages': 100,
@@ -149,7 +135,7 @@ PLAN_LIMITS = {
149
  'max_tokens': 1024,
150
  'reset_hours': 5
151
  },
152
- 'VIP': {
153
  'nano_messages': 100000000000000,
154
  'mini_messages': 1000,
155
  'fast_messages': 5000,
@@ -160,30 +146,6 @@ PLAN_LIMITS = {
160
  }
161
  }
162
 
163
- # Coin prices for plans
164
- PLAN_PRICES = {
165
- 'explore': 500,
166
- 'plus': 1000,
167
- 'pro': 2500,
168
- 'Research': 5000,
169
- 'VIP': 10000
170
- }
171
-
172
- # Ways to earn coins
173
- COIN_REWARDS = {
174
- 'daily_login': 10,
175
- 'first_message': 50,
176
- 'share_conversation': 25,
177
- 'feedback_submit': 15,
178
- 'invite_friend': 100,
179
- 'streak_7days': 100,
180
- 'streak_30days': 500,
181
- 'message_milestone_10': 20,
182
- 'message_milestone_100': 100,
183
- 'message_milestone_1000': 500,
184
- 'watch_ad': 5,
185
- 'complete_survey': 50,
186
- }
187
 
188
  def get_model_type(model_name):
189
  """Get model type from model name."""
@@ -208,16 +170,10 @@ def create_user(username, password, email=""):
208
  try:
209
  c = db_conn.cursor()
210
  now = datetime.now(AUSTRALIA_TZ).isoformat()
211
- # Give new users 100 welcome coins!
212
- c.execute("""INSERT INTO users (username, password_hash, email, rate_limit_start, coins, total_coins_earned)
213
- VALUES (?, ?, ?, ?, ?, ?)""",
214
- (username, hash_password(password), email, now, 100, 100))
215
- user_id = c.lastrowid
216
- # Log the welcome bonus
217
- c.execute("INSERT INTO coin_transactions (user_id, amount, transaction_type, description) VALUES (?, ?, ?, ?)",
218
- (user_id, 100, 'welcome_bonus', 'Welcome to SmilyAI!'))
219
  db_conn.commit()
220
- return True, "Account created successfully! You received 100 welcome coins! 🎉"
221
  except sqlite3.IntegrityError:
222
  return False, "Username already exists!"
223
 
@@ -232,7 +188,7 @@ def authenticate_user(username, password):
232
  return False, None
233
 
234
  def check_and_reset_limits(user_id):
235
- """Check if window has passed and reset limits if needed."""
236
  with db_lock:
237
  c = db_conn.cursor()
238
  c.execute("SELECT rate_limit_start, plan FROM users WHERE id = ?", (user_id,))
@@ -267,15 +223,14 @@ def get_user_limits_info(user_id):
267
  c = db_conn.cursor()
268
  c.execute("""SELECT plan, rate_limit_start,
269
  messages_used_nano, messages_used_mini,
270
- messages_used_fast, messages_used_large,
271
- total_messages_sent
272
  FROM users WHERE id = ?""", (user_id,))
273
  result = c.fetchone()
274
 
275
  if not result:
276
  return None
277
 
278
- plan, rate_limit_start_str, nano_used, mini_used, fast_used, large_used, total_msgs = result
279
  limits = PLAN_LIMITS[plan]
280
 
281
  if rate_limit_start_str:
@@ -302,8 +257,7 @@ def get_user_limits_info(user_id):
302
  'large_limit': limits['large_messages'],
303
  'can_choose_model': limits['can_choose_model'],
304
  'max_tokens': limits['max_tokens'],
305
- 'reset_in': reset_str,
306
- 'total_messages': total_msgs
307
  }
308
 
309
  def can_use_model(user_id, model_name):
@@ -319,7 +273,7 @@ def can_use_model(user_id, model_name):
319
  used = info[used_key]
320
  limit = info[limit_key]
321
 
322
- if limit == -1 or limit > 1000000:
323
  return True, "OK"
324
 
325
  if used >= limit:
@@ -334,7 +288,7 @@ def increment_model_usage(user_id, model_name):
334
 
335
  with db_lock:
336
  c = db_conn.cursor()
337
- c.execute(f"UPDATE users SET {column} = {column} + 1, total_messages_sent = total_messages_sent + 1 WHERE id = ?", (user_id,))
338
  db_conn.commit()
339
 
340
  def get_available_models_for_user(user_id):
@@ -349,7 +303,7 @@ def get_available_models_for_user(user_id):
349
  used = info[f'{model_type}_used']
350
  limit = info[f'{model_type}_limit']
351
 
352
- if limit == -1 or limit > 1000000 or used < limit:
353
  for model_name in available_models.keys():
354
  if get_model_type(model_name) == model_type:
355
  available.append(model_name)
@@ -364,109 +318,6 @@ def log_usage(user_id, tokens, model):
364
  (user_id, tokens, model))
365
  db_conn.commit()
366
 
367
- # ==============================================================================
368
- # Coin System Functions
369
- # ==============================================================================
370
- def get_user_coins(user_id):
371
- with db_lock:
372
- c = db_conn.cursor()
373
- c.execute("SELECT coins FROM users WHERE id = ?", (user_id,))
374
- result = c.fetchone()
375
- return result[0] if result else 0
376
-
377
- def add_coins(user_id, amount, transaction_type, description):
378
- with db_lock:
379
- c = db_conn.cursor()
380
- c.execute("UPDATE users SET coins = coins + ?, total_coins_earned = total_coins_earned + ? WHERE id = ?",
381
- (amount, amount, user_id))
382
- c.execute("INSERT INTO coin_transactions (user_id, amount, transaction_type, description) VALUES (?, ?, ?, ?)",
383
- (user_id, amount, transaction_type, description))
384
- db_conn.commit()
385
- return True
386
-
387
- def deduct_coins(user_id, amount, transaction_type, description):
388
- coins = get_user_coins(user_id)
389
- if coins < amount:
390
- return False, "Insufficient coins"
391
- with db_lock:
392
- c = db_conn.cursor()
393
- c.execute("UPDATE users SET coins = coins - ? WHERE id = ?", (amount, user_id))
394
- c.execute("INSERT INTO coin_transactions (user_id, amount, transaction_type, description) VALUES (?, ?, ?, ?)",
395
- (user_id, -amount, transaction_type, description))
396
- db_conn.commit()
397
- return True, "Success"
398
-
399
- def purchase_plan_with_coins(user_id, plan):
400
- price = PLAN_PRICES.get(plan, 0)
401
- if price == 0:
402
- return False, "Invalid plan"
403
-
404
- success, msg = deduct_coins(user_id, price, 'plan_purchase', f'Purchased {plan} plan')
405
- if success:
406
- with db_lock:
407
- c = db_conn.cursor()
408
- now = datetime.now(AUSTRALIA_TZ).isoformat()
409
- c.execute("""UPDATE users
410
- SET plan = ?,
411
- rate_limit_start = ?,
412
- messages_used_nano = 0,
413
- messages_used_mini = 0,
414
- messages_used_fast = 0,
415
- messages_used_large = 0
416
- WHERE id = ?""", (plan, now, user_id))
417
- db_conn.commit()
418
- return True, f"Successfully purchased {plan} plan!"
419
- return False, msg
420
-
421
- def check_and_award_daily_login(user_id):
422
- """Award coins for daily login"""
423
- with db_lock:
424
- c = db_conn.cursor()
425
- c.execute("SELECT last_daily_claim FROM users WHERE id = ?", (user_id,))
426
- result = c.fetchone()
427
-
428
- if result and result[0]:
429
- last_claim = datetime.fromisoformat(result[0])
430
- now = datetime.now(AUSTRALIA_TZ)
431
- if (now - last_claim).days >= 1:
432
- add_coins(user_id, COIN_REWARDS['daily_login'], 'daily_login', 'Daily login bonus')
433
- c.execute("UPDATE users SET last_daily_claim = ? WHERE id = ?", (now.isoformat(), user_id))
434
- db_conn.commit()
435
- return True, COIN_REWARDS['daily_login']
436
- else:
437
- now = datetime.now(AUSTRALIA_TZ)
438
- add_coins(user_id, COIN_REWARDS['daily_login'], 'daily_login', 'Daily login bonus')
439
- c.execute("UPDATE users SET last_daily_claim = ? WHERE id = ?", (now.isoformat(), user_id))
440
- db_conn.commit()
441
- return True, COIN_REWARDS['daily_login']
442
- return False, 0
443
-
444
- def check_message_milestones(user_id, total_messages):
445
- """Check and award milestone coins"""
446
- milestones = [(10, 'message_milestone_10'), (100, 'message_milestone_100'), (1000, 'message_milestone_1000')]
447
-
448
- with db_lock:
449
- c = db_conn.cursor()
450
- for milestone, reward_key in milestones:
451
- if total_messages == milestone:
452
- c.execute("SELECT COUNT(*) FROM coin_transactions WHERE user_id = ? AND transaction_type = ?",
453
- (user_id, reward_key))
454
- already_awarded = c.fetchone()[0] > 0
455
-
456
- if not already_awarded:
457
- reward = COIN_REWARDS[reward_key]
458
- add_coins(user_id, reward, reward_key, f'{milestone} message milestone!')
459
- return True, reward, milestone
460
- return False, 0, 0
461
-
462
- def grant_coins_admin(user_id, amount, reason):
463
- """Admin function to grant coins"""
464
- add_coins(user_id, amount, 'admin_grant', reason)
465
- return True, f"Granted {amount} coins"
466
-
467
- # ==============================================================================
468
- # Admin Functions (existing + coin stats)
469
- # ==============================================================================
470
  def request_upgrade(user_id, plan, reason):
471
  with db_lock:
472
  try:
@@ -484,7 +335,7 @@ def get_all_users():
484
  c.execute("""SELECT id, username, email, plan, created_at, is_admin,
485
  messages_used_nano, messages_used_mini,
486
  messages_used_fast, messages_used_large,
487
- rate_limit_start, coins, total_coins_earned
488
  FROM users ORDER BY created_at DESC""")
489
  return c.fetchall()
490
 
@@ -498,26 +349,6 @@ def get_pending_requests():
498
  ORDER BY r.created_at DESC""")
499
  return c.fetchall()
500
 
501
- def get_coin_stats():
502
- with db_lock:
503
- c = db_conn.cursor()
504
- c.execute("SELECT SUM(coins), SUM(total_coins_earned), COUNT(*) FROM users")
505
- total_balance, total_earned, user_count = c.fetchone()
506
-
507
- c.execute("SELECT SUM(amount) FROM coin_transactions WHERE transaction_type = 'plan_purchase'")
508
- total_spent = c.fetchone()[0] or 0
509
-
510
- c.execute("SELECT transaction_type, SUM(amount) FROM coin_transactions GROUP BY transaction_type ORDER BY SUM(amount) DESC LIMIT 5")
511
- top_sources = c.fetchall()
512
-
513
- return {
514
- 'total_balance': total_balance or 0,
515
- 'total_earned': total_earned or 0,
516
- 'total_spent': abs(total_spent),
517
- 'user_count': user_count,
518
- 'top_sources': top_sources
519
- }
520
-
521
  def update_user_plan(username, new_plan):
522
  with db_lock:
523
  try:
@@ -571,9 +402,6 @@ def deny_request(request_id):
571
  except Exception as e:
572
  return False, f"Error: {str(e)}"
573
 
574
- # ... REST OF PART 1 STAYS THE SAME (Model Architecture, etc.) ...
575
-
576
-
577
  # ==============================================================================
578
  # Model Architecture
579
  # ==============================================================================
@@ -978,14 +806,12 @@ def generate_response_stream(prompt, temperature=0.7, backend=None, max_tokens=2
978
  final_tokens_per_sec = tokens_generated / elapsed if elapsed > 0 else 0
979
  yield "", False, final_tokens_per_sec, final_tokens_per_sec, False
980
 
981
-
982
- # PART 3 - Production-Grade Multi-Page UI with Coins System
983
-
984
  import secrets
985
  import json
986
  from datetime import datetime
987
 
988
- # Global session storage
989
  active_sessions = {}
990
  session_lock = threading.Lock()
991
 
@@ -1023,7 +849,7 @@ if __name__ == "__main__":
1023
  import gradio as gr
1024
 
1025
  custom_css = """
1026
- /* Modern Production-Grade Styling with Coins */
1027
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
1028
 
1029
  * { font-family: 'Inter', sans-serif; }
@@ -1072,25 +898,6 @@ if __name__ == "__main__":
1072
  border-radius: 20px;
1073
  }
1074
 
1075
- /* Coin Badge */
1076
- .coin-badge {
1077
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
1078
- color: white;
1079
- padding: 6px 14px;
1080
- border-radius: 20px;
1081
- font-weight: 600;
1082
- font-size: 14px;
1083
- display: flex;
1084
- align-items: center;
1085
- gap: 6px;
1086
- box-shadow: 0 2px 8px rgba(251, 191, 36, 0.4);
1087
- animation: coin-glow 2s ease-in-out infinite;
1088
- }
1089
- @keyframes coin-glow {
1090
- 0%, 100% { box-shadow: 0 2px 8px rgba(251, 191, 36, 0.4); }
1091
- 50% { box-shadow: 0 4px 16px rgba(251, 191, 36, 0.6); }
1092
- }
1093
-
1094
  /* Plan Badge */
1095
  .plan-badge {
1096
  display: inline-block;
@@ -1198,6 +1005,55 @@ if __name__ == "__main__":
1198
  }
1199
  .user-message .message-content { color: white; }
1200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1201
  .thinking-content {
1202
  color: #6b7280;
1203
  font-style: italic;
@@ -1210,6 +1066,31 @@ if __name__ == "__main__":
1210
  font-size: 13px;
1211
  }
1212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1213
  /* Input Area */
1214
  .input-container {
1215
  background: white;
@@ -1243,6 +1124,9 @@ if __name__ == "__main__":
1243
  transform: scale(1.08) !important;
1244
  box-shadow: 0 6px 16px rgba(0,0,0,0.25) !important;
1245
  }
 
 
 
1246
  .send-btn {
1247
  background: linear-gradient(135deg, #10a37f 0%, #0d8c6c 100%) !important;
1248
  }
@@ -1250,36 +1134,15 @@ if __name__ == "__main__":
1250
  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
1251
  }
1252
 
1253
- /* Speed Indicator */
1254
- .speed-indicator {
1255
- text-align: center;
1256
- padding: 10px;
1257
- background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
1258
- border-radius: 8px;
1259
- font-weight: 600;
1260
- color: #166534;
1261
- margin-bottom: 10px;
1262
- font-size: 13px;
1263
- display: flex;
1264
- align-items: center;
1265
- justify-content: center;
1266
- gap: 8px;
1267
- }
1268
- .speed-indicator.generating {
1269
- background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
1270
- color: #1e40af;
1271
- animation: pulse 2s ease-in-out infinite;
1272
- }
1273
- @keyframes pulse {
1274
- 0%, 100% { opacity: 1; }
1275
- 50% { opacity: 0.8; }
1276
- }
1277
- .speed-indicator.error {
1278
- background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
1279
- color: #991b1b;
1280
  }
1281
 
1282
- /* Limits Panel */
1283
  .limits-panel {
1284
  background: white;
1285
  border: 1px solid #e5e7eb;
@@ -1335,7 +1198,7 @@ if __name__ == "__main__":
1335
  .progress-fill.warning { background: linear-gradient(90deg, #f59e0b 0%, #ea580c 100%); }
1336
  .progress-fill.danger { background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%); }
1337
 
1338
- /* Coin Shop - Plans Grid */
1339
  .plans-grid {
1340
  display: grid;
1341
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
@@ -1354,11 +1217,11 @@ if __name__ == "__main__":
1354
  .plan-card:hover {
1355
  transform: translateY(-6px);
1356
  box-shadow: 0 12px 28px rgba(0,0,0,0.15);
1357
- border-color: #fbbf24;
1358
  }
1359
  .plan-card.featured {
1360
- border: 3px solid #fbbf24;
1361
- box-shadow: 0 8px 24px rgba(251, 191, 36, 0.25);
1362
  transform: scale(1.02);
1363
  }
1364
  .plan-card.featured::before {
@@ -1366,7 +1229,7 @@ if __name__ == "__main__":
1366
  position: absolute;
1367
  top: 14px;
1368
  right: -28px;
1369
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
1370
  color: white;
1371
  padding: 3px 36px;
1372
  font-size: 10px;
@@ -1381,10 +1244,9 @@ if __name__ == "__main__":
1381
  color: #1f2937;
1382
  }
1383
  .plan-price {
1384
- font-size: 20px;
1385
- color: #f59e0b;
1386
  margin-bottom: 18px;
1387
- font-weight: 700;
1388
  }
1389
  .plan-features {
1390
  list-style: none;
@@ -1402,62 +1264,58 @@ if __name__ == "__main__":
1402
  font-weight: 700;
1403
  margin-right: 6px;
1404
  }
1405
- .buy-plan-btn {
1406
- width: 100%;
1407
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
1408
- color: white;
1409
- border: none;
1410
- padding: 12px;
1411
- border-radius: 8px;
1412
  font-weight: 600;
1413
- font-size: 14px;
1414
- cursor: pointer;
1415
- transition: all 0.2s;
1416
- margin-top: 12px;
 
 
 
1417
  }
1418
- .buy-plan-btn:hover {
1419
- transform: translateY(-2px);
1420
- box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
 
1421
  }
1422
- .buy-plan-btn:disabled {
1423
- opacity: 0.5;
1424
- cursor: not-allowed;
 
 
 
 
1425
  }
1426
 
1427
- /* Earn Coins Section */
1428
- .earn-card {
 
 
 
1429
  background: white;
1430
- border: 2px solid #e5e7eb;
1431
- border-radius: 12px;
1432
- padding: 20px;
1433
- text-align: center;
1434
- transition: all 0.3s;
1435
- }
1436
- .earn-card:hover {
1437
- transform: translateY(-4px);
1438
- box-shadow: 0 8px 16px rgba(0,0,0,0.1);
1439
- border-color: #fbbf24;
1440
- }
1441
- .earn-icon { font-size: 36px; margin-bottom: 8px; }
1442
- .earn-title { font-weight: 600; margin-bottom: 4px; color: #1f2937; font-size: 14px; }
1443
- .earn-reward { color: #f59e0b; font-weight: 700; margin-bottom: 12px; font-size: 16px; }
1444
- .earn-btn {
1445
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
1446
- color: white;
1447
- border: none;
1448
- padding: 8px 16px;
1449
  border-radius: 8px;
1450
- cursor: pointer;
1451
- font-weight: 600;
1452
- font-size: 13px;
1453
- transition: all 0.2s;
 
 
1454
  }
1455
- .earn-btn:hover {
1456
- transform: translateY(-2px);
1457
- box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3);
1458
  }
1459
- .earn-status { color: #10a37f; font-size: 13px; font-weight: 600; }
1460
- .earn-progress { color: #6b7280; font-size: 13px; }
 
1461
 
1462
  /* Settings Panel */
1463
  .settings-panel {
@@ -1468,6 +1326,18 @@ if __name__ == "__main__":
1468
  margin-bottom: 14px;
1469
  }
1470
 
 
 
 
 
 
 
 
 
 
 
 
 
1471
  /* Empty State */
1472
  .empty-state {
1473
  text-align: center;
@@ -1478,6 +1348,18 @@ if __name__ == "__main__":
1478
  .empty-state-title { font-size: 18px; font-weight: 600; color: #6b7280; margin-bottom: 8px; }
1479
  .empty-state-subtitle { font-size: 14px; color: #9ca3af; }
1480
 
 
 
 
 
 
 
 
 
 
 
 
 
1481
  /* Responsive */
1482
  @media (max-width: 1024px) {
1483
  .chat-layout { flex-direction: column; }
@@ -1488,9 +1370,15 @@ if __name__ == "__main__":
1488
  .nav-left, .nav-right { width: 100%; justify-content: center; }
1489
  .chat-container { height: 400px; }
1490
  .plans-grid { grid-template-columns: 1fr; }
 
1491
  }
 
 
 
 
1492
  """
1493
 
 
1494
  def get_greeting(username):
1495
  import random
1496
  greetings = [
@@ -1498,25 +1386,52 @@ if __name__ == "__main__":
1498
  f"Welcome back, {username}! ✨",
1499
  f"Hi {username}! 🚀",
1500
  f"Hello {username}! 😊",
 
 
 
 
1501
  ]
1502
  return random.choice(greetings)
1503
 
 
1504
  def render_markdown(text):
1505
- """Simple markdown rendering"""
1506
  import re
1507
- text = re.sub(r'```(\w+)?\n(.*?)```', r'<pre><code>\2</code></pre>', text, flags=re.DOTALL)
 
 
 
 
1508
  text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
 
 
1509
  text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
1510
  text = re.sub(r'__(.+?)__', r'<strong>\1</strong>', text)
 
 
1511
  text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text)
 
 
 
 
 
 
 
 
 
 
 
1512
  text = text.replace('\n', '<br>')
 
1513
  return text
1514
 
 
1515
  def format_message_html(role, content, show_thinking=True, message_id=None):
1516
  role_class = "user-message" if role == "user" else "assistant-message"
1517
  thinking = ""
1518
  answer = ""
1519
 
 
1520
  if "<think>" in content:
1521
  parts = content.split("<think>", 1)
1522
  before_think = parts[0].strip()
@@ -1538,6 +1453,7 @@ if __name__ == "__main__":
1538
  else:
1539
  answer = content
1540
 
 
1541
  answer = render_markdown(answer)
1542
 
1543
  html = f'<div class="{role_class}" id="msg-{message_id}"><div class="message-content">'
@@ -1548,6 +1464,15 @@ if __name__ == "__main__":
1548
  if answer:
1549
  html += f'<div>{answer}</div>'
1550
 
 
 
 
 
 
 
 
 
 
1551
  html += '</div></div>'
1552
  return html
1553
 
@@ -1592,7 +1517,7 @@ if __name__ == "__main__":
1592
  ]
1593
 
1594
  for model_name, used, limit in models_info:
1595
- if limit == -1 or limit > 1000000:
1596
  percentage = 0
1597
  status_class = "limit-ok"
1598
  status_text = f'{used} / ∞'
@@ -1628,16 +1553,70 @@ if __name__ == "__main__":
1628
 
1629
  with gr.Blocks(css=custom_css, title="SAM-X-1 AI Chat", theme=gr.themes.Soft(primary_hue="slate")) as demo:
1630
 
 
1631
  gr.HTML("""
1632
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1633
  function scrollChatToBottom() {
1634
  const chatContainer = document.querySelector('.chat-container');
1635
- if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
 
 
1636
  }
 
 
1637
  setInterval(scrollChatToBottom, 500);
1638
  </script>
1639
  """)
1640
 
 
1641
  session_code = gr.State("")
1642
  user_data = gr.State(None)
1643
  chat_history = gr.State([])
@@ -1645,50 +1624,75 @@ if __name__ == "__main__":
1645
  # Navigation Bar
1646
  with gr.Row(elem_classes="nav-bar"):
1647
  with gr.Column(scale=1, elem_classes="nav-left"):
1648
- gr.HTML('<div class="nav-brand">🤖 SAM-X-1 <span style="font-size: 12px; opacity: 0.8; font-weight: 400;">v3.0 Coins</span></div>')
1649
  with gr.Column(scale=2, elem_classes="nav-right"):
1650
  user_greeting = gr.HTML('<div class="user-greeting">Please sign in</div>')
1651
- coin_display = gr.HTML('<div class="coin-badge">💰 0 coins</div>', visible=False)
1652
  with gr.Row():
1653
- shop_nav_btn = gr.Button("🪙 Coin Shop", size="sm", visible=False)
1654
  logout_nav_btn = gr.Button("🚪 Logout", size="sm", visible=False)
1655
 
1656
  # AUTH PAGE
1657
  with gr.Group(visible=True) as auth_page:
1658
  with gr.Column(elem_classes="auth-container"):
1659
  gr.HTML('<div class="auth-title">Welcome to SAM-X-1</div>')
1660
- gr.HTML('<div class="auth-subtitle">Sign in or create account • Get 100 free coins! 🎁</div>')
1661
 
1662
- auth_username = gr.Textbox(label="Username", placeholder="Enter your username")
1663
- auth_password = gr.Textbox(label="Password", type="password", placeholder="Enter your password")
1664
- auth_email = gr.Textbox(label="Email (optional)", placeholder="your@email.com")
 
 
 
 
 
 
 
 
 
 
 
 
1665
 
1666
  auth_btn = gr.Button("Continue →", variant="primary", size="lg")
1667
  auth_msg = gr.Markdown("")
 
 
 
 
 
 
 
1668
 
1669
  # CHAT PAGE
1670
  with gr.Group(visible=False) as chat_page:
1671
  with gr.Row(elem_classes="chat-layout"):
 
1672
  with gr.Column(elem_classes="chat-main"):
1673
  chat_html = gr.HTML(value='')
 
1674
  speed_display = gr.HTML('<div class="speed-indicator">⚡ Ready to chat</div>')
1675
 
1676
  with gr.Column(elem_classes="input-container"):
1677
  with gr.Row(elem_classes="input-row"):
1678
  msg_input = gr.Textbox(
1679
- placeholder="Ask me anything...",
1680
  show_label=False,
1681
  scale=10,
1682
  lines=1,
1683
- max_lines=5
 
1684
  )
1685
  send_btn = gr.Button("▶", elem_classes=["circular-btn", "send-btn"])
1686
  stop_btn = gr.Button("⏹", elem_classes=["circular-btn", "stop-btn"], visible=False)
 
 
1687
 
1688
  with gr.Row():
1689
  clear_btn = gr.Button("🗑️ Clear Chat", size="sm")
1690
  new_chat_btn = gr.Button("➕ New Chat", size="sm", variant="primary")
 
1691
 
 
1692
  with gr.Column(elem_classes="chat-sidebar"):
1693
  limits_display = gr.HTML("")
1694
 
@@ -1696,169 +1700,154 @@ if __name__ == "__main__":
1696
  model_selector = gr.Dropdown(
1697
  choices=["🤖 Auto (Recommended)"],
1698
  value="🤖 Auto (Recommended)",
1699
- label="Model Selection"
 
1700
  )
1701
  max_tokens_slider = gr.Slider(
1702
  minimum=64, maximum=512, value=256, step=64,
1703
- label="Max Tokens"
 
1704
  )
1705
  temperature_slider = gr.Slider(
1706
  minimum=0.0, maximum=2.0, value=0.7, step=0.1,
1707
- label="Temperature"
 
1708
  )
1709
  show_thinking_checkbox = gr.Checkbox(
1710
  label="💭 Show Thinking Process",
1711
- value=True
 
1712
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1713
 
1714
- # COIN SHOP PAGE
1715
- with gr.Group(visible=False) as shop_page:
1716
  gr.HTML('''
1717
  <div style="text-align: center; margin-bottom: 32px;">
1718
- <div style="font-size: 32px; font-weight: 700; margin-bottom: 8px;">🪙 SmilyAI Coin Shop</div>
1719
- <div style="font-size: 16px; color: #6b7280;">Upgrade your plan with coins • No credit card needed!</div>
 
 
 
 
1720
  </div>
1721
  ''')
1722
 
1723
- shop_coin_balance = gr.HTML('<div style="text-align: center; font-size: 24px; font-weight: 700; color: #f59e0b; margin-bottom: 24px;">Your Balance: 💰 0 coins</div>')
1724
-
1725
- gr.HTML('<div style="font-size: 20px; font-weight: 700; margin: 24px 0 16px 0;">📦 Available Plans</div>')
1726
-
1727
- with gr.Row():
1728
- with gr.Column():
1729
- gr.HTML('''
1730
- <div class="plan-card">
1731
- <div class="plan-name">Explore 🔍</div>
1732
- <div class="plan-price">💰 500 coins</div>
1733
- <ul class="plan-features">
1734
- <li>Nano & Mini: Unlimited</li>
1735
- <li>Fast: 14 messages/3h</li>
1736
- <li>Large: 10 messages/3h</li>
1737
- <li>Manual model selection</li>
1738
- <li>512 max tokens</li>
1739
- </ul>
1740
- </div>
1741
- ''')
1742
- buy_explore_btn = gr.Button("Purchase Explore Plan", elem_classes="buy-plan-btn", variant="primary")
1743
-
1744
- with gr.Column():
1745
- gr.HTML('''
1746
- <div class="plan-card featured">
1747
- <div class="plan-name">Plus ⭐</div>
1748
- <div class="plan-price">💰 1,000 coins</div>
1749
- <ul class="plan-features">
1750
- <li>Fast: Unlimited messages</li>
1751
- <li>Large: 20 messages/3h</li>
1752
- <li>Manual model selection</li>
1753
- <li>384 max tokens</li>
1754
- <li>Priority support</li>
1755
- </ul>
1756
- </div>
1757
- ''')
1758
- buy_plus_btn = gr.Button("Purchase Plus Plan", elem_classes="buy-plan-btn", variant="primary")
1759
-
1760
- with gr.Column():
1761
- gr.HTML('''
1762
- <div class="plan-card">
1763
- <div class="plan-name">Pro 💎</div>
1764
- <div class="plan-price">💰 2,500 coins</div>
1765
- <ul class="plan-features">
1766
- <li>All models unlimited</li>
1767
- <li>512 max tokens</li>
1768
- <li>Fastest reset (3h)</li>
1769
- <li>24/7 premium support</li>
1770
- <li>Early feature access</li>
1771
- </ul>
1772
- </div>
1773
- ''')
1774
- buy_pro_btn = gr.Button("Purchase Pro Plan", elem_classes="buy-plan-btn", variant="primary")
1775
-
1776
- with gr.Row():
1777
- with gr.Column():
1778
- gr.HTML('''
1779
- <div class="plan-card">
1780
- <div class="plan-name">Research 🔬</div>
1781
- <div class="plan-price">💰 5,000 coins</div>
1782
- <ul class="plan-features">
1783
- <li>Extended limits (1000+ msgs)</li>
1784
- <li>1024 max tokens</li>
1785
- <li>Batch processing</li>
1786
- <li>Custom fine-tuning</li>
1787
- <li>Academic discount</li>
1788
- </ul>
1789
- </div>
1790
- ''')
1791
- buy_research_btn = gr.Button("Purchase Research Plan", elem_classes="buy-plan-btn", variant="primary")
1792
-
1793
- with gr.Column():
1794
- gr.HTML('''
1795
- <div class="plan-card featured">
1796
- <div class="plan-name">VIP 👑</div>
1797
- <div class="plan-price">💰 10,000 coins</div>
1798
- <ul class="plan-features">
1799
- <li>Unlimited everything</li>
1800
- <li>1024 max tokens</li>
1801
- <li>Fastest reset (2h)</li>
1802
- <li>White-glove support</li>
1803
- <li>Custom features</li>
1804
- </ul>
1805
- </div>
1806
- ''')
1807
- buy_vip_btn = gr.Button("Purchase VIP Plan", elem_classes="buy-plan-btn", variant="primary")
1808
-
1809
- shop_msg = gr.Markdown("")
1810
-
1811
- gr.HTML('<div style="font-size: 20px; font-weight: 700; margin: 32px 0 16px 0;">💰 Earn Free Coins</div>')
1812
-
1813
  gr.HTML('''
1814
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px;">
1815
- <div class="earn-card">
1816
- <div class="earn-icon">📅</div>
1817
- <div class="earn-title">Daily Login</div>
1818
- <div class="earn-reward">+10 coins</div>
1819
- <div class="earn-status">✓ Claimed today!</div>
1820
- </div>
1821
- <div class="earn-card">
1822
- <div class="earn-icon">📨</div>
1823
- <div class="earn-title">First Message</div>
1824
- <div class="earn-reward">+50 coins</div>
1825
- <div class="earn-status">Auto-claimed</div>
 
1826
  </div>
1827
- <div class="earn-card">
1828
- <div class="earn-icon">💬</div>
1829
- <div class="earn-title">10 Messages</div>
1830
- <div class="earn-reward">+20 coins</div>
1831
- <div class="earn-progress" id="milestone-10">0/10</div>
 
 
 
 
 
 
 
 
1832
  </div>
1833
- <div class="earn-card">
1834
- <div class="earn-icon">💎</div>
1835
- <div class="earn-title">100 Messages</div>
1836
- <div class="earn-reward">+100 coins</div>
1837
- <div class="earn-progress" id="milestone-100">0/100</div>
 
 
 
 
 
 
 
 
1838
  </div>
1839
- <div class="earn-card">
1840
- <div class="earn-icon">🏆</div>
1841
- <div class="earn-title">1000 Messages</div>
1842
- <div class="earn-reward">+500 coins</div>
1843
- <div class="earn-progress" id="milestone-1000">0/1000</div>
 
 
 
 
 
 
 
 
1844
  </div>
1845
- <div class="earn-card">
1846
- <div class="earn-icon">👥</div>
1847
- <div class="earn-title">Invite Friend</div>
1848
- <div class="earn-reward">+100 coins</div>
1849
- <div class="earn-progress">Coming soon</div>
 
 
 
 
 
 
 
 
1850
  </div>
1851
  </div>
1852
  ''')
1853
 
 
 
 
1854
  with gr.Row():
1855
- back_to_chat_from_shop_btn = gr.Button("← Back to Chat", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1856
 
1857
  # ADMIN PAGE
1858
  with gr.Group(visible=False) as admin_page:
1859
  gr.HTML('''
1860
  <div style="text-align: center; margin-bottom: 24px;">
1861
- <div style="font-size: 28px; font-weight: 700; color: #1f2937;">👨‍💼 Admin Dashboard</div>
 
 
1862
  </div>
1863
  ''')
1864
 
@@ -1868,13 +1857,13 @@ if __name__ == "__main__":
1868
  refresh_users_btn = gr.Button("🔄 Refresh Users", size="sm")
1869
 
1870
  users_table = gr.Dataframe(
1871
- headers=["ID", "Username", "Email", "Plan", "Coins", "Total Earned", "Messages"],
1872
  wrap=True
1873
  )
1874
 
1875
  gr.Markdown("### ✏️ Update User Plan")
1876
  with gr.Row():
1877
- admin_username = gr.Textbox(label="Username", scale=2)
1878
  admin_new_plan = gr.Dropdown(
1879
  choices=["free", "plus", "pro", "explore", "Research", "VIP"],
1880
  label="New Plan",
@@ -1884,20 +1873,6 @@ if __name__ == "__main__":
1884
  update_plan_btn = gr.Button("Update Plan", variant="primary", scale=1)
1885
  admin_msg = gr.Markdown("")
1886
 
1887
- with gr.Tab("💰 Coin Economy"):
1888
- with gr.Row():
1889
- refresh_coin_stats_btn = gr.Button("🔄 Refresh Stats", size="sm")
1890
-
1891
- coin_stats_display = gr.HTML("")
1892
-
1893
- gr.Markdown("### 🎁 Grant Coins to User")
1894
- with gr.Row():
1895
- grant_username = gr.Textbox(label="Username", scale=2)
1896
- grant_amount = gr.Number(label="Coins", precision=0, value=100, scale=1)
1897
- grant_reason = gr.Textbox(label="Reason", scale=2)
1898
- grant_btn = gr.Button("Grant Coins", variant="primary", scale=1)
1899
- grant_msg = gr.Markdown("")
1900
-
1901
  with gr.Tab("📋 Upgrade Requests"):
1902
  with gr.Row():
1903
  refresh_requests_btn = gr.Button("🔄 Refresh Requests", size="sm")
@@ -1908,126 +1883,113 @@ if __name__ == "__main__":
1908
  )
1909
 
1910
  gr.Markdown("### 🔍 Review Request")
1911
- request_id_input = gr.Number(label="Request ID", precision=0, minimum=1)
 
 
 
 
 
1912
  with gr.Row():
1913
  approve_req_btn = gr.Button("✅ Approve Request", variant="primary", size="lg")
1914
  deny_req_btn = gr.Button("❌ Deny Request", variant="stop", size="lg")
1915
  request_msg = gr.Markdown("")
 
 
 
 
 
 
 
 
 
 
1916
 
1917
  # ==================== EVENT HANDLERS ====================
1918
 
1919
  def handle_auth(username, password, email):
 
1920
  if len(username) < 3:
1921
  return (
1922
  None, None, "❌ Username must be at least 3 characters",
1923
  gr.update(), gr.update(), gr.update(), gr.update(),
1924
- gr.update(), gr.update(), gr.update(),
1925
- gr.update(), gr.update(), gr.update(), "", ""
1926
  )
1927
  if len(password) < 6:
1928
  return (
1929
  None, None, "❌ Password must be at least 6 characters",
1930
  gr.update(), gr.update(), gr.update(), gr.update(),
1931
- gr.update(), gr.update(), gr.update(),
1932
- gr.update(), gr.update(), gr.update(), "", ""
1933
  )
1934
 
 
1935
  success, data = authenticate_user(username, password)
1936
 
1937
  if not success:
 
1938
  success, message = create_user(username, password, email)
1939
  if success:
 
1940
  success, data = authenticate_user(username, password)
1941
  if not success:
1942
  return (
1943
  None, None, "❌ Account created but login failed",
1944
  gr.update(), gr.update(), gr.update(), gr.update(),
1945
- gr.update(), gr.update(), gr.update(),
1946
- gr.update(), gr.update(), gr.update(), "", ""
1947
  )
1948
  else:
1949
  return (
1950
  None, None, f"❌ {message}",
1951
  gr.update(), gr.update(), gr.update(), gr.update(),
1952
- gr.update(), gr.update(), gr.update(),
1953
- gr.update(), gr.update(), gr.update(), "", ""
1954
  )
1955
 
 
1956
  if 'id' in data and 'user_id' not in data:
1957
  data['user_id'] = data['id']
1958
 
 
1959
  code = create_session(data)
1960
 
1961
- # Award daily login
1962
- awarded, amount = check_and_award_daily_login(data['user_id'])
1963
-
1964
- coins = get_user_coins(data['user_id'])
1965
  info = get_user_limits_info(data['user_id'])
1966
-
1967
  if not info:
1968
  return (
1969
  None, None, "❌ Could not load user info",
1970
  gr.update(), gr.update(), gr.update(), gr.update(),
1971
- gr.update(), gr.update(), gr.update(),
1972
- gr.update(), gr.update(), gr.update(), "", ""
1973
  )
1974
 
1975
  plan_class = f"plan-{info['plan'].lower()}"
1976
  greeting_html = f'<div class="user-greeting">{get_greeting(username)} <span class="plan-badge {plan_class}">{info["plan"]}</span></div>'
1977
- coin_html = f'<div class="coin-badge">💰 {coins} coins</div>'
1978
 
 
1979
  if info['can_choose_model']:
1980
  model_choices = ["🤖 Auto (Recommended)"] + list(available_models.keys())
1981
  else:
1982
  model_choices = ["🤖 Auto (Recommended)"]
1983
 
1984
  limits_html = render_limits_panel(data)
1985
- empty_chat = render_history([], True)
1986
 
1987
- daily_msg = f" You earned {amount} coins for logging in today! 🎉" if awarded else ""
1988
 
1989
  return (
1990
  code,
1991
  data,
1992
- f"✅ Welcome, **{username}**!{daily_msg}",
1993
  gr.update(visible=False), # auth_page
1994
  gr.update(visible=True), # chat_page
1995
- gr.update(visible=False), # shop_page
1996
  gr.update(visible=data.get('is_admin', False)), # admin_page
1997
  greeting_html,
1998
- gr.update(visible=True, value=coin_html), # coin_display
1999
- gr.update(visible=True), # shop_nav_btn
2000
  gr.update(visible=True), # logout_nav_btn
2001
  gr.update(choices=model_choices, value="🤖 Auto (Recommended)"),
2002
  gr.update(maximum=info['max_tokens'], value=min(256, info['max_tokens'])),
2003
  limits_html,
2004
- empty_chat,
2005
- f'<div style="text-align: center; font-size: 24px; font-weight: 700; color: #f59e0b; margin-bottom: 24px;">Your Balance: 💰 {coins} coins</div>'
2006
  )
2007
 
2008
- def show_shop_page(code):
2009
- if not code:
2010
- return gr.update(), gr.update(), ""
2011
- data = validate_session(code)
2012
- if not data:
2013
- return gr.update(), gr.update(), ""
2014
-
2015
- coins = get_user_coins(data['user_id'])
2016
- info = get_user_limits_info(data['user_id'])
2017
-
2018
- coin_balance_html = f'<div style="text-align: center; font-size: 24px; font-weight: 700; color: #f59e0b; margin-bottom: 24px;">Your Balance: 💰 {coins} coins</div>'
2019
-
2020
- # Update milestone progress
2021
- total_msgs = info.get('total_messages', 0)
2022
- milestone_html = f'''
2023
- <script>
2024
- document.getElementById('milestone-10').textContent = '{min(total_msgs, 10)}/10';
2025
- document.getElementById('milestone-100').textContent = '{min(total_msgs, 100)}/100';
2026
- document.getElementById('milestone-1000').textContent = '{min(total_msgs, 1000)}/1000';
2027
- </script>
2028
- '''
2029
-
2030
- return gr.update(visible=False), gr.update(visible=True), coin_balance_html
2031
 
2032
  def back_to_chat():
2033
  return gr.update(visible=True), gr.update(visible=False)
@@ -2041,94 +2003,71 @@ if __name__ == "__main__":
2041
  [],
2042
  gr.update(visible=True), # auth_page
2043
  gr.update(visible=False), # chat_page
2044
- gr.update(visible=False), # shop_page
2045
  gr.update(visible=False), # admin_page
 
2046
  '<div class="user-greeting">Please sign in</div>',
2047
- gr.update(visible=False), # coin_display
2048
- gr.update(visible=False), # shop_nav_btn
2049
  gr.update(visible=False), # logout_nav_btn
2050
  "",
2051
- "",
2052
  ""
2053
  )
2054
 
2055
- def purchase_plan_handler(plan, code):
2056
- if not code:
2057
- return "❌ Session expired", gr.update(), gr.update()
2058
-
2059
- data = validate_session(code)
2060
- if not data:
2061
- return "❌ Session expired", gr.update(), gr.update()
2062
-
2063
- success, msg = purchase_plan_with_coins(data['user_id'], plan)
2064
- coins = get_user_coins(data['user_id'])
2065
-
2066
- coin_html = f'<div class="coin-badge">💰 {coins} coins</div>'
2067
- coin_balance_html = f'<div style="text-align: center; font-size: 24px; font-weight: 700; color: #f59e0b; margin-bottom: 24px;">Your Balance: 💰 {coins} coins</div>'
2068
-
2069
- if success:
2070
- return f"✅ {msg} You now have {coins} coins remaining!", coin_html, coin_balance_html
2071
- return f"❌ {msg} (You have {coins} coins)", coin_html, coin_balance_html
2072
-
2073
  def send_message_handler(message, history, show_thinking, temperature, model_choice, max_tokens, code):
2074
  global stop_generation
2075
  stop_generation.clear()
2076
 
2077
  if not code:
2078
- error_html = '<div class="speed-indicator error">❌ Session expired</div>'
2079
- return "", history, "", error_html, gr.update(), gr.update(), "", "", ""
2080
 
2081
  data = validate_session(code)
2082
  if not data:
2083
- error_html = '<div class="speed-indicator error">❌ Session expired</div>'
2084
- return "", history, "", error_html, gr.update(), gr.update(), "", "", ""
2085
 
2086
  if not message.strip():
2087
- return "", history, "", '<div class="speed-indicator">⚡ Ready to chat</div>', gr.update(), gr.update(), render_limits_panel(data), gr.update(), gr.update()
2088
 
2089
  info = get_user_limits_info(data['user_id'])
2090
 
2091
- # Check for first message milestone
2092
- if info['total_messages'] == 0:
2093
- add_coins(data['user_id'], COIN_REWARDS['first_message'], 'first_message', 'Sent first message!')
2094
-
2095
  # Model selection
2096
  if model_choice == "🤖 Auto (Recommended)" or not info['can_choose_model']:
2097
  user_available = get_available_models_for_user(data['user_id'])
2098
  if not user_available:
2099
- error_html = '<div class="speed-indicator error">❌ No models available</div>'
2100
- return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data), gr.update(), gr.update()
2101
  backend = select_model_auto(message, available_models, user_available)
2102
  if not backend:
2103
  error_html = '<div class="speed-indicator error">❌ Could not select model</div>'
2104
- return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data), gr.update(), gr.update()
2105
  model_name = backend.get_name()
2106
  else:
2107
  model_name = model_choice
2108
  can_use, msg = can_use_model(data['user_id'], model_name)
2109
  if not can_use:
2110
  error_html = f'<div class="speed-indicator error">❌ {msg}</div>'
2111
- return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data), gr.update(), gr.update()
2112
  backend = available_models[model_name]
2113
 
 
2114
  can_use, msg = can_use_model(data['user_id'], model_name)
2115
  if not can_use:
2116
  error_html = f'<div class="speed-indicator error">❌ {msg}</div>'
2117
- return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data), gr.update(), gr.update()
2118
 
 
2119
  increment_model_usage(data['user_id'], model_name)
2120
 
2121
- # Check milestones
2122
- new_info = get_user_limits_info(data['user_id'])
2123
- milestone_awarded, milestone_amount, milestone_num = check_message_milestones(data['user_id'], new_info['total_messages'])
2124
-
2125
  history.append({"role": "user", "content": message})
2126
- yield "", history, render_history(history, show_thinking), f'<div class="speed-indicator generating">⚡ Using {model_name}...</div>', gr.update(interactive=False), gr.update(visible=True), render_limits_panel(data), gr.update(), gr.update()
2127
 
 
2128
  prompt = f"User: {message}\nSam: <think>"
2129
  history.append({"role": "assistant", "content": "<think>"})
2130
 
2131
  actual_max_tokens = min(max_tokens, info['max_tokens'])
 
2132
  last_speed = 0
2133
  was_stopped = False
2134
 
@@ -2144,20 +2083,14 @@ if __name__ == "__main__":
2144
  history[-1]["content"] += new_chunk
2145
 
2146
  last_speed = avg_speed
2147
- yield "", history, render_history(history, show_thinking), f'<div class="speed-indicator generating">⚡ {tokens_per_sec:.1f} tok/s</div>', gr.update(interactive=False), gr.update(visible=True), render_limits_panel(data), gr.update(), gr.update()
2148
 
2149
- coins = get_user_coins(data['user_id'])
2150
- coin_html = f'<div class="coin-badge">💰 {coins} coins</div>'
2151
- coin_balance_html = f'<div style="text-align: center; font-size: 24px; font-weight: 700; color: #f59e0b; margin-bottom: 24px;">Your Balance: 💰 {coins} coins</div>'
2152
-
2153
- if milestone_awarded:
2154
- final_html = f'<div class="speed-indicator">🎉 {milestone_num} messages! +{milestone_amount} coins! - {last_speed:.1f} tok/s</div>'
2155
- elif was_stopped:
2156
  final_html = f'<div class="speed-indicator error">🛑 Stopped - {last_speed:.1f} tok/s</div>'
2157
  else:
2158
  final_html = f'<div class="speed-indicator">✅ Done - {last_speed:.1f} tok/s</div>'
2159
 
2160
- yield "", history, render_history(history, show_thinking), final_html, gr.update(interactive=True), gr.update(visible=False), render_limits_panel(data), coin_html, coin_balance_html
2161
 
2162
  def stop_generation_handler():
2163
  global stop_generation
@@ -2168,68 +2101,52 @@ if __name__ == "__main__":
2168
  empty = render_history([], True)
2169
  return [], empty, '<div class="speed-indicator">⚡ Ready to chat</div>', gr.update(interactive=True), gr.update(visible=False)
2170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2171
  def load_all_users():
2172
  users = get_all_users()
2173
  formatted = []
2174
  for user in users:
2175
  formatted.append([
2176
- user[0], user[1], user[2] or "N/A", user[3],
2177
- user[11], user[12], user[10]
 
 
 
 
2178
  ])
2179
  return formatted
2180
 
2181
- def load_coin_stats():
2182
- stats = get_coin_stats()
2183
- html = f'''
2184
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 20px 0;">
2185
- <div style="background: white; border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; text-align: center;">
2186
- <div style="font-size: 32px; color: #f59e0b; font-weight: 700;">{stats['total_balance']:,}</div>
2187
- <div style="color: #6b7280; font-size: 14px;">Total Balance</div>
2188
- </div>
2189
- <div style="background: white; border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; text-align: center;">
2190
- <div style="font-size: 32px; color: #10a37f; font-weight: 700;">{stats['total_earned']:,}</div>
2191
- <div style="color: #6b7280; font-size: 14px;">Total Earned</div>
2192
- </div>
2193
- <div style="background: white; border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; text-align: center;">
2194
- <div style="font-size: 32px; color: #ef4444; font-weight: 700;">{stats['total_spent']:,}</div>
2195
- <div style="color: #6b7280; font-size: 14px;">Total Spent</div>
2196
- </div>
2197
- <div style="background: white; border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; text-align: center;">
2198
- <div style="font-size: 32px; color: #3b82f6; font-weight: 700;">{stats['user_count']}</div>
2199
- <div style="color: #6b7280; font-size: 14px;">Total Users</div>
2200
- </div>
2201
- </div>
2202
- <div style="margin-top: 24px;"><strong>Top Coin Sources:</strong></div>
2203
- <ul style="list-style: none; padding: 0;">
2204
- '''
2205
- for source, amount in stats['top_sources']:
2206
- html += f'<li style="padding: 8px; background: #f9fafb; margin: 4px 0; border-radius: 6px;">{source}: <strong>{amount:,} coins</strong></li>'
2207
- html += '</ul>'
2208
- return html
2209
-
2210
- def grant_coins_handler(username, amount, reason):
2211
- if not username or not amount or not reason:
2212
- return "❌ Please fill all fields"
2213
-
2214
- with db_lock:
2215
- c = db_conn.cursor()
2216
- c.execute("SELECT id FROM users WHERE username = ?", (username,))
2217
- result = c.fetchone()
2218
-
2219
- if not result:
2220
- return f"❌ User '{username}' not found"
2221
-
2222
- user_id = result[0]
2223
-
2224
- grant_coins_admin(user_id, int(amount), reason)
2225
- return f"✅ Granted {amount} coins to {username}!"
2226
-
2227
  def load_pending_requests():
2228
  requests = get_pending_requests()
2229
  formatted = []
2230
  for req in requests:
2231
  formatted.append([
2232
- req[0], req[1], req[2],
 
 
2233
  req[3][:100] + "..." if len(req[3]) > 100 else req[3],
2234
  req[4][:10] if req[4] else "N/A"
2235
  ])
@@ -2240,7 +2157,7 @@ if __name__ == "__main__":
2240
  return "❌ Please fill all fields"
2241
  success, msg = update_user_plan(username, new_plan)
2242
  if success:
2243
- return f"✅ {msg}"
2244
  return f"❌ {msg}"
2245
 
2246
  def admin_approve_request_handler(request_id):
@@ -2248,7 +2165,7 @@ if __name__ == "__main__":
2248
  return "❌ Please enter a request ID"
2249
  success, msg = approve_request(int(request_id))
2250
  if success:
2251
- return f"✅ {msg}"
2252
  return f"❌ {msg}"
2253
 
2254
  def admin_deny_request_handler(request_id):
@@ -2256,32 +2173,32 @@ if __name__ == "__main__":
2256
  return "❌ Please enter a request ID"
2257
  success, msg = deny_request(int(request_id))
2258
  if success:
2259
- return f"✅ {msg}"
2260
  return f"❌ {msg}"
2261
 
2262
  # ==================== WIRE UP EVENTS ====================
2263
 
2264
  # Auth
2265
  auth_outputs = [
2266
- session_code, user_data, auth_msg, auth_page, chat_page, shop_page, admin_page,
2267
- user_greeting, coin_display, shop_nav_btn, logout_nav_btn,
2268
- model_selector, max_tokens_slider, limits_display, chat_html, shop_coin_balance
2269
  ]
2270
  auth_btn.click(handle_auth, [auth_username, auth_password, auth_email], auth_outputs)
2271
  auth_password.submit(handle_auth, [auth_username, auth_password, auth_email], auth_outputs)
2272
 
2273
  # Navigation
2274
- shop_nav_btn.click(show_shop_page, [session_code], [chat_page, shop_page, shop_coin_balance])
2275
- back_to_chat_from_shop_btn.click(back_to_chat, outputs=[chat_page, shop_page])
2276
 
2277
  logout_outputs = [
2278
- session_code, user_data, chat_history, auth_page, chat_page, shop_page, admin_page,
2279
- user_greeting, coin_display, shop_nav_btn, logout_nav_btn, chat_html, limits_display, shop_coin_balance
2280
  ]
2281
  logout_nav_btn.click(handle_logout, [session_code], logout_outputs)
2282
 
2283
  # Chat
2284
- send_outputs = [msg_input, chat_history, chat_html, speed_display, send_btn, stop_btn, limits_display, coin_display, shop_coin_balance]
2285
  send_btn.click(
2286
  send_message_handler,
2287
  [msg_input, chat_history, show_thinking_checkbox, temperature_slider, model_selector, max_tokens_slider, session_code],
@@ -2297,21 +2214,21 @@ if __name__ == "__main__":
2297
  clear_btn.click(clear_chat, [chat_history], [chat_history, chat_html, speed_display, send_btn, stop_btn])
2298
  new_chat_btn.click(clear_chat, [chat_history], [chat_history, chat_html, speed_display, send_btn, stop_btn])
2299
 
2300
- # Shop purchases
2301
- buy_explore_btn.click(lambda code: purchase_plan_handler('explore', code), [session_code], [shop_msg, coin_display, shop_coin_balance])
2302
- buy_plus_btn.click(lambda code: purchase_plan_handler('plus', code), [session_code], [shop_msg, coin_display, shop_coin_balance])
2303
- buy_pro_btn.click(lambda code: purchase_plan_handler('pro', code), [session_code], [shop_msg, coin_display, shop_coin_balance])
2304
- buy_research_btn.click(lambda code: purchase_plan_handler('Research', code), [session_code], [shop_msg, coin_display, shop_coin_balance])
2305
- buy_vip_btn.click(lambda code: purchase_plan_handler('VIP', code), [session_code], [shop_msg, coin_display, shop_coin_balance])
2306
 
2307
  # Admin
2308
  refresh_users_btn.click(load_all_users, outputs=[users_table])
2309
- refresh_coin_stats_btn.click(load_coin_stats, outputs=[coin_stats_display])
2310
- grant_btn.click(grant_coins_handler, [grant_username, grant_amount, grant_reason], [grant_msg])
2311
  refresh_requests_btn.click(load_pending_requests, outputs=[requests_table])
2312
  update_plan_btn.click(admin_update_plan_handler, [admin_username, admin_new_plan], [admin_msg])
2313
  approve_req_btn.click(admin_approve_request_handler, [request_id_input], [request_msg])
2314
  deny_req_btn.click(admin_deny_request_handler, [request_id_input], [request_msg])
 
 
2315
 
2316
  demo.launch(
2317
  debug=True,
@@ -2320,4 +2237,4 @@ if __name__ == "__main__":
2320
  server_port=7860,
2321
  favicon_path=None,
2322
  show_error=True
2323
- )
 
37
  conn = sqlite3.connect('sam_users.db', check_same_thread=False)
38
  c = conn.cursor()
39
 
40
+ # Users table
41
  c.execute('''CREATE TABLE IF NOT EXISTS users
42
  (id INTEGER PRIMARY KEY AUTOINCREMENT,
43
  username TEXT UNIQUE NOT NULL,
 
50
  messages_used_nano INTEGER DEFAULT 0,
51
  messages_used_mini INTEGER DEFAULT 0,
52
  messages_used_fast INTEGER DEFAULT 0,
53
+ messages_used_large INTEGER DEFAULT 0)''')
 
 
 
 
54
 
55
  # Upgrade requests table
56
  c.execute('''CREATE TABLE IF NOT EXISTS upgrade_requests
 
71
  timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
72
  FOREIGN KEY (user_id) REFERENCES users(id))''')
73
 
 
 
 
 
 
 
 
 
 
 
74
  # Create admin account if not exists
75
  admin_pass = hashlib.sha256("admin123".encode()).hexdigest()
76
  try:
77
+ c.execute("INSERT INTO users (username, password_hash, email, plan, is_admin) VALUES (?, ?, ?, ?, ?)",
78
+ ("admin", admin_pass, "admin@samx1.ai", "pro", 1))
79
  conn.commit()
80
  print("✅ Admin account created (username: admin, password: admin123)")
81
  except sqlite3.IntegrityError:
 
88
  db_conn = init_database()
89
  db_lock = threading.Lock()
90
 
91
+ # Plan limits with 3-hour rolling window
92
  PLAN_LIMITS = {
93
  'free': {
94
  'nano_messages': 100,
 
135
  'max_tokens': 1024,
136
  'reset_hours': 5
137
  },
138
+ 'VIP': { # 👈 Clean name using "hyper" instead of spaces
139
  'nano_messages': 100000000000000,
140
  'mini_messages': 1000,
141
  'fast_messages': 5000,
 
146
  }
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  def get_model_type(model_name):
151
  """Get model type from model name."""
 
170
  try:
171
  c = db_conn.cursor()
172
  now = datetime.now(AUSTRALIA_TZ).isoformat()
173
+ c.execute("INSERT INTO users (username, password_hash, email, rate_limit_start) VALUES (?, ?, ?, ?)",
174
+ (username, hash_password(password), email, now))
 
 
 
 
 
 
175
  db_conn.commit()
176
+ return True, "Account created successfully!"
177
  except sqlite3.IntegrityError:
178
  return False, "Username already exists!"
179
 
 
188
  return False, None
189
 
190
  def check_and_reset_limits(user_id):
191
+ """Check if 3-hour window has passed and reset limits if needed."""
192
  with db_lock:
193
  c = db_conn.cursor()
194
  c.execute("SELECT rate_limit_start, plan FROM users WHERE id = ?", (user_id,))
 
223
  c = db_conn.cursor()
224
  c.execute("""SELECT plan, rate_limit_start,
225
  messages_used_nano, messages_used_mini,
226
+ messages_used_fast, messages_used_large
 
227
  FROM users WHERE id = ?""", (user_id,))
228
  result = c.fetchone()
229
 
230
  if not result:
231
  return None
232
 
233
+ plan, rate_limit_start_str, nano_used, mini_used, fast_used, large_used = result
234
  limits = PLAN_LIMITS[plan]
235
 
236
  if rate_limit_start_str:
 
257
  'large_limit': limits['large_messages'],
258
  'can_choose_model': limits['can_choose_model'],
259
  'max_tokens': limits['max_tokens'],
260
+ 'reset_in': reset_str
 
261
  }
262
 
263
  def can_use_model(user_id, model_name):
 
273
  used = info[used_key]
274
  limit = info[limit_key]
275
 
276
+ if limit == -1:
277
  return True, "OK"
278
 
279
  if used >= limit:
 
288
 
289
  with db_lock:
290
  c = db_conn.cursor()
291
+ c.execute(f"UPDATE users SET {column} = {column} + 1 WHERE id = ?", (user_id,))
292
  db_conn.commit()
293
 
294
  def get_available_models_for_user(user_id):
 
303
  used = info[f'{model_type}_used']
304
  limit = info[f'{model_type}_limit']
305
 
306
+ if limit == -1 or used < limit:
307
  for model_name in available_models.keys():
308
  if get_model_type(model_name) == model_type:
309
  available.append(model_name)
 
318
  (user_id, tokens, model))
319
  db_conn.commit()
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  def request_upgrade(user_id, plan, reason):
322
  with db_lock:
323
  try:
 
335
  c.execute("""SELECT id, username, email, plan, created_at, is_admin,
336
  messages_used_nano, messages_used_mini,
337
  messages_used_fast, messages_used_large,
338
+ rate_limit_start
339
  FROM users ORDER BY created_at DESC""")
340
  return c.fetchall()
341
 
 
349
  ORDER BY r.created_at DESC""")
350
  return c.fetchall()
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  def update_user_plan(username, new_plan):
353
  with db_lock:
354
  try:
 
402
  except Exception as e:
403
  return False, f"Error: {str(e)}"
404
 
 
 
 
405
  # ==============================================================================
406
  # Model Architecture
407
  # ==============================================================================
 
806
  final_tokens_per_sec = tokens_generated / elapsed if elapsed > 0 else 0
807
  yield "", False, final_tokens_per_sec, final_tokens_per_sec, False
808
 
809
+ # PART 3 - Production-Grade Multi-Page UI (No Backend Changes)
 
 
810
  import secrets
811
  import json
812
  from datetime import datetime
813
 
814
+ # Global session storage (unchanged from original)
815
  active_sessions = {}
816
  session_lock = threading.Lock()
817
 
 
849
  import gradio as gr
850
 
851
  custom_css = """
852
+ /* Modern Production-Grade Styling */
853
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
854
 
855
  * { font-family: 'Inter', sans-serif; }
 
898
  border-radius: 20px;
899
  }
900
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901
  /* Plan Badge */
902
  .plan-badge {
903
  display: inline-block;
 
1005
  }
1006
  .user-message .message-content { color: white; }
1007
 
1008
+ /* Markdown Styling */
1009
+ .message-content code {
1010
+ background: #f3f4f6;
1011
+ padding: 2px 6px;
1012
+ border-radius: 4px;
1013
+ font-family: 'Courier New', monospace;
1014
+ font-size: 13px;
1015
+ }
1016
+ .message-content pre {
1017
+ background: #1f2937;
1018
+ color: #e5e7eb;
1019
+ padding: 12px;
1020
+ border-radius: 8px;
1021
+ overflow-x: auto;
1022
+ margin: 8px 0;
1023
+ position: relative;
1024
+ }
1025
+ .message-content pre code {
1026
+ background: transparent;
1027
+ padding: 0;
1028
+ color: inherit;
1029
+ }
1030
+ .message-content ul, .message-content ol {
1031
+ margin: 8px 0;
1032
+ padding-left: 20px;
1033
+ }
1034
+ .message-content li { margin: 4px 0; }
1035
+ .message-content strong { font-weight: 600; }
1036
+ .message-content em { font-style: italic; }
1037
+ .message-content a { color: #667eea; text-decoration: underline; }
1038
+
1039
+ /* Code Copy Button */
1040
+ .copy-button {
1041
+ position: absolute;
1042
+ top: 8px;
1043
+ right: 8px;
1044
+ background: rgba(255,255,255,0.1);
1045
+ border: 1px solid rgba(255,255,255,0.2);
1046
+ color: white;
1047
+ padding: 4px 8px;
1048
+ border-radius: 4px;
1049
+ font-size: 11px;
1050
+ cursor: pointer;
1051
+ opacity: 0;
1052
+ transition: all 0.2s;
1053
+ }
1054
+ .assistant-message:hover .copy-button { opacity: 1; }
1055
+ .copy-button:hover { background: rgba(255,255,255,0.2); }
1056
+
1057
  .thinking-content {
1058
  color: #6b7280;
1059
  font-style: italic;
 
1066
  font-size: 13px;
1067
  }
1068
 
1069
+ /* Message Actions */
1070
+ .message-actions {
1071
+ display: flex;
1072
+ gap: 8px;
1073
+ margin-top: 8px;
1074
+ opacity: 0;
1075
+ transition: opacity 0.2s;
1076
+ }
1077
+ .assistant-message:hover .message-actions { opacity: 1; }
1078
+ .action-btn {
1079
+ background: #f3f4f6;
1080
+ border: 1px solid #e5e7eb;
1081
+ padding: 4px 10px;
1082
+ border-radius: 6px;
1083
+ font-size: 12px;
1084
+ cursor: pointer;
1085
+ transition: all 0.2s;
1086
+ color: #6b7280;
1087
+ }
1088
+ .action-btn:hover {
1089
+ background: #e5e7eb;
1090
+ color: #374151;
1091
+ transform: translateY(-1px);
1092
+ }
1093
+
1094
  /* Input Area */
1095
  .input-container {
1096
  background: white;
 
1124
  transform: scale(1.08) !important;
1125
  box-shadow: 0 6px 16px rgba(0,0,0,0.25) !important;
1126
  }
1127
+ .circular-btn:active:not(:disabled) {
1128
+ transform: scale(0.95) !important;
1129
+ }
1130
  .send-btn {
1131
  background: linear-gradient(135deg, #10a37f 0%, #0d8c6c 100%) !important;
1132
  }
 
1134
  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
1135
  }
1136
 
1137
+ /* Token Counter */
1138
+ .token-counter {
1139
+ font-size: 11px;
1140
+ color: #9ca3af;
1141
+ text-align: right;
1142
+ padding: 4px 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1143
  }
1144
 
1145
+ /* Sidebar/Limits Panel */
1146
  .limits-panel {
1147
  background: white;
1148
  border: 1px solid #e5e7eb;
 
1198
  .progress-fill.warning { background: linear-gradient(90deg, #f59e0b 0%, #ea580c 100%); }
1199
  .progress-fill.danger { background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%); }
1200
 
1201
+ /* Plans Section */
1202
  .plans-grid {
1203
  display: grid;
1204
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
 
1217
  .plan-card:hover {
1218
  transform: translateY(-6px);
1219
  box-shadow: 0 12px 28px rgba(0,0,0,0.15);
1220
+ border-color: #667eea;
1221
  }
1222
  .plan-card.featured {
1223
+ border: 3px solid #667eea;
1224
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.25);
1225
  transform: scale(1.02);
1226
  }
1227
  .plan-card.featured::before {
 
1229
  position: absolute;
1230
  top: 14px;
1231
  right: -28px;
1232
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1233
  color: white;
1234
  padding: 3px 36px;
1235
  font-size: 10px;
 
1244
  color: #1f2937;
1245
  }
1246
  .plan-price {
1247
+ font-size: 13px;
1248
+ color: #6b7280;
1249
  margin-bottom: 18px;
 
1250
  }
1251
  .plan-features {
1252
  list-style: none;
 
1264
  font-weight: 700;
1265
  margin-right: 6px;
1266
  }
1267
+
1268
+ /* Speed Indicator */
1269
+ .speed-indicator {
1270
+ text-align: center;
1271
+ padding: 10px;
1272
+ background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
1273
+ border-radius: 8px;
1274
  font-weight: 600;
1275
+ color: #166534;
1276
+ margin-bottom: 10px;
1277
+ font-size: 13px;
1278
+ display: flex;
1279
+ align-items: center;
1280
+ justify-content: center;
1281
+ gap: 8px;
1282
  }
1283
+ .speed-indicator.generating {
1284
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
1285
+ color: #1e40af;
1286
+ animation: pulse 2s ease-in-out infinite;
1287
  }
1288
+ @keyframes pulse {
1289
+ 0%, 100% { opacity: 1; }
1290
+ 50% { opacity: 0.8; }
1291
+ }
1292
+ .speed-indicator.error {
1293
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
1294
+ color: #991b1b;
1295
  }
1296
 
1297
+ /* Toast Notifications */
1298
+ .toast {
1299
+ position: fixed;
1300
+ top: 80px;
1301
+ right: 20px;
1302
  background: white;
1303
+ padding: 12px 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1304
  border-radius: 8px;
1305
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
1306
+ display: flex;
1307
+ align-items: center;
1308
+ gap: 10px;
1309
+ z-index: 1000;
1310
+ animation: toastSlide 0.3s ease-out;
1311
  }
1312
+ @keyframes toastSlide {
1313
+ from { transform: translateX(400px); opacity: 0; }
1314
+ to { transform: translateX(0); opacity: 1; }
1315
  }
1316
+ .toast.success { border-left: 4px solid #10a37f; }
1317
+ .toast.error { border-left: 4px solid #ef4444; }
1318
+ .toast.info { border-left: 4px solid #3b82f6; }
1319
 
1320
  /* Settings Panel */
1321
  .settings-panel {
 
1326
  margin-bottom: 14px;
1327
  }
1328
 
1329
+ /* Keyboard Shortcuts */
1330
+ .kbd {
1331
+ display: inline-block;
1332
+ padding: 2px 6px;
1333
+ background: #f3f4f6;
1334
+ border: 1px solid #d1d5db;
1335
+ border-radius: 4px;
1336
+ font-family: monospace;
1337
+ font-size: 11px;
1338
+ color: #4b5563;
1339
+ }
1340
+
1341
  /* Empty State */
1342
  .empty-state {
1343
  text-align: center;
 
1348
  .empty-state-title { font-size: 18px; font-weight: 600; color: #6b7280; margin-bottom: 8px; }
1349
  .empty-state-subtitle { font-size: 14px; color: #9ca3af; }
1350
 
1351
+ /* Skeleton Loader */
1352
+ .skeleton {
1353
+ background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
1354
+ background-size: 200% 100%;
1355
+ animation: shimmer 1.5s infinite;
1356
+ border-radius: 4px;
1357
+ }
1358
+ @keyframes shimmer {
1359
+ 0% { background-position: 200% 0; }
1360
+ 100% { background-position: -200% 0; }
1361
+ }
1362
+
1363
  /* Responsive */
1364
  @media (max-width: 1024px) {
1365
  .chat-layout { flex-direction: column; }
 
1370
  .nav-left, .nav-right { width: 100%; justify-content: center; }
1371
  .chat-container { height: 400px; }
1372
  .plans-grid { grid-template-columns: 1fr; }
1373
+ .user-message, .assistant-message { max-width: 90%; }
1374
  }
1375
+
1376
+ /* Smooth Transitions */
1377
+ * { transition: background-color 0.2s, border-color 0.2s, color 0.2s; }
1378
+ button { transition: all 0.2s !important; }
1379
  """
1380
 
1381
+ # Greeting variations
1382
  def get_greeting(username):
1383
  import random
1384
  greetings = [
 
1386
  f"Welcome back, {username}! ✨",
1387
  f"Hi {username}! 🚀",
1388
  f"Hello {username}! 😊",
1389
+ f"Great to see you, {username}! 🎉",
1390
+ f"What's up, {username}? 💫",
1391
+ f"Howdy, {username}! 🤠",
1392
+ f"Yo {username}! 🔥"
1393
  ]
1394
  return random.choice(greetings)
1395
 
1396
+ # Markdown rendering (simple version)
1397
  def render_markdown(text):
1398
+ """Simple markdown rendering for common patterns"""
1399
  import re
1400
+
1401
+ # Code blocks
1402
+ text = re.sub(r'```(\w+)?\n(.*?)```', r'<pre><code class="\1">\2</code><button class="copy-button" onclick="copyCode(this)">Copy</button></pre>', text, flags=re.DOTALL)
1403
+
1404
+ # Inline code
1405
  text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
1406
+
1407
+ # Bold
1408
  text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
1409
  text = re.sub(r'__(.+?)__', r'<strong>\1</strong>', text)
1410
+
1411
+ # Italic
1412
  text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text)
1413
+ text = re.sub(r'_(.+?)_', r'<em>\1</em>', text)
1414
+
1415
+ # Links
1416
+ text = re.sub(r'\[([^\]]+)\]\(([^\)]+)\)', r'<a href="\2" target="_blank">\1</a>', text)
1417
+
1418
+ # Lists
1419
+ text = re.sub(r'^- (.+)$', r'<li>\1</li>', text, flags=re.MULTILINE)
1420
+ text = re.sub(r'^(\d+)\. (.+)$', r'<li>\2</li>', text, flags=re.MULTILINE)
1421
+ text = re.sub(r'(<li>.*?</li>\n?)+', r'<ul>\g<0></ul>', text, flags=re.DOTALL)
1422
+
1423
+ # Line breaks
1424
  text = text.replace('\n', '<br>')
1425
+
1426
  return text
1427
 
1428
+ # Format message HTML with markdown and actions
1429
  def format_message_html(role, content, show_thinking=True, message_id=None):
1430
  role_class = "user-message" if role == "user" else "assistant-message"
1431
  thinking = ""
1432
  answer = ""
1433
 
1434
+ # Extract thinking
1435
  if "<think>" in content:
1436
  parts = content.split("<think>", 1)
1437
  before_think = parts[0].strip()
 
1453
  else:
1454
  answer = content
1455
 
1456
+ # Render markdown
1457
  answer = render_markdown(answer)
1458
 
1459
  html = f'<div class="{role_class}" id="msg-{message_id}"><div class="message-content">'
 
1464
  if answer:
1465
  html += f'<div>{answer}</div>'
1466
 
1467
+ # Add message actions for assistant messages
1468
+ if role == "assistant":
1469
+ html += '''
1470
+ <div class="message-actions">
1471
+ <button class="action-btn" onclick="copyMessage(this)">📋 Copy</button>
1472
+ <button class="action-btn" onclick="regenerateResponse(this)">🔄 Regenerate</button>
1473
+ </div>
1474
+ '''
1475
+
1476
  html += '</div></div>'
1477
  return html
1478
 
 
1517
  ]
1518
 
1519
  for model_name, used, limit in models_info:
1520
+ if limit == -1:
1521
  percentage = 0
1522
  status_class = "limit-ok"
1523
  status_text = f'{used} / ∞'
 
1553
 
1554
  with gr.Blocks(css=custom_css, title="SAM-X-1 AI Chat", theme=gr.themes.Soft(primary_hue="slate")) as demo:
1555
 
1556
+ # JavaScript for interactive features
1557
  gr.HTML("""
1558
  <script>
1559
+ function copyCode(button) {
1560
+ const pre = button.parentElement;
1561
+ const code = pre.querySelector('code').textContent;
1562
+ navigator.clipboard.writeText(code).then(() => {
1563
+ button.textContent = 'Copied!';
1564
+ setTimeout(() => button.textContent = 'Copy', 2000);
1565
+ });
1566
+ }
1567
+
1568
+ function copyMessage(button) {
1569
+ const messageDiv = button.closest('.assistant-message');
1570
+ const content = messageDiv.querySelector('.message-content').textContent;
1571
+ navigator.clipboard.writeText(content).then(() => {
1572
+ showToast('Message copied!', 'success');
1573
+ });
1574
+ }
1575
+
1576
+ function regenerateResponse(button) {
1577
+ showToast('Regeneration feature coming soon!', 'info');
1578
+ }
1579
+
1580
+ function showToast(message, type = 'info') {
1581
+ const toast = document.createElement('div');
1582
+ toast.className = `toast ${type}`;
1583
+ toast.innerHTML = `
1584
+ <span>${type === 'success' ? '✓' : type === 'error' ? '✗' : 'ℹ'}</span>
1585
+ <span>${message}</span>
1586
+ `;
1587
+ document.body.appendChild(toast);
1588
+ setTimeout(() => toast.remove(), 3000);
1589
+ }
1590
+
1591
+ // Keyboard shortcuts
1592
+ document.addEventListener('keydown', function(e) {
1593
+ // Ctrl/Cmd + K for search (future feature)
1594
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
1595
+ e.preventDefault();
1596
+ showToast('Search coming soon!', 'info');
1597
+ }
1598
+
1599
+ // Esc to stop generation
1600
+ if (e.key === 'Escape') {
1601
+ const stopBtn = document.querySelector('.stop-btn');
1602
+ if (stopBtn && !stopBtn.disabled) stopBtn.click();
1603
+ }
1604
+ });
1605
+
1606
+ // Auto-scroll chat to bottom
1607
  function scrollChatToBottom() {
1608
  const chatContainer = document.querySelector('.chat-container');
1609
+ if (chatContainer) {
1610
+ chatContainer.scrollTop = chatContainer.scrollHeight;
1611
+ }
1612
  }
1613
+
1614
+ // Call after messages update
1615
  setInterval(scrollChatToBottom, 500);
1616
  </script>
1617
  """)
1618
 
1619
+ # State management
1620
  session_code = gr.State("")
1621
  user_data = gr.State(None)
1622
  chat_history = gr.State([])
 
1624
  # Navigation Bar
1625
  with gr.Row(elem_classes="nav-bar"):
1626
  with gr.Column(scale=1, elem_classes="nav-left"):
1627
+ gr.HTML('<div class="nav-brand">🤖 SAM-X-1 <span style="font-size: 12px; opacity: 0.8; font-weight: 400;">v3.0</span></div>')
1628
  with gr.Column(scale=2, elem_classes="nav-right"):
1629
  user_greeting = gr.HTML('<div class="user-greeting">Please sign in</div>')
 
1630
  with gr.Row():
1631
+ upgrade_nav_btn = gr.Button(" Upgrade", size="sm", visible=False)
1632
  logout_nav_btn = gr.Button("🚪 Logout", size="sm", visible=False)
1633
 
1634
  # AUTH PAGE
1635
  with gr.Group(visible=True) as auth_page:
1636
  with gr.Column(elem_classes="auth-container"):
1637
  gr.HTML('<div class="auth-title">Welcome to SAM-X-1</div>')
1638
+ gr.HTML('<div class="auth-subtitle">Sign in or create account • Auto-detects new users</div>')
1639
 
1640
+ auth_username = gr.Textbox(
1641
+ label="Username",
1642
+ placeholder="Enter your username",
1643
+ elem_id="auth-username"
1644
+ )
1645
+ auth_password = gr.Textbox(
1646
+ label="Password",
1647
+ type="password",
1648
+ placeholder="Enter your password",
1649
+ elem_id="auth-password"
1650
+ )
1651
+ auth_email = gr.Textbox(
1652
+ label="Email (optional, for new accounts)",
1653
+ placeholder="your@email.com"
1654
+ )
1655
 
1656
  auth_btn = gr.Button("Continue →", variant="primary", size="lg")
1657
  auth_msg = gr.Markdown("")
1658
+
1659
+ gr.Markdown("""
1660
+ <div style="text-align: center; margin-top: 20px; font-size: 12px; color: #9ca3af;">
1661
+ <p>🔐 Secure authentication • 🆓 Free tier available</p>
1662
+ <p>Press <span class="kbd">Enter</span> to continue</p>
1663
+ </div>
1664
+ """)
1665
 
1666
  # CHAT PAGE
1667
  with gr.Group(visible=False) as chat_page:
1668
  with gr.Row(elem_classes="chat-layout"):
1669
+ # Main Chat Area
1670
  with gr.Column(elem_classes="chat-main"):
1671
  chat_html = gr.HTML(value='')
1672
+
1673
  speed_display = gr.HTML('<div class="speed-indicator">⚡ Ready to chat</div>')
1674
 
1675
  with gr.Column(elem_classes="input-container"):
1676
  with gr.Row(elem_classes="input-row"):
1677
  msg_input = gr.Textbox(
1678
+ placeholder="Ask me anything... (Shift+Enter for new line)",
1679
  show_label=False,
1680
  scale=10,
1681
  lines=1,
1682
+ max_lines=5,
1683
+ elem_id="chat-input"
1684
  )
1685
  send_btn = gr.Button("▶", elem_classes=["circular-btn", "send-btn"])
1686
  stop_btn = gr.Button("⏹", elem_classes=["circular-btn", "stop-btn"], visible=False)
1687
+
1688
+ token_counter = gr.HTML('<div class="token-counter">0 / 256 tokens</div>')
1689
 
1690
  with gr.Row():
1691
  clear_btn = gr.Button("🗑️ Clear Chat", size="sm")
1692
  new_chat_btn = gr.Button("➕ New Chat", size="sm", variant="primary")
1693
+ export_btn = gr.Button("📥 Export", size="sm")
1694
 
1695
+ # Sidebar
1696
  with gr.Column(elem_classes="chat-sidebar"):
1697
  limits_display = gr.HTML("")
1698
 
 
1700
  model_selector = gr.Dropdown(
1701
  choices=["🤖 Auto (Recommended)"],
1702
  value="🤖 Auto (Recommended)",
1703
+ label="Model Selection",
1704
+ info="AI picks the best model"
1705
  )
1706
  max_tokens_slider = gr.Slider(
1707
  minimum=64, maximum=512, value=256, step=64,
1708
+ label="Max Tokens",
1709
+ info="Response length limit"
1710
  )
1711
  temperature_slider = gr.Slider(
1712
  minimum=0.0, maximum=2.0, value=0.7, step=0.1,
1713
+ label="Temperature",
1714
+ info="Creativity level"
1715
  )
1716
  show_thinking_checkbox = gr.Checkbox(
1717
  label="💭 Show Thinking Process",
1718
+ value=True,
1719
+ info="See AI's reasoning"
1720
  )
1721
+
1722
+ with gr.Accordion("ℹ️ Tips & Shortcuts", open=False):
1723
+ gr.Markdown("""
1724
+ ### Keyboard Shortcuts
1725
+ - <span class="kbd">Enter</span> - Send message
1726
+ - <span class="kbd">Shift+Enter</span> - New line
1727
+ - <span class="kbd">Esc</span> - Stop generation
1728
+ - <span class="kbd">Ctrl+K</span> - Search (soon)
1729
+
1730
+ ### Tips
1731
+ - Be specific in your questions
1732
+ - Use markdown for formatting
1733
+ - Auto mode picks the best model
1734
+ - Check limits panel regularly
1735
+ """)
1736
 
1737
+ # UPGRADE PAGE
1738
+ with gr.Group(visible=False) as upgrade_page:
1739
  gr.HTML('''
1740
  <div style="text-align: center; margin-bottom: 32px;">
1741
+ <div style="font-size: 32px; font-weight: 700; margin-bottom: 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
1742
+ Choose Your Plan
1743
+ </div>
1744
+ <div style="font-size: 16px; color: #6b7280;">
1745
+ Unlock more power and flexibility
1746
+ </div>
1747
  </div>
1748
  ''')
1749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1750
  gr.HTML('''
1751
+ <div class="plans-grid">
1752
+ <div class="plan-card">
1753
+ <div class="plan-name">Free 🆓</div>
1754
+ <div class="plan-price">Perfect for getting started</div>
1755
+ <ul class="plan-features">
1756
+ <li>Nano: Unlimited messages</li>
1757
+ <li>Mini: Unlimited messages</li>
1758
+ <li>Fast: 10 messages/3h</li>
1759
+ <li>Large: 8 messages/3h</li>
1760
+ <li>Auto model selection</li>
1761
+ <li>256 max tokens</li>
1762
+ <li>Community support</li>
1763
+ </ul>
1764
  </div>
1765
+
1766
+ <div class="plan-card featured">
1767
+ <div class="plan-name">Plus ⭐</div>
1768
+ <div class="plan-price">Great for power users</div>
1769
+ <ul class="plan-features">
1770
+ <li>Everything in Free</li>
1771
+ <li>Fast: Unlimited messages</li>
1772
+ <li>Large: 20 messages/3h</li>
1773
+ <li>Manual model selection</li>
1774
+ <li>384 max tokens</li>
1775
+ <li>Priority support</li>
1776
+ <li>Advanced settings</li>
1777
+ </ul>
1778
  </div>
1779
+
1780
+ <div class="plan-card">
1781
+ <div class="plan-name">Explore 🔍</div>
1782
+ <div class="plan-price">For curious learners</div>
1783
+ <ul class="plan-features">
1784
+ <li>Everything in Free</li>
1785
+ <li>Nano & Mini: Unlimited</li>
1786
+ <li>Fast: 14 messages/3h</li>
1787
+ <li>Large: 10 messages/3h</li>
1788
+ <li>Manual model selection</li>
1789
+ <li>512 max tokens</li>
1790
+ <li>Extended support</li>
1791
+ </ul>
1792
  </div>
1793
+
1794
+ <div class="plan-card featured">
1795
+ <div class="plan-name">Pro 💎</div>
1796
+ <div class="plan-price">For professionals</div>
1797
+ <ul class="plan-features">
1798
+ <li>Everything in Plus</li>
1799
+ <li>All models unlimited</li>
1800
+ <li>512 max tokens</li>
1801
+ <li>Fastest reset (3h)</li>
1802
+ <li>24/7 premium support</li>
1803
+ <li>Early feature access</li>
1804
+ <li>API access (soon)</li>
1805
+ </ul>
1806
  </div>
1807
+
1808
+ <div class="plan-card">
1809
+ <div class="plan-name">Research 🔬</div>
1810
+ <div class="plan-price">For researchers & educators</div>
1811
+ <ul class="plan-features">
1812
+ <li>Everything in Pro</li>
1813
+ <li>Extended limits (1000+ msgs)</li>
1814
+ <li>1024 max tokens</li>
1815
+ <li>Batch processing</li>
1816
+ <li>Custom fine-tuning</li>
1817
+ <li>Dedicated support</li>
1818
+ <li>Academic discount</li>
1819
+ </ul>
1820
  </div>
1821
  </div>
1822
  ''')
1823
 
1824
+ gr.Markdown("### 📝 Request an Upgrade")
1825
+ gr.Markdown("Fill out the form below and an admin will review your request within 24 hours.")
1826
+
1827
  with gr.Row():
1828
+ with gr.Column(scale=2):
1829
+ upgrade_plan_choice = gr.Radio(
1830
+ choices=["plus", "pro", "explore", "Research"],
1831
+ label="Select Plan",
1832
+ value="plus"
1833
+ )
1834
+ upgrade_reason = gr.Textbox(
1835
+ label="Why do you need this upgrade?",
1836
+ placeholder="Tell us about your use case, what you're building, or why you need more access...",
1837
+ lines=4
1838
+ )
1839
+ with gr.Row():
1840
+ submit_upgrade_btn = gr.Button("Submit Request 📨", variant="primary", size="lg", scale=2)
1841
+ back_to_chat_btn = gr.Button("← Back to Chat", size="lg", scale=1)
1842
+ upgrade_msg = gr.Markdown("")
1843
 
1844
  # ADMIN PAGE
1845
  with gr.Group(visible=False) as admin_page:
1846
  gr.HTML('''
1847
  <div style="text-align: center; margin-bottom: 24px;">
1848
+ <div style="font-size: 28px; font-weight: 700; color: #1f2937;">
1849
+ 👨‍💼 Admin Dashboard
1850
+ </div>
1851
  </div>
1852
  ''')
1853
 
 
1857
  refresh_users_btn = gr.Button("🔄 Refresh Users", size="sm")
1858
 
1859
  users_table = gr.Dataframe(
1860
+ headers=["ID", "Username", "Email", "Plan", "Created", "Admin"],
1861
  wrap=True
1862
  )
1863
 
1864
  gr.Markdown("### ✏️ Update User Plan")
1865
  with gr.Row():
1866
+ admin_username = gr.Textbox(label="Username", scale=2, placeholder="username")
1867
  admin_new_plan = gr.Dropdown(
1868
  choices=["free", "plus", "pro", "explore", "Research", "VIP"],
1869
  label="New Plan",
 
1873
  update_plan_btn = gr.Button("Update Plan", variant="primary", scale=1)
1874
  admin_msg = gr.Markdown("")
1875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1876
  with gr.Tab("📋 Upgrade Requests"):
1877
  with gr.Row():
1878
  refresh_requests_btn = gr.Button("🔄 Refresh Requests", size="sm")
 
1883
  )
1884
 
1885
  gr.Markdown("### 🔍 Review Request")
1886
+ request_id_input = gr.Number(
1887
+ label="Request ID (from table above)",
1888
+ precision=0,
1889
+ minimum=1,
1890
+ info="Enter the ID number from the first column"
1891
+ )
1892
  with gr.Row():
1893
  approve_req_btn = gr.Button("✅ Approve Request", variant="primary", size="lg")
1894
  deny_req_btn = gr.Button("❌ Deny Request", variant="stop", size="lg")
1895
  request_msg = gr.Markdown("")
1896
+
1897
+ with gr.Tab("📊 Analytics (Coming Soon)"):
1898
+ gr.Markdown("""
1899
+ ### 📈 Usage Statistics
1900
+ - Total users: Coming soon
1901
+ - Active users (24h): Coming soon
1902
+ - Total messages: Coming soon
1903
+ - Most used model: Coming soon
1904
+ - Average tokens/message: Coming soon
1905
+ """)
1906
 
1907
  # ==================== EVENT HANDLERS ====================
1908
 
1909
  def handle_auth(username, password, email):
1910
+ """Unified auth - auto signup if new, FIX: Handle both 'id' and 'user_id'"""
1911
  if len(username) < 3:
1912
  return (
1913
  None, None, "❌ Username must be at least 3 characters",
1914
  gr.update(), gr.update(), gr.update(), gr.update(),
1915
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ""
 
1916
  )
1917
  if len(password) < 6:
1918
  return (
1919
  None, None, "❌ Password must be at least 6 characters",
1920
  gr.update(), gr.update(), gr.update(), gr.update(),
1921
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ""
 
1922
  )
1923
 
1924
+ # Try login first
1925
  success, data = authenticate_user(username, password)
1926
 
1927
  if not success:
1928
+ # Try signup
1929
  success, message = create_user(username, password, email)
1930
  if success:
1931
+ # Auto-login after signup
1932
  success, data = authenticate_user(username, password)
1933
  if not success:
1934
  return (
1935
  None, None, "❌ Account created but login failed",
1936
  gr.update(), gr.update(), gr.update(), gr.update(),
1937
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ""
 
1938
  )
1939
  else:
1940
  return (
1941
  None, None, f"❌ {message}",
1942
  gr.update(), gr.update(), gr.update(), gr.update(),
1943
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ""
 
1944
  )
1945
 
1946
+ # FIX: Normalize data to always have 'user_id'
1947
  if 'id' in data and 'user_id' not in data:
1948
  data['user_id'] = data['id']
1949
 
1950
+ # Generate session
1951
  code = create_session(data)
1952
 
1953
+ # Get user info
 
 
 
1954
  info = get_user_limits_info(data['user_id'])
 
1955
  if not info:
1956
  return (
1957
  None, None, "❌ Could not load user info",
1958
  gr.update(), gr.update(), gr.update(), gr.update(),
1959
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ""
 
1960
  )
1961
 
1962
  plan_class = f"plan-{info['plan'].lower()}"
1963
  greeting_html = f'<div class="user-greeting">{get_greeting(username)} <span class="plan-badge {plan_class}">{info["plan"]}</span></div>'
 
1964
 
1965
+ # Set model choices
1966
  if info['can_choose_model']:
1967
  model_choices = ["🤖 Auto (Recommended)"] + list(available_models.keys())
1968
  else:
1969
  model_choices = ["🤖 Auto (Recommended)"]
1970
 
1971
  limits_html = render_limits_panel(data)
 
1972
 
1973
+ empty_chat = render_history([], True)
1974
 
1975
  return (
1976
  code,
1977
  data,
1978
+ f"✅ Welcome, **{username}**! Your session is active.",
1979
  gr.update(visible=False), # auth_page
1980
  gr.update(visible=True), # chat_page
 
1981
  gr.update(visible=data.get('is_admin', False)), # admin_page
1982
  greeting_html,
1983
+ gr.update(visible=True), # upgrade_nav_btn
 
1984
  gr.update(visible=True), # logout_nav_btn
1985
  gr.update(choices=model_choices, value="🤖 Auto (Recommended)"),
1986
  gr.update(maximum=info['max_tokens'], value=min(256, info['max_tokens'])),
1987
  limits_html,
1988
+ empty_chat
 
1989
  )
1990
 
1991
+ def show_upgrade_page():
1992
+ return gr.update(visible=False), gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1993
 
1994
  def back_to_chat():
1995
  return gr.update(visible=True), gr.update(visible=False)
 
2003
  [],
2004
  gr.update(visible=True), # auth_page
2005
  gr.update(visible=False), # chat_page
 
2006
  gr.update(visible=False), # admin_page
2007
+ gr.update(visible=False), # upgrade_page
2008
  '<div class="user-greeting">Please sign in</div>',
2009
+ gr.update(visible=False), # upgrade_nav_btn
 
2010
  gr.update(visible=False), # logout_nav_btn
2011
  "",
 
2012
  ""
2013
  )
2014
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2015
  def send_message_handler(message, history, show_thinking, temperature, model_choice, max_tokens, code):
2016
  global stop_generation
2017
  stop_generation.clear()
2018
 
2019
  if not code:
2020
+ error_html = '<div class="speed-indicator error">❌ Session expired - please sign in again</div>'
2021
+ return "", history, "", error_html, gr.update(), gr.update(), ""
2022
 
2023
  data = validate_session(code)
2024
  if not data:
2025
+ error_html = '<div class="speed-indicator error">❌ Session expired - please sign in again</div>'
2026
+ return "", history, "", error_html, gr.update(), gr.update(), ""
2027
 
2028
  if not message.strip():
2029
+ return "", history, "", '<div class="speed-indicator">⚡ Ready to chat</div>', gr.update(), gr.update(), render_limits_panel(data)
2030
 
2031
  info = get_user_limits_info(data['user_id'])
2032
 
 
 
 
 
2033
  # Model selection
2034
  if model_choice == "🤖 Auto (Recommended)" or not info['can_choose_model']:
2035
  user_available = get_available_models_for_user(data['user_id'])
2036
  if not user_available:
2037
+ error_html = '<div class="speed-indicator error">❌ No models available (limits reached)</div>'
2038
+ return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data)
2039
  backend = select_model_auto(message, available_models, user_available)
2040
  if not backend:
2041
  error_html = '<div class="speed-indicator error">❌ Could not select model</div>'
2042
+ return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data)
2043
  model_name = backend.get_name()
2044
  else:
2045
  model_name = model_choice
2046
  can_use, msg = can_use_model(data['user_id'], model_name)
2047
  if not can_use:
2048
  error_html = f'<div class="speed-indicator error">❌ {msg}</div>'
2049
+ return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data)
2050
  backend = available_models[model_name]
2051
 
2052
+ # Final check
2053
  can_use, msg = can_use_model(data['user_id'], model_name)
2054
  if not can_use:
2055
  error_html = f'<div class="speed-indicator error">❌ {msg}</div>'
2056
+ return "", history, "", error_html, gr.update(), gr.update(), render_limits_panel(data)
2057
 
2058
+ # Increment usage
2059
  increment_model_usage(data['user_id'], model_name)
2060
 
2061
+ # Add user message
 
 
 
2062
  history.append({"role": "user", "content": message})
2063
+ yield "", history, render_history(history, show_thinking), f'<div class="speed-indicator generating">⚡ Using {model_name}...</div>', gr.update(interactive=False), gr.update(visible=True), render_limits_panel(data)
2064
 
2065
+ # Start generation
2066
  prompt = f"User: {message}\nSam: <think>"
2067
  history.append({"role": "assistant", "content": "<think>"})
2068
 
2069
  actual_max_tokens = min(max_tokens, info['max_tokens'])
2070
+
2071
  last_speed = 0
2072
  was_stopped = False
2073
 
 
2083
  history[-1]["content"] += new_chunk
2084
 
2085
  last_speed = avg_speed
2086
+ yield "", history, render_history(history, show_thinking), f'<div class="speed-indicator generating">⚡ {tokens_per_sec:.1f} tok/s</div>', gr.update(interactive=False), gr.update(visible=True), render_limits_panel(data)
2087
 
2088
+ if was_stopped:
 
 
 
 
 
 
2089
  final_html = f'<div class="speed-indicator error">🛑 Stopped - {last_speed:.1f} tok/s</div>'
2090
  else:
2091
  final_html = f'<div class="speed-indicator">✅ Done - {last_speed:.1f} tok/s</div>'
2092
 
2093
+ yield "", history, render_history(history, show_thinking), final_html, gr.update(interactive=True), gr.update(visible=False), render_limits_panel(data)
2094
 
2095
  def stop_generation_handler():
2096
  global stop_generation
 
2101
  empty = render_history([], True)
2102
  return [], empty, '<div class="speed-indicator">⚡ Ready to chat</div>', gr.update(interactive=True), gr.update(visible=False)
2103
 
2104
+ def export_chat(history):
2105
+ # Simple export as text
2106
+ text = ""
2107
+ for msg in history:
2108
+ role = "You" if msg["role"] == "user" else "SAM-X-1"
2109
+ text += f"{role}: {msg['content']}\n\n"
2110
+ return text
2111
+
2112
+ def submit_upgrade_request(code, plan, reason):
2113
+ if not code:
2114
+ return "❌ Session expired"
2115
+
2116
+ data = validate_session(code)
2117
+ if not data:
2118
+ return "❌ Session expired"
2119
+
2120
+ if not reason.strip():
2121
+ return "❌ Please provide a reason for your upgrade request"
2122
+
2123
+ success, msg = request_upgrade(data['user_id'], plan, reason)
2124
+ if success:
2125
+ return f"✅ {msg}\n\nAn admin will review your request within 24 hours. You'll be notified via email if provided."
2126
+ return f"❌ {msg}"
2127
+
2128
  def load_all_users():
2129
  users = get_all_users()
2130
  formatted = []
2131
  for user in users:
2132
  formatted.append([
2133
+ user[0],
2134
+ user[1],
2135
+ user[2] or "N/A",
2136
+ user[3],
2137
+ user[4][:10] if user[4] else "N/A",
2138
+ "Yes" if user[5] else "No"
2139
  ])
2140
  return formatted
2141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2142
  def load_pending_requests():
2143
  requests = get_pending_requests()
2144
  formatted = []
2145
  for req in requests:
2146
  formatted.append([
2147
+ req[0],
2148
+ req[1],
2149
+ req[2],
2150
  req[3][:100] + "..." if len(req[3]) > 100 else req[3],
2151
  req[4][:10] if req[4] else "N/A"
2152
  ])
 
2157
  return "❌ Please fill all fields"
2158
  success, msg = update_user_plan(username, new_plan)
2159
  if success:
2160
+ return f"✅ {msg}\n\nThe user's limits have been reset and they now have access to {new_plan} features."
2161
  return f"❌ {msg}"
2162
 
2163
  def admin_approve_request_handler(request_id):
 
2165
  return "❌ Please enter a request ID"
2166
  success, msg = approve_request(int(request_id))
2167
  if success:
2168
+ return f"✅ {msg}\n\nThe user has been upgraded and can now access their new plan features."
2169
  return f"❌ {msg}"
2170
 
2171
  def admin_deny_request_handler(request_id):
 
2173
  return "❌ Please enter a request ID"
2174
  success, msg = deny_request(int(request_id))
2175
  if success:
2176
+ return f"✅ {msg}\n\nThe request has been marked as denied."
2177
  return f"❌ {msg}"
2178
 
2179
  # ==================== WIRE UP EVENTS ====================
2180
 
2181
  # Auth
2182
  auth_outputs = [
2183
+ session_code, user_data, auth_msg, auth_page, chat_page, admin_page,
2184
+ user_greeting, upgrade_nav_btn, logout_nav_btn,
2185
+ model_selector, max_tokens_slider, limits_display, chat_html
2186
  ]
2187
  auth_btn.click(handle_auth, [auth_username, auth_password, auth_email], auth_outputs)
2188
  auth_password.submit(handle_auth, [auth_username, auth_password, auth_email], auth_outputs)
2189
 
2190
  # Navigation
2191
+ upgrade_nav_btn.click(show_upgrade_page, outputs=[chat_page, upgrade_page])
2192
+ back_to_chat_btn.click(back_to_chat, outputs=[chat_page, upgrade_page])
2193
 
2194
  logout_outputs = [
2195
+ session_code, user_data, chat_history, auth_page, chat_page, admin_page, upgrade_page,
2196
+ user_greeting, upgrade_nav_btn, logout_nav_btn, chat_html, limits_display
2197
  ]
2198
  logout_nav_btn.click(handle_logout, [session_code], logout_outputs)
2199
 
2200
  # Chat
2201
+ send_outputs = [msg_input, chat_history, chat_html, speed_display, send_btn, stop_btn, limits_display]
2202
  send_btn.click(
2203
  send_message_handler,
2204
  [msg_input, chat_history, show_thinking_checkbox, temperature_slider, model_selector, max_tokens_slider, session_code],
 
2214
  clear_btn.click(clear_chat, [chat_history], [chat_history, chat_html, speed_display, send_btn, stop_btn])
2215
  new_chat_btn.click(clear_chat, [chat_history], [chat_history, chat_html, speed_display, send_btn, stop_btn])
2216
 
2217
+ # Upgrade
2218
+ submit_upgrade_btn.click(
2219
+ submit_upgrade_request,
2220
+ [session_code, upgrade_plan_choice, upgrade_reason],
2221
+ [upgrade_msg]
2222
+ )
2223
 
2224
  # Admin
2225
  refresh_users_btn.click(load_all_users, outputs=[users_table])
 
 
2226
  refresh_requests_btn.click(load_pending_requests, outputs=[requests_table])
2227
  update_plan_btn.click(admin_update_plan_handler, [admin_username, admin_new_plan], [admin_msg])
2228
  approve_req_btn.click(admin_approve_request_handler, [request_id_input], [request_msg])
2229
  deny_req_btn.click(admin_deny_request_handler, [request_id_input], [request_msg])
2230
+
2231
+
2232
 
2233
  demo.launch(
2234
  debug=True,
 
2237
  server_port=7860,
2238
  favicon_path=None,
2239
  show_error=True
2240
+ )