vargha commited on
Commit
c7d38be
·
1 Parent(s): b3a5f93

auto-load enabled and progress bar

Browse files
Files changed (1) hide show
  1. components/review_dashboard_page.py +78 -100
components/review_dashboard_page.py CHANGED
@@ -106,13 +106,6 @@ class ReviewDashboardPage:
106
  def register_callbacks(self, login_page, session_state: gr.State, root_blocks: gr.Blocks):
107
  self.header.register_callbacks(login_page, self, session_state)
108
 
109
- # Register progress update callback
110
- self.load_trigger.change(
111
- fn=get_review_progress_fn,
112
- inputs=[session_state],
113
- outputs=self.header.progress_display
114
- )
115
-
116
  def update_ui_interactive_state(is_interactive: bool):
117
  updates = []
118
  for elem in self.interactive_ui_elements:
@@ -183,6 +176,58 @@ class ReviewDashboardPage:
183
 
184
  return validation_status, is_deleted
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  def load_review_items_fn(session):
187
  user_id = session.get("user_id")
188
  username = session.get("username")
@@ -378,81 +423,12 @@ class ReviewDashboardPage:
378
  current_item["annotated_at"],
379
  current_item["validation_status"],
380
  "", # Placeholder for annotator_name
381
- gr.update(value=None, autoplay=False), # Reset audio (will be loaded manually)
382
  gr.update(visible=rejection_visible, value=rejection_reason),
383
  False, # Reset rejection mode
384
  gr.update(value="❌ Reject") # Reset reject button text
385
  )
386
 
