AdnanElAssadi commited on
Commit
48294e4
·
verified ·
1 Parent(s): 5cee7bc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +268 -133
app.py CHANGED
@@ -54,162 +54,288 @@ def create_reranking_interface(task_data):
54
  gr.Markdown("## Query:")
55
  query_text = gr.Textbox(value=samples[0]["query"], label="", interactive=False, lines=3)
56
 
57
- # Validation
58
- with gr.Row():
59
- validate_btn = gr.Button("Validate Rankings", variant="secondary")
60
- validation_text = gr.Textbox(label="Validation", interactive=False)
61
-
62
  # Document ranking section
63
  gr.Markdown("## Documents to Rank:")
64
 
65
- # Container for document elements
66
- doc_containers = []
67
- rank_inputs = []
68
- doc_texts = []
69
 
70
- # Create a container for up to 10 documents
71
- max_docs = 10
72
- for i in range(max_docs):
73
- with gr.Group(visible=(i < len(samples[0]["candidates"]))) as doc_container:
74
- doc_containers.append(doc_container)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- with gr.Row():
77
- # Rank selection
78
- with gr.Column(scale=1, min_width=100):
79
- rank_input = gr.Number(
80
- value=i+1,
81
- label=f"Rank",
82
- minimum=1,
83
- maximum=len(samples[0]["candidates"]),
84
- step=1,
85
- interactive=True
86
- )
87
- rank_inputs.append(rank_input)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- # Document text
90
- with gr.Column(scale=4):
91
- doc_text = gr.Textbox(
92
- value=samples[0]["candidates"][i] if i < len(samples[0]["candidates"]) else "",
93
- label=f"Document {i+1}",
94
- lines=4,
95
- interactive=False
96
- )
97
- doc_texts.append(doc_text)
98
 
99
- gr.Markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  # Navigation and submission buttons
102
  with gr.Row():
103
  prev_btn = gr.Button("← Previous Query", size="sm")
 
104
  submit_btn = gr.Button("Submit Rankings", size="lg", variant="primary")
105
- next_btn = gr.Button("Next →", size="sm")
106
  save_btn = gr.Button("💾 Save All Results", variant="secondary")
107
 
108
- # Function to validate rankings
109
- def validate_rankings(*ranks):
110
- try:
111
- # Filter out None values
112
- valid_ranks = [int(r) for r in ranks if r is not None]
113
-
114
- # Check for duplicates
115
- if len(set(valid_ranks)) != len(valid_ranks):
116
- # Find duplicate ranks
117
- dupes = {}
118
- for r in valid_ranks:
119
- dupes[r] = dupes.get(r, 0) + 1
120
- duplicates = [r for r, count in dupes.items() if count > 1]
121
- return f"⚠️ Duplicate ranks found: {', '.join(str(d) for d in sorted(duplicates))}. Each document must have a unique rank."
122
-
123
- # Check for complete ranking
124
- max_rank = max(valid_ranks) if valid_ranks else 0
125
- expected_ranks = set(range(1, max_rank + 1))
126
- if set(valid_ranks) != expected_ranks:
127
- missing = sorted(expected_ranks - set(valid_ranks))
128
- if missing:
129
- return f"⚠️ Missing ranks: {', '.join(str(m) for m in missing)}. Ranks must be consecutive integers from 1 to {max_rank}."
130
-
131
- return "✓ Rankings are valid! Ready to submit."
132
- except Exception as e:
133
- return f"Error validating rankings: {str(e)}"
134
-
135
  # Function to load a sample
136
  def load_sample(sample_id):
137
  try:
138
  sample = next((s for s in samples if s["id"] == sample_id), None)
139
  if not sample:
140
- return [gr.update()] * (3 + 2*max_docs)
141
-
142
- candidates = sample["candidates"]
143
- num_docs = len(candidates)
144
 
145
  # Get existing ranking if available
146
  existing_ranking = next((anno["rankings"] for anno in results["annotations"] if anno["sample_id"] == sample_id), None)
147
 
148
- # Set default ranks (from existing or sequential)
149
- ranks = []
150
- for i in range(num_docs):
151
- if existing_ranking and i < len(existing_ranking):
152
- ranks.append(existing_ranking[i])
153
- else:
154
- ranks.append(i + 1)
155
-
156
- # Set container visibility
157
- container_visibility = [i < num_docs for i in range(max_docs)]
158
 
