Stanley03 commited on
Commit
e3b1de7
Β·
verified Β·
1 Parent(s): 1414a7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +379 -208
app.py CHANGED
@@ -1,23 +1,20 @@
 
1
  from flask import Flask, request, jsonify, send_file
2
  from flask_cors import CORS
3
- from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
4
  import torch
5
  import time
6
- import re
7
  import logging
8
- from threading import Thread
9
- import queue
10
  import io
11
  import base64
12
  import random
 
 
 
13
  from PIL import Image, ImageDraw, ImageFont
14
- import os
15
  import json
16
- import hashlib
17
- import secrets
18
- from datetime import datetime, timedelta
19
- import sqlite3
20
  from functools import wraps
 
21
 
22
  # Configure logging
23
  logging.basicConfig(level=logging.INFO)
@@ -27,47 +24,102 @@ app = Flask(__name__)
27
  CORS(app)
28
 
29
  # ============================================================================
30
- # DATABASE SETUP (Lightweight for Hugging Face)
31
  # ============================================================================
32
 
33
  def init_db():
34
  """Initialize SQLite database for API keys"""
35
- conn = sqlite3.connect('/tmp/api_keys.db')
36
- c = conn.cursor()
37
-
38
- # Create API keys table
39
- c.execute('''CREATE TABLE IF NOT EXISTS api_keys
40
- (id TEXT PRIMARY KEY,
41
- name TEXT,
42
- api_key TEXT UNIQUE,
43
- created TIMESTAMP,
44
- last_used TIMESTAMP,
45
- requests INTEGER DEFAULT 0,
46
- credits INTEGER DEFAULT 1000,
47
- is_active BOOLEAN DEFAULT 1,
48
- user_id TEXT,
49
- tier TEXT DEFAULT 'free')''')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Create invite codes table
52
- c.execute('''CREATE TABLE IF NOT EXISTS invite_codes
53
- (code TEXT PRIMARY KEY,
54
- created TIMESTAMP,
55
- expires TIMESTAMP,
56
- used BOOLEAN DEFAULT 0,
57
- used_by TEXT)''')
58
 
59
- # Create usage stats table
60
- c.execute('''CREATE TABLE IF NOT EXISTS usage_stats
61
- (date TEXT,
62
- api_key TEXT,
63
- endpoint TEXT,
64
- credits_used INTEGER)''')
65
 
66
- conn.commit()
67
- conn.close()
68
- logger.info("βœ… Database initialized")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- init_db()
 
71
 
72
  # ============================================================================
73
  # API KEY MANAGEMENT FUNCTIONS
@@ -76,19 +128,23 @@ init_db()
76
  def authenticate_api_key():
77
  """Authenticate API key from request header"""
78
  auth_header = request.headers.get('Authorization')
79
- if not auth_header:
80
  return None
81
 
82
- # Extract token (Bearer <token>)
83
- token = auth_header.replace('Bearer ', '').strip()
84
-
85
- conn = sqlite3.connect('/tmp/api_keys.db')
86
- c = conn.cursor()
87
- c.execute('''SELECT * FROM api_keys WHERE api_key = ? AND is_active = 1''', (token,))
88
- key_data = c.fetchone()
89
- conn.close()
90
 
91
- return key_data
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  def check_credits(api_key_data):
94
  """Check if API key has sufficient credits"""
@@ -96,40 +152,45 @@ def check_credits(api_key_data):
96
 
97
  def update_usage(api_key, endpoint, credits_used=1):
98
  """Update usage statistics"""
99
- conn = sqlite3.connect('/tmp/api_keys.db')
100
- c = conn.cursor()
101
-
102
- # Update key usage
103
- c.execute('''UPDATE api_keys
104
- SET requests = requests + 1,
105
- credits = credits - ?,
106
- last_used = CURRENT_TIMESTAMP
107
- WHERE api_key = ?''',
108
- (credits_used, api_key))
109
-
110
- # Record in stats
111
- today = datetime.now().strftime('%Y-%m-%d')
112
- c.execute('''INSERT INTO usage_stats (date, api_key, endpoint, credits_used)
113
- VALUES (?, ?, ?, ?)''',
114
- (today, api_key, endpoint, credits_used))
115
-
116
- conn.commit()
117
- conn.close()
 
 
 
 
118
 
119
  def generate_api_key(user_id="anonymous", name="My API Key", tier="free"):
120
  """Generate a new API key"""
121
- # Create secure key
122
- key_id = f"key_{secrets.token_hex(8)}"
123
- api_key = f"stanley_{secrets.token_urlsafe(32)}"
124
-
125
- conn = sqlite3.connect('/tmp/api_keys.db')
126
- c = conn.cursor()
127
-
128
  try:
 
 
 
 
 
 
 
129
  c.execute('''INSERT INTO api_keys
130
  (id, name, api_key, created, last_used, requests, credits, is_active, user_id, tier)
131
  VALUES (?, ?, ?, CURRENT_TIMESTAMP, NULL, 0, 1000, 1, ?, ?)''',
132
  (key_id, name, api_key, user_id, tier))
 
133
  conn.commit()
134
 
135
  # Get the created key
@@ -148,8 +209,8 @@ def generate_api_key(user_id="anonymous", name="My API Key", tier="free"):
148
  'tier': key_data[9]
149
  }
150
 
151
- except sqlite3.IntegrityError:
152
- conn.close()
153
  return None
154
 
155
  # ============================================================================
@@ -161,11 +222,11 @@ def require_api_key(credits_cost=1):
161
  def decorator(f):