387
- def get_review_progress_fn(session):
388
- """Get progress for reviewer showing how many items they've reviewed"""
389
- user_id = session.get("user_id")
390
- username = session.get("username")
391
-
392
- if not user_id or not username:
393
- return "Review Progress: N/A"
394
-
395
- # Check if user is a reviewer
396
- if username not in conf.REVIEW_MAPPING.values():
397
- return "Review Progress: N/A (Not a reviewer)"
398
-
399
- # Find which annotator this user should review
400
- target_annotator = None
401
- for annotator_name, reviewer_name in conf.REVIEW_MAPPING.items():
402
- if reviewer_name == username:
403
- target_annotator = annotator_name
404
- break
405
-
406
- if not target_annotator:
407
- return "Review Progress: N/A (No assignment)"
408
-
409
- with get_db() as db:
410
- try:
411
- # Get target annotator's ID
412
- target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
413
- if not target_annotator_obj:
414
- return "Review Progress: N/A (Annotator not found)"
415
-
416
- # Count total annotations by target annotator
417
- total_annotations = db.query(Annotation).filter(
418
- Annotation.annotator_id == target_annotator_obj.id
419
- ).count()
420
-
421
- # Count reviewed annotations (both approved and rejected)
422
- reviewed_count = db.query(Validation).filter(
423
- Validation.validator_id == user_id
424
- ).join(
425
- Annotation, Validation.annotation_id == Annotation.id
426
- ).filter(
427
- Annotation.annotator_id == target_annotator_obj.id
428
- ).count()
429
-
430
- if total_annotations > 0:
431
- percent = (reviewed_count / total_annotations) * 100
432
- bar_length = 20 # Length of the progress bar
433
- filled_length = int(bar_length * reviewed_count // total_annotations)
434
- bar = '█' * filled_length + '░' * (bar_length - filled_length)
435
- return f"Review Progress: {bar} {reviewed_count}/{total_annotations} ({percent:.1f}%)"
436
- else:
437
- return "Review Progress: No items to review"
438
-
439
- except Exception as e:
440
- log.error(f"Error calculating review progress: {e}")
441
- return "Review Progress: Error calculating"
442
-
443
- def auto_load_audio_on_navigate_fn(filename_to_load):
444
- """Auto-load audio when navigating between items for smooth UX"""
445
- if not filename_to_load:
446
- return None, None, gr.update(value=None, autoplay=False)
447
- try:
448
- log.info(f"Auto-loading audio for navigation: {filename_to_load}")
449
- sr, wav = LOADER.load_audio(filename_to_load)
450
- log.info(f"Auto-loaded audio: {filename_to_load} (SR: {sr}, Length: {len(wav)} samples)")
451
- return (sr, wav), (sr, wav.copy()), gr.update(value=(sr, wav), autoplay=True)
452
- except Exception as e:
453
- log.error(f"Auto audio load failed for {filename_to_load}: {e}")
454
- return None, None, gr.update(value=None, autoplay=False)
455
-
456
  def navigate_review_fn(items, current_idx, direction):
457
  if not items:
458
  return 0
@@ -674,6 +650,10 @@ class ReviewDashboardPage:
674
  fn=load_review_items_fn,
675
  inputs=[session_state],
676
  outputs=[self.items_state, self.idx_state, self.review_info] + review_display_outputs
 
 
 
 
677
  ).then(
678
  fn=lambda: (None, gr.update(value=None)), # Clear audio state
679
  outputs=[self.original_audio_state, self.audio]
@@ -685,7 +665,7 @@ class ReviewDashboardPage:
685
  # Audio loading is now manual only via the Load Audio button
686
  # Removed automatic filename.change callback to prevent slow loading during initialization
687
 
688
- # Navigation buttons with auto-audio loading
689
  for btn, direction in [(self.btn_prev, "prev"), (self.btn_next, "next")]:
690
  btn.click(
691
  fn=lambda: update_ui_interactive_state(False),
@@ -699,8 +679,8 @@ class ReviewDashboardPage:
699
  inputs=[self.items_state, self.idx_state, session_state],
700
  outputs=review_display_outputs
701
  ).then(
702
- # Auto-load audio on navigation for smooth UX
703
- fn=auto_load_audio_on_navigate_fn,
704
  inputs=[self.filename],
705
  outputs=[self.audio, self.original_audio_state, self.audio]
706
  ).then(
@@ -711,22 +691,21 @@ class ReviewDashboardPage:
711
  outputs=self.interactive_ui_elements
712
  )
713
 
714
- # Approve/Reject buttons with auto-audio loading and progress updates
715
  self.btn_approve.click(
716
  fn=lambda items, idx, session: save_validation_fn(items, idx, session, approved=True, rejection_reason=""), # Pass empty rejection_reason
717
  inputs=[self.items_state, self.idx_state, session_state],
718
  outputs=[self.items_state, self.current_validation_status, self.rejection_reason_input]
 
 
 
 
719
  ).then(
720
  fn=lambda: False, # Reset rejection mode
721
  outputs=[self.rejection_mode_active]
722
  ).then(
723
  fn=lambda: gr.update(value="❌ Reject"), # Reset reject button
724
  outputs=[self.btn_reject]
725
- ).then(
726
- # Update progress after approval
727
- fn=get_review_progress_fn,
728
- inputs=[session_state],
729
- outputs=self.header.progress_display
730
  ).then(
731
  fn=lambda items, idx: navigate_review_fn(items, idx, "next"),
732
  inputs=[self.items_state, self.idx_state],
@@ -736,8 +715,8 @@ class ReviewDashboardPage:
736
  inputs=[self.items_state, self.idx_state, session_state],
737
  outputs=review_display_outputs
738
  ).then(
739
- # Auto-load audio for next item
740
- fn=auto_load_audio_on_navigate_fn,
741
  inputs=[self.filename],
742
  outputs=[self.audio, self.original_audio_state, self.audio]
743
  )
@@ -747,10 +726,9 @@ class ReviewDashboardPage:
747
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_reason_input, self.rejection_mode_active],
748
  outputs=[self.items_state, self.current_validation_status, self.rejection_reason_input, self.rejection_mode_active, self.btn_reject]
749
  ).then(
750
- # Update progress after rejection (only if completed)
751
- fn=lambda session, rejection_mode: get_review_progress_fn(session) if not rejection_mode else "",
752
- inputs=[session_state, self.rejection_mode_active],
753
- outputs=self.header.progress_display
754
  ).then(
755
  fn=lambda items, idx, rejection_mode: navigate_review_fn(items, idx, "next") if not rejection_mode else idx,
756
  inputs=[self.items_state, self.idx_state, self.rejection_mode_active],
@@ -772,13 +750,13 @@ class ReviewDashboardPage:
772
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
773
  outputs=review_display_outputs
774
  ).then(
775
- # Auto-load audio for next item (only if completed rejection)
776
- fn=lambda filename, rejection_mode: auto_load_audio_on_navigate_fn(filename) if not rejection_mode else (None, None, gr.update()),
777
  inputs=[self.filename, self.rejection_mode_active],
778
  outputs=[self.audio, self.original_audio_state, self.audio]
779
  )
780
 
