tyfsadik commited on
Commit
62f6825
Β·
verified Β·
1 Parent(s): 292c470

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1251 -401
app.py CHANGED
@@ -1,425 +1,1275 @@
1
- # app.py - Advanced Deep Humanizer for Hugging Face Spaces
2
- # Optimized for A100/H100 GPUs - Premium Configuration
3
 
4
- import gradio as gr
5
- import torch
6
- import random
7
  import re
8
  import json
 
 
 
 
 
 
 
 
 
 
 
9
  from transformers import (
10
- AutoModelForCausalLM,
11
- AutoTokenizer,
 
 
12
  pipeline,
13
- BitsAndBytesConfig
14
  )
15
- from typing import List, Dict, Tuple
16
- import numpy as np
17
- from dataclasses import dataclass
18
- import spaces # Hugging Face Spaces utility for GPU management
19
-
20
- @dataclass
21
- class HumanizationConfig:
22
- temperature: float = 0.8
23
- top_p: float = 0.92
24
- repetition_penalty: float = 1.15
25
- max_length: int = 4096
26
- style_intensity: str = "medium" # light, medium, aggressive
27
- preserve_meaning: bool = True
28
- add_imperfections: bool = True
29
- burstiness_factor: float = 0.3 # Variation in sentence length
30
- perplexity_target: float = 25.0 # Human text usually 15-30
31
-
32
- class DeepHumanizer:
33
- def __init__(self):
34
- self.model_id = "meta-llama/Llama-3.3-70B-Instruct" # Premium model
35
- # Alternative: "Qwen/Qwen2.5-72B-Instruct" or "deepseek-ai/DeepSeek-V3"
36
-
37
- self.tokenizer = None
38
- self.model = None
39
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
40
- self.initialize_model()
41
-
42
- def initialize_model(self):
43
- """Initialize 70B model with 4-bit quantization for single A100 80GB"""
44
- print(f"Initializing {self.model_id} on {self.device}...")
45
-
46
- # 4-bit quantization config for 70B on single A100 80GB
47
- quantization_config = BitsAndBytesConfig(
48
- load_in_4bit=True,
49
- bnb_4bit_compute_dtype=torch.float16,
50
- bnb_4bit_quant_type="nf4",
51
- bnb_4bit_use_double_quant=True,
52
- )
53
-
54
- self.tokenizer = AutoTokenizer.from_pretrained(
55
- self.model_id,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  trust_remote_code=True,
57
- padding_side="left"
58
  )
59
-
60
- if self.tokenizer.pad_token is None:
61
- self.tokenizer.pad_token = self.tokenizer.eos_token
62
-
63
- # Load model with acceleration
64
- self.model = AutoModelForCausalLM.from_pretrained(
65
- self.model_id,
66
- quantization_config=quantization_config,
67
- device_map="auto",
 
 
 
 
 
 
 
68
  trust_remote_code=True,
69
- torch_dtype=torch.float16,
70
- attn_implementation="flash_attention_2" # Speed optimization
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  )
72
-
73
- self.model.eval()
74
- print("Model loaded successfully")
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def calculate_perplexity(self, text: str) -> float:
77
- """Calculate perplexity score (lower is more predictable/AI-like)"""
78
- encodings = self.tokenizer(text, return_tensors="pt")
79
- input_ids = encodings.input_ids.to(self.device)
80
-
81
- with torch.no_grad():
82
- outputs = self.model(input_ids, labels=input_ids)
83
- loss = outputs.loss
84
-
85
- perplexity = torch.exp(loss).item()
86
- return perplexity
87
-
88
- def analyze_text_patterns(self, text: str) -> Dict:
89
- """Analyze writing patterns to identify AI characteristics"""
90
- sentences = re.split(r'(?<=[.!?])\s+', text)
91
- words = text.split()
92
-
93
- # Calculate burstiness (variation in sentence length)
94
- if len(sentences) > 1:
95
- sent_lengths = [len(s.split()) for s in sentences]
96
- burstiness = np.std(sent_lengths) / (np.mean(sent_lengths) + 1e-8)
97
- else:
98
- burstiness = 0
99
-
100
- # Common AI patterns
101
- ai_patterns = [
102
- r'\b(delve|leverage|utilize|facilitate|optimize)\b',
103
- r'\b(In conclusion|Furthermore|Moreover|Additionally)\b',
104
- r'\b(It is important to note that|It should be noted that)\b',
105
- r'(\b\w+\b)\s+\1', # Repetition
106
- ]
107
-
108
- pattern_matches = sum(len(re.findall(p, text, re.I)) for p in ai_patterns)
109
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  return {
111
- "burstiness": burstiness,
112
- "avg_sentence_length": np.mean([len(s.split()) for s in sentences]) if sentences else 0,
113
- "ai_markers": pattern_matches,
114
- "formality_score": self._estimate_formality(text)
115
  }
116
-
117
- def _estimate_formality(self, text: str) -> float:
118
- """Estimate formality level 0-1"""
119
- formal_words = r'\b(therefore|thus|hence|consequently|furthermore|moreover|nevertheless)\b'
120
- informal_words = r'\b(so|but|anyway|actually|basically|like|you know)\b'
121
-
122
- formal_count = len(re.findall(formal_words, text, re.I))
123
- informal_count = len(re.findall(informal_words, text, re.I))
124
-
125
- total = formal_count + informal_count
126
- if total == 0:
127
- return 0.5
128
- return formal_count / total
129
-
130
- def generate_humanization_prompt(self, text: str, config: HumanizationConfig,
131
- style: str, analysis: Dict) -> str:
132
- """Generate sophisticated system prompt based on analysis"""
133
-
134
- imperfections_guide = ""
135
- if config.add_imperfections:
136
- imperfections_guide = """
137
- - Include natural imperfections: occasional fragments, starting sentences with conjunctions (But, And, So)
138
- - Vary punctuation usage naturally (em-dashes, occasional ellipses...)
139
- - Add conversational fillers where appropriate (well, actually, you know what I mean)
140
- - Break formal structure with rhetorical questions or personal asides
141
- """
142
-
143
- style_prompts = {
144
- "casual": "Make it sound like a knowledgeable friend explaining over coffee. Use contractions, everyday vocabulary, personal anecdotes potential.",
145
- "professional": "Keep it business-appropriate but warm. Like a smart colleague in a Slack messageβ€”not too stiff, not too loose.",
146
- "academic": "Scholarly but accessible. Reduce robotic transitions but keep the rigor. Like a passionate professor speaking, not writing a textbook.",
147
- "creative": "Vivid, varied sentence structures, rhythmic flow. Occasional metaphors, emotional undertones, unpredictable phrasing.",
148
- "reddit": "Authentic internet voice. Like a high-karma r/depthhub or r/explainlikeimfive comment. Informative but colloquial.",
149
- "twitter": "Sharp, punchy, tweet-thread style. Short sentences mixed with longer explanatory ones. Personality-forward."
150
  }
