umar8902 commited on
Commit
2d39154
·
verified ·
1 Parent(s): 4a8e696

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -42
app.py CHANGED
@@ -9,6 +9,8 @@ import requests
9
  import urllib.parse
10
  from flask import Flask, request, jsonify, Response
11
  from flask_cors import CORS
 
 
12
  from datetime import datetime
13
  from dotenv import load_dotenv
14
  from llama_cpp import Llama
@@ -35,6 +37,14 @@ load_dotenv()
35
  app = Flask(__name__)
36
  CORS(app)
37
 
 
 
 
 
 
 
 
 
38
  # --- Configuration & Model Selection ---
39
  OLLAMA_URL = "http://localhost:11434"
40
  HF_FALLBACK_REPO = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF"
@@ -200,25 +210,28 @@ search_manager = SearchManager()
200
  def detect_tools(message):
201
  message = message.lower().strip()
202
  tools = []
 
 
203
  search_keywords = [
204
- "search", "latest", "news", "find out", "who is", "what is the current",
205
- "research", "browse", "today", "current", "facts", "data", "tell me about",
206
- "what happened", "weather", "stock", "price", "how many", "who won",
207
- "generate video", "create video", "make a video", "video about",
208
- "temperature", "forecast", "update", "recent", "now", "happening",
209
- "look up", "check", "get info", "information about", "details on",
210
- "what's", "whats", "how's", "hows", "where is", "when did", "why did",
211
- "did ", "does ", "is ", "are ", "was ", "were ", "will ", "can ", "could ",
212
- "2024", "2025", "2026", "yesterday", "tomorrow", "this week", "this month"
213
  ]
214
- if any(kw in message for kw in search_keywords):
 
 
 
 
 
215
  tools.append("search")
 
 
216
  question_starters = ["who ", "what ", "when ", "where ", "why ", "how "]
217
  if any(message.startswith(kw) for kw in question_starters):
218
  if "search" not in tools:
 
219
  personal_words = ["you", "your", "we", "our", "i ", "my ", "me "]
220
  if not any(pw in message for pw in personal_words):
221
  tools.append("search")
 
222
  image_keywords = ["image", "draw", "visualize", "photo", "paint", "picture", "generate image"]
223
  if any(kw in message for kw in image_keywords) or ("create" in message and "image" in message) or ("make" in message and "image" in message):
224
  tools.append("image")
@@ -264,8 +277,14 @@ def clean_search_query(query):
264
 
265
  def get_time_response(message):
266
  message = message.lower()
267
- time_keywords = ["time", "date", "today", "now", "clock", "day"]
268
- if any(re.search(rf"\b{word}\b", message) for word in time_keywords):
 
 
 
 
 
 
269
  now = datetime.now()
270
  return f"The current time is **{now.strftime('%H:%M:%S')}**. Today is **{now.strftime('%A, %Y-%m-%d')}**."
271
  return None
@@ -292,6 +311,7 @@ def login():
292
  return jsonify({"user": {"username": data.get("username", "Guest"), "messages_used": 0}}), 200
293
 
294
  @app.route('/api/search', methods=['POST'])
 
295
  def search_api():
296
  try:
297
  data = request.json or {}
@@ -313,6 +333,7 @@ def search_api():
313
  return jsonify({"error": "Search service temporarily unavailable", "success": False}), 500
314
 
315
  @app.route('/api/chat', methods=['POST'])
 
316
  def chat():
317
  data = request.json or {}
318
  message = data.get("message", "").strip()
@@ -352,8 +373,19 @@ def chat():
352
  dynamic_system_prompt += f"\n[Active Mode]: {mode.upper()} - {selected_mode_instruction}"
353
  if is_deep_research:
354
  dynamic_system_prompt += "\n[DEEP RESEARCH ACTIVE]: Use a multi-step reasoning approach. Analyze thoroughly."
 
 
355
  if memory:
356
- dynamic_system_prompt += f"\n[User Memory]: {json.dumps(memory)}"
 
 
 
 
 
 
 
 
 
357
 
358
  full_prompt = f"{message}"
359
  if attachments:
@@ -365,6 +397,9 @@ def chat():
365
  # Step 1: Attempt Colab (Ollama) - Skip if on HF Space to save time
366
  if not IS_HF_SPACE:
367
  try:
 
 
 
368
  logger.info("Attempting Colab (Ollama) streaming...")
369
  messages = [{"role": "system", "content": dynamic_system_prompt}]
