codelion commited on
Commit
aeee576
·
verified ·
1 Parent(s): e5e7739

Redesign UI with dark editorial theme, add URL tab, fix examples

Browse files
Files changed (1) hide show
  1. app.py +346 -36
app.py CHANGED
@@ -24,6 +24,286 @@ HEADERS = {
24
  "Accept-Language": "en-US,en;q=0.9",
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  class _TextExtractor(HTMLParser):
29
  """Simple HTML to text extractor."""
@@ -72,7 +352,7 @@ def fetch_url(url: str) -> str:
72
  def detect_text(text: str) -> dict:
73
  """Classify text as human-written or AI-generated."""
74
  if not text or len(text.strip().split()) < 10:
75
- return {"error": "Please enter at least a few sentences of text (~50 words)."}
76
  predictions = classifier.predict(text, k=2)
77
  return {label: round(score, 4) for label, score in predictions}
78
 
@@ -84,58 +364,88 @@ def detect_url(url: str) -> tuple[dict, str]:
84
  try:
85
  text = fetch_url(url)
86
  except Exception as e:
87
- return {"error": f"Failed to fetch URL: {e}"}, ""
88
  if len(text.split()) < 10:
89
- return {"error": "Could not extract enough text from that URL."}, text[:500]
90
- # Truncate to ~2000 words to keep inference fast
91
  words = text.split()
92
  if len(words) > 2000:
93
  text = " ".join(words[:2000])
94
  result = detect_text(text)
95
- # Show a preview of what was extracted
96
- preview = text[:1000] + ("..." if len(text) > 1000 else "")
97
  return result, preview
98
 
99
 
100
- with gr.Blocks(title="AI Text Detector") as demo:
101
- gr.Markdown(
102
- "# AI Text Detector\n"
103
- "Detects whether text is **human-written** or **AI-generated/edited**. "
104
- "Built with [adaptive-classifier](https://github.com/codelion/adaptive-classifier) "
105
- "using frozen [RADAR](https://huggingface.co/TrustSafeAI/RADAR-Vicuna-7B) embeddings, "
106
- "trained on the [EditLens](https://huggingface.co/datasets/pangram/editlens_iclr) dataset. "
107
- "Works best with 50+ words."
108
- )
 
 
 
 
109
 
110
  with gr.Tabs():
111
- with gr.TabItem("Text"):
112
- text_input = gr.Textbox(
113
- lines=8,
114
- placeholder="Paste text here to check if it's human-written or AI-generated...",
115
- label="Input Text",
116
- )
117
- text_btn = gr.Button("Detect", variant="primary")
118
- text_output = gr.Label(num_top_classes=2, label="Prediction")
 
 
 
 
 
119
  text_btn.click(fn=detect_text, inputs=text_input, outputs=text_output, api_name="detect")
120
 
 
121
  gr.Examples(
122
- examples=[
123
- ["The quick brown fox jumps over the lazy dog. I went to the store yesterday and forgot my wallet, which was pretty embarrassing. Had to ask the cashier to hold my stuff while I ran back to the car."],
124
- ["The implementation leverages a novel approach to address the fundamental challenges inherent in modern natural language processing systems. By utilizing advanced transformer architectures, we demonstrate significant improvements across multiple benchmark datasets."],
125
- ],
126
  inputs=text_input,
 
127
  )
128
 
129
- with gr.TabItem("URL"):
130
- url_input = gr.Textbox(
131
- lines=1,
132
- placeholder="Enter a URL to fetch and analyze (e.g. https://example.com/article)",
133
- label="URL",
134
- )
135
- url_btn = gr.Button("Fetch & Detect", variant="primary")
136
- url_output = gr.Label(num_top_classes=2, label="Prediction")
137
- url_preview = gr.Textbox(label="Extracted Text (preview)", lines=6, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
138
  url_btn.click(fn=detect_url, inputs=url_input, outputs=[url_output, url_preview], api_name="detect_url")
139
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  if __name__ == "__main__":
141
  demo.launch(share=False)
 
24
  "Accept-Language": "en-US,en;q=0.9",
25
  }
26
 
27
+ CSS = """
28
+ @import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Outfit:wght@300;400;500;600;700&display=swap');
29
+
30
+ :root {
31
+ --bg-deep: #0a0e17;
32
+ --bg-surface: #111827;
33
+ --bg-card: #1a2234;
34
+ --bg-input: #0f1729;
35
+ --border-subtle: #1e2d45;
36
+ --border-accent: #2563eb;
37
+ --text-primary: #e2e8f0;
38
+ --text-secondary: #8892a6;
39
+ --text-muted: #4a5568;
40
+ --accent-blue: #3b82f6;
41
+ --accent-cyan: #06b6d4;
42
+ --accent-human: #10b981;
43
+ --accent-ai: #f59e0b;
44
+ --glow-blue: rgba(59, 130, 246, 0.15);
45
+ }
46
+
47
+ /* Global overrides */
48
+ .gradio-container {
49
+ background: var(--bg-deep) !important;
50
+ font-family: 'Outfit', sans-serif !important;
51
+ max-width: 820px !important;
52
+ margin: 0 auto !important;
53
+ }
54
+
55
+ .main, .contain {
56
+ background: transparent !important;
57
+ }
58
+
59
+ footer { display: none !important; }
60
+
61
+ /* Header */
62
+ .header-block {
63
+ text-align: center;
64
+ padding: 2rem 1rem 1rem;
65
+ }
66
+
67
+ .header-block h1 {
68
+ font-family: 'DM Mono', monospace !important;
69
+ font-size: 1.6rem !important;
70
+ font-weight: 500 !important;
71
+ color: var(--text-primary) !important;
72
+ letter-spacing: 0.04em;
73
+ margin-bottom: 0.4rem !important;
74
+ }
75
+
76
+ .header-block p {
77
+ font-size: 0.85rem !important;
78
+ color: var(--text-secondary) !important;
79
+ line-height: 1.5 !important;
80
+ max-width: 560px;
81
+ margin: 0 auto !important;
82
+ }
83
+
84
+ .header-block a {
85
+ color: var(--accent-cyan) !important;
86
+ text-decoration: none !important;
87
+ border-bottom: 1px solid rgba(6, 182, 212, 0.3);
88
+ }
89
+
90
+ .header-block a:hover {
91
+ border-bottom-color: var(--accent-cyan);
92
+ }
93
+
94
+ /* Tabs */
95
+ .tabs {
96
+ background: transparent !important;
97
+ border: none !important;
98
+ }
99
+
100
+ .tab-nav {
101
+ background: transparent !important;
102
+ border: none !important;
103
+ justify-content: center !important;
104
+ gap: 0.25rem !important;
105
+ padding: 0.5rem 0 !important;
106
+ }
107
+
108
+ .tab-nav button {
109
+ font-family: 'DM Mono', monospace !important;
110
+ font-size: 0.8rem !important;
111
+ font-weight: 400 !important;
112
+ letter-spacing: 0.06em;
113
+ text-transform: uppercase;
114
+ color: var(--text-muted) !important;
115
+ background: transparent !important;
116
+ border: 1px solid var(--border-subtle) !important;
117
+ border-radius: 6px !important;
118
+ padding: 0.5rem 1.5rem !important;
119
+ transition: all 0.2s ease !important;
120
+ }
121
+
122
+ .tab-nav button:hover {
123
+ color: var(--text-secondary) !important;
124
+ border-color: var(--text-muted) !important;
125
+ }
126
+
127
+ .tab-nav button.selected {
128
+ color: var(--accent-cyan) !important;
129
+ background: rgba(6, 182, 212, 0.08) !important;
130
+ border-color: var(--accent-cyan) !important;
131
+ }
132
+
133
+ .tabitem {
134
+ background: transparent !important;
135
+ border: none !important;
136
+ padding: 0 !important;
137
+ }
138
+
139
+ /* Input card */
140
+ .input-card {
141
+ background: var(--bg-card) !important;
142
+ border: 1px solid var(--border-subtle) !important;
143
+ border-radius: 10px !important;
144
+ padding: 1.25rem !important;
145
+ margin-top: 0.75rem !important;
146
+ }
147
+
148
+ /* Textbox overrides */
149
+ textarea, input[type="text"] {
150
+ font-family: 'DM Mono', monospace !important;
151
+ font-size: 0.85rem !important;
152
+ line-height: 1.65 !important;
153
+ color: var(--text-primary) !important;
154
+ background: var(--bg-input) !important;
155
+ border: 1px solid var(--border-subtle) !important;
156
+ border-radius: 8px !important;
157
+ padding: 0.85rem 1rem !important;
158
+ transition: border-color 0.2s ease !important;
159
+ }
160
+
161
+ textarea:focus, input[type="text"]:focus {
162
+ border-color: var(--accent-blue) !important;
163
+ box-shadow: 0 0 0 3px var(--glow-blue) !important;
164
+ outline: none !important;
165
+ }
166
+
167
+ label span {
168
+ font-family: 'DM Mono', monospace !important;
169
+ font-size: 0.7rem !important;
170
+ text-transform: uppercase !important;
171
+ letter-spacing: 0.08em !important;
172
+ color: var(--text-muted) !important;
173
+ }
174
+
175
+ /* Button */
176
+ .detect-btn {
177
+ font-family: 'DM Mono', monospace !important;
178
+ font-size: 0.8rem !important;
179
+ font-weight: 500 !important;
180
+ letter-spacing: 0.06em !important;
181
+ text-transform: uppercase !important;
182
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan)) !important;
183
+ color: #fff !important;
184
+ border: none !important;
185
+ border-radius: 8px !important;
186
+ padding: 0.7rem 2rem !important;
187
+ cursor: pointer !important;
188
+ transition: all 0.25s ease !important;
189
+ box-shadow: 0 2px 12px rgba(59, 130, 246, 0.25) !important;
190
+ }
191
+
192
+ .detect-btn:hover {
193
+ box-shadow: 0 4px 20px rgba(59, 130, 246, 0.4) !important;
194
+ transform: translateY(-1px) !important;
195
+ }
196
+
197
+ /* Result label */
198
+ .result-card {
199
+ background: var(--bg-card) !important;
200
+ border: 1px solid var(--border-subtle) !important;
201
+ border-radius: 10px !important;
202
+ padding: 1.25rem !important;
203
+ margin-top: 0.5rem !important;
204
+ }
205
+
206
+ .result-card .output-class {
207
+ font-family: 'DM Mono', monospace !important;
208
+ }
209
+
210
+ /* Label component overrides */
211
+ .output-label {
212
+ background: transparent !important;
213
+ }
214
+
215
+ .output-label .label-name {
216
+ font-family: 'DM Mono', monospace !important;
217
+ font-size: 1.1rem !important;
218
+ }
219
+
220
+ /* Examples section */
221
+ .examples-heading {
222
+ font-family: 'DM Mono', monospace !important;
223
+ font-size: 0.7rem !important;
224
+ text-transform: uppercase !important;
225
+ letter-spacing: 0.08em !important;
226
+ color: var(--text-muted) !important;
227
+ margin-top: 1.25rem !important;
228
+ margin-bottom: 0.5rem !important;
229
+ }
230
+
231
+ /* Example buttons */
232
+ .gallery {
233
+ gap: 0.5rem !important;
234
+ }
235
+
236
+ .gallery .gallery-item {
237
+ background: var(--bg-input) !important;
238
+ border: 1px solid var(--border-subtle) !important;
239
+ border-radius: 8px !important;
240
+ padding: 0.75rem !important;
241
+ transition: border-color 0.2s ease !important;
242
+ }
243
+
244
+ .gallery .gallery-item:hover {
245
+ border-color: var(--text-muted) !important;
246
+ }
247
+
248
+ /* Preview textbox */
249
+ .preview-box textarea {
250
+ color: var(--text-secondary) !important;
251
+ font-size: 0.78rem !important;
252
+ opacity: 0.85;
253
+ }
254
+
255
+ /* Misc */
256
+ .gr-group, .gr-block, .gr-box, .gr-panel {
257
+ background: transparent !important;
258
+ border: none !important;
259
+ }
260
+
261
+ .gr-padded {
262
+ padding: 0 !important;
263
+ }
264
+
265
+ /* Info strip */
266
+ .info-strip {
267
+ text-align: center;
268
+ padding: 1.25rem 1rem;
269
+ }
270
+
271
+ .info-strip p {
272
+ font-family: 'DM Mono', monospace !important;
273
+ font-size: 0.68rem !important;
274
+ color: var(--text-muted) !important;
275
+ letter-spacing: 0.03em;
276
+ }
277
+
278
+ .info-strip a {
279
+ color: var(--text-secondary) !important;
280
+ text-decoration: none !important;
281
+ border-bottom: 1px dotted var(--text-muted);
282
+ }
283
+ """
284
+
285
+ HUMAN_EXAMPLE = (
286
+ "Beware! Bait & Switch Scam! $1685 job turns into $4634 at Home Depot. "
287
+ "I was pricing flooring for a new construction project in my home. I went to "
288
+ "Home Depot, right seems to make sense? My project had about 900sqft of flooring "
289
+ "to do. The flooring I chose was $1.59/sqft and with labor it came to $1685. "
290
+ "I signed up, paid the deposit, and scheduled the install. The installer came out "
291
+ "and suddenly the price jumped to $4634 because of 'subfloor prep' and 'transitions'. "
292
+ "Nobody mentioned any of this when I was in the store. Complete bait and switch."
293
+ )
294
+
295
+ AI_EXAMPLE = (
296
+ "Understanding Intramuscular Injections: A Vital Medical Delivery Method. "
297
+ "When we think about receiving medication, most people immediately picture "
298
+ "swallowing pills or receiving shots in the arm. That said, intramuscular "
299
+ "injections represent a crucial and nuanced approach to drug delivery that "
300
+ "deserves a closer look. These injections deliver medication directly into "
301
+ "muscle tissue, allowing for efficient absorption into the bloodstream. "
302
+ "The technique requires careful consideration of injection site selection, "
303
+ "needle gauge, and proper anatomical knowledge to ensure both safety and "
304
+ "efficacy for the patient."
305
+ )
306
+
307
 
308
  class _TextExtractor(HTMLParser):
309
  """Simple HTML to text extractor."""
 
352
  def detect_text(text: str) -> dict:
353
  """Classify text as human-written or AI-generated."""
354
  if not text or len(text.strip().split()) < 10:
355
+ return {"error": "Please enter at least a few sentences (~50 words)."}
356
  predictions = classifier.predict(text, k=2)
357
  return {label: round(score, 4) for label, score in predictions}
358
 
 
364
  try:
365
  text = fetch_url(url)
366
  except Exception as e:
367
+ return {"error": f"Could not fetch URL: {e}"}, ""
368
  if len(text.split()) < 10:
369
+ return {"error": "Not enough readable text found at that URL."}, text[:500]
 
370
  words = text.split()
371
  if len(words) > 2000:
372
  text = " ".join(words[:2000])
373
  result = detect_text(text)
374
+ preview = text[:1500] + ("..." if len(text) > 1500 else "")
 
375
  return result, preview
376
 
377
 
378
+ with gr.Blocks(css=CSS, title="AI Text Detector", theme=gr.themes.Base()) as demo:
379
+
380
+ gr.HTML("""
381
+ <div class="header-block">
382
+ <h1>AI Text Detector</h1>
383
+ <p>
384
+ Classify text as <strong>human-written</strong> or <strong>AI-generated</strong>.
385
+ Built with <a href="https://github.com/codelion/adaptive-classifier">adaptive-classifier</a>
386
+ and trained on the <a href="https://huggingface.co/datasets/pangram/editlens_iclr">EditLens</a> dataset.
387
+ Paste text directly or enter a URL to fetch and analyze.
388
+ </p>
389
+ </div>
390
+ """)
391
 
392
  with gr.Tabs():
393
+ with gr.TabItem("Text", id="text-tab"):
394
+ with gr.Group(elem_classes="input-card"):
395
+ text_input = gr.Textbox(
396
+ lines=7,
397
+ placeholder="Paste text here to analyze...",
398
+ label="Input Text",
399
+ show_label=True,
400
+ )
401
+ text_btn = gr.Button("Analyze", variant="primary", elem_classes="detect-btn")
402
+
403
+ with gr.Group(elem_classes="result-card"):
404
+ text_output = gr.Label(num_top_classes=2, label="Result")
405
+
406
  text_btn.click(fn=detect_text, inputs=text_input, outputs=text_output, api_name="detect")
407
 
408
+ gr.HTML('<div class="examples-heading">Try an example</div>')
409
  gr.Examples(
410
+ examples=[[HUMAN_EXAMPLE], [AI_EXAMPLE]],
 
 
 
411
  inputs=text_input,
412
+ label="",
413
  )
414
 
415
+ with gr.TabItem("URL", id="url-tab"):
416
+ with gr.Group(elem_classes="input-card"):
417
+ url_input = gr.Textbox(
418
+ lines=1,
419
+ placeholder="https://example.com/article",
420
+ label="Web Page URL",
421
+ show_label=True,
422
+ )
423
+ url_btn = gr.Button("Fetch & Analyze", variant="primary", elem_classes="detect-btn")
424
+
425
+ with gr.Group(elem_classes="result-card"):
426
+ url_output = gr.Label(num_top_classes=2, label="Result")
427
+
428
+ with gr.Group(elem_classes="input-card"):
429
+ url_preview = gr.Textbox(
430
+ label="Extracted Text",
431
+ lines=5,
432
+ interactive=False,
433
+ elem_classes="preview-box",
434
+ )
435
+
436
  url_btn.click(fn=detect_url, inputs=url_input, outputs=[url_output, url_preview], api_name="detect_url")
437
 
438
+ gr.HTML("""
439
+ <div class="info-strip">
440
+ <p>
441
+ Model: <a href="https://huggingface.co/adaptive-classifier/ai-detector">adaptive-classifier/ai-detector</a>
442
+ &nbsp;&middot;&nbsp;
443
+ <a href="https://github.com/codelion/adaptive-classifier">GitHub</a>
444
+ &nbsp;&middot;&nbsp;
445
+ Best with 50+ words
446
+ </p>
447
+ </div>
448
+ """)
449
+
450
  if __name__ == "__main__":
451
  demo.launch(share=False)