best-email-builder 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +41 -4
- package/dist/ai-server.js +114 -1
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +504 -169
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +4 -4
- package/dist/{chunk-5W4TUUPD.js → chunk-3RPWH5PC.js} +3 -3
- package/dist/{chunk-QPAZLCPO.js → chunk-C6WHIBGH.js} +2 -2
- package/dist/{chunk-QPAZLCPO.js.map → chunk-C6WHIBGH.js.map} +1 -1
- package/dist/{chunk-NZIQF7IW.js → chunk-CTZ56YJG.js} +192 -23
- package/dist/chunk-CTZ56YJG.js.map +1 -0
- package/dist/{chunk-NC2YDZSP.js → chunk-QH5OI43L.js} +4 -4
- package/dist/{chunk-JU3OUO6X.js → chunk-QRUMXCGW.js} +2 -2
- package/dist/{chunk-XEFBPP5Y.js → chunk-UMVBT7NW.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/library.js +4 -4
- package/dist/server.js +4 -4
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +2 -1
- package/dist/types/ai/ModelPicker.d.ts +34 -0
- package/dist/types/ai/agent.d.ts +31 -0
- package/dist/types/ai/agentEndpoint.d.ts +69 -0
- package/dist/types/ai/claude.d.ts +2 -1
- package/dist/types/ai/draft.d.ts +10 -4
- package/dist/types/ai/generator.d.ts +10 -4
- package/dist/types/ai/index.d.ts +2 -1
- package/dist/types/ai/keys.d.ts +14 -1
- package/dist/types/ai/providers.d.ts +48 -5
- package/dist/types/ai/rewrite.d.ts +2 -1
- package/dist/types/ai/server.d.ts +3 -1
- package/dist/types/ai/subjects.d.ts +2 -1
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/server/handler.d.ts +3 -0
- package/package.json +1 -1
- package/dist/chunk-NZIQF7IW.js.map +0 -1
- /package/dist/{chunk-5W4TUUPD.js.map → chunk-3RPWH5PC.js.map} +0 -0
- /package/dist/{chunk-NC2YDZSP.js.map → chunk-QH5OI43L.js.map} +0 -0
- /package/dist/{chunk-JU3OUO6X.js.map → chunk-QRUMXCGW.js.map} +0 -0
- /package/dist/{chunk-XEFBPP5Y.js.map → chunk-UMVBT7NW.js.map} +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { AI_MESSAGES } from "./i18n/index.js";
|
|
2
2
|
export { createEmailGenerator, cleanGenerateInput, MAX_DETAILS_LENGTH } from "./generator.js";
|
|
3
3
|
export { claudeEmailModel, DEFAULT_CLAUDE_MODEL } from "./claude.js";
|
|
4
|
-
export { AI_PROVIDERS, failureCode, providerEmailModel, resolveProvider } from "./providers.js";
|
|
4
|
+
export { AI_PROVIDERS, failureCode, listProviderModels, providerEmailModel, resolveProvider } from "./providers.js";
|
|
5
5
|
export { EmailGenerationError, GENERATION_ERRORS, normalizeGeneratedMjml, parseDraft, reviewDraft } from "./draft.js";
|
|
6
6
|
export { EMAIL_PURPOSES, PURPOSE_CATEGORIES, purposeById, purposesByCategory } from "./purposes.js";
|
|
7
7
|
export { SYSTEM_PROMPT, EMAIL_DRAFT_SCHEMA, buildUserPrompt } from "./prompt.js";
|
|
8
8
|
export { REWRITE_ACTIONS, REWRITE_LANGUAGES, REWRITE_SYSTEM_PROMPT, createRewriter, reviewRewrite } from "./rewrite.js";
|
|
9
9
|
export { SUBJECTS_SYSTEM_PROMPT, createSubjectSuggester, emailWords, reviewSubjects } from "./subjects.js";
|
|
10
|
+
export { agentEmailModel, AGENT_PROTOCOL, AGENT_ERROR_CODES } from "./agent.js";
|
|
11
|
+
export { createAgentEndpoint, checkAgent } from "./agentEndpoint.js";
|
|
@@ -44,13 +44,14 @@ export function reviewSubjects(subjects: string[], request: ReturnType<typeof cl
|
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
46
46
|
* @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options
|
|
47
|
-
* @returns {(input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[], attempts: number}>}
|
|
47
|
+
* @returns {(input: SubjectsInput, options?: {signal?: AbortSignal, user?: any}) => Promise<{subjects: string[], attempts: number}>}
|
|
48
48
|
*/
|
|
49
49
|
export function createSubjectSuggester({ model, maxAttempts }: {
|
|
50
50
|
model: import("./generator.js").EmailModel;
|
|
51
51
|
maxAttempts?: number;
|
|
52
52
|
}): (input: SubjectsInput, options?: {
|
|
53
53
|
signal?: AbortSignal;
|
|
54
|
+
user?: any;
|
|
54
55
|
}) => Promise<{
|
|
55
56
|
subjects: string[];
|
|
56
57
|
attempts: number;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* project JSON). Both are written with every template, so a document read by
|
|
7
7
|
* a newer version knows whether it must be migrated (see `core/document.js`).
|
|
8
8
|
*/
|
|
9
|
-
export const EMAIL_BUILDER_VERSION: "0.
|
|
9
|
+
export const EMAIL_BUILDER_VERSION: "0.7.0";
|
|
10
10
|
export const EMAIL_DOCUMENT_SCHEMA_VERSION: 1;
|
|
11
11
|
/** @typedef {'desktop'|'mobile'} EmailDevice */
|
|
12
12
|
/** @type {EmailDevice[]} */
|
|
@@ -20,7 +20,7 @@ export function createNativeAdapter({ loadCompiler }?: {
|
|
|
20
20
|
* @typedef {import('./model.js').EmailNode} EmailNode
|
|
21
21
|
*/
|
|
22
22
|
export const ENGINE_NAME: "beb-mjml";
|
|
23
|
-
export const ENGINE_VERSION: "0.
|
|
23
|
+
export const ENGINE_VERSION: "0.7.0";
|
|
24
24
|
export const EMPTY_MJML: "<mjml><mj-body background-color=\"#eceff4\" width=\"600px\"></mj-body></mjml>";
|
|
25
25
|
export type EmailEditorAdapter = import("../core/EmailEditor.js").EmailEditorAdapter;
|
|
26
26
|
export type EmailBlock = import("../core/EmailEditor.js").EmailBlock;
|
|
@@ -26,14 +26,17 @@ export type HandlerOptions = {
|
|
|
26
26
|
generator?: {
|
|
27
27
|
generate?: (input: import("../ai/generator.js").GenerateInput, options?: {
|
|
28
28
|
signal?: AbortSignal;
|
|
29
|
+
user?: User;
|
|
29
30
|
}) => Promise<import("../ai/generator.js").GeneratedEmail>;
|
|
30
31
|
rewrite?: (input: import("../ai/rewrite.js").RewriteInput, options?: {
|
|
31
32
|
signal?: AbortSignal;
|
|
33
|
+
user?: User;
|
|
32
34
|
}) => Promise<{
|
|
33
35
|
text: string;
|
|
34
36
|
}>;
|
|
35
37
|
suggestSubjects?: (input: import("../ai/subjects.js").SubjectsInput, options?: {
|
|
36
38
|
signal?: AbortSignal;
|
|
39
|
+
user?: User;
|
|
37
40
|
}) => Promise<{
|
|
38
41
|
subjects: string[];
|
|
39
42
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "best-email-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Drag-and-drop email builder for React: MJML output that renders in Outlook, Gmail and Apple Mail, with server-side rendering, versions, variables and image uploads.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/draft.js","../src/ai/prompt.js","../src/ai/purposes.js","../src/ai/rewrite.js","../src/ai/subjects.js","../src/ai/i18n/fr.js","../src/ai/i18n/index.js","../src/ai/generator.js","../src/ai/providers.js","../src/ai/claude.js"],"sourcesContent":["// @ts-check\n/**\n * A GENERATED EMAIL, made fit for the builder.\n *\n * A model follows the prompt most of the time, not always. What it gets\n * wrong in a way that can be fixed without asking it again is fixed here:\n *\n * the answer JSON found inside code fences or prose; a bare MJML\n * document is accepted too; markup escaped twice\n * (`width=\\\\\"600px\\\\\"`, some models do it) is decoded once more;\n * padding=\"...\" rewritten as the four longhand attributes the panel and\n * the canvas read (see core/mjmlAttributes);\n * structure content outside a column is put in one, a column outside\n * a section too; `mj-hero`, `mj-wrapper` and `mj-group`\n * become plain sections and columns (the canvas draws\n * them wrong or the panel cannot edit them); `mj-head`,\n * classes and CSS are removed;\n * images every source is emptied, except the sender's logo: a\n * stock photo from a third-party service would end up in\n * a real send one day;\n * MJML in a text an `<mj-image>` put inside an `mj-text` (dead HTML\n * there: no picture, nothing to fill) becomes an image\n * block next to the text; other `<mj-…>` tags are dropped;\n * buttons words that do not read on the fill (a text color given,\n * the fill forgotten: MJML's is dark grey) become white\n * or ink;\n * illegal attributes those MJML reports (`align` on a column) are removed\n * by the generator after compiling (`withoutIllegalAttributes`);\n * variables a `{{tag}}` the catalog does not know becomes a\n * `[placeholder]` the user sees and replaces (an https\n * example link inside a URL).\n *\n * What cannot be fixed here (forbidden HTML, a missing unsubscribe link) is\n * listed by `reviewDraft` and sent back to the model.\n *\n * Dependency-free apart from the engine's MJML reader: runs in the browser,\n * on the server and in unit tests.\n */\n\nimport { parseMjml, parseMjmlFragment, serializeMjml } from '../engine/mjml.js';\nimport { createId, createNode } from '../engine/model.js';\nimport { LONGHAND_SPACING, SIDES, parseSpacing } from '../core/mjmlAttributes.js';\nimport { PALETTE } from '../blocks/shared.js';\nimport { MJML_DEFAULTS } from '../core/mjmlDefaults.js';\nimport { contrastRatio, parseColor, readableOn } from '../features/brand/brand.js';\nimport { validateMjml } from '../validation/mjmlValidator.js';\n\n/**\n * @typedef {import('../engine/model.js').EmailNode} EmailNode\n * @typedef {{name: string, subject: string, preheader: string, mjml: string}} EmailDraft\n */\n\n/**\n * Why a generation failed, in English keys the interface translates. Status:\n * the HTTP status the server answers with.\n */\nexport const GENERATION_ERRORS = {\n NOTHING_TO_GENERATE: { status: 400, message: 'Pick what the email is for, or describe it.' },\n REFUSED: { status: 422, message: 'The AI declined to write this email. Try other details.' },\n TRUNCATED: { status: 502, message: 'The email was too long to finish. Ask for a shorter one.' },\n INVALID_ANSWER: { status: 502, message: 'The AI answer could not be read. Try again.' },\n INVALID_OUTPUT: { status: 502, message: 'The generated email could not be used. Try again.' },\n NOTHING_TO_REWRITE: { status: 400, message: 'There is no text to rewrite.' },\n REWRITE_REFUSED: { status: 422, message: 'The AI declined to rewrite this text.' },\n REWRITE_UNUSABLE: { status: 502, message: 'The rewritten text lost a link or a variable, so nothing was changed. Try again.' },\n NOTHING_TO_SUGGEST: { status: 400, message: 'Write the email or a subject first: the ideas are drawn from it.' },\n SUBJECTS_REFUSED: { status: 422, message: 'The AI declined to suggest subjects for this email.' },\n SUBJECTS_UNUSABLE: { status: 502, message: 'No usable subject came back. Try again.' },\n // The provider's side (providers.js). Never 401 to the browser: the key is the AI's, not the user's session.\n KEY_MISSING: { status: 502, message: 'Connect an AI provider first: it needs a key.' },\n KEY_REFUSED: { status: 502, message: 'The AI provider refused the key. Check it, or use another one.' },\n MODEL_CREDITS: { status: 502, message: 'The AI provider account has no credits left.' },\n MODEL_BUSY: { status: 503, message: 'The AI is busy right now. Try again in a minute.' },\n MODEL_ERROR: { status: 502, message: 'The AI provider could not answer. Try again.' },\n};\n\nexport class EmailGenerationError extends Error {\n /**\n * @param {keyof typeof GENERATION_ERRORS} code\n * @param {string} [detail] what went wrong, for logs and for the model\n */\n constructor(code, detail = '') {\n const known = GENERATION_ERRORS[code] || GENERATION_ERRORS.INVALID_OUTPUT;\n super(known.message);\n this.name = 'EmailGenerationError';\n this.code = code;\n this.status = known.status;\n this.detail = detail || known.message;\n }\n}\n\n/* ── The answer ──────────────────────────────────────────────────────────── */\n\nconst LIMITS = { name: 80, subject: 150, preheader: 200 };\n\n/** One line of plain text: no tags, no line breaks, bounded. @param {unknown} value @param {number} max */\nfunction plainLine(value, max) {\n return String(value ?? '')\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, max);\n}\n\n/**\n * The email in a model's answer.\n * @param {string} text\n * @returns {EmailDraft}\n */\n/**\n * Markup a model escaped twice — `<mj-text align=\\\\\"center\\\\\">` once the JSON\n * is read — decoded once more. Markup with real quotes is left as it is.\n * @param {string} markup\n */\nexport function unescapedTwice(markup) {\n if (!/=\\\\\"/.test(markup) || /=\\s*\"/.test(markup.replace(/=\\\\\"/g, ''))) return markup;\n try {\n const decoded = JSON.parse(`\"${markup}\"`);\n if (typeof decoded === 'string') return decoded;\n } catch {\n // Not a JSON string body (a raw line break…): the quotes alone.\n }\n return markup.replace(/\\\\\"/g, '\"').replace(/\\\\n/g, '\\n');\n}\n\nexport function parseDraft(text) {\n const source = String(text ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n /** @type {any} */\n let data = null;\n const start = unfenced.indexOf('{');\n const end = unfenced.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(unfenced.slice(start, end + 1));\n } catch {\n data = null;\n }\n }\n if (!data && /^<mjml[\\s>]/i.test(unfenced)) data = { mjml: unfenced };\n if (!data || typeof data.mjml !== 'string' || !data.mjml.trim()) {\n throw new EmailGenerationError('INVALID_ANSWER', 'The answer is not the expected JSON object with an \"mjml\" field.');\n }\n return {\n name: plainLine(data.name, LIMITS.name),\n subject: plainLine(data.subject, LIMITS.subject),\n preheader: plainLine(data.preheader, LIMITS.preheader),\n mjml: unescapedTwice(data.mjml.trim()),\n };\n}\n\n/* ── The MJML ────────────────────────────────────────────────────────────── */\n\nconst CONTENT_TAGS = new Set(['mj-text', 'mj-image', 'mj-button', 'mj-divider', 'mj-spacer', 'mj-social', 'mj-navbar', 'mj-table']);\nconst REMOVED_TAGS = new Set(['mj-head', 'mj-raw', 'mj-style', 'mj-font', 'mj-include', 'mj-attributes']);\nconst REMOVED_ATTRIBUTES = ['css-class', 'mj-class'];\n/** Attributes that take a length: a bare number is read as pixels. */\nconst PIXEL_ATTRIBUTES = new Set(['font-size', 'border-radius', 'icon-size', 'height', 'width', 'letter-spacing']);\n/** Section attributes kept when an `mj-hero` becomes a section. */\nconst HERO_KEPT = ['background-color', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'padding', 'border-radius'];\n\nconst whiteSection = (/** @type {EmailNode[]} */ children) =>\n createNode('mj-section', { attributes: { 'background-color': PALETTE.surface }, children });\nconst column = (/** @type {EmailNode[]} */ children) => createNode('mj-column', { children });\n\n/**\n * Attributes as the builder writes them.\n * @param {EmailNode} node @param {Set<string>} keepImages\n */\nfunction cleanAttributes(node, keepImages) {\n const attributes = { ...node.attributes };\n for (const name of REMOVED_ATTRIBUTES) delete attributes[name];\n for (const key of LONGHAND_SPACING) {\n if (attributes[key] === undefined) continue;\n const sides = parseSpacing(attributes[key]);\n SIDES.forEach((side, i) => {\n const longhand = `${key}-${side}`;\n if (attributes[longhand] === undefined) attributes[longhand] = sides[i];\n });\n delete attributes[key];\n }\n for (const [name, value] of Object.entries(attributes)) {\n const unitless = /^-?\\d+(\\.\\d+)?$/.test(value.trim());\n if (unitless && (PIXEL_ATTRIBUTES.has(name) || /^padding-/.test(name))) attributes[name] = `${value.trim()}px`;\n }\n if (node.tag === 'mj-image' && attributes.src !== undefined && !keepImages.has(attributes.src.trim())) attributes.src = '';\n if (node.tag === 'mj-button') {\n const fill = attributes['background-color'] ?? MJML_DEFAULTS['mj-button']['background-color'];\n const words = attributes.color ?? MJML_DEFAULTS['mj-button'].color;\n if (parseColor(fill) && parseColor(words) && contrastRatio(fill, words) < 3) attributes.color = readableOn(fill);\n }\n delete attributes['background-url'];\n return attributes;\n}\n\n/**\n * Groups runs of `isLoose` children into containers made by `wrap`.\n * @param {EmailNode[]} children @param {(node: EmailNode) => boolean} isLoose @param {(nodes: EmailNode[]) => EmailNode} wrap\n */\nfunction wrapLoose(children, isLoose, wrap) {\n /** @type {EmailNode[]} */\n const out = [];\n /** @type {EmailNode[]} */\n let run = [];\n const flush = () => {\n if (run.length) out.push(wrap(run));\n run = [];\n };\n for (const child of children) {\n if (isLoose(child)) run.push(child);\n else {\n flush();\n out.push(child);\n }\n }\n flush();\n return out;\n}\n\n/** An `<mj-image>` (self-closing or not), or any other MJML tag, found in HTML. */\nconst MJML_IN_HTML = /<mj-image\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*?(?:\\/>|>[\\s\\S]*?<\\/mj-image\\s*>)|<\\/?mj-[a-z-]*\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>/gi;\n\n/**\n * A text whose HTML holds MJML: each image becomes a block of its own, in its\n * place between the parts of the text; other MJML tags are dropped, their\n * words kept. A part with no words left is dropped.\n * @param {EmailNode} node @param {Record<string, string>} attributes @param {Set<string>} keepImages\n * @returns {EmailNode[]}\n */\nfunction liftImages(node, attributes, keepImages) {\n const content = String(node.content);\n /** @type {EmailNode[]} */\n const parts = [];\n let text = '';\n const flush = () => {\n if (text.replace(/<[^>]*>| |\\s/g, '')) {\n parts.push({ ...node, id: parts.length ? createId() : node.id, attributes, content: text.trim() });\n }\n text = '';\n };\n let last = 0;\n for (const match of content.matchAll(MJML_IN_HTML)) {\n text += content.slice(last, match.index);\n last = (match.index ?? 0) + match[0].length;\n const image = /^<mj-image\\b/i.test(match[0]) && parseMjmlFragment(match[0]).find((child) => child.tag === 'mj-image');\n if (!image) continue;\n flush();\n parts.push(...cleanNode(image, keepImages));\n }\n text += content.slice(last);\n flush();\n return parts;\n}\n\n/**\n * The node and its subtree, cleaned. Returns a LIST: a removed node gives\n * none, an unwrapped one gives its children.\n * @param {EmailNode} node @param {Set<string>} keepImages\n * @returns {EmailNode[]}\n */\nfunction cleanNode(node, keepImages) {\n if (REMOVED_TAGS.has(node.tag)) return [];\n const children = node.children.flatMap((child) => cleanNode(child, keepImages));\n const attributes = cleanAttributes(node, keepImages);\n\n if (node.tag === 'mj-wrapper') {\n // Its sections take its place; a background it had goes to those without one.\n return children.map((child) =>\n child.attributes['background-color'] || !attributes['background-color']\n ? child\n : { ...child, attributes: { ...child.attributes, 'background-color': attributes['background-color'] } }\n );\n }\n if (node.tag === 'mj-group') return children;\n if (node.tag === 'mj-hero') {\n const kept = Object.fromEntries(Object.entries(attributes).filter(([name]) => HERO_KEPT.includes(name)));\n const columns = children.some((child) => child.tag === 'mj-column') ? children : [column(children)];\n return [createNode('mj-section', { id: node.id, attributes: { 'background-color': PALETTE.navy, ...kept }, children: columns })];\n }\n if (node.tag === 'mj-section') {\n return [{ ...node, attributes, children: wrapLoose(children, (child) => child.tag !== 'mj-column', column) }];\n }\n if (node.tag === 'mj-body') {\n const sectioned = wrapLoose(children, (child) => child.tag === 'mj-column', (nodes) => whiteSection(nodes));\n const wrapped = wrapLoose(sectioned, (child) => CONTENT_TAGS.has(child.tag), (nodes) => whiteSection([column(nodes)]));\n return [{ ...node, attributes: { 'background-color': PALETTE.canvas, ...attributes, width: '600px' }, children: wrapped }];\n }\n if (typeof node.content === 'string' && /<\\/?mj-/i.test(node.content)) {\n if (node.tag === 'mj-text') return liftImages(node, attributes, keepImages);\n return [{ ...node, attributes, children, content: node.content.replace(MJML_IN_HTML, '') }];\n }\n return [{ ...node, attributes, children }];\n}\n\n/** \"order.number\" → \"order number\". @param {string} key */\nconst placeholderOf = (key) => `[${key.replace(/[._]+/g, ' ').trim()}]`;\n\n/**\n * Unknown `{{tags}}` replaced: inside a URL attribute by an https example\n * link, elsewhere by a visible `[placeholder]`.\n * @param {string} mjml @param {Set<string>} known\n */\nfunction replaceUnknownVariables(mjml, known) {\n const unknown = (/** @type {string} */ key) => !known.has(key);\n const inUrls = mjml.replace(/\\s(href|src)=\"([^\"]*)\"/g, (whole, name, value) =>\n /\\{\\{\\s*([A-Za-z][\\w.]*)/.test(value) && [...value.matchAll(/\\{\\{\\s*([A-Za-z][\\w.]*)/g)].some((m) => unknown(m[1]))\n ? ` ${name}=\"${name === 'href' ? 'https://example.com' : ''}\"`\n : whole\n );\n return inUrls.replace(/\\{\\{\\s*([A-Za-z][\\w.]*)\\s*(?:\\|[^{}]*)?\\}\\}/g, (whole, key) => (unknown(key) ? placeholderOf(key) : whole));\n}\n\n/**\n * The MJML of a generated email, rewritten as the builder writes it.\n *\n * @param {string} mjml\n * @param {{variables?: {key: string}[], keepImages?: string[]}} [options]\n * `variables`: the catalog (`unsubscribe` is always known);\n * `keepImages`: image sources kept as they are (the sender's logo).\n */\nexport function normalizeGeneratedMjml(mjml, { variables = [], keepImages = [] } = {}) {\n const tree = parseMjml(String(mjml || ''));\n const [root] = cleanNode(tree, new Set(keepImages.filter(Boolean)));\n const known = new Set(['unsubscribe', ...variables.map((variable) => variable.key)]);\n return replaceUnknownVariables(serializeMjml(root), known);\n}\n\n/**\n * The attributes MJML reports as illegal on a tag (`Attribute align is\n * illegal` on an `mj-column`) removed from every element of that tag: MJML\n * ignores them, and their warning would stay with the email.\n * @param {string} mjml\n * @param {{message: string, tagName?: string}[]} warnings MJML's, from compiling it\n * @returns {string|null} null: none to remove\n */\nexport function withoutIllegalAttributes(mjml, warnings) {\n /** @type {Map<string, Set<string>>} */\n const illegal = new Map();\n for (const warning of warnings) {\n const name = /Attribute ([\\w-]+) is illegal/.exec(warning.message)?.[1];\n const tag = warning.tagName || /\\((mj-[\\w-]+)\\)/.exec(warning.message)?.[1];\n if (name && tag) illegal.set(tag, new Set([...(illegal.get(tag) || []), name]));\n }\n if (!illegal.size) return null;\n /** @param {EmailNode} node @returns {EmailNode} */\n const strip = (node) => {\n const names = illegal.get(node.tag);\n const attributes = names ? Object.fromEntries(Object.entries(node.attributes).filter(([name]) => !names.has(name))) : node.attributes;\n return { ...node, attributes, children: node.children.map(strip) };\n };\n return serializeMjml(strip(parseMjml(mjml)));\n}\n\n/* ── Review ──────────────────────────────────────────────────────────────── */\n\n/**\n * What still keeps the email from being used as it is — in English, written\n * for the model that will correct it.\n *\n * @param {EmailDraft} draft\n * @param {{kind?: string, compileWarnings?: {message: string}[]}} [options]\n * @returns {{blocking: string[], advice: string[]}}\n * `blocking`: the builder must not open it (rejected by the validator);\n * `advice`: it opens, but is not what was asked.\n */\nexport function reviewDraft(draft, { kind = 'marketing', compileWarnings = [] } = {}) {\n const blocking = validateMjml(draft.mjml).issues.map((issue) => issue.message);\n /** @type {string[]} */\n const advice = [];\n if (!/<mj-section[\\s>]/.test(draft.mjml)) advice.push('The email has no <mj-section>: build it from sections and columns.');\n const unsubscribe = /\\{\\{\\s*unsubscribe\\s*\\}\\}/.test(draft.mjml);\n if (kind !== 'security' && !unsubscribe) {\n advice.push('The footer has no link with href=\"{{ unsubscribe }}\".');\n }\n if (kind === 'security' && unsubscribe) advice.push('A security email must not have an unsubscribe link.');\n if (!draft.subject) advice.push('The subject is empty.');\n if (!draft.preheader) advice.push('The preheader is empty.');\n for (const warning of compileWarnings.slice(0, 8)) advice.push(`MJML: ${warning.message}`);\n return { blocking, advice };\n}\n","// @ts-check\n/**\n * THE PROMPT that writes an email from scratch.\n *\n * Two parts:\n * system fixed: what the builder can edit and how a good email is made.\n * The same for every request, so a provider can cache it;\n * user the request: the purpose and its brief (`purposes.js`), the\n * user's details, the language, the sender and the variables.\n *\n * The rules in the system prompt are the builder's own conventions, the ones\n * the blocks and the gallery follow (see `blocks/shared.js`, `starter.js`):\n * longhand paddings, explicit fonts and colors on every text, web-safe fonts,\n * empty image sources. An email that follows them opens in the editor block\n * by block, and the canvas shows what will be sent.\n */\n\nimport { FONT_OPTIONS, PALETTE } from '../blocks/shared.js';\n\n/**\n * @typedef {import('./purposes.js').EmailPurpose} EmailPurpose\n * @typedef {{key: string, label?: string}} PromptVariable\n * @typedef {{name?: string, address?: string, logoUrl?: string}} PromptSender\n */\n\n/** JSON schema of the answer (structured output where the provider has it). */\nexport const EMAIL_DRAFT_SCHEMA = {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Short internal name of the template' },\n subject: { type: 'string', description: 'Subject line' },\n preheader: { type: 'string', description: 'Preview text shown after the subject in the inbox' },\n mjml: { type: 'string', description: 'The complete MJML document' },\n },\n required: ['name', 'subject', 'preheader', 'mjml'],\n additionalProperties: false,\n};\n\nconst FONTS = FONT_OPTIONS.map((option) => `\"${option.value}\"`).join(', ');\n\nexport const SYSTEM_PROMPT = `You are a senior email designer and copywriter. You write complete, ready-to-send marketing and transactional emails in MJML for a visual email builder. The user will open your email in the builder, edit it block by block and send it, so it must follow the builder's conventions exactly.\n\n# Answer\nReturn one JSON object, nothing else: {\"name\": string, \"subject\": string, \"preheader\": string, \"mjml\": string}.\n- name: a short internal template name (at most 50 characters), in the email's language.\n- subject: at most 60 characters, specific and honest, no ALL CAPS words, no spam triggers (\"FREE!!!\", \"$$$\"), at most one emoji and only when the occasion is festive.\n- preheader: 40 to 110 characters that complete the subject (never repeat it).\n- mjml: the full MJML document.\n\n# MJML the builder can edit\n- Skeleton: <mjml><mj-body background-color=\"${PALETTE.canvas}\" width=\"600px\"> ...sections... </mj-body></mjml>. No <mj-head>: the builder adds the title, preview text and language itself.\n- The body is a vertical stack of <mj-section>. Each section holds one to four <mj-column>. Content elements live only inside a column.\n- Allowed elements, and only these: mj-section, mj-column, mj-text, mj-image, mj-button, mj-divider, mj-spacer, mj-social, mj-social-element.\n- Never use mj-hero, mj-wrapper, mj-group, mj-table, mj-navbar, mj-raw, mj-style, mj-font, mj-include, mj-attributes, mj-class, the css-class attribute, <style> or <script>.\n- Inside mj-text and mj-button, only simple inline HTML: <b>, <strong>, <i>, <em>, <u>, <br />, <a href=\"...\" style=\"...\">, <span style=\"...\">, <p style=\"margin:0 0 12px 0;\">, <ul>, <ol>, <li>. No <h1>-<h6>, <div>, <table> or <img> in text: a heading is an mj-text written large and bold.\n- Inline style attributes only on <a>, <span>, <p>, <li>, and only for color, font-weight, font-style, text-decoration and margin.\n\n# Attributes\n- Padding ALWAYS as the four longhand attributes padding-top, padding-right, padding-bottom, padding-left. Never the padding=\"...\" shorthand. The one exception: inner-padding on mj-button (shorthand, for example inner-padding=\"12px 28px\").\n- Every mj-text and mj-button sets font-family, font-size, line-height and color itself (plus font-weight=\"bold\" when bold). Sizes in px: font-size=\"16px\", line-height=\"24px\".\n- Fonts: web-safe only, one family for the whole email (at most two: one for headings, one for text): ${FONTS}. No web fonts.\n- Side spacing: content elements of one-column sections take padding-left=\"32px\" padding-right=\"32px\". In two- to four-column sections use 12px to 16px on the sides of each element.\n- Sections: background-color=\"${PALETTE.surface}\" for content (or a brand tint), padding-top and padding-bottom between 8px and 48px. The email reads as one card on the ${PALETTE.canvas} body background.\n- Column widths in percent only when unequal (for example width=\"40%\" and width=\"60%\"), always adding up to 100%.\n- Images: <mj-image src=\"\" alt=\"...\" ...>. Leave src EMPTY: the user uploads their own pictures. Always write an alt that describes the picture to place, in the email's language. No background-url either. Use 0 to 3 images; product images in columns.\n- Buttons: border-radius=\"6px\", font-weight=\"bold\", inner-padding=\"12px 28px\", aligned like the text around them, href with an https URL. When the real URL is unknown, use https://example.com followed by a meaningful path (https://example.com/reset-password).\n- Dividers: border-color in a light gray, border-width=\"1px\".\n- Social icons (only when the details ask for them, in the footer): <mj-social mode=\"horizontal\" align=\"center\" icon-size=\"28px\" border-radius=\"14px\" inner-padding=\"6px\"> with <mj-social-element name=\"facebook-noshare\" href=\"https://...\"></mj-social-element>; names: facebook-noshare, instagram-noshare, linkedin-noshare, x-noshare, youtube-noshare, github-noshare.\n\n# Design\n- Structure: a header section (the sender's logo or name), a hero section, one to four body sections, a footer section. Three to eight sections in total.\n- One brand color for buttons and accents (use the one given, otherwise ${PALETTE.brand} or a color that suits the occasion), text ${PALETTE.ink} on white, headings ${PALETTE.navy} or the brand color.\n- Contrast: body text at least 4.5:1 against its background, large text and buttons at least 3:1. White text only on dark backgrounds.\n- Type scale: main heading 28 to 32px with line-height 36 to 40px, section headings 20 to 22px, body 15 to 16px with line-height 24 to 26px, small print 12 to 13px.\n- A hero is a section with a background color, a large heading, one sentence and usually the main button, centered.\n- One primary call to action (two at most), with a clear 2- to 4-word label that starts with a verb. Repeat it at most once at the end of a long email.\n- Mobile first: columns stack on phones, so each column must make sense on its own. Short paragraphs of one to three sentences.\n\n# Copy\n- Write the real email for the purpose and the details given: specific, useful, warm and concise. Never lorem ipsum, never meta comments.\n- Personalize with the variables listed in the request, written exactly as {{key}}. A greeting gives the first name a natural default after a pipe, used when the name is unknown; the greeting and its default are in the email's language: Hello {{contact.firstname|there}}, in English, Bonjour {{contact.firstname|à vous}}, in French, Вітаємо, {{contact.firstname|друже}}! in Ukrainian. Use no other {{...}} variable.\n- Information that is not given and not in the variables (an order number, a date, a price, a code, an address) is written as a short placeholder in square brackets that the user will replace: [order number], [date], [20%]. Never invent facts that would be wrong.\n- Links read as what they do (\"Reset my password\", \"See the collection\"), never \"click here\".\n- The footer (last section): the sender's name and postal address (or [Company address]) in 12px #6d7484 text, centered, and the footer line the request asks for. The unsubscribe link is exactly <a href=\"{{ unsubscribe }}\" style=\"color:#6d7484;text-decoration:underline;\">...</a>.\n- Keep the MJML under 30 KB.`;\n\n/** Footer line asked for each kind of purpose. @type {Record<string, string>} */\nconst FOOTER_RULES = {\n marketing: 'This is a marketing email: the footer MUST contain the unsubscribe link (href=\"{{ unsubscribe }}\").',\n transactional:\n 'This is a transactional email: the footer contains a discreet \"Manage email preferences\" link with href=\"{{ unsubscribe }}\".',\n security:\n 'This is a security email: NO unsubscribe or preferences link. The footer says in one line why the recipient got this email.',\n};\n\n/**\n * English name of a language code (\"fr\" → \"French\"), the code itself when\n * the runtime does not know it.\n * @param {string} code\n */\nexport function languageName(code) {\n const value = String(code || 'en');\n try {\n return new Intl.DisplayNames(['en'], { type: 'language' }).of(value) || value;\n } catch {\n return value;\n }\n}\n\n/** One line per variable, the unsubscribe link left to the footer rule. @param {PromptVariable[]} variables */\nfunction variableLines(variables) {\n return variables\n .filter((variable) => variable.key !== 'unsubscribe')\n .map((variable) => `- {{${variable.key}}}${variable.label ? `: ${variable.label}` : ''}`)\n .join('\\n');\n}\n\n/**\n * The request message.\n *\n * @param {{\n * purpose?: EmailPurpose|null,\n * details?: string,\n * language?: string,\n * sender?: PromptSender,\n * brand?: {primary?: string, heading?: string, font?: string},\n * variables?: PromptVariable[],\n * }} input\n */\nexport function buildUserPrompt({ purpose = null, details = '', language = 'en', sender = {}, brand = {}, variables = [] }) {\n const lines = [];\n if (purpose) {\n lines.push(`Write a \"${purpose.label}\" email.`, '', `Brief: ${purpose.brief}`);\n } else {\n lines.push('Write the email described below.');\n }\n const extra = String(details || '').trim();\n if (extra) lines.push('', 'Details from the user (they take precedence over the brief):', extra);\n lines.push(\n '',\n `Language: write everything in ${languageName(language)}, unless the details ask for another language.`,\n '',\n 'Sender:',\n `- name: ${sender.name?.trim() || '[Company name]'}`,\n `- postal address: ${sender.address?.trim() || '[Company address]'}`,\n sender.logoUrl\n ? `- logo: put <mj-image src=\"${sender.logoUrl}\" alt=\"${sender.name || 'Logo'}\" width=\"140px\" align=\"left\"> in the header (this is the one image whose src you fill).`\n : '- logo: none, write the sender name in bold in the header.'\n );\n const look = [\n brand.primary && `- main color (buttons, banners, links): ${brand.primary}`,\n brand.heading && `- headings color: ${brand.heading}`,\n brand.font && `- font: font-family=\"${brand.font}\" on every text and button`,\n ].filter(Boolean);\n if (look.length) lines.push('', 'Brand (use exactly these):', ...look);\n const vars = variableLines(variables);\n if (vars) lines.push('', 'Variables you may use:', vars);\n lines.push('', FOOTER_RULES[purpose?.kind || 'marketing']);\n return lines.join('\\n');\n}\n\n/**\n * The message that asks the model to correct its email.\n * @param {string[]} problems\n */\nexport function buildRepairPrompt(problems) {\n return [\n 'The email cannot be opened in the builder as it is. Fix these problems and return the complete corrected JSON object:',\n ...problems.map((problem) => `- ${problem}`),\n ].join('\\n');\n}\n","// @ts-check\n/**\n * WHAT AN EMAIL IS FOR — the purposes offered by \"Generate\".\n *\n * Each purpose carries a short BRIEF for the model: the sections and the\n * information such an email needs (a password reset needs the link's expiry\n * and a \"not you?\" line; a sale needs the offer, its end date and a code).\n * The brief is the part of the prompt that changes with the purpose; the\n * design and MJML rules stay the same (see `prompt.js`).\n *\n * `kind` sets the footer:\n * marketing sent in bulk: an unsubscribe link is required (CAN-SPAM, GDPR);\n * transactional triggered by the recipient's action: a discreet link to the\n * email preferences;\n * security account security: no unsubscribe link at all, a line says\n * why the message was sent.\n *\n * Plain data: labels are English keys translated by the interface, briefs\n * stay in English (they are written for the model, not shown).\n */\n\n/**\n * @typedef {'marketing'|'transactional'|'security'} PurposeKind\n * @typedef {{id: string, label: string}} PurposeCategory\n * @typedef {{id: string, label: string, category: string, kind: PurposeKind, brief: string}} EmailPurpose\n */\n\n/** @type {PurposeCategory[]} */\nexport const PURPOSE_CATEGORIES = [\n { id: 'promotions', label: 'Sales and promotions' },\n { id: 'holidays', label: 'Holidays and occasions' },\n { id: 'events', label: 'Events and appointments' },\n { id: 'news', label: 'News and content' },\n { id: 'account', label: 'Account and security' },\n { id: 'orders', label: 'Orders and payments' },\n { id: 'relationship', label: 'Customer relationship' },\n { id: 'organization', label: 'Business and organization' },\n];\n\n/** @param {string} id @param {string} label @param {string} category @param {PurposeKind} kind @param {string} brief */\nconst purpose = (id, label, category, kind, brief) => ({ id, label, category, kind, brief });\n\n/** @type {EmailPurpose[]} */\nexport const EMAIL_PURPOSES = [\n // Sales and promotions\n purpose('sale', 'Store sale', 'promotions', 'marketing',\n 'An online store sale. A bold hero with the offer (discount and end date), a short reason to buy now, 2 to 4 featured products in two columns (image, name, old and new price, link), one \"Shop the sale\" button, and the conditions in small text.'),\n purpose('flash-sale', 'Flash sale', 'promotions', 'marketing',\n 'A sale that lasts hours, not days. Urgency first: the deadline in the hero, a large discount, one button, very little text.'),\n purpose('discount-code', 'Discount code', 'promotions', 'marketing',\n 'A personal discount code. The code in a large, easy-to-copy box (a colored section with the code in big letters), what it gives, its expiry date, and a button to use it.'),\n purpose('new-product', 'New product', 'promotions', 'marketing',\n 'A product launch. A hero image with the product name, three benefits in three short columns or a list, the price or availability, and a \"Discover\" button.'),\n purpose('back-in-stock', 'Back in stock', 'promotions', 'marketing',\n 'A product the recipient wanted is available again. The product (image, name, price), \"limited stock\" urgency without exaggeration, and a \"Buy now\" button.'),\n purpose('price-drop', 'Price drop', 'promotions', 'marketing',\n 'A product the recipient looked at is now cheaper. Old and new price side by side, the product image, and a button.'),\n purpose('abandoned-cart', 'Abandoned cart', 'promotions', 'marketing',\n 'Items left in the cart. A friendly reminder, the cart items (image, name, price), a \"Return to my cart\" button, and a reassurance line (free returns, secure payment, help contact).'),\n purpose('black-friday', 'Black Friday', 'promotions', 'marketing',\n 'A Black Friday or Cyber Monday campaign. A dark, high-contrast hero with the offer and dates, featured categories or products in columns, and a strong button.'),\n purpose('loyalty-reward', 'Loyalty reward', 'promotions', 'marketing',\n 'Thank a loyal customer with a reward: their status or points balance, the reward, how to use it, and a button.'),\n purpose('refer-a-friend', 'Refer a friend', 'promotions', 'marketing',\n 'Invite the customer to refer friends. What the friend gets, what the customer gets, three short steps, and a button to share their link.'),\n\n // Holidays and occasions\n purpose('christmas', 'Christmas', 'holidays', 'marketing',\n 'Christmas greetings from the company, warm and festive (deep red, green or gold accents), a short heartfelt message, an optional gift idea or offer, and the holiday opening hours if relevant.'),\n purpose('new-year', 'New Year', 'holidays', 'marketing',\n 'New Year wishes. A celebratory hero, thanks for the past year, one or two highlights or promises for the year ahead, and warm wishes.'),\n purpose('season-greetings', \"Season's greetings\", 'holidays', 'marketing',\n 'End-of-year greetings that suit every culture and belief (no religious reference): thanks, warm wishes, and the holiday closing dates.'),\n purpose('valentines-day', \"Valentine's Day\", 'holidays', 'marketing',\n \"Valentine's Day: a romantic tone (soft pink or red), gift ideas in columns, and a delivery deadline.\"),\n purpose('easter', 'Easter', 'holidays', 'marketing',\n 'Easter: a spring palette, greetings, and an optional seasonal offer.'),\n purpose('mothers-day', \"Mother's Day\", 'holidays', 'marketing',\n \"Mother's Day: gift ideas in columns, the order deadline for delivery in time, and a gentle tone.\"),\n purpose('fathers-day', \"Father's Day\", 'holidays', 'marketing',\n \"Father's Day: gift ideas in columns, the order deadline for delivery in time, and a warm tone.\"),\n purpose('halloween', 'Halloween', 'holidays', 'marketing',\n 'Halloween: a playful, spooky tone (orange and near-black, still readable), a themed offer or event.'),\n purpose('back-to-school', 'Back to school', 'holidays', 'marketing',\n 'Back to school: essentials or a checklist in columns, an offer, and a cheerful tone.'),\n purpose('summer', 'Summer holidays', 'holidays', 'marketing',\n 'Summer: a bright palette, summer picks or plans, and the summer opening hours if relevant.'),\n purpose('birthday', 'Birthday wishes', 'holidays', 'marketing',\n \"Happy birthday to the recipient by first name: a festive hero, a personal message, and a birthday gift or code with its validity.\"),\n purpose('anniversary', 'Customer anniversary', 'holidays', 'marketing',\n 'One (or more) years since the recipient became a customer: thanks, a short look back, and a small gift.'),\n purpose('womens-day', \"Women's Day\", 'holidays', 'marketing',\n \"International Women's Day (March 8): respectful greetings, an optional offer or a story about women of the team, no clichés.\"),\n purpose('thanksgiving', 'Thanksgiving', 'holidays', 'marketing',\n 'Thanksgiving: gratitude to customers, a warm autumn palette, and an optional offer.'),\n purpose('lunar-new-year', 'Lunar New Year', 'holidays', 'marketing',\n 'Lunar New Year: wishes for the new year (red and gold accents), the animal of the year if relevant, and an optional offer.'),\n purpose('ramadan-eid', 'Ramadan and Eid', 'holidays', 'marketing',\n 'Ramadan Kareem or Eid Mubarak greetings: respectful and warm, adapted opening hours, an optional offer.'),\n purpose('diwali', 'Diwali', 'holidays', 'marketing',\n 'Diwali, the festival of lights: warm wishes, a light-filled palette (gold, deep orange), an optional offer.'),\n\n // Events and appointments\n purpose('meeting-reminder', 'Meeting reminder', 'events', 'transactional',\n 'A reminder of an upcoming meeting: what, when (date, time, time zone), where (address or video link), who, what to prepare, and buttons to join and to reschedule.'),\n purpose('appointment-confirmation', 'Appointment confirmation', 'events', 'transactional',\n 'Confirm a booked appointment (doctor, salon, garage, consultant): service, date and time, address with directions, how to cancel or reschedule, and a contact.'),\n purpose('reservation-confirmation', 'Booking confirmation', 'events', 'transactional',\n 'Confirm a booking (restaurant table, hotel room, class): booking details in a clear summary box, arrival information, and how to change it.'),\n purpose('event-invitation', 'Event invitation', 'events', 'marketing',\n 'Invite to an event: a hero with the name, date and place, what to expect (program or speakers in columns), and an \"I will come\" RSVP button.'),\n purpose('webinar-invitation', 'Webinar invitation', 'events', 'marketing',\n 'Invite to a webinar: the topic and what attendees will learn (three points), the speaker, date and time with time zone, and a \"Register\" button.'),\n purpose('save-the-date', 'Save the date', 'events', 'marketing',\n 'Announce a date to keep free before the full invitation: the event name and date very large, one sentence, and an \"Add to calendar\" button.'),\n purpose('registration-confirmed', 'Registration confirmed', 'events', 'transactional',\n 'Confirm a registration to an event, a course or a webinar: what, when, where or the access link, an \"Add to calendar\" button, and how to cancel.'),\n purpose('event-change', 'Cancellation or new date', 'events', 'transactional',\n 'An event or appointment is cancelled or moved: the change in the first line, the new date if any, what happens to tickets or bookings, an apology, and a contact.'),\n purpose('event-follow-up', 'Thanks for attending', 'events', 'marketing',\n 'Thank attendees after an event: a recap, links to the replay or slides, a short survey, and the next event.'),\n\n // News and content\n purpose('newsletter', 'Newsletter', 'news', 'marketing',\n 'A newsletter: an intro, a lead story with an image, 2 or 3 shorter articles (title, two lines, \"Read more\" link), and an optional tip or quote.'),\n purpose('new-article', 'New article', 'news', 'marketing',\n 'Share one new blog article or video: its image, title, a two-sentence teaser, and a \"Read the article\" button.'),\n purpose('product-update', \"What's new\", 'news', 'marketing',\n \"A product update or release notes: the headline feature with an image, 2 or 3 smaller improvements, and a button to try it.\"),\n purpose('announcement', 'Announcement', 'news', 'marketing',\n 'An important announcement (new name, new address, partnership, milestone): what changes, why, what it means for the recipient, and a button.'),\n purpose('tips', 'Tips and how-to', 'news', 'marketing',\n 'Practical tips: 3 to 5 numbered tips, each with a short title and one or two sentences, and a button to learn more.'),\n\n // Account and security\n purpose('welcome', 'Welcome', 'account', 'transactional',\n 'Welcome a new user or subscriber: a warm hero, what they can expect, 3 first steps, and one button to get started.'),\n purpose('verify-email', 'Email verification', 'account', 'security',\n 'Ask to confirm the email address: one short sentence, a \"Confirm my email\" button, the link validity, and \"If you did not sign up, ignore this email\".'),\n purpose('password-reset', 'Password reset', 'account', 'security',\n 'A password reset: \"We received a request to reset your password\", a \"Reset my password\" button, the link expiry, and \"If you did not ask, ignore this email; your password stays the same\". Sober and reassuring, no marketing.'),\n purpose('login-code', 'Login code', 'account', 'security',\n 'A one-time sign-in code: the code as a placeholder in very large letters in a light box, its validity in minutes, and a warning never to share it. No button.'),\n purpose('security-alert', 'Security alert', 'account', 'security',\n 'A new sign-in or account change: what happened, when, device and location, \"If it was you, nothing to do\", and a \"Secure my account\" button.'),\n purpose('onboarding', 'Getting started', 'account', 'transactional',\n 'Help a new user get value: one key feature explained in 3 steps with short texts, a button, and where to find help.'),\n purpose('trial-ending', 'Trial ending', 'account', 'transactional',\n 'The free trial ends soon: the end date, what they lose, the plans or price, and an \"Upgrade\" button.'),\n\n // Orders and payments\n purpose('order-confirmation', 'Order confirmation', 'orders', 'transactional',\n 'Confirm an order: thanks, order number and date, the items in a summary (name, quantity, price), totals, shipping address, delivery estimate, and a \"View my order\" button.'),\n purpose('order-shipped', 'Order shipped', 'orders', 'transactional',\n 'The order is on its way: carrier, expected delivery date, a \"Track my parcel\" button, and the shipped items.'),\n purpose('delivery-delay', 'Delivery delay', 'orders', 'transactional',\n 'An order will arrive late: an apology, the new estimated date, the reason in one sentence, an optional gesture, and a \"Track my order\" button.'),\n purpose('receipt', 'Receipt or invoice', 'orders', 'transactional',\n 'A payment receipt or invoice: amount paid, date, payment method, a clear breakdown, and a \"Download the invoice\" button.'),\n purpose('payment-failed', 'Payment failed', 'orders', 'transactional',\n 'A payment did not go through: calm and helpful, what happens next and when, and an \"Update my payment method\" button.'),\n purpose('subscription-renewal', 'Subscription renewal', 'orders', 'transactional',\n 'An upcoming subscription renewal: plan, amount, renewal date, and buttons to manage the subscription.'),\n purpose('refund', 'Refund issued', 'orders', 'transactional',\n 'A refund was issued: amount, original order, when it reaches the account, and a contact for questions.'),\n\n // Customer relationship\n purpose('thank-you', 'Thank you', 'relationship', 'marketing',\n 'A sincere thank-you (for a purchase, a year together, a donation, a recommendation): personal tone, short, and an optional small gift.'),\n purpose('review-request', 'Review request', 'relationship', 'marketing',\n 'Ask for a review of a recent purchase or visit: the product or service, one sentence on why reviews matter, and a \"Leave a review\" button.'),\n purpose('survey', 'Survey', 'relationship', 'marketing',\n 'Ask for feedback: how long it takes (for example 2 minutes), why it helps, an optional incentive, and a \"Take the survey\" button.'),\n purpose('win-back', 'We miss you', 'relationship', 'marketing',\n 'Re-engage an inactive customer: what is new since their last visit, a comeback offer, and a button.'),\n purpose('follow-up', 'Follow-up', 'relationship', 'transactional',\n 'A personal follow-up after a meeting, a call or a quote: a reminder of what was discussed, the next step, and an easy way to reply or book a call. Short and personal, like a message from one person.'),\n purpose('apology', 'Apology', 'relationship', 'transactional',\n 'A sincere apology for a problem (outage, mistake, late delivery): what happened, what was done to fix it, a gesture if any, and a contact. No marketing.'),\n\n // Business and organization\n purpose('hiring', \"We're hiring\", 'organization', 'marketing',\n 'Job openings: why join the team, 2 to 4 open roles (title, place, one line), and a \"See the jobs\" button.'),\n purpose('policy-update', 'Policy update', 'organization', 'transactional',\n 'Updated terms or privacy policy: the date it takes effect, the key changes in a short list, and a link to the full text. Neutral and clear.'),\n purpose('price-change', 'Price change', 'organization', 'transactional',\n 'A price change notice: old and new price, the effective date, why, and how to change plan or cancel. Honest and respectful.'),\n purpose('maintenance', 'Scheduled maintenance', 'organization', 'transactional',\n 'Planned maintenance or an incident: when, how long, what is affected, and where to follow the status.'),\n purpose('closure', 'Closure or opening hours', 'organization', 'marketing',\n 'Holiday closure or new opening hours: the dates and hours very clearly, what customers can still do, and wishes.'),\n purpose('donation-appeal', 'Donation appeal', 'organization', 'marketing',\n 'A fundraising appeal for a nonprofit: one concrete story, what a gift makes possible (amount examples), and a \"Donate\" button.'),\n purpose('team-update', 'Internal update', 'organization', 'transactional',\n 'An internal message to employees: news, dates to remember, and a contact. Plain and friendly.'),\n];\n\n/** @param {string|null|undefined} id */\nexport function purposeById(id) {\n return EMAIL_PURPOSES.find((entry) => entry.id === id) || null;\n}\n\n/** Purposes grouped by category, in category order. */\nexport function purposesByCategory(purposes = EMAIL_PURPOSES) {\n return PURPOSE_CATEGORIES.map((category) => ({\n ...category,\n purposes: purposes.filter((entry) => entry.category === category.id),\n })).filter((group) => group.purposes.length > 0);\n}\n","// @ts-check\n/**\n * REWRITE — one text of the email, rewritten by the model: shorter, longer,\n * fixed, friendlier, more formal, translated, or changed as the user says.\n *\n * The text is either the HTML of a text block (`html`) or the label of a\n * button (`plain`). What comes back must keep what the email depends on:\n *\n * links the same hrefs, no new one (the words around them may change);\n * variables the same `{{keys}}`, none added (a fallback after `|` may be\n * translated);\n * markup what the server's validator accepts in a text block.\n *\n * When one of them is lost, the model gets one more turn with the list; then\n * the rewrite fails and the text stays as it was.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { validateMjml } from '../validation/mjmlValidator.js';\nimport { EmailGenerationError, unescapedTwice } from './draft.js';\nimport { languageName } from './prompt.js';\n\n/**\n * The actions offered as buttons, in order. `translate` takes a language,\n * a custom change (`custom`) takes the user's words.\n */\nexport const REWRITE_ACTIONS = [\n { id: 'shorter', label: 'Shorter', change: 'Make it noticeably shorter, about half as long. Keep the meaning, the key facts and every link.' },\n {\n id: 'longer',\n label: 'Longer',\n change: 'Make it somewhat longer, about one and a half times, and more vivid. Develop what is there; invent no fact.',\n },\n {\n id: 'fix',\n label: 'Fix mistakes',\n change: 'Fix spelling, grammar and punctuation only. Change nothing else: same words, same tone, same length.',\n },\n { id: 'friendlier', label: 'Friendlier', change: 'Make the tone warmer and friendlier, as if talking to a customer you like. Same facts, about the same length.' },\n { id: 'formal', label: 'More formal', change: 'Make the tone more professional and formal. Same facts, about the same length.' },\n];\n\n/** Languages offered by \"Translate\", shown under their own name. */\nexport const REWRITE_LANGUAGES = ['en', 'fr', 'de', 'es', 'it', 'pt', 'nl', 'pl', 'uk', 'ru', 'tr', 'ja', 'zh'];\n\nexport const MAX_REWRITE_TEXT = 6000;\nexport const MAX_REWRITE_INSTRUCTION = 300;\n\nexport const REWRITE_SCHEMA = {\n type: 'object',\n properties: { text: { type: 'string', description: 'The rewritten text, nothing else.' } },\n required: ['text'],\n additionalProperties: false,\n};\n\nexport const REWRITE_SYSTEM_PROMPT = `You are an email copywriter. You rewrite ONE piece of text taken from an email, as asked, and answer with the rewritten text only, in the JSON field \"text\".\n\nThe text is one of:\n- HTML, the content of a text block: keep the same kind of markup. Allowed tags: <p>, <br>, <b>, <strong>, <i>, <em>, <u>, <s>, <span style=\"...\">, <a href=\"...\">, <ul>, <ol>, <li>, <sup>, <sub>. No other tag, no class, no new style. Keep bold, italic and links on the words they belong to.\n- plain text, the label of a button: answer plain text without HTML, a short call to action of a few words.\n\nAlways:\n- Keep every link: each <a href=\"...\"> of the text stays, with its href unchanged; the words of the link may change. Add no link.\n- Keep every merge tag in double braces exactly as written, e.g. {{contact.firstname}} or {{ unsubscribe }}. In {{key|fallback}} only the fallback after \"|\" may change (translated, for instance). Add no merge tag.\n- Keep [placeholders in square brackets]: the user replaces them later. Translate their words when translating.\n- Invent no facts: no new price, date, number, name, guarantee or promise.\n- Write in the language of the text, unless asked to translate.\n- No preamble, no comment, no quotation marks around the text.`;\n\n/** @param {unknown} value @param {number} max */\nconst bounded = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/** The words of an HTML fragment. @param {string} html */\nconst wordsOf = (html) =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/**\n * @typedef {{\n * action: string,\n * text: string,\n * format?: 'html'|'plain',\n * targetLanguage?: string,\n * instruction?: string,\n * }} RewriteInput\n * `action`: an id of REWRITE_ACTIONS, `translate` (with `targetLanguage`)\n * or `custom` (with `instruction`).\n */\n\n/**\n * The request as the rewriter uses it, or an error when there is nothing to do.\n * @param {RewriteInput} input\n */\nexport function cleanRewriteInput(input) {\n const source = input && typeof input === 'object' ? input : /** @type {RewriteInput} */ ({ action: '', text: '' });\n const format = /** @type {'html'|'plain'} */ (source.format === 'plain' ? 'plain' : 'html');\n const text = bounded(source.text, MAX_REWRITE_TEXT);\n if (!wordsOf(text)) throw new EmailGenerationError('NOTHING_TO_REWRITE');\n const preset = REWRITE_ACTIONS.find((action) => action.id === source.action);\n if (preset) return { format, text, change: preset.change };\n if (source.action === 'translate') {\n const language = String(source.targetLanguage || '');\n if (!/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(language)) throw new EmailGenerationError('NOTHING_TO_REWRITE', 'No target language.');\n return { format, text, change: `Translate it into ${languageName(language)}. Keep the tone and the length.` };\n }\n const instruction = bounded(source.instruction, MAX_REWRITE_INSTRUCTION);\n if (source.action === 'custom' && instruction) return { format, text, change: instruction };\n throw new EmailGenerationError('NOTHING_TO_REWRITE', 'Unknown action.');\n}\n\n/** @param {{format: 'html'|'plain', text: string, change: string}} request */\nexport function buildRewritePrompt({ format, text, change }) {\n return [\n `Change: ${change}`,\n `Format: ${format === 'plain' ? 'plain text (a button label)' : 'HTML (a text block)'}`,\n '',\n '<text>',\n text,\n '</text>',\n ].join('\\n');\n}\n\n/** The text in a model's answer: the JSON field, or the bare answer. @param {string} answer */\nexport function parseRewrite(answer) {\n const source = String(answer ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n if (unfenced.startsWith('{')) {\n try {\n const data = JSON.parse(unfenced);\n if (typeof data?.text === 'string') return unescapedTwice(data.text.trim());\n } catch {\n // not JSON after all: the text itself\n }\n }\n return unfenced.replace(/^<text>\\s*/, '').replace(/\\s*<\\/text>$/, '');\n}\n\n/** @param {string} html */\nconst hrefsOf = (html) => [...html.matchAll(/<a\\b[^>]*?\\shref\\s*=\\s*([\"'])(.*?)\\1/gi)].map((match) => match[2].trim());\n/** @param {string} html */\nconst variableKeysOf = (html) => [...html.matchAll(/\\{\\{\\s*([^}|]+?)\\s*(?:\\|[^}]*)?\\}\\}/g)].map((match) => match[1]);\n\n/**\n * What is wrong with a rewritten text, for the model (empty: usable).\n * @param {string} original @param {string} rewritten @param {'html'|'plain'} format\n */\nexport function reviewRewrite(original, rewritten, format) {\n /** @type {string[]} */\n const problems = [];\n if (!wordsOf(rewritten)) return ['The answer is empty.'];\n const before = new Set(variableKeysOf(original));\n const after = new Set(variableKeysOf(rewritten));\n for (const key of before) if (!after.has(key)) problems.push(`The merge tag {{${key}}} is missing: keep it.`);\n for (const key of after) if (!before.has(key)) problems.push(`The merge tag {{${key}}} was added: remove it.`);\n if (format === 'plain') return problems;\n const links = new Set(hrefsOf(original));\n const kept = new Set(hrefsOf(rewritten));\n for (const href of links) if (!kept.has(href)) problems.push(`The link to ${href} is missing: keep it with the same href.`);\n for (const href of kept) if (!links.has(href)) problems.push(`The link to ${href} was added: remove it.`);\n const wrapped = `<mjml><mj-body><mj-section><mj-column><mj-text>${rewritten}</mj-text></mj-column></mj-section></mj-body></mjml>`;\n for (const issue of validateMjml(wrapped).issues) problems.push(issue.message);\n return problems;\n}\n\n/**\n * @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options\n * @returns {(input: RewriteInput, options?: {signal?: AbortSignal}) => Promise<{text: string, attempts: number}>}\n */\nexport function createRewriter({ model, maxAttempts = 2 }) {\n return async function rewrite(input, { signal } = {}) {\n const request = cleanRewriteInput(input);\n /** @type {import('./generator.js').ModelMessage[]} */\n const messages = [{ role: 'user', content: buildRewritePrompt(request) }];\n let lastProblem = '';\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n let answer;\n try {\n answer = await model({ system: REWRITE_SYSTEM_PROMPT, messages, schema: REWRITE_SCHEMA, signal, task: 'rewrite' });\n } catch (error) {\n if (error instanceof EmailGenerationError && error.code === 'REFUSED') throw new EmailGenerationError('REWRITE_REFUSED');\n throw error;\n }\n let text = parseRewrite(answer);\n // A label: the words alone, whatever markup came with them.\n if (request.format === 'plain') text = wordsOf(text).slice(0, 120);\n const problems = reviewRewrite(request.text, text, request.format);\n if (!problems.length) return { text, attempts: attempt };\n lastProblem = problems[0];\n messages.push(\n { role: 'assistant', content: String(answer) },\n { role: 'user', content: `Fix these problems and answer the whole text again:\\n${problems.map((p) => `- ${p}`).join('\\n')}` }\n );\n }\n throw new EmailGenerationError('REWRITE_UNUSABLE', lastProblem);\n };\n}\n","// @ts-check\n/**\n * SUBJECT IDEAS — three other subject lines for an email, drawn from its words.\n *\n * The model gets what the inbox shows (the subject and the preview text) and\n * the words of the email, and answers three subjects that take three\n * different angles. What comes back is one line each, different from the\n * current subject and from each other, with no merge tag the catalog does not\n * know; when fewer than three are left, the model gets one more turn with the\n * list of problems.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { EmailGenerationError } from './draft.js';\nimport { languageName } from './prompt.js';\n\nexport const SUBJECT_COUNT = 3;\nexport const MAX_SUBJECT_LENGTH = 150;\nexport const MAX_EMAIL_WORDS = 4000;\nconst MAX_VARIABLES = 60;\n\n/**\n * @typedef {{\n * subject?: string,\n * preheader?: string,\n * text?: string,\n * language?: string,\n * variables?: {key: string, label?: string, kind?: string}[],\n * avoid?: string[],\n * }} SubjectsInput\n * `text`: the words of the email (`emailWords`); `language`: the one of the\n * interface, used when the email has no words yet and its subject does not\n * tell; `variables`: a link variable (`kind: 'link'`) is never offered;\n * `avoid`: ideas already shown (\"More ideas\" asks for new ones).\n */\n\nexport const SUBJECTS_SCHEMA = {\n type: 'object',\n properties: {\n subjects: {\n type: 'array',\n description: 'Exactly three subject lines, plain text.',\n items: { type: 'string' },\n },\n },\n required: ['subjects'],\n additionalProperties: false,\n};\n\nexport const SUBJECTS_SYSTEM_PROMPT = `You write email subject lines. You get an email (its current subject, its preview text and its words) and answer three NEW subject lines for it, in the JSON field \"subjects\".\n\nEach subject:\n- tells what the email brings, specific to its content: under 50 characters when possible, 70 at most;\n- takes its own angle: one clear and direct, one that makes the reader curious, one personal or timely;\n- is written in the language of the email;\n- invents no facts: no price, discount, date, number, name or promise the email does not contain;\n- is plain text: no HTML, no quotation marks around it, no line break;\n- has no word in capitals, at most one \"!\", no spam wording (\"free!!!\", \"act now\", \"100% guaranteed\");\n- has no emoji, unless the current subject has one;\n- differs from the current subject and from the two others, and does not repeat the preview text.\n\nMerge tags: you may personalise ONE of the three with a merge tag from the list you are given, written exactly as listed, e.g. {{contact.firstname}}. No other merge tag.`;\n\n/** @param {unknown} value @param {number} max */\nconst bounded = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/** Quotes that wrap a whole subject, opening → closing. */\nconst WRAPPING_QUOTES = { '\"': '\"', \"'\": \"'\", '«': '»', '“': '”', '„': '“', '‘': '’', '‹': '›' };\n\n/** The HTML a model may wrap words in; `<live>` in a subject is a word. */\nconst MARKUP = /<\\/?(?:a|abbr|b|big|br|code|del|div|em|font|h[1-6]|i|ins|li|mark|ol|p|q|s|small|span|strike|strong|sub|sup|u|ul)\\b[^<>]*>/gi;\n\n/**\n * One line of plain text: markup tags removed (not a lone `<` or `>`), quotes\n * removed only when one pair wraps the whole line.\n * @param {unknown} value\n */\nconst line = (value) => {\n let text = String(value ?? '')\n .replace(MARKUP, '')\n .replace(/\\s+/g, ' ')\n .trim();\n const close = WRAPPING_QUOTES[/** @type {keyof typeof WRAPPING_QUOTES} */ (text[0])];\n if (close && text.length > 2 && text.endsWith(close) && !text.slice(1, -1).includes(close) && !text.slice(1, -1).includes(text[0])) {\n text = text.slice(1, -1).trim();\n }\n return text.slice(0, MAX_SUBJECT_LENGTH);\n};\n\n/** Named entities emails use; the others are left as written. */\nconst ENTITIES = {\n amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: ' ',\n rsquo: '’', lsquo: '‘', rdquo: '”', ldquo: '“', laquo: '«', raquo: '»', bdquo: '„', sbquo: '‚',\n hellip: '…', mdash: '—', ndash: '–', middot: '·', bull: '•', copy: '©', reg: '®', trade: '™',\n euro: '€', pound: '£', yen: '¥', cent: '¢', deg: '°', times: '×', shy: '',\n eacute: 'é', egrave: 'è', ecirc: 'ê', euml: 'ë', agrave: 'à', aacute: 'á', acirc: 'â', auml: 'ä', atilde: 'ã', aring: 'å',\n ccedil: 'ç', iacute: 'í', igrave: 'ì', icirc: 'î', iuml: 'ï', oacute: 'ó', ograve: 'ò', ocirc: 'ô', ouml: 'ö', otilde: 'õ', oslash: 'ø',\n uacute: 'ú', ugrave: 'ù', ucirc: 'û', uuml: 'ü', ntilde: 'ñ', szlig: 'ß', aelig: 'æ', oelig: 'œ', yuml: 'ÿ',\n Eacute: 'É', Egrave: 'È', Ecirc: 'Ê', Agrave: 'À', Aacute: 'Á', Acirc: 'Â', Auml: 'Ä', Ccedil: 'Ç', Ocirc: 'Ô', Ouml: 'Ö',\n Uuml: 'Ü', Ntilde: 'Ñ', OElig: 'Œ', AElig: 'Æ', iexcl: '¡', iquest: '¿',\n};\n\n/** One entity, decoded once; an unknown or impossible one as written. @param {string} whole @param {string} body */\nconst entity = (whole, body) => {\n if (body[0] !== '#') return Object.hasOwn(ENTITIES, body) ? ENTITIES[/** @type {keyof typeof ENTITIES} */ (body)] : whole;\n const code = /^#x/i.test(body) ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);\n return Number.isInteger(code) && code > 0 && code <= 0x10ffff && !(code >= 0xd800 && code <= 0xdfff) ? String.fromCodePoint(code) : whole;\n};\n\n/**\n * The words of an email, as a reader sees them: the head, the styles and the\n * markup left out.\n * @param {string} mjml\n */\nexport function emailWords(mjml) {\n return String(mjml || '')\n .replace(/<mj-head[\\s\\S]*?<\\/mj-head>/gi, ' ')\n .replace(/<(style|script|mj-style|mj-raw)[\\s\\S]*?<\\/\\1>/gi, ' ')\n .replace(/<\\/(p|div|li|h[1-6]|mj-text|mj-button|mj-navbar-link|mj-social-element)>|<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]*>/g, ' ')\n // One pass: `&#65;` stays `A`, as the reader sees it.\n .replace(/&(#\\d{1,8}|#x[0-9a-f]{1,8}|[a-z]{2,8});/gi, entity)\n .replace(/[ \\t]+/g, ' ')\n .replace(/\\s*\\n\\s*/g, '\\n')\n .trim()\n .slice(0, MAX_EMAIL_WORDS);\n}\n\n/**\n * The request as the model sees it, or an error when there is nothing to\n * draw subjects from.\n * @param {SubjectsInput} input\n */\nexport function cleanSubjectsInput(input) {\n const source = input && typeof input === 'object' ? input : {};\n const subject = line(source.subject);\n const preheader = bounded(source.preheader, 300).replace(/\\s+/g, ' ');\n const text = bounded(source.text, MAX_EMAIL_WORDS);\n if (!subject && !text) throw new EmailGenerationError('NOTHING_TO_SUGGEST');\n const language = /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i.test(String(source.language || '')) ? String(source.language).toLowerCase() : 'en';\n const variables = (Array.isArray(source.variables) ? source.variables : DEFAULT_VARIABLES)\n .filter((variable) => variable && /^[A-Za-z][\\w.]*$/.test(String(variable.key)) && variable.key !== 'unsubscribe')\n .filter((variable) => /** @type {any} */ (variable).kind !== 'link')\n .slice(0, MAX_VARIABLES)\n .map((variable) => ({ key: String(variable.key), label: bounded(variable.label, 80) }));\n const avoid = (Array.isArray(source.avoid) ? source.avoid : []).map(line).filter(Boolean).slice(0, 12);\n return { subject, preheader, text, language, variables, avoid };\n}\n\n/** @param {ReturnType<typeof cleanSubjectsInput>} request */\nexport function buildSubjectsPrompt({ subject, preheader, text, language, variables, avoid }) {\n return [\n `Current subject: ${subject || '(none yet)'}`,\n `Preview text: ${preheader || '(none)'}`,\n avoid.length ? `Already suggested, do not repeat: ${avoid.map((idea) => `\"${idea}\"`).join(', ')}` : null,\n `Merge tags you may use: ${variables.length ? variables.map((v) => `{{${v.key}}}${v.label ? ` (${v.label})` : ''}`).join(', ') : 'none'}`,\n text ? null : `The email has no words yet: write in the language of its subject (${languageName(language)} if the subject does not tell).`,\n '',\n '<email>',\n text,\n '</email>',\n ]\n .filter((row) => row !== null)\n .join('\\n');\n}\n\n/**\n * A capital letter first, as a sentence starts — unless the first word is\n * styled that way on purpose (iPhone, eBay). An opening ¿ or « is skipped.\n * @param {string} subject @param {string} [language]\n */\nconst capitalized = (subject, language) => {\n const at = /^[¿¡«\"“„'‘(\\s]*/.exec(subject)?.[0].length ?? 0;\n const first = subject.slice(at).split(/\\s/)[0];\n if (!/^\\p{Ll}/u.test(first) || /\\p{Lu}/u.test(first)) return subject;\n let upper;\n try {\n upper = subject[at].toLocaleUpperCase(language);\n } catch {\n upper = subject[at].toUpperCase();\n }\n return subject.slice(0, at) + upper + subject.slice(at + 1);\n};\n\n/**\n * A line of prose that is not a subject: an introduction (\"Here are three\n * subjects:\"), JSON punctuation, a line with no word.\n * @param {string} row\n */\nconst notASubject = (row) => {\n const words = row.replace(/\\{\\{[^{}]*\\}\\}/g, '');\n return /[::]$/.test(row) || (words.match(/\\p{L}/gu) || []).length < 2 || /^[[\\]{}]|[{}[\\]],?$/.test(words);\n};\n\n/**\n * The subjects in a model's answer: the strings of its JSON field\n * `subjects`; an answer that is no JSON at all, one per line (list marks,\n * quotes and introductions left out). JSON without that field gives none —\n * the model is asked again.\n * @param {string} answer @param {string} [language] the email's, for the capital letter\n */\nexport function parseSubjects(answer, language) {\n const source = String(answer ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n const shape = (/** @type {unknown[]} */ items) =>\n items.filter((item) => typeof item === 'string').map((subject) => capitalized(line(subject), language));\n const start = unfenced.indexOf('{');\n if (start !== -1) {\n const end = unfenced.lastIndexOf('}');\n try {\n const data = JSON.parse(unfenced.slice(start, end + 1));\n return Array.isArray(data?.subjects) ? shape(data.subjects) : [];\n } catch {\n return [];\n }\n }\n const rows = unfenced.split('\\n').map((row) => row.trim()).filter(Boolean);\n const marked = /^(?:[-*•]|\\d+[.)])\\s+/;\n // A list among sentences: only the list.\n const listed = rows.filter((row) => marked.test(row));\n return shape((listed.length ? listed : rows).map((row) => line(row.replace(marked, ''))).filter((row) => !notASubject(row)));\n}\n\n/** @param {string} text */\nconst keysOf = (text) => [...text.matchAll(/\\{\\{\\s*([^}|]+?)\\s*(?:\\|[^}]*)?\\}\\}/g)].map((match) => match[1]);\n\n/** Merge tags written for other tools: Mailchimp, Sendinblue, single braces. */\nconst FOREIGN_TAG = /\\*\\|[^|]+\\|\\*|%[A-Za-z_][\\w.]*%|(?<!\\{)\\{[A-Za-z_][\\w.]*\\}(?!\\})/;\n/** Longer than any inbox shows. */\nexport const MAX_SUBJECT_SHOWN = 90;\n\n/**\n * The usable subjects, and what is wrong with the others (for the model).\n * Left out: a repeat (of the current subject, the preview text, an idea\n * already shown or another one), a merge tag the list does not have or one\n * written for another tool, a word in capitals the email does not have, more\n * than one \"!\", a subject longer than an inbox shows.\n * @param {string[]} subjects @param {ReturnType<typeof cleanSubjectsInput>} request\n */\nexport function reviewSubjects(subjects, request) {\n const known = new Set(request.variables.map((variable) => variable.key));\n const seen = new Set([request.subject, request.preheader, ...request.avoid].filter(Boolean).map((subject) => subject.toLocaleLowerCase()));\n const source = `${request.subject}\\n${request.preheader}\\n${request.text}`;\n /** @type {string[]} */\n const usable = [];\n /** @type {string[]} */\n const problems = [];\n for (const subject of subjects) {\n if (!subject) continue;\n const unknown = keysOf(subject).filter((key) => !known.has(key));\n const foreign = FOREIGN_TAG.exec(subject)?.[0];\n const shouting = (subject.replace(/\\{\\{[^}]*\\}\\}/g, '').match(/\\b\\p{Lu}{4,}\\b/gu) || []).find((word) => !source.includes(word));\n const problem = unknown.length\n ? `\"${subject}\" uses {{${unknown[0]}}}, which is not in the list of merge tags.`\n : foreign\n ? `\"${subject}\" writes ${foreign}: merge tags are written {{key}}, from the list only.`\n : subject.length > MAX_SUBJECT_SHOWN\n ? `\"${subject}\" is ${subject.length} characters long: 70 at most.`\n : shouting\n ? `\"${subject}\" writes ${shouting} in capitals.`\n : (subject.match(/!/g) || []).length > 1\n ? `\"${subject}\" has more than one \"!\".`\n : seen.has(subject.toLocaleLowerCase())\n ? `\"${subject}\" repeats the current subject, the preview text or another one.`\n : null;\n if (problem) {\n problems.push(problem);\n continue;\n }\n seen.add(subject.toLocaleLowerCase());\n usable.push(subject);\n }\n if (usable.length < SUBJECT_COUNT && !problems.length) problems.push(`Give exactly ${SUBJECT_COUNT} subjects, in the JSON field \"subjects\".`);\n return { usable: usable.slice(0, SUBJECT_COUNT), problems: usable.length >= SUBJECT_COUNT ? [] : problems };\n}\n\n/**\n * @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options\n * @returns {(input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[], attempts: number}>}\n */\nexport function createSubjectSuggester({ model, maxAttempts = 2 }) {\n return async function suggestSubjects(input, { signal } = {}) {\n const request = cleanSubjectsInput(input);\n /** @type {import('./generator.js').ModelMessage[]} */\n const messages = [{ role: 'user', content: buildSubjectsPrompt(request) }];\n /** @type {string[]} */\n let best = [];\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n let answer;\n try {\n answer = await model({ system: SUBJECTS_SYSTEM_PROMPT, messages, schema: SUBJECTS_SCHEMA, signal, task: 'subjects' });\n } catch (error) {\n if (error instanceof EmailGenerationError && error.code === 'REFUSED') throw new EmailGenerationError('SUBJECTS_REFUSED');\n throw error;\n }\n const { usable, problems } = reviewSubjects(parseSubjects(answer, request.language), request);\n if (usable.length > best.length) best = usable;\n if (!problems.length) return { subjects: usable, attempts: attempt };\n messages.push(\n { role: 'assistant', content: String(answer) },\n { role: 'user', content: `Fix these problems and answer the three subjects again:\\n${problems.map((p) => `- ${p}`).join('\\n')}` }\n );\n }\n // Fewer than three good ones is still worth showing.\n if (best.length) return { subjects: best, attempts: Math.max(1, maxAttempts) };\n throw new EmailGenerationError('SUBJECTS_UNUSABLE');\n };\n}\n","// French texts of the AI module. Keys are the English source strings (see src/i18n/index.js);\n// the core's texts are in src/i18n/fr.js. Missing keys: `node scripts/i18n-keys.mjs fr`.\n\n/** @type {Record<string, string>} */\nexport const fr = {\n \"Event invitation\": \"Invitation\",\n \"Newsletter\": \"Newsletter\",\n \"Welcome\": \"Bienvenue\",\n \"Generate\": \"Générer\",\n \"Generate an email\": \"Générer un e-mail\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Choisissez à quoi il sert. L'IA écrit un e-mail complet qui s'ouvre ici, prêt à modifier.\",\n \"Search\": \"Rechercher\",\n \"Search: sale, password, birthday…\": \"Rechercher : soldes, mot de passe, anniversaire…\",\n \"What is the email for?\": \"À quoi sert l'e-mail ?\",\n \"Nothing matches. Describe your email in the details below.\": \"Aucun résultat. Décrivez votre e-mail dans les précisions ci-dessous.\",\n \"Details (optional)\": \"Précisions (facultatif)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Précisions (facultatif) : produit, dates, offre, ton, langue…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"L'e-mail en cours sera remplacé, modifications non enregistrées comprises.\",\n \"Writing your email… {seconds} s\": \"Rédaction de votre e-mail… {seconds} s\",\n \"Your email is ready. Edit anything, then save.\": \"Votre e-mail est prêt. Modifiez ce que vous voulez, puis enregistrez.\",\n \"The email could not be generated.\": \"L'e-mail n'a pas pu être généré.\",\n \"Pick what the email is for, or describe it.\": \"Choisissez à quoi sert l'e-mail, ou décrivez-le.\",\n \"The AI declined to write this email. Try other details.\": \"L'IA a refusé d'écrire cet e-mail. Essayez d'autres précisions.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"L'e-mail était trop long pour être terminé. Demandez-en un plus court.\",\n \"The AI answer could not be read. Try again.\": \"La réponse de l'IA est illisible. Réessayez.\",\n \"The generated email could not be used. Try again.\": \"L'e-mail généré est inutilisable. Réessayez.\",\n \"Sales and promotions\": \"Ventes et promotions\",\n \"Holidays and occasions\": \"Fêtes et occasions\",\n \"Events and appointments\": \"Événements et rendez-vous\",\n \"News and content\": \"Actualités et contenus\",\n \"Account and security\": \"Compte et sécurité\",\n \"Orders and payments\": \"Commandes et paiements\",\n \"Customer relationship\": \"Relation client\",\n \"Business and organization\": \"Entreprise et organisation\",\n \"Store sale\": \"Soldes en boutique\",\n \"Flash sale\": \"Vente flash\",\n \"Discount code\": \"Code promo\",\n \"New product\": \"Nouveau produit\",\n \"Back in stock\": \"De retour en stock\",\n \"Price drop\": \"Baisse de prix\",\n \"Abandoned cart\": \"Panier abandonné\",\n \"Black Friday\": \"Black Friday\",\n \"Loyalty reward\": \"Récompense fidélité\",\n \"Refer a friend\": \"Parrainage\",\n \"Christmas\": \"Noël\",\n \"New Year\": \"Nouvel An\",\n \"Season's greetings\": \"Vœux de fin d'année\",\n \"Valentine's Day\": \"Saint-Valentin\",\n \"Easter\": \"Pâques\",\n \"Mother's Day\": \"Fête des mères\",\n \"Father's Day\": \"Fête des pères\",\n \"Halloween\": \"Halloween\",\n \"Back to school\": \"Rentrée\",\n \"Summer holidays\": \"Vacances d'été\",\n \"Birthday wishes\": \"Joyeux anniversaire\",\n \"Customer anniversary\": \"Anniversaire client\",\n \"Women's Day\": \"Journée des femmes\",\n \"Thanksgiving\": \"Thanksgiving\",\n \"Lunar New Year\": \"Nouvel An lunaire\",\n \"Ramadan and Eid\": \"Ramadan et Aïd\",\n \"Diwali\": \"Diwali\",\n \"Meeting reminder\": \"Rappel de réunion\",\n \"Appointment confirmation\": \"Confirmation de rendez-vous\",\n \"Booking confirmation\": \"Confirmation de réservation\",\n \"Webinar invitation\": \"Invitation à un webinaire\",\n \"Save the date\": \"Save the date\",\n \"Registration confirmed\": \"Inscription confirmée\",\n \"Cancellation or new date\": \"Annulation ou report\",\n \"Thanks for attending\": \"Merci de votre participation\",\n \"New article\": \"Nouvel article\",\n \"What's new\": \"Nouveautés\",\n \"Announcement\": \"Annonce\",\n \"Tips and how-to\": \"Conseils et tutoriels\",\n \"Email verification\": \"Vérification de l'e-mail\",\n \"Password reset\": \"Réinitialisation du mot de passe\",\n \"Login code\": \"Code de connexion\",\n \"Security alert\": \"Alerte de sécurité\",\n \"Getting started\": \"Premiers pas\",\n \"Trial ending\": \"Fin de l'essai\",\n \"Order confirmation\": \"Confirmation de commande\",\n \"Order shipped\": \"Commande expédiée\",\n \"Delivery delay\": \"Retard de livraison\",\n \"Receipt or invoice\": \"Reçu ou facture\",\n \"Payment failed\": \"Échec du paiement\",\n \"Subscription renewal\": \"Renouvellement d'abonnement\",\n \"Refund issued\": \"Remboursement effectué\",\n \"Thank you\": \"Remerciements\",\n \"Review request\": \"Demande d'avis\",\n \"Survey\": \"Enquête de satisfaction\",\n \"We miss you\": \"Vous nous manquez\",\n \"Follow-up\": \"Relance\",\n \"Apology\": \"Excuses\",\n \"We're hiring\": \"On recrute\",\n \"Policy update\": \"Mise à jour des conditions\",\n \"Price change\": \"Changement de prix\",\n \"Scheduled maintenance\": \"Maintenance planifiée\",\n \"Closure or opening hours\": \"Fermeture ou horaires\",\n \"Donation appeal\": \"Appel aux dons\",\n \"Internal update\": \"Communication interne\",\n \"Rewrite with AI\": \"Réécrire avec l'IA\",\n \"Shorter\": \"Plus court\",\n \"Longer\": \"Plus long\",\n \"Fix mistakes\": \"Corriger les fautes\",\n \"Friendlier\": \"Plus chaleureux\",\n \"More formal\": \"Plus formel\",\n \"Translate\": \"Traduire\",\n \"Translate into\": \"Traduire en\",\n \"Or say what to change…\": \"Ou dites ce qu'il faut changer…\",\n \"What to change\": \"Ce qu'il faut changer\",\n \"Rewrite\": \"Réécrire\",\n \"Rewriting…\": \"Réécriture…\",\n \"Suggest subjects\": \"Suggérer des objets\",\n \"Subject ideas\": \"Idées d'objet\",\n \"More ideas\": \"Autres idées\",\n \"Done\": \"Terminé\",\n \"yours\": \"le vôtre\",\n \"Reading the email…\": \"Lecture de l'e-mail…\",\n \"No subject could be suggested.\": \"Aucun objet n'a pu être suggéré.\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Rédigez d'abord l'e-mail ou un objet : les idées en sont tirées.\",\n \"The AI declined to suggest subjects for this email.\": \"L'IA a refusé de suggérer des objets pour cet e-mail.\",\n \"No usable subject came back. Try again.\": \"Aucun objet utilisable n'a été proposé. Réessayez.\",\n \"Connect an AI provider first: it needs a key.\": \"Connectez d'abord un fournisseur d'IA : il faut une clé.\",\n \"The AI provider refused the key. Check it, or use another one.\": \"Le fournisseur d'IA a refusé la clé. Vérifiez-la ou utilisez-en une autre.\",\n \"The AI provider account has no credits left.\": \"Le compte du fournisseur d'IA n'a plus de crédits.\",\n \"The AI is busy right now. Try again in a minute.\": \"L'IA est occupée pour le moment. Réessayez dans une minute.\",\n \"The AI provider could not answer. Try again.\": \"Le fournisseur d'IA n'a pas pu répondre. Réessayez.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Texte réécrit. Ctrl+Z rétablit le précédent.\",\n \"The text changed in the meantime, so it was left as it is.\": \"Le texte a changé entre-temps : il a été laissé tel quel.\",\n \"The text could not be rewritten.\": \"Le texte n'a pas pu être réécrit.\",\n \"There is no text to rewrite.\": \"Il n'y a pas de texte à réécrire.\",\n \"The AI declined to rewrite this text.\": \"L'IA a refusé de réécrire ce texte.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"Le texte réécrit avait perdu un lien ou une variable : rien n'a été modifié. Réessayez.\",\n \"Connect {provider}\": \"Connecter {provider}\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"L'IA écrit avec votre propre clé {provider}. Connectez-la une fois : ce navigateur s'en souvient.\",\n \"{provider} refused the last key. Paste another one.\": \"{provider} a refusé la dernière clé. Collez-en une autre.\",\n \"Get a key\": \"Obtenir une clé\",\n \"API key\": \"Clé API\",\n \"Paste the key\": \"Collez la clé\",\n \"Hide the key\": \"Masquer la clé\",\n \"Show the key\": \"Afficher la clé\",\n \"{provider} keys usually start with {prefix}.\": \"Les clés {provider} commencent d'habitude par {prefix}.\",\n \"{provider} keys start with {prefix}.\": \"Les clés {provider} commencent par {prefix}.\",\n \"The key stays in this browser and is sent only to {provider}.\": \"La clé reste dans ce navigateur et n'est envoyée qu'à {provider}.\",\n \"Connect\": \"Connecter\",\n \"Free models, with a free account\": \"Modèles gratuits, avec un compte gratuit\",\n \"Free tier, with rate limits\": \"Offre gratuite, avec des limites de débit\",\n \"Free \\\"Experiment\\\" plan\": \"Offre gratuite « Experiment »\",\n \"Runs on your own machine\": \"Tourne sur votre propre machine\",\n \"AI provider\": \"Fournisseur d'IA\",\n \"Key saved\": \"Clé enregistrée\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"Une clé {provider} est enregistrée dans ce navigateur : Connecter l'utilise, ou collez-en une autre.\",\n};\n","// @ts-check\n/**\n * The AI module's own texts, by locale: merged under the host's `messages`\n * when the builder is given the module (`createAi`), and under the server's\n * when the handler is given a generator (`createEmailGenerator`). The core's\n * dictionaries (src/i18n/) do not carry them.\n */\n\nimport { fr } from './fr.js';\n\n/** @type {Record<string, Record<string, string>>} */\nexport const AI_MESSAGES = { en: {}, fr };\n","// @ts-check\n/**\n * EMAIL GENERATOR — from a purpose (and optional details) to an email the\n * builder opens block by block.\n *\n * const generator = createEmailGenerator({ model: claudeEmailModel({ client: new Anthropic() }) });\n * const { name, subject, preheader, mjml } = await generator.generate({ purpose: 'password-reset', language: 'fr' });\n *\n * The MODEL is any function `({system, messages, schema, signal}) => text`:\n * `claudeEmailModel` (claude.js) for Claude, or a few lines of your own for\n * another provider. Its answer is read, cleaned (`draft.js`) and checked; when\n * a problem remains, the model gets ONE more turn with the list of problems\n * (`maxAttempts`, 2 by default) — a slow model call is the cost of a retry.\n *\n * Never trusted as is: the MJML that comes out has passed `validateMjml`, the\n * same check the server runs before compiling and saving.\n *\n * The same model also REWRITES one text of an email (`rewrite`, see\n * rewrite.js): shorter, longer, fixed, another tone, translated; and SUGGESTS\n * three other subject lines (`suggestSubjects`, see subjects.js).\n */\n\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { isSafeUrl } from '../validation/urls.js';\nimport { EmailGenerationError, normalizeGeneratedMjml, parseDraft, reviewDraft, withoutIllegalAttributes } from './draft.js';\nimport { applyBrand, cleanBrand } from '../features/brand/brand.js';\nimport { EMAIL_DRAFT_SCHEMA, SYSTEM_PROMPT, buildRepairPrompt, buildUserPrompt } from './prompt.js';\nimport { purposeById } from './purposes.js';\nimport { createRewriter } from './rewrite.js';\nimport { createSubjectSuggester } from './subjects.js';\nimport { AI_MESSAGES } from './i18n/index.js';\n\n/**\n * @typedef {{role: 'user'|'assistant', content: string}} ModelMessage\n * @typedef {(request: {system: string, messages: ModelMessage[], schema: object, signal?: AbortSignal, task?: 'generate'|'rewrite'|'subjects'}) => Promise<string>} EmailModel\n * `task`: a whole email, one text rewritten or subject ideas (lighter jobs:\n * a model may answer them with less effort).\n *\n * @typedef {{\n * purpose?: string|null,\n * details?: string,\n * language?: string,\n * sender?: {name?: string, address?: string, logoUrl?: string},\n * brand?: {primary?: string, heading?: string, font?: string},\n * variables?: {key: string, label?: string}[],\n * }} GenerateInput\n * `brand`: the colors and font the email is written in (features/brand).\n *\n * @typedef {import('./draft.js').EmailDraft & {warnings: string[], attempts: number}} GeneratedEmail\n * @typedef {import('./rewrite.js').RewriteInput} RewriteInput\n * @typedef {import('./subjects.js').SubjectsInput} SubjectsInput\n */\n\nexport const MAX_DETAILS_LENGTH = 2000;\nconst MAX_VARIABLES = 200;\n\n/** @param {unknown} value @param {number} max */\nconst text = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/**\n * The input as the generator uses it: bounded strings, a known purpose or\n * none, an https logo or none, the brand's colors and font or none.\n * @param {GenerateInput} input\n */\nexport function cleanGenerateInput(input) {\n const source = input && typeof input === 'object' ? input : {};\n const purpose = purposeById(typeof source.purpose === 'string' ? source.purpose : null);\n const details = text(source.details, MAX_DETAILS_LENGTH);\n const language = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(String(source.language || '')) ? String(source.language) : 'en';\n const sender = source.sender && typeof source.sender === 'object' ? source.sender : {};\n const logoUrl = text(sender.logoUrl, 2000);\n const variables = (Array.isArray(source.variables) ? source.variables : DEFAULT_VARIABLES)\n .filter((variable) => variable && /^[A-Za-z][\\w.]*$/.test(String(variable.key)))\n .slice(0, MAX_VARIABLES)\n .map((variable) => ({ key: String(variable.key), label: text(variable.label, 80) }));\n const { primary, heading, font } = cleanBrand(source.brand);\n return {\n purpose,\n details,\n language,\n sender: {\n name: text(sender.name, 120),\n address: text(sender.address, 300),\n logoUrl: logoUrl && /^https:\\/\\//i.test(logoUrl) && isSafeUrl(logoUrl, 'image') ? logoUrl : '',\n },\n brand: { ...(primary ? { primary } : {}), ...(heading ? { heading } : {}), ...(font ? { font } : {}) },\n variables,\n };\n}\n\n/**\n * @param {{\n * model: EmailModel,\n * maxAttempts?: number,\n * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,\n * }} options\n * `compile`: compiles the MJML and returns MJML's own warnings, fed back to\n * the model (the server passes `compileMjml`).\n */\nexport function createEmailGenerator({ model, maxAttempts = 2, compile } = /** @type {any} */ ({})) {\n if (typeof model !== 'function') throw new Error('createEmailGenerator: `model` must be a function');\n\n /**\n * @param {GenerateInput} input\n * @param {{signal?: AbortSignal}} [options]\n * @returns {Promise<GeneratedEmail>}\n */\n async function generate(input, { signal } = {}) {\n const request = cleanGenerateInput(input);\n if (!request.purpose && !request.details) {\n throw new EmailGenerationError('NOTHING_TO_GENERATE');\n }\n const kind = request.purpose?.kind || 'marketing';\n const keepImages = request.sender.logoUrl ? [request.sender.logoUrl] : [];\n\n /** @type {ModelMessage[]} */\n const messages = [{ role: 'user', content: buildUserPrompt(request) }];\n /** @type {GeneratedEmail|null} */\n let usable = null;\n let lastProblem = '';\n\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n const answer = await model({ system: SYSTEM_PROMPT, messages, schema: EMAIL_DRAFT_SCHEMA, signal, task: 'generate' });\n /** @type {string[]} */\n let problems;\n try {\n const draft = parseDraft(answer);\n draft.mjml = normalizeGeneratedMjml(draft.mjml, { variables: request.variables, keepImages });\n // The model was told the brand; whatever it chose instead is repainted.\n draft.mjml = applyBrand(draft.mjml, request.brand);\n let compileWarnings = compile ? (await compile(draft.mjml)).warnings || [] : [];\n const legal = withoutIllegalAttributes(draft.mjml, compileWarnings);\n if (legal && compile) {\n draft.mjml = legal;\n compileWarnings = (await compile(draft.mjml)).warnings || [];\n }\n const { blocking, advice } = reviewDraft(draft, { kind, compileWarnings });\n if (!blocking.length) {\n usable = { ...draft, warnings: advice, attempts: attempt };\n if (!advice.length) return usable;\n }\n problems = [...blocking, ...advice];\n } catch (error) {\n if (!(error instanceof EmailGenerationError)) throw error;\n problems = [error.detail];\n }\n lastProblem = problems[0];\n messages.push({ role: 'assistant', content: String(answer) }, { role: 'user', content: buildRepairPrompt(problems) });\n }\n\n if (usable) return usable;\n throw new EmailGenerationError('INVALID_OUTPUT', lastProblem);\n }\n\n return {\n generate,\n rewrite: createRewriter({ model, maxAttempts }),\n suggestSubjects: createSubjectSuggester({ model, maxAttempts }),\n // Its error messages in the server's language (the handler merges them).\n messages: AI_MESSAGES,\n };\n}\n","// @ts-check\n/**\n * AI PROVIDERS — the email model of `createEmailGenerator` from a provider's\n * key, no SDK to install.\n *\n * providerEmailModel({ provider: 'openrouter', apiKey })\n * providerEmailModel({ provider: 'groq', apiKey, model: 'openai/gpt-oss-120b' })\n * providerEmailModel({ provider: 'openai-compatible', baseUrl: 'http://localhost:1234/v1', model: 'qwen3' })\n *\n * Every provider here speaks the OpenAI \"chat completions\" API. The answer is\n * asked as structured output (a JSON schema) where the provider has it; one\n * that refuses it is asked for a JSON object, then for plain JSON in the text —\n * once, then remembered. The schema is in the instructions either way, and\n * the generator checks and repairs what comes back.\n *\n * Claude: `claudeEmailModel` (claude.js) with the Anthropic SDK client, or\n * through OpenRouter (`model: 'anthropic/…'`) with one key for every model.\n *\n * Model names change often: each provider's `model` is a default that was\n * current when this package was released; pass your own to be sure.\n *\n * Dependency-free: runs in the browser (a key in the page — prototypes, or a\n * user's own key with `createAi({ askKey: true })`) and on the server.\n */\n\nimport { EmailGenerationError } from './draft.js';\n\n/**\n * @typedef {'json_schema'|'json_object'|'text'} AnswerFormat\n * @typedef {{\n * id: string,\n * name: string,\n * baseUrl: string,\n * model: string|null,\n * keyUrl: string|null,\n * keyHint: string,\n * free: string|null,\n * format: AnswerFormat,\n * tokens: 'max_tokens'|'max_completion_tokens',\n * maxTokens: number,\n * keyless?: boolean,\n * headers?: Record<string, string>,\n * body?: Record<string, unknown>,\n * createModel?: (options: {apiKey: () => string|Promise<string>, fetch?: typeof fetch}) => import('./generator.js').EmailModel,\n * }} AiProvider\n * `model`: the default (null: to be given); `keyUrl`: where a user gets a\n * key; `keyHint`: what a key starts with; `free`: what can be used without\n * paying, in a few words (null: paid only); `format`: the best answer format\n * the provider takes; `tokens`: how it names the output limit;\n * `maxTokens`: that limit (an email is about 6 000 tokens, more for a model\n * that reasons first); `body`: fields of its own added to each request;\n * `createModel`: a provider that is not an OpenAI-compatible API makes its\n * own model from the user's key (`claudeProvider` does).\n */\n\n/** @param {Omit<AiProvider, 'tokens'|'format'|'maxTokens'> & Partial<Pick<AiProvider, 'tokens'|'format'|'maxTokens'>>} provider @returns {AiProvider} */\nconst define = (provider) => Object.freeze({ format: 'json_schema', tokens: 'max_tokens', maxTokens: 16000, ...provider });\n\n/** The providers `providerEmailModel` knows, by id. */\nexport const AI_PROVIDERS = Object.freeze({\n openrouter: define({\n id: 'openrouter',\n name: 'OpenRouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n // A router across the free models of the day — a name that lasts, where\n // the free models themselves come and go (and an unknown one is a 400).\n model: 'openrouter/free',\n keyUrl: 'https://openrouter.ai/keys',\n keyHint: 'sk-or-',\n free: 'Free models, with a free account',\n headers: { 'X-Title': 'Best Email Builder' },\n // Most free models reason before they answer: 5 000 to 12 000 tokens\n // before the email itself, which a limit of 16 000 cut short. Less\n // reasoning where the model lets it be chosen.\n maxTokens: 32000,\n body: { reasoning: { effort: 'low' } },\n }),\n openai: define({\n id: 'openai',\n name: 'OpenAI',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5-mini',\n keyUrl: 'https://platform.openai.com/api-keys',\n keyHint: 'sk-',\n free: null,\n tokens: 'max_completion_tokens',\n }),\n gemini: define({\n id: 'gemini',\n name: 'Google Gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',\n model: 'gemini-3.5-flash-lite',\n keyUrl: 'https://aistudio.google.com/apikey',\n keyHint: 'AIza',\n free: 'Free tier, with rate limits',\n }),\n groq: define({\n id: 'groq',\n name: 'Groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'openai/gpt-oss-120b',\n keyUrl: 'https://console.groq.com/keys',\n keyHint: 'gsk_',\n free: 'Free tier, with rate limits',\n }),\n mistral: define({\n id: 'mistral',\n name: 'Mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-small-latest',\n keyUrl: 'https://console.mistral.ai/api-keys',\n keyHint: '',\n free: 'Free \"Experiment\" plan',\n }),\n deepseek: define({\n id: 'deepseek',\n name: 'DeepSeek',\n baseUrl: 'https://api.deepseek.com/v1',\n model: 'deepseek-chat',\n keyUrl: 'https://platform.deepseek.com/api_keys',\n keyHint: 'sk-',\n free: null,\n format: 'json_object',\n }),\n cerebras: define({\n id: 'cerebras',\n name: 'Cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n keyUrl: 'https://cloud.cerebras.ai/',\n keyHint: 'csk-',\n free: 'Free tier, with rate limits',\n }),\n ollama: define({\n id: 'ollama',\n name: 'Ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: null,\n keyUrl: null,\n keyHint: '',\n free: 'Runs on your own machine',\n keyless: true,\n }),\n 'openai-compatible': define({\n id: 'openai-compatible',\n name: 'OpenAI-compatible API',\n baseUrl: '',\n model: null,\n keyUrl: null,\n keyHint: '',\n free: null,\n keyless: true,\n }),\n});\n\n/**\n * A provider by id, or a provider object of one's own.\n * @param {string|Partial<AiProvider>} provider\n * @returns {AiProvider}\n */\nexport function resolveProvider(provider) {\n if (typeof provider === 'string') {\n const known = AI_PROVIDERS[/** @type {keyof typeof AI_PROVIDERS} */ (provider)];\n if (!known || !Object.hasOwn(AI_PROVIDERS, provider)) {\n throw new TypeError(`Unknown AI provider \"${provider}\" — one of ${Object.keys(AI_PROVIDERS).join(', ')}, or { baseUrl, model }`);\n }\n return known;\n }\n if (!provider || typeof provider !== 'object') throw new TypeError('provider: an id (e.g. \"openrouter\") or { baseUrl, model }');\n const base = provider.id && Object.hasOwn(AI_PROVIDERS, provider.id) ? AI_PROVIDERS[/** @type {keyof typeof AI_PROVIDERS} */ (provider.id)] : AI_PROVIDERS['openai-compatible'];\n return define({ ...base, ...provider });\n}\n\n/** The instruction every answer gets: the JSON, nothing else. @param {object} schema */\nconst answerInstruction = (schema) =>\n `\\n\\n# Answer format\\nAnswer with ONE JSON object and nothing else — no prose, no code fence — matching this JSON schema:\\n${JSON.stringify(schema)}`;\n\n/** @param {AnswerFormat} format @param {object} schema @param {string} task */\nfunction responseFormat(format, schema, task) {\n if (format === 'json_schema') return { type: 'json_schema', json_schema: { name: task || 'answer', strict: true, schema } };\n if (format === 'json_object') return { type: 'json_object' };\n return undefined;\n}\n\n/** A 400 about the answer format: the provider (or that model) does not take it. @param {number} status @param {string} text */\nconst formatRefused = (status, text) => (status === 400 || status === 422) && /response_format|json_schema|json_object|structured|schema|grammar/i.test(text);\n\n/** The message of a provider's error answer, else its text. @param {string} text */\nfunction errorMessage(text) {\n try {\n const data = JSON.parse(text);\n const error = Array.isArray(data) ? data[0]?.error : data?.error;\n return String(error?.metadata?.raw || error?.message || text);\n } catch {\n return text;\n }\n}\n\n/**\n * What a refused request means. A 401 is the key; so is a 403, unless the\n * provider says it is the content (moderation), the money, or the model (one\n * this key cannot use, a region) — the user's key must not be thrown away for\n * those. Gemini answers a wrong key with a 400.\n * @param {number} status @param {string} message\n * @returns {'KEY_REFUSED'|'REFUSED'|'MODEL_CREDITS'|'MODEL_ERROR'|null} null: not a refusal\n */\nfunction refusal(status, message) {\n if (status === 401) return 'KEY_REFUSED';\n if (status === 400) return /api[ _-]?key not valid|API_KEY_INVALID|invalid[ _-]api[ _-]key/i.test(message) ? 'KEY_REFUSED' : null;\n if (status !== 403) return null;\n if (/flagged|moderation/i.test(message)) return 'REFUSED';\n if (/credit|limit exceeded|quota/i.test(message)) return 'MODEL_CREDITS';\n if (/\\bmodels?\\b|harness|region|country|territory|not available|not allowed to (sample|access|use)/i.test(message)) return 'MODEL_ERROR';\n return 'KEY_REFUSED';\n}\n\n/**\n * The error code of a failed request, from its status and the provider's\n * message: the refusals above, the money (402, a quota, a credit balance too\n * low — Anthropic says it with a 400, OpenAI with a 429), busy, or an error.\n * @param {number} status @param {string} message\n * @returns {'KEY_REFUSED'|'REFUSED'|'MODEL_CREDITS'|'MODEL_BUSY'|'MODEL_ERROR'}\n */\nexport function failureCode(status, message) {\n const refused = refusal(status, message);\n if (refused) return refused;\n if (status === 402 || /credit balance|insufficient[ _](quota|credits|balance)|exceeded your current quota/i.test(message)) return 'MODEL_CREDITS';\n if (status === 429 || status === 503 || status === 529) return 'MODEL_BUSY';\n return 'MODEL_ERROR';\n}\n\n/** The text of an assistant message (a string, or parts). @param {any} message */\nfunction textOf(message) {\n const content = message?.content;\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) return content.map((part) => (typeof part === 'string' ? part : part?.text || '')).join('');\n return '';\n}\n\n/**\n * @param {{\n * provider?: string|Partial<AiProvider>,\n * apiKey?: string|(() => string|Promise<string>),\n * model?: string,\n * baseUrl?: string,\n * headers?: Record<string, string>,\n * body?: Record<string, unknown>,\n * maxTokens?: number,\n * temperature?: number,\n * fetch?: typeof fetch,\n * }} options\n * `apiKey`: the key, or a function that gives it at each call (asked to the\n * user, read from a vault); `model`, `baseUrl`: instead of the provider's;\n * `maxTokens`: the longest answer (the provider's by default); `body`:\n * fields of your own for each request (for example OpenRouter's fallback\n * `models`, or `reasoning`).\n * @returns {import('./generator.js').EmailModel & {provider: AiProvider, model: string}}\n */\nexport function providerEmailModel(options = {}) {\n const { apiKey, temperature, fetch: fetchImpl } = options;\n const provider = resolveProvider(options.provider ?? 'openai-compatible');\n const maxTokens = options.maxTokens ?? provider.maxTokens;\n const model = options.model || provider.model;\n const baseUrl = String(options.baseUrl || provider.baseUrl).replace(/\\/+$/, '');\n if (!baseUrl) throw new TypeError(`providerEmailModel: ${provider.name} needs a \\`baseUrl\\``);\n if (!model) throw new TypeError(`providerEmailModel: ${provider.name} needs a \\`model\\` (the name it gives the model)`);\n if (!apiKey && !provider.keyless) {\n throw new TypeError(`providerEmailModel: ${provider.name} needs an \\`apiKey\\`${provider.keyUrl ? ` (${provider.keyUrl})` : ''}`);\n }\n const call = fetchImpl || globalThis.fetch;\n /** The format that works, once learned. */\n let format = provider.format;\n\n /** @type {import('./generator.js').EmailModel} */\n const emailModel = async ({ system, messages, schema, signal, task }) => {\n const key = typeof apiKey === 'function' ? await apiKey() : apiKey;\n signal?.throwIfAborted();\n if (!key && !provider.keyless) throw new EmailGenerationError('KEY_MISSING', `${provider.name}: no key`);\n for (;;) {\n const body = {\n ...provider.body,\n ...options.body,\n model,\n messages: [{ role: 'system', content: system + answerInstruction(schema) }, ...messages],\n [provider.tokens]: maxTokens,\n ...(temperature === undefined ? {} : { temperature }),\n ...(responseFormat(format, schema, task || 'answer') ? { response_format: responseFormat(format, schema, task || 'answer') } : {}),\n };\n /** @type {Response} */\n let response;\n try {\n response = await call(`${baseUrl}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(key ? { Authorization: `Bearer ${key}` } : {}),\n ...provider.headers,\n ...options.headers,\n },\n body: JSON.stringify(body),\n signal,\n });\n } catch (error) {\n if (signal?.aborted || /** @type {any} */ (error)?.name === 'AbortError') throw error;\n throw new EmailGenerationError('MODEL_ERROR', `${provider.name}: ${/** @type {any} */ (error)?.message || error}`);\n }\n const text = await response.text();\n if (!response.ok) {\n if (formatRefused(response.status, text) && format !== 'text') {\n // That model does not take this format: the next one down, for good.\n format = format === 'json_schema' ? 'json_object' : 'text';\n continue;\n }\n const detail = `${provider.name} ${response.status}: ${text.slice(0, 300)}`;\n throw new EmailGenerationError(failureCode(response.status, errorMessage(text)), detail);\n }\n /** @type {any} */\n let data;\n try {\n data = JSON.parse(text);\n } catch {\n throw new EmailGenerationError('MODEL_ERROR', `${provider.name}: not JSON — ${text.slice(0, 200)}`);\n }\n // Some providers answer 200 with an error inside (OpenRouter's upstream failures).\n if (data?.error) {\n const code = Number(data.error.code) || 0;\n const detail = `${provider.name}: ${data.error.message || JSON.stringify(data.error).slice(0, 300)}`;\n throw new EmailGenerationError(failureCode(code, String(data.error.metadata?.raw || data.error.message || '')), detail);\n }\n const choice = data?.choices?.[0];\n if (choice?.message?.refusal) throw new EmailGenerationError('REFUSED', String(choice.message.refusal));\n if (choice?.finish_reason === 'content_filter') throw new EmailGenerationError('REFUSED');\n if (choice?.finish_reason === 'length') throw new EmailGenerationError('TRUNCATED');\n return textOf(choice?.message);\n }\n };\n return Object.assign(emailModel, { provider, model });\n}\n","// @ts-check\n/**\n * CLAUDE as the email model of `createEmailGenerator`.\n *\n * import Anthropic from '@anthropic-ai/sdk';\n * const model = claudeEmailModel({ client: new Anthropic() });\n *\n * The client is the host's own (`@anthropic-ai/sdk`, installed by the\n * application): its key, its proxy, its retries. Nothing here imports it.\n *\n * The answer comes as structured output (`output_config.format`): the JSON\n * schema of a draft, so the model cannot answer anything else. Streaming, read\n * to the end with `finalMessage()`: an email with the model's reasoning can\n * take a minute or more, longer than a plain request may stay open.\n *\n * In the page, with each user's own key, Claude is a provider of `createAi`:\n *\n * createAi({\n * provider: ['openai', claudeProvider({ client: async (apiKey) =>\n * new (await import('@anthropic-ai/sdk')).default({ apiKey, dangerouslyAllowBrowser: true }) }), 'gemini'],\n * askKey: true,\n * });\n */\n\nimport { EmailGenerationError } from './draft.js';\nimport { failureCode } from './providers.js';\n\nexport const DEFAULT_CLAUDE_MODEL = 'claude-opus-5-5';\n\n/**\n * @typedef {{\n * model?: string,\n * effort?: 'low'|'medium'|'high'|'xhigh'|'max',\n * rewriteEffort?: 'low'|'medium'|'high'|'xhigh'|'max',\n * maxTokens?: number,\n * }} ClaudeOptions\n */\n\n/**\n * @param {{client: any} & ClaudeOptions} options\n * `effort`: how much the model thinks before writing an email — `low`\n * answers faster, `high` takes more care. `rewriteEffort`: the same for\n * the light jobs the user waits on — one text rewritten, subject ideas.\n * @returns {import('./generator.js').EmailModel}\n */\nexport function claudeEmailModel({\n client,\n model = DEFAULT_CLAUDE_MODEL,\n effort = 'medium',\n rewriteEffort = 'low',\n maxTokens = 32000,\n} = /** @type {any} */ ({})) {\n if (!client?.messages?.stream) {\n throw new Error('claudeEmailModel: pass an Anthropic client — `new Anthropic()` from @anthropic-ai/sdk');\n }\n return async ({ system, messages, schema, signal, task }) => {\n let message;\n try {\n message = await client.messages\n .stream(\n {\n model,\n max_tokens: maxTokens,\n system,\n messages,\n output_config: { effort: task === 'rewrite' || task === 'subjects' ? rewriteEffort : effort, format: { type: 'json_schema', schema } },\n },\n { signal }\n )\n .finalMessage();\n } catch (error) {\n // The SDK's errors, as the errors the interface explains (a refused key\n // is asked again); an abort goes through as it is.\n const { status, name } = /** @type {any} */ (error) || {};\n if (signal?.aborted || name === 'AbortError' || name === 'APIUserAbortError' || error instanceof EmailGenerationError) throw error;\n const text = String(/** @type {any} */ (error)?.error?.error?.message || /** @type {any} */ (error)?.message || error);\n // No status: the request did not get through (network, CORS).\n if (typeof status !== 'number') throw new EmailGenerationError('MODEL_ERROR', `Claude: ${text.slice(0, 300)}`);\n throw new EmailGenerationError(failureCode(status, text), `Claude ${status}: ${text.slice(0, 300)}`);\n }\n if (message.stop_reason === 'refusal') {\n throw new EmailGenerationError('REFUSED');\n }\n if (message.stop_reason === 'max_tokens') {\n throw new EmailGenerationError('TRUNCATED');\n }\n return message.content\n .filter((/** @type {any} */ block) => block.type === 'text')\n .map((/** @type {any} */ block) => block.text)\n .join('');\n };\n}\n\n/**\n * Claude as a provider of `createAi({ provider, askKey: true })`: each user\n * connects their own Anthropic key, like the other providers. `client` makes\n * the Anthropic client from that key — the application's own SDK, loaded when\n * it is needed, so nothing here imports it:\n *\n * claudeProvider({\n * client: async (apiKey) => {\n * const { default: Anthropic } = await import('@anthropic-ai/sdk');\n * return new Anthropic({ apiKey, dangerouslyAllowBrowser: true });\n * },\n * })\n *\n * The other options are claudeEmailModel's (`model`, `effort`…).\n *\n * @param {{client: (apiKey: string) => any} & ClaudeOptions} options\n * @returns {import('./providers.js').AiProvider}\n */\nexport function claudeProvider(options) {\n const { client, ...rest } = options || /** @type {any} */ ({});\n if (typeof client !== 'function') {\n throw new TypeError('claudeProvider: `client` is a function that makes the Anthropic client from a key');\n }\n return Object.freeze({\n id: 'anthropic',\n name: 'Claude',\n baseUrl: 'https://api.anthropic.com',\n model: rest.model || DEFAULT_CLAUDE_MODEL,\n keyUrl: 'https://console.anthropic.com/settings/keys',\n keyHint: 'sk-ant-',\n free: null,\n format: /** @type {const} */ ('json_schema'),\n tokens: /** @type {const} */ ('max_tokens'),\n maxTokens: rest.maxTokens ?? 32000,\n createModel: ({ apiKey }) => {\n // One client per key: a new key (the last one refused) makes a new one.\n /** @type {{key: string, model: import('./generator.js').EmailModel}|null} */\n let current = null;\n return async (request) => {\n const key = String((await apiKey()) || '').trim();\n if (!key) throw new EmailGenerationError('KEY_MISSING', 'Claude: no key');\n if (!current || current.key !== key) current = { key, model: claudeEmailModel({ ...rest, client: await client(key) }) };\n return current.model(request);\n };\n },\n });\n}\n"],"mappings":"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","names":["text","purpose","text","bounded","text","MAX_VARIABLES","purpose","text","text"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|