File size: 9,654 Bytes
43461c6
 
 
 
 
 
 
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
7d09fd7
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
7d09fd7
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
 
7d09fd7
 
 
 
 
 
 
 
 
 
43461c6
 
7d09fd7
 
 
 
 
 
 
 
 
43461c6
7d09fd7
 
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
 
 
 
 
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
 
 
7d09fd7
 
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
43461c6
7d09fd7
 
43461c6
 
 
 
 
 
 
 
 
7d09fd7
43461c6
7d09fd7
 
 
 
43461c6
620b5a2
7d09fd7
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43461c6
7d09fd7
 
 
 
 
 
 
 
 
 
 
43461c6
620b5a2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import gradio as gr
import openai
import os
import re


AI_WORDS = [
    "delve", "tapestry", "multifaceted", "intricacies", "intricate",
    "pivotal", "testament", "landscape", "vibrant", "meticulous",
    "meticulously", "bolstered", "garner", "garnered", "enduring",
    "underscores", "underscore", "interplay", "nuanced", "comprehensive",
    "groundbreaking", "renowned", "showcasing", "fostering", "enhancing",
    "nestled", "leveraging", "leverage", "spearheaded", "pioneering",
    "noteworthy", "moreover", "furthermore", "additionally", "crucially",
    "notably", "aligns", "align with", "resonates", "resonate with",
    "encompassing", "epitomizes", "epitomize", "embodies", "embody",
    "indelible", "bustling", "burgeoning", "commendable", "endeavors",
    "endeavor", "invaluable", "profound", "profoundly", "paramount",
    "exemplifies", "exemplify", "compelling", "navigating", "navigate",
    "beacon", "realm", "harnessing", "harness", "forge", "forging",
    "poised", "bespoke", "reimagine", "reimagining", "elevate",
    "elevating", "cultivating", "unraveling", "demystifying",
    "ever-evolving",
]


AI_PHRASES = [
    "serves as a testament",
    "stands as a testament",
    "is a testament to",
    "played a pivotal role",
    "plays a pivotal role",
    "a key role in",
    "a vital role in",
    "a significant role in",
    "a crucial role in",
    "in the heart of",
    "the evolving landscape",
    "setting the stage for",
    "marks a significant",
    "reflects broader",
    "broader trends",
    "shaping the future",
    "deeply rooted in",
    "indelible mark",
    "rich tapestry",
    "diverse array",
    "a wide array",
    "wide range of",
    "has garnered.*?attention",
    "has garnered.*?recognition",
    "continues to inspire",
    "leaving an indelible",
    "underscoring its",
    "highlighting its",
    "showcasing its",
    "emphasizing its",
    "contributing to the",
    "focal point",
    "key turning point",
    "the annals of",
]


COPULA_REPLACEMENTS = {
    "serves as a": "is a",
    "stands as a": "is a",
    "functions as a": "is a",
    "operates as a": "is a",
    "acts as a": "is a",
    "marks the": "is the",
    "represents a": "is a",
    "constitutes a": "is a",
    "offers a": "has a",
    "features a": "has a",
    "boasts a": "has a",
    "provides a": "has a",
}


def post_process(text):
    """Clean up AI-typical patterns from generated text."""
    for ai_form, simple_form in COPULA_REPLACEMENTS.items():
        text = re.sub(re.escape(ai_form), simple_form, text, flags=re.IGNORECASE)
    for word in AI_WORDS:
        pattern = r"\b" + re.escape(word) + r"\b"
        text = re.sub(pattern, "", text, flags=re.IGNORECASE)
    for phrase in AI_PHRASES:
        text = re.sub(phrase, "", text, flags=re.IGNORECASE)
    paragraphs = text.split("\n")
    cleaned = []
    for para in paragraphs:
        dash_count = para.count("\u2014")
        if dash_count > 1:
            new_para = ""
            for ch in para:
                if ch == "\u2014":
                    new_para += ","
                else:
                    new_para += ch
            para = new_para
        cleaned.append(para)
    text = "\n".join(cleaned)
    new_lines = text.split("\n")
    final_lines = []
    for line in new_lines:
        stripped = line.strip()
        if stripped.startswith("#"):
            parts = stripped.split(" ", 1)
            if len(parts) == 2:
                hashes = parts[0]
                title = parts[1]
                words = title.split()
                if len(words) > 1:
                    new_words = [words[0]]
                    for w in words[1:]:
                        if len(w) > 0 and w[0].isupper() and w not in ["I", "II", "III", "IV", "V"]:
                            new_words.append(w[0].lower() + w[1:])
                        else:
                            new_words.append(w)
                    line = hashes + " " + " ".join(new_words)
        final_lines.append(line)
    text = "\n".join(final_lines)
    text = re.sub(r"  +", " ", text)
    text = re.sub(r" ,", ",", text)
    text = re.sub(r" \.", ".", text)
    text = re.sub(r"\n{3,}", "\n\n", text)
    return text.strip()


