Rasheed commited on
Commit
e6834de
·
verified ·
1 Parent(s): 22cf011

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -21
app.py CHANGED
@@ -28,6 +28,7 @@ import time
28
  import site_pages as SP
29
  import report_flags as RF
30
  import lt_grammar as LT
 
31
 
32
  LT.warmup() # start LanguageTool in the background (free grammar check)
33
  from humanizer import BRAND, RAINBOW, LOGO_SRC, FAVICON_SRC, FAVICON_PATH, MODES, OUT_DIR
@@ -45,17 +46,20 @@ def _stats_gauge(title, pct, caption, good_low=True):
45
  return H._gauge(title, pct, caption, invert=not good_low)
46
 
47
 
48
- def suite_panel(ai_before, ai_after, original, revised, labels=None):
49
- sim = H.similarity_to_source(original, revised)
50
- changed = H.words_changed(original, revised)
51
- lb = labels or ("AI score · before", "RoBERTa detector on your original",
52
- "AI score · after", "RoBERTa detector on the rewritten text")
 
 
 
 
 
 
53
  return f"""
54
- <div class="cs-scores">
55
- {_stats_gauge(lb[0], ai_before, lb[1])}
56
- {_stats_gauge(lb[2], ai_after, lb[3])}
57
- {H._gauge("Similarity to source", sim, "Word n-gram overlap with your original")}
58
- {H._gauge("Vocabulary changed", changed, "Share of new wording", fixed_color=BRAND["navy"])}
59
  </div>
60
  <div class="cs-disclaimer">
61
  <b>Indicators only.</b> The AI score comes from an open-source detector and will differ from Turnitin,
@@ -297,12 +301,16 @@ def _smart_result(res, hi, mod, text_mode, rep=None, name=""):
297
  gline = _grammar_line(res.get("note"))
298
  rs = res.get("report")
299
  table = ""
 
300
  if rs: # report-guided run
301
  what = "AI" if rs.get("kind") != "Similarity" else "Similarity"
302
  if rs.get("score") is not None:
303
  g_before, g_after = rs["score"], rs["est"]
304
- labels = (f"{what} · {rs['tool']}", "Score read from your uploaded report",
305
- f"{what} · estimated after", f"Estimate for the same report; re-check with {rs['tool']} to confirm")
 
 
 
306
  msg = (f"✅ **Done** — {rs['tool']} {what} score {rs['score']:g}% → estimated **{rs['est']:g}%** "
307
  f"after rewriting. Re-check with {rs['tool']} for the actual score.")
308
  else:
@@ -316,7 +324,10 @@ def _smart_result(res, hi, mod, text_mode, rep=None, name=""):
316
  table = _report_table(rs)
317
  msg += gline
318
  preview = D.build_html(res["blocks"], a, hi, mod)
319
- panel = suite_panel(g_before, g_after, res["orig"], res["new"], labels) + table
 
 
 
320
  out = [H.status_done(msg), preview, panel, res["path"], H.download_button(res["path"])]
321
  if text_mode:
322
  out.insert(1, res["text"])
@@ -324,6 +335,23 @@ def _smart_result(res, hi, mod, text_mode, rep=None, name=""):
324
 
325
 
326
  REPORT_KINDS = ["AI-writing report", "Similarity / plagiarism report"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
 
329
  def _use_report(report, kind=None, rep=None):
@@ -414,11 +442,27 @@ def smart_docx(path, target, passes, temp, mode, hi, mod, full_ai, vocab, report
414
 
415
 
416
  # ---------------- Rigorous proofread (of a finished humanized document) ----------------
417
- def _proof_core(result, original, hi, mod, rep):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  import proofread as PR
419
  path = result if isinstance(result, str) else getattr(result, "name", None)
420
  if not path or not str(path).lower().endswith(".docx"):
421
- raise gr.Error("Humanize a Word document first; the proofread works on the humanized file.")
 
 
422
  try:
423
  res = PR.proofread_docx(path, original if original and str(original).lower().endswith(".docx") else None,
424
  rep, hi, mod)
@@ -428,17 +472,53 @@ def _proof_core(result, original, hi, mod, rep):
428
  raise gr.Error(str(err))
429
  rep.meta.update(name=os.path.basename(str(path)), words=res["before"]["all_words"],
430
  before=res["before"]["pct"], after=res["after"]["pct"], output=res["path"])
431
- return (H.status_done(f"✅ **Proofread done** — {res['n']} of {res['total']} sentences corrected. "
432
- f"AI score {res['before']['pct']}% → {res['after']['pct']}%.\n\n" + res["note"]),
433
- res["path"], H.download_button(res["path"]))
 
 
 
 
434
 
435
 
436
  def proofread_doc(result, original, hi, mod, user, request: gr.Request = None):
437
  """Free follow-up step on a document the user just humanized (does not use a document from the plan)."""
438
- yield from _stream(_proof_core, (result, original, hi, mod), 3, 0, user, True, request,
439
  tool="Proofread", quota=False)
440
 
441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  # ---------------- Detector ----------------
443
  def _det_core(file, text, hi, mod, rep):
444
  if mod >= hi:
@@ -491,6 +571,7 @@ def _hum_docx_core(path, temp, min_words, mode, full_ai, vocab, rep):
491
  rep.meta.update(name=os.path.basename(str(path)), words=len(orig.split()), before=b, after=a, output=out,
492
  engine="CPU" if "CPU" in summary else "GPU")
493
  panel = suite_panel(b, a, orig, new)
 
494
  print(f"[humanizer] {summary}", flush=True)
495
  return out, dl, H.status_done(summary.split("\n\n")[0] + _grammar_line(summary)), panel, preview
496
 
@@ -1451,10 +1532,47 @@ with gr.Blocks(title=f"{APP_NAME} · CS Software Solutions", theme=theme, css=CS
1451
  sd_pr_status = gr.HTML()
1452
  sd_pr_file = gr.File(label="Download proofread .docx", interactive=False)
1453
  sd_pr_dl = gr.HTML()
 
1454
  sd_scores = gr.HTML(EMPTY)
1455
  with gr.Accordion("Final AI highlight view", open=False):
1456
  sd_prev = gr.HTML()
1457
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1458
  # ============================ DETECTOR ============================
1459
  with gr.Tab("🔍 AI Detector", id="detect"):
1460
  gr.HTML('<div class="cs-intro"><b>AI Writing Report.</b> Sentence-level highlighting '
@@ -1558,6 +1676,7 @@ with gr.Blocks(title=f"{APP_NAME} · CS Software Solutions", theme=theme, css=CS
1558
  hd_pr_status = gr.HTML()
1559
  hd_pr_file = gr.File(label="Download proofread .docx", interactive=False)
1560
  hd_pr_dl = gr.HTML()
 
1561
  hd_scores = gr.HTML(EMPTY)
1562
  with gr.Accordion("Preview of changes (first 10)", open=False):
1563
  hd_prev = gr.Markdown()
@@ -1906,11 +2025,17 @@ with gr.Blocks(title=f"{APP_NAME} · CS Software Solutions", theme=theme, css=CS
1906
 
1907
  # rigorous proofread of the humanized file
1908
  sd_pr_btn.click(lambda: WAIT, None, sd_pr_status, queue=False, **HID).then(
1909
- proofread_doc, [sd_file, sd_in, s_hi, s_mod, user_state], [sd_pr_status, sd_pr_file, sd_pr_dl],
1910
  **JOB(sd_pr_status))
1911
  hd_pr_btn.click(lambda: WAIT, None, hd_pr_status, queue=False, **HID).then(
1912
- proofread_doc, [hd_file, hd_in, s_hi, s_mod, user_state], [hd_pr_status, hd_pr_file, hd_pr_dl],
1913
  **JOB(hd_pr_status))
 
 
 
 
 
 
1914
 
1915
  # detector
1916
  dp_run = dp_btn.click(lambda: WAIT, None, dp_status, queue=False, **HID).then(detect_text, [dp_in, d_hi, d_mod, user_state], [dp_status, dp_html, dp_pdf, dp_dl], **JOB(dp_status))
 
28
  import site_pages as SP
29
  import report_flags as RF
30
  import lt_grammar as LT
31
+ import textparse as TP
32
 
33
  LT.warmup() # start LanguageTool in the background (free grammar check)
34
  from humanizer import BRAND, RAINBOW, LOGO_SRC, FAVICON_SRC, FAVICON_PATH, MODES, OUT_DIR
 
46
  return H._gauge(title, pct, caption, invert=not good_low)
47
 
48
 
49
+ def suite_panel(ai_before, ai_after, original, revised, labels=None, extra=None, sim_vocab=True):
50
+ lb = labels or ("AI · our detector, before", "Open-source detector on your original",
51
+ "AI · our detector, after", "Same detector on the rewritten text")
52
+ gauges = [_stats_gauge(lb[0], ai_before, lb[1]), _stats_gauge(lb[2], ai_after, lb[3])]
53
+ gauges += [_stats_gauge(t, v, c) for t, v, c in (extra or [])]
54
+ if sim_vocab:
55
+ sim = H.similarity_to_source(original, revised)
56
+ changed = H.words_changed(original, revised)
57
+ gauges += [H._gauge("Similarity to source", sim, "Word n-gram overlap with your original"),
58
+ H._gauge("Vocabulary changed", changed, "Share of new wording", fixed_color=BRAND["navy"])]
59
+ cols = min(4, max(2, len(gauges)))
60
  return f"""
