Spaces:
Running
Running
fixing batching for loading data
Browse files- assets/styles.css +178 -0
- components/review_dashboard_page.py +24 -33
assets/styles.css
CHANGED
@@ -2,3 +2,181 @@
|
|
2 |
align-items: center;
|
3 |
padding: 12px 12px;
|
4 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
align-items: center;
|
3 |
padding: 12px 12px;
|
4 |
}
|
5 |
+
|
6 |
+
/* Beautiful tqdm-style Progress Bar Styles */
|
7 |
+
.progress-container {
|
8 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
9 |
+
border: 1px solid #dee2e6;
|
10 |
+
border-radius: 12px;
|
11 |
+
padding: 16px;
|
12 |
+
margin: 8px 0;
|
13 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
14 |
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
15 |
+
}
|
16 |
+
|
17 |
+
.progress-header {
|
18 |
+
display: flex;
|
19 |
+
align-items: center;
|
20 |
+
gap: 8px;
|
21 |
+
margin-bottom: 12px;
|
22 |
+
font-size: 14px;
|
23 |
+
color: #495057;
|
24 |
+
}
|
25 |
+
|
26 |
+
.progress-icon {
|
27 |
+
font-size: 16px;
|
28 |
+
animation: pulse 2s infinite;
|
29 |
+
}
|
30 |
+
|
31 |
+
.progress-bar-container {
|
32 |
+
display: flex;
|
33 |
+
align-items: center;
|
34 |
+
gap: 12px;
|
35 |
+
margin-bottom: 10px;
|
36 |
+
}
|
37 |
+
|
38 |
+
.progress-percentage {
|
39 |
+
font-weight: bold;
|
40 |
+
font-size: 13px;
|
41 |
+
color: #495057;
|
42 |
+
min-width: 50px;
|
43 |
+
text-align: right;
|
44 |
+
}
|
45 |
+
|
46 |
+
.progress-bar {
|
47 |
+
flex: 1;
|
48 |
+
height: 8px;
|
49 |
+
background-color: #e9ecef;
|
50 |
+
border-radius: 4px;
|
51 |
+
overflow: hidden;
|
52 |
+
position: relative;
|
53 |
+
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
54 |
+
}
|
55 |
+
|
56 |
+
.progress-fill {
|
57 |
+
height: 100%;
|
58 |
+
border-radius: 4px;
|
59 |
+
transition: width 0.3s ease;
|
60 |
+
position: relative;
|
61 |
+
overflow: hidden;
|
62 |
+
}
|
63 |
+
|
64 |
+
.progress-fill::after {
|
65 |
+
content: '';
|
66 |
+
position: absolute;
|
67 |
+
top: 0;
|
68 |
+
left: 0;
|
69 |
+
bottom: 0;
|
70 |
+
right: 0;
|
71 |
+
background-image: linear-gradient(
|
72 |
+
-45deg,
|
73 |
+
rgba(255, 255, 255, 0.2) 25%,
|
74 |
+
transparent 25%,
|
75 |
+
transparent 50%,
|
76 |
+
rgba(255, 255, 255, 0.2) 50%,
|
77 |
+
rgba(255, 255, 255, 0.2) 75%,
|
78 |
+
transparent 75%,
|
79 |
+
transparent
|
80 |
+
);
|
81 |
+
background-size: 20px 20px;
|
82 |
+
animation: progress-stripes 1s linear infinite;
|
83 |
+
}
|
84 |
+
|
85 |
+
@keyframes progress-stripes {
|
86 |
+
0% { background-position: 0 0; }
|
87 |
+
100% { background-position: 20px 0; }
|
88 |
+
}
|
89 |
+
|
90 |
+
.progress-bar-low .progress-fill {
|
91 |
+
background: linear-gradient(90deg, #dc3545, #e74c3c);
|
92 |
+
}
|
93 |
+
|
94 |
+
.progress-bar-medium-low .progress-fill {
|
95 |
+
background: linear-gradient(90deg, #ffc107, #f39c12);
|
96 |
+
}
|
97 |
+
|
98 |
+
.progress-bar-medium .progress-fill {
|
99 |
+
background: linear-gradient(90deg, #fd7e14, #e67e22);
|
100 |
+
}
|
101 |
+
|
102 |
+
.progress-bar-high .progress-fill {
|
103 |
+
background: linear-gradient(90deg, #28a745, #27ae60);
|
104 |
+
}
|
105 |
+
|
106 |
+
.progress-bar-complete .progress-fill {
|
107 |
+
background: linear-gradient(90deg, #20c997, #1abc9c);
|
108 |
+
animation: complete-pulse 1.5s ease-in-out infinite;
|
109 |
+
}
|
110 |
+
|
111 |
+
@keyframes complete-pulse {
|
112 |
+
0%, 100% { opacity: 1; }
|
113 |
+
50% { opacity: 0.8; }
|
114 |
+
}
|
115 |
+
|
116 |
+
.progress-stats {
|
117 |
+
font-weight: bold;
|
118 |
+
font-size: 13px;
|
119 |
+
color: #495057;
|
120 |
+
min-width: 60px;
|
121 |
+
}
|
122 |
+
|
123 |
+
.progress-details {
|
124 |
+
display: flex;
|
125 |
+
align-items: center;
|
126 |
+
gap: 8px;
|
127 |
+
font-size: 12px;
|
128 |
+
color: #6c757d;
|
129 |
+
margin-top: 8px;
|
130 |
+
}
|
131 |
+
|
132 |
+
.progress-details code {
|
133 |
+
background: #f8f9fa;
|
134 |
+
border: 1px solid #e9ecef;
|
135 |
+
border-radius: 4px;
|
136 |
+
padding: 2px 6px;
|
137 |
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
138 |
+
font-size: 11px;
|
139 |
+
letter-spacing: 0.5px;
|
140 |
+
color: #495057;
|
141 |
+
}
|
142 |
+
|
143 |
+
.remaining-items {
|
144 |
+
color: #6c757d;
|
145 |
+
font-style: italic;
|
146 |
+
}
|
147 |
+
|
148 |
+
@keyframes pulse {
|
149 |
+
0%, 100% { transform: scale(1); }
|
150 |
+
50% { transform: scale(1.1); }
|
151 |
+
}
|
152 |
+
|
153 |
+
/* Dark mode support */
|
154 |
+
@media (prefers-color-scheme: dark) {
|
155 |
+
.progress-container {
|
156 |
+
background: linear-gradient(135deg, #343a40 0%, #495057 100%);
|
157 |
+
border-color: #6c757d;
|
158 |
+
color: #f8f9fa;
|
159 |
+
}
|
160 |
+
|
161 |
+
.progress-header, .progress-percentage, .progress-stats {
|
162 |
+
color: #f8f9fa;
|
163 |
+
}
|
164 |
+
|
165 |
+
.progress-bar {
|
166 |
+
background-color: #495057;
|
167 |
+
}
|
168 |
+
|
169 |
+
.progress-details {
|
170 |
+
color: #adb5bd;
|
171 |
+
}
|
172 |
+
|
173 |
+
.progress-details code {
|
174 |
+
background: #495057;
|
175 |
+
border-color: #6c757d;
|
176 |
+
color: #f8f9fa;
|
177 |
+
}
|
178 |
+
|
179 |
+
.remaining-items {
|
180 |
+
color: #adb5bd;
|
181 |
+
}
|
182 |
+
}
|
components/review_dashboard_page.py
CHANGED
@@ -481,15 +481,23 @@ class ReviewDashboardPage:
|
|
481 |
|
482 |
def navigate_review_fn(items, current_idx, direction):
|
483 |
if not items:
|
484 |
-
return 0
|
485 |
if direction == "next":
|
486 |
new_idx = min(current_idx + 1, len(items) - 1)
|
487 |
-
#
|
488 |
-
|
489 |
-
|
490 |
-
|
|
|
491 |
else: # prev
|
492 |
-
return max(current_idx - 1, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
493 |
|
494 |
def save_validation_fn(items, idx, session, approved: bool, rejection_reason: str = ""):
|
495 |
if not items or idx >= len(items):
|
@@ -600,7 +608,7 @@ class ReviewDashboardPage:
|
|
600 |
# gr.Warning(f"Invalid Data ID format: {target_data_id}")
|
601 |
return current_idx
|
602 |
|
603 |
-
def load_more_items_fn(items, session, current_batch_size=
|
604 |
"""Load more items when user needs them (pagination support)"""
|
605 |
user_id = session.get("user_id")
|
606 |
username = session.get("username")
|
@@ -625,44 +633,26 @@ class ReviewDashboardPage:
|
|
625 |
|
626 |
# Load next batch starting from where we left off
|
627 |
offset = len(items)
|
|
|
|
|
628 |
query = db.query(
|
629 |
Annotation,
|
630 |
TTSData.filename,
|
631 |
-
TTSData.sentence
|
632 |
-
Validation.validated,
|
633 |
-
Validation.description
|
634 |
).join(
|
635 |
TTSData, Annotation.tts_data_id == TTSData.id
|
636 |
-
).outerjoin(
|
637 |
-
Validation,
|
638 |
-
(Validation.annotation_id == Annotation.id) &
|
639 |
-
(Validation.validator_id == user_id)
|
640 |
).filter(
|
641 |
Annotation.annotator_id == target_annotator_obj.id
|
642 |
).order_by(Annotation.id).offset(offset).limit(current_batch_size)
|
643 |
|
644 |
results = query.all()
|
645 |
|
646 |
-
# Process new items
|
647 |
new_items = []
|
648 |
-
for annotation, filename, sentence
|
|
|
649 |
is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
|
650 |
-
|
651 |
-
validation_status = "Not Reviewed"
|
652 |
-
if validated is not None:
|
653 |
-
if validated:
|
654 |
-
validation_status = "Approved"
|
655 |
-
else:
|
656 |
-
validation_status = "Rejected"
|
657 |
-
if validation_description:
|
658 |
-
validation_status += f" ({validation_description})"
|
659 |
-
|
660 |
-
if is_deleted:
|
661 |
-
annotated_sentence_display = "[DELETED ANNOTATION]"
|
662 |
-
if validation_status == "Not Reviewed":
|
663 |
-
validation_status = "Not Reviewed (Deleted)"
|
664 |
-
else:
|
665 |
-
annotated_sentence_display = annotation.annotated_sentence
|
666 |
|
667 |
new_items.append({
|
668 |
"annotation_id": annotation.id,
|
@@ -672,7 +662,8 @@ class ReviewDashboardPage:
|
|
672 |
"annotated_sentence": annotated_sentence_display,
|
673 |
"is_deleted": is_deleted,
|
674 |
"annotated_at": annotation.annotated_at.isoformat() if annotation.annotated_at else "",
|
675 |
-
"validation_status":
|
|
|
676 |
})
|
677 |
|
678 |
# Combine with existing items
|
|
|
481 |
|
482 |
def navigate_review_fn(items, current_idx, direction):
|
483 |
if not items:
|
484 |
+
return 0, False
|
485 |
if direction == "next":
|
486 |
new_idx = min(current_idx + 1, len(items) - 1)
|
487 |
+
# Only load more items when user reaches the LAST item of a batch
|
488 |
+
should_load_more = (new_idx == len(items) - 1 and len(items) % 5 == 0)
|
489 |
+
if should_load_more:
|
490 |
+
log.info(f"User reached end of loaded items ({new_idx}/{len(items)}), will load more items")
|
491 |
+
return new_idx, should_load_more
|
492 |
else: # prev
|
493 |
+
return max(current_idx - 1, 0), False
|
494 |
+
|
495 |
+
def conditional_load_more_fn(items, session, should_load_more):
|
496 |
+
"""Load more items if needed, otherwise return existing items"""
|
497 |
+
if should_load_more:
|
498 |
+
log.info("Loading more items for navigation...")
|
499 |
+
return load_more_items_fn(items, session, current_batch_size=10) # Reduced from 50 to 10 items
|
500 |
+
return items
|
501 |
|
502 |
def save_validation_fn(items, idx, session, approved: bool, rejection_reason: str = ""):
|
503 |
if not items or idx >= len(items):
|
|
|
608 |
# gr.Warning(f"Invalid Data ID format: {target_data_id}")
|
609 |
return current_idx
|
610 |
|
611 |
+
def load_more_items_fn(items, session, current_batch_size=10):
|
612 |
"""Load more items when user needs them (pagination support)"""
|
613 |
user_id = session.get("user_id")
|
614 |
username = session.get("username")
|
|
|
633 |
|
634 |
# Load next batch starting from where we left off
|
635 |
offset = len(items)
|
636 |
+
|
637 |
+
# FAST LOADING: Use same strategy as initial load - simple query without complex JOINs
|
638 |
query = db.query(
|
639 |
Annotation,
|
640 |
TTSData.filename,
|
641 |
+
TTSData.sentence
|
|
|
|
|
642 |
).join(
|
643 |
TTSData, Annotation.tts_data_id == TTSData.id
|
|
|
|
|
|
|
|
|
644 |
).filter(
|
645 |
Annotation.annotator_id == target_annotator_obj.id
|
646 |
).order_by(Annotation.id).offset(offset).limit(current_batch_size)
|
647 |
|
648 |
results = query.all()
|
649 |
|
650 |
+
# Process new items with minimal data - validation status loaded on-demand
|
651 |
new_items = []
|
652 |
+
for annotation, filename, sentence in results:
|
653 |
+
# Check if annotation is deleted (minimal processing)
|
654 |
is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
|
655 |
+
annotated_sentence_display = "[DELETED ANNOTATION]" if is_deleted else annotation.annotated_sentence
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
656 |
|
657 |
new_items.append({
|
658 |
"annotation_id": annotation.id,
|
|
|
662 |
"annotated_sentence": annotated_sentence_display,
|
663 |
"is_deleted": is_deleted,
|
664 |
"annotated_at": annotation.annotated_at.isoformat() if annotation.annotated_at else "",
|
665 |
+
"validation_status": "Loading...", # Will be loaded on-demand
|
666 |
+
"validation_loaded": False # Track if validation status has been loaded
|
667 |
})
|
668 |
|
669 |
# Combine with existing items
|