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)