Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -10,14 +10,16 @@ import urllib.parse
|
|
| 10 |
from datetime import datetime
|
| 11 |
from contextlib import asynccontextmanager
|
| 12 |
from fastapi import FastAPI, Query, Request, UploadFile, File
|
| 13 |
-
from fastapi.responses import JSONResponse
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from huggingface_hub import hf_hub_download
|
| 16 |
from rank_bm25 import BM25Okapi
|
| 17 |
import torch
|
| 18 |
from PIL import Image
|
| 19 |
import io
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
# --- 1. SETUP & CPU ALLOCATION (40% for AI) ---
|
| 23 |
TOTAL_CORES = multiprocessing.cpu_count()
|
|
@@ -29,12 +31,12 @@ print(f"๐ฅ๏ธ Total Cores: {TOTAL_CORES} | Allocating {AI_CORES} cores strictl
|
|
| 29 |
raw_data = []
|
| 30 |
bm25_index = None
|
| 31 |
|
| 32 |
-
# Queueing System using Semaphore (Text & Vision dono ke liye same queue)
|
| 33 |
MAX_CONCURRENT_AI = max(1, AI_CORES)
|
| 34 |
ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
|
| 35 |
|
| 36 |
ai_generator = None
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
# --- AI CACHING SYSTEM ---
|
| 40 |
AI_RESPONSE_CACHE = {}
|
|
@@ -104,20 +106,20 @@ def load_and_index_dataset():
|
|
| 104 |
print("โ Dataset file not found!")
|
| 105 |
|
| 106 |
def load_ai_models():
|
| 107 |
-
global ai_generator,
|
| 108 |
print("๐ค Loading Lightweight AI Models (Text & Vision)...")
|
| 109 |
try:
|
| 110 |
-
# 1. Text AI
|
| 111 |
ai_generator = pipeline("text-generation", model="Qwen/Qwen2.5-0.5B-Instruct", device=-1, dtype=torch.float32)
|
| 112 |
|
| 113 |
-
# 2. Vision AI (
|
| 114 |
-
# Ye model image ko dekh kar batata hai ki usme kya hai (AI Review)
|
| 115 |
print("๐๏ธ Loading Vision AI...")
|
| 116 |
-
|
|
|
|
| 117 |
|
| 118 |
print("โ
All AI Models Loaded! System ready.")
|
| 119 |
except Exception as e:
|
| 120 |
-
print("โ Model Loading Failed:
|
| 121 |
|
| 122 |
|
| 123 |
# --- 3. CORE SEARCH ALGORITHM ---
|
|
@@ -162,11 +164,9 @@ async def perform_core_search(raw_query: str):
|
|
| 162 |
doc_tag = str(doc.get("tag", "general")).lower()
|
| 163 |
if tag_filter and tag_filter != doc_tag: continue
|
| 164 |
|
| 165 |
-
# Adult Content Strict Filter
|
| 166 |
is_adult = doc_tag in ["adult", "nsfw"] or any(w in str(doc).lower() for w in ADULT_KEYWORDS)
|
| 167 |
if not user_wants_adult and is_adult: continue
|
| 168 |
|
| 169 |
-
# Ranking Boosts
|
| 170 |
final_score = score
|
| 171 |
exact_phrase = query.lower()
|
| 172 |
doc_title = str(doc.get("title", "")).lower()
|
|
@@ -195,20 +195,19 @@ def _generate_text(messages, max_tokens: int):
|
|
| 195 |
return result[0]['generated_text'][-1]['content']
|
| 196 |
|
| 197 |
def _generate_image_review(image: Image):
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
async def run_ai_task(messages, max_tokens: int, query_key: str):
|
| 202 |
if not ai_generator: return "โ ๏ธ AI Model is starting up."
|
| 203 |
-
|
| 204 |
-
if query_key in AI_RESPONSE_CACHE:
|
| 205 |
-
return AI_RESPONSE_CACHE[query_key]
|
| 206 |
|
| 207 |
async with ai_semaphore:
|
| 208 |
response = await asyncio.to_thread(_generate_text, messages, max_tokens)
|
| 209 |
-
|
| 210 |
-
if len(AI_RESPONSE_CACHE) > MAX_CACHE_SIZE:
|
| 211 |
-
AI_RESPONSE_CACHE.clear()
|
| 212 |
AI_RESPONSE_CACHE[query_key] = response
|
| 213 |
return response
|
| 214 |
|
|
@@ -241,56 +240,82 @@ async def rate_limit_middleware(request: Request, call_next):
|
|
| 241 |
|
| 242 |
return await call_next(request)
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
@app.get("/search")
|
| 245 |
async def pure_search(q: str = Query(...)):
|
| 246 |
req_id = str(uuid.uuid4())
|
| 247 |
results, prediction, _ = await perform_core_search(q)
|
| 248 |
return {"request_id": req_id, "query": q, "results_count": len(results), "results": results}
|
| 249 |
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
@app.post("/image-search")
|
| 252 |
async def reverse_image_search(file: UploadFile = File(...)):
|
| 253 |
-
if not
|
| 254 |
return {"error": "Vision AI Model is still loading."}
|
| 255 |
|
| 256 |
req_id = str(uuid.uuid4())
|
| 257 |
|
| 258 |
try:
|
| 259 |
-
# 1. Read & Process Uploaded Image
|
| 260 |
image_bytes = await file.read()
|
| 261 |
img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 262 |
|
| 263 |
-
# 2. Get AI Review (Uses Semaphore queue to manage CPU limit)
|
| 264 |
async with ai_semaphore:
|
| 265 |
ai_review = await asyncio.to_thread(_generate_image_review, img)
|
| 266 |
|
| 267 |
-
# 3. Use AI Review to search dataset for visually relevant content
|
| 268 |
raw_results, _, _ = await perform_core_search(ai_review)
|
| 269 |
-
|
| 270 |
-
# 4. Filter & Format Results (Strictly max 40 results)
|
| 271 |
visual_results = []
|
| 272 |
|
| 273 |
for doc in raw_results:
|
| 274 |
-
# Smart SafeSearch Filter (Pehle bhi hota hai, double safety ke liye yaha bhi)
|
| 275 |
is_adult = str(doc.get("tag", "general")).lower() in ["adult", "nsfw"] or any(w in str(doc).lower() for w in ADULT_KEYWORDS)
|
| 276 |
if is_adult: continue
|
| 277 |
|
| 278 |
-
# Result formatting (Hum images, titles aur related URLs return kar rahe hain)
|
| 279 |
result_item = {
|
| 280 |
"title": doc.get("title", "No Title"),
|
| 281 |
"url": doc.get("url", ""),
|
| 282 |
-
"image_url": doc.get("image_url", doc.get("specific_data", {}).get("image", "")),
|
| 283 |
"description": doc.get("description", "")
|
| 284 |
}
|
| 285 |
visual_results.append(result_item)
|
| 286 |
-
|
| 287 |
-
# STRICT LIMIT: Maximum 40 results
|
| 288 |
-
if len(visual_results) >= 40:
|
| 289 |
-
break
|
| 290 |
|
| 291 |
return {
|
| 292 |
"request_id": req_id,
|
| 293 |
-
"ai_review": f"Image Analysis: {ai_review
|
| 294 |
"message": "Found visually related content and images.",
|
| 295 |
"results_count": len(visual_results),
|
| 296 |
"results": visual_results
|
|
|
|
| 10 |
from datetime import datetime
|
| 11 |
from contextlib import asynccontextmanager
|
| 12 |
from fastapi import FastAPI, Query, Request, UploadFile, File
|
| 13 |
+
from fastapi.responses import JSONResponse, HTMLResponse
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from huggingface_hub import hf_hub_download
|
| 16 |
from rank_bm25 import BM25Okapi
|
| 17 |
import torch
|
| 18 |
from PIL import Image
|
| 19 |
import io
|
| 20 |
+
|
| 21 |
+
# DIRECT IMPORTS TO FIX THE PIPELINE BUG
|
| 22 |
+
from transformers import pipeline, BlipProcessor, BlipForConditionalGeneration
|
| 23 |
|
| 24 |
# --- 1. SETUP & CPU ALLOCATION (40% for AI) ---
|
| 25 |
TOTAL_CORES = multiprocessing.cpu_count()
|
|
|
|
| 31 |
raw_data = []
|
| 32 |
bm25_index = None
|
| 33 |
|
|
|
|
| 34 |
MAX_CONCURRENT_AI = max(1, AI_CORES)
|
| 35 |
ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
|
| 36 |
|
| 37 |
ai_generator = None
|
| 38 |
+
vision_processor = None
|
| 39 |
+
vision_model = None
|
| 40 |
|
| 41 |
# --- AI CACHING SYSTEM ---
|
| 42 |
AI_RESPONSE_CACHE = {}
|
|
|
|
| 106 |
print("โ Dataset file not found!")
|
| 107 |
|
| 108 |
def load_ai_models():
|
| 109 |
+
global ai_generator, vision_processor, vision_model
|
| 110 |
print("๐ค Loading Lightweight AI Models (Text & Vision)...")
|
| 111 |
try:
|
| 112 |
+
# 1. Text AI
|
| 113 |
ai_generator = pipeline("text-generation", model="Qwen/Qwen2.5-0.5B-Instruct", device=-1, dtype=torch.float32)
|
| 114 |
|
| 115 |
+
# 2. Vision AI (Fix: Direct loading to bypass pipeline task errors)
|
|
|
|
| 116 |
print("๐๏ธ Loading Vision AI...")
|
| 117 |
+
vision_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
|
| 118 |
+
vision_model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base")
|
| 119 |
|
| 120 |
print("โ
All AI Models Loaded! System ready.")
|
| 121 |
except Exception as e:
|
| 122 |
+
print(f"โ Model Loading Failed: {e}")
|
| 123 |
|
| 124 |
|
| 125 |
# --- 3. CORE SEARCH ALGORITHM ---
|
|
|
|
| 164 |
doc_tag = str(doc.get("tag", "general")).lower()
|
| 165 |
if tag_filter and tag_filter != doc_tag: continue
|
| 166 |
|
|
|
|
| 167 |
is_adult = doc_tag in ["adult", "nsfw"] or any(w in str(doc).lower() for w in ADULT_KEYWORDS)
|
| 168 |
if not user_wants_adult and is_adult: continue
|
| 169 |
|
|
|
|
| 170 |
final_score = score
|
| 171 |
exact_phrase = query.lower()
|
| 172 |
doc_title = str(doc.get("title", "")).lower()
|
|
|
|
| 195 |
return result[0]['generated_text'][-1]['content']
|
| 196 |
|
| 197 |
def _generate_image_review(image: Image):
|
| 198 |
+
# Direct model generation (Bypasses the buggy pipeline)
|
| 199 |
+
inputs = vision_processor(image, return_tensors="pt")
|
| 200 |
+
with torch.no_grad():
|
| 201 |
+
out = vision_model.generate(**inputs, max_new_tokens=50)
|
| 202 |
+
return vision_processor.decode(out[0], skip_special_tokens=True).capitalize()
|
| 203 |
|
| 204 |
async def run_ai_task(messages, max_tokens: int, query_key: str):
|
| 205 |
if not ai_generator: return "โ ๏ธ AI Model is starting up."
|
| 206 |
+
if query_key in AI_RESPONSE_CACHE: return AI_RESPONSE_CACHE[query_key]
|
|
|
|
|
|
|
| 207 |
|
| 208 |
async with ai_semaphore:
|
| 209 |
response = await asyncio.to_thread(_generate_text, messages, max_tokens)
|
| 210 |
+
if len(AI_RESPONSE_CACHE) > MAX_CACHE_SIZE: AI_RESPONSE_CACHE.clear()
|
|
|
|
|
|
|
| 211 |
AI_RESPONSE_CACHE[query_key] = response
|
| 212 |
return response
|
| 213 |
|
|
|
|
| 240 |
|
| 241 |
return await call_next(request)
|
| 242 |
|
| 243 |
+
# NAYA: Frontend Load Karne Ke Liye
|
| 244 |
+
@app.get("/", response_class=HTMLResponse)
|
| 245 |
+
async def serve_ui():
|
| 246 |
+
if os.path.exists("index.html"):
|
| 247 |
+
with open("index.html", "r", encoding="utf-8") as f:
|
| 248 |
+
return f.read()
|
| 249 |
+
return "<h1>UI Not Found</h1><p>Please save your frontend code as <b>index.html</b> in the same folder as this main.py file.</p>"
|
| 250 |
+
|
| 251 |
@app.get("/search")
|
| 252 |
async def pure_search(q: str = Query(...)):
|
| 253 |
req_id = str(uuid.uuid4())
|
| 254 |
results, prediction, _ = await perform_core_search(q)
|
| 255 |
return {"request_id": req_id, "query": q, "results_count": len(results), "results": results}
|
| 256 |
|
| 257 |
+
@app.get("/aimode")
|
| 258 |
+
async def ai_mode_short(q: str = Query(...)):
|
| 259 |
+
req_id = str(uuid.uuid4())
|
| 260 |
+
results, _, _ = await perform_core_search(q)
|
| 261 |
+
if not results: return {"request_id": req_id, "ai_response": "I couldn't find any results.", "results": []}
|
| 262 |
+
|
| 263 |
+
context = " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:3]])
|
| 264 |
+
messages = [
|
| 265 |
+
{"role": "system", "content": "Provide a brief 2-3 sentence summary based only on the provided context."},
|
| 266 |
+
{"role": "user", "content": f"Query: {q}\nContext: {context}"}
|
| 267 |
+
]
|
| 268 |
+
|
| 269 |
+
ai_reply = await run_ai_task(messages, 150, query_key=f"short_{q}")
|
| 270 |
+
return {"request_id": req_id, "ai_response": ai_reply, "results": results}
|
| 271 |
+
|
| 272 |
+
@app.get("/aimodechat")
|
| 273 |
+
async def ai_mode_chat(q: str = Query(...)):
|
| 274 |
+
req_id = str(uuid.uuid4())
|
| 275 |
+
results, _, _ = await perform_core_search(q)
|
| 276 |
+
|
| 277 |
+
context_parts = [f"Source [{i+1}]: {r.get('title')} - {r.get('description')}" for i, r in enumerate(results[:5])]
|
| 278 |
+
messages = [
|
| 279 |
+
{"role": "system", "content": "You are a Smart AI Search Assistant. Use search sources to answer accurately."},
|
| 280 |
+
{"role": "user", "content": f"User Query: {q}\n\nContext:\n{chr(10).join(context_parts)}"}
|
| 281 |
+
]
|
| 282 |
+
|
| 283 |
+
ai_reply = await run_ai_task(messages, 400, query_key=f"chat_{q}")
|
| 284 |
+
return {"request_id": req_id, "ai_response": ai_reply, "results": results}
|
| 285 |
+
|
| 286 |
@app.post("/image-search")
|
| 287 |
async def reverse_image_search(file: UploadFile = File(...)):
|
| 288 |
+
if not vision_model: # Yaha updated var check lagaya gaya hai
|
| 289 |
return {"error": "Vision AI Model is still loading."}
|
| 290 |
|
| 291 |
req_id = str(uuid.uuid4())
|
| 292 |
|
| 293 |
try:
|
|
|
|
| 294 |
image_bytes = await file.read()
|
| 295 |
img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 296 |
|
|
|
|
| 297 |
async with ai_semaphore:
|
| 298 |
ai_review = await asyncio.to_thread(_generate_image_review, img)
|
| 299 |
|
|
|
|
| 300 |
raw_results, _, _ = await perform_core_search(ai_review)
|
|
|
|
|
|
|
| 301 |
visual_results = []
|
| 302 |
|
| 303 |
for doc in raw_results:
|
|
|
|
| 304 |
is_adult = str(doc.get("tag", "general")).lower() in ["adult", "nsfw"] or any(w in str(doc).lower() for w in ADULT_KEYWORDS)
|
| 305 |
if is_adult: continue
|
| 306 |
|
|
|
|
| 307 |
result_item = {
|
| 308 |
"title": doc.get("title", "No Title"),
|
| 309 |
"url": doc.get("url", ""),
|
| 310 |
+
"image_url": doc.get("image_url", doc.get("specific_data", {}).get("image", "")),
|
| 311 |
"description": doc.get("description", "")
|
| 312 |
}
|
| 313 |
visual_results.append(result_item)
|
| 314 |
+
if len(visual_results) >= 40: break
|
|
|
|
|
|
|
|
|
|
| 315 |
|
| 316 |
return {
|
| 317 |
"request_id": req_id,
|
| 318 |
+
"ai_review": f"Image Analysis: {ai_review}.",
|
| 319 |
"message": "Found visually related content and images.",
|
| 320 |
"results_count": len(visual_results),
|
| 321 |
"results": visual_results
|