anurag-chand commited on
Commit
ac48187
Β·
verified Β·
1 Parent(s): d8aa76e

Deploy Vivekananda scriptures semantic search Gradio app

Browse files
Files changed (2) hide show
  1. app.py +326 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import gradio as gr
4
+ import lancedb
5
+ from huggingface_hub import snapshot_download
6
+ from sentence_transformers import SentenceTransformer
7
+
8
+ # 1. Setup paths and download dataset from Hugging Face Hub
9
+ DB_REPO = "anurag-chand/vivekananda-scriptures-lancedb"
10
+ LOCAL_DB_DIR = "./scriptures_lancedb"
11
+
12
+ print(f"Checking for scriptures database locally at {LOCAL_DB_DIR}...")
13
+ if not os.path.exists(LOCAL_DB_DIR) or not os.listdir(LOCAL_DB_DIR):
14
+ print(f"Database not found. Downloading {DB_REPO} from Hugging Face Hub...")
15
+ snapshot_download(
16
+ repo_id=DB_REPO,
17
+ repo_type="dataset",
18
+ local_dir=LOCAL_DB_DIR
19
+ )
20
+ print("Download complete!")
21
+
22
+ # Connect to LanceDB
23
+ db = lancedb.connect(LOCAL_DB_DIR)
24
+ table = db.open_table("scriptures")
25
+ print(f"Connected to scriptures table! Total records: {len(table):,}")
26
+
27
+ # 2. Load model
28
+ print("Loading Krutrim Vyakyarth model...")
29
+ model = SentenceTransformer("krutrim-ai-labs/Vyakyarth", device="cpu")
30
+ print("Model loaded successfully.")
31
+
32
+ # Custom CSS for gorgeous aesthetics (dark mode, glassmorphism, responsive grid)
33
+ custom_css = """
34
+ body {
35
+ background-color: #0d0f12 !important;
36
+ color: #e2e8f0 !important;
37
+ font-family: 'Inter', sans-serif !important;
38
+ }
39
+ .gradio-container {
40
+ max-width: 1100px !important;
41
+ margin: 0 auto !important;
42
+ padding: 20px !important;
43
+ background-color: #0d0f12 !important;
44
+ }
45
+ .header-box {
46
+ text-align: center;
47
+ padding: 30px 20px;
48
+ background: linear-gradient(135deg, rgba(30, 41, 59, 0.5), rgba(15, 23, 42, 0.8));
49
+ border: 1px solid rgba(255, 255, 255, 0.05);
50
+ border-radius: 16px;
51
+ margin-bottom: 30px;
52
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
53
+ }
54
+ .header-box h1 {
55
+ font-size: 2.5em;
56
+ font-weight: 800;
57
+ background: linear-gradient(to right, #ffd700, #ff8c00);
58
+ -webkit-background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
+ margin-bottom: 10px;
61
+ }
62
+ .header-box p {
63
+ color: #94a3b8;
64
+ font-size: 1.1em;
65
+ }
66
+ .search-btn {
67
+ background: linear-gradient(135deg, #ff8c00, #d35400) !important;
68
+ color: white !important;
69
+ font-weight: bold !important;
70
+ border: none !important;
71
+ border-radius: 8px !important;
72
+ transition: all 0.3s ease !important;
73
+ }
74
+ .search-btn:hover {
75
+ transform: translateY(-1px) !important;
76
+ box-shadow: 0 4px 15px rgba(211, 84, 0, 0.4) !important;
77
+ }
78
+ .result-card {
79
+ background: rgba(30, 41, 59, 0.4);
80
+ border: 1px solid rgba(255, 255, 255, 0.06);
81
+ border-radius: 14px;
82
+ padding: 22px;
83
+ margin-bottom: 20px;
84
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
85
+ transition: all 0.3s ease;
86
+ }
87
+ .result-card:hover {
88
+ transform: translateY(-2px);
89
+ border-color: rgba(255, 140, 0, 0.3);
90
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
91
+ }
92
+ .card-header {
93
+ display: flex;
94
+ justify-content: space-between;
95
+ align-items: center;
96
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
97
+ padding-bottom: 10px;
98
+ margin-bottom: 15px;
99
+ flex-wrap: wrap;
100
+ gap: 10px;
101
+ }
102
+ .match-badge {
103
+ background: linear-gradient(135deg, #ff8c00, #e67e22);
104
+ color: white;
105
+ padding: 4px 12px;
106
+ border-radius: 20px;
107
+ font-size: 0.85em;
108
+ font-weight: bold;
109
+ }
110
+ .meta-tags {
111
+ display: flex;
112
+ gap: 8px;
113
+ flex-wrap: wrap;
114
+ }
115
+ .meta-tag {
116
+ background: rgba(255, 255, 255, 0.05);
117
+ border: 1px solid rgba(255, 255, 255, 0.08);
118
+ color: #cbd5e1;
119
+ padding: 3px 10px;
120
+ border-radius: 6px;
121
+ font-size: 0.85em;
122
+ }
123
+ .shloka-section {
124
+ background: rgba(255, 215, 0, 0.03);
125
+ border-left: 4px solid #ffd700;
126
+ padding: 12px 16px;
127
+ margin-bottom: 15px;
128
+ border-radius: 0 8px 8px 0;
129
+ }
130
+ .shloka-text {
131
+ font-size: 1.25em;
132
+ color: #f1c40f;
133
+ line-height: 1.6;
134
+ margin: 0;
135
+ font-weight: bold;
136
+ }
137
+ .translation-section {
138
+ background: rgba(46, 204, 113, 0.03);
139
+ border-left: 4px solid #2ecc71;
140
+ padding: 12px 16px;
141
+ margin-bottom: 15px;
142
+ border-radius: 0 8px 8px 0;
143
+ }
144
+ .translation-text {
145
+ font-size: 1.05em;
146
+ color: #2ecc71;
147
+ line-height: 1.5;
148
+ margin: 0;
149
+ font-style: italic;
150
+ }
151
+ .commentary-section {
152
+ padding-left: 4px;
153
+ }
154
+ .commentary-header {
155
+ font-size: 0.95em;
156
+ font-weight: bold;
157
+ color: #94a3b8;
158
+ margin-bottom: 6px;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.05em;
161
+ }
162
+ .commentary-text {
163
+ font-size: 1.05em;
164
+ color: #cbd5e1;
165
+ line-height: 1.6;
166
+ margin: 0;
167
+ }
168
+ """
169
+
170
+ def semantic_search(query: str, limit: int = 5, min_similarity: float = 0.5) -> str:
171
+ if not query.strip():
172
+ return "<div style='text-align: center; color: #94a3b8; padding: 20px;'>Please enter a search query above.</div>"
173
+
174
+ try:
175
+ # Encode query
176
+ query_vector = model.encode(query).tolist()
177
+
178
+ # Search LanceDB table
179
+ results = table.search(query_vector).limit(limit).to_list()
180
+
181
+ if not results:
182
+ return "<div style='text-align: center; color: #ff6b6b; padding: 20px;'>No results found. Try adjusting your query.</div>"
183
+
184
+ html_output = ""
185
+ for idx, res in enumerate(results):
186
+ # Parse metadata
187
+ meta = {}
188
+ if "metadata" in res and res["metadata"]:
189
+ try:
190
+ meta = json.loads(res["metadata"])
191
+ except Exception:
192
+ pass
193
+
194
+ # Compute similarity from L2 distance
195
+ distance = res.get("_distance", 1.0)
196
+ similarity = max(0.0, 1.0 - (distance / 2.0))
197
+
198
+ if similarity < min_similarity:
199
+ continue
200
+
201
+ source = res.get("source_file") or meta.get("source_file") or "Unknown"
202
+ book = meta.get("book_title") or meta.get("title") or os.path.splitext(source)[0]
203
+ book = str(book).replace("_", " ").strip().title()
204
+
205
+ chapter = meta.get("chapter_title") or meta.get("section") or meta.get("chapter_label") or ""
206
+ chapter = str(chapter).replace("_", " ").strip().title()
207
+
208
+ shloka = meta.get("shloka") or meta.get("sutra") or ""
209
+ translation = meta.get("translation") or ""
210
+ commentary_author = meta.get("commentary_author") or ""
211
+ raw_text = res.get("text", "")
212
+
213
+ # Format shloka block
214
+ shloka_html = ""
215
+ if shloka:
216
+ shloka_formatted = shloka.strip().replace("\n", "<br>")
217
+ shloka_html = f"""
218
+ <div class="shloka-section">
219
+ <p class="shloka-text">{shloka_formatted}</p>
220
+ </div>
221
+ """
222
+
223
+ # Format translation block
224
+ translation_html = ""
225
+ if translation:
226
+ translation_formatted = translation.strip().replace("\n", "<br>")
227
+ translation_html = f"""
228
+ <div class="translation-section">
229
+ <p class="translation-text">{translation_formatted}</p>
230
+ </div>
231
+ """
232
+
233
+ # Format commentary or text chunk
234
+ text_to_print = raw_text.strip()
235
+ if shloka and text_to_print.startswith(shloka):
236
+ # Clean out prepended shloka
237
+ parts = text_to_print.split("Commentary")
238
+ if len(parts) > 1:
239
+ text_to_print = "Commentary" + " ".join(parts[1:])
240
+
241
+ text_formatted = text_to_print.strip().replace("\n", "<br>")
242
+
243
+ comm_title = f"Commentary ({commentary_author})" if commentary_author else "Scripture Text"
244
+
245
+ html_output += f"""
246
+ <div class="result-card">
247
+ <div class="card-header">
248
+ <span class="match-badge">Similarity: {similarity*100:.1f}%</span>
249
+ <div class="meta-tags">
250
+ <span class="meta-tag">πŸ“– {book}</span>
251
+ {f'<span class="meta-tag">πŸ”– {chapter}</span>' if chapter else ''}
252
+ <span class="meta-tag">πŸ“„ {source}</span>
253
+ </div>
254
+ </div>
255
+ {shloka_html}
256
+ {translation_html}
257
+ <div class="commentary-section">
258
+ <div class="commentary-header">{comm_title}</div>
259
+ <p class="commentary-text">{text_formatted}</p>
260
+ </div>
261
+ </div>
262
+ """
263
+
264
+ if not html_output:
265
+ return f"<div style='text-align: center; color: #ff6b6b; padding: 20px;'>No results matched the similarity threshold of {min_similarity*100:.0f}%. Try lowering it.</div>"
266
+
267
+ return html_output
268
+
269
+ except Exception as e:
270
+ return f"<div style='text-align: center; color: #ff6b6b; padding: 20px;'>Error during search: {e}</div>"
271
+
272
+ # 3. Create Gradio Interface Block
273
+ with gr.Blocks(css=custom_css, title="Vivekananda Scriptures Semantic Search") as demo:
274
+ # Header block
275
+ gr.HTML("""
276
+ <div class="header-box">
277
+ <h1>Swami Vivekananda Scriptures Semantic Search</h1>
278
+ <p>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.</p>
279
+ </div>
280
+ """)
281
+
282
+ with gr.Row():
283
+ with gr.Column(scale=4):
284
+ query_input = gr.Textbox(
285
+ label="Search Query",
286
+ placeholder="Type your search here (e.g., liberation from the cycle of birth and death, control of mind, nature of Brahman)...",
287
+ lines=1
288
+ )
289
+ with gr.Column(scale=1):
290
+ search_button = gr.Button("Search", elem_classes=["search-btn"])
291
+
292
+ with gr.Row():
293
+ with gr.Column(scale=1):
294
+ limit_slider = gr.Slider(
295
+ label="Number of Results",
296
+ minimum=1,
297
+ maximum=20,
298
+ value=5,
299
+ step=1
300
+ )
301
+ with gr.Column(scale=1):
302
+ similarity_slider = gr.Slider(
303
+ label="Minimum Similarity Threshold",
304
+ minimum=0.0,
305
+ maximum=1.0,
306
+ value=0.35,
307
+ step=0.05
308
+ )
309
+
310
+ # Outputs block
311
+ results_output = gr.HTML(label="Search Results")
312
+
313
+ # Event binds
314
+ search_button.click(
315
+ fn=semantic_search,
316
+ inputs=[query_input, limit_slider, similarity_slider],
317
+ outputs=results_output
318
+ )
319
+ query_input.submit(
320
+ fn=semantic_search,
321
+ inputs=[query_input, limit_slider, similarity_slider],
322
+ outputs=results_output
323
+ )
324
+
325
+ if __name__ == "__main__":
326
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ lancedb
2
+ sentence-transformers
3
+ huggingface_hub
4
+ gradio
5
+ pandas
6
+ jinja2
7
+ requests
8
+ torch --index-url https://download.pytorch.org/whl/cpu