Sathvik-kota commited on
Commit
5cd479f
·
verified ·
1 Parent(s): ef1766e

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. src/ui/streamlit_app.py +174 -98
src/ui/streamlit_app.py CHANGED
@@ -29,7 +29,7 @@ API_GATEWAY_URL = "http://localhost:8000"
29
  st.set_page_config(
30
  page_title="Doc-Fetch",
31
  layout="wide",
32
- initial_sidebar_state="expanded",
33
  )
34
 
35
  # =======================
@@ -37,47 +37,55 @@ st.set_page_config(
37
  # =======================
38
  st.markdown("""
39
  <style>
 
40
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
41
 
42
  html, body, [class*="css"] {
43
  font-family: 'Inter', sans-serif;
44
- background-color: #ffffff;
45
- color: #1f1f1f;
46
  }
47
 
48
- /* Input Field Fix */
 
49
  .stTextInput > div[data-baseweb="input"] {
50
  background-color: transparent !important;
51
  border: none !important;
52
  border-radius: 24px !important;
53
  box-shadow: none !important;
54
  }
 
 
55
  .stTextInput input {
56
  border-radius: 24px !important;
57
- background-color: #f0f4f9 !important;
58
  border: 1px solid transparent !important;
59
  color: #1f1f1f !important;
60
  padding: 12px 20px !important;
61
  font-size: 16px !important;
62
  transition: all 0.2s ease;
63
  }
 
 
64
  .stTextInput input:focus {
65
  background-color: #ffffff !important;
66
- border-color: #0b57d0 !important;
67
  box-shadow: 0 0 0 2px rgba(11, 87, 208, 0.2) !important;
68
  outline: none !important;
69
  }
70
 
71
- /* Buttons */
72
  .stButton > button {
73
  border-radius: 20px;
74
  font-weight: 500;
75
  border: none;
76
  padding: 0.5rem 1.5rem;
77
  transition: all 0.3s ease;
78
- white-space: nowrap;
79
- min-width: 140px;
80
  }
 
 
81
  button[kind="primary"] {
82
  background: linear-gradient(90deg, #4b90ff, #ff5546);
83
  color: white;
@@ -87,18 +95,60 @@ st.markdown("""
87
  box-shadow: 0 4px 12px rgba(75, 144, 255, 0.3);
88
  }
89
 
90
- /* Result Cards */
91
  .result-card {
92
- background-color: #f0f4f9;
93
  border-radius: 16px;
94
  padding: 1.5rem;
95
  margin-bottom: 1rem;
 
 
96
  }
97
  .result-card:hover {
98
  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
99
  }
100
 
101
- /* Gradient Header */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  .gradient-text {
103
  background: linear-gradient(to right, #4285f4, #9b72cb, #d96570);
104
  -webkit-background-clip: text;
@@ -106,12 +156,17 @@ st.markdown("""
106
  font-weight: 700;
107
  font-size: 3rem;
108
  }
 
 
 
 
 
 
109
  </style>
110
  """, unsafe_allow_html=True)
111
 
112
-
113
  # =======================
114
- # SIDEBAR
115
  # =======================
116
  with st.sidebar:
117
  st.markdown("### ⚙️ Settings")
@@ -121,63 +176,44 @@ with st.sidebar:
121
  st.subheader(" Evaluation")
122
  run_eval = st.button("Run Evaluation Script")
123
  st.divider()
124
- st.caption("Semantic Search · Smart Cache · FAISS Retrieval · Multi-Service Architecture · MiniLM Embeddings · LLM Reasoning.")
125
 
126
  API_GATEWAY_URL = url_input
127
 
128
-
129
  # =======================
130
- # MAIN HEADER
131
  # =======================
132
  col1, col2, col3 = st.columns([1, 6, 1])
133
  with col2:
 
134
  st.markdown('<div style="text-align: center; margin-bottom: 10px;"><span class="gradient-text">Hello, Explorer</span></div>', unsafe_allow_html=True)
135
  st.markdown('<div style="text-align: center; color: #444746; font-size: 1.2rem; margin-bottom: 30px;">How can I help you find documents today?</div>', unsafe_allow_html=True)
136
 
137
 
138
  # =======================
139
- # SEARCH BAR
140
  # =======================
 
141
  sc1, sc2, sc3 = st.columns([1, 4, 1])
142
 
143
- # =======================
144
- # DISABLE AUTOCOMPLETE (YOUR PATCH)
145
- # =======================
146
- st.markdown("""
147
- <style>
148
- input[type=text] { autocomplete: off !important; }
149
- </style>
150
-
151
- <script>
152
- document.addEventListener('DOMContentLoaded', function() {
153
- const inputs = window.parent.document.querySelectorAll('input[type="text"]');
154
- inputs.forEach(inp => {
155
- inp.setAttribute('autocomplete', 'off');
156
- inp.setAttribute('autocorrect', 'off');
157
- inp.setAttribute('autocapitalize', 'off');
158
- inp.setAttribute('spellcheck', 'false');
159
- });
160
- });
161
- </script>
162
- """, unsafe_allow_html=True)
163
-
164
  with sc2:
165
  query = st.text_input(
166
- "Search Query",
167
  placeholder="Ask a question about your documents...",
168
  label_visibility="collapsed"
169
  )
170
-
 
171
  b1, b2, b3 = st.columns([2, 1, 2])
172
  with b2:
173
  submit_btn = st.button("Sparkle Search", type="primary", use_container_width=True)
174
 
175
-
176
  # =======================
177
  # SEARCH HANDLER
178
  # =======================
179
  if submit_btn and query.strip():
180
 
 
181
  with st.spinner("✨ Analyzing semantics..."):
182
 
183
  response = requests.post(
@@ -189,18 +225,23 @@ if submit_btn and query.strip():
189
  st.error(f"❌ Connection Error: {response.text}")
190
  st.stop()
191
 
192
- data = response.json()
 
 
 
 
193
 
194
  if "results" not in data:
195
- st.info("No relevant documents found.")
196
  st.stop()
197
 
 
198
  st.markdown("### ✨ Search Results")
199
  st.markdown("---")
200
 
201
- # ---------------------
202
- # RESULT CARDS
203
- # ---------------------
204
  for item in data["results"]:
205
  filename = item["filename"]
206
  score = item["score"]
@@ -209,102 +250,134 @@ if submit_btn and query.strip():
209
  full_text = item["full_text"]
210
 
211
  safe_preview = html.escape(preview)
212
-
 
213
  keywords = explanation.get("keyword_overlap", [])
214
- keyword_html = "".join([f"<span class='keyword-pill'>{kw}</span>" for kw in keywords])
215
-
 
 
 
 
 
 
216
  st.markdown(f"""
217
  <div class="result-card">
218
- <div style="display:flex; justify-content:space-between;">
219
  <div class="card-title">
220
- 📄 {filename}
221
  </div>
222
  <div class="score-badge">match: {score:.4f}</div>
223
  </div>
224
  <p class="card-preview">{safe_preview}...</p>
225
- <div>{keyword_html}</div>
 
 
 
 
 
226
  </div>
227
  """, unsafe_allow_html=True)
228
 
229
- # ---- DETAILS EXPANDER ----
230
  with st.expander(f"View Document Insights: Semantic Overlap, Top Sentences, LLM Reasoning & Full Text for {filename}"):
231
-
232
  overlap_ratio = explanation.get("overlap_ratio", 0)
233
  sentences = explanation.get("top_sentences", [])
234
-
235
  st.caption(f"Semantic Overlap Ratio: {overlap_ratio:.3f}")
236
-
237
  if sentences:
238
  st.markdown("**Key Excerpts:**")
239
  for s in sentences:
 
240
  st.markdown(f"""
241
- <div style='background:#fff; border-left:3px solid #4285f4; padding:10px; margin-bottom:5px;'>
242
- "{s['sentence']}"
243
- <span style='font-size:0.8em; color:#555;'> (conf: {s['score']:.2f})</span>
244
  </div>
245
  """, unsafe_allow_html=True)
246
-
247
  llm_expl = explanation.get("llm_explanation")
248
  if llm_expl:
249
- st.markdown("**Why this document?**")
250
- st.write(llm_expl)
251
-
252
  st.markdown("---")
253
  st.markdown("**📄 Full Document Content:**")
254
- st.code(full_text, language="text")
255
-
256
-
257
- # =======================
258
- # EVALUATION SECTION
259
- # =======================
260
  if run_eval:
261
 
262
- st.info("Running evaluation...")
263
 
264
  results = run_evaluation(top_k=10)
265
 
266
  st.success("Evaluation Complete!")
267
 
268
- st.markdown("## Evaluation Summary")
 
 
 
269
 
270
  c1, c2, c3, c4 = st.columns(4)
271
- with c1: st.metric("Accuracy", f"{results['accuracy']}%")
272
- with c2: st.metric("MRR", results["mrr"])
273
- with c3: st.metric("NDCG", results["ndcg"])
274
- with c4: st.metric("Queries", results["total_queries"])
 
 
 
 
 
 
 
 
 
275
 
276
  st.markdown("---")
277
 
 
 
 
 
 
278
  wrong = [d for d in results["details"] if not d["is_correct"]]
279
- st.markdown("## Incorrect Fetches")
280
 
281
  if wrong:
282
  for item in wrong:
283
  st.markdown(f"""
284
- <div style="padding:14px; background:#ffe5e5;
285
- border-left:5px solid #ff4d4f;
286
- border-radius:8px; margin-bottom:10px;">
287
- <b>Query:</b> {item['query']}<br>
 
 
 
288
  <b>Expected:</b> {item['expected']}<br>
289
  <b>Retrieved:</b> {item['retrieved']}<br>
290
  <b>Rank:</b> {item['rank']}
291
  </div>
292
  """, unsafe_allow_html=True)
293
  else:
294
- st.success("No incorrect queries!")
295
 
296
  st.markdown("---")
297
 
298
- st.markdown("## Correct Fetches")
 
 
 
 
299
  correct_items = [d for d in results["details"] if d["is_correct"]]
300
 
301
  if correct_items:
302
  for item in correct_items:
303
  st.markdown(f"""
304
- <div style="padding:14px; background:#e8ffe5;
305
- border-left:5px solid #2ecc71;
306
- border-radius:8px; margin-bottom:10px;">
307
- <b>Query:</b> {item['query']}<br>
 
 
 
308
  <b>Expected:</b> {item['expected']}<br>
309
  <b>Top-K Retrieved:</b> {item['retrieved']}<br>
310
  <b>Rank:</b> {item['rank']}
@@ -315,16 +388,19 @@ if run_eval:
315
 
316
  st.markdown("---")
317
 
318
- st.markdown("## Full Evaluation Table")
319
- table_data = [
320
- {
321
- "Query": d["query"],
322
- "Expected Doc": d["expected"],
323
- "Retrieved (Top-10)": ", ".join(d["retrieved"]),
324
- "Correct?": "Yes" if d["is_correct"] else "No",
325
- "Rank": d["rank"],
326
- }
327
- for d in results["details"]
328
- ]
 
 
 
329
 
330
  st.dataframe(table_data, use_container_width=True)
 
29
  st.set_page_config(
30
  page_title="Doc-Fetch",
31
  layout="wide",
32
+ initial_sidebar_state="expanded", # Changed from "collapsed" to "expanded"
33
  )
34
 
35
  # =======================
 
37
  # =======================
38
  st.markdown("""
39
  <style>
40
+ /* Global Font & Background */
41
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
42
 
43
  html, body, [class*="css"] {
44
  font-family: 'Inter', sans-serif;
45
+ background-color: #ffffff; /* White Background */
46
+ color: #1f1f1f; /* Dark text for contrast */
47
  }
48
 
49
+ /* --- INPUT FIELD FIX --- */
50
+ /* 1. Remove the default Streamlit border/background on the container */
51
  .stTextInput > div[data-baseweb="input"] {
52
  background-color: transparent !important;
53
  border: none !important;
54
  border-radius: 24px !important;
55
  box-shadow: none !important;
56
  }
57
+
58
+ /* 2. Style the actual input element */
59
  .stTextInput input {
60
  border-radius: 24px !important;
61
+ background-color: #f0f4f9 !important; /* Light ash input */
62
  border: 1px solid transparent !important;
63
  color: #1f1f1f !important;
64
  padding: 12px 20px !important;
65
  font-size: 16px !important;
66
  transition: all 0.2s ease;
67
  }
68
+
69
+ /* 3. Focus state - clean blue border, no default red overlay */
70
  .stTextInput input:focus {
71
  background-color: #ffffff !important;
72
+ border-color: #0b57d0 !important; /* Gemini Blue */
73
  box-shadow: 0 0 0 2px rgba(11, 87, 208, 0.2) !important;
74
  outline: none !important;
75
  }
76
 
77
+ /* Button Styling */
78
  .stButton > button {
79
  border-radius: 20px;
80
  font-weight: 500;
81
  border: none;
82
  padding: 0.5rem 1.5rem;
83
  transition: all 0.3s ease;
84
+ white-space: nowrap; /* Forces text to stay on one line */
85
+ min-width: 140px; /* Ensures button is never too skinny */
86
  }
87
+
88
+ /* Primary Search Button */
89
  button[kind="primary"] {
90
  background: linear-gradient(90deg, #4b90ff, #ff5546);
91
  color: white;
 
95
  box-shadow: 0 4px 12px rgba(75, 144, 255, 0.3);
96
  }
97
 
98
+ /* Result Card - Light Ash Background */
99
  .result-card {
100
+ background-color: #f0f4f9; /* Light Ash */
101
  border-radius: 16px;
102
  padding: 1.5rem;
103
  margin-bottom: 1rem;
104
+ border: none; /* Removed border for cleaner look on light mode */
105
+ transition: transform 0.2s;
106
  }
107
  .result-card:hover {
108
  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
109
  }
110
 
111
+ /* Typography in Cards */
112
+ .card-title {
113
+ color: #1f1f1f; /* Dark Title */
114
+ font-size: 1.1rem;
115
+ font-weight: 600;
116
+ margin-bottom: 0.5rem;
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
120
+ }
121
+
122
+ .card-preview {
123
+ color: #444746; /* Darker gray for readable preview */
124
+ font-size: 0.95rem;
125
+ line-height: 1.5;
126
+ margin-bottom: 1rem;
127
+ }
128
+
129
+ /* Pills & Badges */
130
+ .score-badge {
131
+ background-color: #c4eed0; /* Light Green bg */
132
+ color: #0f5223; /* Dark Green text */
133
+ padding: 4px 12px;
134
+ border-radius: 12px;
135
+ font-size: 0.75rem;
136
+ font-weight: 500;
137
+ display: inline-block;
138
+ }
139
+
140
+ .keyword-pill {
141
+ background-color: #c2e7ff; /* Light Blue bg */
142
+ color: #004a77; /* Dark Blue text */
143
+ padding: 2px 10px;
144
+ border-radius: 8px;
145
+ font-size: 0.8rem;
146
+ margin-right: 6px;
147
+ display: inline-block;
148
+ margin-bottom: 4px;
149
+ }
150
+
151
+ /* Gradient Text for Header */
152
  .gradient-text {
153
  background: linear-gradient(to right, #4285f4, #9b72cb, #d96570);
154
  -webkit-background-clip: text;
 
156
  font-weight: 700;
157
  font-size: 3rem;
158
  }
159
+
160
+ /* Custom Info Box */
161
+ .stAlert {
162
+ background-color: #f0f4f9;
163
+ color: #1f1f1f;
164
+ }
165
  </style>
166
  """, unsafe_allow_html=True)
167
 
 
168
  # =======================
169
+ # SIDEBAR (Settings)
170
  # =======================
171
  with st.sidebar:
172
  st.markdown("### ⚙️ Settings")
 
176
  st.subheader(" Evaluation")
177
  run_eval = st.button("Run Evaluation Script")
178
  st.divider()
179
+ st.caption("Semantic Search · Smart Cache · FAISS Retrieval · Multi-Service Architecture · Relevance by MiniLM Embeddings. Reasoning by LLM.")
180
 
181
  API_GATEWAY_URL = url_input
182
 
 
183
  # =======================
184
+ # MAIN HEADER (Gemini Style)
185
  # =======================
186
  col1, col2, col3 = st.columns([1, 6, 1])
187
  with col2:
188
+ # Use HTML for the gradient text title
189
  st.markdown('<div style="text-align: center; margin-bottom: 10px;"><span class="gradient-text">Hello, Explorer</span></div>', unsafe_allow_html=True)
190
  st.markdown('<div style="text-align: center; color: #444746; font-size: 1.2rem; margin-bottom: 30px;">How can I help you find documents today?</div>', unsafe_allow_html=True)
191
 
192
 
193
  # =======================
194
+ # SEARCH BAR CENTERED
195
  # =======================
196
+ # Centering the search bar using columns
197
  sc1, sc2, sc3 = st.columns([1, 4, 1])
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  with sc2:
200
  query = st.text_input(
201
+ "Search Query", # Label hidden by CSS/Config if needed, or set visibility hidden
202
  placeholder="Ask a question about your documents...",
203
  label_visibility="collapsed"
204
  )
205
+
206
+ # Buttons row
207
  b1, b2, b3 = st.columns([2, 1, 2])
208
  with b2:
209
  submit_btn = st.button("Sparkle Search", type="primary", use_container_width=True)
210
 
 
211
  # =======================
212
  # SEARCH HANDLER
213
  # =======================
214
  if submit_btn and query.strip():
215
 
216
+ # Gemini-style spinner
217
  with st.spinner("✨ Analyzing semantics..."):
218
 
219
  response = requests.post(
 
225
  st.error(f"❌ Connection Error: {response.text}")
226
  st.stop()
227
 
228
+ try:
229
+ data = response.json()
230
+ except:
231
+ st.error("❌ Invalid JSON response.")
232
+ st.stop()
233
 
234
  if "results" not in data:
235
+ st.info("No relevant documents found for that query.")
236
  st.stop()
237
 
238
+ # Results Header
239
  st.markdown("### ✨ Search Results")
240
  st.markdown("---")
241
 
242
+ # =======================
243
+ # DISPLAY RESULTS (Card Style)
244
+ # =======================
245
  for item in data["results"]:
246
  filename = item["filename"]
247
  score = item["score"]
 
250
  full_text = item["full_text"]
251
 
252
  safe_preview = html.escape(preview)
253
+
254
+ # Prepare keyword HTML
255
  keywords = explanation.get("keyword_overlap", [])
256
+ keyword_html = ""
257
+ if keywords:
258
+ keyword_html = "".join([f"<span class='keyword-pill'>{kw}</span>" for kw in keywords])
259
+
260
+ # Doc Icon (SVG) - Changed stroke to dark blue for visibility on light bg
261
+ doc_icon = """<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#0b57d0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>"""
262
+
263
+ # Main Card Render
264
  st.markdown(f"""
265
  <div class="result-card">
266
+ <div style="display:flex; justify-content:space-between; align-items:start;">
267
  <div class="card-title">
268
+ {doc_icon} {filename}
269
  </div>
270
  <div class="score-badge">match: {score:.4f}</div>
271
  </div>
272
  <p class="card-preview">{safe_preview}...</p>
273
+ <div style="margin-top: 10px;">
274
+ <div style="font-weight:600; color:#1f1f1f; margin-bottom:6px;">
275
+ Keyword Overlap:
276
+ </div>
277
+ {keyword_html}
278
+ </div>
279
  </div>
280
  """, unsafe_allow_html=True)
281
 
282
+ # Details Expander (Standard Streamlit but styled via global CSS)
283
  with st.expander(f"View Document Insights: Semantic Overlap, Top Sentences, LLM Reasoning & Full Text for {filename}"):
284
+
285
  overlap_ratio = explanation.get("overlap_ratio", 0)
286
  sentences = explanation.get("top_sentences", [])
287
+
288
  st.caption(f"Semantic Overlap Ratio: {overlap_ratio:.3f}")
289
+
290
  if sentences:
291
  st.markdown("**Key Excerpts:**")
292
  for s in sentences:
293
+ # Updated quote box for light mode
294
  st.markdown(f"""
295
+ <div style="background: #ffffff; border-left: 3px solid #4285f4; padding: 10px; margin-bottom: 5px; border-radius: 0 8px 8px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
296
+ <span style="color: #1f1f1f;">"{s['sentence']}"</span>
297
+ <span style="color: #5e5e5e; font-size: 0.8em; margin-left: 10px;">(conf: {s['score']:.2f})</span>
298
  </div>
299
  """, unsafe_allow_html=True)
 
300
  llm_expl = explanation.get("llm_explanation")
301
  if llm_expl:
302
+ st.markdown("**Why this document?**")
303
+ st.write(llm_expl)
 
304
  st.markdown("---")
305
  st.markdown("**📄 Full Document Content:**")
306
+ st.code(full_text, language="text") # Using code block for better readability of raw text
 
 
 
 
 
307
  if run_eval:
308
 
309
+ st.info("Running evaluation... this may take 10–20 seconds...")
310
 
311
  results = run_evaluation(top_k=10)
312
 
313
  st.success("Evaluation Complete!")
314
 
315
+ # -----------------------------
316
+ # Summary Metrics (Horizontal)
317
+ # -----------------------------
318
+ st.markdown("## Evaluation Summary")
319
 
320
  c1, c2, c3, c4 = st.columns(4)
321
+ with c1:
322
+ st.metric("Accuracy", f"{results['accuracy']}%")
323
+ with c2:
324
+ st.metric("MRR", results["mrr"])
325
+ with c3:
326
+ st.metric("NDCG", results["ndcg"])
327
+ with c4:
328
+ st.metric("Queries", results["total_queries"])
329
+
330
+ st.markdown(
331
+ f"**Correct:** {results['correct_count']} &nbsp;&nbsp;|&nbsp;&nbsp; "
332
+ f"**Incorrect:** {results['incorrect_count']}"
333
+ )
334
 
335
  st.markdown("---")
336
 
337
+ # -----------------------------
338
+ # Incorrect Results
339
+ # -----------------------------
340
+ st.markdown("## Incorrect Fetches ")
341
+
342
  wrong = [d for d in results["details"] if not d["is_correct"]]
 
343
 
344
  if wrong:
345
  for item in wrong:
346
  st.markdown(f"""
347
+ <div style="
348
+ padding:14px;
349
+ background:#ffe5e5;
350
+ border-left:5px solid #ff4d4f;
351
+ border-radius:8px;
352
+ margin-bottom:10px;">
353
+ <b> Query:</b> {item['query']}<br>
354
  <b>Expected:</b> {item['expected']}<br>
355
  <b>Retrieved:</b> {item['retrieved']}<br>
356
  <b>Rank:</b> {item['rank']}
357
  </div>
358
  """, unsafe_allow_html=True)
359
  else:
360
+ st.success(" No incorrect queries!")
361
 
362
  st.markdown("---")
363
 
364
+ # -----------------------------
365
+ # Correct Results
366
+ # -----------------------------
367
+ st.markdown("## Correct Fetches")
368
+
369
  correct_items = [d for d in results["details"] if d["is_correct"]]
370
 
371
  if correct_items:
372
  for item in correct_items:
373
  st.markdown(f"""
374
+ <div style="
375
+ padding:14px;
376
+ background:#e8ffe5;
377
+ border-left:5px solid #2ecc71;
378
+ border-radius:8px;
379
+ margin-bottom:10px;">
380
+ <b> Query:</b> {item['query']}<br>
381
  <b>Expected:</b> {item['expected']}<br>
382
  <b>Top-K Retrieved:</b> {item['retrieved']}<br>
383
  <b>Rank:</b> {item['rank']}
 
388
 
389
  st.markdown("---")
390
 
391
+ # -----------------------------
392
+ # Full Table
393
+ # -----------------------------
394
+ st.markdown("## Full Evaluation Table")
395
+
396
+ table_data = []
397
+ for item in results["details"]:
398
+ table_data.append({
399
+ "Query": item["query"],
400
+ "Expected Doc": item["expected"],
401
+ "Retrieved (Top-10)": ", ".join(item["retrieved"]),
402
+ "Correct?": "Yes" if item["is_correct"] else "No",
403
+ "Rank": item["rank"]
404
+ })
405
 
406
  st.dataframe(table_data, use_container_width=True)