370
  for h in history:
@@ -372,10 +407,12 @@ def chat():
372
  ollama_role = "assistant" if role == "ai" else "user"
373
  messages.append({"role": ollama_role, "content": h.get('content', '')})
374
  messages.append({"role": "user", "content": full_prompt})
 
 
375
  response = requests.post(
376
  f"{OLLAMA_URL}/api/chat",
377
  json={"model": "phi3:mini", "messages": messages, "stream": True, "options": {"num_ctx": 1024, "num_predict": 512}},
378
- timeout=5,
379
  stream=True
380
  )
381
  if response.status_code == 200:
@@ -432,40 +469,49 @@ def generate_video_ai():
432
  if not prompt:
433
  return jsonify({"error": "No prompt provided"}), 400
434
 
435
- # List of stable HF Spaces to try in sequence
436
  video_spaces = [
437
  "ali-vilas/text-to-video-ms-1.7b",
438
  "fffiloni/zeroscope-v2-xl",
439
  "damo-vilab/modelscope-text-to-video"
440
  ]
441
 
442
- last_err = None
443
- for space in video_spaces:
444
- try:
445
- logger.info(f"🎬 Attempting Video Gen via {space} for: {prompt}")
446
- client = Client(space)
447
- result = client.predict(prompt, api_name="/predict")
 
 
 
 
 
448
 
449
- if result and os.path.exists(result):
450
- output_filename = f"ai_video_{uuid.uuid4().hex}.mp4"
451
- output_path = os.path.join(OUTPUT_DIR, output_filename)
 
 
 
452
  shutil.copy(result, output_path)
453
- return jsonify({
454
- "status": "success",
455
- "video_url": f"/static/outputs/{output_filename}",
456
- "provider": space,
457
- "prompt": prompt
458
- })
459
- except Exception as e:
460
- logger.warning(f"⚠️ Space {space} failed: {str(e)}")
461
- last_err = str(e)
462
- continue # Try next space
 
 
463
 
464
- # Final Fallback: Pollinations "Motion Image" (Dynamic Image sequence that looks like video)
465
  try:
466
- logger.info(f"🚀 All video spaces failed. Falling back to Pollinations Motion for: {prompt}")
467
  seed = int(time.time())
468
- # We use a high-quality pollinations image as a "Motion Card" if video is down
469
  motion_url = f"https://image.pollinations.ai/prompt/{requests.utils.quote(prompt)}?seed={seed}&nologo=true&width=1024&height=576"
470
  return jsonify({
471
  "status": "success",
@@ -476,9 +522,11 @@ def generate_video_ai():
476
  })
477
  except Exception as e:
478
  logger.error(f"❌ All video methods failed: {e}")
479
- return jsonify({"error": "AI Video service is currently overloaded. Please try again in a moment."}), 503
 
480
 
481
  @app.route('/api/image', methods=['POST'])
 
482
  def generate_image():
483
  data = request.json or {}
484
  prompt = data.get("prompt", "").strip()
@@ -501,18 +549,23 @@ def analyze_file():
501
  file = request.files['file']
502
  filename = file.filename
503
  ext = filename.split('.')[-1].lower()
 
 
 
 
504
  try:
505
  text_content = ""
506
  if ext == 'pdf':
507
- with pdfplumber.open(file) as pdf:
508
  text_content = "\n".join([page.extract_text() for page in pdf.pages if page.extract_text()])
509
  elif ext == 'docx':
510
- doc = docx.Document(file)
511
  text_content = "\n".join([para.text for para in doc.paragraphs])
512
  elif ext == 'txt':
513
- text_content = file.read().decode('utf-8', errors='ignore')
 
514
  elif ext in ['png', 'jpg', 'jpeg', 'webp']:
515
- img = Image.open(file)
516
  text_content = pytesseract.image_to_string(img)
517
  else:
518
  return jsonify({"error": f"Unsupported file type: {ext}"}), 400
 
9
  import urllib.parse
10
  from flask import Flask, request, jsonify, Response
11
  from flask_cors import CORS
12
+ from flask_limiter import Limiter
13
+ from flask_limiter.util import get_remote_address
14
  from datetime import datetime
15
  from dotenv import load_dotenv
16
  from llama_cpp import Llama
 
37
  app = Flask(__name__)
38
  CORS(app)
39
 
40
+ # --- Rate Limiting ---
41
+ limiter = Limiter(
42
+ get_remote_address,
43
+ app=app,
44
+ default_limits=["200 per day", "50 per hour"],
45
+ storage_uri="memory://",
46
+ )
47
+
48
  # --- Configuration & Model Selection ---
