best-email-builder 0.4.0 → 0.5.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 (81) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +119 -44
  3. package/dist/ai-server.js +68 -0
  4. package/dist/ai-server.js.map +1 -0
  5. package/dist/ai.js +852 -0
  6. package/dist/ai.js.map +1 -0
  7. package/dist/builder.js +16 -8
  8. package/dist/chunk-2RZHGAH5.js +64 -0
  9. package/dist/chunk-2RZHGAH5.js.map +1 -0
  10. package/dist/chunk-3LKF4QBL.js +595 -0
  11. package/dist/chunk-3LKF4QBL.js.map +1 -0
  12. package/dist/chunk-5SASKCUC.js +251 -0
  13. package/dist/chunk-5SASKCUC.js.map +1 -0
  14. package/dist/{chunk-NAEGU6PR.js → chunk-6D3SLT5W.js} +18 -29
  15. package/dist/chunk-6D3SLT5W.js.map +1 -0
  16. package/dist/chunk-7U6BAKM7.js +119 -0
  17. package/dist/chunk-7U6BAKM7.js.map +1 -0
  18. package/dist/{chunk-HHLOE6OZ.js → chunk-DFNHICTF.js} +10 -333
  19. package/dist/chunk-DFNHICTF.js.map +1 -0
  20. package/dist/chunk-ECHGJZ2W.js +327 -0
  21. package/dist/chunk-ECHGJZ2W.js.map +1 -0
  22. package/dist/chunk-KXJJSVN5.js +98 -0
  23. package/dist/chunk-KXJJSVN5.js.map +1 -0
  24. package/dist/{chunk-AT73Y7JA.js → chunk-LXR4QTGP.js} +49 -581
  25. package/dist/chunk-LXR4QTGP.js.map +1 -0
  26. package/dist/{chunk-TGDZ4NFI.js → chunk-LYMJ2TBX.js} +3 -44
  27. package/dist/chunk-LYMJ2TBX.js.map +1 -0
  28. package/dist/{chunk-JNHFZXDL.js → chunk-MPX5CWTZ.js} +560 -649
  29. package/dist/chunk-MPX5CWTZ.js.map +1 -0
  30. package/dist/{chunk-MY6WPDT4.js → chunk-PB7YZ3WP.js} +463 -938
  31. package/dist/chunk-PB7YZ3WP.js.map +1 -0
  32. package/dist/chunk-UFKCLU2D.js +255 -0
  33. package/dist/chunk-UFKCLU2D.js.map +1 -0
  34. package/dist/index.js +31 -37
  35. package/dist/index.js.map +1 -1
  36. package/dist/library.js +12 -7
  37. package/dist/server.js +33 -74
  38. package/dist/server.js.map +1 -1
  39. package/dist/styles.css +1 -1
  40. package/dist/types/ai/ConnectAi.d.ts +12 -0
  41. package/dist/types/ai/GenerateButton.d.ts +4 -0
  42. package/dist/types/ai/SubjectIdeas.d.ts +25 -0
  43. package/dist/types/ai/createAi.d.ts +66 -0
  44. package/dist/types/{features/generate → ai}/draft.d.ts +44 -3
  45. package/dist/types/{features/generate → ai}/generator.d.ts +7 -2
  46. package/dist/types/ai/i18n/fr.d.ts +2 -0
  47. package/dist/types/ai/i18n/index.d.ts +2 -0
  48. package/dist/types/ai/index.d.ts +9 -0
  49. package/dist/types/ai/keys.d.ts +46 -0
  50. package/dist/types/ai/providers.d.ts +67 -0
  51. package/dist/types/{features/generate → ai}/rewrite.d.ts +1 -1
  52. package/dist/types/ai/server.d.ts +9 -0
  53. package/dist/types/{features/generate → ai}/subjects.d.ts +18 -4
  54. package/dist/types/core/constants.d.ts +1 -1
  55. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  56. package/dist/types/features/preview/darkMode.d.ts +33 -16
  57. package/dist/types/features/variables/VariablePicker.d.ts +3 -2
  58. package/dist/types/i18n/index.d.ts +8 -0
  59. package/dist/types/index.d.ts +1 -7
  60. package/dist/types/server/handler.d.ts +5 -4
  61. package/dist/types/server/index.d.ts +1 -8
  62. package/dist/types/storage/client.d.ts +22 -40
  63. package/dist/types/ui/EmailBuilder.d.ts +5 -1
  64. package/dist/types/ui/EmailStudio.d.ts +6 -1
  65. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  66. package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
  67. package/package.json +15 -1
  68. package/dist/chunk-AT73Y7JA.js.map +0 -1
  69. package/dist/chunk-HHLOE6OZ.js.map +0 -1
  70. package/dist/chunk-JNHFZXDL.js.map +0 -1
  71. package/dist/chunk-MY6WPDT4.js.map +0 -1
  72. package/dist/chunk-NAEGU6PR.js.map +0 -1
  73. package/dist/chunk-TGDZ4NFI.js.map +0 -1
  74. package/dist/types/features/generate/GenerateButton.d.ts +0 -3
  75. package/dist/types/features/generate/SubjectIdeas.d.ts +0 -16
  76. package/dist/types/features/generate/index.d.ts +0 -2
  77. /package/dist/types/{features/generate → ai}/RewriteText.d.ts +0 -0
  78. /package/dist/types/{features/generate → ai}/chip.d.ts +0 -0
  79. /package/dist/types/{features/generate → ai}/claude.d.ts +0 -0
  80. /package/dist/types/{features/generate → ai}/prompt.d.ts +0 -0
  81. /package/dist/types/{features/generate → ai}/purposes.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,63 @@ All notable changes are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] — 2026-10-15
