Spaces:
Running
Running
Add share link + result card to text tab, download image button for mobile, hide Gradio toolbar via CSS
Browse files- __pycache__/app.cpython-313.pyc +0 -0
- app.py +130 -20
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -284,6 +284,16 @@ label span {
|
|
| 284 |
text-decoration: none !important;
|
| 285 |
border-bottom: 1px dotted var(--text-muted);
|
| 286 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
"""
|
| 288 |
|
| 289 |
HUMAN_EXAMPLE = (
|
|
@@ -361,13 +371,28 @@ def _error_label(msg: str) -> dict:
|
|
| 361 |
|
| 362 |
|
| 363 |
def detect_text(text: str) -> dict:
|
| 364 |
-
"""Classify text as human-written or AI-generated."""
|
| 365 |
if not text or len(text.strip().split()) < 10:
|
| 366 |
return _error_label("Please enter at least a few sentences (~50 words)")
|
| 367 |
predictions = classifier.predict(text, k=2)
|
| 368 |
return {label: round(score, 4) for label, score in predictions}
|
| 369 |
|
| 370 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
SPACE_URL = "https://adaptive-classifier-ai-detector.hf.space"
|
| 372 |
|
| 373 |
|
|
@@ -419,10 +444,12 @@ def make_result_card(url: str, label: str, confidence: float) -> Image.Image:
|
|
| 419 |
if fill_w > 0:
|
| 420 |
draw.rounded_rectangle([bar_x, bar_y, bar_x + fill_w, bar_y + bar_h], radius=12, fill=result_color)
|
| 421 |
|
| 422 |
-
# URL
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
|
|
|
|
|
|
| 426 |
|
| 427 |
# Footer
|
| 428 |
draw.text((60, H - 60), "adaptive-classifier-ai-detector.hf.space", fill=muted_color, font=font_small)
|
|
@@ -487,8 +514,62 @@ with gr.Blocks(css=CSS, title="AI Text Detector", theme=gr.themes.Base()) as dem
|
|
| 487 |
|
| 488 |
with gr.Group(elem_classes="result-card"):
|
| 489 |
text_output = gr.Label(num_top_classes=2, label="Result")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
|
| 491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
|
| 493 |
gr.HTML('<div class="examples-heading">Try an example</div>')
|
| 494 |
gr.Examples(
|
|
@@ -513,14 +594,12 @@ with gr.Blocks(css=CSS, title="AI Text Detector", theme=gr.themes.Base()) as dem
|
|
| 513 |
with gr.Row(visible=False) as share_row:
|
| 514 |
copy_link_btn = gr.Button("Copy Link", size="sm", elem_classes="detect-btn")
|
| 515 |
copy_img_btn = gr.Button("Copy Image", size="sm", elem_classes="detect-btn")
|
|
|
|
| 516 |
share_card = gr.Image(
|
| 517 |
label="Result card",
|
| 518 |
visible=False,
|
| 519 |
type="pil",
|
| 520 |
elem_id="share-card",
|
| 521 |
-
show_download_button=False,
|
| 522 |
-
show_share_button=False,
|
| 523 |
-
show_fullscreen_button=False,
|
| 524 |
)
|
| 525 |
|
| 526 |
with gr.Group(elem_classes="input-card"):
|
|
@@ -576,6 +655,18 @@ with gr.Blocks(css=CSS, title="AI Text Detector", theme=gr.themes.Base()) as dem
|
|
| 576 |
}""",
|
| 577 |
)
|
| 578 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
gr.HTML('<div class="examples-heading">Try an example</div>')
|
| 580 |
gr.Examples(
|
| 581 |
examples=[
|
|
@@ -600,25 +691,44 @@ with gr.Blocks(css=CSS, title="AI Text Detector", theme=gr.themes.Base()) as dem
|
|
| 600 |
""")
|
| 601 |
|
| 602 |
def _on_load(request: gr.Request):
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
return (
|
| 608 |
gr.update(selected="url-tab"),
|
| 609 |
-
|
| 610 |
-
result,
|
| 611 |
-
gr.update(value=link, visible=
|
| 612 |
-
gr.update(visible=has_result),
|
| 613 |
-
gr.update(value=card, visible=has_result),
|
| 614 |
preview,
|
| 615 |
)
|
| 616 |
-
return
|
| 617 |
|
| 618 |
demo.load(
|
| 619 |
fn=_on_load,
|
| 620 |
inputs=None,
|
| 621 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
)
|
| 623 |
|
| 624 |
if __name__ == "__main__":
|
|
|
|
| 284 |
text-decoration: none !important;
|
| 285 |
border-bottom: 1px dotted var(--text-muted);
|
| 286 |
}
|
| 287 |
+
|
| 288 |
+
/* Hide Gradio image toolbar (download/share/fullscreen) on result cards */
|
| 289 |
+
#share-card .image-toolbar,
|
| 290 |
+
#text-share-card .image-toolbar,
|
| 291 |
+
#share-card .icon-buttons,
|
| 292 |
+
#text-share-card .icon-buttons,
|
| 293 |
+
#share-card button[aria-label],
|
| 294 |
+
#text-share-card button[aria-label] {
|
| 295 |
+
display: none !important;
|
| 296 |
+
}
|
| 297 |
"""
|
| 298 |
|
| 299 |
HUMAN_EXAMPLE = (
|
|
|
|
| 371 |
|
| 372 |
|
| 373 |
def detect_text(text: str) -> dict:
|
| 374 |
+
"""Classify text as human-written or AI-generated (label only)."""
|
| 375 |
if not text or len(text.strip().split()) < 10:
|
| 376 |
return _error_label("Please enter at least a few sentences (~50 words)")
|
| 377 |
predictions = classifier.predict(text, k=2)
|
| 378 |
return {label: round(score, 4) for label, score in predictions}
|
| 379 |
|
| 380 |
|
| 381 |
+
def detect_text_full(text: str) -> tuple[dict, str, Image.Image | None]:
|
| 382 |
+
"""Classify text and generate share link + card."""
|
| 383 |
+
result = detect_text(text)
|
| 384 |
+
if len(result) != 2 or any(k.startswith("Please") for k in result):
|
| 385 |
+
return result, "", None
|
| 386 |
+
# Share link with text snippet as param
|
| 387 |
+
snippet = text.strip()[:200]
|
| 388 |
+
share_link = f"{SPACE_URL}/?text={urllib.parse.quote(snippet, safe='')}"
|
| 389 |
+
# Card image
|
| 390 |
+
top_label = max(result, key=result.get)
|
| 391 |
+
preview = text.strip()[:80] + ("..." if len(text.strip()) > 80 else "")
|
| 392 |
+
card = make_result_card(preview, top_label, result[top_label])
|
| 393 |
+
return result, share_link, card
|
| 394 |
+
|
| 395 |
+
|
| 396 |
SPACE_URL = "https://adaptive-classifier-ai-detector.hf.space"
|
| 397 |
|
| 398 |
|
|
|
|
| 444 |
if fill_w > 0:
|
| 445 |
draw.rounded_rectangle([bar_x, bar_y, bar_x + fill_w, bar_y + bar_h], radius=12, fill=result_color)
|
| 446 |
|
| 447 |
+
# Source text/URL
|
| 448 |
+
display_src = url[:80] + "..." if len(url) > 80 else url
|
| 449 |
+
is_url = url.startswith("http") or url.startswith("www")
|
| 450 |
+
src_label = "URL analyzed:" if is_url else "Text analyzed:"
|
| 451 |
+
draw.text((60, 285), src_label, fill=muted_color, font=font_small)
|
| 452 |
+
draw.text((60, 308), display_src, fill=text_color, font=font_mono)
|
| 453 |
|
| 454 |
# Footer
|
| 455 |
draw.text((60, H - 60), "adaptive-classifier-ai-detector.hf.space", fill=muted_color, font=font_small)
|
|
|
|
| 514 |
|
| 515 |
with gr.Group(elem_classes="result-card"):
|
| 516 |
text_output = gr.Label(num_top_classes=2, label="Result")
|
| 517 |
+
text_share_link = gr.Textbox(visible=False, elem_id="text-share-url")
|
| 518 |
+
with gr.Row(visible=False) as text_share_row:
|
| 519 |
+
text_copy_link_btn = gr.Button("Copy Link", size="sm", elem_classes="detect-btn")
|
| 520 |
+
text_copy_img_btn = gr.Button("Copy Image", size="sm", elem_classes="detect-btn")
|
| 521 |
+
text_dl_img_btn = gr.Button("Download Image", size="sm", elem_classes="detect-btn")
|
| 522 |
+
text_share_card = gr.Image(
|
| 523 |
+
label="Result card",
|
| 524 |
+
visible=False,
|
| 525 |
+
type="pil",
|
| 526 |
+
elem_id="text-share-card",
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
def run_text_detection(text):
|
| 530 |
+
result, link, card = detect_text_full(text)
|
| 531 |
+
has_result = bool(link)
|
| 532 |
+
return (
|
| 533 |
+
result,
|
| 534 |
+
gr.update(value=link, visible=has_result),
|
| 535 |
+
gr.update(visible=has_result),
|
| 536 |
+
gr.update(value=card, visible=has_result),
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
text_btn.click(
|
| 540 |
+
fn=run_text_detection,
|
| 541 |
+
inputs=text_input,
|
| 542 |
+
outputs=[text_output, text_share_link, text_share_row, text_share_card],
|
| 543 |
+
api_name="detect",
|
| 544 |
+
)
|
| 545 |
|
| 546 |
+
text_copy_link_btn.click(
|
| 547 |
+
fn=None, inputs=text_share_link, outputs=None,
|
| 548 |
+
js="(url) => { navigator.clipboard.writeText(url); }",
|
| 549 |
+
)
|
| 550 |
+
text_copy_img_btn.click(
|
| 551 |
+
fn=None, inputs=None, outputs=None,
|
| 552 |
+
js="""() => {
|
| 553 |
+
const img = document.querySelector('#text-share-card img');
|
| 554 |
+
if (img) {
|
| 555 |
+
const c = document.createElement('canvas');
|
| 556 |
+
c.width = img.naturalWidth; c.height = img.naturalHeight;
|
| 557 |
+
c.getContext('2d').drawImage(img, 0, 0);
|
| 558 |
+
c.toBlob(b => navigator.clipboard.write([new ClipboardItem({'image/png': b})]), 'image/png');
|
| 559 |
+
}
|
| 560 |
+
}""",
|
| 561 |
+
)
|
| 562 |
+
text_dl_img_btn.click(
|
| 563 |
+
fn=None, inputs=None, outputs=None,
|
| 564 |
+
js="""() => {
|
| 565 |
+
const img = document.querySelector('#text-share-card img');
|
| 566 |
+
if (img) {
|
| 567 |
+
const a = document.createElement('a');
|
| 568 |
+
a.href = img.src; a.download = 'ai-detector-result.png';
|
| 569 |
+
a.click();
|
| 570 |
+
}
|
| 571 |
+
}""",
|
| 572 |
+
)
|
| 573 |
|
| 574 |
gr.HTML('<div class="examples-heading">Try an example</div>')
|
| 575 |
gr.Examples(
|
|
|
|
| 594 |
with gr.Row(visible=False) as share_row:
|
| 595 |
copy_link_btn = gr.Button("Copy Link", size="sm", elem_classes="detect-btn")
|
| 596 |
copy_img_btn = gr.Button("Copy Image", size="sm", elem_classes="detect-btn")
|
| 597 |
+
dl_img_btn = gr.Button("Download Image", size="sm", elem_classes="detect-btn")
|
| 598 |
share_card = gr.Image(
|
| 599 |
label="Result card",
|
| 600 |
visible=False,
|
| 601 |
type="pil",
|
| 602 |
elem_id="share-card",
|
|
|
|
|
|
|
|
|
|
| 603 |
)
|
| 604 |
|
| 605 |
with gr.Group(elem_classes="input-card"):
|
|
|
|
| 655 |
}""",
|
| 656 |
)
|
| 657 |
|
| 658 |
+
dl_img_btn.click(
|
| 659 |
+
fn=None, inputs=None, outputs=None,
|
| 660 |
+
js="""() => {
|
| 661 |
+
const img = document.querySelector('#share-card img');
|
| 662 |
+
if (img) {
|
| 663 |
+
const a = document.createElement('a');
|
| 664 |
+
a.href = img.src; a.download = 'ai-detector-result.png';
|
| 665 |
+
a.click();
|
| 666 |
+
}
|
| 667 |
+
}""",
|
| 668 |
+
)
|
| 669 |
+
|
| 670 |
gr.HTML('<div class="examples-heading">Try an example</div>')
|
| 671 |
gr.Examples(
|
| 672 |
examples=[
|
|
|
|
| 691 |
""")
|
| 692 |
|
| 693 |
def _on_load(request: gr.Request):
|
| 694 |
+
text_param = request.query_params.get("text", "")
|
| 695 |
+
url_param = request.query_params.get("url", "")
|
| 696 |
+
# Default: no changes
|
| 697 |
+
no_change = (
|
| 698 |
+
gr.update(), "", {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
| 699 |
+
"", {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), "",
|
| 700 |
+
)
|
| 701 |
+
if text_param:
|
| 702 |
+
result, link, card = detect_text_full(text_param)
|
| 703 |
+
has = bool(link)
|
| 704 |
+
return (
|
| 705 |
+
gr.update(selected="text-tab"),
|
| 706 |
+
text_param, result,
|
| 707 |
+
gr.update(value=link, visible=has), gr.update(visible=has), gr.update(value=card, visible=has),
|
| 708 |
+
"", {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), "",
|
| 709 |
+
)
|
| 710 |
+
if url_param:
|
| 711 |
+
result, preview, link, card = detect_url(url_param)
|
| 712 |
+
has = bool(link)
|
| 713 |
return (
|
| 714 |
gr.update(selected="url-tab"),
|
| 715 |
+
"", {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
| 716 |
+
url_param, result,
|
| 717 |
+
gr.update(value=link, visible=has), gr.update(visible=has), gr.update(value=card, visible=has),
|
|
|
|
|
|
|
| 718 |
preview,
|
| 719 |
)
|
| 720 |
+
return no_change
|
| 721 |
|
| 722 |
demo.load(
|
| 723 |
fn=_on_load,
|
| 724 |
inputs=None,
|
| 725 |
+
outputs=[
|
| 726 |
+
tabs,
|
| 727 |
+
# text tab outputs
|
| 728 |
+
text_input, text_output, text_share_link, text_share_row, text_share_card,
|
| 729 |
+
# url tab outputs
|
| 730 |
+
url_input, url_output, share_link, share_row, share_card, url_preview,
|
| 731 |
+
],
|
| 732 |
)
|
| 733 |
|
| 734 |
if __name__ == "__main__":
|