Peiran commited on
Commit
6109248
·
1 Parent(s): 1688aaf

Login required + per-session batch (20), min raters filter (20), periodic reload, spaced repeats, and metrics logging (duration, flat scores); add hidden states and CSV fields; enforce HF-auth annotator id

Browse files
Files changed (2) hide show
  1. __pycache__/app.cpython-311.pyc +0 -0
  2. app.py +215 -9
__pycache__/app.cpython-311.pyc CHANGED
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
 
app.py CHANGED
@@ -4,7 +4,7 @@ import random
4
  import json
5
  import os
6
  import uuid
7
- from datetime import datetime
8
  from io import BytesIO
9
  from typing import Dict, List, Tuple, Optional
10
 
@@ -19,6 +19,13 @@ BASE_DIR = os.path.dirname(__file__)
19
  PERSIST_DIR = os.environ.get("PERSIST_DIR", "/data")
20
  # Persistent local storage inside HF Spaces
21
  PERSIST_DIR = os.environ.get("PERSIST_DIR", "/data")
 
 
 
 
 
 
 
22
  TASK_CONFIG = {
23
  "Scene Composition & Object Insertion": {
24
  "folder": "scene_composition_and_object_insertion",
@@ -171,6 +178,39 @@ def _read_user_done_keys(task_name: str, annotator_id: str) -> set:
171
  return keys
172
 
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  def _schedule_round_robin_by_test_id(pairs: List[Dict[str, str]], seed: Optional[int] = None) -> List[Dict[str, str]]:
175
  """Interleave pairs across test_ids for balanced coverage; shuffle within each group.
176
  """
@@ -203,8 +243,13 @@ def load_task(task_name: str, annotator_id: str = ""):
203
  def key_of(p: Dict[str, str]):
204
  return (p["test_id"], frozenset({p["model1_name"], p["model2_name"]}), p["org_img"])
205
  user_done_keys = _read_user_done_keys(task_name, annotator_id)
 
206
  global_counts = _read_eval_counts(task_name)
207
- pairs = [p for p in pairs_all if key_of(p) not in user_done_keys]
 
 
 
 
208
 
209
  # Balanced schedule: prioritize low-count pairs, and within same count do round-robin by test_id
210
  seed_env = os.environ.get("SCHEDULE_SEED")
@@ -219,6 +264,50 @@ def load_task(task_name: str, annotator_id: str = ""):
219
  ordered.extend(_schedule_round_robin_by_test_id(buckets[c], seed=seed))
220
  pairs = ordered
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  # Assign A/B order to counteract position bias: alternate after scheduling
223
  for idx, p in enumerate(pairs):
224
  p["swap"] = bool(idx % 2) # True -> A=B's image; False -> A=A's image
@@ -270,6 +359,13 @@ def _append_local_persist_csv(task_name: str, row: Dict[str, object]) -> bool:
270
  fieldnames = [
271
  "eval_date",
272
  "annotator_id",
 
 
 
 
 
 
 
273
  "test_id",
274
  "model1_name",
275
  "model2_name",
@@ -358,8 +454,26 @@ def _extract_annotator_id(request: Optional[gr.Request]) -> str:
358
  return ""
359
 
360
 
361
- def on_task_change(task_name: str, _state_pairs: List[Dict[str, str]], request: gr.Request):
 
362
  annotator_id = _extract_annotator_id(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  pairs = load_task(task_name, annotator_id)
364
  # Defaults for A and B (8 sliders total)
365
  default_scores = [3, 3, 3, 3, 3, 3, 3, 3]
@@ -372,7 +486,11 @@ def on_task_change(task_name: str, _state_pairs: List[Dict[str, str]], request:
372
  gr.update(value=None),
373
  gr.update(value=None),
374
  *default_scores,
375
- gr.update(value="No pending pairs. Either all pairs are already evaluated or data paths are invalid."),
 
 
 
 
376
  )
377
 
378
  pair = pairs[0]
@@ -389,11 +507,15 @@ def on_task_change(task_name: str, _state_pairs: List[Dict[str, str]], request:
389
  _resolve_image_path(a_path),
390
  _resolve_image_path(b_path),
391
  *default_scores,
392
- gr.update(value=f"Total {len(pairs)} pairs pending evaluation."),
 
 
 
 
393
  )
394
 
395
 
396
- def on_pair_navigate(index: int, pairs: List[Dict[str, str]]):
397
  if not pairs:
398
  # Gracefully no-op when no pairs
399
  return (
@@ -404,6 +526,7 @@ def on_pair_navigate(index: int, pairs: List[Dict[str, str]]):
404
  gr.update(value=None),
405
  3, 3, 3, 3, # A
406
  3, 3, 3, 3, # B
 
407
  )
408
  index = int(index)
409
  index = max(0, min(index, len(pairs) - 1))
@@ -419,6 +542,7 @@ def on_pair_navigate(index: int, pairs: List[Dict[str, str]]):
419
  _resolve_image_path(b_path),
420
  3, 3, 3, 3, # A
421
  3, 3, 3, 3, # B
 
422
  )
423
 
424
 
@@ -435,6 +559,10 @@ def on_submit(
435
  b_semantic_score: int,
436
  b_overall_score: int,
437
  request: gr.Request,
 
 
 
 
438
  ):
439
  if not task_name:
440
  return (
@@ -447,6 +575,10 @@ def on_submit(
447
  3, 3, 3, 3,
448
  3, 3, 3, 3,
449
  gr.update(value="Please select a task first."),
 
 
 
 
450
  )
451
 
452
  if not pairs:
@@ -460,6 +592,10 @@ def on_submit(
460
  3, 3, 3, 3,
461
  3, 3, 3, 3,
462
  gr.update(value="No pending pairs to submit."),
 
 
 
 
463
  )
464
 
465
  # Resolve annotator id from request
@@ -494,6 +630,20 @@ def on_submit(
494
  # Build record
495
  row = _build_eval_row(pair, score_map)
496
  row["annotator_id"] = annotator_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
  # Idempotency: check if this pair already evaluated; if so, skip writing
499
  done_keys = _read_user_done_keys(task_name, annotator_id)
@@ -518,6 +668,32 @@ def on_submit(
518
  info = f"{info_prefix} Local persistence " + ("succeeded" if ok_local else "skipped/failed") + "."
519
  info += " Dataset upload " + ("succeeded" if ok_hub else "failed") + (f" ({hub_msg})" if hub_msg else "") + "."
520
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  if remaining_pairs:
522
  next_index = min(index, len(remaining_pairs) - 1)
523
  pair = remaining_pairs[next_index]
@@ -534,6 +710,10 @@ def on_submit(
534
  3, 3, 3, 3,
535
  3, 3, 3, 3,
536
  gr.update(value=info + f" Next pair ({next_index + 1}/{len(remaining_pairs)})."),
 
 
 
 
537
  )
538
 
539
  # No remaining pairs: clear UI, hide slider, and return updated empty state
@@ -547,6 +727,10 @@ def on_submit(
547
  3, 3, 3, 3,
548
  3, 3, 3, 3,
549
  gr.update(value=info + " All pairs completed."),
 
 
 
 
550
  )
551
 
552
 
@@ -576,6 +760,11 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
576
  )
577
 
578
  pair_state = gr.State([])
 
 
 
 
 
579
 
580
  pair_header = gr.Markdown("")
581
 
@@ -604,7 +793,7 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
604
  # Event bindings
605
  task_selector.change(
606
  fn=on_task_change,
607
- inputs=[task_selector, pair_state],
608
  outputs=[
609
  pair_state,
610
  index_slider,
@@ -621,12 +810,16 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
621
  b_semantic_input,
622
  b_overall_input,
623
  feedback_box,
 
 
 
 
624
  ],
625
  )
626
 
627
  index_slider.release(
628
  fn=on_pair_navigate,
629
- inputs=[index_slider, pair_state],
630
  outputs=[
631
  index_slider,
632
  pair_header,
@@ -641,6 +834,7 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
641
  b_optical_input,
642
  b_semantic_input,
643
  b_overall_input,
 
644
  ],
645
  )
646
 
@@ -658,6 +852,10 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
658
  b_optical_input,
659
  b_semantic_input,
660
  b_overall_input,
 
 
 
 
661
  ],
662
  outputs=[
663
  pair_state,
@@ -675,13 +873,17 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
675
  b_semantic_input,
676
  b_overall_input,
677
  feedback_box,
 
 
 
 
678
  ],
679
  )
680
 
681
  # Auto-load default task on startup
682
  demo.load(
683
  fn=on_task_change,
684
- inputs=[task_selector, pair_state],
685
  outputs=[
686
  pair_state,
687
  index_slider,
@@ -698,6 +900,10 @@ with gr.Blocks(title="VisArena Human Evaluation") as demo:
698
  b_semantic_input,
699
  b_overall_input,
700
  feedback_box,
 
 
 
 
701
  ],
702
  )
703
 
 
4
  import json
5
  import os
6
  import uuid
7
+ from datetime import datetime, timedelta
8
  from io import BytesIO
9
  from typing import Dict, List, Tuple, Optional
10
 
 
19
  PERSIST_DIR = os.environ.get("PERSIST_DIR", "/data")
20
  # Persistent local storage inside HF Spaces
21
  PERSIST_DIR = os.environ.get("PERSIST_DIR", "/data")
22
+ # Evaluation knobs (can be overridden via env vars)
23
+ MIN_RATERS_PER_PAIR = int(os.environ.get("MIN_RATERS_PER_PAIR", 20))
24
+ BATCH_SIZE = int(os.environ.get("BATCH_SIZE", 20))
25
+ RELOAD_EVERY = int(os.environ.get("RELOAD_EVERY", 5))
26
+ REPEAT_RATE = float(os.environ.get("REPEAT_RATE", 0.05)) # fraction of repeats within batch
27
+ REPEAT_MIN_HOURS = float(os.environ.get("REPEAT_MIN_HOURS", 24))
28
+ FAST_MIN_SEC = float(os.environ.get("FAST_MIN_SEC", 2.0))
29
  TASK_CONFIG = {
30
  "Scene Composition & Object Insertion": {
31
  "folder": "scene_composition_and_object_insertion",
 
178
  return keys
179
 
180
 
181
+ def _read_user_last_times(task_name: str, annotator_id: str) -> Dict[Tuple[str, frozenset, str], datetime]:
182
+ """Return the user's last evaluation datetime per pair key."""
183
+ last: Dict[Tuple[str, frozenset, str], datetime] = {}
184
+ if not annotator_id:
185
+ return last
186
+ csv_path = _persist_csv_path_for_task(task_name)
187
+ if not os.path.exists(csv_path):
188
+ return last
189
+ try:
190
+ with open(csv_path, newline="", encoding="utf-8") as f:
191
+ reader = csv.DictReader(f)
192
+ for r in reader:
193
+ if str(r.get("annotator_id", "")).strip() != str(annotator_id).strip():
194
+ continue
195
+ tid = str(r.get("test_id", "")).strip()
196
+ m1 = str(r.get("model1_name", "")).strip()
197
+ m2 = str(r.get("model2_name", "")).strip()
198
+ org = str(r.get("org_img", "")).strip()
199
+ dt = str(r.get("eval_date", "")).strip() or str(r.get("submit_ts", "")).strip()
200
+ if not (tid and m1 and m2 and org and dt):
201
+ continue
202
+ key = (tid, frozenset({m1, m2}), org)
203
+ try:
204
+ t = datetime.fromisoformat(dt)
205
+ except Exception:
206
+ continue
207
+ if key not in last or t > last[key]:
208
+ last[key] = t
209
+ except Exception:
210
+ pass
211
+ return last
212
+
213
+
214
  def _schedule_round_robin_by_test_id(pairs: List[Dict[str, str]], seed: Optional[int] = None) -> List[Dict[str, str]]:
215
  """Interleave pairs across test_ids for balanced coverage; shuffle within each group.
216
  """
 
243
  def key_of(p: Dict[str, str]):
244
  return (p["test_id"], frozenset({p["model1_name"], p["model2_name"]}), p["org_img"])
245
  user_done_keys = _read_user_done_keys(task_name, annotator_id)
246
+ user_last_times = _read_user_last_times(task_name, annotator_id)
247
  global_counts = _read_eval_counts(task_name)
248
+ # Main eligible set: not done by this user and below min raters threshold
249
+ pairs = [
250
+ p for p in pairs_all
251
+ if key_of(p) not in user_done_keys and global_counts.get(key_of(p), 0) < MIN_RATERS_PER_PAIR
252
+ ]
253
 
254
  # Balanced schedule: prioritize low-count pairs, and within same count do round-robin by test_id
255
  seed_env = os.environ.get("SCHEDULE_SEED")
 
264
  ordered.extend(_schedule_round_robin_by_test_id(buckets[c], seed=seed))
265
  pairs = ordered
266
 
267
+ # Deterministic rotation by user's progress to avoid always starting from the same pairs
268
+ try:
269
+ elig_keys = [key_of(p) for p in pairs]
270
+ progress = len([k for k in user_done_keys if k in elig_keys])
271
+ if pairs:
272
+ rot = progress % len(pairs)
273
+ pairs = pairs[rot:] + pairs[:rot]
274
+ except Exception:
275
+ pass
276
+
277
+ # Limit batch size
278
+ main_batch = pairs[: max(0, BATCH_SIZE)]
279
+
280
+ # Small proportion of spaced repeats for test-retest
281
+ repeats: List[Dict[str, str]] = []
282
+ try:
283
+ repeat_target = int(max(0, round(BATCH_SIZE * REPEAT_RATE)))
284
+ if repeat_target > 0 and user_last_times:
285
+ min_time = datetime.utcnow() - timedelta(hours=REPEAT_MIN_HOURS)
286
+ candidates = [k for k, t in user_last_times.items() if t < min_time]
287
+ def find_pair_from_key(k):
288
+ tid, names, org = k
289
+ for p in pairs_all:
290
+ if p["test_id"] == tid and p["org_img"] == org and frozenset({p["model1_name"], p["model2_name"]}) == names:
291
+ return p
292
+ return None
293
+ picked = 0
294
+ used_keys = {key_of(p) for p in main_batch}
295
+ for k in candidates:
296
+ if picked >= repeat_target:
297
+ break
298
+ p = find_pair_from_key(k)
299
+ if not p:
300
+ continue
301
+ if key_of(p) in used_keys:
302
+ continue
303
+ repeats.append(p)
304
+ used_keys.add(key_of(p))
305
+ picked += 1
306
+ except Exception:
307
+ pass
308
+
309
+ pairs = main_batch + repeats
310
+
311
  # Assign A/B order to counteract position bias: alternate after scheduling
312
  for idx, p in enumerate(pairs):
313
  p["swap"] = bool(idx % 2) # True -> A=B's image; False -> A=A's image
 
359
  fieldnames = [
360
  "eval_date",
361
  "annotator_id",
362
+ "session_id",
363
+ "view_start_ts",
364
+ "submit_ts",
365
+ "duration_sec",
366
+ "is_fast",
367
+ "is_flat_a",
368
+ "is_flat_b",
369
  "test_id",
370
  "model1_name",
371
  "model2_name",
 
454
  return ""
455
 
456
 
457
+ def on_task_change(task_name: str, _state_pairs: List[Dict[str, str]], request: gr.Request,
458
+ view_started_at: float, session_quota: int, reload_count: int, session_id: str):
459
  annotator_id = _extract_annotator_id(request)
460
+ if not annotator_id:
461
+ default_scores = [3, 3, 3, 3, 3, 3, 3, 3]
462
+ return (
463
+ [],
464
+ gr.update(value=0, minimum=0, maximum=0, visible=False),
465
+ gr.update(value=""),
466
+ gr.update(value=None),
467
+ gr.update(value=None),
468
+ gr.update(value=None),
469
+ *default_scores,
470
+ gr.update(value="请先登录你的 Hugging Face 账户后再开始评测。"),
471
+ float(datetime.utcnow().timestamp()),
472
+ BATCH_SIZE,
473
+ 0,
474
+ session_id or str(uuid.uuid4()),
475
+ )
476
+
477
  pairs = load_task(task_name, annotator_id)
478
  # Defaults for A and B (8 sliders total)
479
  default_scores = [3, 3, 3, 3, 3, 3, 3, 3]
 
486
  gr.update(value=None),
487
  gr.update(value=None),
488
  *default_scores,
489
+ gr.update(value="当前没有待评对(或已达到最小标注阈值)。"),
490
+ float(datetime.utcnow().timestamp()),
491
+ BATCH_SIZE,
492
+ 0,
493
+ session_id or str(uuid.uuid4()),
494
  )
495
 
496
  pair = pairs[0]
 
507
  _resolve_image_path(a_path),
508
  _resolve_image_path(b_path),
509
  *default_scores,
510
+ gr.update(value=f"本批次分配 {len(pairs)} 组;目标每对 {MIN_RATERS_PER_PAIR} 人。"),
511
+ float(datetime.utcnow().timestamp()),
512
+ BATCH_SIZE,
513
+ 0,
514
+ session_id or str(uuid.uuid4()),
515
  )
516
 
517
 
518
+ def on_pair_navigate(index: int, pairs: List[Dict[str, str]], view_started_at: float):
519
  if not pairs:
520
  # Gracefully no-op when no pairs
521
  return (
 
526
  gr.update(value=None),
527
  3, 3, 3, 3, # A
528
  3, 3, 3, 3, # B
529
+ float(datetime.utcnow().timestamp()),
530
  )
531
  index = int(index)
532
  index = max(0, min(index, len(pairs) - 1))
 
542
  _resolve_image_path(b_path),
543
  3, 3, 3, 3, # A
544
  3, 3, 3, 3, # B
545
+ float(datetime.utcnow().timestamp()),
546
  )
547
 
548
 
 
559
  b_semantic_score: int,
560
  b_overall_score: int,
561
  request: gr.Request,
562
+ view_started_at: float,
563
+ session_quota: int,
564
+ reload_count: int,
565
+ session_id: str,
566
  ):
567
  if not task_name:
568
  return (
 
575
  3, 3, 3, 3,
576
  3, 3, 3, 3,
577
  gr.update(value="Please select a task first."),
578
+ float(datetime.utcnow().timestamp()),
579
+ session_quota,
580
+ reload_count,
581
+ session_id,
582
  )
583
 
584
  if not pairs:
 
592
  3, 3, 3, 3,
593
  3, 3, 3, 3,
594
  gr.update(value="No pending pairs to submit."),
595
+ float(datetime.utcnow().timestamp()),
596
+ session_quota,
597
+ reload_count,
598
+ session_id,
599
  )
600
 
601
  # Resolve annotator id from request
 
630
  # Build record
631
  row = _build_eval_row(pair, score_map)
632
  row["annotator_id"] = annotator_id
633
+ # timing + heuristics
634
+ submit_ts = datetime.utcnow()
635
+ try:
636
+ started = datetime.utcfromtimestamp(float(view_started_at)) if view_started_at else submit_ts
637
+ except Exception:
638
+ started = submit_ts
639
+ duration = max(0.0, (submit_ts - started).total_seconds())
640
+ row["view_start_ts"] = started.isoformat()
641
+ row["submit_ts"] = submit_ts.isoformat()
642
+ row["duration_sec"] = round(duration, 3)
643
+ row["is_fast"] = bool(duration < FAST_MIN_SEC)
644
+ row["is_flat_a"] = bool(len({int(a_physical_score), int(a_optical_score), int(a_semantic_score), int(a_overall_score)}) == 1)
645
+ row["is_flat_b"] = bool(len({int(b_physical_score), int(b_optical_score), int(b_semantic_score), int(b_overall_score)}) == 1)
646
+ row["session_id"] = session_id or str(uuid.uuid4())
647
 
648
  # Idempotency: check if this pair already evaluated; if so, skip writing
649
  done_keys = _read_user_done_keys(task_name, annotator_id)
 
668
  info = f"{info_prefix} Local persistence " + ("succeeded" if ok_local else "skipped/failed") + "."
669
  info += " Dataset upload " + ("succeeded" if ok_hub else "failed") + (f" ({hub_msg})" if hub_msg else "") + "."
670
 
671
+ # Quota + reload
672
+ session_quota = max(0, int(session_quota) - 1)
673
+ reload_count = int(reload_count) + 1
674
+ # Periodic reload to absorb new results.csv / re-balance
675
+ if reload_count >= RELOAD_EVERY:
676
+ fresh_pairs = load_task(task_name, annotator_id)
677
+ remaining_pairs = fresh_pairs
678
+ reload_count = 0
679
+
680
+ if session_quota <= 0:
681
+ return (
682
+ [],
683
+ gr.update(value=0, minimum=0, maximum=0, visible=False),
684
+ gr.update(value=""),
685
+ gr.update(value=None),
686
+ gr.update(value=None),
687
+ gr.update(value=None),
688
+ 3, 3, 3, 3,
689
+ 3, 3, 3, 3,
690
+ gr.update(value=info + " 本批次已完成 20 组,请刷新页面获取下一批次。"),
691
+ float(datetime.utcnow().timestamp()),
692
+ session_quota,
693
+ reload_count,
694
+ row["session_id"],
695
+ )
696
+
697
  if remaining_pairs:
698
  next_index = min(index, len(remaining_pairs) - 1)
699
  pair = remaining_pairs[next_index]
 
710
  3, 3, 3, 3,
711
  3, 3, 3, 3,
712
  gr.update(value=info + f" Next pair ({next_index + 1}/{len(remaining_pairs)})."),
713
+ float(datetime.utcnow().timestamp()),
714
+ session_quota,
715
+ reload_count,
716
+ row["session_id"],
717
  )