10
+
11
+ ### Changed — the AI is a module (breaking)
12
+ - Everything AI moved to its own entries: `best-email-builder/ai` (the
13
+ builder's side) and `best-email-builder/ai/server` (the generator, the
14
+ prompts, `claudeEmailModel`). An application that does not import them has
15
+ no AI button anywhere, and none of the AI's code or texts in its bundle.
16
+ - The builder takes the module as a prop: `<EmailBuilder ai={createAi()} />`
17
+ (`EmailStudio` passes it on). `createAi()` calls the handler's routes;
18
+ `createAi({ baseUrl, headers })` other ones; `generate`, `rewrite` and
19
+ `suggestSubjects` can each be `false` (no button) or a function of your own.
20
+ - Removed: `createHttpStorage({ generate: true })` and the storage's
21
+ `generate`, `rewrite` and `suggestSubjects`; `createEmailGenerator`,
22
+ `claudeEmailModel` and the prompts are no longer exported by
23
+ `best-email-builder/server` — import them from `best-email-builder/ai/server`.
24
+ The handler still takes the generator as `generator`.
25
+
26
+ ### Added
27
+ - **AI providers by their key**, nothing to install: `providerEmailModel({ provider, apiKey })`
28
+ and `createAi({ provider, … })` for OpenRouter (default `openrouter/free`,
29
+ free models), Google Gemini, Groq, Cerebras, Mistral, OpenAI, DeepSeek,
30
+ Ollama and any OpenAI-compatible API (`{ baseUrl, model }`). Structured
31
+ output where the model takes it, stepped down to a JSON object, then plain
32
+ JSON, once. `AI_PROVIDERS` lists them with their default model, where to get
33
+ a key and what is free. New errors, translated: `KEY_MISSING`,
34
+ `KEY_REFUSED`, `MODEL_CREDITS`, `MODEL_BUSY`, `MODEL_ERROR`.
35
+ - **Each user's own key**: `createAi({ provider, askKey: true })` (off by
36
+ default) — the first AI action opens a *Connect* dialog (the provider, a
37
+ link to get a key, what is free, the key hidden or shown), the key is kept
38
+ in that browser and sent only to the provider; a refused key is forgotten
39
+ and asked again in the same action. `ai.key` reads and changes it.
40
+ - **Ctrl K** (⌘K on Apple) puts the cursor in the search of *Generate* and of
41
+ the variables menu, as in the template library; each search shows the hint.
42
+ - The live demo runs the AI with the visitor's own OpenRouter key, asked in
43
+ the *Connect* dialog instead of the browser's prompt; its code samples have
44
+ a *With AI* tab.
45
+
46
+ ### Fixed
47
+ - Dark-mode preview: text and lines are now decided against the background
48
+ they really sit on (nested tables, `bgcolor`, CSS), by contrast; legacy
49
+ attributes (`<font color>`, `<body text link>`, `bgcolor` without `#`),
50
+ `hsl()` colors and per-rule CSS are handled; comments, Outlook-only blocks
51
+ and the doctype are left alone; the dark sheet goes in the real `<head>`.
52
+ - *Suggest subjects*: a subject quoting itself, a list introduced by a line of
53
+ prose, entities in the email's words, a merge tag of another tool
54
+ (`*|FNAME|*`, `%name%`), more than 90 characters, capitals the email does not
55
+ use, "!!" or the preheader repeated are no longer offered; the language
56
+ falls back to the server's. In the list, what the user types becomes
57
+ "yours", another email loaded closes it, "More ideas" avoids every idea
58
+ already shown and keeps the chosen one, the arrows move through it, and a
59
+ cancelled request can no longer end the one after it.
60
+ - MJML a model escaped twice (`\"`, `\n` left in the markup — some free
61
+ models do) is read as MJML instead of failing the draft or the rewrite.
62
+ - Types: `createEmailGenerator()`'s `rewrite` and `suggestSubjects` named
63
+ input types the declaration file did not import (a TypeScript error without
64
+ `skipLibCheck`).
65
+
9
66
  ## [0.4.0] — 2026-10-15
