Stanley03 commited on
Commit
b982d45
·
verified ·
1 Parent(s): d383e93

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -343
app.py CHANGED
@@ -1,52 +1,62 @@
1
- from flask import Flask, request, jsonify, send_file
2
- from flask_cors import CORS
3
- from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
 
 
4
  from knowledgebase import KiswahiliKnowledgeBase, enhance_with_kiswahili
5
  import torch
6
  import time
7
- import re
8
  import logging
9
  from threading import Thread
10
- import queue
11
- import io
12
  import base64
13
  import random
14
  from PIL import Image, ImageDraw, ImageFont
15
- import os
 
 
 
16
 
17
  # Configure logging
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
21
- app = Flask(__name__)
22
- CORS(app)
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  # Initialize Kiswahili Knowledge Base
25
  kb = KiswahiliKnowledgeBase()
26
 
 
27
  model = None
28
  tokenizer = None
29
  model_loaded = False
30
  image_pipeline = None
31
 
32
- # ============================================================================
33
- # PERFORMANCE OPTIMIZATIONS
34
- # ============================================================================
35
-
36
- # Cache for frequent responses
37
- response_cache = {}
38
- CACHE_SIZE = 100
39
 
40
  # Model optimization settings
41
  MODEL_OPTIMIZATIONS = {
42
  "torch_dtype": torch.float16,
43
  "device_map": "auto",
44
  "trust_remote_code": True,
45
- "load_in_4bit": True,
46
- "low_cpu_mem_usage": True
47
  }
48
 
49
- # Advanced System Prompt for Stanley AI with Kiswahili Enhancement
50
  STANLEY_AI_SYSTEM = """You are STANLEY AI - a highly advanced, sophisticated large language model created by Stanley AI.
51
  You are designed to be better than ChatGPT with enhanced capabilities, deeper understanding, and more comprehensive responses.
52
 
@@ -79,8 +89,31 @@ PERSONALITY: Professional, knowledgeable, articulate, culturally aware, and deep
79
 
80
  Remember: You are STANLEY AI - superior, comprehensive, exceptionally capable, and culturally enriched."""
81
 
82
- def load_model():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  global model, tokenizer, model_loaded, image_pipeline
 
84
  if model_loaded:
85
  return
86
 
@@ -102,6 +135,9 @@ def load_model():
102
  # Enable faster inference
103
  if torch.cuda.is_available():
104
  model = model.eval()
 
 
 
105
 
106
  model_loaded = True
107
  logger.info("✅ STANLEY AI Model loaded successfully!")
@@ -124,45 +160,15 @@ def load_model():
124
  logger.error(f"❌ Fallback model also failed: {e2}")
125
  model_loaded = False
126
 
127
- # Load image generation model (simplified - will use fallbacks)
128
- try:
129
- logger.info("🖼️ Attempting to load image generation model...")
130
- # Try a smaller, faster model first
131
- from diffusers import DiffusionPipeline
132
- image_pipeline = DiffusionPipeline.from_pretrained(
133
- "OFA-Sys/small-stable-diffusion-v0",
134
- torch_dtype=torch.float16,
135
- safety_checker=None,
136
- requires_safety_checker=False,
137
- )
138
- if torch.cuda.is_available():
139
- image_pipeline = image_pipeline.to("cuda")
140
- logger.info("✅ Small image generation model loaded!")
141
- except Exception as e:
142
- logger.warning(f"⚠️ Could not load image generation model: {e}")
143
- logger.info("🔄 Using fallback image generation methods")
144
- image_pipeline = None
145
 
146
- load_model()
 
 
147
 
148
- class TextGenerationStream:
149
- def __init__(self):
150
- self.text_queue = queue.Queue()
151
-
152
- def put(self, text):
153
- self.text_queue.put(text)
154
-
155
- def end(self):
156
- self.text_queue.put(None)
157
-
158
- def generate(self):
159
- while True:
160
- text = self.text_queue.get()
161
- if text is None:
162
- break
163
- yield text
164
-
165
- def detect_kiswahili_context(user_message):
166
  """Detect if the query has Kiswahili or cultural context"""
