Nebulixlabs commited on
Commit
6486459
·
verified ·
1 Parent(s): 411eefc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +253 -562
app.py CHANGED
@@ -1,604 +1,295 @@
1
- # ==============================================================================
2
- # NEBULIX SEARCH ENGINE v6 FINAL (ADVANCED RANKING & SAFE SEARCH)
3
- # Target: Hugging Face Spaces | 2 vCPU • 16 GB RAM • True Agentic RAG
4
- # Model: Qwen2.5-0.5B-Instruct (Inference Only)
5
- # Ranking: Advanced Algorithmic (Domain Trust, Proxy Penalty) + BM25 + Semantic
6
- # Includes ALL API Endpoints (Popular, Trending, Suggestions, Clicks, etc.)
7
- # ==============================================================================
8
  import json
9
  import uuid
10
  import asyncio
11
  import os
12
  import uvicorn
13
  import re
14
- import time
15
- import pickle
16
- import sqlite3
17
- import unicodedata
18
- import threading
19
- import numpy as np
20
- import psutil
21
  import urllib.parse
22
- from datetime import datetime
 
23
  from contextlib import asynccontextmanager
24
- from fastapi import FastAPI, Query, Request, Form, BackgroundTasks
25
- from fastapi.responses import JSONResponse, StreamingResponse
26
  from fastapi.middleware.cors import CORSMiddleware
27
- from rank_bm25 import BM25Okapi
28
- from cachetools import TTLCache, LRUCache
29
- import hashlib
30
- from huggingface_hub import HfApi, hf_hub_download
31
-
32
- # ==============================================================================
33
- # CPU & PYTORCH OPTIMIZATIONS (NO TRAINING)
34
- # ==============================================================================
35
- os.environ["OMP_NUM_THREADS"] = "1"
36
- os.environ["OPENBLAS_NUM_THREADS"] = "1"
37
- os.environ["MKL_NUM_THREADS"] = "1"
38
-
39
- try:
40
- import torch
41
- from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, TextIteratorStreamer
42
- from sentence_transformers import SentenceTransformer
43
-
44
- # 🔥 STRICT NO-TRAINING LOCK 🔥
45
- torch.set_num_threads(1)
46
- torch.set_grad_enabled(False) # Strict Inference Mode
47
- ML_AVAILABLE = True
48
- except ImportError:
49
- ML_AVAILABLE = False
50
- print("⚠️ ML packages missing. Check requirements.txt")
51
-
52
- # ==============================================================================
53
- # SQLITE ANALYTICS SETUP
54
- # ==============================================================================
55
- DB_PATH = "analytics.db"
56
- def init_db():
57
- conn = sqlite3.connect(DB_PATH)
58
- conn.execute('CREATE TABLE IF NOT EXISTS clicks (url TEXT, type TEXT, ts DATETIME)')
59
- conn.execute('CREATE TABLE IF NOT EXISTS logs (query TEXT, latency REAL, ts DATETIME)')
60
- conn.execute('CREATE TABLE IF NOT EXISTS planner_logs (query TEXT, hops INTEGER, decision_log TEXT, ts DATETIME)')
61
- conn.commit()
62
- conn.close()
63
-
64
- def log_to_db(query, args=()):
65
  try:
66
- conn = sqlite3.connect(DB_PATH)
67
- conn.execute(query, args)
68
- conn.commit()
69
- conn.close()
70
- except: pass
71
-
72
- # ==============================================================================
73
- # GLOBAL DATA & CACHES
74
- # ==============================================================================
75
- raw_data = []
76
- url_to_data = {}
77
- bm25_index = None
78
- trie_root = {}
79
- doc_embeddings = None
80
- text_embedder = None
81
-
82
- SEARCH_CACHE = TTLCache(maxsize=10000, ttl=1800) # 30 Min TTL
83
- AI_ANSWER_CACHE = LRUCache(maxsize=2000)
84
-
85
- tokenizer = None
86
- pipe = None
87
- AI_SEMAPHORE = asyncio.Semaphore(1)
88
-
89
- CLICK_TRACKER = {}
90
- STOP_WORDS = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is"}
91
-
92
- ADULT_KEYWORDS = {"porn", "sex", "xxx", "nsfw", "nude", "xvideos", "pornhub", "adult", "erotic"}
93
-
94
- # ==============================================================================
95
- # UTILS & SAFE SEARCH LOGIC
96
- # ==============================================================================
97
- def is_adult_content(text: str) -> bool:
98
- text_lower = text.lower()
99
- return any(word in text_lower for word in ADULT_KEYWORDS)
100
-
101
- def update_dynamic_fields(doc):
102
- human = doc.get("human_opens", 0)
103
- ai = doc.get("ai_opens", 0)
104
- crawl = doc.get("crawler_opens", 0)
105
- bot = doc.get("bot_opens", 0)
106
-
107
- doc["total_opens"] = human + ai + crawl + bot
108
- doc["popularity"] = human + (ai * 2) + (crawl * 0.1) + (bot * 0)
109
-
110
- total_interactive = human + ai
111
- ai_percent = (ai / total_interactive * 100) if total_interactive > 0 else 0
112
- doc["ai_open_percent"] = ai_percent
113
-
114
- tags = set()
115
- if doc.get("trust_score", 50) >= 80: tags.add("Trusted")
116
- if doc.get("quality_score", 50) >= 80: tags.add("High Quality")
117
- if doc.get("domain_quality", 50) >= 90: tags.add("Verified")
118
- if doc.get("total_opens", 0) >= 500: tags.add("Popular")
119
-
120
- if ai >= 25: tags.add("Opened by AI")
121
- if total_interactive > 10 and ai_percent >= 80: tags.add("AI Favorite")
122
-
123
- doc["special_tags"] = list(tags)
124
-
125
- def normalize_text(text: str) -> str:
126
- if not text: return ""
127
- text = unicodedata.normalize('NFKD', str(text)).encode('ASCII', 'ignore').decode('utf-8')
128
- return re.sub(r'<[^>]+>', '', text.lower())
129
-
130
- def tokenize(text: str) -> list:
131
- words = re.findall(r'\b[a-z0-9]+\b', normalize_text(text))
132
- return [w for w in words if w not in STOP_WORDS]
133
-
134
- def add_to_trie(word):
135
- node = trie_root
136
- for char in word:
137
- if char not in node: node[char] = {}
138
- node = node[char]
139
- node['_end'] = True
140
-
141
- def cosine_similarity(vec_a, mat_b):
142
- if len(mat_b) == 0: return np.array([])
143
- return np.dot(mat_b, vec_a) / (np.linalg.norm(mat_b, axis=1) * np.linalg.norm(vec_a) + 1e-10)
144
-
145
- def parse_json_field(val):
146
- if isinstance(val, dict): return val
147
- if isinstance(val, str):
148
- try: return json.loads(val)
149
- except: return {}
150
- return {}
151
-
152
- def build_searchable_text(doc):
153
- parts = [doc.get("title", ""), doc.get("description", ""), doc.get("tag", "")]
154
- for k, v in doc.get("meta_tags", {}).items():
155
- if isinstance(v, str): parts.append(v)
156
- for k, v in doc.get("og_tags", {}).items():
157
- if isinstance(v, str): parts.append(v)
158
- return " ".join(p for p in parts if p).strip()
159
-
160
- def build_runtime_record(doc):
161
- r = doc.copy()
162
- r["meta_tags"] = parse_json_field(doc.get("meta_tags"))
163
- r["og_tags"] = parse_json_field(doc.get("og_tags"))
164
- r["specific_data"] = parse_json_field(doc.get("specific_data"))
165
-
166
- r["images"] = doc.get("images") or []
167
- r["videos"] = doc.get("videos") or []
168
- r["documents"] = doc.get("documents") or []
169
- r["tag"] = doc.get("tag", "general")
170
- r["searchable_text"] = build_searchable_text(r)
171
-
172
- # Safe Search Tagging
173
- r["is_adult"] = is_adult_content(r["searchable_text"] + " " + r.get("url", ""))
174
-
175
- r["human_opens"] = r.get("human_opens", 0)
176
- r["ai_opens"] = r.get("ai_opens", 0)
177
- r["crawler_opens"] = r.get("crawler_opens", 0)
178
- r["bot_opens"] = r.get("bot_opens", 0)
179
- r["trust_score"] = r.get("trust_score", 50.0)
180
- r["quality_score"] = r.get("quality_score", 50.0)
181
- r["learning_score"] = r.get("learning_score", 50.0)
182
- r["search_boost"] = r.get("search_boost", 0.0)
183
- r["spam_score"] = r.get("spam_score", 0.0)
184
- r["domain_quality"] = r.get("domain_quality", 50.0)
185
- r["last_opened"] = r.get("last_opened", datetime.utcnow().isoformat())
186
- r["language"] = r.get("language", "English")
187
- r["country"] = r.get("country", "Global")
188
-
189
- update_dynamic_fields(r)
190
- return r
191
-
192
- # ==============================================================================
193
- # DIRECT DATASET LOADING (No Train Split Errors)
194
- # ==============================================================================
195
- def load_dataset_direct():
196
- global bm25_index, raw_data, url_to_data
197
- hf_token = os.getenv("HF_TOKEN")
198
- repo_id = "Nebulixlabs/Ultimate-Search-Engine-Dataset"
199
-
200
  try:
201
- print("☁️ Fetching raw data files directly from Hugging Face Hub...")
202
- api = HfApi()
203
- files_in_repo = api.list_repo_files(repo_id, repo_type="dataset", token=hf_token)
204
- jsonl_files = [f for f in files_in_repo if f.endswith(".jsonl") or f.endswith(".json")]
205
-
206
- corpus_tokens = []
207
- texts_to_embed = []
208
- for file_name in jsonl_files:
209
- print(f"📥 Downloading {file_name}...")
210
- local_path = hf_hub_download(repo_id, file_name, repo_type="dataset", token=hf_token)
211
-
212
- with open(local_path, "r", encoding="utf-8") as f:
213
- for line in f:
214
- if not line.strip(): continue
215
- try:
216
- doc = build_runtime_record(json.loads(line))
217
- raw_data.append(doc)
218
- url_to_data[doc["url"]] = doc
219
-
220
- tokens = tokenize(doc["searchable_text"])
221
- corpus_tokens.append(tokens)
222
- texts_to_embed.append(doc["searchable_text"])
223
- for t in tokens: add_to_trie(t)
224
- except Exception: pass
225
-
226
- bm25_index = BM25Okapi(corpus_tokens)
227
- return texts_to_embed
228
- except Exception as e:
229
- print(f"❌ Failed to load dataset: {e}")
230
- return []
231
-
232
- def init_system():
233
- global tokenizer, pipe, text_embedder, doc_embeddings
234
- init_db()
235
- print("🚀 [1/3] Initializing System & Downloading Data...")
236
 