10
67
 
11
68
  ### Changed — the builder's own engine (breaking)
package/README.md CHANGED
@@ -59,7 +59,9 @@ Best Email Builder is the second option done once, properly, and given away:
59
59
  shipped…), adds a line of details if they want, and a complete email opens in
60
60
  the editor, subject and preview text included, ready to edit and save. The
61
61
  model's MJML is cleaned and checked like any template before it gets there.
62
- Claude out of the box; any other model in a few lines.
62
+ OpenRouter, Gemini, Groq, Mistral, OpenAI, DeepSeek, Cerebras, Ollama or any
63
+ OpenAI-compatible API by its key — free tiers included — and Claude through
64
+ its SDK; on your server, or in the page with each user's own key.
63
65
  - **Any text rewritten in one click** — select a text or a button: *Shorter*,
64
66
  *Longer*, *Fix mistakes*, *Friendlier*, *More formal*, *Translate* into 13
65
67
  languages, or say what to change in your own words. The text changes in
@@ -67,6 +69,8 @@ Best Email Builder is the second option done once, properly, and given away:
67
69
  - **Three subject lines on demand** — drawn from the words of the email, each
68
70
  from its own angle; a click puts one in the subject, your own stays in the
69
71
  list to come back to.
72
+ - **The AI is optional** — a module of its own (`best-email-builder/ai`):
73
+ without it, no AI button, and none of its code in your bundle.
70
74
  - **Your brand in every email** — give your website: the logo, colors and
71
75
  font are read from it (or set by hand), and new emails, templates and
72
76
  blocks start in them.
@@ -78,7 +82,8 @@ Best Email Builder is the second option done once, properly, and given away:
78
82
  uploads, server preview with quality checks (missing alt text, missing
79
83
  unsubscribe link, unknown variables, Gmail clipping…) and a dark-mode view
80
84
  (as the Gmail and Outlook apps repaint emails), test sends.
81
- - **Merge-tag variables your users create themselves** — searchable menu,
85
+ - **Merge-tag variables your users create themselves** — searchable menu
86
+ (Ctrl K, like every search of the builder),
82
87
  insertion at the caret, in the subject line too, sample values for the
83
88
  preview and fallbacks for recipients without a value.
84
89
  - **Framework-agnostic server**: one Fetch-API handler for Next.js, Hono, Bun,
@@ -298,29 +303,43 @@ const subject = applyVariablesToText(template.subject, values, variables);
298
303
  With an email provider that does its own merge, map the tags instead:
299
304
  `mapVariables(html, { 'contact.firstname': '{{ contact.FIRSTNAME }}' })`.
300
305
 
301
- ### 4. Generating emails with AI (optional)
306
+ ### 4. AI (an optional module)
302
307
 
