[07]
AI ARTICLE GENERATOR
█ TIER A · YEAR 2026 · STATUS: LIVE · LANGUAGES: TYPESCRIPT
Prompt as product: hard content thresholds, grounding and DOCX export
[FIG. 1] MISSION
I design the prompt as a product: one parametrized contract instead of a framework. That is how my long-form SEO article generator works, turning a brief (topic, keywords, audience, length in pages) into a finished, substantive Polish text — hand-writing such texts is slow and stylistically inconsistent, while a prompt can standardize the work. I translate the business requirement into a hard machine threshold: length in pages becomes a word floor (~550 per page) and a minimum count of H2/H3 sections, while the model copies the tone of a pasted reference text. Grounding is conditional: the content is anchored in facts from the provided URLs, and the source list comes back parsed from the response metadata. I combine two model loops in one product — generation and editing by conversation: edits go through function calling (update_article_content returns full new content plus a change summary), not from-scratch regeneration or manual rewriting. I close the result with an export in the format the recipient expects: DOCX built through custom formatting metatags, tuned for SGE/AI Search. React + Vite, deployed on Vercel — a tool actually used for content.
[FIG. 2] ARCHITECTURE
hover a block to see its description
[FIG. 3] CHALLENGES
[+][CH-01]
An LLM summarizes by nature: asked for 5 pages it delivers 2 and considers it a favor. Asking it to 'write long' didn't work — what worked was converting pages into a hard word floor (pages × ~550), a forced minimum of H2/H3 sections, and an explicit no-brevity rule written into the prompt. The lesson: you don't negotiate length with a model using adjectives, but with numbers you can count in the output.
[+][CH-02]
Editing by 'regenerate with this fix' silently destroyed everything that was already good — the new version could drop sections nobody asked about. I switched to function calling: the model invokes update_article_content and must return the full new content plus a change summary, so the contract is deterministic and the diff is immediately visible. Edits stopped being a lottery — the chat edits the article instead of rewriting it.
[+][CH-03]
Over 2–3 thousand words the model drifts: it starts in the reference's tone and ends in its own. I pinned it from both sides — the prompt first makes it analyze the pasted reference (tone, sentence rhythm, formatting), and a set of 'immutable' rules (don't change the title, don't drop keywords, keep the structure) is attached to every call, including the edit loop. Without repeating the rules on each iteration, a tweak could quietly void what the brief had established.
[+][CH-04]
The app had to run embedded in an iframe on a foreign site — and a security-headers lesson was waiting there. Vercel's default X-Frame-Options: SAMEORIGIN blocked the frame, so the first fix forced X-Frame-Options: ALLOWALL — a counterproductive workaround, because ALLOWALL isn't a valid value for that header and some browsers degrade it to DENY. An hour later I removed the legacy header entirely and based embedding on the modern standard: Content-Security-Policy: frame-ancestors. Embed mode gained a postMessage API (set content, trigger generation, fetch the result), and an ?embed=true parameter hides the sidebar — the host page gets the generator as a component, not a whole app. The takeaway, written down for the future: a wrong workaround is better cut out than layered over.
[FIG. 4] AI LAYER
Gemini 2.5 Flash (@google/genai, temperature 0.8, topP 0.95, thinkingBudget 4000) works in two loops: the first generates the article from a parameterized prompt — with conditional Google Search grounding when source links are provided, and a source list parsed from groundingMetadata below the article — while the second edits the content via function calling with a deterministic contract: the full new text plus a change summary. Without that contract every revision would be a regeneration lottery; with it, editing is as predictable as a commit. The model choice itself got a production baptism: preview models hit the free-tier quota on the deployed key (429 RESOURCE_EXHAUSTED, quota 0) and generation died — diagnosing the error code instead of guessing, then switching to gemini-2.5-flash, restored the service in a two-line diff.
[FIG. 2A] PROMPT ANATOMY
services/geminiService.ts → PROMPT (1×, parametrized)
Write an article of MINIMUM 2,750 words (5 pages × 550 words/page). Do not summarize, do not shorten sections.
Analyze the reference text below and copy its tone, sentence rhythm and formatting — the article must read like the same newsroom.
IMMUTABLE RULES (hold on every edit iteration): keep the title, keep the keywords, keep the H2/H3 structure.
Format: # H1 · ## H2 · ### H3 · [CENTER] centered heading — tags mapped 1:1 onto the DOCX structure (Roboto).
- LLMs summarize by default — a hard word floor enforces length
- brand-tone mimicry instead of generic „AI text”
- rules held on EVERY call, including the edit loop
- heading-faithful export — metatags instead of HTML parsing
[FIG. 5] GALLERY