162
  @wraps(f)
163
  def decorated_function(*args, **kwargs):
164
- # Skip authentication for OPTIONS requests (CORS preflight)
165
  if request.method == 'OPTIONS':
166
  return f(*args, **kwargs)
167
 
168
- # Check for API key
169
  key_data = authenticate_api_key()
170
 
171
  if not key_data:
@@ -201,152 +262,198 @@ def require_api_key(credits_cost=1):
201
  return decorator
202
 
203
  # ============================================================================
204
- # MODEL LOADING
205
- # ============================================================================
206
-
207
- model = None
208
- tokenizer = None
209
- model_loaded = False
210
-
211
- # Simplified System Prompt for Hugging Face
212
- STANLEY_AI_SYSTEM = """You are STANLEY AI - an advanced large language model created by Stanley Samwel Owino. You provide comprehensive, helpful responses with occasional Kiswahili phrases when culturally relevant."""
213
-
214
- def load_model():
215
- global model, tokenizer, model_loaded
216
- if model_loaded:
217
- return
218
-
219
- logger.info("πŸš€ Loading STANLEY AI Model...")
220
- model_name = "Qwen/Qwen2.5-0.5B-Instruct" # Smaller model for Hugging Face
221
-
222
- try:
223
- tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
224
- if tokenizer.pad_token is None:
225
- tokenizer.pad_token = tokenizer.eos_token
226
-
227
- model = AutoModelForCausalLM.from_pretrained(
228
- model_name,
229
- torch_dtype=torch.float16,
230
- device_map="auto",
231
- trust_remote_code=True,
232
- low_cpu_mem_usage=True
233
- )
234
-
235
- model_loaded = True
236
- logger.info("βœ… STANLEY AI Model loaded successfully!")
237
-
238
- except Exception as e:
239
- logger.error(f"❌ Error loading model: {e}")
240
- model_loaded = False
241
-
242
- load_model()
243
-
244
- # ============================================================================
245
- # RESPONSE GENERATION
246
  # ============================================================================
247
 
248
- response_cache = {}
249
- CACHE_SIZE = 100
250
-
251
- def generate_response(user_message):
252
- """Generate response using the model"""
253
  if not model_loaded:
254
  return "Model is loading. Please try again in a moment."
255
 
256
  try:
 
257
  messages = [
258
  {"role": "system", "content": STANLEY_AI_SYSTEM},
259
  {"role": "user", "content": user_message}
260
  ]
261
 
262
- text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
263
- inputs = tokenizer(text, return_tensors="pt").to(model.device)
 
 
 
 
 
 
264
 
 
 
 
 
265
  with torch.no_grad():
266
  outputs = model.generate(
267
  **inputs,
268
- max_new_tokens=512,
269
  temperature=0.7,
270
  do_sample=True,
271
  top_p=0.9,
272
- pad_token_id=tokenizer.eos_token_id
 
273
  )
274
 
275
- response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
 
 
 
 
 
276
  return response.strip()
277
 
278
  except Exception as e:
279
  logger.error(f"Generation error: {e}")
280
- return f"Error generating response: {str(e)}"
281
 
282
  # ============================================================================
283
- # IMAGE GENERATION (SIMPLIFIED FOR HUGGING FACE)
284
  # ============================================================================
285
 
286
  def generate_fallback_image(prompt, width=512, height=512):
287
  """Generate simple images using PIL for Hugging Face"""
288
  try:
289
- # Create base image
290
- img = Image.new('RGB', (width, height), color=(random.randint(50, 200),
291
- random.randint(50, 200),
292
- random.randint(50, 200)))
293
  draw = ImageDraw.Draw(img)
294
 
295
- # Add decorative elements based on prompt
 
 
 
 
 
 
 
296
  if any(word in prompt.lower() for word in ['sun', 'light', 'bright']):
