Spaces:
Running
Running
File size: 4,284 Bytes
b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c f92b89b 80ebf7c b585b4b 80ebf7c f92b89b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c b585b4b 80ebf7c 7b5b03a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | """Spot the AI Receipt — 2AFC game by Scam.AI.
Simplified flow: clicking either button atomically scores the guess AND
loads the next pair. No separate 'Next round' button. Fewer outputs,
fewer state updates, faster perceived latency.
"""
import random
from pathlib import Path
import gradio as gr
ROOT = Path(__file__).parent
REAL_IMGS = [str(p) for p in sorted((ROOT / "data" / "real").glob("*"))]
AI_IMGS = [str(p) for p in sorted((ROOT / "data" / "ai").glob("*"))]
HUMAN_F1 = 0.852
CLAUDE_F1 = 0.975
GEMINI_F1 = 0.890
def play(guess, state):
"""One round-trip: score current guess + serve next pair."""
state = dict(state) if state else {"correct": 0, "total": 0, "ai_side": None}
# 1. Score the previous guess (if any)
feedback_md = ""
if guess and state.get("ai_side"):
correct = state["ai_side"] == guess
state["correct"] = state.get("correct", 0) + (1 if correct else 0)
state["total"] = state.get("total", 0) + 1
winner_letter = "A" if state["ai_side"] == "left" else "B"
if correct:
feedback_md = f"### ✅ Correct! **{winner_letter}** was the AI-generated receipt."
else:
feedback_md = f"### ❌ Wrong. **{winner_letter}** was the AI-generated receipt."
# 2. Pick next pair
ai = random.choice(AI_IMGS)
real = random.choice(REAL_IMGS)
ai_on_left = random.random() < 0.5
state["ai_side"] = "left" if ai_on_left else "right"
left = ai if ai_on_left else real
right = real if ai_on_left else ai
# 3. Score card
total = state.get("total", 0)
correct = state.get("correct", 0)
if total > 0:
score_md = (
f"**Your score: {correct} / {total} ({100*correct/total:.0f}%)** · "
f"Trained humans: F1 = {HUMAN_F1} · Claude Sonnet 4: F1 = {CLAUDE_F1}"
)
else:
score_md = (
"*Pick the AI-generated receipt. After each click, you'll see "
"the answer and the next pair.*"
)
# 4. After 10 rounds, append a CTA
if total >= 10:
feedback_md += (
"\n\n---\n\n"
"### Done with 10 rounds 🎉\n"
"AI-generated receipts are visually realistic. The forensic "
"signal is in **arithmetic incoherence** — invisible to the eye, "
"trivial for LLMs to verify.\n\n"
"👉 **Production-grade document fraud detection: "
"[scam.ai](https://www.scam.ai)**"
)
return left, right, state, feedback_md, score_md
with gr.Blocks(title="Spot the AI Receipt — Scam.AI") as demo:
gr.Markdown(
"# 🧾 Spot the AI Receipt\n"
"*One of these receipts is real, one was fully synthesized by "
"GPT-4o + GPT-Image-1. Pick the AI one.*\n\n"
"*Built by [Scam.AI](https://www.scam.ai) · Data: "
"[gpt4o-receipt](https://huggingface.co/datasets/Scam-AI/gpt4o-receipt) "
"+ [CORD-v2](https://huggingface.co/datasets/naver-clova-ix/cord-v2)*"
)
state = gr.State({"correct": 0, "total": 0, "ai_side": None})
score = gr.Markdown()
feedback = gr.Markdown()
with gr.Row():
with gr.Column():
img_l = gr.Image(label="Receipt A", interactive=False, height=400)
btn_l = gr.Button("👉 A is the AI", variant="primary", size="lg")
with gr.Column():
img_r = gr.Image(label="Receipt B", interactive=False, height=400)
btn_r = gr.Button("👉 B is the AI", variant="primary", size="lg")
# All clicks call the same `play` function with different guess label
btn_l.click(
lambda s: play("left", s),
inputs=state,
outputs=[img_l, img_r, state, feedback, score],
queue=True,
)
btn_r.click(
lambda s: play("right", s),
inputs=state,
outputs=[img_l, img_r, state, feedback, score],
queue=True,
)
# Initial load — no guess
demo.load(
lambda s: play(None, s),
inputs=state,
outputs=[img_l, img_r, state, feedback, score],
)
# Queue config: allow parallel processing
demo.queue(default_concurrency_limit=10, max_size=50)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(primary_hue="blue"), ssr_mode=False)
|