import os import json import gradio as gr import lancedb import torch # Limit PyTorch CPU threads to 1 to prevent system freezing torch.set_num_threads(1) from huggingface_hub import snapshot_download from sentence_transformers import SentenceTransformer # 1. Setup paths and download dataset from Hugging Face Hub DB_REPO = "anurag-chand/vivekananda-scriptures-lancedb" LOCAL_DB_DIR = "./scriptures_lancedb" print(f"Checking for scriptures database locally at {LOCAL_DB_DIR}...") if not os.path.exists(LOCAL_DB_DIR) or not os.listdir(LOCAL_DB_DIR): print(f"Database not found. Downloading {DB_REPO} from Hugging Face Hub...") snapshot_download( repo_id=DB_REPO, repo_type="dataset", local_dir=LOCAL_DB_DIR ) print("Download complete!") # Connect to LanceDB db = lancedb.connect(LOCAL_DB_DIR) table = db.open_table("scriptures") print(f"Connected to scriptures table! Total records: {len(table):,}") # 2. Load model print("Loading Krutrim Vyakyarth model...") model = SentenceTransformer("krutrim-ai-labs/Vyakyarth", device="cpu") print("Model loaded successfully.") # Custom CSS for gorgeous aesthetics (dark mode, glassmorphism, responsive grid) custom_css = """ .header-box { text-align: center; padding: 30px 20px; background: linear-gradient(135deg, rgba(30, 41, 59, 0.5), rgba(15, 23, 42, 0.8)); border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 16px; margin-bottom: 30px; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); } .header-box h1 { font-size: 2.5em; font-weight: 800; background: linear-gradient(to right, #ffd700, #ff8c00); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; } .header-box p { color: #94a3b8; font-size: 1.1em; } .search-btn { background: linear-gradient(135deg, #ff8c00, #d35400) !important; color: white !important; font-weight: bold !important; border: none !important; border-radius: 8px !important; transition: all 0.3s ease !important; } .search-btn:hover { transform: translateY(-1px) !important; box-shadow: 0 4px 15px rgba(211, 84, 0, 0.4) !important; } .result-card { background: rgba(30, 41, 59, 0.4); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 14px; padding: 22px; margin-bottom: 20px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); transition: all 0.3s ease; } .result-card:hover { transform: translateY(-2px); border-color: rgba(255, 140, 0, 0.3); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3); } .card-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255, 255, 255, 0.06); padding-bottom: 10px; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; } .match-badge { background: linear-gradient(135deg, #ff8c00, #e67e22); color: white; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: bold; } .meta-tags { display: flex; gap: 8px; flex-wrap: wrap; } .meta-tag { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); color: #cbd5e1; padding: 3px 10px; border-radius: 6px; font-size: 0.85em; } .shloka-section { background: rgba(255, 215, 0, 0.03); border-left: 4px solid #ffd700; padding: 12px 16px; margin-bottom: 15px; border-radius: 0 8px 8px 0; } .shloka-text { font-size: 1.25em; color: #f1c40f; line-height: 1.6; margin: 0; font-weight: bold; } .translation-section { background: rgba(46, 204, 113, 0.03); border-left: 4px solid #2ecc71; padding: 12px 16px; margin-bottom: 15px; border-radius: 0 8px 8px 0; } .translation-text { font-size: 1.05em; color: #2ecc71; line-height: 1.5; margin: 0; font-style: italic; } .commentary-section { padding-left: 4px; } .commentary-header { font-size: 0.95em; font-weight: bold; color: #94a3b8; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em; } .commentary-text { font-size: 1.05em; color: #cbd5e1; line-height: 1.6; margin: 0; } """ def semantic_search(query: str, limit: int = 5, min_similarity: float = 0.5) -> str: if not query.strip(): return "
Please enter a search query above.
" try: # Encode query query_vector = model.encode(query).tolist() # Search LanceDB table with Cosine Similarity results = table.search(query_vector).metric("cosine").limit(limit).to_list() if not results: return "
No results found. Try adjusting your query.
" html_output = "" for idx, res in enumerate(results): # Parse metadata meta = {} if "metadata" in res and res["metadata"]: try: meta = json.loads(res["metadata"]) except Exception: pass # Compute similarity from Cosine distance distance = res.get("_distance", 1.0) similarity = max(0.0, 1.0 - distance) if similarity < min_similarity: continue source = res.get("source_file") or meta.get("source_file") or "Unknown" book = meta.get("book_title") or meta.get("title") or os.path.splitext(source)[0] book = str(book).replace("_", " ").strip().title() chapter = meta.get("chapter_title") or meta.get("section") or meta.get("chapter_label") or "" chapter = str(chapter).replace("_", " ").strip().title() shloka = meta.get("shloka") or meta.get("sutra") or "" translation = meta.get("translation") or "" commentary_author = meta.get("commentary_author") or "" raw_text = res.get("text", "") # Format shloka block shloka_html = "" if shloka: shloka_formatted = shloka.strip().replace("\n", "
") shloka_html = f"""

{shloka_formatted}

""" # Format translation block translation_html = "" if translation: translation_formatted = translation.strip().replace("\n", "
") translation_html = f"""

{translation_formatted}

""" # Format raw text chunk exactly as stored in database text_formatted = raw_text.strip().replace("\n", "
") comm_title = "Full Scripture & Commentary Chunk" html_output += f"""
Similarity: {similarity*100:.1f}%
📖 {book} {f'🔖 {chapter}' if chapter else ''} 📄 {source}
{shloka_html} {translation_html}
{comm_title}

{text_formatted}

""" if not html_output: return f"
No results matched the similarity threshold of {min_similarity*100:.0f}%. Try lowering it.
" return html_output except Exception as e: return f"
Error during search: {e}
" # 3. Create Gradio Interface Block with stunning native dark theme with gr.Blocks(theme=gr.themes.Default(primary_hue="amber", secondary_hue="orange", neutral_hue="slate", dark_mode=True), css=custom_css, title="Vivekananda Scriptures Semantic Search") as demo: # Header block gr.HTML("""

Swami Vivekananda Scriptures Semantic Search

Search over 686,000+ chunks of Sanskrit scriptures, translations, and commentaries (Patanjali Yoga Sutras, Gaudapada Karika, Vivekananda lectures, and more) using high-precision SOTA semantic vector matching.

""") with gr.Row(): with gr.Column(scale=4): query_input = gr.Textbox( label="Search Query", placeholder="Type your search here (e.g., liberation from the cycle of birth and death, control of mind, nature of Brahman)...", lines=1 ) with gr.Column(scale=1): search_button = gr.Button("Search", elem_classes=["search-btn"]) with gr.Row(): with gr.Column(scale=1): limit_slider = gr.Slider( label="Number of Results", minimum=1, maximum=20, value=5, step=1 ) with gr.Column(scale=1): similarity_slider = gr.Slider( label="Minimum Similarity Threshold", minimum=0.0, maximum=1.0, value=0.35, step=0.05 ) # Outputs block results_output = gr.HTML(label="Search Results") # Event binds search_button.click( fn=semantic_search, inputs=[query_input, limit_slider, similarity_slider], outputs=results_output ) query_input.submit( fn=semantic_search, inputs=[query_input, limit_slider, similarity_slider], outputs=results_output ) if __name__ == "__main__": demo.launch()