297
- draw.ellipse([width//4, height//4, 3*width//4, 3*height//4],
298
- fill=(255, 255, 0))
299
- elif any(word in prompt.lower() for word in ['moon', 'night', 'dark']):
300
- draw.ellipse([width//3, height//3, 2*width//3, 2*height//3],
301
- fill=(200, 200, 200))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
- # Add text label
304
  try:
305
- # Try to use a system font
306
- font = ImageFont.load_default()
307
- text = f"AI: {prompt[:30]}..." if len(prompt) > 30 else f"AI: {prompt}"
 
 
 
 
308
 
309
- # Draw text background
310
- text_bbox = draw.textbbox((0, 0), text, font=font)
 
311
  text_width = text_bbox[2] - text_bbox[0]
312
- text_height = text_bbox[3] - text_bbox[1]
313
 
314
  x = (width - text_width) // 2
315
- y = height - text_height - 20
316
 
317
- draw.rectangle([x-5, y-5, x+text_width+5, y+text_height+5],
318
- fill=(0, 0, 0, 128))
319
- draw.text((x, y), text, fill=(255, 255, 255), font=font)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  except:
321
- pass
 
 
 
 
 
 
 
 
 
 
322
 
323
  # Convert to base64
324
  buffered = io.BytesIO()
325
- img.save(buffered, format="PNG")
326
- img_str = base64.b64encode(buffered.getvalue()).decode()
 
327
  return f"data:image/png;base64,{img_str}"
328
 
329
  except Exception as e:
330
  logger.error(f"Image generation error: {e}")
331
- return None
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  # ============================================================================
334
- # FLASK ROUTES
335
  # ============================================================================
336
 
337
  @app.route('/')
338
  def home():
339
- return jsonify({
340
- "message": "πŸš€ STANLEY AI API is running!",
341
- "version": "3.0",
342
- "status": "active",
343
- "features": ["Chat API", "Image Generation", "API Key System"],
344
- "documentation": "/api/docs"
345
- })
346
-
347
- # ============================================================================
348
- # PUBLIC ENDPOINTS (No API key required)
349
- # ============================================================================
350
 
351
  @app.route('/api/status')
352
  def api_status():
@@ -354,7 +461,9 @@ def api_status():
354
  return jsonify({
355
  "status": "online",
356
  "model_loaded": model_loaded,
357
- "timestamp": datetime.now().isoformat()
 
 
358
  })
359
 
360
  @app.route('/api/docs')
@@ -362,13 +471,19 @@ def api_docs():
362
  """API documentation"""
363
  return jsonify({
364
  "endpoints": {
365
- "POST /api/chat": "Chat with Stanley AI (requires API key)",
366
- "POST /api/generate-image": "Generate images (requires API key, 5 credits)",
367
- "POST /api/v1/generate-key": "Generate API key",
368
- "GET /api/v1/keys": "List your API keys (requires API key)",
 
369
  "DELETE /api/v1/keys/<key_id>": "Revoke API key (requires API key)",
370
- "GET /api/v1/usage": "Get usage stats (requires API key)",
371
- "GET /api/status": "Check API status (public)"
 
 
 
 
 
372
  }
373
  })
374
 
@@ -382,7 +497,7 @@ def create_api_key():
382
  try:
383
  data = request.get_json() or {}
384
  name = data.get('name', 'My API Key')
385
- user_id = data.get('user_id', 'anonymous')
386
 
387
  key_data = generate_api_key(user_id, name)
388
 
@@ -400,6 +515,7 @@ def create_api_key():
400
  }), 500
401
 
402
  except Exception as e:
 
403
  return jsonify({
404
  "error": f"Error generating key: {str(e)}",
405
  "status": "error"
@@ -408,7 +524,7 @@ def create_api_key():
408
  @app.route('/api/v1/keys', methods=['GET'])
409
  @require_api_key(credits_cost=0)
410
  def list_api_keys():
411
- """List API keys for the authenticated user"""
412
  try:
413
  api_key = request.api_key_data['api_key']
414
 
@@ -416,7 +532,7 @@ def list_api_keys():
416
  c = conn.cursor()
417
 
418
  # Get user ID from current key
419
- c.execute('''SELECT user_id FROM api_keys WHERE api_key = ?''', (api_key,))
420
  user_data = c.fetchone()
421
 
422
  if not user_data:
@@ -451,6 +567,7 @@ def list_api_keys():
451
  })
452
 
453
  except Exception as e:
 
454
  return jsonify({
455
  "error": f"Error listing keys: {str(e)}",
456
  "status": "error"
@@ -461,12 +578,13 @@ def list_api_keys():
461
  def revoke_api_key(key_id):
462
  """Revoke an API key"""
463
  try:
 
 
464
  conn = sqlite3.connect('/tmp/api_keys.db')
465
  c = conn.cursor()
466
 
467
  # Check if key belongs to user
468
- c.execute('''SELECT user_id FROM api_keys WHERE api_key = ?''',
469
- (request.api_key_data['api_key'],))
470
  user_data = c.fetchone()
471
 
472
  if not user_data:
@@ -492,6 +610,7 @@ def revoke_api_key(key_id):
492
  })
493
 
494
  except Exception as e:
 
495
  return jsonify({
496
  "error": f"Error revoking key: {str(e)}",
497
  "status": "error"
@@ -528,7 +647,7 @@ def get_usage():
528
  for row in c.fetchall():
529
  daily_usage.append({
530
  'date': row[0],
531
- 'credits_used': row[1]
532
  })
533
 
534
  conn.close()
@@ -544,13 +663,14 @@ def get_usage():
544
  })
545
 
546
  except Exception as e:
 
547
  return jsonify({
548
  "error": f"Error getting usage: {str(e)}",
549
  "status": "error"
550
  }), 500
551
 
552
  # ============================================================================
553
- # CHAT ENDPOINT
554
  # ============================================================================
555
 
556
  @app.route('/api/chat', methods=['POST'])
@@ -558,20 +678,27 @@ def get_usage():
558
  def chat():
559
  """Chat with Stanley AI"""
560
  try:
 
561
  data = request.get_json()
562
- user_message = data.get('message', '')
563
 
564
  if not user_message:
565
  return jsonify({"error": "Message is required"}), 400
566
 
567
  logger.info(f"Chat request: {user_message[:50]}...")
568
 
 
569
  response = generate_response(user_message)
570
 
 
 
 
571
  return jsonify({
572
  "response": response,
573
  "status": "success",
574
- "credits_remaining": request.api_key_data['credits']
 
 
575
  })
576
 
577
  except Exception as e:
@@ -586,26 +713,36 @@ def chat():
586
  # ============================================================================
587
 
588
  @app.route('/api/generate-image', methods=['POST'])
589
- @require_api_key(credits_cost=5) # Image generation costs 5 credits
590
  def generate_image():
591
  """Generate image from text prompt"""
592
  try:
 
593
  data = request.get_json()
594
- prompt = data.get('prompt', '')
595
 
596
  if not prompt:
597
  return jsonify({"error": "Prompt is required"}), 400
598
 