237
- texts_to_embed = load_dataset_direct()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- if ML_AVAILABLE:
240
- print("🧠 [2/3] Loading Fast Semantic Embedder (all-MiniLM-L6-v2)...")
241
- text_embedder = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
242
-
243
- # In-memory fast semantic indexing
244
- if texts_to_embed:
245
- doc_embeddings = text_embedder.encode(texts_to_embed, convert_to_numpy=True)
246
-
247
- print("🔥 [3/3] Loading Qwen2.5-0.5B-Instruct for Agentic RAG...")
248
  try:
249
- model_id = "Qwen/Qwen2.5-0.5B-Instruct"
250
- tokenizer = AutoTokenizer.from_pretrained(model_id)
251
- model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32)
252
- model.eval()
253
- pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device=-1)
254
- print("✅ Qwen Model successfully loaded.")
 
 
255
  except Exception as e:
256
- print(f"⚠️ LLM loading failed: {e}")
257
-
258
- # ==============================================================================
259
- # ALGORITHMIC RANKING (DOMAIN TRUST + PROXY PENALTY + BM25 + SEMANTIC)
260
- # ==============================================================================
261
- def get_domain(url):
262
- try: return urllib.parse.urlparse(url).netloc.lower()
263
- except: return ""
264
-
265
- def calculate_domain_trust(url: str, query_terms: list):
266
- domain = get_domain(url)
267
- if not domain: return 0
268
-
269
- trust_score = 0
270
- domain_parts = domain.split('.')
271
-
272
- # 1. Exact Brand/Domain Match (Massive Boost for official domains)
273
- for qt in query_terms:
274
- if qt in domain_parts and len(qt) > 2:
275
- trust_score += 150
276
-
277
- # 2. Proxy / Fake Mirror Penalty
278
- proxy_indicators = ["proxy", "mirror", "unblock", "alt", "fake", "ip"]
279
- if any(p in domain for p in proxy_indicators):
280
- trust_score -= 200
281
-
282
- # 3. High Authority TLDs
283
- if domain.endswith(".gov") or domain.endswith(".edu"): trust_score += 30
284
- elif domain.endswith(".org") or domain.endswith(".com"): trust_score += 10
285
-
286
- # 4. Spam Penalty for long subdomains
287
- if len(domain_parts) > 3: trust_score -= 20
288
-
289
- return trust_score
290
 
291
- def get_ranked_results(query: str):
292
- # Safe Search Check
293
- is_safe_query = not is_adult_content(query)
294
-
295
- q_tokens = tokenize(query)
296
- if not q_tokens: return []
297
- query_terms_lower = [q.lower() for q in query.split()]
298
-
299
- bm25_scores = bm25_index.get_scores(q_tokens) if bm25_index else np.zeros(len(raw_data))
300
- semantic_scores = np.zeros(len(raw_data))
301
 
302
- if text_embedder and doc_embeddings is not None and len(doc_embeddings) > 0:
303
- q_vec = text_embedder.encode([query])[0]
304
- semantic_scores = cosine_similarity(q_vec, doc_embeddings)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- scored_results = []
307
-
308
- for i, doc in enumerate(raw_data):
309
- # Adult filter logic
310
- if is_safe_query and doc.get("is_adult"):
311
- continue
312
-
313
- domain_trust = calculate_domain_trust(doc["url"], query_terms_lower)
314
-
315
- # Advanced Hybrid Formula
316
- score = (bm25_scores[i] * 2.0) + \
317
- (semantic_scores[i] * 15.0) + \
318
- domain_trust + \
319
- (doc.get("trust_score", 50) * 0.1) + \
320
- (doc.get("popularity", 0) * 0.05) + \
321
- doc.get("search_boost", 0) - doc.get("spam_score", 0)
322
-
323
- if score > -50: # Ignore completely heavily penalized spam
324
- scored_results.append((score, doc))
325
-
326
- scored_results.sort(key=lambda x: x[0], reverse=True)
327
-
328
- formatted = []
329
- for s, d in scored_results[:100]:
330
- formatted.append({
331
- "url": d["url"], "title": d["title"], "description": d["description"],
332
- "domain": get_domain(d["url"]), "favicon": d.get("favicon", ""),
333
- "language": d.get("language", "English"), "country": d.get("country", "Global"),
334
- "images": d.get("images", []), "videos": d.get("videos", []),
335
- "tag": d.get("tag", "general"),
336
- "is_adult": d.get("is_adult", False),
337
- "last_opened": d["last_opened"], "trust_score": round(d.get("trust_score", 50), 2),
338
- "quality_score": round(d.get("quality_score", 50), 2),
339
- "learning_score": round(d.get("learning_score", 50), 2),
340
- "human_opens": d["human_opens"], "ai_opens": d["ai_opens"],
341
- "total_opens": d["total_opens"], "special_tags": d.get("special_tags", []),
342
- "match_score": round(s, 2),
343
- "searchable_text": d.get("searchable_text", "")
344
- })
345
- return formatted
346
 