61
+ <div class="cs-scores" style="grid-template-columns:repeat({cols},1fr)">
62
+ {"".join(gauges)}
 
 
 
63
  </div>
64
  <div class="cs-disclaimer">
65
  <b>Indicators only.</b> The AI score comes from an open-source detector and will differ from Turnitin,
 
301
  gline = _grammar_line(res.get("note"))
302
  rs = res.get("report")
303
  table = ""
304
+ extra = None
305
  if rs: # report-guided run
306
  what = "AI" if rs.get("kind") != "Similarity" else "Similarity"
307
  if rs.get("score") is not None:
308
  g_before, g_after = rs["score"], rs["est"]
309
+ labels = (f"{what} · {rs['tool']} report", "Read from the report you uploaded",
310
+ f"{what} · {rs['tool']}, estimated now", f"Our estimate for {rs['tool']}; re-check there to confirm")
311
+ if what == "AI": # our own detector, clearly labelled as a different scale
312
+ extra = [("AI · our detector, now", a["pct"],
313
+ f"Open-source detector (was {b['pct']}%); a different scale from {rs['tool']}")]
314
  msg = (f"✅ **Done** — {rs['tool']} {what} score {rs['score']:g}% → estimated **{rs['est']:g}%** "
315
  f"after rewriting. Re-check with {rs['tool']} for the actual score.")
