Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,41 +1,164 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
from transformers import pipeline
|
| 3 |
|
| 4 |
-
#
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
def detect_text(input_text: str):
|
| 8 |
-
|
|
|
|
| 9 |
return {}, "β Please enter some text."
|
| 10 |
|
| 11 |
try:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
except Exception as e:
|
| 21 |
return {}, f"β Error: {str(e)}"
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
with gr.
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
if __name__ == "__main__":
|
| 41 |
demo.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
from transformers import pipeline
|
| 3 |
|
| 4 |
+
# ----------------------------
|
| 5 |
+
# Load TEXT detector (upgradeable)
|
| 6 |
+
# ----------------------------
|
| 7 |
+
TEXT_MODEL_ID = "wangkevin02/AI_Detect_Model" # swap if you try another model
|
| 8 |
+
|
| 9 |
+
text_pipe = pipeline("text-classification", model=TEXT_MODEL_ID)
|
| 10 |
+
|
| 11 |
+
def _canonical(label: str) -> str | None:
|
| 12 |
+
"""Map raw label names to 'AI' or 'HUMAN' when possible."""
|
| 13 |
+
if not label:
|
| 14 |
+
return None
|
| 15 |
+
l = label.strip().lower()
|
| 16 |
+
# Common explicit names
|
| 17 |
+
if any(k in l for k in ["ai", "machine", "generated", "fake", "synthetic", "gpt"]):
|
| 18 |
+
return "AI"
|
| 19 |
+
if any(k in l for k in ["human", "real", "authentic", "organic"]):
|
| 20 |
+
return "HUMAN"
|
| 21 |
+
# Try LABEL_X -> use id2label if present
|
| 22 |
+
if l.startswith("label_"):
|
| 23 |
+
try:
|
| 24 |
+
idx = int(l.split("_")[-1])
|
| 25 |
+
except ValueError:
|
| 26 |
+
return None
|
| 27 |
+
id2label = getattr(text_pipe.model.config, "id2label", None)
|
| 28 |
+
if isinstance(id2label, dict) and idx in id2label:
|
| 29 |
+
return _canonical(str(id2label[idx]))
|
| 30 |
+
# Sometimes labels are just "0"/"1"
|
| 31 |
+
if l in {"0", "1"}:
|
| 32 |
+
id2label = getattr(text_pipe.model.config, "id2label", None)
|
| 33 |
+
if isinstance(id2label, dict) and l.isdigit():
|
| 34 |
+
mapped = id2label.get(int(l))
|
| 35 |
+
if mapped:
|
| 36 |
+
return _canonical(str(mapped))
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
def _aggregate_probs(raw_results):
|
| 40 |
+
"""
|
| 41 |
+
Convert pipeline outputs into {'AI': p, 'HUMAN': p, 'raw': {...}} robustly.
|
| 42 |
+
Ensures both keys exist and sum <= 1.0 (may be < 1 if labels don't map).
|
| 43 |
+
"""
|
| 44 |
+
# text-classification with top_k=None returns a list of dicts
|
| 45 |
+
# e.g. [{'label': 'AI', 'score': 0.82}, {'label': 'HUMAN', 'score': 0.18}]
|
| 46 |
+
if isinstance(raw_results, list) and raw_results and isinstance(raw_results[0], dict):
|
| 47 |
+
label_scores = {d["label"]: float(d["score"]) for d in raw_results}
|
| 48 |
+
elif isinstance(raw_results, list) and raw_results and isinstance(raw_results[0], list):
|
| 49 |
+
# return_all_scores=True style: [[{label, score}, {label, score}, ...]]
|
| 50 |
+
label_scores = {d["label"]: float(d["score"]) for d in raw_results[0]}
|
| 51 |
+
else:
|
| 52 |
+
label_scores = {}
|
| 53 |
+
|
| 54 |
+
ai_p = 0.0
|
| 55 |
+
human_p = 0.0
|
| 56 |
+
for lbl, sc in label_scores.items():
|
| 57 |
+
canon = _canonical(lbl)
|
| 58 |
+
if canon == "AI":
|
| 59 |
+
ai_p += sc
|
| 60 |
+
elif canon == "HUMAN":
|
| 61 |
+
human_p += sc
|
| 62 |
+
|
| 63 |
+
# If nothing mapped, fall back to top label heuristic
|
| 64 |
+
if ai_p == 0.0 and human_p == 0.0 and label_scores:
|
| 65 |
+
top_lbl = max(label_scores, key=label_scores.get)
|
| 66 |
+
top_sc = label_scores[top_lbl]
|
| 67 |
+
canon = _canonical(top_lbl)
|
| 68 |
+
if canon == "AI":
|
| 69 |
+
ai_p = top_sc
|
| 70 |
+
human_p = 1.0 - top_sc
|
| 71 |
+
elif canon == "HUMAN":
|
| 72 |
+
human_p = top_sc
|
| 73 |
+
ai_p = 1.0 - top_sc
|
| 74 |
+
|
| 75 |
+
return {"AI": round(ai_p, 6), "HUMAN": round(human_p, 6), "raw": label_scores}
|
| 76 |
+
|
| 77 |
+
def _verdict(ai_p: float, human_p: float, n_words: int) -> str:
|
| 78 |
+
conf = max(ai_p, human_p)
|
| 79 |
+
if n_words < 120:
|
| 80 |
+
band = "LOW (short text)"
|
| 81 |
+
elif conf < 0.60:
|
| 82 |
+
band = "LOW (uncertain)"
|
| 83 |
+
elif conf < 0.80:
|
| 84 |
+
band = "MEDIUM"
|
| 85 |
+
else:
|
| 86 |
+
band = "HIGH"
|
| 87 |
+
|
| 88 |
+
if ai_p > human_p:
|
| 89 |
+
return f"π€ Likely AI β Confidence: {band}"
|
| 90 |
+
elif human_p > ai_p:
|
| 91 |
+
return f"π Likely Human β Confidence: {band}"
|
| 92 |
+
else:
|
| 93 |
+
return "β Uncertain β Confidence: LOW"
|
| 94 |
|
| 95 |
def detect_text(input_text: str):
|
| 96 |
+
text = (input_text or "").strip()
|
| 97 |
+
if not text:
|
| 98 |
return {}, "β Please enter some text."
|
| 99 |
|
| 100 |
try:
|
| 101 |
+
# Get ALL label scores so we can map correctly
|
| 102 |
+
results = text_pipe(text, top_k=None)
|
| 103 |
+
agg = _aggregate_probs(results)
|
| 104 |
+
ai_p, human_p = float(agg["AI"]), float(agg["HUMAN"])
|
| 105 |
+
|
| 106 |
+
# Normalize to show nicely, but keep raw too
|
| 107 |
+
probs_out = {
|
| 108 |
+
"AI-generated": round(ai_p, 4),
|
| 109 |
+
"Human-written": round(human_p, 4),
|
| 110 |
+
}
|
| 111 |
+
# Optional: include raw labels so you can debug mappings in UI
|
| 112 |
+
# probs_out.update({f"raw::{k}": round(v, 4) for k, v in agg["raw"].items()})
|
| 113 |
+
|
| 114 |
+
verdict = _verdict(ai_p, human_p, n_words=len(text.split()))
|
| 115 |
+
return probs_out, verdict
|
| 116 |
+
|
| 117 |
except Exception as e:
|
| 118 |
return {}, f"β Error: {str(e)}"
|
| 119 |
|
| 120 |
+
# ----------------------------
|
| 121 |
+
# (Optional) IMAGE detector β won't crash if model unavailable
|
| 122 |
+
# ----------------------------
|
| 123 |
+
try:
|
| 124 |
+
from PIL import Image
|
| 125 |
+
image_pipe = pipeline("image-classification", model="umm-maybe/ai-vs-human-images")
|
| 126 |
+
except Exception:
|
| 127 |
+
image_pipe = None
|
| 128 |
|
| 129 |
+
def detect_image(img):
|
| 130 |
+
if image_pipe is None:
|
| 131 |
+
return {}, "β οΈ Image detector not available on this Space."
|
| 132 |
+
try:
|
| 133 |
+
results = image_pipe(img)
|
| 134 |
+
label_scores = {d["label"]: float(d["score"]) for d in results}
|
| 135 |
+
best = max(label_scores, key=label_scores.get)
|
| 136 |
+
if any(k in best.lower() for k in ["ai", "fake", "generated", "synthetic"]):
|
| 137 |
+
return label_scores, "π€ This image looks AI-generated"
|
| 138 |
+
else:
|
| 139 |
+
return label_scores, "π· This image looks Human/Real"
|
| 140 |
+
except Exception as e:
|
| 141 |
+
return {}, f"β Error: {str(e)}"
|
| 142 |
|
| 143 |
+
# ----------------------------
|
| 144 |
+
# UI
|
| 145 |
+
# ----------------------------
|
| 146 |
+
with gr.Blocks() as demo:
|
| 147 |
+
gr.Markdown("# π AI Content Detector\nDetect whether **text** (and optionally images) are AI-generated or human-made.")
|
| 148 |
|
| 149 |
+
with gr.Tab("π Text"):
|
| 150 |
+
txt = gr.Textbox(label="Enter text", lines=10, placeholder="Paste text hereβ¦")
|
| 151 |
+
out_probs = gr.Label(label="Probabilities")
|
| 152 |
+
out_verdict = gr.Textbox(label="Verdict", interactive=False)
|
| 153 |
+
btn = gr.Button("Analyze", variant="primary")
|
| 154 |
+
btn.click(detect_text, inputs=txt, outputs=[out_probs, out_verdict])
|
| 155 |
|
| 156 |
+
with gr.Tab("π· Image"):
|
| 157 |
+
img_in = gr.Image(type="pil", label="Upload an image")
|
| 158 |
+
img_probs = gr.Label(label="Probabilities")
|
| 159 |
+
img_verdict = gr.Textbox(label="Verdict", interactive=False)
|
| 160 |
+
btn2 = gr.Button("Analyze Image")
|
| 161 |
+
btn2.click(detect_image, inputs=img_in, outputs=[img_probs, img_verdict])
|
| 162 |
|
| 163 |
if __name__ == "__main__":
|
| 164 |
demo.launch()
|