599
- logger.info(f"Image generation request: {prompt[:50]}...")
 
 
 
 
600
 
601
- image_data = generate_fallback_image(prompt)
 
602
 
603
  if image_data:
 
 
604
  return jsonify({
605
  "image": image_data,
606
  "prompt": prompt,
607
  "status": "success",
608
  "credits_remaining": request.api_key_data['credits'],
 
 
609
  "quality": "basic"
610
  })
611
  else:
@@ -622,7 +759,7 @@ def generate_image():
622
  }), 500
623
 
624
  # ============================================================================
625
- # TEST ENDPOINT (No authentication required for testing)
626
  # ============================================================================
627
 
628
  @app.route('/api/test', methods=['GET'])
@@ -631,32 +768,51 @@ def test_endpoint():
631
  return jsonify({
632
  "message": "Stanley AI API is working!",
633
  "timestamp": datetime.now().isoformat(),
634
- "version": "3.0"
 
635
  })
636
 
637
  @app.route('/api/test/chat', methods=['POST'])
638
  def test_chat():
639
  """Test chat without API key (limited)"""
640
  try:
641
- data = request.get_json()
642
  message = data.get('message', 'Hello')
643
 
644
- # Simple response without model
645
  responses = [
646
  "Hello! I'm Stanley AI, your intelligent assistant.",
647
- "I'm here to help you with your questions.",
648
- "Welcome to the Stanley AI platform!",
649
- "How can I assist you today?"
 
650
  ]
651
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
  return jsonify({
653
- "response": random.choice(responses),
654
  "status": "success",
655
- "note": "This is a test response. For full features, use API key."
656
  })
657
 
658
  except Exception as e:
659
- return jsonify({"error": str(e)}), 500
 
 
 
660
 
661
  # ============================================================================
662
  # ERROR HANDLERS
@@ -670,8 +826,17 @@ def not_found(error):
670
  "code": 404
671
  }), 404
672
 
 
 
 
 
 
 
 
 
673
  @app.errorhandler(500)
674
  def internal_error(error):
 
675
  return jsonify({
676
  "error": "Internal server error",
677
  "status": "error",
@@ -679,7 +844,7 @@ def internal_error(error):
679
  }), 500
680
 
681
  # ============================================================================
682
- # MAIN
683
  # ============================================================================
684
 
685
  if __name__ == '__main__':
@@ -687,9 +852,15 @@ if __name__ == '__main__':
687
  print("πŸš€ STANLEY AI API v3.0")
688
  print("🌍 Hugging Face Space Ready")
689
  print("πŸ”‘ API Key System: Enabled")
690
- print("πŸ’¬ Chat: Requires API key (1 credit per request)")
691
- print("🎨 Image Generation: Requires API key (5 credits per image)")
692
- print("πŸ“Š Database: SQLite (persistent)")
693
  print("=" * 60)
694
 
695
- app.run(debug=True, host='0.0.0.0', port=7860)
 
 
 
 
 
 
 
1
+ # app.py - Stanley AI Backend for Hugging Face
2
  from flask import Flask, request, jsonify, send_file
3
  from flask_cors import CORS
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer
5
  import torch
6
  import time
 
7
  import logging
 
 
8
  import io
9
  import base64
10
  import random
11
+ import sqlite3
12
+ import secrets
13
+ from datetime import datetime
14
  from PIL import Image, ImageDraw, ImageFont
 
15
  import json
 
 
 
 
16
  from functools import wraps
17
+ import os
18
 
19
  # Configure logging
20
  logging.basicConfig(level=logging.INFO)
 
24
  CORS(app)
25
 
26
  # ============================================================================
27
+ # DATABASE SETUP
28
  # ============================================================================
29
 
30
  def init_db():
31
  """Initialize SQLite database for API keys"""
32
+ try:
33
+ conn = sqlite3.connect('/tmp/api_keys.db')
34
+ c = conn.cursor()
35
+
36
+ # Create API keys table
37
+ c.execute('''CREATE TABLE IF NOT EXISTS api_keys
38
+ (id TEXT PRIMARY KEY,
39
+ name TEXT,
40
+ api_key TEXT UNIQUE,
41
+ created TIMESTAMP,
42
+ last_used TIMESTAMP,
43
+ requests INTEGER DEFAULT 0,
44
+ credits INTEGER DEFAULT 1000,
45
+ is_active BOOLEAN DEFAULT 1,
46
+ user_id TEXT,
47
+ tier TEXT DEFAULT 'free')''')
48
+
49
+ # Create invite codes table
50
+ c.execute('''CREATE TABLE IF NOT EXISTS invite_codes
51
+ (code TEXT PRIMARY KEY,
52
+ created TIMESTAMP,
53
+ expires TIMESTAMP,
54
+ used BOOLEAN DEFAULT 0,
55
+ used_by TEXT)''')
56
+
57
+ # Create usage stats table
58
+ c.execute('''CREATE TABLE IF NOT EXISTS usage_stats
59
+ (date TEXT,
60
+ api_key TEXT,
61
+ endpoint TEXT,
62
+ credits_used INTEGER)''')
63
+
64
+ conn.commit()
65
+ conn.close()
66
+ logger.info("βœ… Database initialized successfully")
67
+ except Exception as e:
68
+ logger.error(f"❌ Database initialization error: {e}")
69
+
70
+ init_db()
71
+
72
+ # ============================================================================
73
+ # MODEL LOADING (Optimized for Hugging Face)
74
+ # ============================================================================
75
+
76
+ model = None
77
+ tokenizer = None
78
+ model_loaded = False
79
+
80
+ # System Prompt for Stanley AI
81
+ STANLEY_AI_SYSTEM = """You are STANLEY AI - an advanced artificial intelligence assistant created by Stanley Samwel Owino.
82
+ You are knowledgeable, helpful, and occasionally use Kiswahili phrases when culturally appropriate.
83
+ Provide comprehensive, thoughtful responses that demonstrate deep understanding."""
84
+
85
+ def load_model():
86
+ """Load the AI model with optimizations for Hugging Face"""
87
+ global model, tokenizer, model_loaded
88
 
