Spaces:
Running
Running
Jin Zhu commited on
Commit ·
07f68c2
1
Parent(s): 2a268ef
Update app.py
Browse files- src/app.py +66 -14
src/app.py
CHANGED
|
@@ -203,6 +203,11 @@ INTERPRETATION_TEXT = """
|
|
| 203 |
- Not sure of the domain? Switch **Domain of the text** to *Soft Estimate* or *Estimate* — the estimation is conducted by a pretrained transformer-based classifier that infers it automatically.
|
| 204 |
"""
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
FOOTER_TEXT = (
|
| 207 |
"This tool is developed for research purposes only. The detection results are not "
|
| 208 |
"100% accurate and should not be used as the sole basis for any critical decisions. "
|
|
@@ -249,35 +254,46 @@ def on_domain_mode_change(domain_mode: str):
|
|
| 249 |
)
|
| 250 |
|
| 251 |
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
"""Detect button handler: runs inference and refreshes all result widgets.
|
| 254 |
|
| 255 |
-
`
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
if not text or not text.strip():
|
|
|
|
| 263 |
raise gr.Error("⚠️ Please enter some text before detecting.")
|
| 264 |
|
| 265 |
-
progress(0, desc="Validating input…")
|
| 266 |
domain = resolve_domain_selection(domain_mode, manual_domain)
|
| 267 |
-
is_auto_domain = domain in (ESTIMATE_DOMAIN, SOFTEST_DOMAIN)
|
| 268 |
|
| 269 |
start_time = time.time()
|
| 270 |
try:
|
| 271 |
-
progress(0.2, desc="Estimating domain…" if is_auto_domain else "Tokenizing text…")
|
| 272 |
-
progress(0.4, desc="Scoring text against the reference model…")
|
| 273 |
crit, p_value = _run_inference(text, domain)
|
| 274 |
-
progress(0.9, desc="Computing p-value…")
|
| 275 |
except gr.Error:
|
|
|
|
| 276 |
raise
|
| 277 |
except Exception as e: # noqa: BLE001 — surfaced to the user via gr.Error
|
|
|
|
| 278 |
raise gr.Error(f"Detection failed: {e}")
|
| 279 |
elapsed_time = time.time() - start_time
|
| 280 |
-
progress(1.0, desc="Done")
|
| 281 |
|
| 282 |
stats_manager.increment_detection()
|
| 283 |
|
|
@@ -290,7 +306,7 @@ def run_detection(text: str, domain_mode: str, manual_domain: str, alpha: float,
|
|
| 290 |
"feedback_given": False,
|
| 291 |
}
|
| 292 |
|
| 293 |
-
|
| 294 |
gr.update(value=format_conclusion(p_value, alpha), visible=True),
|
| 295 |
gr.update(visible=True), # interpretation accordion
|
| 296 |
gr.update(value=f"⏱️ Processing time: {elapsed_time:.2f} seconds", visible=True),
|
|
@@ -298,6 +314,8 @@ def run_detection(text: str, domain_mode: str, manual_domain: str, alpha: float,
|
|
| 298 |
gr.update(visible=True), # feedback buttons row (re-shown in case a prior run hid it)
|
| 299 |
gr.update(visible=False), # feedback thanks message
|
| 300 |
detection_state,
|
|
|
|
|
|
|
| 301 |
)
|
| 302 |
|
| 303 |
|
|
@@ -355,6 +373,27 @@ CUSTOM_CSS = """
|
|
| 355 |
#detect-btn:hover {
|
| 356 |
background-color: #fd8d3c !important;
|
| 357 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
.conclusion-card {
|
| 359 |
background-color: #f8fafc;
|
| 360 |
border: 1px solid #e5e7eb;
|
|
@@ -451,6 +490,13 @@ def build_interface() -> gr.Blocks:
|
|
| 451 |
label="Significance level (α)", scale=1,
|
| 452 |
)
|
| 453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
domain_mode_info = gr.Markdown(DOMAIN_MODE_INFO[DEFAULT_DOMAIN_MODE])
|
| 455 |
if not DOMAIN_ESTIMATOR_AVAILABLE:
|
| 456 |
gr.Markdown(
|
|
@@ -493,7 +539,13 @@ def build_interface() -> gr.Blocks:
|
|
| 493 |
outputs=[
|
| 494 |
conclusion_md, interpretation_box, elapsed_caption,
|
| 495 |
feedback_section, feedback_row, feedback_thanks, detection_state,
|
|
|
|
| 496 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
)
|
| 498 |
|
| 499 |
expected_btn.click(
|
|
|
|
| 203 |
- Not sure of the domain? Switch **Domain of the text** to *Soft Estimate* or *Estimate* — the estimation is conducted by a pretrained transformer-based classifier that infers it automatically.
|
| 204 |
"""
|
| 205 |
|
| 206 |
+
DETECTING_HTML = (
|
| 207 |
+
'<div class="spinner"></div>'
|
| 208 |
+
'<span>Detecting… this can take a few seconds.</span>'
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
FOOTER_TEXT = (
|
| 212 |
"This tool is developed for research purposes only. The detection results are not "
|
| 213 |
"100% accurate and should not be used as the sole basis for any critical decisions. "
|
|
|
|
| 254 |
)
|
| 255 |
|
| 256 |
|
| 257 |
+
DETECT_BTN_IDLE = gr.update(value="🔍 Detect", interactive=True)
|
| 258 |
+
DETECT_BTN_BUSY = gr.update(value="⏳ Detecting…", interactive=False)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def run_detection(text: str, domain_mode: str, manual_domain: str, alpha: float):
|
| 262 |
"""Detect button handler: runs inference and refreshes all result widgets.
|
| 263 |
|
| 264 |
+
Written as a generator (`yield` instead of `return`) so it can push the
|
| 265 |
+
UI to the frontend in two steps instead of one:
|
| 266 |
+
|
| 267 |
+
1. The instant the button is pressed — flip on the spinner + "Detecting…"
|
| 268 |
+
button state, before any inference has run.
|
| 269 |
+
2. Once `_run_inference` returns (or raises) — turn the spinner back off
|
| 270 |
+
and show the result (or let the error surface via `gr.Error`).
|
| 271 |
+
|
| 272 |
+
That before/after pair is what makes it unambiguous whether detection is
|
| 273 |
+
still running, unlike the old staged `gr.Progress()` bar, which jumped
|
| 274 |
+
between a few fixed percentages and didn't clearly resolve to "done".
|
| 275 |
"""
|
| 276 |
+
# Everything except the spinner + button is left untouched on this first
|
| 277 |
+
# yield — any previous result stays on screen until the new one is ready.
|
| 278 |
+
unchanged_results = (gr.update(),) * 7
|
| 279 |
+
yield unchanged_results + (gr.update(visible=True), DETECT_BTN_BUSY)
|
| 280 |
+
|
| 281 |
if not text or not text.strip():
|
| 282 |
+
yield unchanged_results + (gr.update(visible=False), DETECT_BTN_IDLE)
|
| 283 |
raise gr.Error("⚠️ Please enter some text before detecting.")
|
| 284 |
|
|
|
|
| 285 |
domain = resolve_domain_selection(domain_mode, manual_domain)
|
|
|
|
| 286 |
|
| 287 |
start_time = time.time()
|
| 288 |
try:
|
|
|
|
|
|
|
| 289 |
crit, p_value = _run_inference(text, domain)
|
|
|
|
| 290 |
except gr.Error:
|
| 291 |
+
yield unchanged_results + (gr.update(visible=False), DETECT_BTN_IDLE)
|
| 292 |
raise
|
| 293 |
except Exception as e: # noqa: BLE001 — surfaced to the user via gr.Error
|
| 294 |
+
yield unchanged_results + (gr.update(visible=False), DETECT_BTN_IDLE)
|
| 295 |
raise gr.Error(f"Detection failed: {e}")
|
| 296 |
elapsed_time = time.time() - start_time
|
|
|
|
| 297 |
|
| 298 |
stats_manager.increment_detection()
|
| 299 |
|
|
|
|
| 306 |
"feedback_given": False,
|
| 307 |
}
|
| 308 |
|
| 309 |
+
yield (
|
| 310 |
gr.update(value=format_conclusion(p_value, alpha), visible=True),
|
| 311 |
gr.update(visible=True), # interpretation accordion
|
| 312 |
gr.update(value=f"⏱️ Processing time: {elapsed_time:.2f} seconds", visible=True),
|
|
|
|
| 314 |
gr.update(visible=True), # feedback buttons row (re-shown in case a prior run hid it)
|
| 315 |
gr.update(visible=False), # feedback thanks message
|
| 316 |
detection_state,
|
| 317 |
+
gr.update(visible=False), # detecting indicator — inference is done
|
| 318 |
+
DETECT_BTN_IDLE,
|
| 319 |
)
|
| 320 |
|
| 321 |
|
|
|
|
| 373 |
#detect-btn:hover {
|
| 374 |
background-color: #fd8d3c !important;
|
| 375 |
}
|
| 376 |
+
#detecting-indicator {
|
| 377 |
+
display: flex;
|
| 378 |
+
align-items: center;
|
| 379 |
+
gap: 0.5rem;
|
| 380 |
+
margin: -0.3rem 0 0.5rem;
|
| 381 |
+
color: #b45309;
|
| 382 |
+
font-size: 0.92rem;
|
| 383 |
+
font-weight: 500;
|
| 384 |
+
}
|
| 385 |
+
#detecting-indicator .spinner {
|
| 386 |
+
flex: none;
|
| 387 |
+
width: 1rem;
|
| 388 |
+
height: 1rem;
|
| 389 |
+
border: 2.5px solid #fde3c8;
|
| 390 |
+
border-top-color: #fd8d3c;
|
| 391 |
+
border-radius: 50%;
|
| 392 |
+
animation: detecting-spin 0.7s linear infinite;
|
| 393 |
+
}
|
| 394 |
+
@keyframes detecting-spin {
|
| 395 |
+
to { transform: rotate(360deg); }
|
| 396 |
+
}
|
| 397 |
.conclusion-card {
|
| 398 |
background-color: #f8fafc;
|
| 399 |
border: 1px solid #e5e7eb;
|
|
|
|
| 490 |
label="Significance level (α)", scale=1,
|
| 491 |
)
|
| 492 |
|
| 493 |
+
# Hidden by default; `run_detection` toggles it on for the duration of
|
| 494 |
+
# each detection run so there's an unmistakable, animated "still
|
| 495 |
+
# running" cue right under the button the user just pressed.
|
| 496 |
+
detecting_indicator = gr.HTML(
|
| 497 |
+
DETECTING_HTML, visible=False, elem_id="detecting-indicator",
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
domain_mode_info = gr.Markdown(DOMAIN_MODE_INFO[DEFAULT_DOMAIN_MODE])
|
| 501 |
if not DOMAIN_ESTIMATOR_AVAILABLE:
|
| 502 |
gr.Markdown(
|
|
|
|
| 539 |
outputs=[
|
| 540 |
conclusion_md, interpretation_box, elapsed_caption,
|
| 541 |
feedback_section, feedback_row, feedback_thanks, detection_state,
|
| 542 |
+
detecting_indicator, detect_btn,
|
| 543 |
],
|
| 544 |
+
# We drive the "still running" indicator ourselves (spinner +
|
| 545 |
+
# busy button label, both toggled by `run_detection`'s two
|
| 546 |
+
# yields), so suppress Gradio's own progress overlay to avoid
|
| 547 |
+
# showing two different busy indicators at once.
|
| 548 |
+
show_progress="hidden",
|
| 549 |
)
|
| 550 |
|
| 551 |
expected_btn.click(
|