347
- def get_best_chunks(query, full_text):
348
- if not full_text: return []
349
- words = full_text.split()
350
- chunks = [" ".join(words[i:i + 256]) for i in range(0, len(words), 200)]
351
 
352
- if text_embedder and chunks:
353
- q_emb = text_embedder.encode([query], convert_to_numpy=True)[0]
354
- c_embs = text_embedder.encode(chunks, convert_to_numpy=True)
355
- scores = cosine_similarity(q_emb, c_embs)
356
- best_idx = np.argsort(scores)[::-1][:3]
357
- return [chunks[idx] for idx in best_idx]
358
- return chunks[:3]
359
-
360
- # ==============================================================================
361
- # TRUE AGENTIC RAG PLANNER (QWEN)
362
- # ==============================================================================
363
- async def agent_planner_call(prompt):
364
- if not pipe: return "ACTION: ANSWER"
365
- res = await asyncio.to_thread(
366
- pipe, prompt, max_new_tokens=15, temperature=0.1, do_sample=True, return_full_text=False
367
- )
368
- generated = res[0]["generated_text"].strip()
369
- return generated.split('\n')[0]
370
-
371
- async def execute_agentic_rag(query, candidates, debug=False):
372
- context_memory = ""
373
- sources_used = []
374
- visited_idx = set()
375
- debug_log = []
376
-
377
- metadata_list = "\n".join([f"ID {i} | URL: {d['url']} | Title: {d['title']}" for i, d in enumerate(candidates[:10])])
 
 
 
378
 
379
- async with AI_SEMAPHORE:
380
- for hop in range(5):
381
- sys_prompt = f"Query: {query}\nURLs:\n{metadata_list}\nMemory:{context_memory}\nAction (Type OPEN(id) or ANSWER): "
382
-
383
- decision = await agent_planner_call(sys_prompt)
384
- debug_log.append(f"Hop {hop+1}: {decision}")
385
-
386
- open_match = re.search(r'OPEN\((\d+)\)', decision, re.IGNORECASE)
387
- if open_match:
388
- idx = int(open_match.group(1))
389
- if idx < len(candidates) and idx not in visited_idx:
390
- visited_idx.add(idx)
391
- doc = candidates[idx]
392
- best_chunks = get_best_chunks(query, doc.get("searchable_text", ""))
393
- context_memory += f"\n[From {doc['domain']}]:\n" + "\n".join(best_chunks) + "\n"
394
-
395
- if doc["url"] in url_to_data:
396
- url_to_data[doc["url"]]["ai_opens"] += 1
397
- url_to_data[doc["url"]]["learning_score"] += 1.0
398
- update_dynamic_fields(url_to_data[doc["url"]])
399
-
400
- sources_used.append({
401
- "url": doc["url"], "title": doc["title"], "domain": doc["domain"],
402
- "special_tags": doc["special_tags"], "favicon": doc["favicon"]
403
- })
404
- else: break
405
- else: break
406
-
407
- log_to_db("INSERT INTO planner_logs (query, hops, decision_log, ts) VALUES (?, ?, ?, ?)",
408
- (query, len(visited_idx), " | ".join(debug_log), datetime.utcnow()))
409
-
410
- final_prompt = f"You are a helpful AI assistant. Use ONLY the provided Memory to answer the query.\nQuery: {query}\nMemory:\n{context_memory}\n\nAnswer:\n"
411
 
412
- return final_prompt, sources_used, debug_log
 
 
 
 
 
 
 
 
 
 
413
 
414
- # ==============================================================================
415
- # FASTAPI APP & ALL ENDPOINTS
416
- # ==============================================================================
417
  @asynccontextmanager
418
  async def lifespan(app: FastAPI):
419
- asyncio.create_task(asyncio.to_thread(init_system))
 
 
420
  yield
 
421
 
422
- app = FastAPI(title="Nebulix v6 Qwen SafeSearch", lifespan=lifespan)
423
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
424
 
425
- @app.middleware("http")
426
- async def analytics_middleware(request: Request, call_next):
427
- start_time = time.perf_counter()
428
- response = await call_next(request)
429
- latency = (time.perf_counter() - start_time) * 1000
430
- if "/search" in request.url.path or "/aimode" in request.url.path:
431
- q = request.query_params.get("q", "")
432
- log_to_db("INSERT INTO logs (query, latency, ts) VALUES (?, ?, ?)", (q, latency, datetime.utcnow()))
433
- return response
434
 
435
  @app.get("/search")
436
- async def api_search(q: str = Query(...)):
437
- cache_key = q.lower()
438
- if cache_key in SEARCH_CACHE: return SEARCH_CACHE[cache_key]
 
439
 
440
- results = get_ranked_results(q)
441
- for r in results: r.pop("searchable_text", None)
442
-
443
- resp = {"query": q, "results": results}
444
- SEARCH_CACHE[cache_key] = resp
445
- return resp
 
446
 
447
  @app.get("/aimode")