49
  OLLAMA_URL = "http://localhost:11434"
50
  HF_FALLBACK_REPO = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF"
 
210
  def detect_tools(message):
211
  message = message.lower().strip()
212
  tools = []
213
+
214
+ # Explicit search intent keywords
215
  search_keywords = [
216
+ "search", "look up", "find information about", "latest news", "current weather"
 
 
 
 
 
 
 
 
217
  ]
218
+
219
+ # Check for years only if other indicators are present
220
+ has_year = any(yr in message for yr in ["2024", "2025", "2026"])
221
+ search_indicators = ["who ", "what ", "when ", "where ", "how ", "news", "weather", "stock", "price"]
222
+
223
+ if any(kw in message for kw in search_keywords) or (has_year and any(si in message for si in search_indicators)):
224
  tools.append("search")
225
+
226
+ # Refined search detection for questions
227
  question_starters = ["who ", "what ", "when ", "where ", "why ", "how "]
228
  if any(message.startswith(kw) for kw in question_starters):
229
  if "search" not in tools:
230
+ # Avoid triggering search for personal questions
231
  personal_words = ["you", "your", "we", "our", "i ", "my ", "me "]
232
  if not any(pw in message for pw in personal_words):
233
  tools.append("search")
234
+
235
  image_keywords = ["image", "draw", "visualize", "photo", "paint", "picture", "generate image"]
236
  if any(kw in message for kw in image_keywords) or ("create" in message and "image" in message) or ("make" in message and "image" in message):
237
  tools.append("image")
 
277
 
278
  def get_time_response(message):
279
  message = message.lower()
280
+ # ONLY exact phrases or specific combinations
281
+ time_phrases = ["what time is it", "current time", "what's the date", "whats the date"]
282
+
283
+ # Require at least 2 time-related words using regex boundaries
284
+ time_words = ["time", "date", "clock", "hour", "minute"]
285
+ word_matches = [word for word in time_words if re.search(rf"\b{word}\b", message)]
286
+
287
+ if any(phrase in message for phrase in time_phrases) or len(word_matches) >= 2:
288
  now = datetime.now()
289
  return f"The current time is **{now.strftime('%H:%M:%S')}**. Today is **{now.strftime('%A, %Y-%m-%d')}**."
290
  return None
 
311
  return jsonify({"user": {"username": data.get("username", "Guest"), "messages_used": 0}}), 200
312
 
313
  @app.route('/api/search', methods=['POST'])
314
+ @limiter.limit("5 per minute")
315
  def search_api():
316
  try:
317
  data = request.json or {}
 
333
  return jsonify({"error": "Search service temporarily unavailable", "success": False}), 500
334
 
335
  @app.route('/api/chat', methods=['POST'])
336
+ @limiter.limit("10 per minute")
337
  def chat():
338
  data = request.json or {}
339
  message = data.get("message", "").strip()
 
373
  dynamic_system_prompt += f"\n[Active Mode]: {mode.upper()} - {selected_mode_instruction}"
374
  if is_deep_research:
375
  dynamic_system_prompt += "\n[DEEP RESEARCH ACTIVE]: Use a multi-step reasoning approach. Analyze thoroughly."
376
+
377
+ # Task 8: Memory injection security
378
  if memory:
379
+ sanitized_memory = {}
380
+ for k, v in memory.items():
381
+ # Disallow sensitive keys
382
+ if k.lower() in ["role", "system", "instruction"]:
383
+ continue
384
+ # Sanitize values: strip [, ], <, > and limit length
385
+ clean_val = str(v).replace("[", "").replace("]", "").replace("<", "").replace(">", "")[:100]
386
+ sanitized_memory[k] = clean_val
387
+ if sanitized_memory:
388
+ dynamic_system_prompt += f"\n[User Memory]: {json.dumps(sanitized_memory)}"
389
 
390
  full_prompt = f"{message}"
391
  if attachments:
 
397
  # Step 1: Attempt Colab (Ollama) - Skip if on HF Space to save time
398
  if not IS_HF_SPACE:
399
  try:
400
+ # Task 6: Connection check before request
401
+ requests.head(OLLAMA_URL, timeout=2)
402
+
403
  logger.info("Attempting Colab (Ollama) streaming...")
404
  messages = [{"role": "system", "content": dynamic_system_prompt}]
