ST-x-Tony commited on
Commit
1dec737
·
verified ·
1 Parent(s): 48adbca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +367 -228
app.py CHANGED
@@ -1,39 +1,36 @@
1
  import os
2
- import json
3
  import asyncio
 
 
4
  import gradio as gr
5
 
6
  from web_search import XrudraWebSearch
7
 
8
 
9
- # ============================================================
10
- # X-RUDRA GRADIO APP
11
- # ============================================================
12
-
13
- VERSION = "3.0.0"
14
-
15
  ENGINE = XrudraWebSearch()
16
 
17
 
18
  # ============================================================
19
- # ASYNC SEARCH
20
  # ============================================================
21
 
22
- async def run_search(
23
- question: str,
24
- max_results: int,
25
- max_rounds: int,
26
- use_models: bool,
27
- freshness: str,
28
  ):
29
  if not question or not question.strip():
30
  return (
31
- " Please enter a question.",
32
- "",
33
- "",
34
  "",
35
  )
36
 
 
 
37
  try:
38
  report = await ENGINE.search(
39
  question=question.strip(),
@@ -49,132 +46,161 @@ async def run_search(
49
  else report
50
  )
51
 
 
 
 
 
52
  # ----------------------------------------------------
53
- # Sources
54
  # ----------------------------------------------------
55
 
56
- sources = data.get("sources", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- source_lines = []
59
 
60
  for i, source in enumerate(sources, 1):
61
- title = source.get("title", "Untitled")
62
  url = source.get("url", "")
63
  method = source.get("fetch_method", "unknown")
64
  score = source.get("source_score", 0)
65
 
66
- source_lines.append(
67
- f"### {i}. {title}\n"
68
- f"**URL:** {url}\n"
69
- f"**Fetch:** `{method}`\n"
70
- f"**Score:** `{score}`\n"
71
  )
72
 
73
- source_text = "\n\n".join(source_lines)
 
74
 
75
  # ----------------------------------------------------
76
- # Claims
77
  # ----------------------------------------------------
78
 
79
- claims = data.get("claims", [])
80
-
81
- claim_lines = []
82
-
83
- for i, claim in enumerate(claims, 1):
84
- claim_text = claim.get("claim", "")
85
- url = claim.get("source_url", "")
86
- score = claim.get("support_score", 0)
87
 
88
- claim_lines.append(
89
- f"### Claim {i}\n"
90
- f"{claim_text}\n\n"
91
- f"Source: `{url}`\n"
92
- f"Support: `{score}`"
 
 
 
93
  )
94
 
95
- claim_text = "\n\n".join(claim_lines)
 
96
 
97
  # ----------------------------------------------------
98
- # Contradictions
99
  # ----------------------------------------------------
100
 
101
- contradictions = data.get(
102
- "contradictions",
103
- [],
104
- )
105
-
106
  if contradictions:
107
- contradiction_text = "\n\n".join(
108
- [
109
- (
110
- f"### Possible contradiction {i}\n\n"
111
- f"**A:** {x.get('claim_a', '')}\n\n"
112
- f"Source A: {x.get('source_a', '')}\n\n"
113
- f"**B:** {x.get('claim_b', '')}\n\n"
114
- f"Source B: {x.get('source_b', '')}"
115
- )
116
- for i, x in enumerate(
117
- contradictions,
118
- 1,
119
- )
120
- ]
121
- )
122
  else:
123
- contradiction_text = (
 
124
  "No major automatic contradictions detected."
125
  )
126
 
127
  # ----------------------------------------------------
128
- # Full JSON
129
  # ----------------------------------------------------
130
 
131
- json_text = json.dumps(
132
- data,
133
- ensure_ascii=False,
134
- indent=2,
135
- )
136
-
137
- # ----------------------------------------------------
138
- # Summary
139
- # ----------------------------------------------------
140
-
141
- summary = (
142
- f"## X-RUDRA Research Complete\n\n"
143
- f"**Question:** {question}\n\n"
144
- f"**Rounds:** {data.get('rounds', 0)}\n\n"
145
- f"**Sources:** {len(sources)}\n\n"
146
- f"**Claims:** {len(claims)}\n\n"
147
- f"**Stopping reason:** "
148
- f"{data.get('stopping_reason', 'unknown')}\n\n"
149
- f"**Models:** "
150
- f"`Shrijanagain/M1` + `Shrijanagain/M2`\n\n"
151
- f"**Web:** DuckDuckGo + Scrapling + Playwright"
152
- )
153
 
154
  return (
155
- summary,
156
- source_text or "No sources returned.",
157
- claim_text or "No claims extracted.",
158
- contradiction_text + "\n\n---\n\n```json\n"
159
- + json_text
160
- + "\n```",
161
  )
162
 
163
  except Exception as exc:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  return (
165
- f"## Search failed\n\n"
166
- f"`{type(exc).__name__}: {exc}`",
167
- "",
168
  "",
169
  "",
170
  )
171
 
172
 
173
- # ============================================================
174
- # SYNC WRAPPER FOR GRADIO
175
- # ============================================================
176
-
177
- def search_wrapper(
178
  question,
179
  max_results,
180
  max_rounds,
@@ -182,7 +208,7 @@ def search_wrapper(
182
  freshness,
183
  ):
184
  return asyncio.run(
185
- run_search(
186
  question,
187
  max_results,
188
  max_rounds,
@@ -193,195 +219,308 @@ def search_wrapper(
193
 
194
 
195
  # ============================================================
196
- # HEALTH
197
  # ============================================================
198
 
199
- def health():
200
- return (
201
- "## X-RUDRA\n\n"
202
- f"Version: `{VERSION}`\n\n"
203
- "M1: `Shrijanagain/M1`\n\n"
204
- "M2: `Shrijanagain/M2`\n\n"
205
- "Web Search: `DuckDuckGo`\n\n"
206
- "Fetcher: `Scrapling`\n\n"
207
- "Browser: `Playwright`\n\n"
208
- "Evidence Engine: `Enabled`"
209
- )
210
 
 
 
 
211
 
212
- # ============================================================
213
- # GRADIO UI
214
- # ============================================================
 
215
 
216
- CSS = """
217
- #title {
218
  text-align: center;
219
- font-size: 42px;
 
 
 
 
220
  font-weight: 800;
 
221
  }
222
 
223
- #subtitle {
224
- text-align: center;
225
- opacity: 0.75;
 
 
 
 
 
 
 
 
226
  }
227
 
228
- .search-btn {
229
- min-height: 55px;
230
- font-size: 18px;
231
- font-weight: 700;
 
 
 
 
 
 
 
 
232
  }
233
  """
234
 
235
 
 
 
 
 
236
  with gr.Blocks(
237
  title="X-RUDRA",
238
  css=CSS,
 
 
 
 
239
  ) as demo:
240
 
241
- gr.Markdown(
242
- "# ⚡ X-RUDRA",
243
- elem_id="title",
244
- )
245
 
246
- gr.Markdown(
247
- "Dual-model AI research + live web intelligence",
248
- elem_id="subtitle",
249
- )
250
-
251
- gr.Markdown(
 
 
252
  """
253
- **M1 + M2 → Query Planning → DuckDuckGo → Scrapling
254
- → Playwright → Evidence → Cross-check**
255
- """
256
  )
257
 
 
 
 
 
258
  with gr.Row():
259
 
260
- with gr.Column(scale=4):
 
 
 
 
 
 
261
 
262
- question = gr.Textbox(
263
- label="Research Question",
 
 
 
264
  placeholder=(
265
- "Ask anything that requires current web research..."
 
266
  ),
267
- lines=5,
268
  )
269
 
270
  with gr.Row():
271
 
272
- max_results = gr.Slider(
273
- minimum=1,
274
- maximum=30,
275
- value=10,
276
- step=1,
277
- label="Max Sources",
 
278
  )
279
 
280
- max_rounds = gr.Slider(
281
- minimum=1,
282
- maximum=5,
283
- value=3,
284
- step=1,
285
- label="Research Rounds",
286
  )
287
 
288
- with gr.Row():
 
 
289
 
290
- use_models = gr.Checkbox(
291
- value=True,
292
- label="Use M1 + M2",
293
- )
294
 
295
- freshness = gr.Dropdown(
296
- choices=[
297
- "auto",
298
- "latest",
299
- "recent",
300
- "current",
301
- ],
302
- value="auto",
303
- label="Freshness",
304
- )
305
 
306
- search_button = gr.Button(
307
- "SEARCH WITH X-RUDRA",
308
- variant="primary",
309
- elem_classes=["search-btn"],
310
  )
311
 
312
- health_button = gr.Button(
313
- "Check Engine Health",
314
  )
315
 
316
- with gr.Column(scale=6):
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- summary = gr.Markdown(
319
- label="Research Summary",
 
 
 
 
 
 
 
 
 
 
 
 
320
  )
321
 
322
- with gr.Tab("Sources"):
 
 
323
 
324
- sources = gr.Markdown(
325
- "Sources will appear here."
326
- )
 
327
 
328
- with gr.Tab(" Evidence / Claims"):
329
 
330
- claims = gr.Markdown(
331
- "Evidence will appear here."
332
- )
 
 
 
 
333
 
334
- with gr.Tab("⚠️ Contradictions + Raw JSON"):
 
 
 
 
 
 
335
 
336
- contradictions = gr.Markdown(
337
- "Contradictions and raw response will appear here."
338
- )
339
 
340
- health_output = gr.Markdown()
 
 
 
341
 
342
- search_button.click(
343
- fn=search_wrapper,
344
- inputs=[
345
- question,
346
- max_results,
347
- max_rounds,
348
- use_models,
349
- freshness,
350
- ],
351
- outputs=[
352
- summary,
353
- sources,
354
- claims,
355
- contradictions,
356
- ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  )
358
 
359
- question.submit(
360
- fn=search_wrapper,
361
- inputs=[
362
- question,
363
- max_results,
364
- max_rounds,
365
- use_models,
366
- freshness,
367
- ],
368
- outputs=[
369
- summary,
370
- sources,
371
- claims,
372
- contradictions,
373
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  )
375
 
376
- health_button.click(
377
- fn=health,
378
- inputs=[],
379
- outputs=[health_output],
380
  )
381
 
382
 
383
  # ============================================================
384
- # LAUNCH
385
  # ============================================================
386
 
387
  if __name__ == "__main__":
 
1
  import os
 
2
  import asyncio
3
+ import json
4
+ import time
5
  import gradio as gr
6
 
7
  from web_search import XrudraWebSearch
8
 
9
 
 
 
 
 
 
 
10
  ENGINE = XrudraWebSearch()
11
 
12
 
13
  # ============================================================
14
+ # RESEARCH ENGINE
15
  # ============================================================
16
 
17
+ async def research(
18
+ question,
19
+ max_results,
20
+ max_rounds,
21
+ use_models,
22
+ freshness,
23
  ):
24
  if not question or not question.strip():
25
  return (
26
+ [],
27
+ "⚪ Waiting for a question...",
28
+ "No research started.",
29
  "",
30
  )
31
 
32
+ start = time.perf_counter()
33
+
34
  try:
35
  report = await ENGINE.search(
36
  question=question.strip(),
 
46
  else report
47
  )
48
 
49
+ sources = data.get("sources", [])
50
+ claims = data.get("claims", [])
51
+ contradictions = data.get("contradictions", [])
52
+
53
  # ----------------------------------------------------
54
+ # FINAL ANSWER
55
  # ----------------------------------------------------
56
 
57
+ answer_parts = []
58
+
59
+ for claim in claims[:8]:
60
+ text = claim.get("claim", "").strip()
61
+
62
+ if text:
63
+ answer_parts.append(text)
64
+
65
+ if answer_parts:
66
+ answer = "\n\n".join(answer_parts)
67
+ else:
68
+ answer = (
69
+ "Research completed, but no high-confidence "
70
+ "evidence passages were extracted."
71
+ )
72
+
73
+ # ----------------------------------------------------
74
+ # ACTIVITY
75
+ # ----------------------------------------------------
76
+
77
+ activity = f"""
78
+ ### ⚡ X-RUDRA Research Pipeline
79
+
80
+ ✓ Task analyzed
81
+ ✓ M1 + M2 query planning completed
82
+ ✓ DuckDuckGo web discovery completed
83
+ ✓ {len(sources)} sources fetched
84
+ ✓ Evidence extraction completed
85
+ ✓ Source quality evaluated
86
+ ✓ Cross-source verification completed
87
+ {"⚠️ Contradictions detected" if contradictions else "✓ No major contradictions detected"}
88
+ ✓ Final synthesis completed
89
+
90
+ **Research time:** {int((time.perf_counter() - start) * 1000)} ms
91
+ """
92
+
93
+ # ----------------------------------------------------
94
+ # SOURCES
95
+ # ----------------------------------------------------
96
 
97
+ source_md = "### 📚 Sources\n\n"
98
 
99
  for i, source in enumerate(sources, 1):
100
+ title = source.get("title") or "Untitled"
101
  url = source.get("url", "")
102
  method = source.get("fetch_method", "unknown")
103
  score = source.get("source_score", 0)
104
 
105
+ source_md += (
106
+ f"**{i}. [{title}]({url})**\n\n"
107
+ f"`{method}` · source score `{score}`\n\n"
 
 
108
  )
109
 
110
+ if not sources:
111
+ source_md += "No sources returned."
112
 
113
  # ----------------------------------------------------
114
+ # EVIDENCE
115
  # ----------------------------------------------------
116
 
117
+ evidence_md = "### 🧠 Evidence\n\n"
 
 
 
 
 
 
 
118
 
119
+ for i, claim in enumerate(claims[:20], 1):
120
+ evidence_md += (
121
+ f"**Evidence {i}**\n\n"
122
+ f"{claim.get('claim', '')}\n\n"
123
+ f"Support score: "
124
+ f"`{claim.get('support_score', 0)}`\n\n"
125
+ f"Source: `{claim.get('source_url', '')}`\n\n"
126
+ "---\n\n"
127
  )
128
 
129
+ if not claims:
130
+ evidence_md += "No evidence extracted."
131
 
132
  # ----------------------------------------------------
133
+ # CONTRADICTIONS
134
  # ----------------------------------------------------
135
 
 
 
 
 
 
136
  if contradictions:
137
+ verification_md = "### ⚠️ Contradictions\n\n"
138
+
139
+ for i, item in enumerate(
140
+ contradictions,
141
+ 1,
142
+ ):
143
+ verification_md += (
144
+ f"**Potential contradiction {i}**\n\n"
145
+ f"**Source A:**\n"
146
+ f"{item.get('claim_a', '')}\n\n"
147
+ f"**Source B:**\n"
148
+ f"{item.get('claim_b', '')}\n\n"
149
+ "---\n\n"
150
+ )
 
151
  else:
152
+ verification_md = (
153
+ "### ✅ Verification\n\n"
154
  "No major automatic contradictions detected."
155
  )
156
 
157
  # ----------------------------------------------------
158
+ # RETURN CHAT + PANELS
159
  # ----------------------------------------------------
160
 
161
+ chat = [
162
+ {
163
+ "role": "user",
164
+ "content": question,
165
+ },
166
+ {
167
+ "role": "assistant",
168
+ "content": answer,
169
+ },
170
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  return (
173
+ chat,
174
+ activity,
175
+ source_md,
176
+ evidence_md + "\n" + verification_md,
 
 
177
  )
178
 
179
  except Exception as exc:
180
+
181
+ chat = [
182
+ {
183
+ "role": "user",
184
+ "content": question,
185
+ },
186
+ {
187
+ "role": "assistant",
188
+ "content": (
189
+ "❌ X-RUDRA encountered an error:\n\n"
190
+ f"`{type(exc).__name__}: {exc}`"
191
+ ),
192
+ },
193
+ ]
194
+
195
  return (
196
+ chat,
197
+ " Research failed.",
 
198
  "",
199
  "",
200
  )
201
 
202
 
203
+ def run_research(
 
 
 
 
204
  question,
205
  max_results,
206
  max_rounds,
 
208
  freshness,
209
  ):
210
  return asyncio.run(
211
+ research(
212
  question,
213
  max_results,
214
  max_rounds,
 
219
 
220
 
221
  # ============================================================
222
+ # CSS
223
  # ============================================================
224
 
225
+ CSS = r"""
226
+ :root {
227
+ --radius: 18px;
228
+ }
 
 
 
 
 
 
 
229
 
230
+ body {
231
+ background: #f7f7f8 !important;
232
+ }
233
 
234
+ .gradio-container {
235
+ max-width: 1500px !important;
236
+ margin: auto !important;
237
+ }
238
 
239
+ #header {
 
240
  text-align: center;
241
+ padding: 18px 0 8px 0;
242
+ }
243
+
244
+ #logo {
245
+ font-size: 34px;
246
  font-weight: 800;
247
+ letter-spacing: -1px;
248
  }
249
 
250
+ #tagline {
251
+ opacity: .65;
252
+ font-size: 14px;
253
+ }
254
+
255
+ #chat {
256
+ border-radius: 18px !important;
257
+ }
258
+
259
+ #research-panel {
260
+ border-radius: 18px !important;
261
  }
262
 
263
+ #search-button {
264
+ border-radius: 14px !important;
265
+ min-height: 50px !important;
266
+ font-weight: 700 !important;
267
+ }
268
+
269
+ .status-box {
270
+ border-radius: 14px !important;
271
+ }
272
+
273
+ footer {
274
+ display: none !important;
275
  }
276
  """
277
 
278
 
279
+ # ============================================================
280
+ # UI
281
+ # ============================================================
282
+
283
  with gr.Blocks(
284
  title="X-RUDRA",
285
  css=CSS,
286
+ theme=gr.themes.Soft(
287
+ primary_hue="indigo",
288
+ neutral_hue="slate",
289
+ ),
290
  ) as demo:
291
 
292
+ # --------------------------------------------------------
293
+ # HEADER
294
+ # --------------------------------------------------------
 
295
 
296
+ gr.HTML(
297
+ """
298
+ <div id="header">
299
+ <div id="logo">⚡ X-RUDRA</div>
300
+ <div id="tagline">
301
+ Dual-Model AI Research · Live Web Intelligence · Evidence
302
+ </div>
303
+ </div>
304
  """
 
 
 
305
  )
306
 
307
+ # --------------------------------------------------------
308
+ # MAIN
309
+ # --------------------------------------------------------
310
+
311
  with gr.Row():
312
 
313
+ # ====================================================
314
+ # CHAT
315
+ # ====================================================
316
+
317
+ with gr.Column(
318
+ scale=7,
319
+ ):
320
 
321
+ chatbot = gr.Chatbot(
322
+ label="X-RUDRA",
323
+ type="messages",
324
+ height=620,
325
+ elem_id="chat",
326
  placeholder=(
327
+ "Ask X-RUDRA anything that needs "
328
+ "real-time research..."
329
  ),
 
330
  )
331
 
332
  with gr.Row():
333
 
334
+ question = gr.Textbox(
335
+ placeholder=(
336
+ "Ask a research question..."
337
+ ),
338
+ lines=2,
339
+ scale=8,
340
+ show_label=False,
341
  )
342
 
343
+ send = gr.Button(
344
+ "➤",
345
+ variant="primary",
346
+ scale=1,
347
+ elem_id="search-button",
 
348
  )
349
 
350
+ # ====================================================
351
+ # LIVE RESEARCH PANEL
352
+ # ====================================================
353
 
354
+ with gr.Column(
355
+ scale=4,
356
+ ):
 
357
 
358
+ gr.Markdown(
359
+ "## 🔬 Live Research"
360
+ )
 
 
 
 
 
 
 
361
 
362
+ activity = gr.Markdown(
363
+ " Waiting for research..."
 
 
364
  )
365
 
366
+ gr.Markdown(
367
+ "---"
368
  )
369
 
370
+ gr.Markdown(
371
+ "### ⚙️ Engine"
372
+ )
373
+
374
+ gr.Markdown(
375
+ """
376
+ **Models**
377
+
378
+ `M1` · `Shrijanagain/M1`
379
+
380
+ `M2` · `Shrijanagain/M2`
381
+
382
+ **Search**
383
 
384
+ `DuckDuckGo`
385
+
386
+ **Fetching**
387
+
388
+ `Scrapling`
389
+
390
+ **Dynamic pages**
391
+
392
+ `Playwright`
393
+
394
+ **Verification**
395
+
396
+ `Evidence Engine`
397
+ """
398
  )
399
 
400
+ # --------------------------------------------------------
401
+ # CONTROLS
402
+ # --------------------------------------------------------
403
 
404
+ with gr.Accordion(
405
+ "⚙️ Research Controls",
406
+ open=False,
407
+ ):
408
 
409
+ with gr.Row():
410
 
411
+ max_results = gr.Slider(
412
+ minimum=1,
413
+ maximum=30,
414
+ value=10,
415
+ step=1,
416
+ label="Maximum Sources",
417
+ )
418
 
419
+ max_rounds = gr.Slider(
420
+ minimum=1,
421
+ maximum=5,
422
+ value=3,
423
+ step=1,
424
+ label="Research Rounds",
425
+ )
426
 
427
+ with gr.Row():
 
 
428
 
429
+ use_models = gr.Checkbox(
430
+ value=True,
431
+ label="Use M1 + M2",
432
+ )
433
 
434
+ freshness = gr.Dropdown(
435
+ choices=[
436
+ "auto",
437
+ "latest",
438
+ "recent",
439
+ "current",
440
+ ],
441
+ value="auto",
442
+ label="Freshness",
443
+ )
444
+
445
+ # --------------------------------------------------------
446
+ # RESEARCH DETAILS
447
+ # --------------------------------------------------------
448
+
449
+ with gr.Tabs():
450
+
451
+ with gr.Tab(
452
+ "📚 Sources",
453
+ ):
454
+ sources = gr.Markdown(
455
+ "Sources will appear after research."
456
+ )
457
+
458
+ with gr.Tab(
459
+ "🧠 Evidence",
460
+ ):
461
+ evidence = gr.Markdown(
462
+ "Evidence will appear after research."
463
+ )
464
+
465
+ with gr.Tab(
466
+ "⚖️ Verification",
467
+ ):
468
+ verification = gr.Markdown(
469
+ "Verification will appear after research."
470
+ )
471
+
472
+ # --------------------------------------------------------
473
+ # EXAMPLES
474
+ # --------------------------------------------------------
475
+
476
+ gr.Markdown(
477
+ "### Try X-RUDRA"
478
  )
479
 
480
+ gr.Examples(
481
+ examples=[
482
+ ["What are the latest AI education initiatives by UNESCO?"],
483
+ ["What happened in AI research this week?"],
484
+ ["Compare the latest open-source AI models."],
485
+ ["Find reliable evidence about India's AI ecosystem."],
 
 
 
 
 
 
 
 
486
  ],
487
+ inputs=question,
488
+ )
489
+
490
+ # --------------------------------------------------------
491
+ # EVENTS
492
+ # --------------------------------------------------------
493
+
494
+ inputs = [
495
+ question,
496
+ max_results,
497
+ max_rounds,
498
+ use_models,
499
+ freshness,
500
+ ]
501
+
502
+ outputs = [
503
+ chatbot,
504
+ activity,
505
+ sources,
506
+ evidence,
507
+ ]
508
+
509
+ send.click(
510
+ fn=run_research,
511
+ inputs=inputs,
512
+ outputs=outputs,
513
  )
514
 
515
+ question.submit(
516
+ fn=run_research,
517
+ inputs=inputs,
518
+ outputs=outputs,
519
  )
520
 
521
 
522
  # ============================================================
523
+ # HF SPACES LAUNCH
524
  # ============================================================
525
 
526
  if __name__ == "__main__":