159
- # Update maximum values for number inputs
160
- for input_field in rank_inputs:
161
- input_field.maximum = num_docs
162
-
163
- # Fill in document contents
164
- docs = [candidates[i] if i < num_docs else "" for i in range(max_docs)]
165
-
166
- # Update visuals based on completed status
167
  status = "Already ranked" if completed_samples.get(sample_id, False) else "Ready to rank"
168
  progress = f"Progress: {sum(completed_samples.values())}/{len(samples)}"
169
 
170
- # Prepare all outputs
171
- outputs = [sample["query"], progress, status]
172
- outputs.extend(ranks) # Rank values
173
- outputs.extend(docs) # Document texts
174
- outputs.extend(container_visibility) # Container visibilities
175
-
176
- return outputs
177
  except Exception as e:
178
  import traceback
179
  print(traceback.format_exc())
180
- return [gr.update(value=f"Error loading sample: {str(e)}")] + [gr.update()] * (2 + 2*max_docs)
181
 
182
- # Function to save rankings
183
- def save_rankings(sample_id, *ranks):
184
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  # Get the sample
186
  sample = next((s for s in samples if s["id"] == sample_id), None)
187
  if not sample:
188
  return "⚠️ Sample not found", progress_text.value
189
 
 
 
 
 
190
  num_candidates = len(sample["candidates"])
 
191
 
192
- # Get the rankings for just this sample
193
- valid_ranks = [int(r) for r in ranks[:num_candidates] if r is not None]
 
 
 
 
194
 
195
- # Validate rankings
196
- if len(valid_ranks) != num_candidates:
197
- return f"⚠️ Not all documents have ranks. Expected {num_candidates}, got {len(valid_ranks)}.", progress_text.value
198
 
199
- if sorted(valid_ranks) != list(range(1, num_candidates + 1)):
200
- return "⚠️ Rankings must include all integers from 1 to " + str(num_candidates), progress_text.value
201
 
202
  # Create annotation
203
- annotation = {"sample_id": sample_id, "rankings": valid_ranks}
204
 
205
- # Update or add the annotation
206
  existing_idx = next((i for i, a in enumerate(results["annotations"]) if a["sample_id"] == sample_id), None)
207
  if existing_idx is not None:
208
  results["annotations"][existing_idx] = annotation
209
  else:
210
  results["annotations"].append(annotation)
211
 
212
- # Mark sample as completed
213
  completed_samples[sample_id] = True
214
 
215
  # Save to file
@@ -223,7 +349,7 @@ def create_reranking_interface(task_data):
223
  except Exception as e:
224
  import traceback
225
  print(traceback.format_exc())
226
- return f"Error saving rankings: {str(e)}", progress_text.value
227
 
228
  # Function to navigate to next sample
229
  def next_sample_id(current_id):
@@ -250,21 +376,19 @@ def create_reranking_interface(task_data):
250
  except Exception as e:
251
  return f"⚠️ Error saving results file: {str(e)}"
252
 
253
- # Connect validation button
254
  validate_btn.click(
255
- validate_rankings,
256
- inputs=rank_inputs,
257
- outputs=validation_text
258
  )
259
 
260
- # Connect submission button
261
  submit_btn.click(
262
- save_rankings,
263
- inputs=[current_sample_id] + rank_inputs,
264
  outputs=[status_box, progress_text]
265
  )
266
 
267
- # Connect navigation buttons
268
  next_btn.click(
269
  next_sample_id,
270
  inputs=[current_sample_id],
@@ -272,10 +396,15 @@ def create_reranking_interface(task_data):
272
  ).then(
273
  load_sample,
274
  inputs=[current_sample_id],
275
- outputs=[query_text, progress_text, status_box] +
276
- rank_inputs +
277
- doc_texts +
278
- doc_containers
 
 
 
 
 
279
  )
280
 
281
  prev_btn.click(
@@ -285,27 +414,33 @@ def create_reranking_interface(task_data):
285
  ).then(
286
  load_sample,
287
  inputs=[current_sample_id],
288
- outputs=[query_text, progress_text, status_box] +
289
- rank_inputs +
290
- doc_texts +
291
- doc_containers
 
 
 
 
 
292
  )
293
 
294
- # Connect save button
295
  save_btn.click(save_results, outputs=[status_box])
296
 
297
- # Initialize interface with first sample
298
  demo.load(
299
- lambda: load_sample(samples[0]['id']),
300
- outputs=[query_text, progress_text, status_box] +
301
- rank_inputs +
302
- doc_texts +
303
- doc_containers
 
 
 
 
 
304
  )