448
- async def api_aimode(q: str = Query(...), debug: bool = False):
449
- q_hash = hashlib.md5(q.encode()).hexdigest()
450
- if q_hash in AI_ANSWER_CACHE: return AI_ANSWER_CACHE[q_hash]
451
-
452
- results = get_ranked_results(q)
453
- final_prompt, sources, debug_log = await execute_agentic_rag(q, results, debug)
454
 
455
- if not pipe: return {"Answer": "Mocked", "Sources": sources}
456
-
457
- async with AI_SEMAPHORE:
458
- res = await asyncio.to_thread(
459
- pipe, final_prompt, max_new_tokens=1024, temperature=0.2, do_sample=True, return_full_text=False
460
- )
461
- answer = res[0]["generated_text"].strip()
462
 
463
- resp = {
464
- "Answer": answer[:150] + "...",
465
- "Summary": answer[:300],
466
- "Detailed Explanation": answer,
467
- "Sources": sources
468
- }
469
- if debug: resp["Debug_Planner"] = debug_log
470
- AI_ANSWER_CACHE[q_hash] = resp
471
- return resp
472
 
473
  @app.get("/aimodechat")
474
- async def api_aimodechat(q: str = Query(...)):
475
- results = get_ranked_results(q)
476
- final_prompt, sources, _ = await execute_agentic_rag(q, results)
 
477
 
478
- async def true_event_stream():
479
- if pipe and tokenizer:
480
- async with AI_SEMAPHORE:
481
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
482
- def generate():
483
- try: pipe(final_prompt, max_new_tokens=1024, temperature=0.3, streamer=streamer, return_full_text=False)
484
- except: pass
485
-
486
- thread = threading.Thread(target=generate)
487
- thread.start()
488
-
489
- while True:
490
- try:
491
- text = await asyncio.to_thread(next, streamer)
492
- if text: yield f"data: {text}\n\n"
493
- except StopIteration: break
494
- else:
495
- yield "data: [Qwen Model Missing]\n\n"
496
- yield f"data: [SOURCES_JSON] {json.dumps(sources)}\n\n"
497
-
498
- return StreamingResponse(true_event_stream(), media_type="text/event-stream")
499
-
500
- @app.get("/popular")
501
- async def api_popular():
502
- docs = sorted(raw_data, key=lambda x: x.get("popularity", 0), reverse=True)[:10]
503
- return {"popular": [{"url": d["url"], "title": d["title"], "popularity": d["popularity"]} for d in docs]}
504
-
505
- @app.get("/ai-popular")
506
- async def api_ai_popular():
507
- docs = sorted(raw_data, key=lambda x: x.get("ai_opens", 0), reverse=True)[:10]
508
- return {"ai_popular": [{"url": d["url"], "title": d["title"], "ai_opens": d["ai_opens"], "special_tags": d["special_tags"]} for d in docs]}
509
-
510
- @app.get("/trending")
511
- async def api_trending():
512
- docs = sorted(raw_data, key=lambda x: x.get("total_opens", 0), reverse=True)[:10]
513
- return {"trending": [{"url": d["url"], "title": d["title"]} for d in docs]}
514
-
515
- @app.get("/suggestions")
516
- async def api_suggestions(q: str = Query(...)):
517
- base_q = q.lower().split()[-1]
518
- words = []
519
- def dfs(node, prefix):
520
- if len(words) >= 5: return
521
- if node.get('_end'): words.append(prefix)
522
- for char, child in node.items():
523
- if char != '_end': dfs(child, prefix + char)
524
-
525
- node = trie_root
526
- for c in base_q:
527
- if c in node: node = node[c]
528
- else: break
529
- if node and node != trie_root: dfs(node, base_q)
530
- return {"suggestions": [q.replace(base_q, w) for w in words]}
531
-
532
- @app.get("/stats")
533
- async def api_stats():
534
- mem = psutil.virtual_memory()
535
- return {
536
- "engine": "Nebulix v6.5 HF Spaces Edition",
537
- "llm_model": "Qwen2.5-0.5B-Instruct",
538
- "ranking": "Hybrid (Algorithmic Domain Trust + BM25 + Semantic)",
539
- "documents": len(raw_data),
540
- "cpu_usage_percent": psutil.cpu_percent(),
541
- "ram_usage_percent": mem.percent,
542
- "search_cache_items": len(SEARCH_CACHE),
543
- "ai_cache_items": len(AI_ANSWER_CACHE)
544
- }
545
-
546
- @app.get("/health")
547
- async def api_health(): return {"status": "ok", "version": "6.5"}
548
-
549
- @app.get("/url-info")
550
- async def api_url_info(url: str = Query(...)):
551
- if url in url_to_data:
552
- safe_doc = url_to_data[url].copy()
553
- safe_doc.pop("searchable_text", None)
554
- return safe_doc
555
- return {"error": "Not Found"}
556
-
557
- @app.post("/click")
558
- async def api_click(req: Request):
559
- data = await req.json()
560
- url = data.get("url")
561
- ip = req.client.host
562
- track_id = f"{ip}_{url}"
563
- if track_id in CLICK_TRACKER and time.time() - CLICK_TRACKER[track_id] < 1800:
564
- return {"status": "ignored_duplicate"}
565
 
