Spaces:
Sleeping
Sleeping
| 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) |