305
 
306
- # Add CSS styling
307
- demo.load(lambda: gr.Accordion.update(open=True), outputs=[])
308
-
309
  return demo
310
 
311
  # Main app with file upload capability
 
54
  gr.Markdown("## Query:")
55
  query_text = gr.Textbox(value=samples[0]["query"], label="", interactive=False, lines=3)
56
 
 
 
 
 
 
57
  # Document ranking section
58
  gr.Markdown("## Documents to Rank:")
59
 
60
+ # Create simple data structure for documents
61
+ doc_state = gr.State(value=samples[0]["candidates"])
 
 
62
 
63
+ # Create dynamic HTML for the ranking interface
64
+ def generate_ranking_html(docs, existing_ranks=None):
65
+ """Generate HTML for number-based ranking interface."""
66
+ if not docs:
67
+ return ""
68
+
69
+ # Use existing ranks if available
70
+ ranks = list(range(1, len(docs) + 1))
71
+ if existing_ranks and len(existing_ranks) == len(docs):
72
+ ranks = existing_ranks
73
+
74
+ html = """
75
+ <style>
76
+ .doc-container {
77
+ margin-bottom: 15px;
78
+ border: 1px solid #ddd;
79
+ border-radius: 8px;
80
+ padding: 15px;
81
+ background-color: #f9f9f9;
82
+ }
83
+ .doc-header {
84
+ display: flex;
85
+ align-items: center;
86
+ margin-bottom: 10px;
87
+ }
88
+ .doc-rank {
89
+ display: flex;
90
+ align-items: center;
91
+ margin-right: 15px;
92
+ }
93
+ .rank-label {
94
+ font-weight: bold;
95
+ margin-right: 8px;
96
+ min-width: 80px;
97
+ }
98
+ .rank-input {
99
+ width: 60px;
100
+ padding: 5px;
101
+ border: 2px solid #007bff;
102
+ border-radius: 4px;
103
+ text-align: center;
104
+ font-size: 16px;
105
+ }
106
+ .doc-content {
107
+ padding: 10px;
108
+ background-color: white;
109
+ border-radius: 4px;
110
+ border-left: 4px solid #007bff;
111
+ white-space: pre-wrap;
112
+ font-family: sans-serif;
113
+ line-height: 1.5;
114
+ }
115
+ </style>
116
+
117
+ <div id="ranking-form">
118
+ <input type="hidden" id="ranking-state" value="">
119
+ """
120
+
121
+ # Add each document with a number input
122
+ for i, doc in enumerate(docs):
123
+ import html as html_lib
124
+ escaped_doc = html_lib.escape(doc)
125
+ current_rank = ranks[i] if i < len(ranks) else i + 1
126
 
127
+ html += f"""
128
+ <div class="doc-container" id="doc-{i}">
129
+ <div class="doc-header">
130
+ <div class="doc-rank">
131
+ <span class="rank-label">Document {i+1} Rank:</span>
132
+ <input type="number" class="rank-input" id="rank-{i}" value="{current_rank}"
133
+ min="1" max="{len(docs)}" data-doc-id="{i}"
134
+ onchange="updateRankings()">
135
+ </div>
136
+ </div>
137
+ <div class="doc-content">{escaped_doc}</div>
138
+ </div>
139
+ """
140
+
141
+ # Add validation and state tracking JS
142
+ html += """
143
+ <script>
144
+ function updateRankings() {
145
+ // Collect all rank inputs
146
+ const inputs = document.querySelectorAll('.rank-input');
147
+ const rankings = [];
148
+
149
+ // Get values and highlight duplicates
150
+ const values = new Map();
151
+ const duplicates = new Set();
152
+
153
+ inputs.forEach(input => {
154
+ const docId = parseInt(input.getAttribute('data-doc-id'));
155
+ const rank = parseInt(input.value);
156
+
157
+ // Store value
158
+ rankings.push({
159
+ docId: docId,
160
+ rank: rank
161
+ });
162
+
163
+ // Check for duplicates
164
+ if (values.has(rank)) {
165
+ duplicates.add(rank);
166
+ } else {
167
+ values.set(rank, docId);
168
+ }
169
 
170
+ // Reset styling
171
+ input.style.borderColor = '#007bff';
172
+ });
 
 
 
 
 
 
173
 
174
+ // Highlight duplicates
175
+ inputs.forEach(input => {
176
+ const rank = parseInt(input.value);
177
+ if (duplicates.has(rank)) {
178
+ input.style.borderColor = '#ff3860';
179
+ }
180
+ });
181
+
182
+ // Store to hidden input
183
+ const stateInput = document.getElementById('ranking-state');
184
+ if (stateInput) {
185
+ stateInput.value = JSON.stringify(rankings);
186
+ }
187
+
188
+ // Update gradio text area
189
+ const textArea = document.querySelector('#rankings-state-input textarea');
190
+ if (textArea) {
191
+ textArea.value = JSON.stringify(rankings);
192
+ const event = new Event('input', { bubbles: true });
193
+ textArea.dispatchEvent(event);
194
+ }
195
+ }
196
+
197
+ // Initialize on page load
198
+ document.addEventListener('DOMContentLoaded', updateRankings);
199
+ // Also use a delay as a backup
200
+ setTimeout(updateRankings, 500);
201
+ </script>
202
+ </div>
203
+ """
204
+
205
+ return html
206
+
207
+ # Initial ranking HTML
208
+ ranking_html = gr.HTML(
209
+ generate_ranking_html(samples[0]["candidates"]),
210
+ elem_id="ranking-container"
211
+ )
212
+
213
+ # Hidden input for state
214
+ rankings_state = gr.Textbox(
215
+ value="[]",
216
+ visible=False,
217
+ elem_id="rankings-state-input"
218
+ )
219
+
220
+ # Validation message
221
+ validation_msg = gr.Textbox(
222
+ label="Validation",
223
+ interactive=False
224
+ )
225
 
