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.
- package/CHANGELOG.md +57 -0
- package/README.md +119 -44
- package/dist/ai-server.js +68 -0
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.js +852 -0
- package/dist/ai.js.map +1 -0
- package/dist/builder.js +16 -8
- package/dist/chunk-2RZHGAH5.js +64 -0
- package/dist/chunk-2RZHGAH5.js.map +1 -0
- package/dist/chunk-3LKF4QBL.js +595 -0
- package/dist/chunk-3LKF4QBL.js.map +1 -0
- package/dist/chunk-5SASKCUC.js +251 -0
- package/dist/chunk-5SASKCUC.js.map +1 -0
- package/dist/{chunk-NAEGU6PR.js → chunk-6D3SLT5W.js} +18 -29
- package/dist/chunk-6D3SLT5W.js.map +1 -0
- package/dist/chunk-7U6BAKM7.js +119 -0
- package/dist/chunk-7U6BAKM7.js.map +1 -0
- package/dist/{chunk-HHLOE6OZ.js → chunk-DFNHICTF.js} +10 -333
- package/dist/chunk-DFNHICTF.js.map +1 -0
- package/dist/chunk-ECHGJZ2W.js +327 -0
- package/dist/chunk-ECHGJZ2W.js.map +1 -0
- package/dist/chunk-KXJJSVN5.js +98 -0
- package/dist/chunk-KXJJSVN5.js.map +1 -0
- package/dist/{chunk-AT73Y7JA.js → chunk-LXR4QTGP.js} +49 -581
- package/dist/chunk-LXR4QTGP.js.map +1 -0
- package/dist/{chunk-TGDZ4NFI.js → chunk-LYMJ2TBX.js} +3 -44
- package/dist/chunk-LYMJ2TBX.js.map +1 -0
- package/dist/{chunk-JNHFZXDL.js → chunk-MPX5CWTZ.js} +560 -649
- package/dist/chunk-MPX5CWTZ.js.map +1 -0
- package/dist/{chunk-MY6WPDT4.js → chunk-PB7YZ3WP.js} +463 -938
- package/dist/chunk-PB7YZ3WP.js.map +1 -0
- package/dist/chunk-UFKCLU2D.js +255 -0
- package/dist/chunk-UFKCLU2D.js.map +1 -0
- package/dist/index.js +31 -37
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -7
- package/dist/server.js +33 -74
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +12 -0
- package/dist/types/ai/GenerateButton.d.ts +4 -0
- package/dist/types/ai/SubjectIdeas.d.ts +25 -0
- package/dist/types/ai/createAi.d.ts +66 -0
- package/dist/types/{features/generate → ai}/draft.d.ts +44 -3
- package/dist/types/{features/generate → ai}/generator.d.ts +7 -2
- package/dist/types/ai/i18n/fr.d.ts +2 -0
- package/dist/types/ai/i18n/index.d.ts +2 -0
- package/dist/types/ai/index.d.ts +9 -0
- package/dist/types/ai/keys.d.ts +46 -0
- package/dist/types/ai/providers.d.ts +67 -0
- package/dist/types/{features/generate → ai}/rewrite.d.ts +1 -1
- package/dist/types/ai/server.d.ts +9 -0
- package/dist/types/{features/generate → ai}/subjects.d.ts +18 -4
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +33 -16
- package/dist/types/features/variables/VariablePicker.d.ts +3 -2
- package/dist/types/i18n/index.d.ts +8 -0
- package/dist/types/index.d.ts +1 -7
- package/dist/types/server/handler.d.ts +5 -4
- package/dist/types/server/index.d.ts +1 -8
- package/dist/types/storage/client.d.ts +22 -40
- package/dist/types/ui/EmailBuilder.d.ts +5 -1
- package/dist/types/ui/EmailStudio.d.ts +6 -1
- package/dist/types/ui/PropertiesPanel.d.ts +2 -1
- package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
- package/package.json +15 -1
- package/dist/chunk-AT73Y7JA.js.map +0 -1
- package/dist/chunk-HHLOE6OZ.js.map +0 -1
- package/dist/chunk-JNHFZXDL.js.map +0 -1
- package/dist/chunk-MY6WPDT4.js.map +0 -1
- package/dist/chunk-NAEGU6PR.js.map +0 -1
- package/dist/chunk-TGDZ4NFI.js.map +0 -1
- package/dist/types/features/generate/GenerateButton.d.ts +0 -3
- package/dist/types/features/generate/SubjectIdeas.d.ts +0 -16
- package/dist/types/features/generate/index.d.ts +0 -2
- /package/dist/types/{features/generate → ai}/RewriteText.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/chip.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/claude.d.ts +0 -0
- /package/dist/types/{features/generate → ai}/prompt.d.ts +0 -0
- /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
|
-
|
|
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.
|
|
306
|
+
### 4. AI (an optional module)
|
|
302
307
|
|
|
303
|
-
The
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
import {
|
|
314
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
-
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
378
|
-
|
|
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,
|
|
388
|
-
|
|
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":[]}
|