566
- CLICK_TRACKER[track_id] = time.time()
567
- if url in url_to_data:
568
- d = url_to_data[url]
569
- d["human_opens"] += 1
570
- d["last_opened"] = datetime.utcnow().isoformat()
571
- update_dynamic_fields(d)
572
- log_to_db("INSERT INTO clicks (url, type, ts) VALUES (?, ?, ?)", (url, "human", datetime.utcnow()))
573
- return {"status": "success", "human_opens": d["human_opens"]}
574
- return {"error": "Not found"}
575
-
576
- @app.post("/ai-open")
577
- async def api_ai_open(req: Request):
578
- data = await req.json()
579
- url = data.get("url")
580
- if url in url_to_data:
581
- d = url_to_data[url]
582
- d["ai_opens"] += 1
583
- d["learning_score"] += 1.0
584
- d["last_opened"] = datetime.utcnow().isoformat()
585
- update_dynamic_fields(d)
586
- log_to_db("INSERT INTO clicks (url, type, ts) VALUES (?, ?, ?)", (url, "ai", datetime.utcnow()))
587
- return {"status": "success"}
588
- return {"error": "Not found"}
589
-
590
- @app.post("/feedback")
591
- async def api_feedback(req: Request):
592
- data = await req.json()
593
- url = data.get("url")
594
- if url in url_to_data:
595
- url_to_data[url]["trust_score"] += 2.0 if data.get("helpful") else -1.0
596
- update_dynamic_fields(url_to_data[url])
597
- return {"status": "feedback_registered"}
598
-
599
- @app.post("/search-log")
600
- async def api_search_log(req: Request):
601
- return {"status": "logged"}
602
 
603
  if __name__ == "__main__":
604
- uvicorn.run(app, host="0.0.0.0", port=7860, log_level="warning")
 
 
 
 
 
 
 
 
1
  import json
2
  import uuid
3
  import asyncio
4
  import os
5
  import uvicorn
6
  import re
7
+ import multiprocessing
8
+ import httpx
 
 
 
 
 
9
  import urllib.parse
10
+ import xml.etree.ElementTree as ET
11
+ from collections import deque
12
  from contextlib import asynccontextmanager
13
+ from datetime import datetime
14
+ from fastapi import FastAPI, Query, BackgroundTasks, Request
15
  from fastapi.middleware.cors import CORSMiddleware