226
  # Navigation and submission buttons
227
  with gr.Row():
228
  prev_btn = gr.Button("← Previous Query", size="sm")
229
+ validate_btn = gr.Button("Validate Rankings", variant="secondary")
230
  submit_btn = gr.Button("Submit Rankings", size="lg", variant="primary")
231
+ next_btn = gr.Button("Next Query →", size="sm")
232
  save_btn = gr.Button("💾 Save All Results", variant="secondary")
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  # Function to load a sample
235
  def load_sample(sample_id):
236
  try:
237
  sample = next((s for s in samples if s["id"] == sample_id), None)
238
  if not sample:
239
+ return sample_id, gr.update(), gr.update(), gr.update(), gr.update(), "[]", gr.update()
 
 
 
240
 
241
  # Get existing ranking if available
242
  existing_ranking = next((anno["rankings"] for anno in results["annotations"] if anno["sample_id"] == sample_id), None)
243
 
244
+ # Generate HTML with existing rankings if available
245
+ html = generate_ranking_html(sample["candidates"], existing_ranking)
 
 
 
 
 
 
 
 
246
 
247
+ # Update status
 
 
 
 
 
 
 
248
  status = "Already ranked" if completed_samples.get(sample_id, False) else "Ready to rank"
249
  progress = f"Progress: {sum(completed_samples.values())}/{len(samples)}"
250
 
251
+ return sample_id, sample["query"], html, progress, status, "[]", ""
 
 
 
 
 
 
252
  except Exception as e:
253
  import traceback
254
  print(traceback.format_exc())
255
+ return sample_id, gr.update(), gr.update(), gr.update(), f"Error: {str(e)}", "[]", ""
256
 
257
+ # Function to validate rankings from JSON state
258
+ def validate_ranking_state(state_json):
259
  try:
260
+ if not state_json or state_json == "[]":
261
+ return "Please rank all documents before submitting."
262
+
263
+ # Parse the state
264
+ state = json.loads(state_json)
265
+ if not state:
266
+ return "No ranking data found."
267
+
268
+ # Extract ranks
269
+ ranks = [item.get("rank") for item in state if "rank" in item]
270
+ if not ranks:
271
+ return "No valid ranks found."
272
+
273
+ # Check for duplicates
274
+ if len(set(ranks)) != len(ranks):
275
+ # Find duplicates
276
+ dupes = {}
277
+ for r in ranks:
278
+ dupes[r] = dupes.get(r, 0) + 1
279
+ duplicates = [r for r, count in dupes.items() if count > 1]
280
+ return f"⚠️ Duplicate ranks found: {', '.join(map(str, sorted(duplicates)))}. Each document must have a unique rank."
281
+
282
+ # Check for complete sequence
283
+ max_rank = max(ranks)
284
+ expected = set(range(1, max_rank + 1))
285
+ if set(ranks) != expected:
286
+ missing = sorted(expected - set(ranks))
287
+ return f"⚠️ Missing ranks: {', '.join(map(str, missing))}. Ranks must be consecutive from 1 to {max_rank}."
288
+
289
+ return "✅ Rankings are valid. Ready to submit."
290
+
291
+ except json.JSONDecodeError:
292
+ return "Error parsing ranking data."
293
+ except Exception as e:
294
+ return f"Error validating rankings: {str(e)}"
295
+
296
+ # Function to save rankings from JSON state
297
+ def save_ranking_state(sample_id, state_json):
298
+ try:
299
+ if not state_json or state_json == "[]":
300
+ return "Please rank all documents before submitting.", progress_text.value
301
+
302
  # Get the sample