316
  else:
 
324
  table = _report_table(rs)
325
  msg += gline
326
  preview = D.build_html(res["blocks"], a, hi, mod)
327
+ panel = suite_panel(g_before, g_after, res["orig"], res["new"], labels, extra) + table
328
+ _remember(res["path"], ours=a["pct"], ours_before=b["pct"],
329
+ tool=(rs or {}).get("tool"), kind=(rs or {}).get("kind"),
330
+ rep_score=(rs or {}).get("score"), rep_est=(rs or {}).get("est"))
331
  out = [H.status_done(msg), preview, panel, res["path"], H.download_button(res["path"])]
332
  if text_mode:
333
  out.insert(1, res["text"])
 
335
 
336
 
337
  REPORT_KINDS = ["AI-writing report", "Similarity / plagiarism report"]
338
+ RESULT_CTX = {} # sha1 of an output file -> its scores (so a later proofread can show matching indicators)
339
+
340
+
341
+ def _file_key(path):
342
+ try:
343
+ with open(path, "rb") as fh:
344
+ return hashlib.sha1(fh.read()).hexdigest()
345
+ except Exception:
346
+ return None
347
+
348
+
349
+ def _remember(path, **ctx):
350
+ k = _file_key(path)
351
+ if k:
352
+ if len(RESULT_CTX) > 2000:
353
+ RESULT_CTX.clear()
354
+ RESULT_CTX[k] = ctx
355
 
356
 
357
  def _use_report(report, kind=None, rep=None):
 
442
 
443
 
444
  # ---------------- Rigorous proofread (of a finished humanized document) ----------------
445
+ def _proof_panel(res, ctx):
446
+ """Indicators after a proofread, on the same scales as the result it was run on."""
447
+ b, a = res["before"]["pct"], res["after"]["pct"]
448
+ labels = ("AI · our detector, before proofread", "Open-source detector on the file you proofread",
449
+ "AI · our detector, after proofread", "Same detector on the proofread file")
450
+ extra = [("Sentences corrected", round(100 * res["n"] / max(1, res["total"])),
451
+ f"{res['n']} of {res['total']} sentences")]
452
+ if ctx and ctx.get("rep_est") is not None and ctx.get("kind") != "Similarity":
453
+ est = max(0, min(100, round(ctx["rep_est"] + (a - b))))
454
+ extra.insert(0, (f"AI · {ctx['tool']}, estimated after proofread", est,
455
+ f"Was {ctx['rep_est']:g}% estimated after humanizing; re-check with {ctx['tool']}"))
456
+ return suite_panel(b, a, "", "", labels, extra, sim_vocab=False)
457
+
458
+
459
+ def _proof_core(result, original, hi, mod, rep, standalone=False):
460
  import proofread as PR
461
  path = result if isinstance(result, str) else getattr(result, "name", None)
462
  if not path or not str(path).lower().endswith(".docx"):
