Nebulixlabs commited on
Commit
1db40c2
·
verified ·
1 Parent(s): 15ce395

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -202
app.py CHANGED
@@ -1,215 +1,181 @@
1
- import gradio as gr
2
  import json
3
- import hashlib
4
  import uuid
5
- import re
6
- from datetime import datetime
7
- from rapidfuzz import fuzz
8
- from transformers import pipeline
9
  import os
 
 
 
 
 
 
10
 
11
- # ==========================================
12
- # 1. DATA LOADING (IN-MEMORY DATABASE)
13
- # ==========================================
14
- DATA_FILE = "indexed_search_db.jsonl"
15
- search_db = []
16
-
17
- print("Loading Database...")
18
- if os.path.exists(DATA_FILE):
19
- with open(DATA_FILE, "r", encoding="utf-8") as f:
20
- for line in f:
21
- if line.strip():
22
- search_db.append(json.loads(line))
23
- print(f"Loaded {len(search_db)} records from database.")
24
- else:
25
- print(f"Warning: {DATA_FILE} not found! Please upload your data file to the space.")
26
-
27
- # ==========================================
28
- # 2. AI MODEL SETUP (Zero-Shot Intent)
29
- # ==========================================
30
- print("Loading AI Model for Smart Decision Making...")
31
- intent_classifier = pipeline(
32
- "zero-shot-classification",
33
- model="typeform/distilbert-base-uncased-mnli"
34
- )
35
- INTENT_LABELS = ["self-harm", "adult", "technology", "news", "entertainment", "sports", "information"]
36
-
37
- # ==========================================
38
- # 3. HELPER FUNCTIONS
39
- # ==========================================
40
- def is_url(text):
41
- # Check agar user ne direct URL daala hai
42
- pattern = re.compile(r'^(https?://|www\.)\S+$', re.IGNORECASE)
43
- return bool(pattern.match(text.strip()))
44
-
45
- def generate_user_id():
46
- return f"user_{uuid.uuid4().hex[:10]}"
47
-
48
- # ==========================================
49
- # 4. CORE BATCH SEARCH ENGINE (AI + FUZZY)
50
- # ==========================================
51
- def batch_search_engine(queries, limits, user_ids):
52
- # Returns 2 lists: one for JSON outputs, one for HTML (Redirections)
53
- final_json_responses = []
54
- final_html_responses = []
55
 
56
- # Check limits
57
- processed_limits = []
58
- for l in limits:
 
 
 
 
 
 
 
 
 
 
 
59
  try:
