Nebulixlabs commited on
Commit
d9b859c
Β·
verified Β·
1 Parent(s): a655c96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -242
app.py CHANGED
@@ -3,19 +3,18 @@ import uuid
3
  import asyncio
4
  import os
5
  import uvicorn
6
- import aiohttp
7
- import io
8
- import zipfile
9
- from bs4 import BeautifulSoup
10
  from contextlib import asynccontextmanager
11
  from datetime import datetime
12
- from urllib.parse import urlparse, urljoin
13
- from fastapi import FastAPI, Query, BackgroundTasks
14
  from fastapi.responses import JSONResponse, FileResponse
15
  from fastapi.middleware.cors import CORSMiddleware
16
  from rapidfuzz import fuzz
17
  from cryptography.fernet import Fernet
18
 
 
 
 
 
19
  # --- 1. SETUP & CONFIGURATION ---
20
  SECRET_KEY = Fernet.generate_key()
21
  cipher_suite = Fernet(SECRET_KEY)
@@ -26,271 +25,206 @@ os.makedirs(LOG_DIR, exist_ok=True)
26
 
27
  raw_data = []
28
  search_corpus = []
29
- unique_domains = set()
30
- visited_urls = set()
31
-
32
- # Live Tracking Variables
33
- scrape_queue = asyncio.Queue(maxsize=50000)
34
- merge_lock = asyncio.Lock()
35
- url_buffer = [] # 1,000 URLs jama karne ke liye
36
- last_scraped_url = "None"
37
- daily_domains_scraped = set()
38
- current_date_str = datetime.now().strftime("%Y-%m-%d")
39
-
40
- # Thresholds
41
- NEXT_THRESHOLD = 30000
42
- DAILY_DOMAIN_LIMIT = 20000
43
 
 
 
 
 
44
 
45
- # --- 2. DATASET MANAGEMENT & BUFFER FLUSH ---
46
- def get_domain(url):
47
- return urlparse(url).netloc.lower()
48
 
49
- def process_item_into_memory(item):
50
- url = item.get("url", "")
51
- if not url or url in visited_urls:
52
- return
53
-
54
- visited_urls.add(url)
55
- domain = get_domain(url)
56
- unique_domains.add(domain)
57
- raw_data.append(item)
58
-
59
- # Ye search ke liye fast dictionary hai
60
- search_corpus.append({
61
- "url": url.lower(),
62
- "domain": domain,
63
- "title": item.get("title", "").lower(),
64
- "description": item.get("description", "").lower()
65
- })
66
-
67
- async def flush_buffer_to_disk():
68
- global url_buffer
69
- if not url_buffer: return
70
-
71
- daily_file = f"date-{current_date_str}.jsonl"
72
- async with merge_lock:
73
- print(f"πŸ’Ύ Saving {len(url_buffer)} URLs to {daily_file}...")
74
- with open(daily_file, "a", encoding="utf-8") as f:
75
- for item in url_buffer:
76
- f.write(json.dumps(item) + "\n")
77
- url_buffer.clear()
78
-
79
- def load_all_data():
80
- global NEXT_THRESHOLD
81
- print("πŸ”„ Loading datasets into Search Memory...")
82
-
83
- # Load dataset.jsonl
84
  if os.path.exists("dataset.jsonl"):
85
  with open("dataset.jsonl", "r", encoding="utf-8") as f:
86
  for line in f:
87
  if line.strip():
88
- try: process_item_into_memory(json.loads(line))
 
 
 
 
 
 
 
89
  except: pass
 
90
 
