Spaces:
Paused
Paused
Upload 3 files
Browse files- README.md +16 -1
- humanizer.py +22 -12
- smart.py +2 -0
README.md
CHANGED
|
@@ -178,13 +178,28 @@ in your privacy policy.
|
|
| 178 |
- **Best of 3**: Parrot samples 3 paraphrases of every sentence in one pass (`HUM_CANDIDATES`, default 3); lost
|
| 179 |
technical terms are copied back in place, broken versions are dropped, and the AI detector picks the most
|
| 180 |
human-sounding survivor (`HUM_SELECT=0` turns the detector choice off). Groq then checks the chosen sentence.
|
| 181 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
plain-wording rules add more options, the detector ranks them, and Groq checks the 2 most human versions
|
| 183 |
(`HUM_GROQ_SHORTLIST`). The most human version Groq accepts is kept; if both are rejected, the next
|
| 184 |
version or the original sentence is used. Parrot still does all the rewriting; Groq only checks.
|
| 185 |
It also aims for 70% new wording per sentence (`HUM_AI_CHANGE_TARGET`, same measure as the "Vocabulary
|
| 186 |
changed" gauge): sentences below it get up to 2 extra Parrot rounds with more variation (`HUM_AI_ROUNDS`),
|
| 187 |
versions that reach it are preferred, and Groq's check may not pull the wording back toward the original.
|
|
|
|
|
|
|
|
|
|
| 188 |
- Speed: sentences are batched shortest-first and generation stops just past the input length; Groq batches run
|
| 189 |
3 at a time (`GROQ_PARALLEL`).
|
| 190 |
|
|
|
|
| 178 |
- **Best of 3**: Parrot samples 3 paraphrases of every sentence in one pass (`HUM_CANDIDATES`, default 3); lost
|
| 179 |
technical terms are copied back in place, broken versions are dropped, and the AI detector picks the most
|
| 180 |
human-sounding survivor (`HUM_SELECT=0` turns the detector choice off). Groq then checks the chosen sentence.
|
| 181 |
+
- **Single method in every mode (default `HUM_AI_METHOD=single`)**: box ticked or not, Parrot writes ONE
|
| 182 |
+
paraphrase per sentence and Groq repairs it. **Vocabulary change (%) slider**: 0 = off (one round only);
|
| 183 |
+
above 0, sentences below the level are paraphrased again, up to `HUM_SINGLE_ROUNDS` (3) extra rounds.
|
| 184 |
+
`HUM_AI_METHOD=multi` restores the saved best version (3 / 5 versions per sentence). Ticking the 100% AI box sets
|
| 185 |
+
the slider to 70; unticking sets it back to 0. In Smart Humanize (box unticked) only the sentences the
|
| 186 |
+
detector flags are rewritten, so the level applies to those sentences.
|
| 187 |
+
- **100% AI-written box — single method (default, `HUM_AI_METHOD=single`)**: Parrot writes ONE paraphrase per
|
| 188 |
+
sentence and Groq repairs it against the original (terms, meaning, grammar), as before. The user picks the
|
| 189 |
+
**Vocabulary change (%)** slider (30–90, shown when the box is ticked). Sentences below that level are
|
| 190 |
+
paraphrased again — Parrot rewrites its own version, one version per round with a little more variation —
|
| 191 |
+
up to `HUM_SINGLE_ROUNDS` (3) extra rounds; the version with the most new wording is kept. Groq's repair is
|
| 192 |
+
used unless it copies most of the original back. Set `HUM_AI_METHOD=multi` to use the method below instead.
|
| 193 |
+
- **100% AI-written box — multi method (`HUM_AI_METHOD=multi`, the saved best version)**: Parrot writes 5 versions of every sentence instead of 3 (`HUM_CANDIDATES_AI`), the
|
| 194 |
plain-wording rules add more options, the detector ranks them, and Groq checks the 2 most human versions
|
| 195 |
(`HUM_GROQ_SHORTLIST`). The most human version Groq accepts is kept; if both are rejected, the next
|
| 196 |
version or the original sentence is used. Parrot still does all the rewriting; Groq only checks.
|
| 197 |
It also aims for 70% new wording per sentence (`HUM_AI_CHANGE_TARGET`, same measure as the "Vocabulary
|
| 198 |
changed" gauge): sentences below it get up to 2 extra Parrot rounds with more variation (`HUM_AI_ROUNDS`),
|
| 199 |
versions that reach it are preferred, and Groq's check may not pull the wording back toward the original.
|
| 200 |
+
In Smart Humanize each pass already rewrites again, so the inner extra rounds drop to 1 (`SMART_AI_ROUNDS`)
|
| 201 |
+
and the separate retry is skipped in this mode. Speed settings: `HUM_GPU_BATCH` (32), `GROQ_PARALLEL` (5),
|
| 202 |
+
`GROQ_BATCH` (12).
|
| 203 |
- Speed: sentences are batched shortest-first and generation stops just past the input length; Groq batches run
|
| 204 |
3 at a time (`GROQ_PARALLEL`).
|
| 205 |
|
humanizer.py
CHANGED
|
@@ -384,12 +384,12 @@ CANDIDATES_AI = max(1, int(os.getenv("HUM_CANDIDATES_AI", "5")))
|
|
| 384 |
GROQ_SHORTLIST = max(1, int(os.getenv("HUM_GROQ_SHORTLIST", "2")))
|
| 385 |
# 100% AI mode: target share of new wording per sentence (same measure as the "Vocabulary changed" gauge)
|
| 386 |
# and the number of extra Parrot rounds for sentences below it.
|
| 387 |
-
AI_CHANGE_TARGET = float(os.getenv("HUM_AI_CHANGE_TARGET", "70"))
|
| 388 |
AI_ROUNDS = max(0, int(os.getenv("HUM_AI_ROUNDS", "2")))
|
| 389 |
# 100% AI method: "single" = Parrot writes ONE paraphrase per sentence and Groq repairs it; sentences below the
|
| 390 |
# user's vocabulary-change level are paraphrased again (Parrot re-writes its own version) up to SINGLE_ROUNDS
|
| 391 |
# more times. "multi" = the saved best version (5 Parrot versions per sentence, detector picks, Groq checks top 2).
|
| 392 |
-
AI_METHOD = os.getenv("HUM_AI_METHOD", "single").strip().lower()
|
| 393 |
SINGLE_ROUNDS = max(0, int(os.getenv("HUM_SINGLE_ROUNDS", "3")))
|
| 394 |
# Vocabulary-change level chosen by the user on the slider (per job); None -> AI_CHANGE_TARGET.
|
| 395 |
VOCAB_TARGET = contextvars.ContextVar("vocab_target", default=None)
|
|
@@ -441,12 +441,13 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
|
|
| 441 |
groups.append((g0, len(sent_map)))
|
| 442 |
|
| 443 |
full_ai = FULL_AI.get()
|
| 444 |
-
if
|
| 445 |
return _paraphrase_single(texts, temperature, progress, lo, hi, mode)
|
| 446 |
k = CANDIDATES_AI if full_ai else CANDIDATES
|
| 447 |
-
|
|
|
|
| 448 |
er = EXTRA_ROUNDS.get()
|
| 449 |
-
rounds = 1 + ((AI_ROUNDS if er is None else er) if full_ai else 0)
|
| 450 |
temp0 = min(1.5, float(temperature) + (0.2 if full_ai else 0.0))
|
| 451 |
p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.85) # room on the bar for scoring / Groq
|
| 452 |
p_gen = lo + (p_hi - lo) * (0.75 if full_ai else 1.0) # 100% AI: room for the extra rounds
|
|
@@ -620,8 +621,8 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
|
|
| 620 |
for n_, (si, ok, cand_) in enumerate(fixes):
|
| 621 |
if sc and sc[n_] > sc[m + n_] + 0.02: # never trade AI score for polish
|
| 622 |
continue
|
| 623 |
-
if target and change(si, ok) <
|
| 624 |
-
continue #
|
| 625 |
chosen[si] = ok
|
| 626 |
note += (f" ✅ Groq checked the top {shortlist} versions of each sentence." if shortlist > 1
|
| 627 |
else " ✅ Academic wording checked by Groq.")
|
|
@@ -631,6 +632,12 @@ def _paraphrase_local(texts, temperature, progress=None, lo=0.15, hi=0.85, mode=
|
|
| 631 |
|
| 632 |
sentences = [chosen[si] or s for si, (s, _, _) in enumerate(sent_map)]
|
| 633 |
changed = sum(1 for si, (s, _, _) in enumerate(sent_map) if sentences[si].strip() != s.strip())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
if full_ai:
|
| 635 |
hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
|
| 636 |
if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
|
|
@@ -675,7 +682,7 @@ def _gen_flat(flat, temp, k, state, progress, a_, b_, label):
|
|
| 675 |
|
| 676 |
|
| 677 |
def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode="Auto"):
|
| 678 |
-
"""
|
| 679 |
1. Parrot writes ONE paraphrase of every sentence (long sentences clause by clause).
|
| 680 |
2. Lost technical terms are copied back; versions that lose terms / numbers / citations are dropped.
|
| 681 |
3. Sentences whose new wording is below the user's level are paraphrased again: Parrot rewrites its own
|
|
@@ -690,9 +697,11 @@ def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode
|
|
| 690 |
groups.append((len(sents), len(sents) + len(ss)))
|
| 691 |
sents.extend(ss)
|
| 692 |
n = len(sents)
|
| 693 |
-
|
|
|
|
|
|
|
| 694 |
er = EXTRA_ROUNDS.get()
|
| 695 |
-
rounds = 1 + (SINGLE_ROUNDS if er is None else er)
|
| 696 |
glossary = TP.build_glossary(texts) | TP.GLOSSARY.get()
|
| 697 |
state = {"gpu": ON_GPU and not str(mode).lower().startswith("cpu"), "fell_back": False}
|
| 698 |
p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.95)
|
|
@@ -774,9 +783,10 @@ def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode
|
|
| 774 |
out = [chosen[si] or sents[si] for si in range(n)]
|
| 775 |
changed = sum(1 for si in range(n) if out[si].strip() != sents[si].strip())
|
| 776 |
hit = sum(1 for si in range(n) if out[si].strip() != sents[si].strip() and change(si, out[si]) >= target)
|
| 777 |
-
note += (
|
| 778 |
+ (f", up to {used + 1} rounds" if used else "")
|
| 779 |
-
+ f"; {hit} of {n} sentences reach {target:.0f}% new wording.
|
|
|
|
| 780 |
return [" ".join(out[a:b]) for a, b in groups], note
|
| 781 |
|
| 782 |
|
|
|
|
| 384 |
GROQ_SHORTLIST = max(1, int(os.getenv("HUM_GROQ_SHORTLIST", "2")))
|
| 385 |
# 100% AI mode: target share of new wording per sentence (same measure as the "Vocabulary changed" gauge)
|
| 386 |
# and the number of extra Parrot rounds for sentences below it.
|
| 387 |
+
AI_CHANGE_TARGET = float(os.getenv("HUM_AI_CHANGE_TARGET", "70")) # used when no slider value is passed
|
| 388 |
AI_ROUNDS = max(0, int(os.getenv("HUM_AI_ROUNDS", "2")))
|
| 389 |
# 100% AI method: "single" = Parrot writes ONE paraphrase per sentence and Groq repairs it; sentences below the
|
| 390 |
# user's vocabulary-change level are paraphrased again (Parrot re-writes its own version) up to SINGLE_ROUNDS
|
| 391 |
# more times. "multi" = the saved best version (5 Parrot versions per sentence, detector picks, Groq checks top 2).
|
| 392 |
+
AI_METHOD = os.getenv("HUM_AI_METHOD", "single").strip().lower() # applies with the box ticked AND unticked
|
| 393 |
SINGLE_ROUNDS = max(0, int(os.getenv("HUM_SINGLE_ROUNDS", "3")))
|
| 394 |
# Vocabulary-change level chosen by the user on the slider (per job); None -> AI_CHANGE_TARGET.
|
| 395 |
VOCAB_TARGET = contextvars.ContextVar("vocab_target", default=None)
|
|
|
|
| 441 |
groups.append((g0, len(sent_map)))
|
| 442 |
|
| 443 |
full_ai = FULL_AI.get()
|
| 444 |
+
if AI_METHOD == "single": # box ticked or not: ONE Parrot version per sentence + Groq repair
|
| 445 |
return _paraphrase_single(texts, temperature, progress, lo, hi, mode)
|
| 446 |
k = CANDIDATES_AI if full_ai else CANDIDATES
|
| 447 |
+
# normal mode: the Vocabulary change slider (0 = off = the original behaviour)
|
| 448 |
+
target = change_target() if full_ai else float(VOCAB_TARGET.get() or 0)
|
| 449 |
er = EXTRA_ROUNDS.get()
|
| 450 |
+
rounds = 1 + ((AI_ROUNDS if er is None else er) if (full_ai or target) else 0)
|
| 451 |
temp0 = min(1.5, float(temperature) + (0.2 if full_ai else 0.0))
|
| 452 |
p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.85) # room on the bar for scoring / Groq
|
| 453 |
p_gen = lo + (p_hi - lo) * (0.75 if full_ai else 1.0) # 100% AI: room for the extra rounds
|
|
|
|
| 621 |
for n_, (si, ok, cand_) in enumerate(fixes):
|
| 622 |
if sc and sc[n_] > sc[m + n_] + 0.02: # never trade AI score for polish
|
| 623 |
continue
|
| 624 |
+
if target and change(si, ok) < 0.5 * change(si, cand_):
|
| 625 |
+
continue # Groq copied most of the original back
|
| 626 |
chosen[si] = ok
|
| 627 |
note += (f" ✅ Groq checked the top {shortlist} versions of each sentence." if shortlist > 1
|
| 628 |
else " ✅ Academic wording checked by Groq.")
|
|
|
|
| 632 |
|
| 633 |
sentences = [chosen[si] or s for si, (s, _, _) in enumerate(sent_map)]
|
| 634 |
changed = sum(1 for si, (s, _, _) in enumerate(sent_map) if sentences[si].strip() != s.strip())
|
| 635 |
+
if target and not full_ai:
|
| 636 |
+
hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
|
| 637 |
+
if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
|
| 638 |
+
note += (f" 🔤 Vocabulary change {target:.0f}%"
|
| 639 |
+
+ (f", {extra_rounds} extra round(s)" if extra_rounds else "")
|
| 640 |
+
+ f": {hit} of {len(sent_map)} sentences reach it.")
|
| 641 |
if full_ai:
|
| 642 |
hit = sum(1 for si, (s0, _, _) in enumerate(sent_map)
|
| 643 |
if sentences[si].strip() != s0.strip() and change(si, sentences[si]) >= target)
|
|
|
|
| 682 |
|
| 683 |
|
| 684 |
def _paraphrase_single(texts, temperature, progress=None, lo=0.15, hi=0.85, mode="Auto"):
|
| 685 |
+
""""single" method (default, box ticked or not). Returns (paragraphs, engine_note).
|
| 686 |
1. Parrot writes ONE paraphrase of every sentence (long sentences clause by clause).
|
| 687 |
2. Lost technical terms are copied back; versions that lose terms / numbers / citations are dropped.
|
| 688 |
3. Sentences whose new wording is below the user's level are paraphrased again: Parrot rewrites its own
|
|
|
|
| 697 |
groups.append((len(sents), len(sents) + len(ss)))
|
| 698 |
sents.extend(ss)
|
| 699 |
n = len(sents)
|
| 700 |
+
full_ai = FULL_AI.get()
|
| 701 |
+
# box ticked: slider level (70 unless the user changed it); unticked: slider level, 0 = off (one round only)
|
| 702 |
+
target = change_target() if full_ai else float(VOCAB_TARGET.get() or 0)
|
| 703 |
er = EXTRA_ROUNDS.get()
|
| 704 |
+
rounds = 1 + ((SINGLE_ROUNDS if er is None else er) if target else 0)
|
| 705 |
glossary = TP.build_glossary(texts) | TP.GLOSSARY.get()
|
| 706 |
state = {"gpu": ON_GPU and not str(mode).lower().startswith("cpu"), "fell_back": False}
|
| 707 |
p_hi = lo + (hi - lo) * (0.7 if G.available() else 0.95)
|
|
|
|
| 783 |
out = [chosen[si] or sents[si] for si in range(n)]
|
| 784 |
changed = sum(1 for si in range(n) if out[si].strip() != sents[si].strip())
|
| 785 |
hit = sum(1 for si in range(n) if out[si].strip() != sents[si].strip() and change(si, out[si]) >= target)
|
| 786 |
+
note += ((" 🤖 100% AI mode:" if full_ai else "") + " ✏️ 1 Parrot version per sentence"
|
| 787 |
+ (f", up to {used + 1} rounds" if used else "")
|
| 788 |
+
+ (f"; {hit} of {n} sentences reach {target:.0f}% new wording." if target else ".")
|
| 789 |
+
+ f" ✍️ {changed} of {n} sentences rewritten.")
|
| 790 |
return [" ".join(out[a:b]) for a, b in groups], note
|
| 791 |
|
| 792 |
|
smart.py
CHANGED
|
@@ -155,6 +155,8 @@ def _rewrite_retry(sentences, temperature, mode, rep, lo, hi):
|
|
| 155 |
"""Paraphrase; sentences that come back unchanged or rejected get one bolder retry.
|
| 156 |
100% AI mode: the humanizer already re-runs weak sentences itself (1 extra round per pass here, since
|
| 157 |
every Smart pass rewrites again), so the separate retry is skipped — it only repeated the same work."""
|
|
|
|
|
|
|
| 158 |
if _full_ai():
|
| 159 |
tok = H.EXTRA_ROUNDS.set(int(__import__("os").getenv("SMART_AI_ROUNDS", "1")))
|
| 160 |
try:
|
|
|
|
| 155 |
"""Paraphrase; sentences that come back unchanged or rejected get one bolder retry.
|
| 156 |
100% AI mode: the humanizer already re-runs weak sentences itself (1 extra round per pass here, since
|
| 157 |
every Smart pass rewrites again), so the separate retry is skipped — it only repeated the same work."""
|
| 158 |
+
if getattr(H, "AI_METHOD", "") == "single" and not _full_ai():
|
| 159 |
+
return _rewrite(sentences, temperature, mode, rep, lo, hi) # one Parrot version; slider rounds only
|
| 160 |
if _full_ai():
|
| 161 |
tok = H.EXTRA_ROUNDS.set(int(__import__("os").getenv("SMART_AI_ROUNDS", "1")))
|
| 162 |
try:
|