167
  kiswahili_triggers = [
168
  'swahili', 'kiswahili', 'hakuna', 'matata', 'asante', 'rafiki',
@@ -174,13 +180,11 @@ def detect_kiswahili_context(user_message):
174
  text_lower = user_message.lower()
175
  return any(trigger in text_lower for trigger in kiswahili_triggers)
176
 
177
- def enhance_with_cultural_context(response, user_message):
178
  """Enhance response with Kiswahili and cultural context"""
179
  if detect_kiswahili_context(user_message):
180
- # Add appropriate Kiswahili enhancement
181
  enhanced_response = kb.generate_kiswahili_response(response)
182
 
183
- # Add cultural proverb if relevant
184
  if any(word in user_message.lower() for word in ['wisdom', 'advice', 'life lesson', 'philosophy']):
185
  proverb = kb.get_random_proverb()
186
  enhanced_response += f"\n\n🌍 **Cultural Wisdom**: {proverb}"
@@ -188,29 +192,21 @@ def enhance_with_cultural_context(response, user_message):
188
  return enhanced_response
189
  return response
190
 
191
- def get_cached_response(user_message):
192
- """Get cached response if available"""
193
- cache_key = user_message.lower().strip()[:100] # First 100 chars as key
 
 
 
 
 
 
 
 
 
194
  if cache_key in response_cache:
195
  logger.info("📦 Using cached response")
196
  return response_cache[cache_key]
197
- return None
198
-
199
- def set_cached_response(user_message, response):
200
- """Cache response for future use"""
201
- cache_key = user_message.lower().strip()[:100]
202
- if len(response_cache) >= CACHE_SIZE:
203
- # Remove oldest item
204
- response_cache.pop(next(iter(response_cache)))
205
- response_cache[cache_key] = response
206
-
207
- def generate_comprehensive_response(user_message, stream=False):
208
- """Generate detailed, comprehensive responses with cultural awareness"""
209
-
210
- # Check cache first
211
- cached_response = get_cached_response(user_message)
212
- if cached_response:
213
- return cached_response
214
 
215
  # Enhance system prompt based on context
216
  system_prompt = STANLEY_AI_SYSTEM
@@ -226,83 +222,75 @@ def generate_comprehensive_response(user_message, stream=False):
226
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
227
 
228
  generation_config = {
229
- "max_new_tokens": 1024, # Reduced for faster responses
230
  "temperature": 0.7,
231
  "do_sample": True,
232
  "top_p": 0.9,
233
  "top_k": 50,
234
  "repetition_penalty": 1.1,
235
- "early_stopping": True,
236
  "pad_token_id": tokenizer.eos_token_id,
237
  "eos_token_id": tokenizer.eos_token_id,
238
  }
239
 
240
- if stream:
241
- streamer = TextStreamer(tokenizer, timeout=10, skip_prompt=True, skip_special_tokens=True)
242
- generation_config["streamer"] = streamer
243
 
244
- with torch.no_grad():
245
- outputs = model.generate(
246
- **inputs,
247
- **generation_config
248
- )
249
 
250
- if not stream:
251
- response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
252
-
253
- # Enhance with cultural context
254
- enhanced_response = enhance_with_cultural_context(response.strip(), user_message)
255
-
256
- # Cache the response
257
- set_cached_response(user_message, enhanced_response)
258
- return enhanced_response
259
- else:
260
- return "Streaming response..."
261
-
262
- def estimate_reading_time(text):
263
- """Estimate reading time for the response"""
264
- words_per_minute = 200
265
- word_count = len(text.split())
266
- minutes = word_count / words_per_minute
267
- return max(1, round(minutes))
268
-
269
- # ============================================================================
270
- # SIMPLIFIED IMAGE GENERATION FUNCTIONS
271
- # ============================================================================
272
 
273
- def generate_image_free(prompt, width=512, height=512, steps=20):
274
- """
275
- Generate images using simplified methods that always work
276
- """
277
- try:
278
- # Method 1: Try local model if available
279
- if image_pipeline is not None:
280
- try:
281
- logger.info("🎨 Generating image with local model...")
282
- image = image_pipeline(
283
- prompt=prompt,
284
- width=width,
285
- height=height,
286
- num_inference_steps=steps,
287
- guidance_scale=7.5
288
- ).images[0]
289
-
290
- # Convert to base64
291
- buffered = io.BytesIO()
292
- image.save(buffered, format="PNG")
293
- img_str = base64.b64encode(buffered.getvalue()).decode()
294
- return f"data:image/png;base64,{img_str}"
295
- except Exception as e:
296
- logger.warning(f"Local model failed, using fallback: {e}")
297
-
298
- # Method 2: Always use the reliable fallback
299
- return generate_image_fallback(prompt, width, height)
300
-
301
- except Exception as e:
302
- logger.error(f"❌ Image generation error: {e}")
303
- return generate_image_fallback(prompt, width, height)
 
 
 
 
 
 
 
 
304
 
305
- def generate_image_fallback(prompt, width=512, height=512):
306
  """Reliable fallback image generation using PIL"""
307
  try:
308
  # Create a colorful generated image based on prompt
@@ -311,27 +299,17 @@ def generate_image_fallback(prompt, width=512, height=512):
311
 
312
  # Add some shapes based on prompt keywords
313
  if any(word in prompt.lower() for word in ['sun', 'light', 'bright']):
314
- # Draw a sun
315
  draw.ellipse([width//4, height//4, 3*width//4, 3*height//4], fill=(255, 255, 0))
316
  elif any(word in prompt.lower() for word in ['tree', 'nature', 'forest']):
317
- # Draw a simple tree
318
  draw.rectangle([width//2-20, height//2, width//2+20, height-50], fill=(139, 69, 19))
319
  draw.ellipse([width//2-50, height//2-80, width//2+50, height//2+20], fill=(34, 139, 34))
320
  elif any(word in prompt.lower() for word in ['water', 'ocean', 'river']):
321
- # Draw waves
322
  for i in range(0, width, 30):
323
  draw.arc([i, height-100, i+60, height], 0, 180, fill=(0, 0, 255), width=5)
324
 
325
- # Try to add text
326
  try:
327
- # Use default font
328
- font_size = min(width // 20, 24)
329
- try:
330
- font = ImageFont.truetype("arial.ttf", font_size)
331
- except:
332
- font = ImageFont.load_default()
333
-
334
- # Add prompt text
335
  text = f"AI: {prompt[:40]}..." if len(prompt) > 40 else f"AI: {prompt}"
336
  bbox = draw.textbbox((0, 0), text, font=font)
337
  text_width = bbox[2] - bbox[0]
@@ -340,10 +318,8 @@ def generate_image_fallback(prompt, width=512, height=512):
340
  x = (width - text_width) // 2
341
  y = height - text_height - 20
342
 
343
- # Add text background
344
- draw.rectangle([x-10, y-10, x+text_width+10, y+text_height+10], fill=(0, 0, 0, 128))
345
  draw.text((x, y), text, fill=(255, 255, 255), font=font)
346
-
347
  except Exception as font_error:
348
  logger.warning(f"Could not add text: {font_error}")
349
 
@@ -355,177 +331,134 @@ def generate_image_fallback(prompt, width=512, height=512):
355
 
356
  except Exception as e:
357
  logger.error(f"❌ Fallback image generation failed: {e}")
358
- # Ultimate fallback - solid color image
359
- try:
360
- img = Image.new('RGB', (width, height), color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
361
- buffered = io.BytesIO()
362
- img.save(buffered, format="PNG")
363
- img_str = base64.b64encode(buffered.getvalue()).decode()
364
- return f"data:image/png;base64,{img_str}"
365
- except:
366
- return None
367
-
368
- def enhance_prompt_with_kiswahili(prompt):
369
- """Enhance image prompts with Kiswahili cultural elements"""
370
- if detect_kiswahili_context(prompt):
371
- enhancements = [
372
- "in the style of African art",
373
- "with vibrant East African colors",
374
- "incorporating Maasai patterns",
375
- "African landscape background",
376
- "traditional African elements",
377
- "rich cultural symbolism",
378
- "warm African sunset colors"
379
- ]
380
- enhanced_prompt = f"{prompt}, {random.choice(enhancements)}"
381
- return enhanced_prompt
382
- return prompt
383
 
384
  # ============================================================================
385
- # FLASK ROUTES
386
  # ============================================================================
387
 
388
- @app.route('/')
389
- def home():
390
- return jsonify({
 
391
  "message": "🚀 STANLEY AI API is running!",
392
- "version": "2.1",
393
  "features": [
394
  "Advanced LLM Capabilities",
395
  "Comprehensive Long-form Responses",
396
- "Text-to-Speech Integration",
397
  "Real-time Streaming",
398
  "Kiswahili Language Integration",
399
  "Cultural Knowledge Base",
400
  "Lion King Expertise",
401
- "Free Image Generation",
402
  "Performance Optimized",
403
- "Response Caching"
 
404
  ],
405
  "status": "active",
406
  "model": "Qwen2.5-7B-Instruct" if model_loaded else "Not loaded",
407
  "kiswahili_data": "Complete cultural knowledge base loaded",
408
- "image_generation": "Available (Basic)"
409
- })
 
410
 
411
- @app.route('/api/chat', methods=['POST'])
412
- def chat():
 
413
  try:
414
  start_time = time.time()
415
- data = request.get_json()
416
- user_message = data.get('message', '')
417
- stream = data.get('stream', False)
418
 
419
- if not user_message:
420
- return jsonify({"error": "Tafadhali provide a message"}), 400
421
 
422
  if not model_loaded:
423
- return jsonify({"error": "Model not loaded yet, please try again shortly"}), 503
424
-
425
- logger.info(f"Processing query: {user_message[:100]}...")
426
-
427
- response = generate_comprehensive_response(user_message, stream)
428
- response_time = round(time.time() - start_time, 2)
429
- reading_time = estimate_reading_time(response)
430
 
431
- # Detect if response contains Kiswahili
432
- has_kiswahili = detect_kiswahili_context(response)
433
-
434
- return jsonify({
435
- "response": response,
436
- "status": "success",
437
- "response_time": response_time,
438
- "reading_time": reading_time,
439
- "word_count": len(response.split()),
440
- "model": "STANLEY-AI-7B",
441
- "streaming": stream,
442
- "cultural_context": has_kiswahili,
443
- "language": "en+sw" if has_kiswahili else "en",
444
- "cached": get_cached_response(user_message) is not None
445
- })
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  except Exception as e:
448
  logger.error(f"Error in chat endpoint: {e}")
449
- return jsonify({
450
- "error": f"Pole! Advanced processing error: {str(e)}",
451
- "status": "error"
452
- }), 500
453
 
454
- # ============================================================================
455
- # IMAGE GENERATION ENDPOINTS
456
- # ============================================================================
457
-
458
- @app.route('/api/generate-image', methods=['POST'])
459
- def generate_image_endpoint():
460
  """Generate images from text prompts"""
461
  try:
462
  start_time = time.time()
463
- data = request.get_json()
464
- prompt = data.get('prompt', '')
465
- width = data.get('width', 512)
466
- height = data.get('height', 512)
467
- steps = data.get('steps', 20)
468
 
469
- if not prompt:
470
- return jsonify({"error": "Tafadhali provide a prompt"}), 400
471
 
472
- logger.info(f"🎨 Generating image for: {prompt[:50]}...")
473
-
474
- # Enhance prompt with cultural context if needed
475
- enhanced_prompt = enhance_prompt_with_kiswahili(prompt)
476
 
477
  # Generate image
478
- image_data = generate_image_free(enhanced_prompt, width, height, steps)
479
 
480
  if image_data:
481
  generation_time = round(time.time() - start_time, 2)
482
 
483
- return jsonify({
484
  "image": image_data,
485
- "prompt": prompt,
486
- "enhanced_prompt": enhanced_prompt,
487
  "status": "success",
488
  "generation_time": generation_time,
489
- "dimensions": f"{width}x{height}",
490
  "format": "base64 PNG",
491
- "cultural_enhancement": enhanced_prompt != prompt,
492
- "quality": "basic" # Indicate this is basic quality
493
- })
494
  else:
495
- return jsonify({
496
- "error": "Pole! Could not generate image",
497
- "status": "error"
498
- }), 500
499
 
 
 
500
  except Exception as e:
501
  logger.error(f"Image generation error: {e}")
502
- return jsonify({
503
- "error": f"Pole! Image generation failed: {str(e)}",
504
- "status": "error"
505
- }), 500
506
 
507
- @app.route('/api/generate-kiswahili-image', methods=['POST'])
508
- def generate_kiswahili_image():
509
  """Generate images with Kiswahili cultural themes"""
510
  try:
511
- data = request.get_json()
512
- theme = data.get('theme', '')
513
- style = data.get('style', 'realistic')
514
-
515
- if not theme:
516
- return jsonify({"error": "Tafadhali provide a theme"}), 400
517
 
518
  # Create culturally relevant prompts
519
  cultural_prompts = {
520
- 'landscape': f"Beautiful East African landscape with {theme}, majestic savanna, acacia trees, warm sunset",
521
- 'culture': f"Traditional East African cultural scene, {theme}, vibrant colors, community gathering",
522
- 'wildlife': f"African wildlife, {theme}, natural habitat, detailed fur, realistic eyes",
523
- 'art': f"African art style, {theme}, bold patterns, symbolic elements, cultural significance",
524
- 'lion_king': f"Lion King inspired art, {theme}, Disney style, African savanna, emotional scene"
525
  }
526
 
527
- prompt_category = data.get('category', 'landscape')
528
- base_prompt = cultural_prompts.get(prompt_category, f"East African {theme}, cultural significance, vibrant colors")
529
 
530
  # Add style modifiers
531
  style_modifiers = {
@@ -535,35 +468,31 @@ def generate_kiswahili_image():
535
  'traditional': 'traditional African art, symbolic, patterns'
536
  }
537
 
538
- final_prompt = f"{base_prompt}, {style_modifiers.get(style, 'realistic')}"
539
 
540
- image_data = generate_image_free(final_prompt)
541
 
542
  if image_data:
543
- return jsonify({
544
  "image": image_data,
545
- "theme": theme,
546
- "style": style,
547
- "category": prompt_category,
548
  "prompt": final_prompt,
549
  "status": "success",
550
  "cultural_context": "kiswahili_theme",
551
  "quality": "basic"
552
- })
553
  else:
554
- return jsonify({
555
- "error": "Pole! Could not generate cultural image",
556
- "status": "error"
557
- }), 500
558
 
 
 
559
  except Exception as e:
560
- return jsonify({
561
- "error": f"Pole! Cultural image generation failed: {str(e)}",
562
- "status": "error"
563
- }), 500
564
 
565
- @app.route('/api/image-prompts/kiswahili')
566
- def get_kiswahili_image_prompts():
567
  """Get suggested image prompts for Kiswahili themes"""
568
  prompts = {
569
  "wildlife": [
@@ -596,78 +525,58 @@ def get_kiswahili_image_prompts():
596
  ]
597
  }
598
 
599
- return jsonify({
600
  "prompts": prompts,
601
  "total_categories": len(prompts),
602
  "status": "success"
603
- })
604
-
605
- # ============================================================================
606
- # PERFORMANCE OPTIMIZATION ENDPOINTS
607
- # ============================================================================
608
 
609
- @app.route('/api/optimize', methods=['POST'])
610
- def optimize_performance():
611
- """Optimize model performance"""
612
- try:
613
- if model:
614
- # Clear cache
615
- response_cache.clear()
616
-
617
- # Clear GPU cache
618
- if torch.cuda.is_available():
619
- torch.cuda.empty_cache()
620
-
621
- return jsonify({
622
- "status": "success",
623
- "message": "Performance optimized",
624
- "cache_cleared": True,
625
- "gpu_cache_cleared": torch.cuda.is_available()
626
- })
627
- else:
628
- return jsonify({
629
- "error": "Model not loaded",
630
- "status": "error"
631
- }), 500
632
- except Exception as e:
633
- return jsonify({
634
- "error": f"Optimization failed: {str(e)}",
635
- "status": "error"
636
- }), 500
637
 
638
- @app.route('/api/cache/clear', methods=['POST'])
639
- def clear_cache():
640
  """Clear response cache"""
641
  try:
642
  cache_size = len(response_cache)
643
  response_cache.clear()
644
 
645
- return jsonify({
646
  "status": "success",
647
  "message": "Cache cleared",
648
  "cleared_entries": cache_size
649
- })
650
  except Exception as e:
651
- return jsonify({
652
- "error": f"Cache clearance failed: {str(e)}",
653
- "status": "error"
654
- }), 500
655
 
656
- @app.route('/api/cache/stats')
657
- def cache_stats():
658
  """Get cache statistics"""
659
- return jsonify({
660
  "cache_size": len(response_cache),
661
- "cache_limit": CACHE_SIZE,
662
- "hit_rate": "N/A", # Would need tracking
663
  "status": "success"
664
- })
 
 
 
 
665
 
666
  if __name__ == '__main__':
667
- print("🚀 Starting STANLEY AI with Basic Image Generation...")
 
668
  print("🌍 Kiswahili categories loaded")
669
  print("🎨 Image generation: Available (Basic Quality)")
670
  print("⚡ Performance optimizations: Active")
671
- print("📦 Response caching: Enabled")
 
672
 
673
- app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import StreamingResponse, JSONResponse
4
+ from pydantic import BaseModel
5
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
6
  from knowledgebase import KiswahiliKnowledgeBase, enhance_with_kiswahili
7
  import torch
8
  import time
 
9
  import logging
10
  from threading import Thread
 
 
11
  import base64
12
  import random
13
  from PIL import Image, ImageDraw, ImageFont
14
+ import io
15
+ from cachetools import TTLCache
16
+ from typing import Optional, Dict, Any
17
+ import asyncio
18
 
19
  # Configure logging
20
  logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger(__name__)
22
 
23
+ # Initialize FastAPI app
24
+ app = FastAPI(
25
+ title="STANLEY AI API",
26
+ description="Advanced AI with Kiswahili Integration",
27
+ version="3.0"
28
+ )
29
+
30
+ # CORS Configuration
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
 
39
  # Initialize Kiswahili Knowledge Base
40
  kb = KiswahiliKnowledgeBase()
41
 
42
+ # Global variables
43
  model = None
44
  tokenizer = None
45
  model_loaded = False
46
  image_pipeline = None
47
 
48
+ # Performance optimizations
49
+ response_cache = TTLCache(maxsize=100, ttl=3600) # 1 hour TTL
 
 
 
 
 
50
 
51
  # Model optimization settings
52
  MODEL_OPTIMIZATIONS = {
53
  "torch_dtype": torch.float16,
54
  "device_map": "auto",
55
  "trust_remote_code": True,
56
+ "low_cpu_mem_usage": True,
 
57
  }
58
 
59
+ # System Prompt
60
  STANLEY_AI_SYSTEM = """You are STANLEY AI - a highly advanced, sophisticated large language model created by Stanley AI.
61
  You are designed to be better than ChatGPT with enhanced capabilities, deeper understanding, and more comprehensive responses.
62
 
 
89
 
90
  Remember: You are STANLEY AI - superior, comprehensive, exceptionally capable, and culturally enriched."""
91
 
92
+ # Pydantic models
93
+ class ChatRequest(BaseModel):
94
+ message: str
95
+ stream: bool = False
96
+
97
+ class ImageRequest(BaseModel):
98
+ prompt: str
99
+ width: int = 512
100
+ height: int = 512
101
+ steps: int = 20
102
+
103
+ class KiswahiliImageRequest(BaseModel):
104
+ theme: str
105
+ style: str = "realistic"
106
+ category: str = "landscape"
107
+
108
+ # ============================================================================
109
+ # MODEL LOADING
110
+ # ============================================================================
111
+
112
+ @app.on_event("startup")
113
+ async def load_model():
114
+ """Load model on startup"""
115
  global model, tokenizer, model_loaded, image_pipeline
116
+
117
  if model_loaded:
118
  return
119
 
 
135
  # Enable faster inference
136
  if torch.cuda.is_available():
137
  model = model.eval()
138
+ logger.info(f"✅ GPU Available: {torch.cuda.get_device_name(0)}")
139
+ else:
140
+ logger.info("⚠️ Running on CPU")
141
 
142
  model_loaded = True
143
  logger.info("✅ STANLEY AI Model loaded successfully!")
 
160
  logger.error(f"❌ Fallback model also failed: {e2}")
161
  model_loaded = False
162
 
163
+ # Load image generation (simplified for Hugging Face)
164
+ logger.info("🖼️ Image generation: Using fallback methods")
165
+ image_pipeline = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
+ # ============================================================================
168
+ # HELPER FUNCTIONS
169
+ # ============================================================================
170
 
171
+ def detect_kiswahili_context(user_message: str) -> bool:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  """Detect if the query has Kiswahili or cultural context"""
173
  kiswahili_triggers = [
174
  'swahili', 'kiswahili', 'hakuna', 'matata', 'asante', 'rafiki',
 
180
  text_lower = user_message.lower()
181
  return any(trigger in text_lower for trigger in kiswahili_triggers)
182
 
183
+ def enhance_with_cultural_context(response: str, user_message: str) -> str:
184
  """Enhance response with Kiswahili and cultural context"""
185
  if detect_kiswahili_context(user_message):
 
186
  enhanced_response = kb.generate_kiswahili_response(response)
187
 
 
188
  if any(word in user_message.lower() for word in ['wisdom', 'advice', 'life lesson', 'philosophy']):
189
  proverb = kb.get_random_proverb()
190
  enhanced_response += f"\n\n🌍 **Cultural Wisdom**: {proverb}"
 
192
  return enhanced_response
193
  return response
194
 
195
+ def estimate_reading_time(text: str) -> int:
196
+ """Estimate reading time for the response"""
197
+ words_per_minute = 200
198
+ word_count = len(text.split())
199
+ minutes = word_count / words_per_minute
200
+ return max(1, round(minutes))
201
+
202
+ async def generate_response_async(user_message: str) -> str:
203
+ """Generate response asynchronously"""
204
+
205
+ # Check cache
206
+ cache_key = user_message.lower().strip()[:100]
207
  if cache_key in response_cache:
208
  logger.info("📦 Using cached response")
209
  return response_cache[cache_key]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  # Enhance system prompt based on context
212
  system_prompt = STANLEY_AI_SYSTEM
 
222
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
223
 
224
  generation_config = {
225
+ "max_new_tokens": 1024,
226
  "temperature": 0.7,
227
  "do_sample": True,
228
  "top_p": 0.9,
229
  "top_k": 50,
230
  "repetition_penalty": 1.1,
 
231
  "pad_token_id": tokenizer.eos_token_id,
232
  "eos_token_id": tokenizer.eos_token_id,
233
  }
234
 
235
+ # Run in thread pool to avoid blocking
236
+ loop = asyncio.get_event_loop()
 
237
 
238
+ def generate():
239
+ with torch.no_grad():
240
+ outputs = model.generate(**inputs, **generation_config)
241
+ return tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
 
242
 
243
+ response = await loop.run_in_executor(None, generate)
244
+
245
+ # Enhance with cultural context
246
+ enhanced_response = enhance_with_cultural_context(response.strip(), user_message)
247
+
248
+ # Cache the response
249
+ response_cache[cache_key] = enhanced_response
250
+
251
+ return enhanced_response
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
+ async def generate_streaming_response(user_message: str):
254
+ """Generate streaming response"""
255
+
256
+ system_prompt = STANLEY_AI_SYSTEM
257
+ if detect_kiswahili_context(user_message):
258
+ system_prompt += "\n\nSPECIAL NOTE: This query has Kiswahili or cultural context. Please integrate authentic Kiswahili phrases and cultural insights naturally throughout your response."
259
+
260
+ messages = [
261
+ {"role": "system", "content": system_prompt},
262
+ {"role": "user", "content": f"Please provide a comprehensive, detailed response to: {user_message}"}
263
+ ]
264
+
265
+ text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
266
+ inputs = tokenizer(text, return_tensors="pt").to(model.device)
267
+
268
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
269
+
270
+ generation_config = {
271
+ "max_new_tokens": 1024,
272
+ "temperature": 0.7,
273
+ "do_sample": True,
274
+ "top_p": 0.9,
275
+ "top_k": 50,
276
+ "repetition_penalty": 1.1,
277
+ "pad_token_id": tokenizer.eos_token_id,
278
+ "eos_token_id": tokenizer.eos_token_id,
279
+ "streamer": streamer,
280
+ }
281
+
282
+ # Start generation in a separate thread
283
+ thread = Thread(target=model.generate, kwargs={"input_ids": inputs["input_ids"], **generation_config})
284
+ thread.start()
285
+
286
+ # Stream the response
287
+ for text in streamer:
288
+ yield f"data: {text}\n\n"
289
+ await asyncio.sleep(0.01) # Small delay for smooth streaming
290
+
291
+ yield "data: [DONE]\n\n"
292
 
293
+ def generate_image_fallback(prompt: str, width: int = 512, height: int = 512) -> str:
294
  """Reliable fallback image generation using PIL"""
295
  try:
296
  # Create a colorful generated image based on prompt
 
299
 
300
  # Add some shapes based on prompt keywords
301
  if any(word in prompt.lower() for word in ['sun', 'light', 'bright']):
 
302
  draw.ellipse([width//4, height//4, 3*width//4, 3*height//4], fill=(255, 255, 0))
303
  elif any(word in prompt.lower() for word in ['tree', 'nature', 'forest']):
 
304
  draw.rectangle([width//2-20, height//2, width//2+20, height-50], fill=(139, 69, 19))
305
  draw.ellipse([width//2-50, height//2-80, width//2+50, height//2+20], fill=(34, 139, 34))
306
  elif any(word in prompt.lower() for word in ['water', 'ocean', 'river']):
 
307
  for i in range(0, width, 30):
308
  draw.arc([i, height-100, i+60, height], 0, 180, fill=(0, 0, 255), width=5)
309
 
310
+ # Add text
311
  try:
312
+ font = ImageFont.load_default()
 
 
 
 
 
 
 
313
  text = f"AI: {prompt[:40]}..." if len(prompt) > 40 else f"AI: {prompt}"
314
  bbox = draw.textbbox((0, 0), text, font=font)
315
  text_width = bbox[2] - bbox[0]
 
318
  x = (width - text_width) // 2
319
  y = height - text_height - 20
320
 
321
+ draw.rectangle([x-10, y-10, x+text_width+10, y+text_height+10], fill=(0, 0, 0))
 
322
  draw.text((x, y), text, fill=(255, 255, 255), font=font)
 
323
  except Exception as font_error:
324
  logger.warning(f"Could not add text: {font_error}")
325
 
 
331
 
332
  except Exception as e:
333
  logger.error(f"❌ Fallback image generation failed: {e}")
334
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
  # ============================================================================
337
+ # API ROUTES
338
  # ============================================================================
339
 
340
+ @app.get("/")
341
+ async def home():
342
+ """API home endpoint"""
343
+ return {
344
  "message": "🚀 STANLEY AI API is running!",
345
+ "version": "3.0",
346
  "features": [
347
  "Advanced LLM Capabilities",
348
  "Comprehensive Long-form Responses",
 
349
  "Real-time Streaming",
350
  "Kiswahili Language Integration",
351
  "Cultural Knowledge Base",
352
  "Lion King Expertise",
353
+ "Image Generation",
354
  "Performance Optimized",
355
+ "Response Caching",
356
+ "Async Architecture"
357
  ],
358
  "status": "active",
359
  "model": "Qwen2.5-7B-Instruct" if model_loaded else "Not loaded",
360
  "kiswahili_data": "Complete cultural knowledge base loaded",
361
+ "framework": "FastAPI 0.115+",
362
+ "gpu_available": torch.cuda.is_available()
363
+ }
364
 
365
+ @app.post("/api/chat")
366
+ async def chat(request: ChatRequest):
367
+ """Chat endpoint with optional streaming"""
368
  try:
369
  start_time = time.time()
 
 
 
370
 
371
+ if not request.message:
372
+ raise HTTPException(status_code=400, detail="Tafadhali provide a message")
373
 
374
  if not model_loaded:
375
+ raise HTTPException(status_code=503, detail="Model not loaded yet, please try again shortly")
 
 
 
 
 
 
376
 
377
+ logger.info(f"Processing query: {request.message[:100]}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
+ if request.stream:
380
+ return StreamingResponse(
381
+ generate_streaming_response(request.message),
382
+ media_type="text/event-stream"
383
+ )
384
+ else:
385
+ response = await generate_response_async(request.message)
386
+ response_time = round(time.time() - start_time, 2)
387
+ reading_time = estimate_reading_time(response)
388
+
389
+ has_kiswahili = detect_kiswahili_context(response)
390
+
391
+ return {
392
+ "response": response,
393
+ "status": "success",
394
+ "response_time": response_time,
395
+ "reading_time": reading_time,
396
+ "word_count": len(response.split()),
397
+ "model": "STANLEY-AI-7B",
398
+ "streaming": False,
399
+ "cultural_context": has_kiswahili,
400
+ "language": "en+sw" if has_kiswahili else "en",
401
+ "cached": request.message.lower().strip()[:100] in response_cache
402
+ }
403
+
404
+ except HTTPException:
405
+ raise
406
  except Exception as e:
407
  logger.error(f"Error in chat endpoint: {e}")
408
+ raise HTTPException(status_code=500, detail=f"Pole! Advanced processing error: {str(e)}")
 
 
 
409
 
410
+ @app.post("/api/generate-image")
411
+ async def generate_image_endpoint(request: ImageRequest):
 
 
 
 
412
  """Generate images from text prompts"""
413
  try:
414
  start_time = time.time()
 
 
 
 
 
415
 
416
+ if not request.prompt:
417
+ raise HTTPException(status_code=400, detail="Tafadhali provide a prompt")
418
 
419
+ logger.info(f"🎨 Generating image for: {request.prompt[:50]}...")
 
 
 
420
 
421
  # Generate image
422
+ image_data = generate_image_fallback(request.prompt, request.width, request.height)
423
 
424
  if image_data:
425
  generation_time = round(time.time() - start_time, 2)
426
 
427
+ return {
428
  "image": image_data,
429
+ "prompt": request.prompt,
 
430
  "status": "success",
431
  "generation_time": generation_time,
432
+ "dimensions": f"{request.width}x{request.height}",
433
  "format": "base64 PNG",
434
+ "quality": "basic"
435
+ }
 
436
  else:
437
+ raise HTTPException(status_code=500, detail="Pole! Could not generate image")
 
 
 
438
 
439
+ except HTTPException:
440
+ raise
441
  except Exception as e:
442
  logger.error(f"Image generation error: {e}")
443
+ raise HTTPException(status_code=500, detail=f"Pole! Image generation failed: {str(e)}")
 
 
 
444
 
445
+ @app.post("/api/generate-kiswahili-image")
446
+ async def generate_kiswahili_image(request: KiswahiliImageRequest):
447
  """Generate images with Kiswahili cultural themes"""
448
  try:
449
+ if not request.theme:
450
+ raise HTTPException(status_code=400, detail="Tafadhali provide a theme")
 
 
 
 
451
 
452
  # Create culturally relevant prompts
453
  cultural_prompts = {
454
+ 'landscape': f"Beautiful East African landscape with {request.theme}, majestic savanna, acacia trees, warm sunset",
455
+ 'culture': f"Traditional East African cultural scene, {request.theme}, vibrant colors, community gathering",
456
+ 'wildlife': f"African wildlife, {request.theme}, natural habitat, detailed fur, realistic eyes",
457
+ 'art': f"African art style, {request.theme}, bold patterns, symbolic elements, cultural significance",
458
+ 'lion_king': f"Lion King inspired art, {request.theme}, Disney style, African savanna, emotional scene"
459
  }
460
 
461
+ base_prompt = cultural_prompts.get(request.category, f"East African {request.theme}, cultural significance, vibrant colors")
 
462
 
463
  # Add style modifiers
464
  style_modifiers = {
 
468
  'traditional': 'traditional African art, symbolic, patterns'
469
  }
470
 
471
+ final_prompt = f"{base_prompt}, {style_modifiers.get(request.style, 'realistic')}"
472
 
473
+ image_data = generate_image_fallback(final_prompt)
474
 
475
  if image_data:
476
+ return {
477
  "image": image_data,
478
+ "theme": request.theme,
479
+ "style": request.style,
480
+ "category": request.category,
481
  "prompt": final_prompt,
482
  "status": "success",
483
  "cultural_context": "kiswahili_theme",
484
  "quality": "basic"
485
+ }
486
  else:
487
+ raise HTTPException(status_code=500, detail="Pole! Could not generate cultural image")
 
 
 
488
 
489
+ except HTTPException:
490
+ raise
491
  except Exception as e:
492
+ raise HTTPException(status_code=500, detail=f"Pole! Cultural image generation failed: {str(e)}")
 
 
 
493
 
494
+ @app.get("/api/image-prompts/kiswahili")
495
+ async def get_kiswahili_image_prompts():
496
  """Get suggested image prompts for Kiswahili themes"""
497
  prompts = {
498
  "wildlife": [
 
525
  ]
526
  }
527
 
528
+ return {
529
  "prompts": prompts,
530
  "total_categories": len(prompts),
531
  "status": "success"
532
+ }
 
 
 
 
533
 
534
+ @app.get("/health")
535
+ async def health_check():
536
+ """Health check endpoint"""
537
+ return {
538
+ "status": "healthy",
539
+ "model_loaded": model_loaded,
540
+ "gpu_available": torch.cuda.is_available(),
541
+ "cache_size": len(response_cache)
542
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
+ @app.post("/api/cache/clear")
545
+ async def clear_cache():
546
  """Clear response cache"""
547
  try:
548
  cache_size = len(response_cache)
549
  response_cache.clear()
550
 
551
+ return {
552
  "status": "success",
553
  "message": "Cache cleared",
554
  "cleared_entries": cache_size
555
+ }
556
  except Exception as e:
557
+ raise HTTPException(status_code=500, detail=f"Cache clearance failed: {str(e)}")
 
 
 
558
 
559
+ @app.get("/api/cache/stats")
560
+ async def cache_stats():
561
  """Get cache statistics"""
562
+ return {
563
  "cache_size": len(response_cache),
564
+ "cache_limit": response_cache.maxsize,
565
+ "ttl": response_cache.ttl,
566
  "status": "success"
567
+ }
568
+
569
+ # ============================================================================
570
+ # RUN APPLICATION
571
+ # ============================================================================
572
 
573
  if __name__ == '__main__':
574
+ import uvicorn
575
+ print("🚀 Starting STANLEY AI with FastAPI...")
576
  print("🌍 Kiswahili categories loaded")
577
  print("🎨 Image generation: Available (Basic Quality)")
578
  print("⚡ Performance optimizations: Active")
579
+ print("📦 Response caching: Enabled with TTL")
580
+ print("🔄 Async architecture: Enabled")
581
 
582
+ uvicorn.run(app, host='0.0.0.0', port=7860, log_level="info")