303
  sample = next((s for s in samples if s["id"] == sample_id), None)
304
  if not sample:
305
  return "⚠️ Sample not found", progress_text.value
306
 
307
+ # Parse the state
308
+ state = json.loads(state_json)
309
+
310
+ # Create a rankings array in the correct order
311
  num_candidates = len(sample["candidates"])
312
+ rankings = [0] * num_candidates
313
 
314
+ # Fill in rankings from state
315
+ for item in state:
316
+ doc_id = item.get("docId")
317
+ rank = item.get("rank")
318
+ if doc_id is not None and doc_id < num_candidates and rank is not None:
319
+ rankings[doc_id] = rank
320
 
321
+ # Validate rankings
322
+ if any(r == 0 for r in rankings):
323
+ return "⚠️ Not all documents have rankings", progress_text.value
324
 
325
+ if sorted(rankings) != list(range(1, num_candidates + 1)):
326
+ return f"⚠️ Invalid ranking sequence. Please use each number from 1 to {num_candidates} exactly once.", progress_text.value
327
 
328
  # Create annotation
329
+ annotation = {"sample_id": sample_id, "rankings": rankings}
330
 
331
+ # Update or add to results
332
  existing_idx = next((i for i, a in enumerate(results["annotations"]) if a["sample_id"] == sample_id), None)
333
  if existing_idx is not None:
334
  results["annotations"][existing_idx] = annotation
335
  else:
336
  results["annotations"].append(annotation)
337
 
338
+ # Mark as completed
339
  completed_samples[sample_id] = True
340
 
341
  # Save to file
 
349
  except Exception as e:
350
  import traceback
351
  print(traceback.format_exc())
352
+ return f"⚠️ Error saving rankings: {str(e)}", progress_text.value
353
 
354
  # Function to navigate to next sample
355
  def next_sample_id(current_id):
 
376
  except Exception as e:
377
  return f"⚠️ Error saving results file: {str(e)}"
378
 
379
+ # Connect buttons
380
  validate_btn.click(
381
+ validate_ranking_state,
382
+ inputs=[rankings_state],
383
+ outputs=[validation_msg]
384
  )
385
 
 
386
  submit_btn.click(
387
+ save_ranking_state,
388
+ inputs=[current_sample_id, rankings_state],
389
  outputs=[status_box, progress_text]
390
  )
391
 
 
392
  next_btn.click(
393
  next_sample_id,
394
  inputs=[current_sample_id],
 
396
  ).then(
397
  load_sample,
398
  inputs=[current_sample_id],
399
+ outputs=[
400
+ current_sample_id,
401
+ query_text,
402
+ ranking_html,
403
+ progress_text,
404
+ status_box,
405
+ rankings_state,
406
+ validation_msg
407
+ ]
408
  )
409
 
410
  prev_btn.click(
 
414
  ).then(
415
  load_sample,
416
  inputs=[current_sample_id],
417
+ outputs=[
418
+ current_sample_id,
419
+ query_text,
420
+ ranking_html,
421
+ progress_text,
422
+ status_box,
423
+ rankings_state,
424
+ validation_msg
425
+ ]
426
  )
427
 
 
428
  save_btn.click(save_results, outputs=[status_box])
429
 
430
+ # Initialize with first sample
431
  demo.load(
432
+ lambda: load_sample(samples[0]['id']),
433
+ outputs=[
434
+ current_sample_id,
435
+ query_text,
436
+ ranking_html,
437
+ progress_text,
438
+ status_box,
439
+ rankings_state,
440
+ validation_msg
441
+ ]
442
  )
443
 
 
 
 
444
  return demo
445
 
446
  # Main app with file upload capability