463
+ raise gr.Error("Upload a Word (.docx) file to proofread." if standalone else
464
+ "Humanize a Word document first; the proofread works on the humanized file.")
465
+ ctx = RESULT_CTX.get(_file_key(path))
466
  try:
467
  res = PR.proofread_docx(path, original if original and str(original).lower().endswith(".docx") else None,
468
  rep, hi, mod)
 
472
  raise gr.Error(str(err))
473
  rep.meta.update(name=os.path.basename(str(path)), words=res["before"]["all_words"],
474
  before=res["before"]["pct"], after=res["after"]["pct"], output=res["path"])
475
+ _remember(res["path"], ours=res["after"]["pct"], ours_before=res["before"]["pct"],
476
+ **{k: (ctx or {}).get(k) for k in ("tool", "kind", "rep_score")},
477
+ rep_est=(None if not ctx or ctx.get("rep_est") is None else
478
+ max(0, min(100, round(ctx["rep_est"] + res["after"]["pct"] - res["before"]["pct"])))))
479
+ head = f"✅ **Proofread done** — {res['n']} of {res['total']} sentences corrected."
480
+ return (H.status_done(head + "\n\n" + res["note"]), res["path"], H.download_button(res["path"]),
481
+ _proof_panel(res, ctx))
482
 
483
 
484
  def proofread_doc(result, original, hi, mod, user, request: gr.Request = None):
485
  """Free follow-up step on a document the user just humanized (does not use a document from the plan)."""
486
+ yield from _stream(_proof_core, (result, original, hi, mod), 4, 0, user, True, request,
487
  tool="Proofread", quota=False)
488
 
489
 
490
+ PROOF_STANDALONE_FREE = os.getenv("PROOF_STANDALONE_FREE", "0") == "1"
491
+
492
+
493
+ def _proof_new_core(file, original, hi, mod, rep):
494
+ return _proof_core(file, original, hi, mod, rep, standalone=True)
495
+
496
+
497
+ def proofread_new(file, original, hi, mod, user, request: gr.Request = None):
498
+ """Proofread any Word document (a new article): uses one document from the plan unless PROOF_STANDALONE_FREE=1."""
499
+ yield from _stream(_proof_new_core, (file, original, hi, mod), 4, 0, user, True, request,
500
+ tool="Proofread", words=P.doc_words(file) if file else None, quota=not PROOF_STANDALONE_FREE)
501
+
502
+
503
+ def _proof_text_core(text, hi, mod, rep):
504
+ from docx import Document
505
+ d = Document()
506
+ for p in [p for p, _ in TP.parse_paste(text)]:
507
+ d.add_paragraph(p)
508
+ tmp = OUT_DIR / f"paste_{secrets.token_hex(6)}.docx"
509
+ d.save(tmp)
510
+ st, path, dl, panel = _proof_core(str(tmp), None, hi, mod, rep, standalone=True)
511
+ out_text = "\n\n".join(p.text for p in Document(path).paragraphs if p.text.strip())
512
+ return st, out_text, path, dl, panel
513
+
514
+
515
+ def proofread_text(text, hi, mod, user, request: gr.Request = None):
516
+ if not (text or "").strip():
517
+ raise gr.Error("Please paste some text to proofread.")
518
+ yield from _stream(_proof_text_core, (text, hi, mod), 5, 0, user, False, request, tool="Proofread",
519
+ paste=text)
520
+
521
+
522
  # ---------------- Detector ----------------
523
  def _det_core(file, text, hi, mod, rep):
524
  if mod >= hi:
 
571
  rep.meta.update(name=os.path.basename(str(path)), words=len(orig.split()), before=b, after=a, output=out,
572
  engine="CPU" if "CPU" in summary else "GPU")
573
  panel = suite_panel(b, a, orig, new)
574
+ _remember(out, ours=a, ours_before=b)
575
  print(f"[humanizer] {summary}", flush=True)
576
  return out, dl, H.status_done(summary.split("\n\n")[0] + _grammar_line(summary)), panel, preview
577
 
 
1532
  sd_pr_status = gr.HTML()
1533
  sd_pr_file = gr.File(label="Download proofread .docx", interactive=False)
1534
  sd_pr_dl = gr.HTML()
1535
+ sd_pr_panel = gr.HTML()
1536
  sd_scores = gr.HTML(EMPTY)
1537
  with gr.Accordion("Final AI highlight view", open=False):
1538
  sd_prev = gr.HTML()
1539
 