303
- The *Generate* button asks one thing: what the email is for (a badge among
304
- 68, grouped: sales, holidays, events, news, account, orders, relationship,
305
- organization), with an optional line of details. The email is written in the
306
- interface's language, with the builder's sender and variables, and replaces
307
- the one being edited — the dialog says so before, not after.
308
+ The AI is a module of its own, `best-email-builder/ai`. An application that
309
+ does not import it has no AI button anywhere, and none of the AI's code or
310
+ texts in its bundle. Given to the builder, it brings three things:
308
311
 
309
- The model runs on the server, where its key is:
312
+ - **Generate** (toolbar) — asks one thing: what the email is for (a badge
313
+ among 68, grouped: sales, holidays, events, news, account, orders,
314
+ relationship, organization), with an optional line of details. The email is
315
+ written in the interface's language, with the builder's sender and
316
+ variables, and replaces the one being edited — the dialog says so before,
317
+ not after.
318
+ - **Rewrite with AI** (properties of a text or a button) and **Suggest
319
+ subjects** (under the subject), below.
320
+
321
+ ```jsx
322
+ import { createAi } from 'best-email-builder/ai';
323
+
324
+ <EmailBuilder storage={storage} ai={createAi(/* where the model runs, below */)} />
325
+ ```
326
+
327
+ Pass `generate: false`, `rewrite: false` or `suggestSubjects: false` to leave
328
+ a part out (its button with it), or functions of your own for any of them.
329
+
330
+ #### Where the model runs
331
+
332
+ **On your server** — the key stays there; the right choice for production:
310
333
 
311
334
  ```js
312
- import Anthropic from '@anthropic-ai/sdk'; // npm install @anthropic-ai/sdk
313
- import {
314
- createEmailBuilderHandler,
315
- createEmailGenerator,
316
- claudeEmailModel,
317
- compileMjml,
318
- } from 'best-email-builder/server';
335
+ // server
336
+ import { createEmailBuilderHandler, compileMjml } from 'best-email-builder/server';
337
+ import { createEmailGenerator, providerEmailModel } from 'best-email-builder/ai/server';
319
338
 
320
339
  const handler = createEmailBuilderHandler({
321
340
  /* … */
322
341
  generator: createEmailGenerator({
323
- model: claudeEmailModel({ client: new Anthropic() }), // ANTHROPIC_API_KEY
342
+ model: providerEmailModel({ provider: 'openrouter', apiKey: process.env.OPENROUTER_API_KEY }),
324
343
  compile: compileMjml, // MJML's own warnings go back to the model
325
344
  }),
326
345
  rateLimits: { generatePerHour: 30, rewritePerHour: 200, subjectsPerHour: 100 }, // per user (defaults)
@@ -328,24 +347,83 @@ const handler = createEmailBuilderHandler({
328
347
  ```
329
348
 