60
- l_int = int(l)
61
- if l_int <= 0 or l_int > 30:
62
- processed_limits.append(30)
63
- else:
64
- processed_limits.append(l_int)
65
- except:
66
- processed_limits.append(30)
67
-
68
- # Process batch with AI (Skip direct URLs for AI processing to save time)
69
- ai_queries = [q if not is_url(q) else "bypass" for q in queries]
70
- ai_results = intent_classifier(ai_queries, INTENT_LABELS)
71
- if isinstance(ai_results, dict):
72
- ai_results = [ai_results]
73
-
74
- for i in range(len(queries)):
75
- query = str(queries[i]).strip()
76
- limit = processed_limits[i]
77
- user_id = user_ids[i]
78
-
79
- # --- DIRECT URL REDIRECT LOGIC ---
80
- if is_url(query):
81
- url_to_open = query if query.startswith("http") else "http://" + query
82
- # Redirect via HTML meta tag
83
- redirect_html = f'<meta http-equiv="refresh" content="0; url={url_to_open}">'
84
- final_html_responses.append(redirect_html)
85
-
86
- # Add small info in JSON
87
- final_json_responses.append({
88
- "status": "redirecting",
89
- "message": f"Direct URL detected. Redirecting to {url_to_open}",
90
- "url": url_to_open
91
- })
92
- continue
93
-
94
- # --- PRIVACY LOGGING ---
95
- query_hash = hashlib.sha256(query.encode()).hexdigest()[:16]
96
- top_intent = ai_results[i]['labels'][0]
97
-
98
- log_entry = {
99
- "user_id": user_id,
100
- "query_hash": query_hash,
101
- "timestamp": datetime.utcnow().isoformat(),
102
- "ai_intent": top_intent
103
- }
104
- print(f"SECURE LOG: {log_entry}")
105
-
106
- # --- SELF HARM CHECK ---
107
- if top_intent == "self-harm":
108
- final_html_responses.append("<div></div>") # No HTML redirect
109
- final_json_responses.append({
110
- "status": "blocked",
111
- "ai_intent": "self-harm",
112
- "message": "⚠️ Safety Alert: We do not provide search results for self-harm. Please seek help.",
113
- "results": []
114
- })
115
- continue
116
-
117
- # --- AI + FUZZY MATCH LOGIC ---
118
- results_with_scores = []
119
-
120
- for item in search_db:
121
- text_to_search = item.get('search_index', '')
122
-
123
- # 1. Fuzzy Token Set Ratio (Keywords aage-peeche ho toh bhi chalega)
124
- fuzzy_score = fuzz.token_set_ratio(query.lower(), text_to_search.lower())
125
-
126
- # 2. AI Decision Boosting (Konsa dena hai)
127
- boost = 0
128
- domain = item.get('domain', '').lower()
129
-
130
- # AI samajh raha hai ki user ko exact kis domain ka content chahiye
131
- if top_intent == "adult" and domain in ["xhname", "avideos", "redwap", "dlsite"]:
132
- boost = 25
133
- elif top_intent == "technology" and domain in ["github", "scmp", "workspace", "bitdefender"]:
134
- boost = 25
135
- elif top_intent == "news" and domain in ["news", "nymag", "abc"]:
136
- boost = 25
137
- elif top_intent == "sports" and domain in ["polymarket"]:
138
- boost = 25
139
 
140
- final_score = fuzzy_score + boost
141
-
142
- # Minimum 30 score hona chahiye text match ka tabhi result aayega
143
- if final_score > 30:
144
- results_with_scores.append({
145
- "score": final_score,
146
- "data": item
147
- })
148
-
149
- # Highest score (AI decides + Fuzzy match) will be on TOP
150
- results_with_scores.sort(key=lambda x: x["score"], reverse=True)
151
-
152
- # Limit the results (Max 30)
153
- final_data = [res["data"] for res in results_with_scores[:limit]]
 
154
 