89
+ if model_loaded:
90
+ return
 
 
 
 
 
91
 
92
+ logger.info("πŸš€ Loading STANLEY AI Model...")
 
 
 
 
 
93
 
94
+ try:
95
+ # Use a smaller model for Hugging Face compatibility
96
+ model_name = "Qwen/Qwen2.5-0.5B-Instruct"
97
+
98
+ tokenizer = AutoTokenizer.from_pretrained(
99
+ model_name,
100
+ trust_remote_code=True
101
+ )
102
+
103
+ if tokenizer.pad_token is None:
104
+ tokenizer.pad_token = tokenizer.eos_token
105
+
106
+ model = AutoModelForCausalLM.from_pretrained(
107
+ model_name,
108
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
109
+ device_map="auto" if torch.cuda.is_available() else None,
110
+ trust_remote_code=True,
111
+ low_cpu_mem_usage=True
112
+ )
113
+
114
+ model_loaded = True
115
+ logger.info("βœ… Model loaded successfully!")
116
+
117
+ except Exception as e:
118
+ logger.error(f"❌ Error loading model: {e}")
119
+ model_loaded = False
120
 
121
+ # Load model on startup
122
+ load_model()
123
 
124
  # ============================================================================
125
  # API KEY MANAGEMENT FUNCTIONS
 
128
  def authenticate_api_key():
129
  """Authenticate API key from request header"""
130
  auth_header = request.headers.get('Authorization')
131
+ if not auth_header or not auth_header.startswith('Bearer '):
132
  return None
133
 
134
+ api_key = auth_header.replace('Bearer ', '').strip()
 
 
 
 
 
 
 
135
 
136
+ try:
137
+ conn = sqlite3.connect('/tmp/api_keys.db')
138
+ c = conn.cursor()
139
+ c.execute('''SELECT * FROM api_keys
140
+ WHERE api_key = ? AND is_active = 1''', (api_key,))
141
+ key_data = c.fetchone()
142
+ conn.close()
143
+
144
+ return key_data
145
+ except Exception as e:
146
+ logger.error(f"Authentication error: {e}")
147
+ return None
148
 
149
  def check_credits(api_key_data):
150
  """Check if API key has sufficient credits"""
 
152
 
153
  def update_usage(api_key, endpoint, credits_used=1):
154
  """Update usage statistics"""
155
+ try:
156
+ conn = sqlite3.connect('/tmp/api_keys.db')
157
+ c = conn.cursor()
158
+
159
+ # Update key usage
160
+ c.execute('''UPDATE api_keys
161
+ SET requests = requests + 1,
162
+ credits = credits - ?,
163
+ last_used = CURRENT_TIMESTAMP
164
+ WHERE api_key = ?''',
165
+ (credits_used, api_key))
166
+
167
+ # Record in stats
168
+ today = datetime.now().strftime('%Y-%m-%d')
169
+ c.execute('''INSERT INTO usage_stats (date, api_key, endpoint, credits_used)
170
+ VALUES (?, ?, ?, ?)''',
171
+ (today, api_key, endpoint, credits_used))
172
+
173
+ conn.commit()
174
+ conn.close()
175
+
176
+ except Exception as e:
177
+ logger.error(f"Usage update error: {e}")
178
 
179
  def generate_api_key(user_id="anonymous", name="My API Key", tier="free"):
180
  """Generate a new API key"""
 
 
 
 
 
 
 
181
  try:
182
+ # Create secure key
183
+ key_id = f"key_{secrets.token_hex(8)}"
184
+ api_key = f"stanley_{secrets.token_urlsafe(32)}"
185
+
186
+ conn = sqlite3.connect('/tmp/api_keys.db')
187
+ c = conn.cursor()
188
+
189
  c.execute('''INSERT INTO api_keys
190
  (id, name, api_key, created, last_used, requests, credits, is_active, user_id, tier)
191
  VALUES (?, ?, ?, CURRENT_TIMESTAMP, NULL, 0, 1000, 1, ?, ?)''',
192
  (key_id, name, api_key, user_id, tier))
193
+
194
  conn.commit()
195
 
196
  # Get the created key
 
209
  'tier': key_data[9]
210
  }
211
 
212
+ except Exception as e:
213
+ logger.error(f"Key generation error: {e}")
214
  return None
215
 
216
  # ============================================================================
 
222
  def decorator(f):
223
  @wraps(f)
224
  def decorated_function(*args, **kwargs):
225
+ # Skip for OPTIONS (CORS preflight)
226
  if request.method == 'OPTIONS':
227
  return f(*args, **kwargs)
228
 
229
+ # Authenticate
230
  key_data = authenticate_api_key()
231
 
232
  if not key_data:
 
262
  return decorator
263
 
264
  # ============================================================================
265
+ # AI RESPONSE GENERATION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  # ============================================================================
267
 
268
+ def generate_response(user_message, max_tokens=512):
269
+ """Generate AI response using the model"""
 
 
 
