best-email-builder 0.7.0 → 0.8.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +127 -735
  3. package/dist/ai-server.js +3 -3
  4. package/dist/ai.js +6 -6
  5. package/dist/builder.js +8 -8
  6. package/dist/chunk-62HBUBBI.js +3866 -0
  7. package/dist/chunk-62HBUBBI.js.map +1 -0
  8. package/dist/{chunk-QH5OI43L.js → chunk-6W3QUZR7.js} +8 -8
  9. package/dist/{chunk-W424KPCR.js → chunk-AXIM6E7T.js} +2 -2
  10. package/dist/{chunk-QRUMXCGW.js → chunk-DXWIVGHP.js} +3 -3
  11. package/dist/{chunk-C6WHIBGH.js → chunk-FAEVYSIF.js} +3 -3
  12. package/dist/{chunk-C6WHIBGH.js.map → chunk-FAEVYSIF.js.map} +1 -1
  13. package/dist/{chunk-4U7L2NYQ.js → chunk-G4U6PSF6.js} +2 -2
  14. package/dist/{chunk-3RPWH5PC.js → chunk-ITIG5NTJ.js} +4 -4
  15. package/dist/{chunk-CNJZLZRH.js → chunk-LUM7J5FF.js} +2 -2
  16. package/dist/{chunk-SAIW46IC.js → chunk-P7ABBLX6.js} +2 -2
  17. package/dist/{chunk-CTZ56YJG.js → chunk-SHHHYRPC.js} +834 -4
  18. package/dist/chunk-SHHHYRPC.js.map +1 -0
  19. package/dist/{chunk-UMVBT7NW.js → chunk-WM2EEV4Q.js} +4 -4
  20. package/dist/index.js +10 -10
  21. package/dist/library.js +5 -5
  22. package/dist/server.js +5 -5
  23. package/dist/types/ai/i18n/de.d.ts +2 -0
  24. package/dist/types/ai/i18n/es.d.ts +2 -0
  25. package/dist/types/ai/i18n/pt.d.ts +2 -0
  26. package/dist/types/ai/i18n/ru.d.ts +2 -0
  27. package/dist/types/ai/i18n/zh.d.ts +2 -0
  28. package/dist/types/core/constants.d.ts +1 -1
  29. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  30. package/dist/types/i18n/de.d.ts +2 -0
  31. package/dist/types/i18n/es.d.ts +2 -0
  32. package/dist/types/i18n/pt.d.ts +2 -0
  33. package/dist/types/i18n/ru.d.ts +2 -0
  34. package/dist/types/i18n/zh.d.ts +2 -0
  35. package/package.json +2 -2
  36. package/dist/chunk-CTZ56YJG.js.map +0 -1
  37. package/dist/chunk-JI36FNWE.js +0 -1361
  38. package/dist/chunk-JI36FNWE.js.map +0 -1
  39. /package/dist/{chunk-QH5OI43L.js.map → chunk-6W3QUZR7.js.map} +0 -0
  40. /package/dist/{chunk-W424KPCR.js.map → chunk-AXIM6E7T.js.map} +0 -0
  41. /package/dist/{chunk-QRUMXCGW.js.map → chunk-DXWIVGHP.js.map} +0 -0
  42. /package/dist/{chunk-4U7L2NYQ.js.map → chunk-G4U6PSF6.js.map} +0 -0
  43. /package/dist/{chunk-3RPWH5PC.js.map → chunk-ITIG5NTJ.js.map} +0 -0
  44. /package/dist/{chunk-CNJZLZRH.js.map → chunk-LUM7J5FF.js.map} +0 -0
  45. /package/dist/{chunk-SAIW46IC.js.map → chunk-P7ABBLX6.js.map} +0 -0
  46. /package/dist/{chunk-UMVBT7NW.js.map → chunk-WM2EEV4Q.js.map} +0 -0