151
-
152
- style_instruction = style_prompts.get(style, style_prompts["casual"])
153
-
154
- # Adjust based on detected patterns
155
- if analysis["ai_markers"] > 3:
156
- de_ai_instruction = "CRITICAL: Remove all AI-signaling phrases (delve, leverage, moreover, it is important to note). "
157
- else:
158
- de_ai_instruction = ""
159
-
160
- prompt = f"""<|im_start|>system
161
- You are an elite linguistic surgeon specializing in humanization of AI-generated text. Your task is to transform robotic, predictable text into authentic human writing that bypasses AI detection through natural variation and cognitive authenticity.
162
-
163
- {style_instruction}
164
- {de_ai_instruction}{imperfections_guide}
165
-
166
- TECHNICAL REQUIREMENTS:
167
- - Target perplexity: {config.perplexity_target} (human range)
168
- - Burstiness factor: Inject {int(config.burstiness_factor * 100)}% variation in sentence length
169
- - Maintain core meaning: {config.preserve_meaning}
170
- - Output ONLY the rewritten text, no explanations, no markdown code blocks
171
-
172
- HUMANIZATION LAYERS:
173
- 1. Lexical variation: Replace generic AI terms with context-specific vocabulary
174
- 2. Syntactic diversity: Mix simple, compound, complex sentences irregularly
175
- 3. Semantic noise: Add slight ambiguity or subjective framing where appropriate
176
- 4. Pragmatic markers: Include hesitation, self-correction, natural flow disruptions
177
- 5. Cognitive fingerprint: Inject personal stance or mild opinion<|im_end|>
178
- <|im_start|>user
179
- Transform this text into deeply human writing:
180
-
181
- {text}<|im_end|>
182
- <|im_start|>assistant"""
183
-
184
- return prompt
185
-
186
- @spaces.GPU(duration=120) # HF Spaces GPU decorator
187
- def humanize(self, text: str, style: str = "casual", intensity: str = "medium",
188
- creativity: float = 0.8, add_typos: bool = False,
189
- target_reading_level: str = "default") -> Tuple[str, Dict]:
190
- """
191
- Main humanization pipeline with multi-step refinement
192
- """
193
- config = HumanizationConfig(
194
- temperature=creativity,
195
- style_intensity=intensity,
196
- add_imperfections=intensity in ["medium", "aggressive"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  )
198
-
199
- # Step 1: Analysis
200
- analysis = self.analyze_text_patterns(text)
201
-
202
- # Step 2: Initial rewrite
203
- prompt = self.generate_humanization_prompt(text, config, style, analysis)
204
-
205
- inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
206
-
207
- with torch.no_grad():
208
- outputs = self.model.generate(
209
- **inputs,
210
- max_new_tokens=len(text.split()) * 3, # Generous buffer
211
- temperature=config.temperature,
212
- top_p=config.top_p,
213
- repetition_penalty=config.repetition_penalty,
214
- do_sample=True,
215
- pad_token_id=self.tokenizer.pad_token_id,
216
- eos_token_id=self.tokenizer.eos_token_id,
 
 
 
 
 
 
217
  )
218
-
219
- decoded = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
220
- # Extract only the assistant's response
221
- humanized = decoded.split("assistant")[-1].strip()
222
-
223
- # Step 3: Post-processing based on intensity
224
- if intensity == "aggressive":
225
- humanized = self._inject_aggressive_variation(humanized)
226
- elif intensity == "light":
227
- humanized = self._light_touch(humanized)
228
-
229
- # Step 4: Optional imperfections
230
- if add_typos and intensity == "aggressive":
231
- humanized = self._add_natural_typos(humanized)
232
-
233
- # Step 5: Metrics calculation
234
- final_analysis = {
235
- "original_perplexity": round(self.calculate_perplexity(text), 2),
236
- "humanized_perplexity": round(self.calculate_perplexity(humanized), 2),
237
- "burstiness_change": round(self.analyze_text_patterns(humanized)["burstiness"] - analysis["burstiness"], 2),
238
- "human_score": self._calculate_human_score(humanized),
239
- "processing_style": style,
240
- "intensity": intensity
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  }
242
-
243
- return humanized, final_analysis
244
-
245
- def _inject_aggressive_variation(self, text: str) -> str:
246
- """Add high-level human variation"""
247
- # Randomly combine sentences with conjunctions
248
- text = re.sub(r'\.\s+([A-Z])', lambda m: f", and {m.group(1).lower()}" if random.random() > 0.7 else f". {m.group(1)}", text)
249
-
250
- # Add occasional fragments
251
- sentences = text.split('. ')
252
- if len(sentences) > 3 and random.random() > 0.5:
253
- idx = random.randint(1, len(sentences)-2)
254
- sentences[idx] = sentences[idx].split(',')[0] # Make first part a fragment
255
- return '. '.join(sentences)
256
-
257
- def _light_touch(self, text: str) -> str:
258
- """Minimal changes, just polish"""
259
- # Remove common AI transitions
260
- text = re.sub(r'\b(In conclusion|To summarize|Overall),\s*', '', text, flags=re.I)
261
- return text
262
-
263
- def _add_natural_typos(self, text: str) -> str:
264
- """Add believable human typos (use sparingly)"""
265
- # Very subtle: duplicate letters occasionally
266
- words = text.split()
267
- for i in range(len(words)):
268
- if random.random() > 0.98 and len(words[i]) > 4:
269
- words[i] = words[i][:2] + words[i][1] + words[i][2:]
270
- return ' '.join(words)
271
-
272
- def _calculate_human_score(self, text: str) -> int:
273
- """Estimate likelihood of passing as human 0-100"""
274
- score = 70 # Base
275
-
276
- # Check for AI markers
277
- ai_markers = len(re.findall(r'\b(leverage|delve|utilize|facilitate|optimize)\b', text, re.I))
278
- score -= ai_markers * 5
279
-
280
- # Check variation
281
- sentences = re.split(r'(?<=[.!?])\s+', text)
282
- if len(sentences) > 1:
283
- lengths = [len(s) for s in sentences]
284
- variation = np.std(lengths) / np.mean(lengths)
285
- if variation > 0.3: # Good burstiness
286
- score += 15
287
-
288
- # Check contractions
289
- if len(re.findall(r"\b\w+'\w+\b", text)) > 0:
290
- score += 10
291
-
292
- return min(100, max(0, score))
293
-
294
- # Initialize singleton
295
- humanizer = DeepHumanizer()
296
-
297
- # Gradio Interface
298
- def process_text(text, style, intensity, creativity, add_imperfections, comparison_mode):
299
- if not text.strip():
300
- return "", {}, ""
301
-
302
- humanized, metrics = humanizer.humanize(
303
- text=text,
304
- style=style,
305
- intensity=intensity,
306
- creativity=creativity,
307
- add_typos=(add_imperfections == "Aggressive")
308
- )
309
-
310
- # Format metrics display
311
- metrics_md = f"""
312
- ### πŸ“Š Analysis Results
313
-
314
- | Metric | Value | Status |
315
- |--------|-------|--------|
316
- | **Human Likelihood Score** | {metrics['human_score']}/100 | {'🟒 Human' if metrics['human_score'] > 80 else '🟑 Unclear' if metrics['human_score'] > 60 else 'πŸ”΅ AI'} |
317
- | **Perplexity Change** | {metrics['original_perplexity']} β†’ {metrics['humanized_perplexity']} | {'🟒 Good Variation' if metrics['humanized_perplexity'] > metrics['original_perplexity'] else '⚠️ Check needed'} |
318
- | **Burstiness Delta** | +{metrics['burstiness_change']:.2f} | {'🟒 Natural Flow' if metrics['burstiness_change'] > 0 else '⚠️ Monotonous'} |
319
- """
320
-
321
- if comparison_mode:
322
- comparison = f"""
323
- **Original ({len(text.split())} words):**
324
- {text[:500]}{'...' if len(text) > 500 else ''}
325
-
326
- ---
327
- **Humanized ({len(humanized.split())} words):**
328
- {humanized}
329
- """
330
- return humanized, metrics_md, comparison
331
-
332
- return humanized, metrics_md, ""
333
-
334
- # Custom CSS for premium feel
335
- css = """
336
- .gradio-container {
337
- font-family: 'Inter', sans-serif;
338
- }
339
- .metric-card {
340
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
341
- border-radius: 8px;
342
- padding: 16px;
343
- color: white;
344
- }
345
- """
346
-
347
- with gr.Blocks(css=css, title="Deep Humanizer Pro", theme=gr.themes.Soft()) as demo:
348
- gr.Markdown("""
349
- # 🧠 Deep Humanizer Pro
350
- ### Advanced AI-to-Human Text Transformation using Llama 3.3 70B
351
- *Elite-grade humanization with linguistic analysis and adversarial pattern disruption*
352
- """)
353
-
354
- with gr.Row():
355
- with gr.Column(scale=1):
356
- input_text = gr.Textbox(
357
- label="Input Text (AI-generated)",
358
- placeholder="Paste your AI-generated content here...",
359
- lines=10
360
  )
361
-
362
- with gr.Row():
363
- style = gr.Dropdown(
364
- choices=["casual", "professional", "academic", "creative", "reddit", "twitter"],
365
- value="casual",
366
- label="Voice Style"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  )
368
- intensity = gr.Radio(
369
- choices=["light", "medium", "aggressive"],
370
- value="medium",
371
- label="Humanization Intensity"
 
 
 
 
372
  )
373
-
374
- with gr.Row():
375
- creativity = gr.Slider(
376
- minimum=0.1, maximum=1.0, value=0.8, step=0.1,
377
- label="Creativity (Temperature)"
 
 
 
378
  )
379
- add_imperfections = gr.Checkbox(
380
- label="Add Natural Imperfections",
381
- value=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  )
383
-
384
- comparison_mode = gr.Checkbox(
385
- label="Show Side-by-Side Comparison",
386
- value=False
387
- )
388
-
389
- submit_btn = gr.Button("πŸš€ Humanize Text", variant="primary")
390
-
391
- with gr.Column(scale=1):
392
- output_text = gr.Textbox(
393
- label="Humanized Output",
394
- lines=10,
395
- show_copy_button=True
396
- )
397
- metrics_display = gr.Markdown()
398
- comparison_display = gr.Markdown()
399
-
400
- # Examples
401
- gr.Examples(
402
- examples=[
403
- ["Artificial Intelligence (AI) refers to the simulation of human intelligence in machines that are programmed to think like humans and mimic their actions. The term may also be applied to any machine that exhibits traits associated with a human mind such as learning and problem-solving.", "casual", "medium"],
404
- ["In conclusion, it is important to note that leveraging cutting-edge technologies can facilitate optimal outcomes for stakeholders.", "professional", "aggressive"],
405
- ],
406
- inputs=[input_text, style, intensity],
407
- label="Try these examples"
408
- )
409
-
410
- submit_btn.click(
411
- fn=process_text,
412
- inputs=[input_text, style, intensity, creativity, add_imperfections, comparison_mode],
413
- outputs=[output_text, metrics_display, comparison_display]
414
- )
415
-
416
- gr.Markdown("""
417
- ### πŸ› οΈ Technical Specifications
418
- - **Model**: Llama 3.3 70B Instruct (4-bit quantized)
419
- - **Architecture**: Flash Attention 2 + Gradient Checkpointing
420
- - **Analysis**: Perplexity scoring, burstiness calculation, AI marker detection
421
- - **GPU**: Optimized for A100/H100 (80GB VRAM)
422
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
 
424
  if __name__ == "__main__":
425
- demo.launch()
 
 
 
 
 
 
 
 
 
1
+ # app.py β€” Advanced AI Text Humanizer
2
+ # Hugging Face Spaces | Paid GPU Config
3
 
4
+ import os
 
 
5
  import re
6
  import json
7
+ import math
8
+ import random
9
+ import time
10
+ import textwrap
11
+ import hashlib
12
+ from collections import Counter, defaultdict
13
+ from typing import Dict, List, Tuple, Optional
14
+
15
+ import numpy as np
16
+ import torch
17
+ import gradio as gr
18
  from transformers import (
19
+ AutoTokenizer,
20
+ AutoModelForCausalLM,
21
+ AutoModelForSequenceClassification,
22
+ AutoModelForSeq2SeqLM,
23
  pipeline,
24
+ set_seed,
25
  )
26
+
27
+ # ============================================================
28
+ # CONFIGURATION
29
+ # ============================================================
30
+
31
+ class Config:
32
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
33
+ TORCH_DTYPE = torch.float16 if torch.cuda.is_available() else torch.float32
34
+
35
+ # Primary rewriting models (ensemble)
36
+ REWRITE_MODEL_1 = "HuggingFaceH4/zephyr-7b-beta"
37
+ REWRITE_MODEL_2 = "microsoft/phi-2"
38
+ REWRITE_MODEL_3 = "mistralai/Mistral-7B-Instruct-v0.3"
39
+
40
+ # Style transfer model
41
+ STYLE_MODEL = "humarin/chatgpt_paraphrase_xl_t5_base"
42
+
43
+ # Perplexity analysis
44
+ PERPLEXITY_MODEL = "EleutherAI/gpt-neo-2.7B"
45
+
46
+ # AI Detection model
47
+ AI_DETECT_MODEL = "roberta-base-openai-detector"
48
+
49
+ # Sentiment & Tone
50
+ SENTIMENT_MODEL = "SamLowe/roberta-base-go_emotions"
51
+
52
+ MAX_INPUT_LENGTH = 4000
53
+ MAX_OUTPUT_LENGTH = 4000
54
+ ENSEMBLE_WEIGHTS = [0.4, 0.35, 0.25] # weights for each rewrite model
55
+
56
+ # Humanization presets
57
+ PRESETS = {
58
+ "🟒 Natural (Light)": {
59
+ "burstiness": 0.2,
60
+ "perplexity_boost": 0.15,
61
+ "style_change": 0.2,
62
+ "idiom_rate": 0.05,
63
+ "sentence_variation": 0.2,
64
+ "emotional_depth": 0.15,
65
+ "vocabulary_richness": 0.1,
66
+ "imperfection_rate": 0.05,
67
+ "personal_touch": 0.1,
68
+ },
69
+ "🟑 Conversational (Medium)": {
70
+ "burstiness": 0.4,
71
+ "perplexity_boost": 0.3,
72
+ "style_change": 0.4,
73
+ "idiom_rate": 0.12,
74
+ "sentence_variation": 0.4,
75
+ "emotional_depth": 0.35,
76
+ "vocabulary_richness": 0.25,
77
+ "imperfection_rate": 0.08,
78
+ "personal_touch": 0.25,
79
+ },
80
+ "πŸ”΄ Fully Human (Aggressive)": {
81
+ "burstiness": 0.6,
82
+ "perplexity_boost": 0.5,
83
+ "style_change": 0.6,
84
+ "idiom_rate": 0.2,
85
+ "sentence_variation": 0.6,
86
+ "emotional_depth": 0.5,
87
+ "vocabulary_richness": 0.4,
88
+ "imperfection_rate": 0.12,
89
+ "personal_touch": 0.4,
90
+ },
91
+ "πŸŽ“ Academic": {
92
+ "burstiness": 0.3,
93
+ "perplexity_boost": 0.35,
94
+ "style_change": 0.5,
95
+ "idiom_rate": 0.03,
96
+ "sentence_variation": 0.5,
97
+ "emotional_depth": 0.1,
98
+ "vocabulary_richness": 0.5,
99
+ "imperfection_rate": 0.02,
100
+ "personal_touch": 0.05,
101
+ },
102
+ "πŸ’Ό Professional": {
103
+ "burstiness": 0.25,
104
+ "perplexity_boost": 0.2,
105
+ "style_change": 0.3,
106
+ "idiom_rate": 0.06,
107
+ "sentence_variation": 0.3,
108
+ "emotional_depth": 0.2,
109
+ "vocabulary_richness": 0.3,
110
+ "imperfection_rate": 0.04,
111
+ "personal_touch": 0.15,
112
+ },
113
+ "✍️ Creative": {
114
+ "burstiness": 0.55,
115
+ "perplexity_boost": 0.5,
116
+ "style_change": 0.7,
117
+ "idiom_rate": 0.18,
118
+ "sentence_variation": 0.7,
119
+ "emotional_depth": 0.6,
120
+ "vocabulary_richness": 0.55,
121
+ "imperfection_rate": 0.1,
122
+ "personal_touch": 0.4,
123
+ },
124
+ }
125
+
126
+
127
+ # ============================================================
128
+ # MODEL LOADER
129
+ # ============================================================
130
+
131
+ class ModelHub:
132
+ """Centralized model loading and caching."""
133
+
134
+ _instance = None
135
+ _loaded_models = {}
136
+ _loaded_pipelines = {}
137
+
138
+ def __new__(cls):
139
+ if cls._instance is None:
140
+ cls._instance = super().__new__(cls)
141
+ return cls._instance
142
+
143
+ @property
144
+ def device(self):
145
+ return Config.DEVICE
146
+
147
+ def load_model_and_tokenizer(self, model_name: str, model_class=None):
148
+ cache_key = model_name
149
+ if cache_key in self._loaded_models:
150
+ return self._loaded_models[cache_key]
151
+
152
+ print(f"[ModelHub] Loading {model_name}...")
153
+ tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
154
+ if model_class is None:
155
+ model_class = AutoModelForCausalLM
156
+
157
+ model = model_class.from_pretrained(
158
+ model_name,
159
+ torch_dtype=Config.TORCH_DTYPE,
160
+ device_map="auto",
161
  trust_remote_code=True,
162
+ low_cpu_mem_usage=True,
163
  )
164
+ model.eval()
165
+ self._loaded_models[cache_key] = (model, tokenizer)
166
+ print(f"[ModelHub] βœ“ {model_name} loaded")
167
+ return model, tokenizer
168
+
169
+ def load_pipeline(self, task: str, model_name: str, **kwargs):
170
+ cache_key = f"{task}_{model_name}"
171
+ if cache_key in self._loaded_pipelines:
172
+ return self._loaded_pipelines[cache_key]
173
+
174
+ print(f"[ModelHub] Loading pipeline: {task} - {model_name}")
175
+ pipe = pipeline(
176
+ task,
177
+ model=model_name,
178
+ device=0 if torch.cuda.is_available() else -1,
179
+ torch_dtype=Config.TORCH_DTYPE,
180
  trust_remote_code=True,
181
+ **kwargs,
182
+ )
183
+ self._loaded_pipelines[cache_key] = pipe
184
+ print(f"[ModelHub] βœ“ Pipeline ready: {cache_key}")
185
+ return pipe
186
+
187
+ def load_all_models(self):
188
+ """Pre-load all models at startup."""
189
+ print("\n" + "=" * 60)
190
+ print("πŸš€ LOADING ALL MODELS β€” Advanced Humanizer Engine")
191
+ print("=" * 60 + "\n")
192
+
193
+ # Rewrite models
194
+ self.load_model_and_tokenizer(Config.REWRITE_MODEL_1)
195
+ self.load_model_and_tokenizer(Config.REWRITE_MODEL_2)
196
+ self.load_model_and_tokenizer(Config.REWRITE_MODEL_3)
197
+
198
+ # Style transfer
199
+ self.load_model_and_tokenizer(
200
+ Config.STYLE_MODEL, model_class=AutoModelForSeq2SeqLM
201
  )
202
+
203
+ # Perplexity
204
+ self.load_model_and_tokenizer(Config.PERPLEXITY_MODEL)
205
+
206
+ # AI Detector
207
+ self.load_model_and_tokenizer(
208
+ Config.AI_DETECT_MODEL,
209
+ model_class=AutoModelForSequenceClassification,
210
+ )
211
+
212
+ # Sentiment
213
+ self.load_model_and_tokenizer(
214
+ Config.SENTIMENT_MODEL,
215
+ model_class=AutoModelForSequenceClassification,
216
+ )
217
+
218
+ print("\n" + "=" * 60)
219
+ print("βœ… ALL MODELS LOADED SUCCESSFULLY")
220
+ print("=" * 60 + "\n")
221
+
222
+
223
+ # ============================================================
224
+ # TEXT ANALYZER
225
+ # ============================================================
226
+
227
+ class TextAnalyzer:
228
+ """Deep text analysis with multiple metrics."""
229
+
230
+ def __init__(self, hub: ModelHub):
231
+ self.hub = hub
232
+
233
  def calculate_perplexity(self, text: str) -> float:
234
+ """Calculate perplexity using a language model."""
235
+ try:
236
+ model, tokenizer = self.hub.load_model_and_tokenizer(
237
+ Config.PERPLEXITY_MODEL
238
+ )
239
+ encodings = tokenizer(text, return_tensors="pt").to(self.hub.device)
240
+ max_length = model.config.max_position_embeddings
241
+ seq_len = encodings.input_ids.size(1)
242
+
243
+ nlls = []
244
+ prev_end_loc = 0
245
+ stride = 512
246
+
247
+ for begin_loc in range(0, seq_len, stride):
248
+ end_loc = min(begin_loc + max_length, seq_len)
249
+ trg_len = end_loc - prev_end_loc
250
+ input_ids = encodings.input_ids[:, begin_loc:end_loc]
251
+ target_ids = input_ids.clone()
252
+ target_ids[:, :-trg_len] = -100
253
+
254
+ with torch.no_grad():
255
+ outputs = model(input_ids, labels=target_ids)
256
+ neg_log_likelihood = outputs.loss
257
+ nlls.append(neg_log_likelihood)
258
+ prev_end_loc = end_loc
259
+ if end_loc == seq_len:
260
+ break
261
+
262
+ ppl = torch.exp(torch.stack(nlls).mean()).item()
263
+ return ppl
264
+ except Exception as e:
265
+ print(f"[Perplexity Error] {e}")
266
+ return 0.0
267
+
268
+ def detect_ai_probability(self, text: str) -> Dict:
269
+ """Detect AI-generated text probability."""
270
+ try:
271
+ model, tokenizer = self.hub.load_model_and_tokenizer(
272
+ Config.AI_DETECT_MODEL,
273
+ model_class=AutoModelForSequenceClassification,
274
+ )
275
+ inputs = tokenizer(
276
+ text, return_tensors="pt", truncation=True, max_length=512
277
+ ).to(self.hub.device)
278
+
279
+ with torch.no_grad():
280
+ outputs = model(**inputs)
281
+ probs = torch.softmax(outputs.logits, dim=-1)[0]
282
+
283
+ # Label 0 = Real (Human), Label 1 = Fake (AI)
284
+ human_score = probs[0].item()
285
+ ai_score = probs[1].item()
286
+
287
+ return {
288
+ "human_probability": round(human_score, 4),
289
+ "ai_probability": round(ai_score, 4),
290
+ "verdict": "Likely Human" if human_score > ai_score else "Likely AI",
291
+ "confidence": round(max(human_score, ai_score), 4),
292
+ }
293
+ except Exception as e:
294
+ print(f"[AI Detection Error] {e}")
295
+ return {
296
+ "human_probability": 0.5,
297
+ "ai_probability": 0.5,
298
+ "verdict": "Unknown",
299
+ "confidence": 0.0,
300
+ }
301
+
302
+ def analyze_sentiment_and_tone(self, text: str) -> Dict:
303
+ """Analyze emotional tone of text."""
304
+ try:
305
+ pipe = self.hub.load_pipeline(
306
+ "text-classification", Config.SENTIMENT_MODEL
307
+ )
308
+ results = pipe(text[:1000], top_k=5)
309
+ emotions = {
310
+ r["label"]: round(r["score"], 4) for r in results
311
+ }
312
+ return {
313
+ "emotions": emotions,
314
+ "dominant_emotion": results[0]["label"] if results else "neutral",
315
+ "dominant_score": (
316
+ round(results[0]["score"], 4) if results else 0.0
317
+ ),
318
+ }
319
+ except Exception as e:
320
+ print(f"[Sentiment Error] {e}")
321
+ return {
322
+ "emotions": {"neutral": 1.0},
323
+ "dominant_emotion": "neutral",
324
+ "dominant_score": 1.0,
325
+ }
326
+
327
+ def calculate_burstiness(self, text: str) -> float:
328
+ """Measure sentence length variation (burstiness)."""
329
+ sentences = re.split(r"[.!?]+", text)
330
+ sentences = [s.strip() for s in sentences if s.strip()]
331
+ if len(sentences) < 2:
332
+ return 0.0
333
+
334
+ lengths = [len(s.split()) for s in sentences]
335
+ mean_len = np.mean(lengths)
336
+ std_len = np.std(lengths)
337
+ cv = std_len / mean_len if mean_len > 0 else 0 # coefficient of variation
338
+ return round(cv, 4)
339
+
340
+ def calculate_vocabulary_richness(self, text: str) -> float:
341
+ """Type-Token Ratio (lexical diversity)."""
342
+ words = re.findall(r"\b\w+\b", text.lower())
343
+ if not words:
344
+ return 0.0
345
+ unique_words = set(words)
346
+ return round(len(unique_words) / len(words), 4)
347
+
348
+ def calculate_readability(self, text: str) -> Dict:
349
+ """Flesch Reading Ease and related metrics."""
350
+ words = re.findall(r"\b\w+\b", text)
351
+ sentences = re.split(r"[.!?]+", text)
352
+ sentences = [s.strip() for s in sentences if s.strip()]
353
+
354
+ if not words or not sentences:
355
+ return {"flesch_ease": 0, "grade_level": 0}
356
+
357
+ num_words = len(words)
358
+ num_sentences = len(sentences)
359
+ num_syllables = sum(self._count_syllables(w) for w in words)
360
+
361
+ if num_sentences == 0 or num_words == 0:
362
+ return {"flesch_ease": 0, "grade_level": 0}
363
+
364
+ flesch_ease = (
365
+ 206.835
366
+ - 1.015 * (num_words / num_sentences)
367
+ - 84.6 * (num_syllables / num_words)
368
+ )
369
+ flesch_ease = max(0, min(100, flesch_ease))
370
+
371
+ grade_level = (
372
+ 0.39 * (num_words / num_sentences)
373
+ + 11.8 * (num_syllables / num_words)
374
+ - 15.59
375
+ )
376
+
377
  return {
378
+ "flesch_ease": round(flesch_ease, 2),
379
+ "grade_level": round(max(0, grade_level), 1),
380
+ "avg_words_per_sentence": round(num_words / num_sentences, 1),
381
+ "avg_syllables_per_word": round(num_syllables / num_words, 2),
382
  }
383
+
384
+ def _count_syllables(self, word: str) -> int:
385
+ word = word.lower()
386
+ if len(word) <= 3:
387
+ return 1
388
+ word = re.sub(r"(?:[^laeiouy]es|ed|[^laeiouy]e)$", "", word)
389
+ word = re.sub(r"^y", "", word)
390
+ syllables = len(re.findall(r"[aeiouy]{1,2}", word))
391
+ return max(1, syllables)
392
+
393
+ def full_analysis(self, text: str) -> Dict:
394
+ """Run complete text analysis."""
395
+ return {
396
+ "perplexity": self.calculate_perplexity(text),
397
+ "ai_detection": self.detect_ai_probability(text),
398
+ "sentiment": self.analyze_sentiment_and_tone(text),
399
+ "burstiness": self.calculate_burstiness(text),
400
+ "vocabulary_richness": self.calculate_vocabulary_richness(text),
401
+ "readability": self.calculate_readability(text),
402
+ "word_count": len(re.findall(r"\b\w+\b", text)),
403
+ "sentence_count": len(
404
+ [s for s in re.split(r"[.!?]+", text) if s.strip()]
405
+ ),
 
 
 
 
 
 
 
 
 
 
 
406
  }
407
+
408
+
409
+ # ============================================================
410
+ # HUMANIZATION ENGINE
411
+ # ============================================================
412
+
413
+ class HumanizationEngine:
414
+ """Multi-strategy text humanization."""
415
+
416
+ def __init__(self, hub: ModelHub):
417
+ self.hub = hub
418
+
419
+ # ---- STRATEGIC PROMPTS ----
420
+
421
+ HUMANIZATION_PROMPTS = {
422
+ "general": """You are an expert human writer. Rewrite the following text to sound completely natural, human, and authentic. Use these techniques:
423
+ 1. Vary sentence lengths dramatically (mix very short with longer ones)
424
+ 2. Use contractions naturally (don't, can't, it's, we're)
425
+ 3. Add subtle personal opinions or hedging ("I think", "in my experience", "it seems")
426
+ 4. Include transitional phrases ("That said", "On the flip side", "Here's the thing")
427
+ 5. Occasionally use colloquialisms or idioms
428
+ 6. Add mild imperfections (slight redundancy, conversational asides)
429
+ 7. Use active voice predominantly
430
+ 8. Vary paragraph structure
431
+ 9. Include rhetorical questions occasionally
432
+ 10. Make it sound like a real person wrote it β€” not a robot
433
+
434
+ TEXT TO HUMANIZE:
435
+ {text}
436
+
437
+ Rewritten version (same meaning, but fully human-sounding):""",
438
+
439
+ "academic": """Rewrite this text in a natural academic style that sounds like a real researcher wrote it:
440
+ - Use sophisticated but natural academic vocabulary
441
+ - Include appropriate hedging ("suggests that", "it appears", "may indicate")
442
+ - Vary sentence structure with complex and simple sentences
443
+ - Use natural transitions between ideas
444
+ - Avoid repetitive patterns typical of AI
445
+ - Sound like an experienced academic, not a textbook
446
+
447
+ TEXT:
448
+ {text}
449
+
450
+ Rewritten:""",
451
+
452
+ "professional": """Rewrite this text in a natural professional/business tone:
453
+ - Sound like an experienced professional writing to colleagues
454
+ - Use natural business language without being overly formal
455
+ - Include practical insights and real-world framing
456
+ - Use contractions appropriately
457
+ - Vary sentence structure naturally
458
+ - Add subtle personal experience markers
459
+
460
+ TEXT:
461
+ {text}
462
+
463
+ Rewritten:""",
464
+
465
+ "creative": """Rewrite this text in a vivid, creative, and engaging style:
466
+ - Use rich, varied vocabulary
467
+ - Employ metaphors and analogies naturally
468
+ - Mix short punchy sentences with flowing longer ones
469
+ - Add personality and voice
470
+ - Include sensory or emotional language
471
+ - Make it captivating and unique
472
+
473
+ TEXT:
474
+ {text}
475
+
476
+ Rewritten:""",
477
+ }
478
+
479
+ def generate_with_model(
480
+ self,
481
+ model_name: str,
482
+ prompt: str,
483
+ max_new_tokens: int = 1024,
484
+ temperature: float = 0.8,
485
+ top_p: float = 0.9,
486
+ top_k: int = 50,
487
+ repetition_penalty: float = 1.2,
488
+ ) -> str:
489
+ """Generate text using a specific model."""
490
+ try:
491
+ model, tokenizer = self.hub.load_model_and_tokenizer(model_name)
492
+ inputs = tokenizer(prompt, return_tensors="pt").to(self.hub.device)
493
+
494
+ input_len = inputs.input_ids.shape[1]
495
+
496
+ with torch.no_grad():
497
+ outputs = model.generate(
498
+ **inputs,
499
+ max_new_tokens=max_new_tokens,
500
+ temperature=temperature,
501
+ top_p=top_p,
502
+ top_k=top_k,
503
+ repetition_penalty=repetition_penalty,
504
+ do_sample=True,
505
+ num_return_sequences=1,
506
+ pad_token_id=tokenizer.eos_token_id,
507
+ eos_token_id=tokenizer.eos_token_id,
508
+ )
509
+
510
+ generated = tokenizer.decode(
511
+ outputs[0][input_len:], skip_special_tokens=True
512
+ )
513
+ return generated.strip()
514
+ except Exception as e:
515
+ print(f"[Generation Error - {model_name}] {e}")
516
+ return ""
517
+
518
+ def generate_with_t5(self, text: str, max_length: int = 512) -> str:
519
+ """Paraphrase using T5-based model."""
520
+ try:
521
+ model, tokenizer = self.hub.load_model_and_tokenizer(
522
+ Config.STYLE_MODEL, model_class=AutoModelForSeq2SeqLM
523
+ )
524
+ input_text = f"paraphrase: {text}"
525
+ inputs = tokenizer(
526
+ input_text,
527
+ return_tensors="pt",
528
+ max_length=512,
529
+ truncation=True,
530
+ padding=True,
531
+ ).to(self.hub.device)
532
+
533
+ with torch.no_grad():
534
+ outputs = model.generate(
535
+ **inputs,
536
+ max_length=max_length,
537
+ num_beams=4,
538
+ temperature=0.9,
539
+ top_p=0.9,
540
+ do_sample=True,
541
+ num_return_sequences=1,
542
+ )
543
+
544
+ return tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
545
+ except Exception as e:
546
+ print(f"[T5 Paraphrase Error] {e}")
547
+ return text
548
+
549
+ def ensemble_rewrite(
550
+ self, text: str, prompt_template: str, settings: Dict
551
+ ) -> List[Tuple[str, float]]:
552
+ """Generate multiple rewrites and score them."""
553
+ prompt = prompt_template.format(text=text)
554
+ results = []
555
+
556
+ models = [
557
+ (Config.REWRITE_MODEL_1, Config.ENSEMBLE_WEIGHTS[0]),
558
+ (Config.REWRITE_MODEL_2, Config.ENSEMBLE_WEIGHTS[1]),
559
+ (Config.REWRITE_MODEL_3, Config.ENSEMBLE_WEIGHTS[2]),
560
+ ]
561
+
562
+ for model_name, base_weight in models:
563
+ rewritten = self.generate_with_model(
564
+ model_name,
565
+ prompt,
566
+ temperature=0.7 + settings.get("burstiness", 0.3),
567
+ top_p=0.85 + settings.get("style_change", 0.15) * 0.15,
568
+ repetition_penalty=1.1 + settings.get("vocabulary_richness", 0.2) * 0.5,
569
+ max_new_tokens=Config.MAX_OUTPUT_LENGTH,
570
+ )
571
+ if rewritten:
572
+ results.append((rewritten, base_weight))
573
+
574
+ return results
575
+
576
+ def apply_linguistic_transforms(self, text: str, settings: Dict) -> str:
577
+ """Apply rule-based linguistic transformations."""
578
+
579
+ # Sentence splitting
580
+ sentences = re.split(r"(?<=[.!?])\s+", text)
581
+ transformed = []
582
+
583
+ for i, sent in enumerate(sentences):
584
+ sent = sent.strip()
585
+ if not sent:
586
+ continue
587
+
588
+ words = sent.split()
589
+
590
+ # Add contractions
591
+ if settings.get("imperfection_rate", 0) > 0.05:
592
+ contractions = {
593
+ "do not": "don't",
594
+ "does not": "doesn't",
595
+ "did not": "didn't",
596
+ "cannot": "can't",
597
+ "could not": "couldn't",
598
+ "would not": "wouldn't",
599
+ "should not": "shouldn't",
600
+ "will not": "won't",
601
+ "is not": "isn't",
602
+ "are not": "aren't",
603
+ "was not": "wasn't",
604
+ "were not": "weren't",
605
+ "has not": "hasn't",
606
+ "have not": "haven't",
607
+ "had not": "hadn't",
608
+ "it is": "it's",
609
+ "that is": "that's",
610
+ "there is": "there's",
611
+ "they are": "they're",
612
+ "we are": "we're",
613
+ "you are": "you're",
614
+ "I am": "I'm",
615
+ "I will": "I'll",
616
+ "I would": "I'd",
617
+ "I had": "I'd",
618
+ "I have": "I've",
619
+ }
620
+ for formal, contracted in contractions.items():
621
+ if re.search(rf"\b{re.escape(formal)}\b", sent, re.IGNORECASE):
622
+ if random.random() < 0.7:
623
+ sent = re.sub(
624
+ rf"\b{re.escape(formal)}\b",
625
+ contracted,
626
+ sent,
627
+ flags=re.IGNORECASE,
628
+ count=1,
629
+ )
630
+
631
+ # Add sentence starters for variety
632
+ if settings.get("personal_touch", 0) > 0.2 and random.random() < settings[
633
+ "personal_touch"
634
+ ]:
635
+ starters = [
636
+ "Honestly, ",
637
+ "Look, ",
638
+ "Here's the thing: ",
639
+ "The way I see it, ",
640
+ "Truth be told, ",
641
+ "From my perspective, ",
642
+ "In my experience, ",
643
+ "I think ",
644
+ "I believe ",
645
+ "It seems to me that ",
646
+ ]
647
+ if not any(
648
+ sent.lower().startswith(s.lower().rstrip()) for s in starters
649
+ ):
650
+ sent = random.choice(starters) + sent[0].lower() + sent[1:]
651
+
652
+ # Add hedging
653
+ if settings.get("emotional_depth", 0) > 0.2 and random.random() < settings[
654
+ "emotional_depth"
655
+ ] * 0.3:
656
+ hedging = [
657
+ "I think",
658
+ "It seems like",
659
+ "From what I can tell",
660
+ "I'd say",
661
+ "It appears that",
662
+ ]
663
+ if not any(h.lower() in sent.lower() for h in hedging):
664
+ sent = f"{random.choice(hedging)} {sent[0].lower()}{sent[1:]}"
665
+
666
+ # Add colloquialisms
667
+ if settings.get("idiom_rate", 0) > 0.05 and random.random() < settings[
668
+ "idiom_rate"
669
+ ]:
670
+ idioms = [
671
+ "at the end of the day",
672
+ "in the grand scheme of things",
673
+ "when it comes down to it",
674
+ "for the most part",
675
+ "by and large",
676
+ "in a nutshell",
677
+ "the bottom line is",
678
+ "all things considered",
679
+ ]
680
+ if random.random() < 0.5 and len(words) > 8:
681
+ insert_pos = random.randint(
682
+ min(3, len(words) - 1), len(words) - 1
683
+ )
684
+ idiom = random.choice(idioms)
685
+ words.insert(insert_pos, f", {idiom},")
686
+ sent = " ".join(words)
687
+
688
+ # Add transitional phrases
689
+ if settings.get("sentence_variation", 0) > 0.3 and random.random() < 0.3:
690
+ transitions = [
691
+ "That said,",
692
+ "On the flip side,",
693
+ "Having said that,",
694
+ "At the same time,",
695
+ "In other words,",
696
+ "Put differently,",
697
+ "To put it simply,",
698
+ ]
699
+ if i > 0 and not any(
700
+ sent.lower().startswith(t.lower().replace(",", ""))
701
+ for t in transitions
702
+ ):
703
+ if random.random() < 0.4:
704
+ sent = f"{random.choice(transitions)} {sent[0].lower()}{sent[1:]}"
705
+
706
+ # Vary sentence length β€” occasionally split long sentences
707
+ if (
708
+ len(words) > 25
709
+ and settings.get("burstiness", 0) > 0.3
710
+ and random.random() < settings["burstiness"] * 0.5
711
+ ):
712
+ split_points = [",", ";", "and", "but", "which", "that"]
713
+ for sp in split_points:
714
+ idx = sent.lower().find(f" {sp} ")
715
+ if idx > len(sent) // 3 and idx < len(sent) * 2 // 3:
716
+ part1 = sent[:idx].strip() + "."
717
+ part2 = sp.capitalize() + sent[idx + len(sp) + 1 :].strip()
718
+ sent = f"{part1} {part2}"
719
+ break
720
+
721
+ # Occasionally merge short sentences
722
+ if (
723
+ i > 0
724
+ and len(words) < 8
725
+ and settings.get("burstiness", 0) > 0.3
726
+ and random.random() < settings["burstiness"] * 0.3
727
+ ):
728
+ if transformed:
729
+ prev = transformed[-1]
730
+ prev_words = prev.split()
731
+ if len(prev_words) > 10:
732
+ merged = prev.rstrip(".") + ", " + sent[0].lower() + sent[1:]
733
+ transformed[-1] = merged
734
+ continue
735
+
736
+ transformed.append(sent)
737
+
738
+ return " ".join(transformed)
739
+
740
+ def humanize(
741
+ self, text: str, preset_name: str = "🟑 Conversational (Medium)", steps: int = 2
742
+ ) -> Dict:
743
+ """Main humanization pipeline."""
744
+ settings = Config.PRESETS.get(
745
+ preset_name, Config.PRESETS["🟑 Conversational (Medium)"]
746
+ )
747
+ prompt_template = self.HUMANIZATION_PROMPTS.get(
748
+ "general", self.HUMANIZATION_PROMPTS["general"]
749
  )
750
+
751
+ # Detect style from preset name
752
+ if "Academic" in preset_name:
753
+ prompt_template = self.HUMANIZATION_PROMPTS["academic"]
754
+ elif "Professional" in preset_name:
755
+ prompt_template = self.HUMANIZATION_PROMPTS["professional"]
756
+ elif "Creative" in preset_name:
757
+ prompt_template = self.HUMANIZATION_PROMPTS["creative"]
758
+
759
+ current_text = text
760
+ pipeline_log = []
761
+
762
+ # Step 1: T5 Paraphrase (style transfer)
763
+ pipeline_log.append("πŸ”„ Step 1: T5 Style Transfer...")
764
+ t5_result = self.generate_with_t5(current_text)
765
+ if t5_result and t5_result != current_text:
766
+ current_text = t5_result
767
+ pipeline_log.append(" βœ“ T5 paraphrase applied")
768
+ else:
769
+ pipeline_log.append(" ⚠ T5 skipped (no change)")
770
+
771
+ # Step 2-N: LLM Ensemble Rewriting
772
+ for step_num in range(steps):
773
+ pipeline_log.append(
774
+ f"πŸ”„ Step {step_num + 2}: LLM Ensemble Rewrite (round {step_num + 1})..."
775
  )
776
+ results = self.ensemble_rewrite(current_text, prompt_template, settings)
777
+
778
+ if results:
779
+ # Weighted selection or blending
780
+ if len(results) == 1:
781
+ current_text = results[0][0]
782
+ else:
783
+ # Score each result and pick the best
784
+ scored = []
785
+ for rewritten, weight in results:
786
+ # Prefer more varied, natural text
787
+ richness = self._calc_vocabulary_richness_simple(rewritten)
788
+ burstiness = self._calc_burstiness_simple(rewritten)
789
+ length_ratio = min(
790
+ 1.0, len(rewritten.split()) / max(1, len(current_text.split()))
791
+ )
792
+ score = (
793
+ richness * 0.3
794
+ + burstiness * 0.3
795
+ + length_ratio * 0.2
796
+ + weight * 0.2
797
+ )
798
+ scored.append((rewritten, score))
799
+
800
+ scored.sort(key=lambda x: x[1], reverse=True)
801
+ current_text = scored[0][0]
802
+ pipeline_log.append(
803
+ f" βœ“ Best rewrite selected (score: {scored[0][1]:.3f})"
804
+ )
805
+ else:
806
+ pipeline_log.append(" ⚠ No results from LLM ensemble")
807
+
808
+ # Step N+1: Linguistic Transforms
809
+ pipeline_log.append("πŸ”„ Applying linguistic transformations...")
810
+ current_text = self.apply_linguistic_transforms(current_text, settings)
811
+ pipeline_log.append(" βœ“ Linguistic transforms applied")
812
+
813
+ # Step N+2: Post-processing cleanup
814
+ current_text = self._cleanup_text(current_text)
815
+ pipeline_log.append(" βœ“ Post-processing complete")
816
+
817
+ return {
818
+ "original": text,
819
+ "humanized": current_text,
820
+ "pipeline_log": "\n".join(pipeline_log),
821
+ "settings_used": settings,
822
+ "preset": preset_name,
823
  }
824
+
825
+ def _calc_vocabulary_richness_simple(self, text: str) -> float:
826
+ words = re.findall(r"\b\w+\b", text.lower())
827
+ if not words:
828
+ return 0.0
829
+ return len(set(words)) / len(words)
830
+
831
+ def _calc_burstiness_simple(self, text: str) -> float:
832
+ sentences = re.split(r"[.!?]+", text)
833
+ sentences = [s.strip() for s in sentences if s.strip()]
834
+ if len(sentences) < 2:
835
+ return 0.0
836
+ lengths = [len(s.split()) for s in sentences]
837
+ mean_len = np.mean(lengths)
838
+ std_len = np.std(lengths)
839
+ return (std_len / mean_len) if mean_len > 0 else 0.0
840
+
841
+ def _cleanup_text(self, text: str) -> str:
842
+ """Clean up generated text."""
843
+ # Remove extra whitespace
844
+ text = re.sub(r"\s+", " ", text).strip()
845
+ # Fix double punctuation
846
+ text = re.sub(r"([.!?])\s*\1+", r"\1", text)
847
+ # Fix spacing around punctuation
848
+ text = re.sub(r"\s+([,;:.!?])", r"\1", text)
849
+ # Capitalize first letter
850
+ if text:
851
+ text = text[0].upper() + text[1:]
852
+ # Remove any obvious artifacts
853
+ text = re.sub(r"^(Rewritten|Rewritten version|Here is the rewritten text)[:\s]*", "", text, flags=re.IGNORECASE)
854
+ text = re.sub(r"\n{3,}", "\n\n", text)
855
+ return text.strip()
856
+
857
+
858
+ # ============================================================
859
+ # SCORING & METRICS
860
+ # ============================================================
861
+
862
+ class HumanizationScorer:
863
+ """Score how 'human' text appears."""
864
+
865
+ @staticmethod
866
+ def compute_human_score(analysis: Dict) -> float:
867
+ """Compute overall human-likeness score (0-100)."""
868
+ scores = []
869
+
870
+ # AI detection component (higher human prob = better)
871
+ ai_det = analysis.get("ai_detection", {})
872
+ human_prob = ai_det.get("human_probability", 0.5)
873
+ scores.append(human_prob * 100)
874
+
875
+ # Burstiness component (moderate burstiness is good)
876
+ burstiness = analysis.get("burstiness", 0)
877
+ burst_score = min(1.0, burstiness / 0.6) * 100 # normalize to 0.6
878
+ scores.append(burst_score)
879
+
880
+ # Vocabulary richness
881
+ vocab = analysis.get("vocabulary_richness", 0)
882
+ vocab_score = min(1.0, vocab / 0.7) * 100
883
+ scores.append(vocab_score)
884
+
885
+ # Perplexity (higher = more human-like typically)
886
+ ppl = analysis.get("perplexity", 0)
887
+ if ppl > 0:
888
+ ppl_score = min(100, (math.log(ppl + 1) / math.log(100)) * 100)
889
+ scores.append(ppl_score)
890
+
891
+ # Readability
892
+ readability = analysis.get("readability", {})
893
+ flesch = readability.get("flesch_ease", 50)
894
+ readability_score = flesch # already 0-100
895
+ scores.append(readability_score)
896
+
897
+ if not scores:
898
+ return 50.0
899
+
900
+ return round(np.mean(scores), 1)
901
+
902
+
903
+ # ============================================================
904
+ # GRADIO UI
905
+ # ============================================================
906
+
907
+ def create_ui():
908
+ """Build the Gradio interface."""
909
+
910
+ hub = ModelHub()
911
+ analyzer = TextAnalyzer(hub)
912
+ engine = HumanizationEngine(hub)
913
+ scorer = HumanizationScorer()
914
+
915
+ # Pre-load models
916
+ hub.load_all_models()
917
+
918
+ def process_text(
919
+ input_text: str,
920
+ preset: str,
921
+ steps: int,
922
+ seed: int,
923
+ ) -> Tuple:
924
+ """Main processing function."""
925
+ if not input_text.strip():
926
+ return (
927
+ "⚠️ Please enter some text to humanize.",
928
+ "",
929
+ "No analysis available.",
930
+ "No metrics available.",
931
+ "πŸ“Š Enter text and click Humanize to see results.",
 
 
 
 
 
 
 
 
 
 
932
  )
933
+
934
+ if len(input_text) > Config.MAX_INPUT_LENGTH:
935
+ return (
936
+ f"⚠️ Text too long. Max {Config.MAX_INPUT_LENGTH} characters.",
937
+ "",
938
+ "",
939
+ "",
940
+ "",
941
+ )
942
+
943
+ set_seed(seed)
944
+ random.seed(seed)
945
+ np.random.seed(seed)
946
+
947
+ start_time = time.time()
948
+
949
+ # Analyze original
950
+ orig_analysis = analyzer.full_analysis(input_text)
951
+ orig_human_score = scorer.compute_human_score(orig_analysis)
952
+
953
+ # Humanize
954
+ result = engine.humanize(input_text, preset, steps)
955
+
956
+ # Analyze humanized
957
+ human_analysis = analyzer.full_analysis(result["humanized"])
958
+ human_score = scorer.compute_human_score(human_analysis)
959
+
960
+ elapsed = time.time() - start_time
961
+
962
+ # Format analysis comparison
963
+ comparison = format_analysis_comparison(orig_analysis, human_analysis, orig_human_score, human_score)
964
+
965
+ # Format detailed metrics
966
+ metrics = format_detailed_metrics(orig_analysis, human_analysis, orig_human_score, human_score)
967
+
968
+ # Progress log
969
+ progress = result["pipeline_log"] + f"\n\n⏱️ Processing time: {elapsed:.1f}s"
970
+
971
+ return (
972
+ result["humanized"],
973
+ comparison,
974
+ metrics,
975
+ progress,
976
+ f"πŸ“Š Human Score: {orig_human_score:.1f} β†’ {human_score:.1f} ({human_score - orig_human_score:+.1f})",
977
+ )
978
+
979
+ def analyze_only(text: str) -> str:
980
+ """Analyze text without humanizing."""
981
+ if not text.strip():
982
+ return "Please enter text to analyze."
983
+ analysis = analyzer.full_analysis(text)
984
+ score = scorer.compute_human_score(analysis)
985
+ return format_detailed_single(analysis, score)
986
+
987
+ def format_analysis_comparison(orig, human, orig_score, human_score):
988
+ """Format comparison for display."""
989
+ lines = []
990
+ lines.append("πŸ“Š TEXT ANALYSIS COMPARISON")
991
+ lines.append("=" * 60)
992
+ lines.append("")
993
+
994
+ # Score
995
+ arrow = "🟒" if human_score > orig_score else "πŸ”΄" if human_score < orig_score else "🟑"
996
+ lines.append(f" Overall Human Score:")
997
+ lines.append(f" Original: {orig_score:.1f}/100")
998
+ lines.append(f" Humanized: {human_score:.1f}/100")
999
+ lines.append(f" Change: {human_score - orig_score:+.1f} {arrow}")
1000
+ lines.append("")
1001
+
1002
+ # AI Detection
1003
+ lines.append(" πŸ€– AI Detection:")
1004
+ lines.append(f" Original: {orig['ai_detection']['verdict']} ({orig['ai_detection']['ai_probability']:.1%} AI)")
1005
+ lines.append(f" Humanized: {human['ai_detection']['verdict']} ({human['ai_detection']['ai_probability']:.1%} AI)")
1006
+ lines.append("")
1007
+
1008
+ # Burstiness
1009
+ lines.append(" πŸ“ˆ Burstiness (sentence variation):")
1010
+ lines.append(f" Original: {orig['burstiness']:.3f}")
1011
+ lines.append(f" Humanized: {human['burstiness']:.3f}")
1012
+ lines.append("")
1013
+
1014
+ # Vocabulary
1015
+ lines.append(" πŸ“š Vocabulary Richness:")
1016
+ lines.append(f" Original: {orig['vocabulary_richness']:.3f}")
1017
+ lines.append(f" Humanized: {human['vocabulary_richness']:.3f}")
1018
+ lines.append("")
1019
+
1020
+ # Perplexity
1021
+ lines.append(" πŸ”’ Perplexity:")
1022
+ lines.append(f" Original: {orig['perplexity']:.2f}")
1023
+ lines.append(f" Humanized: {human['perplexity']:.2f}")
1024
+ lines.append("")
1025
+
1026
+ # Readability
1027
+ lines.append(" πŸ“– Readability:")
1028
+ lines.append(f" Original: Flesch Ease {orig['readability']['flesch_ease']:.1f} (Grade {orig['readability']['grade_level']})")
1029
+ lines.append(f" Humanized: Flesch Ease {human['readability']['flesch_ease']:.1f} (Grade {human['readability']['grade_level']})")
1030
+ lines.append("")
1031
+
1032
+ # Sentiment
1033
+ lines.append(" 😊 Dominant Emotion:")
1034
+ lines.append(f" Original: {orig['sentiment']['dominant_emotion']} ({orig['sentiment']['dominant_score']:.1%})")
1035
+ lines.append(f" Humanized: {human['sentiment']['dominant_emotion']} ({human['sentiment']['dominant_score']:.1%})")
1036
+ lines.append("")
1037
+
1038
+ # Counts
1039
+ lines.append(" πŸ“ Text Stats:")
1040
+ lines.append(f" Original: {orig['word_count']} words, {orig['sentence_count']} sentences")
1041
+ lines.append(f" Humanized: {human['word_count']} words, {human['sentence_count']} sentences")
1042
+
1043
+ return "\n".join(lines)
1044
+
1045
+ def format_detailed_metrics(orig, human, orig_score, human_score):
1046
+ """Format detailed metrics table."""
1047
+ import html
1048
+
1049
+ # Create an HTML table
1050
+ html_content = """
1051
+ <div style="background: #1a1a2e; padding: 20px; border-radius: 10px; font-family: monospace;">
1052
+ <h3 style="color: #e94560; margin-top: 0;">πŸ“Š Detailed Metrics Comparison</h3>
1053
+ <table style="width: 100%; border-collapse: collapse;">
1054
+ <tr style="background: #16213e;">
1055
+ <th style="padding: 10px; text-align: left; color: #e94560; border-bottom: 2px solid #e94560;">Metric</th>
1056
+ <th style="padding: 10px; text-align: center; color: #0f3460; border-bottom: 2px solid #e94560;">Original</th>
1057
+ <th style="padding: 10px; text-align: center; color: #533483; border-bottom: 2px solid #e94560;">Humanized</th>
1058
+ <th style="padding: 10px; text-align: center; color: #e94560; border-bottom: 2px solid #e94560;">Change</th>
1059
+ </tr>
1060
+ """
1061
+
1062
+ metrics_data = [
1063
+ ("🎯 Human Score", f"{orig_score:.1f}", f"{human_score:.1f}", f"{human_score - orig_score:+.1f}"),
1064
+ ("πŸ€– AI Probability", f"{orig['ai_detection']['ai_probability']:.1%}", f"{human['ai_detection']['ai_probability']:.1%}", ""),
1065
+ ("πŸ“ˆ Burstiness", f"{orig['burstiness']:.3f}", f"{human['burstiness']:.3f}", ""),
1066
+ ("πŸ“š Vocab Richness", f"{orig['vocabulary_richness']:.3f}", f"{human['vocabulary_richness']:.3f}", ""),
1067
+ ("πŸ”’ Perplexity", f"{orig['perplexity']:.2f}", f"{human['perplexity']:.2f}", ""),
1068
+ ("πŸ“– Flesch Ease", f"{orig['readability']['flesch_ease']:.1f}", f"{human['readability']['flesch_ease']:.1f}", ""),
1069
+ ("πŸ“ Word Count", f"{orig['word_count']}", f"{human['word_count']}", ""),
1070
+ ("πŸ“ Sentence Count", f"{orig['sentence_count']}", f"{human['sentence_count']}", ""),
1071
+ ]
1072
+
1073
+ for metric, orig_val, human_val, change in metrics_data:
1074
+ html_content += f"""
1075
+ <tr>
1076
+ <td style="padding: 8px; color: #eee; border-bottom: 1px solid #333;">{metric}</td>
1077
+ <td style="padding: 8px; text-align: center; color: #aaa; border-bottom: 1px solid #333;">{orig_val}</td>
1078
+ <td style="padding: 8px; text-align: center; color: #4ecca3; border-bottom: 1px solid #333;">{human_val}</td>
1079
+ <td style="padding: 8px; text-align: center; color: {'#4ecca3' if change.startswith('+') else '#e94560' if change.startswith('-') else '#aaa'}; border-bottom: 1px solid #333;">{change}</td>
1080
+ </tr>
1081
+ """
1082
+
1083
+ html_content += """
1084
+ </table>
1085
+ </div>
1086
+ """
1087
+ return html_content
1088
+
1089
+ def format_detailed_single(analysis, score):
1090
+ """Format single text analysis."""
1091
+ lines = []
1092
+ lines.append("πŸ“Š TEXT ANALYSIS REPORT")
1093
+ lines.append("=" * 50)
1094
+ lines.append(f"\n 🎯 Overall Human Score: {score:.1f}/100")
1095
+ lines.append(f"\n πŸ€– AI Detection:")
1096
+ lines.append(f" Verdict: {analysis['ai_detection']['verdict']}")
1097
+ lines.append(f" Human: {analysis['ai_detection']['human_probability']:.1%}")
1098
+ lines.append(f" AI: {analysis['ai_detection']['ai_probability']:.1%}")
1099
+ lines.append(f"\n πŸ“ˆ Burstiness: {analysis['burstiness']:.3f}")
1100
+ lines.append(f" πŸ“š Vocabulary Richness: {analysis['vocabulary_richness']:.3f}")
1101
+ lines.append(f" πŸ”’ Perplexity: {analysis['perplexity']:.2f}")
1102
+ lines.append(f"\n πŸ“– Readability:")
1103
+ lines.append(f" Flesch Ease: {analysis['readability']['flesch_ease']:.1f}")
1104
+ lines.append(f" Grade Level: {analysis['readability']['grade_level']}")
1105
+ lines.append(f"\n 😊 Emotions:")
1106
+ for emo, sc in analysis['sentiment']['emotions'].items():
1107
+ lines.append(f" {emo}: {sc:.1%}")
1108
+ lines.append(f"\n πŸ“ Words: {analysis['word_count']} | Sentences: {analysis['sentence_count']}")
1109
+ return "\n".join(lines)
1110
+
1111
+ # Build UI
1112
+ custom_css = """
1113
+ .gradio-container { max-width: 1400px !important; }
1114
+ textarea { font-size: 15px !important; }
1115
+ #output-text { font-size: 16px !important; line-height: 1.7 !important; }
1116
+ .metric-box { background: #1a1a2e; padding: 15px; border-radius: 8px; }
1117
+ """
1118
+
1119
+ with gr.Blocks(
1120
+ title="🧬 Advanced AI Text Humanizer",
1121
+ css=custom_css,
1122
+ theme=gr.themes.Soft(
1123
+ primary_hue="purple",
1124
+ secondary_hue="blue",
1125
+ neutral_hue="slate",
1126
+ ),
1127
+ ) as demo:
1128
+
1129
+ gr.Markdown(
1130
+ """
1131
+ # 🧬 Advanced AI Text Humanizer
1132
+ ### Multi-Model Ensemble | Deep Linguistic Analysis | Real-Time Metrics
1133
+
1134
+ This tool uses **4+ AI models** working together to transform AI-generated text into natural, human-sounding writing.
1135
+ Powered by **Zephyr-7B**, **Phi-2**, **Mistral-7B**, **T5 Paraphrase**, **GPT-Neo**, **RoBERTa**, and **GoEmotions** models.
1136
+
1137
+ > πŸ’‘ **Tip**: Start with "🟑 Conversational (Medium)" preset and adjust based on results.
1138
+ """
1139
+ )
1140
+
1141
+ with gr.Row():
1142
+ with gr.Column(scale=1):
1143
+ gr.Markdown("### πŸ“ Input")
1144
+ input_text = gr.Textbox(
1145
+ label="Paste AI-generated text here",
1146
+ placeholder="Enter the text you want to humanize...",
1147
+ lines=12,
1148
+ max_lines=30,
1149
+ value="",
1150
+ )
1151
+
1152
+ preset = gr.Dropdown(
1153
+ choices=list(Config.PRESETS.keys()),
1154
+ value="🟑 Conversational (Medium)",
1155
+ label="🎭 Humanization Preset",
1156
+ interactive=True,
1157
  )
1158
+
1159
+ steps = gr.Slider(
1160
+ minimum=1,
1161
+ maximum=4,
1162
+ value=2,
1163
+ step=1,
1164
+ label="πŸ”„ Rewrite Steps (more = more thorough but slower)",
1165
+ interactive=True,
1166
  )
1167
+
1168
+ seed = gr.Slider(
1169
+ minimum=0,
1170
+ maximum=9999,
1171
+ value=42,
1172
+ step=1,
1173
+ label="🎲 Random Seed (change for different outputs)",
1174
+ interactive=True,
1175
  )
1176
+
1177
+ with gr.Row():
1178
+ humanize_btn = gr.Button(
1179
+ "πŸš€ Humanize Text",
1180
+ variant="primary",
1181
+ size="lg",
1182
+ )
1183
+ analyze_btn = gr.Button(
1184
+ "πŸ” Analyze Only",
1185
+ variant="secondary",
1186
+ size="lg",
1187
+ )
1188
+
1189
+ with gr.Column(scale=1):
1190
+ gr.Markdown("### ✨ Humanized Output")
1191
+ output_text = gr.Textbox(
1192
+ label="Humanized Text",
1193
+ lines=12,
1194
+ max_lines=30,
1195
+ interactive=False,
1196
+ elem_id="output-text",
1197
  )
1198
+
1199
+ score_display = gr.Textbox(
1200
+ label="πŸ“Š Quick Score",
1201
+ interactive=False,
1202
+ lines=1,
1203
+ )
1204
+
1205
+ with gr.Tabs():
1206
+ with gr.Tab("πŸ“Š Analysis"):
1207
+ analysis_output = gr.Textbox(
1208
+ label="Comparison Analysis",
1209
+ lines=15,
1210
+ interactive=False,
1211
+ )
1212
+ with gr.Tab("πŸ“ˆ Metrics"):
1213
+ metrics_output = gr.HTML(label="Detailed Metrics")
1214
+ with gr.Tab("βš™οΈ Pipeline"):
1215
+ pipeline_output = gr.Textbox(
1216
+ label="Processing Pipeline Log",
1217
+ lines=15,
1218
+ interactive=False,
1219
+ )
1220
+
1221
+ gr.Markdown(
1222
+ """
1223
+ ---
1224
+ ### πŸ—οΈ Architecture
1225
+ - **T5 Paraphraser** β†’ Initial style transfer and structural changes
1226
+ - **LLM Ensemble** (Zephyr-7B + Phi-2 + Mistral-7B) β†’ Weighted multi-model rewriting
1227
+ - **Linguistic Engine** β†’ Contractions, idioms, hedging, sentence variation, transitions
1228
+ - **Analysis Suite** β†’ Perplexity, burstiness, vocabulary, AI detection, sentiment, readability
1229
+
1230
+ ### πŸ“‹ Presets Explained
1231
+ - **🟒 Natural (Light)**: Subtle changes, maintains original structure
1232
+ - **🟑 Conversational (Medium)**: Balanced human-like rewriting
1233
+ - **πŸ”΄ Fully Human (Aggressive)**: Maximum humanization with significant restructuring
1234
+ - **πŸŽ“ Academic**: Scholarly tone with natural academic phrasing
1235
+ - **πŸ’Ό Professional**: Business-appropriate natural writing
1236
+ - **✍️ Creative**: Expressive, vivid, personality-rich output
1237
+ """
1238
+ )
1239
+
1240
+ # Event handlers
1241
+ humanize_btn.click(
1242
+ fn=process_text,
1243
+ inputs=[input_text, preset, steps, seed],
1244
+ outputs=[
1245
+ output_text,
1246
+ analysis_output,
1247
+ metrics_output,
1248
+ pipeline_output,
1249
+ score_display,
1250
+ ],
1251
+ )
1252
+
1253
+ analyze_btn.click(
1254
+ fn=analyze_only,
1255
+ inputs=[input_text],
1256
+ outputs=[analysis_output],
1257
+ )
1258
+
1259
+ return demo
1260
+
1261
+
1262
+ # ============================================================
1263
+ # MAIN ENTRY
1264
+ # ============================================================
1265
 
1266
  if __name__ == "__main__":
1267
+ demo = create_ui()
1268
+ demo.queue(
1269
+ max_size=20,
1270
+ default_concurrency_limit=4,
1271
+ ).launch(
1272
+ server_name="0.0.0.0",
1273
+ server_port=7860,
1274
+ share=False,
1275
+ )