718
 
719
  # No remaining pairs: clear UI, hide slider, and return updated empty state
 
727
  3, 3, 3, 3,
728
  3, 3, 3, 3,
729
  gr.update(value=info + " All pairs completed."),
730
+ float(datetime.utcnow().timestamp()),
731
+ session_quota,
732
+ reload_count,
733
+ row["session_id"],
734
  )
735
 
736
 
 
760
  )
761
 
762
  pair_state = gr.State([])
763
+ # Hidden states for session control and metrics
764
+ view_started_at_state = gr.State(0.0)
765
+ session_quota_state = gr.State(BATCH_SIZE)
766
+ reload_count_state = gr.State(0)
767
+ session_id_state = gr.State("")
768
 
769
  pair_header = gr.Markdown("")
770
 
 
793
  # Event bindings
794
  task_selector.change(
795
  fn=on_task_change,
796
+ inputs=[task_selector, pair_state, gr.Request(), view_started_at_state, session_quota_state, reload_count_state, session_id_state],
797
  outputs=[
798
  pair_state,
799
  index_slider,
 
810
  b_semantic_input,
811
  b_overall_input,
812
  feedback_box,
813
+ view_started_at_state,
814
+ session_quota_state,
815
+ reload_count_state,
816
+ session_id_state,
817
  ],
818
  )