@@ -0,0 +1 @@
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/de.js","../src/ai/i18n/es.js","../src/ai/i18n/fr.js","../src/ai/i18n/pt.js","../src/ai/i18n/ru.js","../src/ai/i18n/zh.js","../src/ai/i18n/index.js","../src/ai/generator.js","../src/ai/providers.js","../src/ai/claude.js","../src/ai/agent.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_UNAVAILABLE: { status: 502, message: 'The AI provider does not have this model. Choose 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(/<[^>]*>|&nbsp;|\\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(/&nbsp;/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, user?: any}) => Promise<{text: string, attempts: number}>}\n */\nexport function createRewriter({ model, maxAttempts = 2 }) {\n return async function rewrite(input, { signal, user } = {}) {\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', user });\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: `&amp;#65;` stays `&#65;`, 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, user?: any}) => Promise<{subjects: string[], attempts: number}>}\n */\nexport function createSubjectSuggester({ model, maxAttempts = 2 }) {\n return async function suggestSubjects(input, { signal, user } = {}) {\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', user });\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","// German 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/de.js. Missing keys: `node scripts/i18n-keys.mjs de`.\n\n/** @type {Record<string, string>} */\nexport const de = {\n \"Event invitation\": \"Einladung zur Veranstaltung\",\n \"Newsletter\": \"Newsletter\",\n \"Welcome\": \"Willkommen\",\n \"Generate\": \"Generieren\",\n \"Generate an email\": \"E-Mail generieren\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Wählen Sie den Zweck aus. Die KI schreibt eine vollständige E-Mail, die sich hier öffnet und sofort bearbeitet werden kann.\",\n \"Search\": \"Suchen\",\n \"Search: sale, password, birthday…\": \"Suchen: Sale, Passwort, Geburtstag…\",\n \"What is the email for?\": \"Wofür ist die E-Mail?\",\n \"Nothing matches. Describe your email in the details below.\": \"Keine Treffer. Beschreiben Sie Ihre E-Mail unten in den Details.\",\n \"Details (optional)\": \"Details (optional)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Details (optional): Produkt, Termine, Angebot, Tonfall, Sprache…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"Die aktuell bearbeitete E-Mail wird ersetzt, einschließlich nicht gespeicherter Änderungen.\",\n \"Writing your email… {seconds} s\": \"Ihre E-Mail wird geschrieben… {seconds} s\",\n \"Your email is ready. Edit anything, then save.\": \"Ihre E-Mail ist fertig. Bearbeiten Sie sie nach Belieben und speichern Sie sie dann.\",\n \"The email could not be generated.\": \"Die E-Mail konnte nicht generiert werden.\",\n \"Pick what the email is for, or describe it.\": \"Wählen Sie den Zweck der E-Mail aus oder beschreiben Sie sie.\",\n \"The AI declined to write this email. Try other details.\": \"Die KI hat es abgelehnt, diese E-Mail zu schreiben. Versuchen Sie es mit anderen Details.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"Die E-Mail war zu lang, um sie fertigzustellen. Fordern Sie eine kürzere an.\",\n \"The AI answer could not be read. Try again.\": \"Die Antwort der KI konnte nicht gelesen werden. Versuchen Sie es erneut.\",\n \"The generated email could not be used. Try again.\": \"Die generierte E-Mail ist nicht verwendbar. Versuchen Sie es erneut.\",\n \"Sales and promotions\": \"Angebote und Aktionen\",\n \"Holidays and occasions\": \"Feiertage und Anlässe\",\n \"Events and appointments\": \"Veranstaltungen und Termine\",\n \"News and content\": \"Neuigkeiten und Inhalte\",\n \"Account and security\": \"Konto und Sicherheit\",\n \"Orders and payments\": \"Bestellungen und Zahlungen\",\n \"Customer relationship\": \"Kundenbeziehung\",\n \"Business and organization\": \"Unternehmen und Organisation\",\n \"Store sale\": \"Sale im Geschäft\",\n \"Flash sale\": \"Blitzangebot\",\n \"Discount code\": \"Rabattcode\",\n \"New product\": \"Neues Produkt\",\n \"Back in stock\": \"Wieder auf Lager\",\n \"Price drop\": \"Preissenkung\",\n \"Abandoned cart\": \"Warenkorbabbruch\",\n \"Black Friday\": \"Black Friday\",\n \"Loyalty reward\": \"Treueprämie\",\n \"Refer a friend\": \"Freunde werben\",\n \"Christmas\": \"Weihnachten\",\n \"New Year\": \"Neujahr\",\n \"Season's greetings\": \"Festtagsgrüße\",\n \"Valentine's Day\": \"Valentinstag\",\n \"Easter\": \"Ostern\",\n \"Mother's Day\": \"Muttertag\",\n \"Father's Day\": \"Vatertag\",\n \"Halloween\": \"Halloween\",\n \"Back to school\": \"Schulanfang\",\n \"Summer holidays\": \"Sommerferien\",\n \"Birthday wishes\": \"Geburtstagsgrüße\",\n \"Customer anniversary\": \"Kundenjubiläum\",\n \"Women's Day\": \"Weltfrauentag\",\n \"Thanksgiving\": \"Thanksgiving\",\n \"Lunar New Year\": \"Mondneujahr\",\n \"Ramadan and Eid\": \"Ramadan und Zuckerfest\",\n \"Diwali\": \"Diwali\",\n \"Meeting reminder\": \"Besprechungserinnerung\",\n \"Appointment confirmation\": \"Terminbestätigung\",\n \"Booking confirmation\": \"Buchungsbestätigung\",\n \"Webinar invitation\": \"Webinar-Einladung\",\n \"Save the date\": \"Save the Date\",\n \"Registration confirmed\": \"Anmeldung bestätigt\",\n \"Cancellation or new date\": \"Absage oder neuer Termin\",\n \"Thanks for attending\": \"Danke für Ihre Teilnahme\",\n \"New article\": \"Neuer Artikel\",\n \"What's new\": \"Neuigkeiten\",\n \"Announcement\": \"Ankündigung\",\n \"Tips and how-to\": \"Tipps und Anleitungen\",\n \"Email verification\": \"Bestätigung der E-Mail-Adresse\",\n \"Password reset\": \"Passwort zurücksetzen\",\n \"Login code\": \"Anmeldecode\",\n \"Security alert\": \"Sicherheitswarnung\",\n \"Getting started\": \"Erste Schritte\",\n \"Trial ending\": \"Testphase endet\",\n \"Order confirmation\": \"Bestellbestätigung\",\n \"Order shipped\": \"Bestellung versandt\",\n \"Delivery delay\": \"Lieferverzögerung\",\n \"Receipt or invoice\": \"Quittung oder Rechnung\",\n \"Payment failed\": \"Zahlung fehlgeschlagen\",\n \"Subscription renewal\": \"Abo-Verlängerung\",\n \"Refund issued\": \"Rückerstattung veranlasst\",\n \"Thank you\": \"Dankeschön\",\n \"Review request\": \"Bitte um Bewertung\",\n \"Survey\": \"Umfrage\",\n \"We miss you\": \"Wir vermissen Sie\",\n \"Follow-up\": \"Nachfassen\",\n \"Apology\": \"Entschuldigung\",\n \"We're hiring\": \"Wir stellen ein\",\n \"Policy update\": \"Aktualisierung der Richtlinien\",\n \"Price change\": \"Preisänderung\",\n \"Scheduled maintenance\": \"Geplante Wartung\",\n \"Closure or opening hours\": \"Schließung oder Öffnungszeiten\",\n \"Donation appeal\": \"Spendenaufruf\",\n \"Internal update\": \"Interne Mitteilung\",\n \"Rewrite with AI\": \"Mit KI umschreiben\",\n \"Shorter\": \"Kürzer\",\n \"Longer\": \"Länger\",\n \"Fix mistakes\": \"Fehler korrigieren\",\n \"Friendlier\": \"Freundlicher\",\n \"More formal\": \"Förmlicher\",\n \"Translate\": \"Übersetzen\",\n \"Translate into\": \"Übersetzen in\",\n \"Or say what to change…\": \"Oder sagen Sie, was geändert werden soll…\",\n \"What to change\": \"Was geändert werden soll\",\n \"Rewrite\": \"Umschreiben\",\n \"Rewriting…\": \"Wird umgeschrieben…\",\n \"Suggest subjects\": \"Betreff vorschlagen\",\n \"Subject ideas\": \"Betreffideen\",\n \"More ideas\": \"Weitere Ideen\",\n \"Done\": \"Fertig\",\n \"yours\": \"Ihrer\",\n \"Reading the email…\": \"E-Mail wird gelesen…\",\n \"No subject could be suggested.\": \"Es konnte kein Betreff vorgeschlagen werden.\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Schreiben Sie zuerst die E-Mail oder einen Betreff: Die Ideen werden daraus abgeleitet.\",\n \"The AI declined to suggest subjects for this email.\": \"Die KI hat es abgelehnt, für diese E-Mail einen Betreff vorzuschlagen.\",\n \"No usable subject came back. Try again.\": \"Es kam kein brauchbarer Betreff zurück. Versuchen Sie es erneut.\",\n \"Connect an AI provider first: it needs a key.\": \"Verbinden Sie zuerst einen KI-Anbieter: Dafür ist ein Schlüssel nötig.\",\n \"The AI provider refused the key. Check it, or use another one.\": \"Der KI-Anbieter hat den Schlüssel abgelehnt. Prüfen Sie ihn oder verwenden Sie einen anderen.\",\n \"The AI provider does not have this model. Choose another one.\": \"Der KI-Anbieter bietet dieses Modell nicht an. Wählen Sie ein anderes aus.\",\n \"The AI provider account has no credits left.\": \"Das Konto beim KI-Anbieter hat kein Guthaben mehr.\",\n \"The AI is busy right now. Try again in a minute.\": \"Die KI ist gerade ausgelastet. Versuchen Sie es in einer Minute erneut.\",\n \"The AI provider could not answer. Try again.\": \"Der KI-Anbieter konnte nicht antworten. Versuchen Sie es erneut.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Text umgeschrieben. Mit Ctrl+Z stellen Sie den vorherigen wieder her.\",\n \"The text changed in the meantime, so it was left as it is.\": \"Der Text wurde zwischenzeitlich geändert und bleibt daher unverändert.\",\n \"The text could not be rewritten.\": \"Der Text konnte nicht umgeschrieben werden.\",\n \"There is no text to rewrite.\": \"Es gibt keinen Text zum Umschreiben.\",\n \"The AI declined to rewrite this text.\": \"Die KI hat es abgelehnt, diesen Text umzuschreiben.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"Im umgeschriebenen Text fehlte ein Link oder eine Variable, daher wurde nichts geändert. Versuchen Sie es erneut.\",\n \"Connect {provider}\": \"{provider} verbinden\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"Die KI schreibt mit Ihrem eigenen {provider}-Schlüssel. Verbinden Sie ihn einmal: Dieser Browser merkt ihn sich.\",\n \"{provider} refused the last key. Paste another one.\": \"{provider} hat den letzten Schlüssel abgelehnt. Fügen Sie einen anderen ein.\",\n \"Get a key\": \"Schlüssel anfordern\",\n \"API key\": \"API-Schlüssel\",\n \"Paste the key\": \"Schlüssel einfügen\",\n \"Hide the key\": \"Schlüssel ausblenden\",\n \"Show the key\": \"Schlüssel anzeigen\",\n \"{provider} keys usually start with {prefix}.\": \"Schlüssel von {provider} beginnen normalerweise mit {prefix}.\",\n \"{provider} keys start with {prefix}.\": \"Schlüssel von {provider} beginnen mit {prefix}.\",\n \"The key stays in this browser and is sent only to {provider}.\": \"Der Schlüssel bleibt in diesem Browser und wird nur an {provider} gesendet.\",\n \"Connect\": \"Verbinden\",\n \"Free models, with a free account\": \"Kostenlose Modelle mit kostenlosem Konto\",\n \"Free tier, with rate limits\": \"Kostenloser Tarif mit Anfragelimits\",\n \"Free \\\"Experiment\\\" plan\": \"Kostenloser Tarif „Experiment“\",\n \"Runs on your own machine\": \"Läuft auf Ihrem eigenen Rechner\",\n \"AI provider\": \"KI-Anbieter\",\n \"Key saved\": \"Schlüssel gespeichert\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"In diesem Browser ist ein Schlüssel für {provider} gespeichert: „Verbinden“ verwendet ihn, oder fügen Sie einen anderen ein.\",\n \"Model\": \"Modell\",\n \"Choose a model\": \"Modell auswählen\",\n \"Free\": \"Kostenlos\",\n \"Free only\": \"Nur kostenlose\",\n \"Search the models\": \"Modelle durchsuchen\",\n \"Search the models…\": \"Modelle durchsuchen…\",\n \"Use “{model}”\": \"„{model}“ verwenden\",\n \"No model matches.\": \"Kein passendes Modell.\",\n \"Loading the models of {provider}…\": \"Modelle von {provider} werden geladen…\",\n \"Paste the key to see all the models of {provider}.\": \"Fügen Sie den Schlüssel ein, um alle Modelle von {provider} zu sehen.\",\n \"{provider} refused this key: its models could not be listed.\": \"{provider} hat diesen Schlüssel abgelehnt: Die Modelle konnten nicht aufgelistet werden.\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"Die Modellliste konnte nicht geladen werden. Das Standardmodell funktioniert, oder geben Sie die ID eines anderen ein.\",\n \"{count} models, the free ones first.\": \"Modelle: {count}, die kostenlosen zuerst.\",\n \"{count} models.\": \"Modelle: {count}.\",\n \"{provider} lists no models here: type the id of the one you want.\": \"{provider} listet hier keine Modelle auf: Geben Sie die ID des gewünschten Modells ein.\",\n};\n","// Spanish 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/es.js. Missing keys: `node scripts/i18n-keys.mjs es`.\n\n/** @type {Record<string, string>} */\nexport const es = {\n \"Event invitation\": \"Invitación a un evento\",\n \"Newsletter\": \"Boletín\",\n \"Welcome\": \"Bienvenida\",\n \"Generate\": \"Generar\",\n \"Generate an email\": \"Generar un correo\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Elige para qué sirve. La IA redacta un correo completo que se abre aquí, listo para editar.\",\n \"Search\": \"Buscar\",\n \"Search: sale, password, birthday…\": \"Buscar: ofertas, contraseña, cumpleaños…\",\n \"What is the email for?\": \"¿Para qué es el correo?\",\n \"Nothing matches. Describe your email in the details below.\": \"No hay resultados. Describe tu correo en los detalles de abajo.\",\n \"Details (optional)\": \"Detalles (opcional)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Detalles (opcional): producto, fechas, oferta, tono, idioma…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"Se reemplazará el correo que estás editando, incluidos los cambios sin guardar.\",\n \"Writing your email… {seconds} s\": \"Redactando tu correo… {seconds} s\",\n \"Your email is ready. Edit anything, then save.\": \"Tu correo está listo. Edita lo que quieras y luego guarda.\",\n \"The email could not be generated.\": \"No se pudo generar el correo.\",\n \"Pick what the email is for, or describe it.\": \"Elige para qué es el correo o descríbelo.\",\n \"The AI declined to write this email. Try other details.\": \"La IA se negó a redactar este correo. Prueba con otros detalles.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"El correo era demasiado largo para terminarlo. Pide uno más corto.\",\n \"The AI answer could not be read. Try again.\": \"No se pudo leer la respuesta de la IA. Inténtalo de nuevo.\",\n \"The generated email could not be used. Try again.\": \"No se pudo usar el correo generado. Inténtalo de nuevo.\",\n \"Sales and promotions\": \"Ventas y promociones\",\n \"Holidays and occasions\": \"Festividades y ocasiones especiales\",\n \"Events and appointments\": \"Eventos y citas\",\n \"News and content\": \"Noticias y contenidos\",\n \"Account and security\": \"Cuenta y seguridad\",\n \"Orders and payments\": \"Pedidos y pagos\",\n \"Customer relationship\": \"Relación con los clientes\",\n \"Business and organization\": \"Empresa y organización\",\n \"Store sale\": \"Ofertas en tienda\",\n \"Flash sale\": \"Venta flash\",\n \"Discount code\": \"Código de descuento\",\n \"New product\": \"Nuevo producto\",\n \"Back in stock\": \"De nuevo disponible\",\n \"Price drop\": \"Rebaja de precio\",\n \"Abandoned cart\": \"Carrito abandonado\",\n \"Black Friday\": \"Black Friday\",\n \"Loyalty reward\": \"Recompensa por fidelidad\",\n \"Refer a friend\": \"Recomienda a un amigo\",\n \"Christmas\": \"Navidad\",\n \"New Year\": \"Año Nuevo\",\n \"Season's greetings\": \"Felices fiestas\",\n \"Valentine's Day\": \"San Valentín\",\n \"Easter\": \"Pascua\",\n \"Mother's Day\": \"Día de la Madre\",\n \"Father's Day\": \"Día del Padre\",\n \"Halloween\": \"Halloween\",\n \"Back to school\": \"Regreso a clases\",\n \"Summer holidays\": \"Vacaciones de verano\",\n \"Birthday wishes\": \"Feliz cumpleaños\",\n \"Customer anniversary\": \"Aniversario del cliente\",\n \"Women's Day\": \"Día de la Mujer\",\n \"Thanksgiving\": \"Acción de Gracias\",\n \"Lunar New Year\": \"Año Nuevo Lunar\",\n \"Ramadan and Eid\": \"Ramadán y Eid\",\n \"Diwali\": \"Diwali\",\n \"Meeting reminder\": \"Recordatorio de reunión\",\n \"Appointment confirmation\": \"Confirmación de cita\",\n \"Booking confirmation\": \"Confirmación de reserva\",\n \"Webinar invitation\": \"Invitación a un seminario web\",\n \"Save the date\": \"Reserva la fecha\",\n \"Registration confirmed\": \"Inscripción confirmada\",\n \"Cancellation or new date\": \"Cancelación o cambio de fecha\",\n \"Thanks for attending\": \"Gracias por asistir\",\n \"New article\": \"Nuevo artículo\",\n \"What's new\": \"Novedades\",\n \"Announcement\": \"Anuncio\",\n \"Tips and how-to\": \"Consejos y tutoriales\",\n \"Email verification\": \"Verificación del correo\",\n \"Password reset\": \"Restablecimiento de contraseña\",\n \"Login code\": \"Código de inicio de sesión\",\n \"Security alert\": \"Alerta de seguridad\",\n \"Getting started\": \"Primeros pasos\",\n \"Trial ending\": \"Fin del periodo de prueba\",\n \"Order confirmation\": \"Confirmación de pedido\",\n \"Order shipped\": \"Pedido enviado\",\n \"Delivery delay\": \"Retraso en la entrega\",\n \"Receipt or invoice\": \"Recibo o factura\",\n \"Payment failed\": \"Error en el pago\",\n \"Subscription renewal\": \"Renovación de suscripción\",\n \"Refund issued\": \"Reembolso realizado\",\n \"Thank you\": \"Agradecimiento\",\n \"Review request\": \"Solicitud de reseña\",\n \"Survey\": \"Encuesta\",\n \"We miss you\": \"Te echamos de menos\",\n \"Follow-up\": \"Seguimiento\",\n \"Apology\": \"Disculpas\",\n \"We're hiring\": \"Estamos contratando\",\n \"Policy update\": \"Actualización de políticas\",\n \"Price change\": \"Cambio de precios\",\n \"Scheduled maintenance\": \"Mantenimiento programado\",\n \"Closure or opening hours\": \"Cierre u horario de apertura\",\n \"Donation appeal\": \"Solicitud de donaciones\",\n \"Internal update\": \"Comunicación interna\",\n \"Rewrite with AI\": \"Reescribir con IA\",\n \"Shorter\": \"Más corto\",\n \"Longer\": \"Más largo\",\n \"Fix mistakes\": \"Corregir errores\",\n \"Friendlier\": \"Más cercano\",\n \"More formal\": \"Más formal\",\n \"Translate\": \"Traducir\",\n \"Translate into\": \"Traducir a\",\n \"Or say what to change…\": \"O di qué cambiar…\",\n \"What to change\": \"Qué cambiar\",\n \"Rewrite\": \"Reescribir\",\n \"Rewriting…\": \"Reescribiendo…\",\n \"Suggest subjects\": \"Sugerir asuntos\",\n \"Subject ideas\": \"Ideas de asunto\",\n \"More ideas\": \"Más ideas\",\n \"Done\": \"Listo\",\n \"yours\": \"el tuyo\",\n \"Reading the email…\": \"Leyendo el correo…\",\n \"No subject could be suggested.\": \"No se pudo sugerir ningún asunto.\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Escribe primero el correo o un asunto: las ideas salen de ahí.\",\n \"The AI declined to suggest subjects for this email.\": \"La IA se negó a sugerir asuntos para este correo.\",\n \"No usable subject came back. Try again.\": \"No llegó ningún asunto utilizable. Inténtalo de nuevo.\",\n \"Connect an AI provider first: it needs a key.\": \"Conecta primero un proveedor de IA: necesita una clave.\",\n \"The AI provider refused the key. Check it, or use another one.\": \"El proveedor de IA rechazó la clave. Compruébala o usa otra.\",\n \"The AI provider does not have this model. Choose another one.\": \"El proveedor de IA no tiene este modelo. Elige otro.\",\n \"The AI provider account has no credits left.\": \"La cuenta del proveedor de IA se quedó sin créditos.\",\n \"The AI is busy right now. Try again in a minute.\": \"La IA está ocupada en este momento. Inténtalo de nuevo en un minuto.\",\n \"The AI provider could not answer. Try again.\": \"El proveedor de IA no pudo responder. Inténtalo de nuevo.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Texto reescrito. Ctrl+Z recupera el anterior.\",\n \"The text changed in the meantime, so it was left as it is.\": \"El texto cambió mientras tanto, así que se dejó como estaba.\",\n \"The text could not be rewritten.\": \"No se pudo reescribir el texto.\",\n \"There is no text to rewrite.\": \"No hay texto que reescribir.\",\n \"The AI declined to rewrite this text.\": \"La IA se negó a reescribir este texto.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"El texto reescrito perdió un enlace o una variable, así que no se cambió nada. Inténtalo de nuevo.\",\n \"Connect {provider}\": \"Conectar {provider}\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"La IA escribe con tu propia clave de {provider}. Conéctala una sola vez: este navegador la recuerda.\",\n \"{provider} refused the last key. Paste another one.\": \"{provider} rechazó la última clave. Pega otra.\",\n \"Get a key\": \"Obtener una clave\",\n \"API key\": \"Clave de API\",\n \"Paste the key\": \"Pega la clave\",\n \"Hide the key\": \"Ocultar la clave\",\n \"Show the key\": \"Mostrar la clave\",\n \"{provider} keys usually start with {prefix}.\": \"Las claves de {provider} suelen empezar por {prefix}.\",\n \"{provider} keys start with {prefix}.\": \"Las claves de {provider} empiezan por {prefix}.\",\n \"The key stays in this browser and is sent only to {provider}.\": \"La clave se queda en este navegador y solo se envía a {provider}.\",\n \"Connect\": \"Conectar\",\n \"Free models, with a free account\": \"Modelos gratuitos, con una cuenta gratuita\",\n \"Free tier, with rate limits\": \"Plan gratuito, con límites de solicitudes\",\n \"Free \\\"Experiment\\\" plan\": \"Plan gratuito \\\"Experiment\\\"\",\n \"Runs on your own machine\": \"Se ejecuta en tu propio equipo\",\n \"AI provider\": \"Proveedor de IA\",\n \"Key saved\": \"Clave guardada\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"Hay una clave de {provider} guardada en este navegador: Conectar la usa, o pega otra.\",\n \"Model\": \"Modelo\",\n \"Choose a model\": \"Elegir un modelo\",\n \"Free\": \"Gratis\",\n \"Free only\": \"Solo gratuitos\",\n \"Search the models\": \"Buscar modelos\",\n \"Search the models…\": \"Buscar modelos…\",\n \"Use “{model}”\": \"Usar “{model}”\",\n \"No model matches.\": \"Ningún modelo coincide.\",\n \"Loading the models of {provider}…\": \"Cargando los modelos de {provider}…\",\n \"Paste the key to see all the models of {provider}.\": \"Pega la clave para ver todos los modelos de {provider}.\",\n \"{provider} refused this key: its models could not be listed.\": \"{provider} rechazó esta clave: no se pudieron mostrar sus modelos.\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"No se pudo cargar la lista de modelos. El modelo predeterminado funciona, o escribe el ID de otro.\",\n \"{count} models, the free ones first.\": \"Modelos: {count}, primero los gratuitos.\",\n \"{count} models.\": \"Modelos: {count}.\",\n \"{provider} lists no models here: type the id of the one you want.\": \"{provider} no muestra modelos aquí: escribe el ID del que quieras.\",\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 does not have this model. Choose another one.\": \"Le fournisseur d'IA n'a pas ce modèle. Choisissez-en un 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 \"Model\": \"Modèle\",\n \"Choose a model\": \"Choisir un modèle\",\n \"Free\": \"Gratuit\",\n \"Free only\": \"Gratuits\",\n \"Search the models\": \"Rechercher un modèle\",\n \"Search the models…\": \"Rechercher un modèle…\",\n \"Use “{model}”\": \"Utiliser « {model} »\",\n \"No model matches.\": \"Aucun modèle ne correspond.\",\n \"Loading the models of {provider}…\": \"Chargement des modèles de {provider}…\",\n \"Paste the key to see all the models of {provider}.\": \"Collez la clé pour voir tous les modèles de {provider}.\",\n \"{provider} refused this key: its models could not be listed.\": \"{provider} a refusé cette clé : ses modèles n'ont pas pu être listés.\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"La liste des modèles n'a pas pu être chargée. Le modèle par défaut fonctionne, ou tapez l'identifiant d'un autre.\",\n \"{count} models, the free ones first.\": \"{count} modèles, les gratuits en premier.\",\n \"{count} models.\": \"{count} modèles.\",\n \"{provider} lists no models here: type the id of the one you want.\": \"{provider} ne liste aucun modèle ici : tapez l'identifiant de celui que vous voulez.\",\n};\n","// Portuguese (Brazil) 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/pt.js. Missing keys: `node scripts/i18n-keys.mjs pt`.\n\n/** @type {Record<string, string>} */\nexport const pt = {\n \"Event invitation\": \"Convite para evento\",\n \"Newsletter\": \"Newsletter\",\n \"Welcome\": \"Boas-vindas\",\n \"Generate\": \"Gerar\",\n \"Generate an email\": \"Gerar um e-mail\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Escolha a finalidade. A IA escreve um e-mail completo que abre aqui, pronto para editar.\",\n \"Search\": \"Pesquisar\",\n \"Search: sale, password, birthday…\": \"Pesquisar: promoção, senha, aniversário…\",\n \"What is the email for?\": \"Para que serve o e-mail?\",\n \"Nothing matches. Describe your email in the details below.\": \"Nenhum resultado. Descreva seu e-mail nos detalhes abaixo.\",\n \"Details (optional)\": \"Detalhes (opcional)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Detalhes (opcional): produto, datas, oferta, tom, idioma…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"O e-mail em edição será substituído, incluindo as alterações não salvas.\",\n \"Writing your email… {seconds} s\": \"Escrevendo seu e-mail… {seconds} s\",\n \"Your email is ready. Edit anything, then save.\": \"Seu e-mail está pronto. Edite o que quiser e depois salve.\",\n \"The email could not be generated.\": \"Não foi possível gerar o e-mail.\",\n \"Pick what the email is for, or describe it.\": \"Escolha a finalidade do e-mail ou descreva-o.\",\n \"The AI declined to write this email. Try other details.\": \"A IA se recusou a escrever este e-mail. Tente outros detalhes.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"O e-mail ficou longo demais para ser concluído. Peça um mais curto.\",\n \"The AI answer could not be read. Try again.\": \"Não foi possível ler a resposta da IA. Tente novamente.\",\n \"The generated email could not be used. Try again.\": \"Não foi possível usar o e-mail gerado. Tente novamente.\",\n \"Sales and promotions\": \"Vendas e promoções\",\n \"Holidays and occasions\": \"Feriados e datas especiais\",\n \"Events and appointments\": \"Eventos e compromissos\",\n \"News and content\": \"Notícias e conteúdo\",\n \"Account and security\": \"Conta e segurança\",\n \"Orders and payments\": \"Pedidos e pagamentos\",\n \"Customer relationship\": \"Relacionamento com o cliente\",\n \"Business and organization\": \"Empresa e organização\",\n \"Store sale\": \"Liquidação na loja\",\n \"Flash sale\": \"Promoção relâmpago\",\n \"Discount code\": \"Cupom de desconto\",\n \"New product\": \"Novo produto\",\n \"Back in stock\": \"De volta ao estoque\",\n \"Price drop\": \"Queda de preço\",\n \"Abandoned cart\": \"Carrinho abandonado\",\n \"Black Friday\": \"Black Friday\",\n \"Loyalty reward\": \"Recompensa de fidelidade\",\n \"Refer a friend\": \"Indique um amigo\",\n \"Christmas\": \"Natal\",\n \"New Year\": \"Ano-Novo\",\n \"Season's greetings\": \"Boas festas\",\n \"Valentine's Day\": \"Dia dos Namorados\",\n \"Easter\": \"Páscoa\",\n \"Mother's Day\": \"Dia das Mães\",\n \"Father's Day\": \"Dia dos Pais\",\n \"Halloween\": \"Halloween\",\n \"Back to school\": \"Volta às aulas\",\n \"Summer holidays\": \"Férias de verão\",\n \"Birthday wishes\": \"Feliz aniversário\",\n \"Customer anniversary\": \"Aniversário de cliente\",\n \"Women's Day\": \"Dia da Mulher\",\n \"Thanksgiving\": \"Ação de Graças\",\n \"Lunar New Year\": \"Ano-Novo Lunar\",\n \"Ramadan and Eid\": \"Ramadã e Eid\",\n \"Diwali\": \"Diwali\",\n \"Meeting reminder\": \"Lembrete de reunião\",\n \"Appointment confirmation\": \"Confirmação de agendamento\",\n \"Booking confirmation\": \"Confirmação de reserva\",\n \"Webinar invitation\": \"Convite para webinar\",\n \"Save the date\": \"Reserve a data\",\n \"Registration confirmed\": \"Inscrição confirmada\",\n \"Cancellation or new date\": \"Cancelamento ou nova data\",\n \"Thanks for attending\": \"Obrigado pela presença\",\n \"New article\": \"Novo artigo\",\n \"What's new\": \"Novidades\",\n \"Announcement\": \"Comunicado\",\n \"Tips and how-to\": \"Dicas e tutoriais\",\n \"Email verification\": \"Verificação de e-mail\",\n \"Password reset\": \"Redefinição de senha\",\n \"Login code\": \"Código de login\",\n \"Security alert\": \"Alerta de segurança\",\n \"Getting started\": \"Primeiros passos\",\n \"Trial ending\": \"Fim do período de teste\",\n \"Order confirmation\": \"Confirmação de pedido\",\n \"Order shipped\": \"Pedido enviado\",\n \"Delivery delay\": \"Atraso na entrega\",\n \"Receipt or invoice\": \"Recibo ou fatura\",\n \"Payment failed\": \"Falha no pagamento\",\n \"Subscription renewal\": \"Renovação de assinatura\",\n \"Refund issued\": \"Reembolso efetuado\",\n \"Thank you\": \"Agradecimento\",\n \"Review request\": \"Pedido de avaliação\",\n \"Survey\": \"Pesquisa de satisfação\",\n \"We miss you\": \"Sentimos sua falta\",\n \"Follow-up\": \"Follow-up\",\n \"Apology\": \"Pedido de desculpas\",\n \"We're hiring\": \"Estamos contratando\",\n \"Policy update\": \"Atualização de políticas\",\n \"Price change\": \"Mudança de preço\",\n \"Scheduled maintenance\": \"Manutenção programada\",\n \"Closure or opening hours\": \"Fechamento ou horário de funcionamento\",\n \"Donation appeal\": \"Pedido de doações\",\n \"Internal update\": \"Comunicação interna\",\n \"Rewrite with AI\": \"Reescrever com IA\",\n \"Shorter\": \"Mais curto\",\n \"Longer\": \"Mais longo\",\n \"Fix mistakes\": \"Corrigir erros\",\n \"Friendlier\": \"Mais amigável\",\n \"More formal\": \"Mais formal\",\n \"Translate\": \"Traduzir\",\n \"Translate into\": \"Traduzir para\",\n \"Or say what to change…\": \"Ou diga o que mudar…\",\n \"What to change\": \"O que mudar\",\n \"Rewrite\": \"Reescrever\",\n \"Rewriting…\": \"Reescrevendo…\",\n \"Suggest subjects\": \"Sugerir assuntos\",\n \"Subject ideas\": \"Ideias de assunto\",\n \"More ideas\": \"Mais ideias\",\n \"Done\": \"Concluído\",\n \"yours\": \"o seu\",\n \"Reading the email…\": \"Lendo o e-mail…\",\n \"No subject could be suggested.\": \"Não foi possível sugerir nenhum assunto.\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Escreva primeiro o e-mail ou um assunto: as ideias partem dele.\",\n \"The AI declined to suggest subjects for this email.\": \"A IA se recusou a sugerir assuntos para este e-mail.\",\n \"No usable subject came back. Try again.\": \"Nenhum assunto aproveitável foi retornado. Tente novamente.\",\n \"Connect an AI provider first: it needs a key.\": \"Conecte primeiro um provedor de IA: é preciso uma chave.\",\n \"The AI provider refused the key. Check it, or use another one.\": \"O provedor de IA recusou a chave. Verifique-a ou use outra.\",\n \"The AI provider does not have this model. Choose another one.\": \"O provedor de IA não tem este modelo. Escolha outro.\",\n \"The AI provider account has no credits left.\": \"A conta do provedor de IA está sem créditos.\",\n \"The AI is busy right now. Try again in a minute.\": \"A IA está ocupada agora. Tente novamente daqui a um minuto.\",\n \"The AI provider could not answer. Try again.\": \"O provedor de IA não conseguiu responder. Tente novamente.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Texto reescrito. Ctrl+Z traz de volta o anterior.\",\n \"The text changed in the meantime, so it was left as it is.\": \"O texto mudou nesse meio-tempo, então foi mantido como estava.\",\n \"The text could not be rewritten.\": \"Não foi possível reescrever o texto.\",\n \"There is no text to rewrite.\": \"Não há texto para reescrever.\",\n \"The AI declined to rewrite this text.\": \"A IA se recusou a reescrever este texto.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"O texto reescrito perdeu um link ou uma variável, então nada foi alterado. Tente novamente.\",\n \"Connect {provider}\": \"Conectar {provider}\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"A IA escreve com sua própria chave de {provider}. Conecte-a uma vez: este navegador se lembra dela.\",\n \"{provider} refused the last key. Paste another one.\": \"{provider} recusou a última chave. Cole outra.\",\n \"Get a key\": \"Obter uma chave\",\n \"API key\": \"Chave de API\",\n \"Paste the key\": \"Cole a chave\",\n \"Hide the key\": \"Ocultar a chave\",\n \"Show the key\": \"Mostrar a chave\",\n \"{provider} keys usually start with {prefix}.\": \"As chaves de {provider} costumam começar com {prefix}.\",\n \"{provider} keys start with {prefix}.\": \"As chaves de {provider} começam com {prefix}.\",\n \"The key stays in this browser and is sent only to {provider}.\": \"A chave fica neste navegador e só é enviada para {provider}.\",\n \"Connect\": \"Conectar\",\n \"Free models, with a free account\": \"Modelos gratuitos, com uma conta gratuita\",\n \"Free tier, with rate limits\": \"Plano gratuito, com limites de uso\",\n \"Free \\\"Experiment\\\" plan\": \"Plano gratuito \\\"Experiment\\\"\",\n \"Runs on your own machine\": \"Roda no seu próprio computador\",\n \"AI provider\": \"Provedor de IA\",\n \"Key saved\": \"Chave salva\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"Há uma chave de {provider} salva neste navegador: Conectar vai usá-la, ou cole outra.\",\n \"Model\": \"Modelo\",\n \"Choose a model\": \"Escolher um modelo\",\n \"Free\": \"Gratuito\",\n \"Free only\": \"Só gratuitos\",\n \"Search the models\": \"Pesquisar modelos\",\n \"Search the models…\": \"Pesquisar modelos…\",\n \"Use “{model}”\": \"Usar “{model}”\",\n \"No model matches.\": \"Nenhum modelo corresponde.\",\n \"Loading the models of {provider}…\": \"Carregando os modelos de {provider}…\",\n \"Paste the key to see all the models of {provider}.\": \"Cole a chave para ver todos os modelos de {provider}.\",\n \"{provider} refused this key: its models could not be listed.\": \"{provider} recusou esta chave: não foi possível listar os modelos.\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"Não foi possível carregar a lista de modelos. O modelo padrão funciona, ou digite o ID de outro.\",\n \"{count} models, the free ones first.\": \"Modelos: {count}, os gratuitos primeiro.\",\n \"{count} models.\": \"Modelos: {count}.\",\n \"{provider} lists no models here: type the id of the one you want.\": \"{provider} não lista modelos aqui: digite o ID do que você quer.\",\n};\n","// Russian 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/ru.js. Missing keys: `node scripts/i18n-keys.mjs ru`.\n\n/** @type {Record<string, string>} */\nexport const ru = {\n \"Event invitation\": \"Приглашение на мероприятие\",\n \"Newsletter\": \"Новостная рассылка\",\n \"Welcome\": \"Приветственное письмо\",\n \"Generate\": \"Сгенерировать\",\n \"Generate an email\": \"Сгенерировать письмо\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"Выберите, для чего письмо. ИИ напишет его целиком, и оно откроется здесь — готовое к редактированию.\",\n \"Search\": \"Поиск\",\n \"Search: sale, password, birthday…\": \"Поиск: распродажа, пароль, день рождения…\",\n \"What is the email for?\": \"Для чего это письмо?\",\n \"Nothing matches. Describe your email in the details below.\": \"Ничего не найдено. Опишите письмо в подробностях ниже.\",\n \"Details (optional)\": \"Подробности (необязательно)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"Подробности (необязательно): товар, даты, предложение, тон, язык…\",\n \"The email being edited will be replaced, unsaved changes included.\": \"Текущее письмо будет заменено вместе с несохранёнными изменениями.\",\n \"Writing your email… {seconds} s\": \"ИИ пишет письмо… {seconds} с\",\n \"Your email is ready. Edit anything, then save.\": \"Письмо готово. Измените что нужно и сохраните.\",\n \"The email could not be generated.\": \"Не удалось сгенерировать письмо.\",\n \"Pick what the email is for, or describe it.\": \"Выберите, для чего письмо, или опишите его.\",\n \"The AI declined to write this email. Try other details.\": \"ИИ отказался писать это письмо. Попробуйте указать другие подробности.\",\n \"The email was too long to finish. Ask for a shorter one.\": \"Письмо оказалось слишком длинным, и ИИ не успел его закончить. Попросите письмо покороче.\",\n \"The AI answer could not be read. Try again.\": \"Не удалось прочитать ответ ИИ. Попробуйте ещё раз.\",\n \"The generated email could not be used. Try again.\": \"Сгенерированное письмо не удалось использовать. Попробуйте ещё раз.\",\n \"Sales and promotions\": \"Распродажи и акции\",\n \"Holidays and occasions\": \"Праздники и поводы\",\n \"Events and appointments\": \"Мероприятия и встречи\",\n \"News and content\": \"Новости и контент\",\n \"Account and security\": \"Аккаунт и безопасность\",\n \"Orders and payments\": \"Заказы и оплата\",\n \"Customer relationship\": \"Работа с клиентами\",\n \"Business and organization\": \"Компания и организация\",\n \"Store sale\": \"Распродажа в магазине\",\n \"Flash sale\": \"Флеш-распродажа\",\n \"Discount code\": \"Промокод\",\n \"New product\": \"Новинка\",\n \"Back in stock\": \"Снова в наличии\",\n \"Price drop\": \"Снижение цены\",\n \"Abandoned cart\": \"Брошенная корзина\",\n \"Black Friday\": \"Чёрная пятница\",\n \"Loyalty reward\": \"Бонус для постоянных клиентов\",\n \"Refer a friend\": \"Приведи друга\",\n \"Christmas\": \"Рождество\",\n \"New Year\": \"Новый год\",\n \"Season's greetings\": \"Поздравление с праздниками\",\n \"Valentine's Day\": \"День святого Валентина\",\n \"Easter\": \"Пасха\",\n \"Mother's Day\": \"День матери\",\n \"Father's Day\": \"День отца\",\n \"Halloween\": \"Хеллоуин\",\n \"Back to school\": \"Снова в школу\",\n \"Summer holidays\": \"Летние каникулы\",\n \"Birthday wishes\": \"Поздравление с днём рождения\",\n \"Customer anniversary\": \"Годовщина сотрудничества\",\n \"Women's Day\": \"8 Марта\",\n \"Thanksgiving\": \"День благодарения\",\n \"Lunar New Year\": \"Лунный Новый год\",\n \"Ramadan and Eid\": \"Рамадан и Ураза-байрам\",\n \"Diwali\": \"Дивали\",\n \"Meeting reminder\": \"Напоминание о встрече\",\n \"Appointment confirmation\": \"Подтверждение записи\",\n \"Booking confirmation\": \"Подтверждение бронирования\",\n \"Webinar invitation\": \"Приглашение на вебинар\",\n \"Save the date\": \"Запомните дату\",\n \"Registration confirmed\": \"Регистрация подтверждена\",\n \"Cancellation or new date\": \"Отмена или перенос\",\n \"Thanks for attending\": \"Спасибо за участие\",\n \"New article\": \"Новая статья\",\n \"What's new\": \"Что нового\",\n \"Announcement\": \"Объявление\",\n \"Tips and how-to\": \"Советы и инструкции\",\n \"Email verification\": \"Подтверждение электронной почты\",\n \"Password reset\": \"Сброс пароля\",\n \"Login code\": \"Код для входа\",\n \"Security alert\": \"Оповещение безопасности\",\n \"Getting started\": \"Первые шаги\",\n \"Trial ending\": \"Окончание пробного периода\",\n \"Order confirmation\": \"Подтверждение заказа\",\n \"Order shipped\": \"Заказ отправлен\",\n \"Delivery delay\": \"Задержка доставки\",\n \"Receipt or invoice\": \"Чек или счёт\",\n \"Payment failed\": \"Платёж не прошёл\",\n \"Subscription renewal\": \"Продление подписки\",\n \"Refund issued\": \"Возврат оформлен\",\n \"Thank you\": \"Благодарность\",\n \"Review request\": \"Просьба оставить отзыв\",\n \"Survey\": \"Опрос\",\n \"We miss you\": \"Мы скучаем\",\n \"Follow-up\": \"Письмо вдогонку\",\n \"Apology\": \"Извинение\",\n \"We're hiring\": \"Открыты вакансии\",\n \"Policy update\": \"Обновление условий\",\n \"Price change\": \"Изменение цен\",\n \"Scheduled maintenance\": \"Плановые технические работы\",\n \"Closure or opening hours\": \"Закрытие или часы работы\",\n \"Donation appeal\": \"Сбор пожертвований\",\n \"Internal update\": \"Внутренние новости\",\n \"Rewrite with AI\": \"Переписать с помощью ИИ\",\n \"Shorter\": \"Короче\",\n \"Longer\": \"Длиннее\",\n \"Fix mistakes\": \"Исправить ошибки\",\n \"Friendlier\": \"Дружелюбнее\",\n \"More formal\": \"Официальнее\",\n \"Translate\": \"Перевести\",\n \"Translate into\": \"Перевести на\",\n \"Or say what to change…\": \"Или опишите, что изменить…\",\n \"What to change\": \"Что изменить\",\n \"Rewrite\": \"Переписать\",\n \"Rewriting…\": \"ИИ переписывает текст…\",\n \"Suggest subjects\": \"Предложить темы\",\n \"Subject ideas\": \"Идеи для темы\",\n \"More ideas\": \"Ещё идеи\",\n \"Done\": \"Готово\",\n \"yours\": \"ваша\",\n \"Reading the email…\": \"Чтение письма…\",\n \"No subject could be suggested.\": \"Не удалось предложить ни одной темы.\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"Сначала напишите письмо или тему: идеи строятся на их основе.\",\n \"The AI declined to suggest subjects for this email.\": \"ИИ отказался предлагать темы для этого письма.\",\n \"No usable subject came back. Try again.\": \"ИИ не вернул ни одной подходящей темы. Попробуйте ещё раз.\",\n \"Connect an AI provider first: it needs a key.\": \"Сначала подключите провайдера ИИ: нужен ключ.\",\n \"The AI provider refused the key. Check it, or use another one.\": \"Провайдер ИИ отклонил ключ. Проверьте его или используйте другой.\",\n \"The AI provider does not have this model. Choose another one.\": \"У провайдера ИИ нет такой модели. Выберите другую.\",\n \"The AI provider account has no credits left.\": \"На аккаунте провайдера ИИ закончились кредиты.\",\n \"The AI is busy right now. Try again in a minute.\": \"ИИ сейчас перегружен. Попробуйте через минуту.\",\n \"The AI provider could not answer. Try again.\": \"Провайдер ИИ не смог ответить. Попробуйте ещё раз.\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"Текст переписан. Ctrl+Z вернёт прежний вариант.\",\n \"The text changed in the meantime, so it was left as it is.\": \"Текст за это время изменился, поэтому его оставили как есть.\",\n \"The text could not be rewritten.\": \"Не удалось переписать текст.\",\n \"There is no text to rewrite.\": \"Нет текста для переписывания.\",\n \"The AI declined to rewrite this text.\": \"ИИ отказался переписывать этот текст.\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"В переписанном тексте пропала ссылка или переменная, поэтому ничего не изменено. Попробуйте ещё раз.\",\n \"Connect {provider}\": \"Подключить {provider}\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"ИИ работает с вашим собственным ключом {provider}. Подключите его один раз: этот браузер его запомнит.\",\n \"{provider} refused the last key. Paste another one.\": \"Провайдер {provider} отклонил последний ключ. Вставьте другой.\",\n \"Get a key\": \"Получить ключ\",\n \"API key\": \"API-ключ\",\n \"Paste the key\": \"Вставьте ключ\",\n \"Hide the key\": \"Скрыть ключ\",\n \"Show the key\": \"Показать ключ\",\n \"{provider} keys usually start with {prefix}.\": \"Ключи {provider} обычно начинаются с {prefix}.\",\n \"{provider} keys start with {prefix}.\": \"Ключи {provider} начинаются с {prefix}.\",\n \"The key stays in this browser and is sent only to {provider}.\": \"Ключ хранится в этом браузере и отправляется только провайдеру {provider}.\",\n \"Connect\": \"Подключить\",\n \"Free models, with a free account\": \"Бесплатные модели с бесплатным аккаунтом\",\n \"Free tier, with rate limits\": \"Бесплатный тариф с ограничением частоты запросов\",\n \"Free \\\"Experiment\\\" plan\": \"Бесплатный тариф «Experiment»\",\n \"Runs on your own machine\": \"Работает на вашем компьютере\",\n \"AI provider\": \"Провайдер ИИ\",\n \"Key saved\": \"Ключ сохранён\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"В этом браузере сохранён ключ {provider}: нажмите «Подключить», чтобы использовать его, или вставьте другой.\",\n \"Model\": \"Модель\",\n \"Choose a model\": \"Выбрать модель\",\n \"Free\": \"Бесплатно\",\n \"Free only\": \"Только бесплатные\",\n \"Search the models\": \"Поиск моделей\",\n \"Search the models…\": \"Поиск моделей…\",\n \"Use “{model}”\": \"Использовать «{model}»\",\n \"No model matches.\": \"Подходящих моделей нет.\",\n \"Loading the models of {provider}…\": \"Загрузка моделей {provider}…\",\n \"Paste the key to see all the models of {provider}.\": \"Вставьте ключ, чтобы увидеть все модели {provider}.\",\n \"{provider} refused this key: its models could not be listed.\": \"Провайдер {provider} отклонил этот ключ: не удалось получить список его моделей.\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"Не удалось загрузить список моделей. Модель по умолчанию работает — или введите идентификатор другой.\",\n \"{count} models, the free ones first.\": \"Моделей: {count}, сначала бесплатные.\",\n \"{count} models.\": \"Моделей: {count}.\",\n \"{provider} lists no models here: type the id of the one you want.\": \"Список моделей {provider} здесь недоступен: введите идентификатор нужной модели.\",\n};\n","// Chinese (Simplified) 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/zh.js. Missing keys: `node scripts/i18n-keys.mjs zh`.\n\n/** @type {Record<string, string>} */\nexport const zh = {\n \"Event invitation\": \"活动邀请\",\n \"Newsletter\": \"资讯简报\",\n \"Welcome\": \"欢迎邮件\",\n \"Generate\": \"生成\",\n \"Generate an email\": \"生成邮件\",\n \"Pick what it is for. AI writes a complete email that opens here, ready to edit.\": \"选择邮件用途,AI 会撰写一封完整的邮件并在此处打开,供您直接编辑。\",\n \"Search\": \"搜索\",\n \"Search: sale, password, birthday…\": \"搜索:促销、密码、生日……\",\n \"What is the email for?\": \"邮件用途是什么?\",\n \"Nothing matches. Describe your email in the details below.\": \"没有匹配项。请在下方的补充说明中描述您的邮件。\",\n \"Details (optional)\": \"补充说明(可选)\",\n \"Details (optional): product, dates, offer, tone, language…\": \"补充说明(可选):产品、日期、优惠、语气、语言……\",\n \"The email being edited will be replaced, unsaved changes included.\": \"当前正在编辑的邮件将被替换,包括未保存的更改。\",\n \"Writing your email… {seconds} s\": \"正在撰写邮件…… {seconds} 秒\",\n \"Your email is ready. Edit anything, then save.\": \"邮件已生成。可随意修改,然后保存。\",\n \"The email could not be generated.\": \"无法生成邮件。\",\n \"Pick what the email is for, or describe it.\": \"请选择邮件用途,或描述您的邮件。\",\n \"The AI declined to write this email. Try other details.\": \"AI 拒绝撰写这封邮件。请尝试其他补充说明。\",\n \"The email was too long to finish. Ask for a shorter one.\": \"邮件过长,未能写完。请要求写得简短一些。\",\n \"The AI answer could not be read. Try again.\": \"无法读取 AI 的回复,请重试。\",\n \"The generated email could not be used. Try again.\": \"生成的邮件无法使用,请重试。\",\n \"Sales and promotions\": \"销售与促销\",\n \"Holidays and occasions\": \"节日与纪念日\",\n \"Events and appointments\": \"活动与预约\",\n \"News and content\": \"资讯与内容\",\n \"Account and security\": \"账户与安全\",\n \"Orders and payments\": \"订单与支付\",\n \"Customer relationship\": \"客户关系\",\n \"Business and organization\": \"企业与组织\",\n \"Store sale\": \"店铺促销\",\n \"Flash sale\": \"限时抢购\",\n \"Discount code\": \"优惠码\",\n \"New product\": \"新品上市\",\n \"Back in stock\": \"到货提醒\",\n \"Price drop\": \"降价提醒\",\n \"Abandoned cart\": \"购物车提醒\",\n \"Black Friday\": \"黑色星期五\",\n \"Loyalty reward\": \"会员回馈\",\n \"Refer a friend\": \"邀请好友\",\n \"Christmas\": \"圣诞节\",\n \"New Year\": \"新年\",\n \"Season's greetings\": \"节日问候\",\n \"Valentine's Day\": \"情人节\",\n \"Easter\": \"复活节\",\n \"Mother's Day\": \"母亲节\",\n \"Father's Day\": \"父亲节\",\n \"Halloween\": \"万圣节\",\n \"Back to school\": \"开学季\",\n \"Summer holidays\": \"夏季假期\",\n \"Birthday wishes\": \"生日祝福\",\n \"Customer anniversary\": \"客户周年纪念\",\n \"Women's Day\": \"妇女节\",\n \"Thanksgiving\": \"感恩节\",\n \"Lunar New Year\": \"春节\",\n \"Ramadan and Eid\": \"斋月与开斋节\",\n \"Diwali\": \"排灯节\",\n \"Meeting reminder\": \"会议提醒\",\n \"Appointment confirmation\": \"预约确认\",\n \"Booking confirmation\": \"预订确认\",\n \"Webinar invitation\": \"网络研讨会邀请\",\n \"Save the date\": \"日程预告\",\n \"Registration confirmed\": \"报名成功\",\n \"Cancellation or new date\": \"取消或改期\",\n \"Thanks for attending\": \"感谢参加\",\n \"New article\": \"新文章发布\",\n \"What's new\": \"产品动态\",\n \"Announcement\": \"公告\",\n \"Tips and how-to\": \"技巧与教程\",\n \"Email verification\": \"邮箱验证\",\n \"Password reset\": \"重置密码\",\n \"Login code\": \"登录验证码\",\n \"Security alert\": \"安全提醒\",\n \"Getting started\": \"新手入门\",\n \"Trial ending\": \"试用即将到期\",\n \"Order confirmation\": \"订单确认\",\n \"Order shipped\": \"订单已发货\",\n \"Delivery delay\": \"配送延迟\",\n \"Receipt or invoice\": \"收据或发票\",\n \"Payment failed\": \"支付失败\",\n \"Subscription renewal\": \"订阅续费\",\n \"Refund issued\": \"退款已发放\",\n \"Thank you\": \"致谢\",\n \"Review request\": \"邀请评价\",\n \"Survey\": \"问卷调查\",\n \"We miss you\": \"我们想念您\",\n \"Follow-up\": \"跟进\",\n \"Apology\": \"致歉\",\n \"We're hiring\": \"招聘启事\",\n \"Policy update\": \"条款更新\",\n \"Price change\": \"价格调整\",\n \"Scheduled maintenance\": \"计划维护\",\n \"Closure or opening hours\": \"停业或营业时间调整\",\n \"Donation appeal\": \"募捐倡议\",\n \"Internal update\": \"内部通知\",\n \"Rewrite with AI\": \"AI 改写\",\n \"Shorter\": \"更简短\",\n \"Longer\": \"更详细\",\n \"Fix mistakes\": \"修正错误\",\n \"Friendlier\": \"更亲切\",\n \"More formal\": \"更正式\",\n \"Translate\": \"翻译\",\n \"Translate into\": \"翻译为\",\n \"Or say what to change…\": \"或者说明要修改的内容……\",\n \"What to change\": \"修改要求\",\n \"Rewrite\": \"改写\",\n \"Rewriting…\": \"正在改写……\",\n \"Suggest subjects\": \"推荐主题\",\n \"Subject ideas\": \"主题建议\",\n \"More ideas\": \"更多建议\",\n \"Done\": \"完成\",\n \"yours\": \"您的\",\n \"Reading the email…\": \"正在读取邮件……\",\n \"No subject could be suggested.\": \"未能推荐任何主题。\",\n \"Write the email or a subject first: the ideas are drawn from it.\": \"请先撰写邮件或主题:建议将基于其内容生成。\",\n \"The AI declined to suggest subjects for this email.\": \"AI 拒绝为这封邮件推荐主题。\",\n \"No usable subject came back. Try again.\": \"未返回可用的主题,请重试。\",\n \"Connect an AI provider first: it needs a key.\": \"请先连接 AI 服务商:需要提供密钥。\",\n \"The AI provider refused the key. Check it, or use another one.\": \"AI 服务商拒绝了该密钥。请检查密钥,或换用其他密钥。\",\n \"The AI provider does not have this model. Choose another one.\": \"该 AI 服务商没有此模型,请选择其他模型。\",\n \"The AI provider account has no credits left.\": \"AI 服务商账户的额度已用完。\",\n \"The AI is busy right now. Try again in a minute.\": \"AI 当前繁忙,请一分钟后重试。\",\n \"The AI provider could not answer. Try again.\": \"AI 服务商未能响应,请重试。\",\n \"Text rewritten. Ctrl+Z brings back the previous one.\": \"文本已改写。按 Ctrl+Z 可恢复原文。\",\n \"The text changed in the meantime, so it was left as it is.\": \"文本在此期间已被修改,因此保持不变。\",\n \"The text could not be rewritten.\": \"无法改写文本。\",\n \"There is no text to rewrite.\": \"没有可改写的文本。\",\n \"The AI declined to rewrite this text.\": \"AI 拒绝改写这段文本。\",\n \"The rewritten text lost a link or a variable, so nothing was changed. Try again.\": \"改写后的文本丢失了链接或变量,因此未做任何更改。请重试。\",\n \"Connect {provider}\": \"连接 {provider}\",\n \"The AI writes with your own {provider} key. Connect it once: this browser remembers it.\": \"AI 将使用您自己的 {provider} 密钥撰写内容。只需连接一次,此浏览器会记住密钥。\",\n \"{provider} refused the last key. Paste another one.\": \"{provider} 拒绝了上次使用的密钥,请粘贴其他密钥。\",\n \"Get a key\": \"获取密钥\",\n \"API key\": \"API 密钥\",\n \"Paste the key\": \"粘贴密钥\",\n \"Hide the key\": \"隐藏密钥\",\n \"Show the key\": \"显示密钥\",\n \"{provider} keys usually start with {prefix}.\": \"{provider} 密钥通常以 {prefix} 开头。\",\n \"{provider} keys start with {prefix}.\": \"{provider} 密钥以 {prefix} 开头。\",\n \"The key stays in this browser and is sent only to {provider}.\": \"密钥仅保存在此浏览器中,且只会发送给 {provider}。\",\n \"Connect\": \"连接\",\n \"Free models, with a free account\": \"注册免费账户即可使用免费模型\",\n \"Free tier, with rate limits\": \"免费套餐,有调用频率限制\",\n \"Free \\\"Experiment\\\" plan\": \"免费的“Experiment”套餐\",\n \"Runs on your own machine\": \"在您自己的电脑上运行\",\n \"AI provider\": \"AI 服务商\",\n \"Key saved\": \"密钥已保存\",\n \"A key for {provider} is saved in this browser: Connect uses it, or paste another one.\": \"此浏览器已保存 {provider} 密钥:点击“连接”即可使用,也可粘贴其他密钥。\",\n \"Model\": \"模型\",\n \"Choose a model\": \"选择模型\",\n \"Free\": \"免费\",\n \"Free only\": \"仅显示免费\",\n \"Search the models\": \"搜索模型\",\n \"Search the models…\": \"搜索模型……\",\n \"Use “{model}”\": \"使用“{model}”\",\n \"No model matches.\": \"没有匹配的模型。\",\n \"Loading the models of {provider}…\": \"正在加载 {provider} 的模型……\",\n \"Paste the key to see all the models of {provider}.\": \"粘贴密钥即可查看 {provider} 的所有模型。\",\n \"{provider} refused this key: its models could not be listed.\": \"{provider} 拒绝了此密钥,无法列出其模型。\",\n \"The list of models could not be loaded. The default model works, or type the id of another.\": \"无法加载模型列表。可以使用默认模型,或输入其他模型的 ID。\",\n \"{count} models, the free ones first.\": \"共 {count} 个模型,免费模型优先显示。\",\n \"{count} models.\": \"共 {count} 个模型。\",\n \"{provider} lists no models here: type the id of the one you want.\": \"{provider} 未在此列出任何模型,请输入所需模型的 ID。\",\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 { de } from './de.js';\nimport { es } from './es.js';\nimport { fr } from './fr.js';\nimport { pt } from './pt.js';\nimport { ru } from './ru.js';\nimport { zh } from './zh.js';\n\n/** @type {Record<string, Record<string, string>>} */\nexport const AI_MESSAGES = { en: {}, fr, de, es, pt, ru, zh };\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', user?: any}) => 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). `user`: on the server, the one\n * the handler's `authorize` returned (agent.js forwards who asks).\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, user?: any}} [options]\n * @returns {Promise<GeneratedEmail>}\n */\n async function generate(input, { signal, user } = {}) {\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', user });\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 * publicModels?: boolean,\n * createModel?: (options: {apiKey: () => string|Promise<string>, fetch?: typeof fetch, model?: string}) => import('./generator.js').EmailModel,\n * listModels?: (options: {apiKey: string, fetch?: typeof fetch, signal?: AbortSignal}) => Promise<ModelEntry[]>,\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), and `listModels`\n * lists its models; `publicModels`: its list of models needs no key.\n *\n * @typedef {{id: string, name?: string, free?: boolean, created?: number}} ModelEntry\n * @typedef {Readonly<{id: string, name: string, free: boolean}>} ModelChoice\n * A model a provider offers: the id requests name it by, the name it shows,\n * whether it costs nothing (known for OpenRouter only).\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 publicModels: true,\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/**\n * Model ids that do not write text: embeddings, speech, pictures, video,\n * music, live audio, moderation, search and agents' tools.\n */\nconst NOT_A_WRITER =\n /embed|tts|audio|transcri|whisper|speech|voxtral|image|imagen|dall-e|sora|veo|lyria|music|moderation|realtime|(^|[-_/.])live($|[-_.])|search|computer-use|robotics|\\baqa\\b|nano-banana|deep-research|antigravity|guard|safety|ocr|rerank|translate|customtools|codex|^(davinci|babbage)|turbo-instruct/i;\n\n/** An entry of a `/models` list that writes text. @param {any} entry */\nfunction writes(entry) {\n const id = typeof entry?.id === 'string' ? entry.id : '';\n if (!id || NOT_A_WRITER.test(id)) return false;\n // OpenRouter says what comes out; Mistral, whether it chats.\n const outputs = entry.architecture?.output_modalities;\n if (Array.isArray(outputs) && !outputs.every((output) => output === 'text')) return false;\n return entry.capabilities?.completion_chat !== false;\n}\n\n/** @param {any} entry @returns {ModelEntry} */\nconst entryOf = (entry) => {\n const id = String(entry.id).replace(/^models\\//, '');\n const price = entry.pricing;\n return {\n id,\n name: String(entry.display_name || entry.name || id),\n free: /:free$/.test(id) || (price != null && Number(price.prompt) === 0 && Number(price.completion) === 0),\n created: Number(entry.created) || (entry.created_at ? Date.parse(entry.created_at) / 1000 : 0) || 0,\n };\n};\n\n/** The version an id carries (`gemini-3.5-flash` → 3.5), for lists without dates. @param {string} id */\nconst versionOf = (id) => Number(/(\\d+(?:\\.\\d+)?)/.exec(id)?.[1] || 0);\n\n/**\n * The models in the order a user looks for them: the default one first, then\n * the free ones, then the newest (by date, else by the version in the id).\n * Each once.\n * @param {ModelEntry[]} entries @param {string|null} defaultModel\n * @returns {ModelChoice[]}\n */\nfunction orderModels(entries, defaultModel) {\n const seen = new Set();\n const models = entries.filter((entry) => entry?.id && !seen.has(entry.id) && seen.add(entry.id));\n const rank = (/** @type {ModelEntry} */ entry) => (entry.id === defaultModel ? 0 : entry.free ? 1 : 2);\n const dated = models.some((entry) => entry.created);\n return models\n .map((entry, at) => ({ entry, at }))\n .sort(\n (a, b) =>\n rank(a.entry) - rank(b.entry) ||\n (dated ? (b.entry.created || 0) - (a.entry.created || 0) : versionOf(b.entry.id) - versionOf(a.entry.id)) ||\n a.at - b.at\n )\n .map(({ entry }) => Object.freeze({ id: entry.id, name: entry.name || entry.id, free: Boolean(entry.free) }));\n}\n\n/**\n * The models a provider offers for writing, from its list of models (the\n * `/models` of an OpenAI-compatible API — OpenRouter's needs no key — or the\n * provider's own `listModels`): the default first, then the free ones, then\n * the newest. Embeddings, speech, pictures and video are left out. Its\n * failures are the errors of `providerEmailModel` (`KEY_REFUSED`…).\n *\n * listProviderModels({ provider: 'gemini', apiKey })\n *\n * @param {{provider?: string|Partial<AiProvider>, apiKey?: string, fetch?: typeof fetch, signal?: AbortSignal}} [options]\n * @returns {Promise<ModelChoice[]>}\n */\nexport async function listProviderModels(options = {}) {\n const { apiKey = '', fetch: fetchImpl, signal } = options;\n const provider = resolveProvider(options.provider ?? 'openai-compatible');\n if (provider.listModels) return orderModels(await provider.listModels({ apiKey, fetch: fetchImpl, signal }), provider.model);\n const baseUrl = String(provider.baseUrl).replace(/\\/+$/, '');\n if (!baseUrl) return [];\n if (!apiKey && !provider.keyless && !provider.publicModels) throw new EmailGenerationError('KEY_MISSING', `${provider.name}: no key`);\n /** @type {Response} */\n let response;\n try {\n response = await (fetchImpl || globalThis.fetch)(`${baseUrl}/models`, {\n headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...provider.headers },\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 throw new EmailGenerationError(failureCode(response.status, errorMessage(text)), `${provider.name} ${response.status}: ${text.slice(0, 300)}`);\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 const entries = Array.isArray(data?.data) ? data.data : Array.isArray(data?.models) ? data.models : [];\n return orderModels(entries.filter(writes).map(entryOf), provider.model);\n}\n\n/** The instruction every answer gets: the JSON, nothing else. @param {object} schema */\n/** The answer format, said in words: for a model without structured output. @param {object} schema */\nexport const 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 * A model the provider does not have (any more): a 404 about a model\n * (Gemini, Anthropic, Groq), or a 400 that says the id is not one\n * (OpenRouter). The user chose it, so the user must choose another.\n * @param {number} status @param {string} message\n */\nconst missingModel = (status, message) =>\n (status === 404 && /\\bmodel/i.test(message)) ||\n ((status === 400 || status === 404) && /not a valid model|(unknown|invalid) model|model\\b.*\\b(not found|does not exist|no longer available)/i.test(message));\n\n/**\n * The error code of a failed request, from its status and the provider's\n * message: the refusals above, a model that is not there, the money (402, a\n * quota, a credit balance too low — Anthropic says it with a 400, OpenAI with\n * a 429), busy, or an error.\n * @param {number} status @param {string} message\n * @returns {'KEY_REFUSED'|'REFUSED'|'MODEL_UNAVAILABLE'|'MODEL_CREDITS'|'MODEL_BUSY'|'MODEL_ERROR'}\n */\nexport function failureCode(status, message) {\n if (missingModel(status, message)) return 'MODEL_UNAVAILABLE';\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 throw sdkFailure(error, signal);\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 * The SDK's errors, as the errors the interface explains (a refused key is\n * asked again); an abort goes through as it is.\n * @param {unknown} error @param {AbortSignal} [signal]\n */\nfunction sdkFailure(error, signal) {\n const { status, name } = /** @type {any} */ (error) || {};\n if (signal?.aborted || name === 'AbortError' || name === 'APIUserAbortError' || error instanceof EmailGenerationError) return 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') return new EmailGenerationError('MODEL_ERROR', `Claude: ${text.slice(0, 300)}`);\n return new EmailGenerationError(failureCode(status, text), `Claude ${status}: ${text.slice(0, 300)}`);\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`…). The user can\n * choose another of the models their key opens, in the Connect dialog.\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, model }) => {\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) {\n current = { key, model: claudeEmailModel({ ...rest, ...(model ? { model } : {}), client: await client(key) }) };\n }\n return current.model(request);\n };\n },\n listModels: async ({ apiKey, signal }) => {\n const key = String(apiKey || '').trim();\n if (!key) throw new EmailGenerationError('KEY_MISSING', 'Claude: no key');\n try {\n const page = await (await client(key)).models.list({ limit: 100 }, { signal });\n return (page?.data || []).map((/** @type {any} */ entry) => ({\n id: String(entry.id),\n name: String(entry.display_name || entry.id),\n created: entry.created_at ? Date.parse(entry.created_at) / 1000 : 0,\n }));\n } catch (error) {\n throw sdkFailure(error, signal);\n }\n },\n });\n}\n","// @ts-check\n/**\n * YOUR OWN AI AGENT — the assistant an application already has (its tools,\n * its knowledge, its rights) writes the builder's emails, rewrites its texts\n * and suggests its subjects, instead of a model called directly.\n *\n * // On the server: the handler's AI routes, the agent behind them.\n * createEmailBuilderHandler({\n * …,\n * generator: createEmailGenerator({\n * model: agentEmailModel({\n * url: 'http://agent.internal/email-builder',\n * headers: ({ user }) => ({ Authorization: `Bearer ${user.token}` }),\n * }),\n * compile: compileMjml,\n * }),\n * });\n * // and in the page: createAi({ baseUrl: '/api/email-builder' })\n *\n * // Or in the page, through a route of the application that adds the session:\n * createAi({ model: agentEmailModel({ url: '/api/agent/email-builder' }) })\n *\n * THE PROTOCOL, `best-email-builder/agent@1`: one POST per model turn.\n *\n * → { protocol, task, system, messages, schema }\n * task 'generate' | 'rewrite' | 'subjects'\n * system the instructions, the answer format included: a model\n * given `system` and `messages` as they are answers right\n * messages [{ role: 'user'|'assistant', content }]; a repair turn adds\n * the last answer and the problems found in it\n * schema the JSON schema of the answer, for structured output\n * ← 200 { text: '<the answer, JSON>' } or { output: {…the answer} }\n * ← 4xx/5xx { error: 'what happened', code?: 'REFUSED' | 'MODEL_BUSY' | … }\n *\n * The agent decides how to answer: one model call, or its own tools first (a\n * knowledge base, the customer's data). Its answer is then treated as any\n * model's — read, cleaned, checked, and sent back once for repair when it\n * has problems — so the agent needs to know nothing of MJML.\n *\n * WHO ASKS is never in the body (an agent must not trust it): on the server,\n * `headers({ task, user })` gets the user the handler's `authorize` returned,\n * to forward the identity the agent applies its rights with.\n *\n * The agent's side: `createAgentEndpoint` (agentEndpoint.js) for an agent in\n * JavaScript, the FastAPI router of examples/agent for one in Python; and\n * `checkAgent` to try an agent with the builder's own checks.\n */\n\nimport { EmailGenerationError } from './draft.js';\nimport { answerInstruction } from './providers.js';\n\nexport const AGENT_PROTOCOL = 'best-email-builder/agent@1';\n\n/** The errors an agent can name; any other is read from the status. */\nexport const AGENT_ERROR_CODES = Object.freeze(['REFUSED', 'TRUNCATED', 'MODEL_UNAVAILABLE', 'MODEL_CREDITS', 'MODEL_BUSY', 'MODEL_ERROR']);\n\n/** Three minutes: an agent may search before it writes. */\nconst DEFAULT_TIMEOUT = 180_000;\n\n/**\n * @typedef {{task: 'generate'|'rewrite'|'subjects', user?: any}} AgentCallContext\n * @typedef {Record<string, string> | ((context: AgentCallContext) => Record<string, string> | Promise<Record<string, string>>)} AgentHeaders\n */\n\n/** @param {number} status @returns {'MODEL_BUSY'|'MODEL_ERROR'} */\nconst statusCode = (status) => (status === 429 || status === 503 || status === 504 || status === 529 ? 'MODEL_BUSY' : 'MODEL_ERROR');\n\n/** @param {unknown} code @returns {code is typeof AGENT_ERROR_CODES[number]} */\nconst isAgentCode = (code) => typeof code === 'string' && AGENT_ERROR_CODES.includes(/** @type {any} */ (code));\n\n/** @param {any} data @returns {string} */\nconst errorText = (data) => {\n const error = data?.error ?? data?.detail;\n if (typeof error === 'string') return error;\n return String(error?.message || (error ? JSON.stringify(error) : ''));\n};\n\n/**\n * @param {{\n * url: string,\n * headers?: AgentHeaders,\n * fetch?: typeof fetch,\n * timeout?: number,\n * name?: string,\n * }} options\n * `headers`: added to each request — or a function of `{ task, user }` that\n * gives them (a token signed for that user). `timeout`: the longest wait\n * for one answer, in ms (3 minutes; 0 for none). `name`: the agent's, in\n * the details of its errors.\n * @returns {import('./generator.js').EmailModel & {url: string}}\n */\nexport function agentEmailModel(options) {\n const { url, headers, fetch: fetchImpl, timeout = DEFAULT_TIMEOUT, name = 'Agent' } = options || /** @type {any} */ ({});\n if (typeof url !== 'string' || !url) throw new TypeError('agentEmailModel: `url` is required — the endpoint of your agent');\n if (headers !== undefined && typeof headers !== 'function' && (typeof headers !== 'object' || headers === null)) {\n throw new TypeError('agentEmailModel: `headers` is an object, or a function of { task, user } that returns one');\n }\n\n /** @type {import('./generator.js').EmailModel} */\n const emailModel = async ({ system, messages, schema, signal, task = 'generate', user }) => {\n const call = fetchImpl || globalThis.fetch;\n const own = typeof headers === 'function' ? await headers({ task, user }) : headers;\n signal?.throwIfAborted();\n\n // The caller's signal (the user closed the dialog) and the timeout, as one.\n const controller = new AbortController();\n const stop = () => controller.abort(signal?.reason);\n signal?.addEventListener('abort', stop, { once: true });\n let late = false;\n const timer = timeout > 0 ? setTimeout(() => ((late = true), controller.abort()), timeout) : null;\n\n /** @type {Response} */\n let response;\n let text;\n try {\n response = await call(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...own },\n body: JSON.stringify({ protocol: AGENT_PROTOCOL, task, system: system + answerInstruction(schema), messages, schema }),\n signal: controller.signal,\n });\n text = await response.text();\n } catch (error) {\n if (signal?.aborted) throw signal.reason ?? error;\n if (late) throw new EmailGenerationError('MODEL_BUSY', `${name}: no answer after ${Math.round(timeout / 1000)} s`);\n throw new EmailGenerationError('MODEL_ERROR', `${name}: ${/** @type {any} */ (error)?.message || error}`);\n } finally {\n if (timer) clearTimeout(timer);\n signal?.removeEventListener('abort', stop);\n }\n\n /** @type {any} */\n let data = null;\n try {\n data = JSON.parse(text);\n } catch {\n // Not JSON: the answer itself, as text — unless the agent failed.\n }\n if (!response.ok || (data && typeof data === 'object' && 'error' in data && !('text' in data) && !('output' in data))) {\n const said = data ? errorText(data) : text;\n const detail = `${name} ${response.status}: ${said.slice(0, 300)}`;\n throw new EmailGenerationError(isAgentCode(data?.code) ? data.code : statusCode(response.ok ? 502 : response.status), detail);\n }\n if (typeof data?.text === 'string') return data.text;\n if (data?.output && typeof data.output === 'object') return JSON.stringify(data.output);\n // The answer as the whole body (the agent passed its model's text on).\n return text;\n };\n return Object.assign(emailModel, { url });\n}\n"],"mappings":"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","names":["text","purpose","text","bounded","text","MAX_VARIABLES","purpose","text","text","text"]}