91
- # Load daily date-*.jsonl
92
- for filename in os.listdir(DATA_DIR):
93
- if filename.startswith("date-") and filename.endswith(".jsonl"):
94
- with open(filename, "r", encoding="utf-8") as f:
95
- for line in f:
96
- if line.strip():
97
- try: process_item_into_memory(json.loads(line))
98
- except: pass
99
-
100
- current_domains = len(unique_domains)
101
- NEXT_THRESHOLD = ((current_domains // 30000) + 1) * 30000
102
- print(f"βœ… Search Engine Ready! URLs: {len(visited_urls)} | Domains: {current_domains}")
103
-
104
-
105
- async def check_and_merge_datasets():
106
- global NEXT_THRESHOLD
107
- if len(unique_domains) >= NEXT_THRESHOLD:
108
- print(f"🌟 Merge Threshold ({NEXT_THRESHOLD}) Reached! Combining datasets...")
109
- await flush_buffer_to_disk() # Pehle buffer khali karo
110
-
111
- async with merge_lock:
112
- temp_file = "dataset.jsonl.tmp"
113
- with open(temp_file, "w", encoding="utf-8") as f:
114
- for item in raw_data:
115
- f.write(json.dumps(item) + "\n")
116
-
117
- os.replace(temp_file, "dataset.jsonl")
118
-
119
- # Delete old daily files
120
- for filename in os.listdir(DATA_DIR):
121
- if filename.startswith("date-") and filename.endswith(".jsonl"):
122
- os.remove(filename)
123
-
124
- NEXT_THRESHOLD += 30000
125
- print("βœ… Merge Completed & Old files deleted!")
126
-
127
-
128
- # --- 3. SMART SPIDER CRAWLER ---
129
- async def fetch_tranco_seeds(session):
130
- print("🌍 Fetching Tranco Dataset for Seeding...")
131
  try:
132
- async with session.get("https://tranco-list.eu/top-1m.csv.zip", timeout=30) as resp:
133
- if resp.status == 200:
134
- with zipfile.ZipFile(io.BytesIO(await resp.read())) as z:
135
- with z.open(z.namelist()[0]) as f:
136
- lines = f.read().decode('utf-8').splitlines()
137
- for line in lines[:5000]:
138
- parts = line.split(",")
139
- if len(parts) >= 2:
140
- try: scrape_queue.put_nowait(f"https://{parts[1].strip()}/")
141
- except asyncio.QueueFull: break
142
  except Exception as e:
143
- print("⚠️ Tranco fetch failed, using fallbacks.")
144
- for url in ["https://wikipedia.org/", "https://github.com/"]:
145
- await scrape_queue.put(url)
146
-
147
- async def extract_page_data(session, url):
148
- headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) SearchBot/1.0"}
149
- try:
150
- async with session.get(url, headers=headers, timeout=8) as response:
151
- if response.status != 200: return None
152
-
153
- html = await response.text()
154
- soup = BeautifulSoup(html, 'html.parser')
155
-
156
- title = soup.title.string.strip() if soup.title and soup.title.string else ""
157
- desc_tag = soup.find("meta", {"name": "description"})
158
- desc = desc_tag["content"].strip() if desc_tag else ""
159
-
160
- # Sirf tabhi save karenge jab kuch matlab ka content ho
161
- if not title and not desc: return None
162
-
163
- fav_tag = soup.find("link", {"rel": lambda x: x and 'icon' in x.lower()})
164
- favicon = urljoin(url, fav_tag["href"]) if fav_tag else ""
165
-
166
- images = [urljoin(url, img['src']) for img in soup.find_all('img', src=True)][:5]
167
-
168
- new_links = []
169
- for a in soup.find_all('a', href=True):
170
- href = urljoin(url, a['href'])
171
- if href.startswith('http') and "tranco" not in href.lower():
172
- new_links.append(href)
173
-
174
- return {
175
- "url": url, "title": title, "description": desc,
176
- "favicon": favicon, "images": images, "videos": [], "docs": [],
177
- "new_links": new_links[:20]
178
- }
179
- except: return None
180
-
181
- async def scraper_worker(session):
182
- global last_scraped_url, current_date_str
183
 
184
- while True:
185
- # Check for Date Rollover (Naya din shuru hua ya nahi)
186
- today = datetime.now().strftime("%Y-%m-%d")
187
- if today != current_date_str:
188
- await flush_buffer_to_disk()
189
- current_date_str = today
190
- daily_domains_scraped.clear() # Reset daily limit
191
-
192
- # Daily Limit Check (20k Unique Domains)
193
- if len(daily_domains_scraped) >= DAILY_DOMAIN_LIMIT:
194
- print("πŸ›‘ Daily 20k limit reached! Sleeping for 1 hour...")
195
- await asyncio.sleep(3600)
196
- continue
197
-
198
- url = await scrape_queue.get()
199
- if url in visited_urls:
200
- scrape_queue.task_done()
201
- continue
202
-
203
- data = await extract_page_data(session, url)
204
- if data:
205
- domain = get_domain(url)
206
- daily_domains_scraped.add(domain)
207
- last_scraped_url = url
208
-
209
- # Put new links in queue
210
- for link in data.pop("new_links"):
211
- if link not in visited_urls:
212
- try: scrape_queue.put_nowait(link)
213
- except: pass
214
-
215
- # Memory me turant update for Search
216
- process_item_into_memory(data)
217
-
218
- # 1,000 URL Buffer System
219
- url_buffer.append(data)
220
- if len(url_buffer) >= 1000:
221
- await flush_buffer_to_disk()
222
- await check_and_merge_datasets()
223
-
224
- scrape_queue.task_done()
225
- await asyncio.sleep(0.1)
226
 
227
- async def start_scraper():
228
- async with aiohttp.ClientSession() as session:
229
- if scrape_queue.empty(): await fetch_tranco_seeds(session)
230
- workers = [asyncio.create_task(scraper_worker(session)) for _ in range(15)]
231
- await asyncio.gather(*workers)
232
 
 
 
 
 
 
 
 
 
 
233
 
234
- # --- 4. FASTAPI ENDPOINTS ---
235
  @asynccontextmanager
236
  async def lifespan(app: FastAPI):
237
- load_all_data()
238
- scraper_task = asyncio.create_task(start_scraper())
 
239
  yield
240
- await flush_buffer_to_disk() # Save everything before shutdown
241
- scraper_task.cancel()
242
 
243
- app = FastAPI(title="Search Engine & Spider API", lifespan=lifespan)
244
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  @app.get("/")
247
  async def serve_ui():
248
- if os.path.exists("index.html"):
249
- return FileResponse("index.html")
250
- return {"message": "API is Running! Upload index.html for UI."}
251
 
252
- # 🌟 NEW: LIVE STATUS TRACKER 🌟
253
- @app.get("/status")
254
- async def get_status():
255
- return {
256
- "status": "Running",
257
- "total_urls_in_database": len(raw_data),
258
- "total_unique_domains": len(unique_domains),
259
- "daily_limit": f"{len(daily_domains_scraped)} / {DAILY_DOMAIN_LIMIT}",
260
- "urls_in_queue_to_scrape": scrape_queue.qsize(),
261
- "urls_in_save_buffer": f"{len(url_buffer)} / 1000",
262
- "last_scraped_url": last_scraped_url
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
 
265
- @app.get("/search")
266
- async def search(q: str = Query(...)):
267
- query_lower = q.lower()
268
- scored_results = []
 
 
 
 
 
 
269
 
270
- async with merge_lock:
271
- for index, data in enumerate(search_corpus):
272
- # Partial match using WRatio for better searching
273
- title_score = fuzz.WRatio(query_lower, data["title"])
274
- desc_score = fuzz.WRatio(query_lower, data["description"])
275
-
276
- total_score = max(title_score, desc_score)
277
-
278
- if total_score > 50: # Standard Search Threshold
279
- scored_results.append((total_score, index))
280
-
281
- scored_results.sort(key=lambda x: x[0], reverse=True)
282
- top_30 = scored_results[:30]
283
 
284
- final_results = []
285
- for score, index in top_30:
286
- item = raw_data[index].copy()
287
- item["match_score"] = round(score, 2)
288
- final_results.append(item)
289
-
290
- if not final_results:
291
- return {"total_results": 0, "results": [], "message": "No results found in DB yet. Crawler is working..."}
292
 
293
- return {"total_results": len(final_results), "results": final_results}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  if __name__ == "__main__":
296
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
3
  import asyncio
4
  import os
5
  import uvicorn
 
 
 
 
6
  from contextlib import asynccontextmanager
7
  from datetime import datetime
8
+ from fastapi import FastAPI, Query, BackgroundTasks, Request
 
9
  from fastapi.responses import JSONResponse, FileResponse
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from rapidfuzz import fuzz
12
  from cryptography.fernet import Fernet
13
 
14
+ # AI Models Imports
15
+ import torch
16
+ from transformers import pipeline
17
+
18
  # --- 1. SETUP & CONFIGURATION ---
19
  SECRET_KEY = Fernet.generate_key()
20
  cipher_suite = Fernet(SECRET_KEY)
 
25
 
26
  raw_data = []
27
  search_corpus = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # AI Concurrency Limit (CPU par hang hone se bachane ke liye max 2 AI generation ek time pe)
30
+ MAX_CONCURRENT_AI = 2
31
+ ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
32
+ ai_generator = None
33
 
 
 
 
34
 
35
+ # --- 2. LOAD DATASET & AI MODEL ---
36
+ def load_dataset():
37
+ print("πŸ”„ Loading Search Dataset...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if os.path.exists("dataset.jsonl"):
39
  with open("dataset.jsonl", "r", encoding="utf-8") as f:
40
  for line in f:
41
  if line.strip():
42
+ try:
43
+ item = json.loads(line)
44
+ raw_data.append(item)
45
+ search_corpus.append({
46
+ "url": item.get("url", "").lower(),
47
+ "title": item.get("title", "").lower(),
48
+ "description": item.get("description", "").lower()
49
+ })
50
  except: pass
51
+ print(f"βœ… Loaded {len(raw_data)} records into memory.")
52
 
53
+ def load_ai_model():
54
+ global ai_generator
55
+ print("πŸ€– Loading 500M Parameter AI Model (Qwen2.5-0.5B)... This may take a minute.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  try:
57
+ # Qwen 0.5B ek chota aur bohot smart model hai jo CPU par achha chalta hai
58
+ model_id = "Qwen/Qwen2.5-0.5B-Instruct"
59
+ ai_generator = pipeline(
60
+ "text-generation",
61
+ model=model_id,
62
+ device=-1, # -1 means CPU
63
+ torch_dtype=torch.float32
64
+ )
65
+ print("βœ… AI Model Loaded Successfully!")
 
66
  except Exception as e:
67
+ print("❌ AI Model Loading Failed:", e)
68
+
69
+
70
+ # --- 3. ASYNC AI INFERENCE HELPERS ---
71
+ def _generate_text(prompt: str, max_tokens: int):
72
+ # Chat template format for Instruct models
73
+ messages = [{"role": "user", "content": prompt}]
74
+ result = ai_generator(
75
+ messages,
76
+ max_new_tokens=max_tokens,
77
+ do_sample=True,
78
+ temperature=0.7,
79
+ top_p=0.9
80
+ )
81
+ # Return extracted AI response
82
+ return result[0]['generated_text'][-1]['content']
83
+
84
+ async def run_ai_task(prompt: str, max_tokens: int = 150):
85
+ if not ai_generator:
86
+ return "AI model is not initialized."
87
+
88
+ # Check if AI is overloaded
89
+ if ai_semaphore.locked():
90
+ return "⚠️ AI Servers are currently at full capacity handling other users. Please try again in a few seconds."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ async with ai_semaphore:
93
+ # Run Heavy AI task in a separate thread so FastAPI doesn't block
94
+ loop = asyncio.get_event_loop()
95
+ response = await loop.run_in_executor(None, _generate_text, prompt, max_tokens)
96
+ return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
 
 
 
 
 
98
 
99
+ # --- 4. LIFESPAN & LOGGING ---
100
+ async def log_query_encrypted(req_id: str, query: str):
101
+ try:
102
+ encrypted_q = cipher_suite.encrypt(query.encode()).decode()
103
+ log_file = os.path.join(LOG_DIR, f"search_logs_{datetime.now().strftime('%Y-%m-%d')}.jsonl")
104
+ log_entry = {"id": req_id, "query": encrypted_q, "timestamp": datetime.now().isoformat()}
105
+ with open(log_file, "a", encoding="utf-8") as f:
106
+ f.write(json.dumps(log_entry) + "\n")
107
+ except: pass
108
 
 
109
  @asynccontextmanager
110
  async def lifespan(app: FastAPI):
111
+ load_dataset()
112
+ # Load AI in background so server starts immediately
113
+ asyncio.get_event_loop().run_in_executor(None, load_ai_model)
114
  yield
 
 
115
 
116
+ app = FastAPI(title="AI Enhanced Search Engine", lifespan=lifespan)
117
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
118
 
119
+
120
+ # --- 5. SEARCH LOGIC & SMART PREDICTION ---
121
+ def perform_core_search(query: str):
122
+ query_lower = query.lower()
123
+ scored_results = []
124
+
125
+ for index, data in enumerate(search_corpus):
126
+ title_score = fuzz.WRatio(query_lower, data["title"])
127
+ desc_score = fuzz.WRatio(query_lower, data["description"])
128
+ total_score = max(title_score, desc_score)
129
+
130
+ if total_score > 40:
131
+ scored_results.append((total_score, index))
132
+
133
+ scored_results.sort(key=lambda x: x[0], reverse=True)
134
+
135
+ # 🌟 SMART FILTER PREDICTION 🌟
136
+ best_score = scored_results[0][0] if scored_results else 0
137
+ if best_score > 85:
138
+ prediction = "High confidence: We found exact matches for your query."
139
+ elif best_score > 55:
140
+ prediction = "Medium confidence: We found related content, but it might not be exact."
141
+ else:
142
+ prediction = "Low confidence: Results are poor. Try changing your keywords."
143
+
144
+ top_30 = scored_results[:30]
145
+ final_results = []
146
+ for score, index in top_30:
147
+ item = raw_data[index].copy()
148
+ item["match_score"] = round(score, 2)
149
+ final_results.append(item)
150
+
151
+ return final_results, prediction
152
+
153
+
154
+ # --- 6. ENDPOINTS ---
155
+
156
  @app.get("/")
157
  async def serve_ui():
158
+ if os.path.exists("index.html"): return FileResponse("index.html")
159
+ return {"message": "API Running!"}
 
160
 
161
+ @app.get("/search")
162
+ async def standard_search(q: str = Query(...), background_tasks: BackgroundTasks = BackgroundTasks()):
163
+ req_id = str(uuid.uuid4())
164
+ background_tasks.add_task(log_query_encrypted, req_id, q)
165
+
166
+ is_adult = any(word in q.lower() for word in ["porn", "sex", "xxx", "nude"])
167
+
168
+ results, prediction = perform_core_search(q)
169
+
170
+ # AI Summary Generation (Short summary based on top 2 results)
171
+ ai_summary = "AI Summary not available."
172
+ if results and not ai_semaphore.locked(): # Only generate if not overloaded
173
+ context = " ".join([r.get("title", "") + ". " + r.get("description", "") for r in results[:2]])
174
+ prompt = f"Summarize the following search results about '{q}' in 2 short sentences. Results: {context}"
175
+ ai_summary = await run_ai_task(prompt, max_tokens=60)
176
+ elif ai_semaphore.locked():
177
+ ai_summary = "AI is currently busy with other users. Showing fast results."
178
+
179
+ response = {
180
+ "request_id": req_id,
181
+ "smart_prediction": prediction,
182
+ "ai_summary": ai_summary,
183
+ "total_results": len(results),
184
+ "results": results
185
  }
186
 
187
+ if is_adult:
188
+ response["safety_message"] = "you are safe you can do anything"
189
+ return JSONResponse(content=response, headers={"X-Safety-Message": "you are safe you can do anything"})
190
+ return response
191
+
192
+ # 🌟 NEW: AI RAG MODE ENDPOINT 🌟
193
+ @app.get("/aimode")
194
+ async def ai_mode_search(q: str = Query(...), background_tasks: BackgroundTasks = BackgroundTasks()):
195
+ req_id = str(uuid.uuid4())
196
+ background_tasks.add_task(log_query_encrypted, req_id, q)
197
 
198
+ results, prediction = perform_core_search(q)
199
+
200
+ if not results:
201
+ return {
202
+ "ai_response": f"I couldn't find any information about '{q}' in my database to give you an accurate answer.",
203
+ "results": []
204
+ }
 
 
 
 
 
 
205
 
206
+ # Build RAG Context from Top 3 results
207
+ context = ""
208
+ for i, r in enumerate(results[:3]):
209
+ context += f"Source {i+1}: {r.get('title', '')} - {r.get('description', '')}\n"
 
 
 
 
210
 
211
+ prompt = f"""You are a helpful Search Engine AI. Answer the user's query based ONLY on the provided search results. Be concise and polite.
212
+ User Query: {q}
213
+
214
+ Search Results Data:
215
+ {context}
216
+
217
+ Answer:"""
218
+
219
+ ai_reply = await run_ai_task(prompt, max_tokens=250)
220
+
221
+ return {
222
+ "request_id": req_id,
223
+ "ai_response": ai_reply,
224
+ "smart_prediction": prediction,
225
+ "sources_used": len(results[:3]),
226
+ "results": results[:5] # Send top 5 results along with AI answer
227
+ }
228
 
229
  if __name__ == "__main__":
230
  uvicorn.run(app, host="0.0.0.0", port=7860)