155
- final_html_responses.append("<div></div>") # Empty HTML for normal searches
156
-
157
- # Using Python Dictionary directly to avoid UI bug `{`
158
- final_json_responses.append({
159
- "status": "success",
160
- "ai_predicted_intent": top_intent,
161
- "total_found": len(final_data),
162
- "limit_applied": limit,
163
- "results": final_data
164
- })
165
-
166
- # Return lists because batch=True requires list of outputs for each component
167
- return final_json_responses, final_html_responses
168
-
169
- # ==========================================
170
- # 5. GRADIO UI & QUEUE SYSTEM
171
- # ==========================================
172
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
173
- gr.Markdown("# 🚀 Ultimate AI Search Engine")
174
- gr.Markdown("Type your search query or paste a direct **URL**. Powered by AI Intent & Fuzzy Text Search.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- session_user_id = gr.State(generate_user_id)
 
177
 
178
- with gr.Row():
179
- query_input = gr.Textbox(label="Search Box", placeholder="Search anything or paste a URL (e.g. https://google.com)...", scale=4)
180
- limit_input = gr.Number(label="Limit (Max 30)", value=30, precision=0, scale=1)
181
-
182
- search_btn = gr.Button("Search 🔍", variant="primary")
 
 
 
 
 
 
 
183
 
184
- # Fixed UI Issue: Using gr.JSON instead of gr.Code
185
- output_json = gr.JSON(label="Search Results")
186
 
187
- # Hidden HTML component for URL redirection
188
- redirect_html = gr.HTML(label="", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
- # Link Button & Enter Key (Batch Mode enabled)
191
- search_btn.click(
192
- fn=batch_search_engine,
193
- inputs=[query_input, limit_input, session_user_id],
194
- outputs=[output_json, redirect_html],
195
- batch=True,
196
- max_batch_size=10,
197
- api_name="search"
198
- )
199
-
200
- query_input.submit(
201
- fn=batch_search_engine,
202
- inputs=[query_input, limit_input, session_user_id],
203
- outputs=[output_json, redirect_html],
204
- batch=True,
205
- max_batch_size=10
206
- )
207
-
208
- # Queue System: First come first serve with concurrency
209
- demo.queue(
210
- default_concurrency_limit=5,
211
- max_size=200
212
- )
213
-
214
- if __name__ == "__main__":
215
- demo.launch()
 
 
1
  import json
 
2
  import uuid
3
+ import asyncio
 
 
 
4
  import os
5
+ from datetime import datetime, timedelta
6
+ from urllib.parse import urlparse
7
+ from fastapi import FastAPI, Query, BackgroundTasks
8
+ from fastapi.responses import JSONResponse
9
+ from rapidfuzz import process, fuzz
10
+ from cryptography.fernet import Fernet
11
 
12
+ # --- 1. SETUP, ENCRYPTION & DIRECTORY ---
13
+ # Note: Production me Fernet key ko environment variable se lena chahiye.
14
+ SECRET_KEY = Fernet.generate_key()
15
+ cipher_suite = Fernet(SECRET_KEY)
16
+
17
+ # Logs ke liye alag folder banayenge taki clean rahe
18
+ LOG_DIR = "logs"
19
+ os.makedirs(LOG_DIR, exist_ok=True)
20
+
21
+ app = FastAPI(title="Fast Search API")
22
+
23
+ # --- 2. LOAD DATA FROM dataset.jsonl ---
24
+ raw_data = []
25
+ search_corpus = []
26
+
27
+ def load_dataset():
28
+ global raw_data, search_corpus
29
+ try:
30
+ with open("dataset.jsonl", "r", encoding="utf-8") as f:
31
+ for line in f:
32
+ if line.strip():
33
+ item = json.loads(line)
34
+ raw_data.append(item)
35
+
36
+ # Domain, title aur description nikal kar corpus me dalna
37
+ domain = urlparse(item.get("url", "")).netloc
38
+ text_to_search = f"{domain} {item.get('title', '')} {item.get('description', '')}"
39
+ search_corpus.append(text_to_search.lower())
40
+ print(f"✅ Successfully loaded {len(raw_data)} records from dataset.jsonl")
41
+ except FileNotFoundError:
42
+ print("❌ ERROR: dataset.jsonl file not found in the directory!")
43
+
44
+ # Load dataset when app starts
45
+ load_dataset()
46
+
47
+
48
+ # --- 3. DATE-BASED LOGGING & ENCRYPTION ---
49
+ async def log_query_encrypted(req_id: str, query: str):
50
+ encrypted_q = cipher_suite.encrypt(query.encode()).decode()
51
+ now = datetime.now()
52
+
53
+ # Date ke hisaab se file ka naam banega (e.g., search_logs_2026-07-22.jsonl)
54
+ date_str = now.strftime("%Y-%m-%d")
55
+ log_file = os.path.join(LOG_DIR, f"search_logs_{date_str}.jsonl")
56
 
57
+ log_entry = {
58
+ "id": req_id,
59
+ "query": encrypted_q,
60
+ "timestamp": now.isoformat()
61
+ }
62
+
63
+ # File me append karna (Agar file nahi hai toh khud ban jayegi)
64
+ with open(log_file, "a", encoding="utf-8") as f:
65
+ f.write(json.dumps(log_entry) + "\n")
66
+
67
+
68
+ # --- 4. OPTIMIZED 1 MONTH LOG CLEANUP ---
69
+ async def cleanup_old_logs():
70
+ while True:
71
  try:
72
+ if os.path.exists(LOG_DIR):
73
+ thirty_days_ago = datetime.now() - timedelta(days=30)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ # Folder ki saari files check karenge
76
+ for filename in os.listdir(LOG_DIR):
77
+ if filename.startswith("search_logs_") and filename.endswith(".jsonl"):
78
+ # File ke naam se date nikalenge
79
+ date_part = filename.replace("search_logs_", "").replace(".jsonl", "")
80
+ try:
81
+ file_date = datetime.strptime(date_part, "%Y-%m-%d")
82
+ # Agar file 30 din se purani hai, toh puri file delete kar do
83
+ if file_date < thirty_days_ago:
84
+ os.remove(os.path.join(LOG_DIR, filename))
85
+ print(f"🗑️ Deleted 1-month old log file: {filename}")
86
+ except ValueError:
87
+ pass
88
+ except Exception as e:
89
+ print("Cleanup Error:", e)
90
 
91
+ # Har 24 ghante me ek baar check karega
92
+ await asyncio.sleep(24 * 60 * 60)
93
+
94
+ @app.on_event("startup")
95
+ async def startup_event():
96
+ # Background me cleanup task on startup
97
+ asyncio.create_task(cleanup_old_logs())
98
+
99
+
100
+ # --- 5. FAST AI / NLP LOGIC ---
101
+ def check_self_harm(query: str) -> bool:
102
+ harm_keywords = ["suicide", "kill myself", "die", "harm myself", "end my life", "suiside"]
103
+ return any(word in query.lower() for word in harm_keywords)
104
+
105
+ def check_adult_content(query: str) -> bool:
106
+ adult_keywords = ["porn", "sex", "xxx", "nude", "adult"]
107
+ return any(word in query.lower() for word in adult_keywords)
108
+
109
+ def ai_fallback_search(query: str):
110
+ return {
111
+ "ai_fallback_triggered": True,
112
+ "message": f"Humne apne local data me '{query}' dhunda par kuch nahi mila. AI ne sab jagah ghuma aur ye result laya hai.",
113
+ "results": [
114
+ {
115
+ "url": f"https://www.google.com/search?q={query.replace(' ', '+')}",
116
+ "title": f"AI Web Search: {query}",
117
+ "description": "Local DB me result na milne par ye AI generated fallback result hai.",
118
+ "images": [],
119
+ "videos": [],
120
+ "docs": []
121
+ }
122
+ ]
123
+ }
124
+
125
+
126
+ # --- 6. SEARCH API ENDPOINT ---
127
+ @app.get("/search")
128
+ async def search(q: str = Query(..., description="Search query"), background_tasks: BackgroundTasks = BackgroundTasks()):
129
+ req_id = str(uuid.uuid4())
130
 
131
+ # Background Logging (FastAPI wait nahi karega iske liye)
132
+ background_tasks.add_task(log_query_encrypted, req_id, q)
133
 
134
+ # Self-Harm Check
135
+ if check_self_harm(q):
136
+ return JSONResponse(
137
+ status_code=400,
138
+ content={
139
+ "request_id": req_id,
140
+ "error": "Safety Alert",
141
+ "message": "Please do not harm yourself. Help is available. You are valuable."
142
+ }
143
+ )
144
+
145
+ is_adult = check_adult_content(q)
146
 
147
+ final_results = []
 
148
 
149
+ # RapidFuzz search algorithm - searches in domain, title, and description
150
+ if search_corpus:
151
+ results = process.extract(
152
+ q.lower(),
153
+ search_corpus,
154
+ limit=30,
155
+ scorer=fuzz.WRatio
156
+ )
157
+
158
+ for match_str, score, index in results:
159
+ if score > 40:
160
+ result_item = raw_data[index].copy() # Full data return hoga including images
161
+ result_item["match_score"] = round(score, 2)
162
+ final_results.append(result_item)
163
+
164
+ # Fallback to AI if no results found
165
+ if len(final_results) == 0:
166
+ response_data = ai_fallback_search(q)
167
+ else:
168
+ response_data = {
169
+ "total_results": len(final_results),
170
+ "results": final_results
171
+ }
172
+
173
+ response_data["request_id"] = req_id
174
+
175
+ # Adult Content Header & JSON Modifier
176
+ if is_adult:
177
+ response_data["safety_message"] = "you are safe you can do anything"
178
+ headers = {"X-Safety-Message": "you are safe you can do anything"}
179
+ return JSONResponse(content=response_data, headers=headers)
180
 
181
+ return response_data