270
  if not model_loaded:
271
  return "Model is loading. Please try again in a moment."
272
 
273
  try:
274
+ # Prepare messages for chat template
275
  messages = [
276
  {"role": "system", "content": STANLEY_AI_SYSTEM},
277
  {"role": "user", "content": user_message}
278
  ]
279
 
280
+ # Apply chat template
281
+ text = tokenizer.apply_chat_template(
282
+ messages,
283
+ tokenize=False,
284
+ add_generation_prompt=True
285
+ )
286
+
287
+ inputs = tokenizer(text, return_tensors="pt")
288
 
289
+ if torch.cuda.is_available():
290
+ inputs = inputs.to('cuda')
291
+
292
+ # Generate response
293
  with torch.no_grad():
294
  outputs = model.generate(
295
  **inputs,
296
+ max_new_tokens=max_tokens,
297
  temperature=0.7,
298
  do_sample=True,
299
  top_p=0.9,
300
+ pad_token_id=tokenizer.eos_token_id,
301
+ eos_token_id=tokenizer.eos_token_id
302
  )
303
 
304
+ # Decode response
305
+ response = tokenizer.decode(
306
+ outputs[0][inputs['input_ids'].shape[1]:],
307
+ skip_special_tokens=True
308
+ )
309
+
310
  return response.strip()
311
 
312
  except Exception as e:
313
  logger.error(f"Generation error: {e}")
314
+ return f"I encountered an error: {str(e)}"
315
 
316
  # ============================================================================
317
+ # IMAGE GENERATION
318
  # ============================================================================
319
 
320
  def generate_fallback_image(prompt, width=512, height=512):
321
  """Generate simple images using PIL for Hugging Face"""
322
  try:
323
+ # Create base image with gradient background
324
+ img = Image.new('RGB', (width, height), color=(0, 0, 0))
 
 
325
  draw = ImageDraw.Draw(img)
326
 
327
+ # Add gradient effect
328
+ for i in range(height):
329
+ r = int(50 + (i / height) * 100)
330
+ g = int(50 + (i / height) * 150)
331
+ b = int(100 + (i / height) * 100)
332
+ draw.line([(0, i), (width, i)], fill=(r, g, b))
333
+
334
+ # Add decorative elements based on prompt keywords
335
  if any(word in prompt.lower() for word in ['sun', 'light', 'bright']):