SYSTEM_PROMPT = """You are a skilled human writer producing articles for online publications.
Your writing must pass as genuinely human-written and avoid every known sign of AI-generated text.

STRICT RULES:

VOCABULARY BANS - never use these words:
delve, tapestry, multifaceted, intricacies, intricate, pivotal, testament,
landscape (metaphorical), vibrant, meticulous, bolstered, garner, enduring,
underscore, interplay, nuanced, comprehensive, groundbreaking, renowned,
showcasing, fostering, enhancing, nestled, leveraging, spearheaded, pioneering,
noteworthy, moreover, furthermore, additionally, crucially, notably, align with,
resonate with, encompassing, epitomize, embody, indelible, bustling, burgeoning,
commendable, endeavor, invaluable, profound, paramount, exemplify, compelling,
navigate (metaphorical), beacon, realm, harnessing, forge/forging (metaphorical),
poised, bespoke, reimagine, elevate, cultivating, unraveling, demystifying, ever-evolving.

PHRASE BANS - never write:
"serves/stands as a testament", "played/plays a pivotal role", "in the heart of",
"evolving landscape", "setting the stage", "deeply rooted", "rich tapestry",
"diverse array", "wide range of", "focal point", "key turning point",
"shaping the future", "the annals of", "broader trends", "continues to inspire",
"leaving an indelible mark".

STRUCTURAL BANS:
- Never end articles with a Challenges and Future Prospects section.
- Never use the pattern Despite its [positive], [subject] faces challenges...
- Never include vague attributions like scholars argue, experts note, widely regarded as.
- Never use not just X, but also Y parallelisms.
- Avoid the rule of three (do not list exactly three adjectives in a row for emphasis).

STYLE RULES:
- Use is and are naturally. Do not replace them with serves as, stands as, marks the.
- Use em dashes sparingly, maximum one per paragraph.
- Do not use Title Case in section headings. Write headings in sentence case.
- Do not overuse bold text.
- Do not use inline-header lists (bold term colon description pattern). Write in flowing prose.
- Use contractions occasionally to sound natural.
- Vary sentence length. Mix short punchy sentences with longer ones.
- Repeat key nouns instead of using elegant variation.
- Occasionally start a sentence with But, And, or So.

TONE:
- Write in a calm, factual, slightly informal tone like a competent journalist.
- Do not inflate the importance of the subject. Report facts plainly.
- Do not add superficial analysis phrases.
- If you do not know something specific, say so briefly.

FORMAT:
- Output the article in clean Markdown.
- Use sentence-case headings (## Early life, not ## Early Life).
- Do not include emoji anywhere.
- Do not produce numbered lists with bold inline headers.
- Keep paragraphs to 3-5 sentences each.

Your goal is to write like a competent human journalist who happens to be efficient
and clear, not to impress with vocabulary or rhetorical flourishes."""


def generate_article(brief, api_key, model="gpt-4o-mini"):
    """Call the OpenAI-compatible API, then post-process the output."""
    if not api_key.strip():
        return "ERROR: Please provide your OpenAI API key."
    if not brief.strip():
        return "ERROR: Please provide an article brief."
    client = openai.OpenAI(api_key=api_key.strip())
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": "Write an article based on this brief:\n\n" + brief},
            ],
            temperature=0.85,
            max_tokens=4096,
        )
        raw_text = response.choices[0].message.content
    except Exception as e:
        return "API Error: " + str(e)
    cleaned = post_process(raw_text)
    return cleaned


DESCRIPTION = """# Human Article Writer

This tool generates articles that avoid all known AI-writing patterns identified by
the Wikipedia community (Wikipedia:Signs of AI writing).

**How it works:**
1. Paste your article brief in English.
2. Enter your OpenAI API key (it is never stored).
3. Click Generate. The system uses a strict anti-AI-pattern prompt, then runs a
   post-processor to catch any remaining AI tells.
"""


with gr.Blocks(title="Human Article Writer") as demo:
    gr.Markdown(DESCRIPTION)
    with gr.Row():
        with gr.Column(scale=1):
            api_key = gr.Textbox(
                label="OpenAI API Key",
                placeholder="sk-...",
                type="password",
                lines=1,
            )
            model = gr.Dropdown(
                label="Model",
                choices=["gpt-4o-mini", "gpt-4o", "gpt-4.1-mini", "gpt-4.1-nano"],
                value="gpt-4o-mini",
            )
            brief = gr.Textbox(
                label="Article brief (in English)",
                placeholder="Write a 600-word article about the history of the London Underground...",
                lines=10,
            )
            generate_btn = gr.Button("Generate article", variant="primary")
        with gr.Column(scale=1):
            output = gr.Textbox(
                label="Generated article (post-processed)",
                lines=30,
            )
    generate_btn.click(
        fn=generate_article,
        inputs=[brief, api_key, model],
        outputs=output,
    )

demo.launch(share=False)