781
- # Skip button (just navigate to next) with auto-audio loading
782
  self.btn_skip.click(
783
  fn=navigate_review_fn,
784
  inputs=[self.items_state, self.idx_state, gr.State("next")],
@@ -788,13 +766,13 @@ class ReviewDashboardPage:
788
  inputs=[self.items_state, self.idx_state, session_state],
789
  outputs=review_display_outputs
790
  ).then(
791
- # Auto-load audio for next item
792
- fn=auto_load_audio_on_navigate_fn,
793
  inputs=[self.filename],
794
  outputs=[self.audio, self.original_audio_state, self.audio]
795
  )
796
 
797
- # Jump button with auto-audio loading
798
  self.btn_jump.click(
799
  fn=jump_by_data_id_fn,
800
  inputs=[self.items_state, self.jump_data_id_input, self.idx_state],
@@ -804,8 +782,8 @@ class ReviewDashboardPage:
804
  inputs=[self.items_state, self.idx_state, session_state],
805
  outputs=review_display_outputs
806
  ).then(
807
- # Auto-load audio for jumped item
808
- fn=auto_load_audio_on_navigate_fn,
809
  inputs=[self.filename],
810
  outputs=[self.audio, self.original_audio_state, self.audio]
811
  ).then(
 
106
  def register_callbacks(self, login_page, session_state: gr.State, root_blocks: gr.Blocks):
107
  self.header.register_callbacks(login_page, self, session_state)
108
 
 
 
 
 
 
 
 
109
  def update_ui_interactive_state(is_interactive: bool):
110
  updates = []
111
  for elem in self.interactive_ui_elements:
 
176
 
177
  return validation_status, is_deleted
178
 
179
+ def get_review_progress_fn(session):
180
+ """Calculate review progress for the current reviewer"""
181
+ user_id = session.get("user_id")
182
+ username = session.get("username")
183
+
184
+ if not user_id or not username:
185
+ return "Review Progress: N/A"
186
+
187
+ # Check if user is a reviewer
188
+ if username not in conf.REVIEW_MAPPING.values():
189
+ return "Review Progress: N/A (Not a reviewer)"
190
+
191
+ # Find target annotator
192
+ target_annotator = None
193
+ for annotator_name, reviewer_name in conf.REVIEW_MAPPING.items():
194
+ if reviewer_name == username:
195
+ target_annotator = annotator_name
196
+ break
197
+
198
+ if not target_annotator:
199
+ return "Review Progress: N/A (No target annotator)"
200
+
201
+ with get_db() as db:
202
+ try:
203
+ # Get target annotator's ID
204
+ target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
205
+ if not target_annotator_obj:
206
+ return f"Review Progress: Error (Annotator '{target_annotator}' not found)"
207
+
208
+ # Count total annotations for target annotator
209
+ total_count = db.query(Annotation).filter(
210
+ Annotation.annotator_id == target_annotator_obj.id
211
+ ).count()
212
+
213
+ # Count reviewed annotations (have validation from this reviewer)
214
+ reviewed_count = db.query(Annotation).join(
215
+ Validation, Annotation.id == Validation.annotation_id
216
+ ).filter(
217
+ Annotation.annotator_id == target_annotator_obj.id,
218
+ Validation.validator_id == user_id
219
+ ).count()
220
+
221
+ if total_count > 0:
222
+ percentage = (reviewed_count / total_count) * 100
223
+ return f"**Review Progress:** {reviewed_count}/{total_count} ({percentage:.1f}%) - Reviewing {target_annotator}'s work"
224
+ else:
225
+ return f"**Review Progress:** No items found for {target_annotator}"
226
+
227
+ except Exception as e:
228
+ log.error(f"Error calculating review progress for user {user_id}: {e}")
229
+ return "Review Progress: Error calculating progress"
230
+
231
  def load_review_items_fn(session):
232
  user_id = session.get("user_id")
233
  username = session.get("username")
 
423
  current_item["annotated_at"],
424
  current_item["validation_status"],
425
  "", # Placeholder for annotator_name
426
+ gr.update(value=None, autoplay=False),
427
  gr.update(visible=rejection_visible, value=rejection_reason),
428
  False, # Reset rejection mode
429
  gr.update(value="❌ Reject") # Reset reject button text
430
  )
431
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  def navigate_review_fn(items, current_idx, direction):
433
  if not items:
434
  return 0
 
650
  fn=load_review_items_fn,
651
  inputs=[session_state],
652
  outputs=[self.items_state, self.idx_state, self.review_info] + review_display_outputs