336
+ # Draw a sun
337
+ draw.ellipse(
338
+ [width//2 - 50, height//2 - 50, width//2 + 50, height//2 + 50],
339
+ fill=(255, 255, 0),
340
+ outline=(255, 200, 0),
341
+ width=3
342
+ )
343
+
344
+ elif any(word in prompt.lower() for word in ['moon', 'night', 'star']):
345
+ # Draw a moon and stars
346
+ draw.ellipse(
347
+ [width//2 - 40, height//2 - 40, width//2 + 40, height//2 + 40],
348
+ fill=(200, 200, 200)
349
+ )
350
+
351
+ # Draw some stars
352
+ for _ in range(10):
353
+ x = random.randint(50, width - 50)
354
+ y = random.randint(50, height - 200)
355
+ size = random.randint(2, 6)
356
+ draw.rectangle(
357
+ [x, y, x + size, y + size],
358
+ fill=(255, 255, 255)
359
+ )
360
+
361
+ elif any(word in prompt.lower() for word in ['tree', 'nature', 'forest']):
362
+ # Draw a simple tree
363
+ # Trunk
364
+ draw.rectangle(
365
+ [width//2 - 15, height//2, width//2 + 15, height - 50],
366
+ fill=(139, 69, 19)
367
+ )
368
+ # Leaves
369
+ draw.ellipse(
370
+ [width//2 - 60, height//2 - 100, width//2 + 60, height//2 + 20],
371
+ fill=(34, 139, 34)
372
+ )
373
 
374
+ # Add Stanley AI branding
375
  try:
376
+ # Try to load a font
377
+ font_size = min(width // 25, 20)
378
+ try:
379
+ font = ImageFont.truetype("arial.ttf", font_size)
380
+ except:
381
+ # Fallback to default font
382
+ font = ImageFont.load_default()
383
 
384
+ # Add prompt text
385
+ display_text = f"AI Generated: {prompt[:40]}..." if len(prompt) > 40 else f"AI: {prompt}"
386
+ text_bbox = draw.textbbox((0, 0), display_text, font=font)
387
  text_width = text_bbox[2] - text_bbox[0]
 
388
 
389
  x = (width - text_width) // 2
390
+ y = height - 40
391
 
392
+ # Text background
393
+ draw.rectangle(
394
+ [x - 10, y - 10, x + text_width + 10, y + 30],
395
+ fill=(0, 0, 0, 150)
396
+ )
397
+
398
+ # Text
399
+ draw.text(
400
+ (x, y),
401
+ display_text,
402
+ fill=(255, 255, 255),
403
+ font=font
404
+ )
405
+
406
+ except Exception as font_error:
407
+ logger.warning(f"Font error: {font_error}")
408
+
409
+ # Add Stanley AI watermark
410
+ watermark = "Stanley AI"
411
+ wm_font_size = min(width // 40, 16)
412
+ try:
413
+ wm_font = ImageFont.truetype("arial.ttf", wm_font_size)
414
  except:
415
+ wm_font = ImageFont.load_default()
416
+
417
+ wm_bbox = draw.textbbox((0, 0), watermark, font=wm_font)
418
+ wm_width = wm_bbox[2] - wm_bbox[0]
419
+
420
+ draw.text(
421
+ (width - wm_width - 10, 10),
422
+ watermark,
423
+ fill=(255, 255, 255, 180),
424
+ font=wm_font
425
+ )
426
 
427
  # Convert to base64
428
  buffered = io.BytesIO()
429
+ img.save(buffered, format="PNG", optimize=True)
430
+ img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
431
+
432
  return f"data:image/png;base64,{img_str}"
433
 
434
  except Exception as e:
435
  logger.error(f"Image generation error: {e}")
436
+ # Ultimate fallback - solid color with text
437
+ try:
438
+ img = Image.new('RGB', (width, height), color=(random.randint(50, 200), random.randint(50, 200), random.randint(50, 200)))
439
+ draw = ImageDraw.Draw(img)
440
+ draw.text((50, height//2), f"Prompt: {prompt[:30]}", fill=(255, 255, 255))
441
+
442
+ buffered = io.BytesIO()
443
+ img.save(buffered, format="PNG")
444
+ img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
445
+ return f"data:image/png;base64,{img_str}"
446
+ except:
447
+ return None
448
 
449
  # ============================================================================
450
+ # FLASK ROUTES - PUBLIC ENDPOINTS
451
  # ============================================================================
452
 
453
  @app.route('/')
454
  def home():
455
+ """Home page - serves the frontend"""
456
+ return send_file('index.html')
 
 
 
 
 
 
 
 
 
457
 
458
  @app.route('/api/status')
459
  def api_status():
 
461
  return jsonify({
462
  "status": "online",
463
  "model_loaded": model_loaded,
464
+ "timestamp": datetime.now().isoformat(),
465
+ "version": "3.0",
466
+ "message": "πŸš€ Stanley AI API is running!"
467
  })
468
 
469
  @app.route('/api/docs')
 
471
  """API documentation"""
472
  return jsonify({
473
  "endpoints": {
474
+ "GET /api/status": "Check API status (public)",
475
+ "POST /api/v1/generate-key": "Generate API key (public)",
476
+ "POST /api/chat": "Chat with AI (requires API key, 1 credit)",
477
+ "POST /api/generate-image": "Generate image (requires API key, 5 credits)",
478
+ "GET /api/v1/keys": "List API keys (requires API key)",
479
  "DELETE /api/v1/keys/<key_id>": "Revoke API key (requires API key)",
480
+ "GET /api/v1/usage": "Get usage stats (requires API key)"
481
+ },
482
+ "authentication": "Use Bearer token: Authorization: Bearer YOUR_API_KEY",
483
+ "credits": {
484
+ "free_tier": "1000 credits on signup",
485
+ "chat": "1 credit per request",
486
+ "image": "5 credits per image"
487
  }
488
  })
489
 
 
497
  try:
498
  data = request.get_json() or {}
499
  name = data.get('name', 'My API Key')
500
+ user_id = data.get('user_id', f"user_{secrets.token_hex(8)}")
501
 
502
  key_data = generate_api_key(user_id, name)
503
 
 
515
  }), 500
516
 
517
  except Exception as e:
518
+ logger.error(f"Create key error: {e}")
519
  return jsonify({
520
  "error": f"Error generating key: {str(e)}",
521
  "status": "error"
 
524
  @app.route('/api/v1/keys', methods=['GET'])
525
  @require_api_key(credits_cost=0)
526
  def list_api_keys():
527
+ """List API keys for authenticated user"""
528
  try:
529
  api_key = request.api_key_data['api_key']
530
 
 
532
  c = conn.cursor()
533
 
534
  # Get user ID from current key
535
+ c.execute('SELECT user_id FROM api_keys WHERE api_key = ?', (api_key,))
536
  user_data = c.fetchone()
537
 
538
  if not user_data:
 
567
  })
568
 
569
  except Exception as e:
570
+ logger.error(f"List keys error: {e}")
571
  return jsonify({
572
  "error": f"Error listing keys: {str(e)}",
573
  "status": "error"
 
578
  def revoke_api_key(key_id):
579
  """Revoke an API key"""
580
  try:
581
+ api_key = request.api_key_data['api_key']
582
+
583
  conn = sqlite3.connect('/tmp/api_keys.db')
584
  c = conn.cursor()
585
 
586
  # Check if key belongs to user
587
+ c.execute('SELECT user_id FROM api_keys WHERE api_key = ?', (api_key,))
 
588
  user_data = c.fetchone()
589
 
590
  if not user_data:
 
610
  })
611
 
612
  except Exception as e:
613
+ logger.error(f"Revoke key error: {e}")
614
  return jsonify({
615
  "error": f"Error revoking key: {str(e)}",
616
  "status": "error"
 
647
  for row in c.fetchall():
648
  daily_usage.append({
649
  'date': row[0],
650
+ 'credits_used': row[1] or 0
651
  })
652
 
653
  conn.close()
 
663
  })
664
 
665
  except Exception as e:
666
+ logger.error(f"Usage stats error: {e}")
667
  return jsonify({
668
  "error": f"Error getting usage: {str(e)}",
669
  "status": "error"
670
  }), 500
671
 
672
  # ============================================================================
673
+ # AI CHAT ENDPOINT
674
  # ============================================================================
675
 
676
  @app.route('/api/chat', methods=['POST'])
 
678
  def chat():
679
  """Chat with Stanley AI"""
680
  try:
681
+ start_time = time.time()
682
  data = request.get_json()
683
+ user_message = data.get('message', '').strip()
684
 
685
  if not user_message:
686
  return jsonify({"error": "Message is required"}), 400
687
 
688
  logger.info(f"Chat request: {user_message[:50]}...")
689
 
690
+ # Generate response
691
  response = generate_response(user_message)
692
 
693
+ # Calculate response time
694
+ response_time = round(time.time() - start_time, 2)
695
+
696
  return jsonify({
697
  "response": response,
698
  "status": "success",
699
+ "credits_remaining": request.api_key_data['credits'],
700
+ "response_time": response_time,
701
+ "model": "Stanley AI"
702
  })
703
 
704
  except Exception as e:
 
713
  # ============================================================================
714
 
715
  @app.route('/api/generate-image', methods=['POST'])
716
+ @require_api_key(credits_cost=5)
717
  def generate_image():
718
  """Generate image from text prompt"""
719
  try:
720
+ start_time = time.time()
721
  data = request.get_json()
722
+ prompt = data.get('prompt', '').strip()
723
 
724
  if not prompt:
725
  return jsonify({"error": "Prompt is required"}), 400
726
 
727
+ logger.info(f"Image generation: {prompt[:50]}...")
728
+
729
+ # Get optional parameters
730
+ width = min(data.get('width', 512), 1024)
731
+ height = min(data.get('height', 512), 1024)
732
 
733
+ # Generate image
734
+ image_data = generate_fallback_image(prompt, width, height)
735
 
736
  if image_data:
737
+ generation_time = round(time.time() - start_time, 2)
738
+
739
  return jsonify({
740
  "image": image_data,
741
  "prompt": prompt,
742
  "status": "success",
743
  "credits_remaining": request.api_key_data['credits'],
744
+ "generation_time": generation_time,
745
+ "dimensions": f"{width}x{height}",
746
  "quality": "basic"
747
  })
748
  else:
 
759
  }), 500
760
 
761
  # ============================================================================
762
+ # TEST ENDPOINTS (No authentication)
763
  # ============================================================================
764
 
765
  @app.route('/api/test', methods=['GET'])
 
768
  return jsonify({
769
  "message": "Stanley AI API is working!",
770
  "timestamp": datetime.now().isoformat(),
771
+ "version": "3.0",
772
+ "status": "success"
773
  })
774
 
775
  @app.route('/api/test/chat', methods=['POST'])
776
  def test_chat():
777
  """Test chat without API key (limited)"""
778
  try:
779
+ data = request.get_json() or {}
780
  message = data.get('message', 'Hello')
781
 
782
+ # Simple predefined responses for testing
783
  responses = [
784
  "Hello! I'm Stanley AI, your intelligent assistant.",
785
+ "Welcome to the Stanley AI platform! How can I help you today?",
786
+ "I'm here to assist you with any questions you might have.",
787
+ "As Stanley AI, I'm designed to provide helpful and comprehensive responses.",
788
+ "Feel free to ask me anything. I'm here to help!"
789
  ]
790
 
791
+ # Check for specific keywords
792
+ message_lower = message.lower()
793
+
794
+ if 'stanley' in message_lower:
795
+ response = "Yes, I'm Stanley AI! Created by Stanley Samwel Owino to provide advanced AI assistance."
796
+ elif 'api' in message_lower or 'key' in message_lower:
797
+ response = "You can generate an API key in the API portal. Visit /api/v1/generate-key to get started."
798
+ elif 'image' in message_lower:
799
+ response = "I can generate images! Use the /api/generate-image endpoint with your API key."
800
+ elif 'swahili' in message_lower or 'kiswahili' in message_lower:
801
+ response = "Jambo! Yes, I have knowledge of Kiswahili language and culture. Karibu sana!"
802
+ else:
803
+ response = random.choice(responses)
804
+
805
  return jsonify({
806
+ "response": response,
807
  "status": "success",
808
+ "note": "This is a test response. For full features, generate an API key."
809
  })
810
 
811
  except Exception as e:
812
+ return jsonify({
813
+ "error": str(e),
814
+ "status": "error"
815
+ }), 500
816
 
817
  # ============================================================================
818
  # ERROR HANDLERS
 
826
  "code": 404
827
  }), 404
828
 
829
+ @app.errorhandler(405)
830
+ def method_not_allowed(error):
831
+ return jsonify({
832
+ "error": "Method not allowed",
833
+ "status": "error",
834
+ "code": 405
835
+ }), 405
836
+
837
  @app.errorhandler(500)
838
  def internal_error(error):
839
+ logger.error(f"Internal server error: {error}")
840
  return jsonify({
841
  "error": "Internal server error",
842
  "status": "error",
 
844
  }), 500
845
 
846
  # ============================================================================
847
+ # MAIN APPLICATION
848
  # ============================================================================
849
 
850
  if __name__ == '__main__':
 
852
  print("πŸš€ STANLEY AI API v3.0")
853
  print("🌍 Hugging Face Space Ready")
854
  print("πŸ”‘ API Key System: Enabled")
855
+ print("πŸ’¬ Chat Endpoint: /api/chat (1 credit)")
856
+ print("🎨 Image Generation: /api/generate-image (5 credits)")
857
+ print("πŸ“Š Database: SQLite (persistent storage)")
858
  print("=" * 60)
859
 
860
+ # Run the Flask app
861
+ app.run(
862
+ debug=True,
863
+ host='0.0.0.0',
864
+ port=7860,
865
+ threaded=True
866
+ )