330
349
  ```jsx
331
- <EmailBuilder storage={createHttpStorage({ generate: true })} />
350
+ // browser: the handler's /generate, /rewrite and /subjects routes
351
+ <EmailBuilder storage={createHttpStorage()} ai={createAi()} />
352
+ ```
353
+
354
+ `createAi({ baseUrl, headers })` points at the same routes elsewhere, or with
355
+ the user's credentials. Without a `generator`, those routes answer 501.
356
+
357
+ **In the page, with each user's own key** — no server needed: the first AI
358
+ action opens a *Connect* dialog (the provider, a link to get a key, what is
359
+ free), the key is kept in that browser and sent only to the provider. A key
360
+ the provider refuses is forgotten and asked again. Off by default:
361
+
362
+ ```jsx
363
+ const ai = createAi({ provider: 'openrouter', askKey: true });
364
+ ```
365
+
366
+ `ai.key` reads and changes it: `get()`, `set(key)`, `forget()`,
367
+ `request()`, `subscribe(fn)`, `state()` (`{ asking, refused, connected }`).
368
+ This is what the live demo does.
369
+
370
+ **In the page, with one key for everyone** — prototypes and internal tools
371
+ only, the key is in the page's code: `createAi({ provider: 'groq', apiKey })`.
372
+
373
+ #### Providers
374
+
375
+ Every provider here speaks the OpenAI *chat completions* API; nothing to
376
+ install. `provider` is an id below, or `{ id, model, baseUrl, headers }` to
377
+ change the model or reach an endpoint of your own. `AI_PROVIDERS` lists them
378
+ (name, address, default model, where to get a key, what is free).
379
+
380
+ | `provider` | Default model | Free to start | Key |
381
+ |---|---|---|---|
382
+ | `'openrouter'` | `openrouter/free` (a router over the free models of the day) | free models, with a free account | [openrouter.ai/keys](https://openrouter.ai/keys) |
383
+ | `'gemini'` | `gemini-3.5-flash-lite` | free tier, rate-limited | [aistudio.google.com/apikey](https://aistudio.google.com/apikey) |
384
+ | `'groq'` | `openai/gpt-oss-120b` | free tier, rate-limited | [console.groq.com/keys](https://console.groq.com/keys) |
385
+ | `'cerebras'` | `gpt-oss-120b` | free tier, rate-limited | [cloud.cerebras.ai](https://cloud.cerebras.ai/) |
386
+ | `'mistral'` | `mistral-small-latest` | free *Experiment* plan | [console.mistral.ai/api-keys](https://console.mistral.ai/api-keys) |
387
+ | `'openai'` | `gpt-5-mini` | — | [platform.openai.com/api-keys](https://platform.openai.com/api-keys) |
388
+ | `'deepseek'` | `deepseek-chat` | — | [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys) |
389
+ | `'ollama'` | — (give `model`) | runs on your machine, no key | — |
390
+ | `'openai-compatible'` | — (give `baseUrl` and `model`) | LM Studio, vLLM, Together, a proxy of your own… | as it needs |
391
+
392
+ ```js
393
+ providerEmailModel({ provider: { id: 'groq', model: 'llama-4-scout' }, apiKey });
394
+ providerEmailModel({ provider: { baseUrl: 'http://localhost:1234/v1', model: 'qwen3' } });
332
395
  ```
333
396
 
334
- - `claudeEmailModel({ client, model, effort, maxTokens })` — `claude-opus-5-5`
335
- by default, `effort: 'medium'`; the answer is structured output (a JSON
336
- schema: name, subject, preheader, MJML) and is streamed, so a long email does
337
- not hit a request timeout. A generation takes from 20 seconds to a minute or
338
- two: give the route that long (`maxDuration` on Vercel, the proxy's timeout).
339
- - **Another provider** is a function `({ system, messages, schema, signal, task }) =>
340
- Promise<string>` returning the JSON text — pass it as `model`.
341
- - What comes out is never trusted as is: shorthand padding is expanded,
342
- elements the builder does not edit (`mj-hero`, `mj-wrapper`, `mj-head`…)
343
- are turned into sections and columns, stock photos are removed (the user
344
- picks real images; the alt text stays), unknown variables become visible
345
- `[placeholders]`, and the result must pass the same validation as a saved
346
- template. When a problem remains, the model gets one more turn with the list.
347
- - The prompt (`SYSTEM_PROMPT`, `buildUserPrompt`) and the purposes
348
- (`EMAIL_PURPOSES`) are exported, to read or to build on.
397
+ - Model names change often: the defaults were current at this release —
398
+ pass `model` to be sure of yours.
399
+ - The answer is asked as structured output (a JSON schema); a model that
400
+ refuses it is asked for a JSON object, then for plain JSON — once, then
401
+ remembered. A model whose structured output breaks the JSON (some do on the
402
+ quotes of HTML) can be told to skip it: `provider: { …, format: 'text' }`.
403
+ - The provider's failures become errors the interface explains, in its
404
+ language: `KEY_MISSING`, `KEY_REFUSED`, `MODEL_CREDITS`, `MODEL_BUSY`,
405
+ `MODEL_ERROR` (with the provider's own text in `detail`). None is a 401:
406
+ the key is the AI's, not the user's session.
407
+ - **Claude**: `claudeEmailModel({ client: new Anthropic() })` with the
408
+ Anthropic SDK (`npm install @anthropic-ai/sdk`) — `claude-opus-5-5` by
409
+ default, `effort: 'medium'`, structured output, streamed so a long email
410
+ does not hit a request timeout — or through OpenRouter with an
411
+ `anthropic/…` model and one key for every model.
412
+ - **Anything else** is a function `({ system, messages, schema, signal, task })
413
+ => Promise<string>` returning the JSON text — `createEmailGenerator({ model })`
414
+ on the server, `createAi({ model })` in the page.
415
+
416
+ A generation takes from 20 seconds to a minute or two: on a server, give the
417
+ route that long (`maxDuration` on Vercel, the proxy's timeout).
418
+
419
+ What comes out is never trusted as is: shorthand padding is expanded,
420
+ elements the builder does not edit (`mj-hero`, `mj-wrapper`, `mj-head`…) are
421
+ turned into sections and columns, stock photos are removed (the user picks
422
+ real images; the alt text stays), unknown variables become visible
423
+ `[placeholders]`, and the result must pass the same validation as a saved
424
+ template. When a problem remains, the model gets one more turn with the list.
425
+ The prompt (`SYSTEM_PROMPT`, `buildUserPrompt`) and the purposes
426
+ (`EMAIL_PURPOSES`) are exported from `best-email-builder/ai/server`.
349
427
 
350
428
  **Rewrite with AI.** The same generator rewrites one text: with it, selecting
351
429
  a text block or a button shows a *Rewrite with AI* section in its properties —
@@ -356,14 +434,14 @@ the previous text back, so there is nothing to accept or compare. Selecting
356
434
  another element cancels a rewrite in progress; if the text was edited
357
435
  meanwhile, the user's words win and the answer is dropped.
358
436
 
359
- - It is `POST {base}/rewrite`, enabled with `createHttpStorage({ generate: true })`
360
- like generation; a generator without `rewrite` answers 501.
437
+ - On a server it is `POST {base}/rewrite`; a generator without `rewrite`
438
+ answers 501.
361
439
  - Every link (the same `href`, none added) and every variable (the same keys,
362
440
  none added; a fallback after `|` may be translated) must survive, and the
363
441
  HTML must pass the validator — otherwise the model gets one more turn, then
364
442
  nothing is changed. A button label comes back as plain words.
365
443
  - `claudeEmailModel` asks a rewrite for `rewriteEffort: 'low'` (a few seconds);
366
- another provider gets `task: 'rewrite'` in its request to tell the two apart.
444
+ a model of your own gets `task: 'rewrite'` in its request to tell the two apart.
367
445
  - `REWRITE_ACTIONS`, `REWRITE_LANGUAGES` and `REWRITE_SYSTEM_PROMPT` are
368
446
  exported.
369
447
 
@@ -374,8 +452,8 @@ pick from, with the user's own subject first: a click puts one in the field,
374
452
  another click another one, the first row brings the user's subject back.
375
453
  *More ideas* asks for three new ones, *Done* closes the list.
376
454
 
377
- - It is `POST {base}/subjects` (`generator.suggestSubjects`), enabled with
378
- `createHttpStorage({ generate: true })` too; 100 per user per hour.
455
+ - On a server it is `POST {base}/subjects` (`generator.suggestSubjects`);
456
+ 100 per user per hour.
379
457
  - The subjects are plain lines, different from the current subject and from
380
458
  each other, in the language of the email; a merge tag is allowed only from
381
459
  the server's catalog. Otherwise the model gets one more turn; fewer than
@@ -384,9 +462,8 @@ another click another one, the first row brings the user's subject back.
384
462
  `emailWords(mjml)` (the words a reader sees) and `SUBJECTS_SYSTEM_PROMPT`
385
463
  are exported.
386
464
 
387
- The live demo has no server: there, *Generate*, *Rewrite with AI* and
388
- *Suggest subjects* ask for the visitor's own Anthropic key, keep it in the
389
- browser and call Anthropic directly.
465
+ The live demo has no server: there, the AI runs in the page with the
466
+ visitor's own OpenRouter key (`createAi({ provider: 'openrouter', askKey: true })`).
390
467
 
391
468
  ### 5. Your brand (optional)
392
469
 
@@ -461,6 +538,7 @@ The live demo keeps the brand in `localStorage` and has no website field
461
538
  | `onSaved` | — | Called with the saved template. |
462
539
  | `onDirtyChange` | — | Called when the unsaved state changes. |
463
540
  | `dirtyCheckRef` | — | Ref receiving a function `() => boolean` (synchronous dirty check). |
541
+ | `ai` | — | The AI module, `createAi()` from `best-email-builder/ai` (see *AI*). Without it, no AI button and no AI code. |
464
542
 
465
543
  ## Storage
466
544
 
@@ -479,9 +557,6 @@ interface TemplateStorage {
479
557
  getVersion(id, versionId): Promise<Version & { document; mjml }>;
480
558
  uploadImage(file): Promise<{ path }>;
481
559
  render?(input): Promise<{ html; warnings; quality }>; // enables Preview
482
- generate?(input, { signal }): Promise<{ name; subject; preheader; mjml; warnings }>; // enables Generate
483
- rewrite?(input, { signal }): Promise<{ text }>; // enables Rewrite with AI
484
- suggestSubjects?(input, { signal }): Promise<{ subjects }>; // enables Suggest subjects
485
560
  sendTest?(input): Promise<{ sent; failed; invalid; results }>; // enables Send test
486
561
  listVariables?(): Promise<Variable[]>; // enable user-created variables
487
562
  saveVariable?(variable, { create }): Promise<Variable>;
@@ -0,0 +1,68 @@
1
+ import {
2
+ AI_MESSAGES,
3
+ AI_PROVIDERS,
4
+ DEFAULT_CLAUDE_MODEL,
5
+ EMAIL_DRAFT_SCHEMA,
6
+ EMAIL_PURPOSES,
7
+ EmailGenerationError,
8
+ GENERATION_ERRORS,
9
+ MAX_DETAILS_LENGTH,
10
+ PURPOSE_CATEGORIES,
11
+ REWRITE_ACTIONS,
12
+ REWRITE_LANGUAGES,
13
+ REWRITE_SYSTEM_PROMPT,
14
+ SUBJECTS_SYSTEM_PROMPT,
15
+ SYSTEM_PROMPT,
16
+ buildUserPrompt,
17
+ claudeEmailModel,
18
+ cleanGenerateInput,
19
+ createEmailGenerator,
20
+ createRewriter,
21
+ createSubjectSuggester,
22
+ emailWords,
23
+ normalizeGeneratedMjml,
24
+ parseDraft,
25
+ providerEmailModel,
26
+ purposeById,
27
+ purposesByCategory,
28
+ resolveProvider,
29
+ reviewDraft,
30
+ reviewRewrite,
31
+ reviewSubjects
32
+ } from "./chunk-MPX5CWTZ.js";
33
+ import "./chunk-2RZHGAH5.js";
34
+ import "./chunk-5SASKCUC.js";
35
+ import "./chunk-DFNHICTF.js";
36
+ export {
37
+ AI_MESSAGES,
38
+ AI_PROVIDERS,
39
+ DEFAULT_CLAUDE_MODEL,
40
+ EMAIL_DRAFT_SCHEMA,
41
+ EMAIL_PURPOSES,
42
+ EmailGenerationError,
43
+ GENERATION_ERRORS,
44
+ MAX_DETAILS_LENGTH,
45
+ PURPOSE_CATEGORIES,
46
+ REWRITE_ACTIONS,
47
+ REWRITE_LANGUAGES,
48
+ REWRITE_SYSTEM_PROMPT,
49
+ SUBJECTS_SYSTEM_PROMPT,
50
+ SYSTEM_PROMPT,
51
+ buildUserPrompt,
52
+ claudeEmailModel,
53
+ cleanGenerateInput,
54
+ createEmailGenerator,
55
+ createRewriter,
56
+ createSubjectSuggester,
57
+ emailWords,
58
+ normalizeGeneratedMjml,
59
+ parseDraft,
60
+ providerEmailModel,
61
+ purposeById,
62
+ purposesByCategory,
63
+ resolveProvider,
64
+ reviewDraft,
65
+ reviewRewrite,
66
+ reviewSubjects
67
+ };
68
+ //# sourceMappingURL=ai-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}