653
+ ).then(
654
+ fn=get_review_progress_fn,
655
+ inputs=[session_state],
656
+ outputs=[self.header.progress_display]
657
  ).then(
658
  fn=lambda: (None, gr.update(value=None)), # Clear audio state
659
  outputs=[self.original_audio_state, self.audio]
 
665
  # Audio loading is now manual only via the Load Audio button
666
  # Removed automatic filename.change callback to prevent slow loading during initialization
667
 
668
+ # Navigation buttons
669
  for btn, direction in [(self.btn_prev, "prev"), (self.btn_next, "next")]:
670
  btn.click(
671
  fn=lambda: update_ui_interactive_state(False),
 
679
  inputs=[self.items_state, self.idx_state, session_state],
680
  outputs=review_display_outputs
681
  ).then(
682
+ # Auto-load audio with autoplay for smooth navigation
683
+ fn=download_voice_fn,
684
  inputs=[self.filename],
685
  outputs=[self.audio, self.original_audio_state, self.audio]
686
  ).then(
 
691
  outputs=self.interactive_ui_elements
692
  )
693
 
694
+ # Approve/Reject buttons
695
  self.btn_approve.click(
696
  fn=lambda items, idx, session: save_validation_fn(items, idx, session, approved=True, rejection_reason=""), # Pass empty rejection_reason
697
  inputs=[self.items_state, self.idx_state, session_state],
698
  outputs=[self.items_state, self.current_validation_status, self.rejection_reason_input]
699
+ ).then(
700
+ fn=get_review_progress_fn, # Update progress after approval
701
+ inputs=[session_state],
702
+ outputs=[self.header.progress_display]
703
  ).then(
704
  fn=lambda: False, # Reset rejection mode
705
  outputs=[self.rejection_mode_active]
706
  ).then(
707
  fn=lambda: gr.update(value="❌ Reject"), # Reset reject button
708
  outputs=[self.btn_reject]
 
 
 
 
 
709
  ).then(
710
  fn=lambda items, idx: navigate_review_fn(items, idx, "next"),
711
  inputs=[self.items_state, self.idx_state],
 
715
  inputs=[self.items_state, self.idx_state, session_state],
716
  outputs=review_display_outputs
717
  ).then(
718
+ # Auto-load audio with autoplay after moving to next item
719
+ fn=download_voice_fn,
720
  inputs=[self.filename],
721
  outputs=[self.audio, self.original_audio_state, self.audio]
722
  )
 
726
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_reason_input, self.rejection_mode_active],
727
  outputs=[self.items_state, self.current_validation_status, self.rejection_reason_input, self.rejection_mode_active, self.btn_reject]
728
  ).then(
729
+ fn=lambda items, idx, session, rejection_mode: get_review_progress_fn(session) if not rejection_mode else "", # Update progress only after successful rejection
730
+ inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
731
+ outputs=[self.header.progress_display]
 
732
  ).then(
733
  fn=lambda items, idx, rejection_mode: navigate_review_fn(items, idx, "next") if not rejection_mode else idx,
734
  inputs=[self.items_state, self.idx_state, self.rejection_mode_active],
 
750
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
751
  outputs=review_display_outputs
752
  ).then(
753
+ # Auto-load audio with autoplay only if we moved to next item (not in rejection mode)
754
+ fn=lambda filename, rejection_mode: download_voice_fn(filename) if not rejection_mode else (None, None, gr.update(value=None, autoplay=False)),
755
  inputs=[self.filename, self.rejection_mode_active],
756
  outputs=[self.audio, self.original_audio_state, self.audio]
757
  )
758
 
759
+ # Skip button (just navigate to next)
760
  self.btn_skip.click(
761
  fn=navigate_review_fn,
762
  inputs=[self.items_state, self.idx_state, gr.State("next")],
 
766
  inputs=[self.items_state, self.idx_state, session_state],
767
  outputs=review_display_outputs
768
  ).then(
769
+ # Auto-load audio with autoplay after skipping
770
+ fn=download_voice_fn,
771
  inputs=[self.filename],
772
  outputs=[self.audio, self.original_audio_state, self.audio]
773
  )
774
 
775
+ # Jump button
776
  self.btn_jump.click(
777
  fn=jump_by_data_id_fn,
778
  inputs=[self.items_state, self.jump_data_id_input, self.idx_state],
 
782
  inputs=[self.items_state, self.idx_state, session_state],
783
  outputs=review_display_outputs
784
  ).then(
785
+ # Auto-load audio with autoplay after jumping
786
+ fn=download_voice_fn,
787
  inputs=[self.filename],
788
  outputs=[self.audio, self.original_audio_state, self.audio]
789
  ).then(