1540
+ with gr.Tab("🔍 Proofread only"):
1541
+ gr.Markdown("Proofread **any** article or chapter, no humanizing needed: grammar first (every "
1542
+ "error fixed and verified), then clarity, academic wording and flow, doubled terms "
1543
+ "and AI leftovers removed, while keeping the AI score where it was.")
1544
+ with gr.Tabs(elem_classes="sub-tabs"):
1545
+ with gr.Tab("📄 Word document 🔒"):
1546
+ _, pn_open, pn_lockbtn = lock_gate("Word documents")
1547
+ GOTO_ACCOUNT.append(pn_lockbtn)
1548
+ with pn_open:
1549
+ with gr.Row(equal_height=False):
1550
+ with gr.Column(elem_classes="cs-card"):
1551
+ pn_in = gr.File(label="Document to proofread (.docx)", file_types=[".docx"],
1552
+ type="filepath")
1553
+ pn_orig = gr.File(label="Optional: the author's original version (.docx), "
1554
+ "used to repair unclear sentences",
1555
+ file_types=[".docx"], type="filepath")
1556
+ pn_btn = gr.Button("🔍 Proofread document", variant="primary", size="lg")
1557
+ with gr.Column(elem_classes="cs-card"):
1558
+ pn_status = gr.HTML(H.status_idle("Upload a .docx and press **Proofread "
1559
+ "document**."))
1560
+ pn_file = gr.File(label="Download proofread .docx", interactive=False)
1561
+ pn_dl = gr.HTML()
1562
+ pn_panel = gr.HTML(EMPTY)
1563
+ with gr.Tab("✍️ Paste text"):
1564
+ with gr.Row(equal_height=False):
1565
+ with gr.Column(elem_classes="cs-card"):
1566
+ pt_in = gr.Textbox(label="Text to proofread", lines=12,
1567
+ placeholder="Paste a paragraph, abstract or section…")
1568
+ pt_btn = gr.Button("🔍 Proofread text", variant="primary", size="lg")
1569
+ with gr.Column(elem_classes="cs-card"):
1570
+ pt_status = gr.HTML(H.status_idle("Paste text and press **Proofread text**."))
1571
+ pt_out = gr.Textbox(label="Proofread text", lines=12, interactive=False)
1572
+ pt_file = gr.File(label="Download as .docx", interactive=False)
1573
+ pt_dl = gr.HTML()
1574
+ pt_panel = gr.HTML(EMPTY)
1575
+
1576
  # ============================ DETECTOR ============================
1577
  with gr.Tab("🔍 AI Detector", id="detect"):
1578
  gr.HTML('<div class="cs-intro"><b>AI Writing Report.</b> Sentence-level highlighting '
 
1676
  hd_pr_status = gr.HTML()
1677
  hd_pr_file = gr.File(label="Download proofread .docx", interactive=False)
1678
  hd_pr_dl = gr.HTML()
1679
+ hd_pr_panel = gr.HTML()
1680
  hd_scores = gr.HTML(EMPTY)
1681
  with gr.Accordion("Preview of changes (first 10)", open=False):
1682
  hd_prev = gr.Markdown()
 
2025
 
2026
  # rigorous proofread of the humanized file
2027
  sd_pr_btn.click(lambda: WAIT, None, sd_pr_status, queue=False, **HID).then(
2028
+ proofread_doc, [sd_file, sd_in, s_hi, s_mod, user_state], [sd_pr_status, sd_pr_file, sd_pr_dl, sd_pr_panel],
2029
  **JOB(sd_pr_status))
2030
  hd_pr_btn.click(lambda: WAIT, None, hd_pr_status, queue=False, **HID).then(
2031
+ proofread_doc, [hd_file, hd_in, s_hi, s_mod, user_state], [hd_pr_status, hd_pr_file, hd_pr_dl, hd_pr_panel],
2032
  **JOB(hd_pr_status))
2033
+ pn_btn.click(lambda: WAIT, None, pn_status, queue=False, **HID).then(
2034
+ proofread_new, [pn_in, pn_orig, s_hi, s_mod, user_state], [pn_status, pn_file, pn_dl, pn_panel],
2035
+ **JOB(pn_status))
2036
+ pt_btn.click(lambda: WAIT, None, pt_status, queue=False, **HID).then(
2037
+ proofread_text, [pt_in, s_hi, s_mod, user_state], [pt_status, pt_out, pt_file, pt_dl, pt_panel],
2038
+ **JOB(pt_status))
2039
 
2040
  # detector
2041
  dp_run = dp_btn.click(lambda: WAIT, None, dp_status, queue=False, **HID).then(detect_text, [dp_in, d_hi, d_mod, user_state], [dp_status, dp_html, dp_pdf, dp_dl], **JOB(dp_status))