405
  for h in history:
 
407
  ollama_role = "assistant" if role == "ai" else "user"
408
  messages.append({"role": ollama_role, "content": h.get('content', '')})
409
  messages.append({"role": "user", "content": full_prompt})
410
+
411
+ # Task 6: Increase timeout to 30 seconds
412
  response = requests.post(
413
  f"{OLLAMA_URL}/api/chat",
414
  json={"model": "phi3:mini", "messages": messages, "stream": True, "options": {"num_ctx": 1024, "num_predict": 512}},
415
+ timeout=30,
416
  stream=True
417
  )
418
  if response.status_code == 200:
 
469
  if not prompt:
470
  return jsonify({"error": "No prompt provided"}), 400
471
 
472
+ # Task 7: List of stable HF Spaces to try in sequence
473
  video_spaces = [
474
  "ali-vilas/text-to-video-ms-1.7b",
475
  "fffiloni/zeroscope-v2-xl",
476
  "damo-vilab/modelscope-text-to-video"
477
  ]
478
 
479
+ # Try the first space, if it fails or blocks connections, fallback to Pollinations
480
+ space = video_spaces[0]
481
+ try:
482
+ logger.info(f"🎬 Attempting Video Gen via {space} for: {prompt}")
483
+ client = Client(space)
484
+ result = client.predict(prompt, api_name="/predict")
485
+
486
+ # result is a URL from Gradio Client, not necessarily a local path
487
+ if result:
488
+ output_filename = f"ai_video_{uuid.uuid4().hex}.mp4"
489
+ output_path = os.path.join(OUTPUT_DIR, output_filename)
490
 
491
+ # If it's a URL, download it; if it's a local path (some clients return paths), copy it
492
+ if result.startswith("http"):
493
+ resp = requests.get(result, stream=True)
494
+ with open(output_path, 'wb') as f:
495
+ shutil.copyfileobj(resp.raw, f)
496
+ elif os.path.exists(result):
497
  shutil.copy(result, output_path)
498
+ else:
499
+ raise Exception("Invalid result from Gradio Client")
500
+
501
+ return jsonify({
502
+ "status": "success",
503
+ "video_url": f"/static/outputs/{output_filename}",
504
+ "provider": space,
505
+ "prompt": prompt
506
+ })
507
+ except Exception as e:
508
+ logger.warning(f"⚠️ Space {space} failed: {str(e)}")
509
+ # Task 7: Immediate fallback to Pollinations if Gradio fails
510
 
511
+ # Fallback: Pollinations "Motion Image"
512
  try:
513
+ logger.info(f"🚀 Video space failed or blocked. Falling back to Pollinations Motion for: {prompt}")
514
  seed = int(time.time())
 
515
  motion_url = f"https://image.pollinations.ai/prompt/{requests.utils.quote(prompt)}?seed={seed}&nologo=true&width=1024&height=576"
516
  return jsonify({
517
  "status": "success",
 
522
  })
523
  except Exception as e:
524
  logger.error(f"❌ All video methods failed: {e}")
525
+ # Task 11: Fix typo in error message
526
+ return jsonify({"error": "Our video generation servers are temporarily unavailable. Please try again later."}), 503
527
 
528
  @app.route('/api/image', methods=['POST'])
529
+ @limiter.limit("5 per minute")
530
  def generate_image():
531
  data = request.json or {}
532
  prompt = data.get("prompt", "").strip()
 
549
  file = request.files['file']
550
  filename = file.filename
551
  ext = filename.split('.')[-1].lower()
552
+
553
+ # Task 10: Use independent copy of file stream
554
+ file_bytes = io.BytesIO(file.read())
555
+
556
  try:
557
  text_content = ""
558
  if ext == 'pdf':
559
+ with pdfplumber.open(file_bytes) as pdf:
560
  text_content = "\n".join([page.extract_text() for page in pdf.pages if page.extract_text()])
561
  elif ext == 'docx':
562
+ doc = docx.Document(file_bytes)
563
  text_content = "\n".join([para.text for para in doc.paragraphs])
564
  elif ext == 'txt':
565
+ file_bytes.seek(0)
566
+ text_content = file_bytes.read().decode('utf-8', errors='ignore')
567
  elif ext in ['png', 'jpg', 'jpeg', 'webp']:
568
+ img = Image.open(file_bytes)
569
  text_content = pytesseract.image_to_string(img)
570
  else:
571
  return jsonify({"error": f"Unsupported file type: {ext}"}), 400