Spaces:
Paused
Paused
unknown
Claude Opus 5.5
Product name "Catalyst Humanizer" everywhere (was "CS AI Writing Suite"): the top bar, page title, footer and trademark note, About and Contact pages, emails (sender name), PhonePe payment notes, detector PDF reports and the Space card; CS Software Solutions stays as the company
5a7132a |
Download README.md from Rasheed/Test_AI_writing: direct link, hf CLI and curl.
- Browser
- Download file 33 kB
-
https://huggingface.co/spaces/Rasheed/Test_AI_writing/resolve/main/README.md
- Command line
-
hf download hf://spaces/Rasheed/Test_AI_writing/README.md
-
curl -L -o README.md https://huggingface.co/spaces/Rasheed/Test_AI_writing/resolve/main/README.md
33 kB
| title: Catalyst Humanizer | |
| emoji: ✨ | |
| colorFrom: indigo | |
| colorTo: pink | |
| sdk: gradio | |
| sdk_version: 5.49.1 | |
| python_version: "3.10" | |
| app_file: app.py | |
| pinned: true | |
| license: apache-2.0 | |
| short_description: Detect AI writing and humanize it, Word layout kept | |
| # Catalyst Humanizer — by CS Software Solutions | |
| One Space, three tools in a navigation bar: | |
| | Tab | What it does | | |
| |---|---| | |
| | 🚀 **Smart Humanize** | Detector + humanizer together: scores every sentence with the AI detector, rewrites **only the flagged sentences**, re-checks, and repeats until the target AI % or the pass limit is reached. | | |
| | 🔍 **AI Detector** | Highlighted AI-likelihood view (cyan = high, purple = moderate) + PDF report. | | |
| | ✨ **Humanizer** | Full paraphrase of every eligible paragraph (Parrot T5), with the detector score before/after. | | |
| | 📊 **Dashboard** | Signed-in users: totals, words processed, average AI reduction, today's usage vs limits, 14-day chart, full run history, re-download of recent results. | | |
| | 👤 **Account** | Sign in / sign up / profile · **🔒 Change password** · **🔑 Forgot password** (6-digit email code, or recovery key) · create a new recovery key. | | |
| | 🛡️ **Admin** | Only for `ADMIN_EMAILS`. **Overview** (users, runs, guests, errors, job time, AI reduction, CPU fallbacks, 30-day charts, most active users) · **Activity** (every run incl. guests, filter by user/tool/errors, CSV export) · **Users** (search, click a row for full history, per-user daily limits, enable/disable, reset password, delete, CSV export). | | |
| Shortcut buttons link the tools: *Humanize this text →* (Detector → Smart Humanize) and | |
| *Send result to AI Detector →* (Humanizer → Detector). | |
| ## Plans (plans.py) | |
| | Plan | Price | Documents (reports) | Paste | Smart passes | | |
| |---|---|---|---|---| | |
| | Guest | free | — | 300 words, 15 runs/day | 2 | | |
| | Free account | free | 2 (lifetime trial) | 300 words, 25 runs/day | 2 | | |
| | Student | ₹4,999 / $59 per month | 5 per day | 2,000 words, 100/day | 4 | | |
| | Agency | ₹7,999 / $95 per month | 10 per day | 3,000 words, 300/day | 4 | | |
| | Pro | ₹10,000 / $119 per month | unlimited | 5,000 words, unlimited | 4 | | |
| | **Admin** (`ADMIN_EMAILS`) | **free, unlimited, never charged** | ∞ | 20,000 words | 4 | | |
| Prices can be changed with the Space variables `PRICE_STUDENT`, `PRICE_AGENCY`, `PRICE_PRO` (₹) and | |
| `PRICE_STUDENT_USD`, `PRICE_AGENCY_USD`, `PRICE_PRO_USD` ($); `PRICE_CURRENCY` (INR / USD) sets which one the Pricing page shows first. Daily limits reset at | |
| midnight IST (`DAY_TZ_OFFSET_MIN`, default 330). | |
| **Monthly subscriptions.** The users table keeps `plan_started` (subscribed since), `period_start` (current month | |
| from), `plan_until` (paid until: the same date next calendar month) and `plan_status` (active / expired / free). | |
| A plan that is not renewed by `plan_until` is switched off automatically (checked every hour and whenever the user | |
| signs in or runs a document): the user goes back to the free plan, gets an email, and the event is written to | |
| `plan_history`. Renewing before the end date adds a month to the end date; renewing after it starts a new | |
| subscription from that day. Admin → Users shows every subscription (start date, paid until, days left, status) and | |
| the full plan history. | |
| **Activating / renewing a plan today:** Admin → Users → 💎 Plans & credits → choose the plan and number of months → | |
| Activate plan. Document credits can still be added there for special cases. | |
| **Razorpay (next step):** in the payment webhook, after verifying the signature, call | |
| `plans.activate_plan(email, "student", 1, source="razorpay", amount=...)` (1 = months) or | |
| `plans.add_credits(email, 1, source="razorpay", amount=...)` — the same functions the admin buttons use. | |
| Optional variables: `PAY_WHATSAPP` (digits with country code), `PAY_EMAIL`, `PROMO_TEXT`, `FREE_TRIAL_DOCS` (default 2). | |
| ## Access rules | |
| - **Paste text** — open to everyone, no sign-in (limit `MAX_PASTE_WORDS`, default 300 words; longer texts → upload a document). | |
| - **Document upload** (.docx / .pdf / .txt) — requires a free account. Upload tabs show a lock card | |
| until the user signs in, and the server also rejects uploads from signed-out sessions. | |
| ## Setup (Settings → Variables and secrets) | |
| | Name | Type | Purpose | | |
| |---|---|---| | |
| | `HF_TOKEN` | secret | Read token of an account that accepted the terms of the gated `coai/roberta-ai-detector-v2` model | | |
| | `ADMIN_EMAILS` | secret | Comma-separated admin emails | | |
| | `APP_NAME` | variable (optional) | Header title (default "Catalyst Humanizer") | | |
| | `REPORT_BRAND` | variable (optional) | Header text on PDF report pages | | |
| | `MAX_PASTE_WORDS` | variable (optional) | Paste limit | | |
| | `DETECTOR_MODEL`, `PARA_MODEL` | variable (optional) | Swap models (`PARA_MODEL` = paraphraser; the older name `PARROT_MODEL` still works) | | |
| | `USERS_REPO` / `USERS_REPO_TOKEN` | secret | Private dataset (e.g. `you/suite-users`) + write token: backs up accounts so they survive restarts | | |
| | `DAILY_DOC_LIMIT` | variable (optional) | Document runs per signed-in user per day (default 20, 0 = unlimited) | | |
| | `DAILY_TEXT_LIMIT` | variable (optional) | Pasted-text runs per signed-in user per day (default 100) | | |
| | `DAILY_GUEST_TEXT_LIMIT` | variable (optional) | Pasted-text runs per guest (per IP) per day (default 15) | | |
| | `CONCURRENCY` | variable (optional) | Jobs of the same kind running at once (default 3) | | |
| | `QUEUE_SIZE` | variable (optional) | Max waiting jobs per button (default 60) | | |
| | `HISTORY_DAYS` | variable (optional) | How long run history is kept (default 180 days) | | |
| | `BREVO_API_KEY` | secret | Brevo **API key** (`xkeysib-…`, Brevo → SMTP & API → API Keys). Turns on result emails, email reset codes, welcome and plan mails | | |
| | `MAIL_FROM` | variable | Sender address verified in Brevo (e.g. `noreply@cssoftwaresolutions.in`) | | |
| | `MAIL_FROM_NAME`, `MAIL_MAX_MB` | variable (optional) | Sender name; max attachment size (default 8 MB) | | |
| Admins (`ADMIN_EMAILS`) are never limited. | |
| ## Email (mailer.py) | |
| Hugging Face Spaces only allow outgoing ports 80/443/8080, so Brevo's SMTP relay (`smtp-relay.brevo.com:587`) | |
| cannot connect from a Space. The app uses Brevo's HTTPS API with the same account instead. | |
| - Document results (output file + PDF report) are emailed to signed-in users (switch off in Account). | |
| - Forgot password → *Email me a 6-digit code* (15 min, 3 codes/hour, 5 tries). | |
| - Welcome mail on sign-up; plan-activated mail when a plan is activated. | |
| - Admin → Users → 📧 Email: status, *Send test email*, delivery log with the exact Brevo error and fix. | |
| - In Brevo: verify the sender, and **Security → Authorised IPs → deactivate IP blocking** (Space IPs change). | |
| ## "Result ready" notifications (push.py, static/) | |
| - Customers press **🔔 Notify me** (or any run button) and allow notifications once. | |
| - When a job finishes, the server sends a **Web Push** (VAPID, `push.py`) to their browser's service worker | |
| (`static/cs-notify-sw.js`), so the notification arrives even when the tab is in the background, the phone is | |
| locked or they are in another app. The page also shows its own notification, a chime, a flashing tab title and an | |
| animated toast — all with the same tag, so only one notification appears. | |
| - Needs `cryptography` (in requirements.txt). VAPID keys are created once and stored in the user database | |
| (backed up with `USERS_REPO`); optional `VAPID_PRIVATE_KEY` / `VAPID_CONTACT` variables. | |
| - Works on the custom domain (www.catalysthumanizer.com), not inside the huggingface.co page (browsers block | |
| notifications in embedded frames). iPhone: only when the site is added to the home screen. | |
| ## Dropped connections | |
| Jobs run in the background on the server, not inside the browser connection. If a customer's connection | |
| drops (mobile data, switching apps, Wi-Fi) the job keeps running; when the page reconnects it asks again | |
| automatically and picks up the same job — progress continues, or the finished result appears at once — with | |
| no restart and no second charge. Usage is counted and the "ready" notification is sent when the job ends, even | |
| if the page was closed. Unclaimed results are kept for 3 hours (`JOB_KEEP_SECONDS`). | |
| ## Passwords | |
| - **Recovery key**: shown once when an account is created (format `XXXX-XXXX-XXXX-XXXX`, stored only as a hash). | |
| Forgot password = email + recovery key + new password; the used key is replaced by a new one, shown on screen. | |
| - **Change password**: Account → 🔒 Change password (current + new password). | |
| - **New recovery key**: Account → 🔑 Recovery key (cancels the old key). Accounts created before this update | |
| have no key until the user creates one here. | |
| - **No key?** Forgot password → *Ask the admin*: the user leaves a WhatsApp/phone number; the request appears in | |
| Admin → Users → Reset password. Set a temporary password there and send it to them; the request is marked done. | |
| - 5 wrong recovery keys per email per hour, then the form waits an hour. | |
| ## Multiple users / commercial hosting | |
| - The app runs up to `CONCURRENCY` (default 4) jobs of each tool at the same time, verified with 3 simultaneous users. | |
| - **ZeroGPU is not suitable for paying customers**: it caps GPU slots per Space (users wait "for a GPU slot on | |
| this Space") and visitors who are not logged in to Hugging Face get only ~2 minutes of GPU a day. | |
| - **For launch use dedicated hardware** (Settings → Space hardware), e.g. *Nvidia T4 small* ($0.40/hour), and set | |
| **Sleep time → Don't sleep** so customers never hit a cold start. No code change is needed: the app detects | |
| ZeroGPU only through `SPACES_ZERO_GPU` and otherwise uses the GPU directly (or the CPU on CPU hardware). | |
| - **CPU Upgrade (8 vCPU / 32 GB)** works too: jobs go straight to the CPU and share the cores through | |
| `runtime.py` (`CPU_JOBS` jobs at once, default = cores ÷ 2 = 4). Pastes stay fast; long documents take minutes. | |
| - One Space = one machine. Beyond ~20–30 simultaneous heavy jobs, move the two models to an autoscaling endpoint. | |
| ## Automatic proofread, pay per use, yearly plans | |
| **Automatic proofread.** Every humanized text or document (Smart Humanize and Humanizer) now goes through the | |
| final check & proofread by itself; the customer downloads the proofread file. Because that proofread is the final | |
| check, the humanizer's own language-model polish is not run a second time. If the proofread cannot run, the | |
| humanized result is delivered and the message says so. `AUTO_PROOFREAD=0` = only on request (the button stays). | |
| Note: the proofread reads the whole document, so on a document with few flagged paragraphs it costs more than the | |
| old polish of the changed paragraphs only. | |
| **Pay per use.** A signed-in customer without credits or a plan can still run a document (`PAY_PER_USE=0` = the | |
| old "credits needed" block). The job runs, the scores are shown, and the result is locked behind a charge worked | |
| out from what the job used: | |
| cost = GPU seconds x COST_GPU_HOUR_USD / 3600 (Hugging Face Nvidia T4 small: 0.40) | |
| + idle share of the Space (x (COST_IDLE_FACTOR - 1); default factor 5) | |
| + what the language model charged for this job (the job's [cost] account) | |
| price = cost x (1 + PRICE_MARGIN/100) x (1 + PRICE_GST/100) x PRICE_USD_INR, rounded up to a rupee | |
| Defaults: margin 200%, GST 18%, 85 rupees to the dollar, smallest charge Rs 10 (`PRICE_MIN_INR`). Example: 5 minutes | |
| of GPU and 20 cents of language model -> cost $0.37 -> Rs 111. A pop-up shows the price; its Pay button opens | |
| `PAY_URL` (a payment page; `{amount}`, `{ref}`, `{email}`, `{item}` are filled in) or, while that is not set, the | |
| WhatsApp / email contact. The customer then presses "I have paid" to download. | |
| *Until a payment gateway is connected, a charge is confirmed by hand:* Admin -> Plans & credits -> Pay-per-use | |
| charges -> enter the reference (CS-XXXXXXXX) -> Mark paid & unlock. A gateway webhook only has to call | |
| `plans.settle_charge(ref, source="razorpay", amount=...)`. Every charge is logged with its full breakdown: | |
| `[charge] CS-... : 5120 words, 300s -> GPU $ + Space share $ + language model $ = cost $; +200% +18% GST = Rs`. | |
| Locked files live in the Space's temporary folder: a Space restart before payment loses them (the customer is told | |
| to contact you with the reference). | |
| **Yearly plans.** Scholar and Pro can be bought for a year at `YEARLY_DISCOUNT` percent (default 20) off twelve | |
| monthly payments: Rs 9,590 and Rs 28,790. The Pricing page shows them and has two more buy buttons. To activate: | |
| Admin -> Plans & credits -> Activate plan with Months = 12. | |
| ## Development and testing | |
| ``` | |
| python -m venv .venv && .venv/Scripts/pip install -r requirements.txt gradio==5.49.1 pytest # (bin/ on Linux) | |
| python -m pytest tests # 79 fast unit tests, no model download, no network | |
| python devtools/run_local.py # the app on http://localhost:7860 | |
| python devtools/e2e.py smart --sample social --passes 3 # real models + Groq on a sample text | |
| python devtools/e2e.py docx # Word path: checks headings, bold/italic, superscripts, table, references | |
| ``` | |
| - Secrets for local runs go in `.env` (git-ignored; `KEY=value` lines, same names as the Space secrets). | |
| `devtools/env.py` loads it; the Space itself keeps using Settings → Variables and secrets. | |
| - `tests/` covers what must never regress: the rewrite guard (numbers, relation signs such as `p < 0.01`, | |
| citations, brackets, terms, typography), the publication rules, sentence splitting, in-place Word editing with | |
| formatting, the detector score cache and the final-polish protection. | |
| - `HUM_DEBUG=1` prints every candidate of every sentence with its AI score and the reason each rejected version | |
| was rejected (server log) — the quickest way to see why a sentence stayed unchanged. | |
| - `SKIP_MODEL_LOAD=1` delays loading the paraphraser and detector to first use (tests, tools). | |
| **What a rewrite may never do** (checked for every version, whatever wrote it): change or drop a number, unit, | |
| relation sign (`<`, `=`, `≥` ...), citation, acronym or protected term; add or duplicate a citation; leave a bracket | |
| open; change negation or the direction of a comparison; strengthen a hedged claim; add a cause ("because", | |
| "therefore") or a "we" the author did not write; split or merge sentences. Characters that language models | |
| substitute (no-break hyphens, curly quotes, the minus sign) are turned back into the author's own typography. | |
| **Meaning check.** After the rules, every rewritten sentence is read once more against the author's sentence by | |
| the language model (`groqai.verify`, `HUM_VERIFY=0` = off). It catches what rules and a similarity score cannot: | |
| a p-value moved to another claim, "increased by 38% compared with the control" turned into "the control was 38% | |
| lower", a dropped method, "but" turned into "so", an association turned into a cause. A sentence that fails gets | |
| the next version that passes (`HUM_VERIFY_ALTS`, default 3), else the author's wording. On 16 subject samples it | |
| replaced about one rewrite in three; expect a somewhat higher AI score in exchange for text that says what the | |
| author said. | |
| **Groq limits.** Groq counts rate limits per model. When a model reaches its per-minute or daily limit the request | |
| goes to the next model in `GROQ_MODELS` at once (then to the next key), and back to the preferred model when its | |
| limit has passed, so one key now gives the quota of every listed model instead of only the first. | |
| **Subjects.** `python devtools/batch.py` runs Smart Humanize on one sample per subject (engineering, computing, | |
| medicine, public health, economics, finance, psychology, education, law, chemistry, biology, environment, | |
| management, history) and writes every sentence before / after with its checks to `devtools/logs/batch.md`. | |
| **Speed.** Detector scores are cached per text and new texts are scored shortest-first (`DETECTOR_CACHE`, default | |
| 20,000 texts). On CPU hardware only the int8 copy of the paraphraser is kept in memory, and the wording / | |
| faithfulness models are loaded only when the chosen `PRIORITY` uses them. | |
| ## Add money (Pricing tab) and PhonePe | |
| Pricing tab -> **Add money to your wallet**: pick ₹100 / ₹250 / ₹500 / ₹1,000 or type a whole number of rupees | |
| (₹50 to ₹20,000; `₹1,250` and `1250` both work). The customer is sent to PhonePe, pays by UPI / card / net banking and | |
| comes back to the Pricing tab with the balance updated. If they close the page, PhonePe's webhook (and a background | |
| poll) still credits the wallet, exactly once. Without PhonePe credentials the button shows the WhatsApp/UPI fallback. | |
| **Try it locally without credentials:** set the environment variables `PHONEPE_ENV=mock` and | |
| `PUBLIC_URL=http://127.0.0.1:7860`. A fake PhonePe page with **Pay** / **Cancel** buttons is served at | |
| `/phonepe/mock/pay`; no money moves. The simulator switches itself off on a Hugging Face Space (`SPACE_ID` is set | |
| there), so it can never reach customers. Real PhonePe: `PHONEPE_ENV` defaults to `production`; use `sandbox` while testing. | |
| ## Files | |
| - `app.py` — UI, navigation bar, sign-in gating, handlers | |
| - `smart.py` — detect → rewrite flagged sentences → re-check loop (in-place Word editing) | |
| - `detector.py` — AI detector + PDF/HTML report | |
| - `docx_report.py` — Word highlighting + LibreOffice conversion | |
| - `humanizer.py` — Parrot paraphraser | |
| - `auth.py` — accounts, sign-in sessions, limits, admin user actions | |
| - `dashboard.py` — run history, user dashboard, admin analytics | |
| - `plans.py` — pricing plans, quotas, credits, upgrade prompts, manual/Razorpay activation | |
| - `textparse.py` — pasted-text parsing, long-sentence handling, rewrite guard | |
| - `mailer.py` — result emails, reset codes, welcome/plan mails (Brevo API) | |
| - `groqai.py` — Groq quality check of Parrot's output (repairs terms/grammar, rejects meaning changes) | |
| - `push.py` — Web Push notifications when a result is ready | |
| - `static/` — notification service worker + icon | |
| - `runtime.py` — CPU core sharing between simultaneous users (CPU hardware) | |
| - `packages.txt` — LibreOffice + fonts (Word → PDF reports) | |
| - `logo.png`, `favicon.png` | |
| ## Groq quality check (groqai.py) — optional, recommended | |
| **Parrot does all the rewriting.** With the `GROQ_API_KEY` secret set, Groq then checks every sentence Parrot changed: | |
| it gets the original (A) and Parrot's paraphrase (B) and makes only the minimum edits to B — puts back any | |
| technical term, acronym, number, unit or citation exactly as in A, fixes broken grammar and casual wording, and | |
| keeps Parrot's phrasing everywhere else. If B changed the meaning and cannot be repaired, it is rejected and the | |
| original sentence is kept. The local guard checks Groq's result once more. Without Groq (no key, quota used up, | |
| network error) the local checks alone are used, exactly as before. | |
| Admin → Users → ✅ Groq quality check shows the model, checked / repaired / rejected counts, tokens and last error. | |
| | Variable | Default | Purpose | | |
| |---|---|---| | |
| | `GROQ_API_KEY` | — | secret; turns the check on | | |
| | `GROQ_MODELS` | `openai/gpt-oss-120b,openai/gpt-oss-20b` | first model available on the key is used; the next one when it is at a rate limit. Keep expensive models out of this list: `qwen/qwen3.8-27b` costs about six times more per token | | |
| | `GROQ_LIGHT_MODEL` / `GROQ_LIGHT_KINDS` | `openai/gpt-oss-20b` / `verify,grammar` | half-price model for the tasks that only judge or correct a sentence; `GROQ_LIGHT_MODEL=` (empty) = main model for everything | | |
| | `GROQ_JOB_BUDGET` | `1.00` | ceiling in US$ for one job; past it the job continues with the local checks (0 = no ceiling) | | |
| | `GROQ_PRICES` | built-in table | `model=in/out` per million tokens, comma separated, when Groq changes its prices | | |
| | `POLISH_CHANGED_ONLY` | `1` | the final polish is sent only for paragraphs the tool changed; `0` = every paragraph | | |
| | `GROQ_BATCH` / `GROQ_MAX_WAIT` | `10` / `60` | sentence pairs per request / max seconds to wait on rate limits | | |
| **What it costs (measured, October 2026 prices: gpt-oss-120b $0.15 in / $0.60 out per million tokens, gpt-oss-20b | |
| half of that; input read from Groq's prompt cache is billed at half price).** One full pass of every task over | |
| 5,000 words is about 6-7 US cents: the natural-style rewrites are a little over half of it (they return several | |
| versions of each sentence), the quality check and final polish about 15% each, the meaning check and grammar check | |
| together under 10%. A fully AI-written 5,000-word document that needs 2-4 passes therefore costs roughly 10-25 | |
| cents; a document with only some flagged paragraphs costs proportionally less, because only flagged sentences are | |
| rewritten and only changed paragraphs are polished. Every job prints its own bill in the Space log: | |
| `[cost] Smart Humanize, 5120 words: 14 requests, 61.2k tokens in (38% from cache) + 19.8k out = $0.0191 | variants ...`. | |
| The free tier (200K tokens per model per day) covers only a handful of documents. | |
| Customer text is sent to Groq — mention this | |
| in your privacy policy. | |
| ## Smart Humanize details | |
| - **Best of 3**: Parrot samples 3 paraphrases of every sentence in one pass (`HUM_CANDIDATES`, default 3); lost | |
| technical terms are copied back in place, broken versions are dropped, and the AI detector picks the most | |
| human-sounding survivor (`HUM_SELECT=0` turns the detector choice off). Groq then checks the chosen sentence. | |
| - **100% AI-written box**: Parrot writes 5 versions of every sentence instead of 3 (`HUM_CANDIDATES_AI`), the | |
| plain-wording rules add more options, the detector ranks them, and Groq checks the 2 most human versions | |
| (`HUM_GROQ_SHORTLIST`). The most human version Groq accepts is kept; if both are rejected, the next | |
| version or the original sentence is used. Parrot still does all the rewriting; Groq only checks. | |
| It also aims for 70% new wording per sentence (`HUM_AI_CHANGE_TARGET`, same measure as the "Vocabulary | |
| changed" gauge): sentences below it get up to 2 extra Parrot rounds with more variation (`HUM_AI_ROUNDS`), | |
| versions that reach it are preferred, and Groq's check may not pull the wording back toward the original. | |
| In Smart Humanize each pass already rewrites again, so the inner extra rounds drop to 1 (`SMART_AI_ROUNDS`) | |
| and the separate retry is skipped in this mode. Speed settings: `HUM_GPU_BATCH` (32), `GROQ_PARALLEL` (5), | |
| `GROQ_BATCH` (12). | |
| - Speed: sentences are batched shortest-first and generation stops just past the input length; Groq batches run | |
| 3 at a time (`GROQ_PARALLEL`). | |
| - Sentences with P(AI) ≥ the moderate threshold (default 0.50) are rewritten; each later pass uses | |
| slightly bolder variation. | |
| - A rewrite is rejected (original kept) if it drops numbers or changes length drastically; dropped | |
| citations like `[12]` or `(Kumar et al., 2021)` are re-attached. | |
| - **Technical terms are protected**: a rewrite is rejected if it changes an acronym/symbol/model name | |
| (CNN, LiFePO4, ResNet-50, 5G, α), a term defined in the document ("phase change material (PCM)"), a phrase | |
| or word the document uses repeatedly ("state of charge", "battery"), or a word whose synonyms change the | |
| meaning ("accuracy", "model", "layer", "voltage"…). Rejected sentences get one calmer retry; if that also | |
| damages a term, the original sentence is kept. | |
| - In Word files only the flagged sentence's text is replaced; the rest of the paragraph keeps its | |
| formatting. Citation fields, hyperlinks and superscript/subscript runs are never edited — the prose | |
| around them is rewritten instead. | |
| Scores are indicators from an open-source detector and will differ from Turnitin, GPTZero, etc. | |
| ## Licences | |
| Paraphraser: humarin/chatgpt_paraphraser_on_T5_base, OpenRAIL (default) · Parrot paraphraser (fallback): Apache-2.0 · RoBERTa AI detector: Apache-2.0. | |
| ## Final grammar check (Groq) | |
| After all rewriting is finished, Groq corrects grammar, spelling and punctuation only (no rewording). A correction is | |
| kept only if it changes at most 25% of a sentence's words (`HUM_GRAMMAR_MAX_EDIT`) and keeps every technical term, | |
| number and citation. Smart Humanize runs it once on the final version. `HUM_GRAMMAR=0` turns it off. Without a Groq | |
| key the step is skipped. | |
| ## Report-guided rewriting (Smart Humanize) — image processing | |
| Upload an AI or similarity report PDF from any checker (Turnitin, iThenticate, GPTZero, ZeroGPT, Copyleaks, Academi, | |
| scanned reports too). `report_flags.py` renders every page to an image and, for every word, looks at the pixels in | |
| the word's box: a tinted background (cyan, purple, yellow ... even very pale tints) = highlighted; coloured letters = | |
| coloured text. Word positions come from the PDF text layer, or from Tesseract OCR for scanned pages (parallel). | |
| Flagged words are grouped into the report's sentences (cyan = High, purple = Moderate), matched to the document | |
| (word-trigram overlap >= 50%) and only those sentences are rewritten in one pass. The result shows the report's own | |
| score -> an estimated score, and a table of every flagged sentence with its rephrased version. | |
| Flagged sentences get the strongest rewrite: `restructure.py` reshapes them first (clause order, splits, | |
| stock openers; rule-based, no AI model), then Parrot with the 100% AI settings and a 60% new-wording target. | |
| Rows where less than half the wording changed are marked "Likely still flagged" for the writer to revise. | |
| Needs `tesseract-ocr` (packages.txt) and `pytesseract` (requirements.txt) for scanned reports. | |
| ## Final polish (Groq) | |
| After all rewriting, the finished text goes through a final polish, paragraph by paragraph. Groq sees each | |
| paragraph together with the author's ORIGINAL paragraph. **Unclear or garbled sentences are repaired** to the | |
| original meaning (clarity wins over the AI score for these); a repair that adds facts or drifts in meaning is | |
| rejected and the clear original sentence is put back. Clear sentences get the light polish: grammar, technical | |
| academic wording (wrong-sense or informal words, redundancy) and flow between sentences (logical links, varied | |
| openings, smoother word order). Sentences are never merged, split, dropped or reordered, so the Word layout stays | |
| intact. A polished sentence is kept only if it keeps every term, number and citation, keeps its meaning (meaning | |
| check), changes at most `POLISH_MAX_EDIT` of its words and, with `POLISH_KEEP_HUMAN=1`, does not turn a sentence | |
| the detector reads as human into one it reads as AI-written; otherwise the grammar-only correction is used. | |
| LanguageTool then fixes any remaining mechanical errors. | |
| **The polish never undoes the humanization.** The editor model tends to call a correctly humanized sentence | |
| "unclear" and rewrite it back towards the original. A repair is therefore accepted as a repair only when the | |
| sentence really fails the clarity checks against the author's sentence (defects, structure, grammar, meaning). A | |
| sentence that passes them is clear: its "repair" is treated as an optional edit, kept only if it does not raise the | |
| AI score of the sentence read with its neighbours, and all optional edits of a paragraph are undone together if the | |
| paragraph as a whole reads as AI-written again (`POLISH_KEEP_HUMAN=0` switches this protection off). | |
| | Variable | Default | Effect | | |
| |---|---|---| | |
| | `POLISH` | `1` | `0` = grammar-only final check | | |
| | `POLISH_MAX_EDIT` | `0.6` | Largest share of a sentence's words the polish may change | | |
| | `POLISH_KEEP_HUMAN` | `1` | `0` = always keep the polished wording, even if the AI score rises | | |
| ## Token use and key distribution (groqai.py) | |
| - **Changes-only replies**: the grammar check, quality check and polish return only the sentences they changed; | |
| every other id is listed under `"unchanged"`. Ids missing from both lists are retried, so nothing is skipped. | |
| This cuts output tokens by roughly 80% on a typical document. | |
| - **Size-based batches**: a request holds up to `GROQ_BATCH` sentences (default 20) and about `GROQ_BATCH_CHARS` | |
| characters of text (default 6000), so the fixed system prompt is shared by more sentences (about 40% fewer requests). | |
| - **Token-aware key choice**: each Groq reply reports the key's remaining tokens per minute; the next request goes | |
| to the key with the most room (requests in flight are booked against it), and waits only when no key has room. | |
| - **Right-sized `max_tokens`**: sized to the reply, plus a separate allowance for the hidden reasoning of | |
| reasoning models (`GROQ_REASONING`: low / medium / high). A reply cut off at the limit is retried once with a | |
| larger budget, and complete items are salvaged from a cut-off reply. | |
| - **Cache**: a sentence already checked (grammar / quality check) is not sent again (`GROQ_CACHE`, default 20000). | |
| - Admin → Groq status now shows tokens per key and cache hits. | |
| ## Rigorous proofread: quality settings | |
| - Repairs of unclear or garbled sentences are judged against the author's original sentence (they used to be | |
| rejected for not resembling the garbled text). They count as required fixes and are never undone or reworded | |
| by the AI-score steps. | |
| - A sentence with a grammar error gets the full correction when its AI rise stays within `PROOF_REQ_RISE`; | |
| otherwise it gets the minimal grammar fix. | |
| - The full proofread runs with `PROOF_REASONING` (default `medium`); the grammar audit stays on `low`. | |
| | Variable | Default | Effect | | |
| |---|---|---| | |
| | `PROOF_KEEP_RISE` | `0.05` | Largest AI rise allowed for a style-only edit (was 0.03) | | |
| | `PROOF_ALT_RISE` | `0.10` | A corrected sentence is reworded for the AI score only above this rise | | |
| | `PROOF_MAX_DOC_RISE` | `2.0` | Document AI points allowed before style edits are undone (was 1.0) | | |
| | `PROOF_VARIANTS` | `3` | Alternative versions per sentence when rewording (was 4) | | |
| | `PROOF_REPAIR_SIM` | `0.60` | Minimum similarity of a repair to the current sentence (original known) | | |
| | `PROOF_REPAIR_SIM_NOREF` | `0.70` | Same, when no original document is given | | |
| | `PROOF_REASONING` | `medium` | Reasoning effort of the full proofread | | |
| ## Paraphrase model | |
| Parrot (`prithivida/parrot_paraphraser_on_T5`) is the paraphraser. `PARA_MODEL` (or the older `PARROT_MODEL`) can | |
| name another T5 paraphraser with the same `paraphrase:` input, e.g. `humarin/chatgpt_paraphraser_on_T5_base` | |
| (`PARA_REP_PENALTY`, default 1.0 for Parrot and 1.15 for other models, discourages copying the input back). | |
| ## Adversarial Paraphrasing (detector-guided rewriting, adversarial.py) | |
| Technique from Cheng et al. (2025), *Adversarial Paraphrasing: A Universal Attack for Humanizing AI-Generated Text* | |
| (arXiv:2506.07001). While Parrot writes a sentence, the AI detector chooses the words: at every step Parrot proposes | |
| its most likely next tokens, each one is appended to the text so far and scored by the detector, and the most | |
| human-reading one is kept. Parrot keeps the sentence fluent (only its own likely tokens are allowed); the detector | |
| steers the wording. The result is one extra version per sentence next to Parrot's sampled versions, and it goes | |
| through exactly the same checks (terms, numbers, citations, meaning, fluency, clarity gate, Groq), so a damaged | |
| version is never chosen. | |
| | Variable | Default | Effect | | |
| |---|---|---| | |
| | `HUM_ADV` | `ai` | `ai` = "100% AI-written" box and report-guided rewriting · `always` · `off` | | |
| | `ADV_K` | `8` | Candidate words scored per step (the paper uses 50; more = stronger, slower) | | |
| | `ADV_TOP_P` / `ADV_MIN_P` | `0.95` / `0.05` | Only Parrot's likely words are candidates (keeps grammar intact) | | |
| | `ADV_EVERY` | `1` | Guide every n-th word (`2` halves the detector cost) | | |
| | `ADV_BATCH` / `ADV_MAX` | `16` / `400` | Sentences decoded together / most sentence pieces per job | | |
| | `ADV_ON_ZERO` | `0` | ZeroGPU hardware: off (it would need nested GPU calls); `1` = run on CPU | | |
| Cost: about 2-3x the paraphrasing time for the sentences it is used on (dedicated GPU). Admin → Users shows its | |
| counters next to the Groq status. | |
| ## Protection against construct renaming and other rewrite errors (textparse.py) | |
| Checked on every rewrite, in every mode (including report-guided rewriting, where ordinary words are free): | |
| - **Study constructs are locked.** Terms defined with an abbreviation ("travel satisfaction (TS)") and the Keywords | |
| line are read from the whole document; a rewrite that drops one, or brings in an everyday synonym for it | |
| ("pleasure"/"contentment" for satisfaction, "closeness" for intimacy, "allegiance" for loyalty), is rejected. | |
| - **Abbreviations** may not change ("EWB" -> "EWD") or appear from nowhere. | |
| - **Statistics symbols** (< > ≤ ≥ = ± ×) must survive ("p < .001" never becomes "p .001"). | |
| - **Brackets** must stay balanced ("(TS amongst ..."). | |
| - **Foreign words** ("well-être") and **off-topic vocabulary** (2+ uncommon words that occur nowhere in the | |
| document, e.g. "organizational recruitment" in a tourism paper; `HUM_OFFTOPIC`, 0 = off) are rejected. | |
| A rejected rewrite falls back to another checked version, or to the author's sentence. |