16
+ from huggingface_hub import hf_hub_download
17
+
18
+ # Elasticsearch Import
19
+ from elasticsearch import AsyncElasticsearch, helpers
20
+
21
+ # AI Models Imports
22
+ import torch
23
+ from transformers import pipeline
24
+
25
+ # --- 1. SETUP & CONFIGURATION ---
26
+ TOTAL_CORES = multiprocessing.cpu_count()
27
+ AI_CORES = max(1, int(TOTAL_CORES * 0.8))
28
+ torch.set_num_threads(AI_CORES)
29
+
30
+ print(f"🖥️ Allocating {AI_CORES} cores to AI.")
31
+
32
+ # Initialize Elasticsearch Client
33
+ es = AsyncElasticsearch(["http://localhost:9200"])
34
+ INDEX_NAME = "nebulix_search_index"
35
+
36
+ MAX_CONCURRENT_AI = max(1, AI_CORES // 2)
37
+ ai_semaphore = asyncio.Semaphore(MAX_CONCURRENT_AI)
38
+ ai_generator = None
39
+
40
+ ADULT_KEYWORDS = {"porn", "sex", "nude", "nsfw", "xxx", "adult", "erotic"}
41
+ HF_TOKEN = os.getenv("HF_TOKEN")
42
+
43
+ # --- 2. EXTERNAL SMART APIs ---
44
+ async def get_currency_conversion(amount, base, target):
 
 
 
 
 
 
 
 
 
45
  try:
46
+ url = f"https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{base}.json"
47
+ async with httpx.AsyncClient() as client:
48
+ resp = await client.get(url, timeout=5.0)
49
+ if resp.status_code == 200:
50
+ rate = resp.json()[base][target]
51
+ return {"type": "currency", "content": f"💱 {amount} {base.upper()} = {float(amount) * rate:.2f} {target.upper()}"}
52
+ except Exception: pass
53
+ return None
54
+
55
+ async def get_stock_crypto_price(query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  try:
57
+ search_url = f"https://query2.finance.yahoo.com/v1/finance/search?q={urllib.parse.quote(query)}"
58
+ async with httpx.AsyncClient() as client:
59
+ resp = await client.get(search_url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=5.0)
60
+ if resp.status_code == 200 and resp.json()['quotes']:
61
+ quote = resp.json()['quotes'][0]
62
+ symbol = quote.get('symbol')
63
+ price_url = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}"
64
+ price_resp = await client.get(price_url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=5.0)
65
+ if price_resp.status_code == 200:
66
+ data = price_resp.json()['chart']['result'][0]['meta']
67
+ return {"type": "finance", "content": f"📈 {quote.get('shortname')} ({symbol}) Price: {data['regularMarketPrice']} {data['currency']}"}
68
+ except Exception: pass
69
+ return None
70
+
71
+ async def perform_translation(text: str, target_lang: str, source_lang: str = "auto"):
72
+ try:
73
+ url = "https://translate.googleapis.com/translate_a/single"
74
+ params = {"client": "gtx", "sl": source_lang, "tl": target_lang, "dt": "t", "q": text}
75
+ async with httpx.AsyncClient() as client:
76
+ resp = await client.get(url, params=params, timeout=5.0)
77
+ if resp.status_code == 200:
78
+ translated_text = "".join([s[0] for s in resp.json()[0]])
79
+ return {"source_lang": source_lang, "target_lang": target_lang, "original": text, "translated": translated_text}
80
+ except Exception: pass
81
+ return None
82
+
83
+ async def analyze_query_for_apis(query: str):
84
+ q_lower = query.lower().strip()
 
 
 
 
 
 
 
85
 
86
+ translate_match = re.search(r'^translate\s+(.*?)(?:\s+to\s+([a-zA-Z]+))?$', q_lower)
87
+ if translate_match:
88
+ return {"type": "translation", "content": f"🗣️ Translation: {(await perform_translation(translate_match.group(1).strip(), translate_match.group(2) or 'en'))['translated']}"}
89
+
90
+ curr_match = re.search(r'convert\s+([0-9.]+)\s+([a-z]{3})\s+(?:to|in)\s+([a-z]{3})', q_lower)
91
+ if curr_match:
92
+ return await get_currency_conversion(*curr_match.groups())
93
+
94
+ if any(k in q_lower for k in ["price of", "stock price", "crypto price"]):
95
+ return await get_stock_crypto_price(q_lower.replace("price of", "").replace("stock price", "").replace("crypto price", "").strip())
96
+
97
+ if "time" in q_lower and "now" in q_lower:
98
+ return {"type": "time", "content": f"⏰ Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"}
99
+
100
+ return None
101
+
102
+ # --- 3. ELASTICSEARCH DATASET LOAD & INDEX ---
103
+ async def load_and_index_dataset():
104
+ print("📥 Downloading Private Dataset from Hugging Face...")
105
+ dataset_path = "dataset.jsonl"
106
 
107
+ if HF_TOKEN:
 
 
 
 
 
 
 
 
108
  try:
109
+ dataset_path = await asyncio.to_thread(
110
+ hf_hub_download,
111
+ repo_id="Nebulixlabs/Ultimate-Search-Engine-Dataset",
112
+ filename="dataset.jsonl",
113
+ repo_type="dataset",
114
+ token=HF_TOKEN
115
+ )
116
+ print("✅ Private Dataset Downloaded Successfully!")
117
  except Exception as e:
118
+ print(f"⚠️ Hugging Face Download failed: {e}")
119
+
120
+ # Check if index exists, if not create with SEO boosting map
121
+ if not await es.indices.exists(index=INDEX_NAME):
122
+ print("⚡ Creating Elasticsearch Index...")
123
+ await es.indices.create(index=INDEX_NAME, body={
124
+ "mappings": {
125
+ "properties": {
126
+ "title": {"type": "text", "analyzer": "standard"},
127
+ "description": {"type": "text", "analyzer": "standard"},
128
+ "tag": {"type": "keyword"}, # Keyword is exact match
129
+ "url": {"type": "keyword"}
130
+ }
131
+ }
132
+ })
133
+ else:
134
+ print("⚡ Elasticsearch Index already exists. Ready.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ print("🔄 Loading Data into Elasticsearch...")
137
+ actions = []
 
 
 
 
 
 
 
 
138
 
139
+ if os.path.exists(dataset_path):
140
+ with open(dataset_path, "r", encoding="utf-8") as f:
141
+ for line in f:
142
+ if line.strip():
143
+ try:
144
+ doc = json.loads(line)
145
+ action = {
146
+ "_op_type": "index",
147
+ "_index": INDEX_NAME,
148
+ "_source": doc # Pushing ENTIRE raw dictionary
149
+ }
150
+ actions.append(action)
151
+ except: pass
152
+
153
+ if actions:
154
+ await helpers.async_bulk(es, actions)
155
+ print(f"🚀 Indexed {len(actions)} records into Elasticsearch!")
156
+ else:
157
+ print("❌ Dataset file not found!")
158
+
159
+ def load_ai_models():
160
+ global ai_generator
161
+ print("🤖 Loading RAG AI Model...")
162
+ try:
163
+ ai_generator = pipeline("text-generation", model="Qwen/Qwen2.5-0.5B-Instruct", device=-1, dtype=torch.float32)
164
+ print("✅ AI Model Loaded Successfully!")
165
+ except Exception as e:
166
+ print("❌ AI Model Failed:", e)
167
 
168
+ # --- 4. ELASTICSEARCH SEARCH LOGIC (SEO & SMART FILTER) ---
169
+ async def perform_core_search(query: str):
170
+ if not await es.indices.exists(index=INDEX_NAME):
171
+ return [], "Search index not ready.", 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ user_wants_adult = any(word in query.lower() for word in ADULT_KEYWORDS)
 
 
 
174
 
175
+ # 🎯 Google-Grade SEO Query Construction
176
+ es_query = {
177
+ "query": {
178
+ "bool": {
179
+ "must": [
180
+ {
181
+ "multi_match": {
182
+ "query": query,
183
+ # Field Boosting for SEO: Title gets 4x points, Desc 2x, Meta 1x
184
+ "fields": ["title^4", "description^2", "meta_tags.description^1", "url"],
185
+ "fuzziness": "AUTO" # Auto typo correction (e.g. githb -> github)
186
+ }
187
+ }
188
+ ],
189
+ "must_not": []
190
+ }
191
+ },
192
+ "size": 100 # Can fetch up to 100 dynamically based on match
193
+ }
194
+
195
+ # 🛡️ Smart Tag Filter (Hide adult content if user didn't ask for it)
196
+ if not user_wants_adult:
197
+ es_query["query"]["bool"]["must_not"].extend([
198
+ {"match": {"tag": "adult"}},
199
+ {"match": {"tag": "nsfw"}}
200
+ ])
201
+
202
+ response = await es.search(index=INDEX_NAME, body=es_query)
203
+ hits = response["hits"]["hits"]
204
 
205
+ best_score = hits[0]["_score"] if hits else 0
206
+ prediction = "High confidence" if best_score > 10 else "Medium confidence" if best_score > 3 else "Low confidence"
207
+
208
+ final_results = []
209
+ for hit in hits:
210
+ # RETURNING THE ENTIRE DOCUMENT (Favicon, images, specific_data, etc.)
211
+ doc = hit["_source"]
212
+ doc["match_score"] = hit["_score"] # Add ES score for frontend
213
+ final_results.append(doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
+ return final_results, prediction, best_score
216
+
217
+ # --- AI GENERATION HELPER ---
218
+ def _generate_text(messages, max_tokens: int):
219
+ result = ai_generator(messages, max_new_tokens=max_tokens, do_sample=True, temperature=0.3, top_p=0.85)
220
+ return result[0]['generated_text'][-1]['content']
221
+
222
+ async def run_ai_task(messages, max_tokens: int):
223
+ if not ai_generator: return "⚠️ AI Model is starting up."
224
+ async with ai_semaphore:
225
+ return await asyncio.to_thread(_generate_text, messages, max_tokens)
226
 
227
+ # --- 5. FASTAPI APP & ENDPOINTS ---
 
 
228
  @asynccontextmanager
229
  async def lifespan(app: FastAPI):
230
+ # Run setup tasks
231
+ await load_and_index_dataset()
232
+ asyncio.create_task(asyncio.to_thread(load_ai_models))
233
  yield
234
+ await es.close()
235
 
236
+ app = FastAPI(title="Nebulix Search Engine (Powered by Elasticsearch)", lifespan=lifespan)
237
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
238
 
239
+ @app.get("/translate")
240
+ async def translate_api(q: str = Query(...), target: str = Query("en"), source: str = Query("auto")):
241
+ result = await perform_translation(q, target, source)
242
+ return {"status": "success", "data": result} if result else {"status": "error"}
 
 
 
 
 
243
 
244
  @app.get("/search")
245
+ async def pure_search(q: str = Query(...)):
246
+ req_id = str(uuid.uuid4())
247
+ smart_data = await analyze_query_for_apis(q)
248
+ results, prediction, _ = await perform_core_search(q)
249
 
250
+ return {
251
+ "request_id": req_id,
252
+ "query": q,
253
+ "smart_widget": smart_data,
254
+ "results_count": len(results),
255
+ "results": results # Now returns ENTIRE JSON structure including Favicon, Videos, Tag, etc.
256
+ }
257
 
258
  @app.get("/aimode")
259
+ async def ai_mode_short(q: str = Query(...)):
260
+ req_id = str(uuid.uuid4())
261
+ smart_data = await analyze_query_for_apis(q)
262
+ results, _, _ = await perform_core_search(q)
 
 
263
 
264
+ if not results and not smart_data:
265
+ return {"request_id": req_id, "ai_response": "I couldn't find any results.", "results": []}
 
 
 
 
 
266
 
267
+ context = (f"Live Info: {smart_data['content']}. " if smart_data else "") + " ".join([f"{r.get('title', '')} - {r.get('description', '')}" for r in results[:2]])
268
+ messages = [
269
+ {"role": "system", "content": "Provide a brief 2-3 sentence summary based only on the provided context."},
270
+ {"role": "user", "content": f"Query: {q}\nContext: {context}"}
271
+ ]
272
+
273
+ ai_reply = await run_ai_task(messages, max_tokens=150)
274
+ return {"request_id": req_id, "ai_response": ai_reply, "smart_widget": smart_data, "results": results}
 
275
 
276
  @app.get("/aimodechat")
277
+ async def ai_mode_chat(q: str = Query(...)):
278
+ req_id = str(uuid.uuid4())
279
+ smart_data = await analyze_query_for_apis(q)
280
+ results, _, _ = await perform_core_search(q)
281
 
282
+ context_parts = [f"LIVE SYSTEM DATA: {smart_data['content']}"] if smart_data else []
283
+ for i, r in enumerate(results[:5]):
284
+ context_parts.append(f"Source [{i+1}]: {r.get('title')} - {r.get('description')}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ messages = [
287
+ {"role": "system", "content": "You are a Smart AI Search Assistant. Use live data and search sources to answer accurately."},
288
+ {"role": "user", "content": f"User Query: {q}\n\nContext & Sources:\n{chr(10).join(context_parts)}"}
289
+ ]
290
+
291
+ ai_reply = await run_ai_task(messages, max_tokens=400)
292
+ return {"request_id": req_id, "ai_response": ai_reply, "smart_widget": smart_data, "results": results}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
  if __name__ == "__main__":
295
+ uvicorn.run(app, host="0.0.0.0", port=7860)