Owner: Engineering Team | Last Updated: 2026-01-30 | Status: Current
Note: The humanizer uses
fetch()while the rest of the codebase usesaxios. This inconsistency exists in the source code. See the API Integration page for the standard axios pattern.
The humanizer is the core feature of WWAI. It transforms AI-generated text to appear human-written, with configurable readability, purpose, and anti-detection strength. The main component is Humanizer.tsx (951 lines) which orchestrates text input, settings selection, API calls, and result display.
1. Input text (paste, upload file, or type)
│
2. Configure settings:
├── Readability level (dropdown — 5 options)
├── Purpose category (dropdown — 11 options)
└── Anti-detection strength (tab selector — 3 levels: 0, 1, 2)
│
3. Click "Humanize & Scan"
│
4. View results:
├── Humanized output text (streamed/returned)
├── Detection scores (9 external detectors)
├── Word count display
└── Actions: Copy, Download, Save, Rate (Like/Dislike)
| Component | File | Purpose |
|---|---|---|
| Humanizer | components/partials/humanizer/Humanizer.tsx |
Main container: text editor, settings, action buttons |
| Humanize (button) | components/partials/humanizer/HumanizeButton.tsx |
"Humanize & Scan" button with API call via fetch() |
| Detector (button) | components/partials/humanizer/DetectorButton.tsx |
"Scan for AI" button with API call via axios |
| HumanizerLayout | HumanizerLayout.tsx |
Layout wrapper with split panels |
| HumanizerContext | HumanizerContext.tsx |
React context for shared humanizer state |
| HumanizerSelect | HumanizerSelect.tsx |
Dropdown component for readability/purpose |
| HumanizerBanner | HumanizerBanner.tsx |
Promotional banners (1hr cache) |
| DetectorScorecard | DetectorScorecard.tsx |
Grid of 9 detector score cards |
| DetectorResult | DetectorResult.tsx |
Single detector result row |
| HumanizerPreloader | HumanizerPreloader.tsx |
Loading animation during processing |
| HumanizerHistoryModal | HumanizerHistoryModal.tsx |
Document history viewer |
| TiptapEditor | TiptapEditor.tsx |
Rich text editor (TipTap-based) |
| TiptapToolbar | TiptapToolbar.tsx |
Editor formatting toolbar |
From Humanizer.tsx:
const Readability: HumanizerSelectOptions[] = [
{ id: 2, name: t('university'), value: 'University', icon: '🎓 ' },
{ id: 1, name: t('highSchool'), value: 'High School', icon: '📚 ' },
{ id: 3, name: t('doctorate'), value: 'Doctorate', icon: '🧠 ' },
{ id: 4, name: t('journalist'), value: 'Journalist', icon: '📰 ' },
{ id: 5, name: t('marketing'), value: 'Marketing', icon: '📈 ' },
]
Default: University (id: 2, first in list). Free users can only select the first option; others require a paid plan.
From Humanizer.tsx:
const Purpose: HumanizerSelectOptions[] = [
{ id: 1, name: t('general'), icon: '📖 ', value: 'General Writing' },
{ id: 2, name: t('academic'), icon: '🎓 ', value: 'Academic' },
{ id: 3, name: t('marketing'), icon: '📈 ', value: 'Marketing Material' },
{ id: 4, name: t('business'), icon: '💼 ', value: 'Business Material' },
{ id: 5, name: t('essay'), icon: '📝 ', value: 'Essay' },
{ id: 11, name: t('email'), icon: '📧 ', value: 'Email' },
{ id: 6, name: t('legal'), icon: '⚖️ ', value: 'Legal Material' },
{ id: 7, name: t('story'), icon: '🎤 ', value: 'Story' },
{ id: 8, name: t('letter'), icon: '📃 ', value: 'Cover Letter' },
{ id: 9, name: t('report'), icon: '📂 ', value: 'Report' },
{ id: 10, name: t('blog'), icon: '📰 ', value: 'Article' },
]
Default: General Writing (id: 1). Free users can only select id=1; others require a paid plan.
const tabItem = [t('simple'), t('standard'), t('enhanced')]
// Maps to index: Simple=0, Standard=1, Enhanced=2
| Level | Index | API Value | Description |
|---|---|---|---|
| Simple | 0 | "0" |
Light rewriting, preserves original structure |
| Standard | 1 | "1" |
Moderate rewriting (default for paid users) |
| Enhanced | 2 | "2" |
Aggressive rewriting for maximum bypass |
Important: The strength is 0-2 (3 levels), NOT 0-4 as previously documented. Free users can only use Simple (0); Standard and Enhanced require a paid plan.
Default: anti_detection: 2 (from Redux initial state in humanizerSelectionSlice.ts).
The humanizer uses fetch() (NOT axios) for the API call. This is an intentional inconsistency with the rest of the codebase which uses axios.
From HumanizeButton.tsx:
const userData = {
history_id: historyId,
content: sanitizedContent,
readability: readabilitySelected.value || '', // e.g., "University"
purpose: purposeSelected.value || '', // e.g., "General Writing"
detection_strength: detectionStrength, // "0", "1", or "2"
language: 'English',
is_plain_text: isPlainText,
}
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/feature/humanizer/`,
{
method: 'POST',
body: JSON.stringify(userData),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${sanitizedToken}`,
},
}
)
| Field | Type | Example | Description |
|---|---|---|---|
history_id |
string|null | "abc123" |
Document history ID (null for new documents) |
content |
string | "AI text..." |
The text to humanize (sanitized) |
readability |
string | "University" |
One of the 5 readability value strings |
purpose |
string | "General Writing" |
One of the 11 purpose value strings |
detection_strength |
string | "2" |
"0", "1", or "2" |
language |
string | "English" |
Language (currently hardcoded to "English") |
is_plain_text |
boolean | true |
Whether to return plain text (vs. HTML) |
const data = await response.json()
data.result // string — humanized output text
data.history // string — document title
data.version.history // string — document history ID
data.credit // number — updated credit balance
| Endpoint | Purpose |
|---|---|
POST /api/feature/humanizer/ |
Humanize text (authenticated users) |
POST /api/feature/free-humanizer/ |
Free tier humanization (limited) |
POST /api/feature/file-to-html/ |
Upload .docx/.pdf/.txt file and convert to text |
Supported file formats: .docx, .pdf, .txt
.txt files are read directly via FileReader.docx and .pdf files are uploaded to /api/feature/file-to-html/ for server-side conversionUser preferences are managed by humanizerSelectionSlice.ts:
interface HumanizerSelectionState {
readability: HumanizerSelectOptions | null
purpose: HumanizerSelectOptions | null
anti_detection: number // 0, 1, or 2
}
Two async thunks sync settings with the backend:
fetchHumanizerSelectionData: GET /api/feature/remember-humanizer-selection/postHumanizerSelectionData: POST /api/feature/remember-humanizer-selection/The "Save my preferences" toggle dispatches postHumanizerSelectionData on activation.
The humanizer tracks 4 independent loading states:
originalHumanizerLoading — "Humanize" button processingoriginalDetectorLoading — "Scan" button processinghumanizedHumanizerLoading — Humanized panel humanize processinghumanizedDetectorLoading — Humanized panel detector processingAny active loading state disables both buttons.
| Check | Condition | Action |
|---|---|---|
| Per-request word limit | wordCount > totalWordsPerRequest |
Shows plan limit warning |
| Total credit balance | wordCount > userCredit |
Shows insufficient credits warning |
| Minimum word count | wordCount < 50 |
Toast: "Please enter at least 50 words" |
| Character limit | content.length > 150,000 |
Toast: "Character limit exceeded" |
| File size limit | file.size > 15 MB |
Toast: "File size limit exceeded" |
| File type | Not .docx, .pdf, .txt |
Toast: "Unsupported file format" |
| Concurrent requests | Any loading state active | Silently returns |
Error messages are extracted from the response with fallback chain:
const errorMessage = errorData.detail || errorData.message || errorData.error || t('errorOccurred')
HTTP 403 errors have specific handling:
"Your subscription is paused" → Toast error"word_limit" → Word limit alert + analytics event"Your content exceeds the limit of your plan" → Plan limit UI"You have not enough credits" → Credit warning UIAll errors are logged to Sentry via logError() with context: userId, email, credits, wordCount, plan.
| Date | Author | Change |
|---|---|---|
| 2026-01-30 | Admin | Initial creation |
| 2026-01-30 | Docs team | Rewritten with real code: exact readability (5), purpose (11), strength (0-2) values, fetch() API pattern, file upload, Redux persistence, validation rules |
Prev: Web App - Troubleshooting | Next: Web App - Detector Feature | Up: WalterWrites