Essay Generator – Single‑file (Offline + API Mode)
Inputs
Subject / Topic
Tip: Be specific – add context or angle for better results.
Language
English
Français
Essay Type
Argumentative
Expository
Analytical
Persuasive
Compare & Contrast
Narrative
Stance
Pro / In favor
Against
Neutral / Balanced
Structure
5‑paragraph
Problem → Causes → Solutions
Compare → Contrast → Synthesis
Thematic (3 key themes)
Tone
Formal academic
Neutral
Creative
Concise
Citations
None (placeholders only)
APA placeholders
MLA placeholders
(Optional) Thesis override
(Optional) Keywords to include (comma‑separated)
Mode
Use AI API mode (optional)
Unchecked = Offline template mode (no external calls). Checked = call your API (OpenAI‑style) in the browser. Mind your API key
Keys are saved to localStorage in your browser. Prefer a server relay (e.g., n8n webhook) for security. Some providers block browser CORS.
Generate essay
Generate outline only
Clear
Output
Copy
Download .doc
Download .md
Print
0 words
Tip: You can edit the essay directly in the box above before exporting.
`;
download('essay.doc', 'application/msword', docHtml);
}
function downloadMd(text){ download('essay.md', 'text/markdown', text); }
// --- Offline Template Generator ---
const L = {
en: {
introHooks:[
'Few issues spark as much debate as',
'In recent years, the conversation around',
'Beneath the headlines, a deeper question lingers about',
'From classrooms to boardrooms, people are grappling with',
'At first glance, it may seem simple, yet the reality of'
],
transitions:['Moreover','Furthermore','However','Nevertheless','In addition','By contrast','Consequently','Therefore','Notably','Crucially'],
counters:['Skeptics argue that','A common objection is that','Some contend that','Critics warn that','It is sometimes claimed that'],
rebuttals:['This concern matters, yet','While important, this claim overlooks that','Even so, evidence suggests','Still, a closer look reveals','Granted—but it remains true that'],
conclude:['Ultimately','In conclusion','Taken together','Stepping back','Looking ahead'],
thesisLead:['This essay argues that','I contend that','This discussion maintains that','The central claim here is that','The evidence points to this thesis:'],
title:(subject)=>`An Essay on ${subject.charAt(0).toUpperCase()+subject.slice(1)}`,
toc:'Outline',
placeholders:{apa:'(Author, YEAR)', mla:'(Author page)'}
},
fr: {
introHooks:[
'Peu de questions suscitent autant de débats que',
'Ces dernières années, le débat autour de',
'Au‑delà des gros titres, une question demeure au sujet de',
'Des salles de classe aux entreprises, on s’interroge sur',
'À première vue le sujet paraît simple, mais la réalité de'
],
transitions:['De plus','Par ailleurs','Cependant','Néanmoins','En outre','À l’inverse','Par conséquent','Donc','Notamment','Surtout'],
counters:['Les sceptiques soutiennent que','Une objection fréquente veut que','Certains avancent que','Des critiques avertissent que','On affirme parfois que'],
rebuttals:['Cette préoccupation est légitime, mais','Bien que pertinent, cet argument omet que','Pourtant, les données indiquent','Toutefois, un examen attentif révèle','Soit—mais il n’en demeure pas moins que'],
conclude:['En définitive','En conclusion','Dans l’ensemble','Avec du recul','Pour l’avenir'],
thesisLead:['Cet essai soutient que','Nous défendons l’idée selon laquelle','La thèse centrale est que','L’argument principal est que','Les éléments conduisent à la thèse suivante :'],
title:(subject)=>`Essai sur ${subject.charAt(0).toUpperCase()+subject.slice(1)}`,
toc:'Plan',
placeholders:{apa:'(Auteur, ANNÉE)', mla:'(Auteur page)'}
}
};
function pick(arr){ return arr[Math.floor(Math.random()*arr.length)] }
function buildThesis(subject, stance, lang){
const lead = pick(L[lang].thesisLead);
const verb = (stance==='pro') ? (lang==='en'?'that we should':'qu’il faut')
: (stance==='con') ? (lang==='en'?'that we should not':'qu’il ne faut pas')
: (lang==='en'?'that a balanced approach is needed for':'qu’une approche équilibrée s’impose face à');
return `${lead} ${verb} ${subject}.`;
}
function toKeywords(str){
return str ? str.split(',').map(s=>s.trim()).filter(Boolean) : [];
}
function sentenceCap(s){ return s.charAt(0).toUpperCase()+s.slice(1); }
function makeParagraph(sentences){ return sentences.join(' ') }
function offlineOutline(p){
const lang = p.language;
const tr = L[lang];
const keywords = toKeywords(p.keywords);
const thesis = p.thesis || buildThesis(p.subject, p.stance, lang);
const outline = [];
// Intro
outline.push({
title: (lang==='en'?'Introduction':'Introduction'),
bullets: [
`${pick(tr.introHooks)} ${p.subject}.`,
thesis,
(lang==='en'?'Roadmap of the arguments.':'Annonce du plan.')
]
});
// Body sections depend on structure
if(p.structure==='five' || p.structure==='thematic'){
outline.push({title: lang==='en'?'Background & Definitions':'Contexte et définitions', bullets:[
(lang==='en'?'Key terms and scope':'Termes clés et périmètre'),
(lang==='en'?'Historical or recent context':'Contexte historique ou récent'),
(lang==='en'?'Why the topic matters now':'Pourquoi le sujet importe aujourd’hui')
]});
outline.push({title: lang==='en'?'Primary Argument':'Argument principal', bullets:[
(lang==='en'?'Claim + brief evidence':'Assertion + brèves preuves'),
(lang==='en'?'Example or data point':'Exemple ou donnée'),
(lang==='en'?'Implications':'Implications')
]});
outline.push({title: lang==='en'?'Counterargument & Rebuttal':'Objection & Réfutation', bullets:[
`${pick(tr.counters)} …`,
`${pick(tr.rebuttals)} …`,
(lang==='en'?'Synthesis back to thesis':'Lien de synthèse vers la thèse')
]});
} else if(p.structure==='problem'){
outline.push({title: lang==='en'?'Problem':'Problème', bullets:[
(lang==='en'?'What exactly is the problem?':'Quel est précisément le problème ?'),
(lang==='en'?'Who is affected?':'Qui est affecté ?'),
(lang==='en'?'Magnitude & urgency':'Ampleur et urgence')
]});
outline.push({title: lang==='en'?'Causes':'Causes', bullets:[
(lang==='en'?'Root causes + examples':'Causes profondes + exemples'),
(lang==='en'?'Systemic factors':'Facteurs systémiques')
]});
outline.push({title: lang==='en'?'Solutions':'Solutions', bullets:[
(lang==='en'?'Policy + practice suggestions':'Pistes politiques et pratiques'),
(lang==='en'?'Trade‑offs & risks':'Arbitrages et risques')
]});
} else if(p.structure==='compare'){
outline.push({title: lang==='en'?'Point of Comparison':'Point de comparaison', bullets:[
(lang==='en'?'Criteria for comparison':'Critères de comparaison'),
(lang==='en'?'Context of both cases':'Contexte des deux cas')
]});
outline.push({title: lang==='en'?'Similarities':'Similarités', bullets:[
(lang==='en'?'Two to three strong similarities':'Deux à trois similarités fortes')
]});
outline.push({title: lang==='en'?'Differences':'Différences', bullets:[
(lang==='en'?'Two to three critical differences':'Deux à trois différences clés')
]});
}
// Conclusion
outline.push({title: lang==='en'?'Conclusion':'Conclusion', bullets:[
`${pick(tr.conclude)} …`,
(lang==='en'?'Restate thesis in new words':'Reformuler la thèse'),
(lang==='en'?'Implications or call‑to‑action':'Portée ou appel à l’action')
]});
if(keywords.length){
outline.push({title: lang==='en'?'Keywords':'Mots‑clés', bullets: keywords});
}
return { outline, thesis };
}
function offlineEssay(p){
const { outline, thesis } = offlineOutline(p);
const tr = L[p.language];
const cite = tr.placeholders[p.citations] || '';
const T = (k)=>{
const map = {
intro: p.language==='en'?'Introduction':'Introduction',
body: p.language==='en'?'Body':'Développement',
conclusion: p.language==='en'?'Conclusion':'Conclusion'
};
return map[k];
}
const introHook = pick(tr.introHooks);
const trans = pick(tr.transitions);
// Build paragraphs roughly matching target words
const target = Math.max(150, p.words|0);
const paraCount = Math.min(6, Math.max(3, Math.round(target/170)));
const paragraphs = [];
// Intro
paragraphs.push(makeParagraph([
sentenceCap(`${introHook} ${p.subject}.`),
thesis,
(p.language==='en'?
`${trans}, this essay outlines context, key arguments, counterpoints, and a reasoned conclusion.`:
`${trans}, cet essai expose le contexte, les arguments majeurs, des contre‑arguments, puis une conclusion motivée.`)
]));
// Body paragraphs – lightweight templating
const bodySeeds = [
p.language==='en'?
['Definitions & scope', 'Clarifying terms prevents category errors '+cite, 'Recent data points illustrate the stakes '+cite]:
['Définitions & périmètre', 'Clarifier les termes évite les confusions '+cite, 'Des données récentes illustrent les enjeux '+cite],
p.language==='en'?
['Primary argument', 'Empirical findings and examples support the claim '+cite, 'Mechanisms explain how and why '+cite]:
['Argument principal', 'Des constats empiriques et exemples étayent l’assertion '+cite, 'Des mécanismes expliquent le comment et le pourquoi '+cite],
p.language==='en'?
['Counterargument & rebuttal', pick(L.en.counters)+` …`, pick(L.en.rebuttals)+` …`]:
['Objection & réfutation', pick(L.fr.counters)+` …`, pick(L.fr.rebuttals)+` …`]
];
for(let i=0;i
${escapeHtml(title)}
${escapeHtml(T('intro'))} • ${escapeHtml(T('body'))} • ${escapeHtml(T('conclusion'))}
${paragraphs.map(p=>`${escapeHtml(p)}
`).join('\n')}
${p.citations!=='none'?`${p.language==='en'?'Note: Replace citation placeholders with real sources.':'Note : remplacez les citations par de vraies sources.'}
`:''}
`;
return out;
}
// --- API Mode (OpenAI‑style chat.completions or n8n relay that accepts {messages} ) ---
async function llmEssay(p, outlineOnly=false){
const sys = (p.language==='en') ?
`You are an expert academic writing assistant. Produce a ${p.words}-word ${p.essayType} essay in ${p.language==='en'?'English':'French'}. Avoid hallucinations; if sources are uncertain, use placeholders like [Author, YEAR]. Include a clear thesis, logically ordered paragraphs, at least one counterargument with rebuttal, and a concise conclusion. Keep tone ${p.tone}. Target level: ${p.level}. Structure template: ${p.structure}.` :
`Tu es un assistant d’écriture académique. Rédige un essai ${p.essayType} en ${p.language==='fr'?'français':'anglais'} d’environ ${p.words} mots. Évite les inventions ; si les sources sont incertaines, utilise des repères comme [Auteur, ANNÉE]. Intègre une thèse claire, des paragraphes ordonnés logiquement, au moins une objection et sa réfutation, puis une conclusion concise. Ton ${p.tone}. Niveau visé : ${p.level}. Structure : ${p.structure}.`;
const user = (outlineOnly?
((p.language==='en')?
`Create a detailed outline and thesis for the topic: "${p.subject}". Include section headings and bullet points. Keywords to include: ${p.keywords||'—'}.`:
`Crée un plan détaillé et une thèse pour le sujet : "${p.subject}". Inclue des titres de sections et des puces. Mots‑clés : ${p.keywords||'—'}.`)
:
((p.language==='en')?
`Write the full essay about: "${p.subject}". If provided, use this thesis: ${p.thesis||'(no override)'}; otherwise craft a strong thesis matching the stance: ${p.stance}. Include ${p.citations==='none'?'no references, but add citation placeholders when necessary':p.citations+' styled citation placeholders'}. Keywords: ${p.keywords||'—'}.`:
`Rédige l’essai complet sur : "${p.subject}". Si fourni, utilise cette thèse : ${p.thesis||'(aucune)'} ; sinon, formule une thèse forte conforme à la position : ${p.stance}. Inclure ${p.citations==='none'?'aucune référence, mais des repères de citation si nécessaire': 'des repères façon '+p.citations}. Mots‑clés : ${p.keywords||'—'}.`)
);
const body = {
model: p.apiModel || 'gpt-4o-mini',
messages: [
{role:'system', content: sys},
{role:'user', content: user}
],
temperature: 0.7,
max_tokens: p.maxTokens || 1200,
};
const res = await fetch(p.apiEndpoint, {
method:'POST',
headers:{
'Content-Type':'application/json',
'Authorization': 'Bearer '+p.apiKey
},
body: JSON.stringify(body)
});
if(!res.ok){
const text = await res.text();
throw new Error(`API error ${res.status}: ${text.slice(0,300)}`);
}
const json = await res.json();
// OpenAI-style response
const content = json?.choices?.[0]?.message?.content || JSON.stringify(json);
return (outlineOnly) ? mdToHtml(content) : mdToHtml(content);
}
// Minimal Markdown → HTML converter for headings & lists
function mdToHtml(md){
const esc = escapeHtml(md);
// Convert basic markdown patterns after escaping, re-insert tags via replacements
return esc
.replace(/^######\s?(.*)$/gm,'$1<\/h6>')
.replace(/^#####\s?(.*)$/gm,'$1<\/h5>')
.replace(/^####\s?(.*)$/gm,'$1<\/h4>')
.replace(/^###\s?(.*)$/gm,'$1<\/h3>')
.replace(/^##\s?(.*)$/gm,'$1<\/h2>')
.replace(/^#\s?(.*)$/gm,'$1<\/h1>')
.replace(/^\s*[-*]\s+(.*)$/gm,' $1<\/li>')
.replace(/( .*<\/li>)(\n?)(?! )/gs,'