Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -12,17 +12,21 @@ def create_reranking_interface(task_data):
|
|
12 |
def save_ranking(rankings, sample_id):
|
13 |
"""Save the current set of rankings."""
|
14 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
# Check if all documents have rankings
|
16 |
-
|
17 |
-
if not all_ranked:
|
18 |
return "⚠️ Please assign a rank to all documents before submitting", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
19 |
|
20 |
-
# Convert rankings to integers with better error handling
|
21 |
-
try:
|
22 |
-
processed_rankings = [int(r) for r in rankings]
|
23 |
-
except ValueError:
|
24 |
-
return "⚠️ Invalid ranking value. Please use only numbers.", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
25 |
-
|
26 |
# Check for duplicate rankings
|
27 |
if len(set(processed_rankings)) != len(processed_rankings):
|
28 |
return "⚠️ Each document must have a unique rank. Please review your rankings.", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
@@ -86,342 +90,40 @@ def create_reranking_interface(task_data):
|
|
86 |
|
87 |
gr.Markdown("## Documents to Rank:")
|
88 |
|
89 |
-
# Create document displays and ranking
|
90 |
doc_containers = []
|
91 |
-
|
92 |
|
93 |
with gr.Column():
|
94 |
for i, doc in enumerate(samples[0]["candidates"]):
|
95 |
-
with gr.
|
|
|
96 |
doc_box = gr.Textbox(
|
97 |
value=doc,
|
98 |
-
label=
|
99 |
interactive=False,
|
100 |
-
|
101 |
)
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
value=None,
|
106 |
-
|
|
|
|
|
|
|
107 |
)
|
108 |
-
|
109 |
-
with gr.Column(min_width=120):
|
110 |
-
gr.Markdown(f"Quick Rank", elem_classes="quick-rank-label")
|
111 |
-
with gr.Row():
|
112 |
-
# Add first 5 rank buttons (or fewer if there are fewer candidates)
|
113 |
-
num_buttons = min(5, len(samples[0]["candidates"]))
|
114 |
-
for r in range(1, num_buttons + 1):
|
115 |
-
button = gr.Button(f"{r}", size="sm", elem_classes=f"quick-rank-btn quick-rank-btn-{i}-{r}")
|
116 |
-
# Use JavaScript to set the dropdown value when clicked
|
117 |
-
button.click(
|
118 |
-
None,
|
119 |
-
[],
|
120 |
-
[],
|
121 |
-
_js=f"() => {{ document.querySelectorAll('.ranking-dropdown')[{i}].value = '{r}'; return []; }}"
|
122 |
-
)
|
123 |
-
|
124 |
-
doc_containers.append(doc_box)
|
125 |
-
ranking_dropdowns.append(dropdown)
|
126 |
|
127 |
-
# Add
|
128 |
-
with gr.Accordion("
|
129 |
gr.Markdown("""
|
130 |
-
###
|
131 |
-
-
|
132 |
-
|
133 |
-
-
|
134 |
-
|
135 |
-
|
136 |
-
- Press 's' to submit the current rankings
|
137 |
-
""")
|
138 |
-
|
139 |
-
# Add JavaScript for keyboard shortcuts
|
140 |
-
gr.HTML("""
|
141 |
-
<script>
|
142 |
-
document.addEventListener('DOMContentLoaded', function() {
|
143 |
-
// Wait for Gradio elements to be fully loaded
|
144 |
-
setTimeout(() => {
|
145 |
-
// Get all document textboxes
|
146 |
-
const docBoxes = document.querySelectorAll('.doc-box');
|
147 |
-
const dropdowns = document.querySelectorAll('.ranking-dropdown');
|
148 |
-
|
149 |
-
// Add event listeners to document boxes
|
150 |
-
docBoxes.forEach((box, index) => {
|
151 |
-
box.addEventListener('click', function() {
|
152 |
-
// Mark this box as active for keyboard shortcuts
|
153 |
-
docBoxes.forEach(b => b.classList.remove('active-doc'));
|
154 |
-
box.classList.add('active-doc');
|
155 |
-
});
|
156 |
-
});
|
157 |
-
|
158 |
-
// Add event listeners to dropdowns for color coding
|
159 |
-
dropdowns.forEach((dropdown, index) => {
|
160 |
-
dropdown.addEventListener('change', function() {
|
161 |
-
updateDropdownColor(dropdown, docBoxes[index]);
|
162 |
-
});
|
163 |
-
});
|
164 |
-
|
165 |
-
// Function to update color based on rank
|
166 |
-
function updateDropdownColor(dropdown, docBox) {
|
167 |
-
const value = dropdown.value;
|
168 |
-
if (!value) return;
|
169 |
-
|
170 |
-
// Remove existing color classes
|
171 |
-
dropdown.classList.remove('rank-1', 'rank-2', 'rank-3', 'rank-4', 'rank-5', 'rank-high');
|
172 |
-
|
173 |
-
// Add appropriate color class
|
174 |
-
if (value === '1') dropdown.classList.add('rank-1');
|
175 |
-
else if (value === '2') dropdown.classList.add('rank-2');
|
176 |
-
else if (value === '3') dropdown.classList.add('rank-3');
|
177 |
-
else if (value === '4') dropdown.classList.add('rank-4');
|
178 |
-
else if (value === '5') dropdown.classList.add('rank-5');
|
179 |
-
else dropdown.classList.add('rank-high');
|
180 |
-
|
181 |
-
// Add highlighting to document box
|
182 |
-
docBox.classList.add('ranked-doc');
|
183 |
-
}
|
184 |
-
|
185 |
-
// Add global keyboard listener
|
186 |
-
document.addEventListener('keydown', function(e) {
|
187 |
-
// Number keys 1-9 for ranking
|
188 |
-
if (e.key >= '1' && e.key <= '9') {
|
189 |
-
const activeDoc = document.querySelector('.active-doc');
|
190 |
-
if (activeDoc) {
|
191 |
-
const index = Array.from(docBoxes).indexOf(activeDoc);
|
192 |
-
const dropdown = document.querySelectorAll('.ranking-dropdown')[index];
|
193 |
-
if (dropdown) {
|
194 |
-
dropdown.value = e.key;
|
195 |
-
dropdown.dispatchEvent(new Event('change'));
|
196 |
-
updateDropdownColor(dropdown, activeDoc);
|
197 |
-
}
|
198 |
-
}
|
199 |
-
}
|
200 |
-
|
201 |
-
// Navigation shortcuts
|
202 |
-
if (e.key === 'n') {
|
203 |
-
// Next query
|
204 |
-
document.querySelector('#next-btn').click();
|
205 |
-
} else if (e.key === 'p') {
|
206 |
-
// Previous query
|
207 |
-
document.querySelector('#prev-btn').click();
|
208 |
-
} else if (e.key === 's') {
|
209 |
-
// Submit rankings
|
210 |
-
document.querySelector('#submit-btn').click();
|
211 |
-
}
|
212 |
-
});
|
213 |
-
|
214 |
-
// Add some CSS for active document
|
215 |
-
const style = document.createElement('style');
|
216 |
-
style.textContent = `
|
217 |
-
.active-doc {
|
218 |
-
border-left: 3px solid #3B82F6 !important;
|
219 |
-
background-color: rgba(59, 130, 246, 0.05) !important;
|
220 |
-
}
|
221 |
-
.ranked-doc {
|
222 |
-
border-bottom: 2px solid #4ADE80 !important;
|
223 |
-
}
|
224 |
-
.rank-1 {
|
225 |
-
background-color: rgba(74, 222, 128, 0.2) !important;
|
226 |
-
font-weight: bold !important;
|
227 |
-
}
|
228 |
-
.rank-2 {
|
229 |
-
background-color: rgba(74, 222, 128, 0.15) !important;
|
230 |
-
}
|
231 |
-
.rank-3 {
|
232 |
-
background-color: rgba(251, 191, 36, 0.15) !important;
|
233 |
-
}
|
234 |
-
.rank-4 {
|
235 |
-
background-color: rgba(251, 191, 36, 0.1) !important;
|
236 |
-
}
|
237 |
-
.rank-5 {
|
238 |
-
background-color: rgba(239, 68, 68, 0.1) !important;
|
239 |
-
}
|
240 |
-
.rank-high {
|
241 |
-
background-color: rgba(239, 68, 68, 0.05) !important;
|
242 |
-
}
|
243 |
-
.quick-rank-label {
|
244 |
-
margin-bottom: 0 !important;
|
245 |
-
font-size: 0.8rem !important;
|
246 |
-
opacity: 0.8;
|
247 |
-
}
|
248 |
-
.quick-rank-btn {
|
249 |
-
min-width: 20px !important;
|
250 |
-
height: 24px !important;
|
251 |
-
line-height: 1 !important;
|
252 |
-
padding: 2px 6px !important;
|
253 |
-
}
|
254 |
-
`;
|
255 |
-
document.head.appendChild(style);
|
256 |
-
}, 1000);
|
257 |
-
});
|
258 |
-
</script>
|
259 |
-
""")
|
260 |
-
|
261 |
-
# Add visual ranking mode option
|
262 |
-
with gr.Row():
|
263 |
-
visual_mode_btn = gr.Button("Toggle Visual Ranking Mode", size="sm")
|
264 |
-
reset_rankings_btn = gr.Button("Reset Rankings", size="sm", variant="secondary")
|
265 |
-
|
266 |
-
# Visual ranking display
|
267 |
-
with gr.Column(visible=False) as visual_ranking_container:
|
268 |
-
gr.Markdown("## Current Rankings (Most to Least Relevant)")
|
269 |
-
ranked_display = gr.HTML("No rankings yet")
|
270 |
-
|
271 |
-
# Function to toggle visual ranking mode
|
272 |
-
def toggle_visual_mode(visible):
|
273 |
-
return not visible
|
274 |
-
|
275 |
-
# Function to update visual ranking display
|
276 |
-
def update_visual_ranking(*rankings):
|
277 |
-
# Convert to integers with error handling
|
278 |
-
clean_rankings = []
|
279 |
-
for r in rankings:
|
280 |
-
try:
|
281 |
-
if r and r.strip():
|
282 |
-
clean_rankings.append(int(r))
|
283 |
-
else:
|
284 |
-
clean_rankings.append(None)
|
285 |
-
except ValueError:
|
286 |
-
clean_rankings.append(None)
|
287 |
-
|
288 |
-
# Check if any rankings exist
|
289 |
-
if not any(r is not None for r in clean_rankings):
|
290 |
-
return "<p>No rankings assigned yet.</p>"
|
291 |
-
|
292 |
-
# Create sorted order
|
293 |
-
ranked_indices = []
|
294 |
-
for rank in range(1, len(clean_rankings) + 1):
|
295 |
-
try:
|
296 |
-
idx = clean_rankings.index(rank)
|
297 |
-
ranked_indices.append(idx)
|
298 |
-
except ValueError:
|
299 |
-
pass
|
300 |
-
|
301 |
-
# Build HTML
|
302 |
-
html = "<div class='visual-ranking'>"
|
303 |
-
for i, idx in enumerate(ranked_indices):
|
304 |
-
rank = i + 1
|
305 |
-
doc_text = doc_containers[idx].value
|
306 |
-
|
307 |
-
# Apply color classes based on rank
|
308 |
-
rank_class = ""
|
309 |
-
if rank == 1:
|
310 |
-
rank_class = "visual-rank-1"
|
311 |
-
elif rank == 2:
|
312 |
-
rank_class = "visual-rank-2"
|
313 |
-
elif rank == 3:
|
314 |
-
rank_class = "visual-rank-3"
|
315 |
-
elif rank <= 5:
|
316 |
-
rank_class = "visual-rank-45"
|
317 |
-
else:
|
318 |
-
rank_class = "visual-rank-high"
|
319 |
-
|
320 |
-
html += f"""
|
321 |
-
<div class='visual-rank-item {rank_class}'>
|
322 |
-
<div class='visual-rank-number'>{rank}</div>
|
323 |
-
<div class='visual-rank-content'>{doc_text}</div>
|
324 |
-
</div>
|
325 |
-
"""
|
326 |
-
|
327 |
-
# Add unranked items if any
|
328 |
-
unranked_indices = [i for i, r in enumerate(clean_rankings) if r is None]
|
329 |
-
if unranked_indices:
|
330 |
-
html += "<h3>Unranked Documents</h3>"
|
331 |
-
for idx in unranked_indices:
|
332 |
-
doc_text = doc_containers[idx].value
|
333 |
-
html += f"""
|
334 |
-
<div class='visual-rank-item visual-rank-unranked'>
|
335 |
-
<div class='visual-rank-number'>?</div>
|
336 |
-
<div class='visual-rank-content'>{doc_text}</div>
|
337 |
-
</div>
|
338 |
-
"""
|
339 |
-
|
340 |
-
html += "</div>"
|
341 |
-
|
342 |
-
# Add CSS
|
343 |
-
html += """
|
344 |
-
<style>
|
345 |
-
.visual-ranking {
|
346 |
-
margin-top: 15px;
|
347 |
-
}
|
348 |
-
.visual-rank-item {
|
349 |
-
display: flex;
|
350 |
-
margin-bottom: 15px;
|
351 |
-
padding: 10px;
|
352 |
-
border-radius: 8px;
|
353 |
-
}
|
354 |
-
.visual-rank-number {
|
355 |
-
font-size: 18px;
|
356 |
-
font-weight: bold;
|
357 |
-
margin-right: 10px;
|
358 |
-
min-width: 30px;
|
359 |
-
height: 30px;
|
360 |
-
border-radius: 15px;
|
361 |
-
background-color: #e5e7eb;
|
362 |
-
display: flex;
|
363 |
-
align-items: center;
|
364 |
-
justify-content: center;
|
365 |
-
}
|
366 |
-
.visual-rank-content {
|
367 |
-
flex: 1;
|
368 |
-
}
|
369 |
-
.visual-rank-1 {
|
370 |
-
background-color: rgba(74, 222, 128, 0.2);
|
371 |
-
border-left: 4px solid #4ADE80;
|
372 |
-
}
|
373 |
-
.visual-rank-2 {
|
374 |
-
background-color: rgba(74, 222, 128, 0.15);
|
375 |
-
border-left: 3px solid #4ADE80;
|
376 |
-
}
|
377 |
-
.visual-rank-3 {
|
378 |
-
background-color: rgba(251, 191, 36, 0.15);
|
379 |
-
border-left: 3px solid #FBBF24;
|
380 |
-
}
|
381 |
-
.visual-rank-45 {
|
382 |
-
background-color: rgba(251, 191, 36, 0.1);
|
383 |
-
border-left: 2px solid #FBBF24;
|
384 |
-
}
|
385 |
-
.visual-rank-high {
|
386 |
-
background-color: rgba(239, 68, 68, 0.05);
|
387 |
-
border-left: 2px solid #EF4444;
|
388 |
-
}
|
389 |
-
.visual-rank-unranked {
|
390 |
-
background-color: #f9fafb;
|
391 |
-
border: 1px dashed #d1d5db;
|
392 |
-
}
|
393 |
-
.visual-rank-unranked .visual-rank-number {
|
394 |
-
background-color: #d1d5db;
|
395 |
-
}
|
396 |
-
</style>
|
397 |
-
"""
|
398 |
-
|
399 |
-
return html
|
400 |
-
|
401 |
-
# Function to reset all rankings
|
402 |
-
def reset_rankings():
|
403 |
-
return ["" for _ in ranking_dropdowns]
|
404 |
-
|
405 |
-
# Connect events
|
406 |
-
visual_mode_btn.click(
|
407 |
-
toggle_visual_mode,
|
408 |
-
inputs=[visual_ranking_container],
|
409 |
-
outputs=[visual_ranking_container]
|
410 |
-
)
|
411 |
-
|
412 |
-
# Update visual ranking when any dropdown changes
|
413 |
-
for dropdown in ranking_dropdowns:
|
414 |
-
dropdown.change(
|
415 |
-
update_visual_ranking,
|
416 |
-
inputs=ranking_dropdowns,
|
417 |
-
outputs=[ranked_display]
|
418 |
-
)
|
419 |
-
|
420 |
-
# Reset rankings button
|
421 |
-
reset_rankings_btn.click(
|
422 |
-
reset_rankings,
|
423 |
-
outputs=ranking_dropdowns
|
424 |
-
)
|
425 |
|
426 |
with gr.Row():
|
427 |
prev_btn = gr.Button("← Previous Query", size="sm", elem_id="prev-btn")
|
@@ -434,7 +136,7 @@ def create_reranking_interface(task_data):
|
|
434 |
"""Load a specific sample into the interface."""
|
435 |
sample = next((s for s in samples if s["id"] == sample_id), None)
|
436 |
if not sample:
|
437 |
-
return [query_text.value] + [d.value for d in doc_containers] + [
|
438 |
|
439 |
# Update query
|
440 |
new_query = sample["query"]
|
@@ -446,7 +148,7 @@ def create_reranking_interface(task_data):
|
|
446 |
new_docs.append(doc)
|
447 |
|
448 |
# Initialize rankings
|
449 |
-
new_rankings = [
|
450 |
|
451 |
# Check if this sample has already been annotated
|
452 |
existing_annotation = next((a for a in results["annotations"] if a["sample_id"] == sample_id), None)
|
@@ -454,7 +156,7 @@ def create_reranking_interface(task_data):
|
|
454 |
# Restore previous rankings
|
455 |
for i, rank in enumerate(existing_annotation["rankings"]):
|
456 |
if i < len(new_rankings) and rank is not None:
|
457 |
-
new_rankings[i] =
|
458 |
|
459 |
# Update progress
|
460 |
current_idx = samples.index(sample)
|
@@ -497,22 +199,64 @@ def create_reranking_interface(task_data):
|
|
497 |
json.dump(results, f, indent=2)
|
498 |
return f"✅ Results saved to {output_path} ({len(results['annotations'])} annotations)"
|
499 |
|
500 |
-
#
|
501 |
-
def
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
|
507 |
# Connect events
|
508 |
submit_btn.click(
|
509 |
-
|
510 |
-
inputs=
|
511 |
outputs=[status_box, progress_text]
|
512 |
-
).then(
|
513 |
-
update_visual_ranking,
|
514 |
-
inputs=ranking_dropdowns,
|
515 |
-
outputs=[ranked_display]
|
516 |
)
|
517 |
|
518 |
next_btn.click(
|
@@ -522,11 +266,7 @@ def create_reranking_interface(task_data):
|
|
522 |
).then(
|
523 |
load_sample,
|
524 |
inputs=[current_sample_id],
|
525 |
-
outputs=[query_text] + doc_containers +
|
526 |
-
).then(
|
527 |
-
update_visual_ranking,
|
528 |
-
inputs=ranking_dropdowns,
|
529 |
-
outputs=[ranked_display]
|
530 |
)
|
531 |
|
532 |
prev_btn.click(
|
@@ -536,11 +276,7 @@ def create_reranking_interface(task_data):
|
|
536 |
).then(
|
537 |
load_sample,
|
538 |
inputs=[current_sample_id],
|
539 |
-
outputs=[query_text] + doc_containers +
|
540 |
-
).then(
|
541 |
-
update_visual_ranking,
|
542 |
-
inputs=ranking_dropdowns,
|
543 |
-
outputs=[ranked_display]
|
544 |
)
|
545 |
|
546 |
save_btn.click(save_results, outputs=[status_box])
|
@@ -754,9 +490,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
754 |
|
755 |
# Add download options
|
756 |
with gr.Row():
|
757 |
-
|
758 |
-
|
759 |
-
|
|
|
|
|
760 |
|
761 |
# Add results visualization placeholder
|
762 |
gr.Markdown("### Results Visualization")
|
|
|
12 |
def save_ranking(rankings, sample_id):
|
13 |
"""Save the current set of rankings."""
|
14 |
try:
|
15 |
+
# Convert to integers with error handling
|
16 |
+
processed_rankings = []
|
17 |
+
for r in rankings:
|
18 |
+
if r is None or r == "":
|
19 |
+
processed_rankings.append(None)
|
20 |
+
else:
|
21 |
+
try:
|
22 |
+
processed_rankings.append(int(r))
|
23 |
+
except ValueError:
|
24 |
+
return "⚠️ Invalid ranking value. Please use only numbers.", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
25 |
+
|
26 |
# Check if all documents have rankings
|
27 |
+
if None in processed_rankings:
|
|
|
28 |
return "⚠️ Please assign a rank to all documents before submitting", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
# Check for duplicate rankings
|
31 |
if len(set(processed_rankings)) != len(processed_rankings):
|
32 |
return "⚠️ Each document must have a unique rank. Please review your rankings.", f"Progress: {sum(completed_samples.values())}/{len(samples)}"
|
|
|
90 |
|
91 |
gr.Markdown("## Documents to Rank:")
|
92 |
|
93 |
+
# Create document displays and ranking inputs in synchronized pairs
|
94 |
doc_containers = []
|
95 |
+
ranking_inputs = []
|
96 |
|
97 |
with gr.Column():
|
98 |
for i, doc in enumerate(samples[0]["candidates"]):
|
99 |
+
with gr.Box():
|
100 |
+
gr.Markdown(f"### Document {i+1}")
|
101 |
doc_box = gr.Textbox(
|
102 |
value=doc,
|
103 |
+
label=None,
|
104 |
interactive=False,
|
105 |
+
lines=4
|
106 |
)
|
107 |
+
doc_containers.append(doc_box)
|
108 |
+
|
109 |
+
rank_input = gr.Number(
|
110 |
value=None,
|
111 |
+
label=f"Rank (1 = highest, {len(samples[0]['candidates'])} = lowest)",
|
112 |
+
minimum=1,
|
113 |
+
maximum=len(samples[0]['candidates']),
|
114 |
+
step=1
|
115 |
)
|
116 |
+
ranking_inputs.append(rank_input)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
+
# Add simple instructions for ranking
|
119 |
+
with gr.Accordion("Ranking Instructions", open=True):
|
120 |
gr.Markdown("""
|
121 |
+
### Ranking Documents:
|
122 |
+
- Enter a number from 1 to {max_rank} for each document
|
123 |
+
- 1 = most relevant document
|
124 |
+
- Higher numbers = less relevant documents
|
125 |
+
- Each document must have a unique rank
|
126 |
+
""".format(max_rank=len(samples[0]["candidates"])))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
|
128 |
with gr.Row():
|
129 |
prev_btn = gr.Button("← Previous Query", size="sm", elem_id="prev-btn")
|
|
|
136 |
"""Load a specific sample into the interface."""
|
137 |
sample = next((s for s in samples if s["id"] == sample_id), None)
|
138 |
if not sample:
|
139 |
+
return [query_text.value] + [d.value for d in doc_containers] + [None] * len(ranking_inputs) + [current_sample_id.value, progress_text.value, status_box.value]
|
140 |
|
141 |
# Update query
|
142 |
new_query = sample["query"]
|
|
|
148 |
new_docs.append(doc)
|
149 |
|
150 |
# Initialize rankings
|
151 |
+
new_rankings = [None] * len(ranking_inputs)
|
152 |
|
153 |
# Check if this sample has already been annotated
|
154 |
existing_annotation = next((a for a in results["annotations"] if a["sample_id"] == sample_id), None)
|
|
|
156 |
# Restore previous rankings
|
157 |
for i, rank in enumerate(existing_annotation["rankings"]):
|
158 |
if i < len(new_rankings) and rank is not None:
|
159 |
+
new_rankings[i] = rank
|
160 |
|
161 |
# Update progress
|
162 |
current_idx = samples.index(sample)
|
|
|
199 |
json.dump(results, f, indent=2)
|
200 |
return f"✅ Results saved to {output_path} ({len(results['annotations'])} annotations)"
|
201 |
|
202 |
+
# Resolve rank conflicts automatically
|
203 |
+
def resolve_rank_conflicts(*ranks):
|
204 |
+
ranks = list(ranks)
|
205 |
+
# Convert to integers with validation
|
206 |
+
int_ranks = []
|
207 |
+
for r in ranks:
|
208 |
+
try:
|
209 |
+
r_int = int(r) if r is not None else None
|
210 |
+
if r_int is not None and (r_int < 1 or r_int > len(ranks)):
|
211 |
+
r_int = None
|
212 |
+
int_ranks.append(r_int)
|
213 |
+
except:
|
214 |
+
int_ranks.append(None)
|
215 |
+
|
216 |
+
# Find duplicates
|
217 |
+
seen = set()
|
218 |
+
duplicates = set()
|
219 |
+
for i, r in enumerate(int_ranks):
|
220 |
+
if r is None:
|
221 |
+
continue
|
222 |
+
if r in seen:
|
223 |
+
duplicates.add(r)
|
224 |
+
seen.add(r)
|
225 |
+
|
226 |
+
# Resolve duplicates by incrementing/shifting
|
227 |
+
for dup in sorted(duplicates):
|
228 |
+
indices = [i for i, r in enumerate(int_ranks) if r == dup]
|
229 |
+
# Keep the first occurrence, shift others
|
230 |
+
for idx in indices[1:]:
|
231 |
+
# Find the next available rank
|
232 |
+
next_rank = dup + 1
|
233 |
+
while next_rank in int_ranks and next_rank <= len(ranks):
|
234 |
+
next_rank += 1
|
235 |
+
if next_rank <= len(ranks):
|
236 |
+
int_ranks[idx] = next_rank
|
237 |
+
else:
|
238 |
+
# If no ranks available, find the first empty spot
|
239 |
+
for j in range(1, len(ranks) + 1):
|
240 |
+
if j not in int_ranks:
|
241 |
+
int_ranks[idx] = j
|
242 |
+
break
|
243 |
+
|
244 |
+
# Convert back to original type
|
245 |
+
return int_ranks
|
246 |
+
|
247 |
+
# Connect events
|
248 |
+
for i, rank_input in enumerate(ranking_inputs):
|
249 |
+
rank_input.change(
|
250 |
+
resolve_rank_conflicts,
|
251 |
+
inputs=ranking_inputs,
|
252 |
+
outputs=ranking_inputs
|
253 |
+
)
|
254 |
|
255 |
# Connect events
|
256 |
submit_btn.click(
|
257 |
+
save_ranking,
|
258 |
+
inputs=ranking_inputs + [current_sample_id],
|
259 |
outputs=[status_box, progress_text]
|
|
|
|
|
|
|
|
|
260 |
)
|
261 |
|
262 |
next_btn.click(
|
|
|
266 |
).then(
|
267 |
load_sample,
|
268 |
inputs=[current_sample_id],
|
269 |
+
outputs=[query_text] + doc_containers + ranking_inputs + [current_sample_id, progress_text, status_box]
|
|
|
|
|
|
|
|
|
270 |
)
|
271 |
|
272 |
prev_btn.click(
|
|
|
276 |
).then(
|
277 |
load_sample,
|
278 |
inputs=[current_sample_id],
|
279 |
+
outputs=[query_text] + doc_containers + ranking_inputs + [current_sample_id, progress_text, status_box]
|
|
|
|
|
|
|
|
|
280 |
)
|
281 |
|
282 |
save_btn.click(save_results, outputs=[status_box])
|
|
|
490 |
|
491 |
# Add download options
|
492 |
with gr.Row():
|
493 |
+
with gr.Column():
|
494 |
+
download_all_btn = gr.Button("Download All Results (ZIP)")
|
495 |
+
with gr.Column():
|
496 |
+
result_select = gr.Dropdown(choices=[f for f in os.listdir(".") if f.endswith("_human_results.json")], label="Select Result to Download", value=None)
|
497 |
+
download_selected_btn = gr.Button("Download Selected")
|
498 |
|
499 |
# Add results visualization placeholder
|
500 |
gr.Markdown("### Results Visualization")
|