819
 
820
  index_slider.release(
821
  fn=on_pair_navigate,
822
+ inputs=[index_slider, pair_state, view_started_at_state],
823
  outputs=[
824
  index_slider,
825
  pair_header,
 
834
  b_optical_input,
835
  b_semantic_input,
836
  b_overall_input,
837
+ view_started_at_state,
838
  ],
839
  )
840
 
 
852
  b_optical_input,
853
  b_semantic_input,
854
  b_overall_input,
855
+ view_started_at_state,
856
+ session_quota_state,
857
+ reload_count_state,
858
+ session_id_state,
859
  ],
860
  outputs=[
861
  pair_state,
 
873
  b_semantic_input,
874
  b_overall_input,
875
  feedback_box,
876
+ view_started_at_state,
877
+ session_quota_state,
878
+ reload_count_state,
879
+ session_id_state,
880
  ],
881
  )
882
 
883
  # Auto-load default task on startup
884
  demo.load(
885
  fn=on_task_change,
886
+ inputs=[task_selector, pair_state, gr.Request(), view_started_at_state, session_quota_state, reload_count_state, session_id_state],
887
  outputs=[
888
  pair_state,
889
  index_slider,
 
900
  b_semantic_input,
901
  b_overall_input,
902
  feedback_box,
903
+ view_started_at_state,
904
+ session_quota_state,
905
+ reload_count_state,
906
+ session_id_state,
907
  ],
908
  )
909