best-email-builder 0.3.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 (112) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +290 -20
  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-JOC752I3.js → chunk-6D3SLT5W.js} +27 -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-DFNHICTF.js +1359 -0
  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-ZSUHOCCE.js → chunk-LXR4QTGP.js} +123 -579
  25. package/dist/chunk-LXR4QTGP.js.map +1 -0
  26. package/dist/{chunk-BVOVAEEJ.js → chunk-LYMJ2TBX.js} +2 -2
  27. package/dist/chunk-MPX5CWTZ.js +1687 -0
  28. package/dist/chunk-MPX5CWTZ.js.map +1 -0
  29. package/dist/chunk-PB7YZ3WP.js +5635 -0
  30. package/dist/chunk-PB7YZ3WP.js.map +1 -0
  31. package/dist/chunk-UFKCLU2D.js +255 -0
  32. package/dist/chunk-UFKCLU2D.js.map +1 -0
  33. package/dist/index.js +51 -20
  34. package/dist/index.js.map +1 -1
  35. package/dist/library.js +12 -7
  36. package/dist/server.js +513 -214
  37. package/dist/server.js.map +1 -1
  38. package/dist/styles.css +1 -1
  39. package/dist/types/ai/ConnectAi.d.ts +12 -0
  40. package/dist/types/ai/GenerateButton.d.ts +4 -0
  41. package/dist/types/ai/RewriteText.d.ts +5 -0
  42. package/dist/types/ai/SubjectIdeas.d.ts +25 -0
  43. package/dist/types/ai/chip.d.ts +1 -0
  44. package/dist/types/ai/claude.d.ts +21 -0
  45. package/dist/types/ai/createAi.d.ts +66 -0
  46. package/dist/types/ai/draft.d.ts +163 -0
  47. package/dist/types/ai/generator.d.ts +123 -0
  48. package/dist/types/ai/i18n/fr.d.ts +2 -0
  49. package/dist/types/ai/i18n/index.d.ts +2 -0
  50. package/dist/types/ai/index.d.ts +9 -0
  51. package/dist/types/ai/keys.d.ts +46 -0
  52. package/dist/types/ai/prompt.d.ts +76 -0
  53. package/dist/types/ai/providers.d.ts +67 -0
  54. package/dist/types/ai/purposes.d.ts +48 -0
  55. package/dist/types/ai/rewrite.d.ts +83 -0
  56. package/dist/types/ai/server.d.ts +9 -0
  57. package/dist/types/ai/subjects.d.ts +97 -0
  58. package/dist/types/blocks/shared.d.ts +20 -0
  59. package/dist/types/core/EmailEditor.d.ts +72 -19
  60. package/dist/types/core/constants.d.ts +1 -1
  61. package/dist/types/core/mjmlDefaults.d.ts +13 -0
  62. package/dist/types/core/scrollbars.d.ts +1 -1
  63. package/dist/types/defaultEditor.d.ts +4 -1
  64. package/dist/types/engine/NativeAdapter.d.ts +28 -0
  65. package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
  66. package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
  67. package/dist/types/engine/canvas/icons.d.ts +14 -0
  68. package/dist/types/engine/canvas/morph.d.ts +21 -0
  69. package/dist/types/engine/canvasMjml.d.ts +30 -0
  70. package/dist/types/engine/mjml.d.ts +31 -0
  71. package/dist/types/engine/model.d.ts +130 -0
  72. package/dist/types/engine/placement.d.ts +69 -0
  73. package/dist/types/engine/richText.d.ts +15 -0
  74. package/dist/types/engine/unwrapModule.d.ts +29 -0
  75. package/dist/types/features/brand/BrandButton.d.ts +4 -0
  76. package/dist/types/features/brand/brand.d.ts +108 -0
  77. package/dist/types/features/brand/extract.d.ts +61 -0
  78. package/dist/types/features/brand/index.d.ts +2 -0
  79. package/dist/types/features/brand/messages.d.ts +13 -0
  80. package/dist/types/features/brand/useBrand.d.ts +22 -0
  81. package/dist/types/features/preview/PreviewButton.d.ts +1 -1
  82. package/dist/types/features/preview/darkMode.d.ts +65 -0
  83. package/dist/types/features/variables/VariablePicker.d.ts +3 -2
  84. package/dist/types/i18n/index.d.ts +8 -0
  85. package/dist/types/index.d.ts +5 -2
  86. package/dist/types/server/brand.d.ts +32 -0
  87. package/dist/types/server/brandImport.d.ts +93 -0
  88. package/dist/types/server/compileMjml.d.ts +2 -0
  89. package/dist/types/server/handler.d.ts +26 -0
  90. package/dist/types/server/index.d.ts +5 -1
  91. package/dist/types/storage/client.d.ts +57 -3
  92. package/dist/types/ui/EmailBuilder.d.ts +5 -1
  93. package/dist/types/ui/EmailStudio.d.ts +6 -1
  94. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  95. package/dist/types/ui/fields.d.ts +2 -1
  96. package/dist/types/ui/primitives/button.d.ts +1 -1
  97. package/dist/types/ui/primitives/color-picker.d.ts +2 -1
  98. package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
  99. package/dist/types/ui/useLayoutWidth.d.ts +1 -1
  100. package/package.json +27 -7
  101. package/dist/chunk-3M36VVXS.js +0 -665
  102. package/dist/chunk-3M36VVXS.js.map +0 -1
  103. package/dist/chunk-57CTQNTK.js +0 -56
  104. package/dist/chunk-57CTQNTK.js.map +0 -1
  105. package/dist/chunk-ILRAQTHS.js +0 -3677
  106. package/dist/chunk-ILRAQTHS.js.map +0 -1
  107. package/dist/chunk-JOC752I3.js.map +0 -1
  108. package/dist/chunk-ZSUHOCCE.js.map +0 -1
  109. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
  110. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
  111. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
  112. /package/dist/{chunk-BVOVAEEJ.js.map → chunk-LYMJ2TBX.js.map} +0 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The email in a model's answer.
3
+ * @param {string} text
4
+ * @returns {EmailDraft}
5
+ */
6
+ /**
7
+ * Markup a model escaped twice — `<mj-text align=\\"center\\">` once the JSON
8
+ * is read — decoded once more. Markup with real quotes is left as it is.
9
+ * @param {string} markup
10
+ */
11
+ export function unescapedTwice(markup: string): string;
12
+ export function parseDraft(text: any): {
13
+ name: string;
14
+ subject: string;
15
+ preheader: string;
16
+ mjml: string;
17
+ };
18
+ /**
19
+ * The MJML of a generated email, rewritten as the builder writes it.
20
+ *
21
+ * @param {string} mjml
22
+ * @param {{variables?: {key: string}[], keepImages?: string[]}} [options]
23
+ * `variables`: the catalog (`unsubscribe` is always known);
24
+ * `keepImages`: image sources kept as they are (the sender's logo).
25
+ */
26
+ export function normalizeGeneratedMjml(mjml: string, { variables, keepImages }?: {
27
+ variables?: {
28
+ key: string;
29
+ }[];
30
+ keepImages?: string[];
31
+ }): string;
32
+ /**
33
+ * What still keeps the email from being used as it is — in English, written
34
+ * for the model that will correct it.
35
+ *
36
+ * @param {EmailDraft} draft
37
+ * @param {{kind?: string, compileWarnings?: {message: string}[]}} [options]
38
+ * @returns {{blocking: string[], advice: string[]}}
39
+ * `blocking`: the builder must not open it (rejected by the validator);
40
+ * `advice`: it opens, but is not what was asked.
41
+ */
42
+ export function reviewDraft(draft: EmailDraft, { kind, compileWarnings }?: {
43
+ kind?: string;
44
+ compileWarnings?: {
45
+ message: string;
46
+ }[];
47
+ }): {
48
+ blocking: string[];
49
+ advice: string[];
50
+ };
51
+ export namespace GENERATION_ERRORS {
52
+ namespace NOTHING_TO_GENERATE {
53
+ let status: number;
54
+ let message: string;
55
+ }
56
+ namespace REFUSED {
57
+ let status_1: number;
58
+ export { status_1 as status };
59
+ let message_1: string;
60
+ export { message_1 as message };
61
+ }
62
+ namespace TRUNCATED {
63
+ let status_2: number;
64
+ export { status_2 as status };
65
+ let message_2: string;
66
+ export { message_2 as message };
67
+ }
68
+ namespace INVALID_ANSWER {
69
+ let status_3: number;
70
+ export { status_3 as status };
71
+ let message_3: string;
72
+ export { message_3 as message };
73
+ }
74
+ namespace INVALID_OUTPUT {
75
+ let status_4: number;
76
+ export { status_4 as status };
77
+ let message_4: string;
78
+ export { message_4 as message };
79
+ }
80
+ namespace NOTHING_TO_REWRITE {
81
+ let status_5: number;
82
+ export { status_5 as status };
83
+ let message_5: string;
84
+ export { message_5 as message };
85
+ }
86
+ namespace REWRITE_REFUSED {
87
+ let status_6: number;
88
+ export { status_6 as status };
89
+ let message_6: string;
90
+ export { message_6 as message };
91
+ }
92
+ namespace REWRITE_UNUSABLE {
93
+ let status_7: number;
94
+ export { status_7 as status };
95
+ let message_7: string;
96
+ export { message_7 as message };
97
+ }
98
+ namespace NOTHING_TO_SUGGEST {
99
+ let status_8: number;
100
+ export { status_8 as status };
101
+ let message_8: string;
102
+ export { message_8 as message };
103
+ }
104
+ namespace SUBJECTS_REFUSED {
105
+ let status_9: number;
106
+ export { status_9 as status };
107
+ let message_9: string;
108
+ export { message_9 as message };
109
+ }
110
+ namespace SUBJECTS_UNUSABLE {
111
+ let status_10: number;
112
+ export { status_10 as status };
113
+ let message_10: string;
114
+ export { message_10 as message };
115
+ }
116
+ namespace KEY_MISSING {
117
+ let status_11: number;
118
+ export { status_11 as status };
119
+ let message_11: string;
120
+ export { message_11 as message };
121
+ }
122
+ namespace KEY_REFUSED {
123
+ let status_12: number;
124
+ export { status_12 as status };
125
+ let message_12: string;
126
+ export { message_12 as message };
127
+ }
128
+ namespace MODEL_CREDITS {
129
+ let status_13: number;
130
+ export { status_13 as status };
131
+ let message_13: string;
132
+ export { message_13 as message };
133
+ }
134
+ namespace MODEL_BUSY {
135
+ let status_14: number;
136
+ export { status_14 as status };
137
+ let message_14: string;
138
+ export { message_14 as message };
139
+ }
140
+ namespace MODEL_ERROR {
141
+ let status_15: number;
142
+ export { status_15 as status };
143
+ let message_15: string;
144
+ export { message_15 as message };
145
+ }
146
+ }
147
+ export class EmailGenerationError extends Error {
148
+ /**
149
+ * @param {keyof typeof GENERATION_ERRORS} code
150
+ * @param {string} [detail] what went wrong, for logs and for the model
151
+ */
152
+ constructor(code: keyof typeof GENERATION_ERRORS, detail?: string);
153
+ code: "NOTHING_TO_GENERATE" | "REFUSED" | "TRUNCATED" | "INVALID_ANSWER" | "INVALID_OUTPUT" | "NOTHING_TO_REWRITE" | "REWRITE_REFUSED" | "REWRITE_UNUSABLE" | "NOTHING_TO_SUGGEST" | "SUBJECTS_REFUSED" | "SUBJECTS_UNUSABLE" | "KEY_MISSING" | "KEY_REFUSED" | "MODEL_CREDITS" | "MODEL_BUSY" | "MODEL_ERROR";
154
+ status: number;
155
+ detail: string;
156
+ }
157
+ export type EmailNode = import("../engine/model.js").EmailNode;
158
+ export type EmailDraft = {
159
+ name: string;
160
+ subject: string;
161
+ preheader: string;
162
+ mjml: string;
163
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The input as the generator uses it: bounded strings, a known purpose or
3
+ * none, an https logo or none, the brand's colors and font or none.
4
+ * @param {GenerateInput} input
5
+ */
6
+ export function cleanGenerateInput(input: GenerateInput): {
7
+ purpose: import("./purposes.js").EmailPurpose | null;
8
+ details: string;
9
+ language: string;
10
+ sender: {
11
+ name: string;
12
+ address: string;
13
+ logoUrl: string;
14
+ };
15
+ brand: {
16
+ font?: string | undefined;
17
+ heading?: string | undefined;
18
+ primary?: string | undefined;
19
+ };
20
+ variables: {
21
+ key: string;
22
+ label: string;
23
+ }[];
24
+ };
25
+ /**
26
+ * @param {{
27
+ * model: EmailModel,
28
+ * maxAttempts?: number,
29
+ * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,
30
+ * }} options
31
+ * `compile`: compiles the MJML and returns MJML's own warnings, fed back to
32
+ * the model (the server passes `compileMjml`).
33
+ */
34
+ export function createEmailGenerator({ model, maxAttempts, compile }?: {
35
+ model: EmailModel;
36
+ maxAttempts?: number;
37
+ compile?: (mjml: string) => Promise<{
38
+ warnings?: {
39
+ message: string;
40
+ }[];
41
+ }>;
42
+ }): {
43
+ generate: (input: GenerateInput, { signal }?: {
44
+ signal?: AbortSignal;
45
+ }) => Promise<GeneratedEmail>;
46
+ rewrite: (input: import("./rewrite.js").RewriteInput, options?: {
47
+ signal?: AbortSignal;
48
+ }) => Promise<{
49
+ text: string;
50
+ attempts: number;
51
+ }>;
52
+ suggestSubjects: (input: import("./subjects.js").SubjectsInput, options?: {
53
+ signal?: AbortSignal;
54
+ }) => Promise<{
55
+ subjects: string[];
56
+ attempts: number;
57
+ }>;
58
+ messages: Record<string, Record<string, string>>;
59
+ };
60
+ /**
61
+ * @typedef {{role: 'user'|'assistant', content: string}} ModelMessage
62
+ * @typedef {(request: {system: string, messages: ModelMessage[], schema: object, signal?: AbortSignal, task?: 'generate'|'rewrite'|'subjects'}) => Promise<string>} EmailModel
63
+ * `task`: a whole email, one text rewritten or subject ideas (lighter jobs:
64
+ * a model may answer them with less effort).
65
+ *
66
+ * @typedef {{
67
+ * purpose?: string|null,
68
+ * details?: string,
69
+ * language?: string,
70
+ * sender?: {name?: string, address?: string, logoUrl?: string},
71
+ * brand?: {primary?: string, heading?: string, font?: string},
72
+ * variables?: {key: string, label?: string}[],
73
+ * }} GenerateInput
74
+ * `brand`: the colors and font the email is written in (features/brand).
75
+ *
76
+ * @typedef {import('./draft.js').EmailDraft & {warnings: string[], attempts: number}} GeneratedEmail
77
+ * @typedef {import('./rewrite.js').RewriteInput} RewriteInput
78
+ * @typedef {import('./subjects.js').SubjectsInput} SubjectsInput
79
+ */
80
+ export const MAX_DETAILS_LENGTH: 2000;
81
+ export type ModelMessage = {
82
+ role: "user" | "assistant";
83
+ content: string;
84
+ };
85
+ /**
86
+ * `task`: a whole email, one text rewritten or subject ideas (lighter jobs:
87
+ * a model may answer them with less effort).
88
+ */
89
+ export type EmailModel = (request: {
90
+ system: string;
91
+ messages: ModelMessage[];
92
+ schema: object;
93
+ signal?: AbortSignal;
94
+ task?: "generate" | "rewrite" | "subjects";
95
+ }) => Promise<string>;
96
+ /**
97
+ * `brand`: the colors and font the email is written in (features/brand).
98
+ */
99
+ export type GenerateInput = {
100
+ purpose?: string | null;
101
+ details?: string;
102
+ language?: string;
103
+ sender?: {
104
+ name?: string;
105
+ address?: string;
106
+ logoUrl?: string;
107
+ };
108
+ brand?: {
109
+ primary?: string;
110
+ heading?: string;
111
+ font?: string;
112
+ };
113
+ variables?: {
114
+ key: string;
115
+ label?: string;
116
+ }[];
117
+ };
118
+ export type GeneratedEmail = import("./draft.js").EmailDraft & {
119
+ warnings: string[];
120
+ attempts: number;
121
+ };
122
+ export type RewriteInput = import("./rewrite.js").RewriteInput;
123
+ export type SubjectsInput = import("./subjects.js").SubjectsInput;
@@ -0,0 +1,2 @@
1
+ /** @type {Record<string, string>} */
2
+ export const fr: Record<string, string>;
@@ -0,0 +1,2 @@
1
+ /** @type {Record<string, Record<string, string>>} */
2
+ export const AI_MESSAGES: Record<string, Record<string, string>>;
@@ -0,0 +1,9 @@
1
+ export { createAi } from "./createAi.js";
2
+ export { AI_MESSAGES } from "./i18n/index.js";
3
+ export { emailWords } from "./subjects.js";
4
+ export { createEmailGenerator } from "./generator.js";
5
+ export { EMAIL_PURPOSES, PURPOSE_CATEGORIES } from "./purposes.js";
6
+ export { REWRITE_ACTIONS, REWRITE_LANGUAGES } from "./rewrite.js";
7
+ export { EmailGenerationError, GENERATION_ERRORS } from "./draft.js";
8
+ export { claudeEmailModel, DEFAULT_CLAUDE_MODEL } from "./claude.js";
9
+ export { AI_PROVIDERS, providerEmailModel, resolveProvider } from "./providers.js";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @typedef {{
3
+ * provider: Readonly<{id: string, name: string, keyUrl: string|null, keyHint: string, free: string|null}>,
4
+ * get: () => string,
5
+ * set: (key: string) => void,
6
+ * forget: () => void,
7
+ * request: () => Promise<string>,
8
+ * subscribe: (listener: () => void) => () => void,
9
+ * state: () => Readonly<{asking: boolean, refused: boolean, connected: boolean}>,
10
+ * answer: (key: string|null) => void,
11
+ * refuse: () => void,
12
+ * }} AiKey
13
+ * `state`, `answer` and `refuse` serve the Connect dialog and the module:
14
+ * whether the user is being asked, whether the last key was refused.
15
+ */
16
+ /**
17
+ * @param {AiProvider} provider
18
+ * @returns {AiKey}
19
+ */
20
+ export function createKeyStore(provider: AiProvider): AiKey;
21
+ /**
22
+ * `state`, `answer` and `refuse` serve the Connect dialog and the module:
23
+ * whether the user is being asked, whether the last key was refused.
24
+ */
25
+ export type AiKey = {
26
+ provider: Readonly<{
27
+ id: string;
28
+ name: string;
29
+ keyUrl: string | null;
30
+ keyHint: string;
31
+ free: string | null;
32
+ }>;
33
+ get: () => string;
34
+ set: (key: string) => void;
35
+ forget: () => void;
36
+ request: () => Promise<string>;
37
+ subscribe: (listener: () => void) => () => void;
38
+ state: () => Readonly<{
39
+ asking: boolean;
40
+ refused: boolean;
41
+ connected: boolean;
42
+ }>;
43
+ answer: (key: string | null) => void;
44
+ refuse: () => void;
45
+ };
46
+ export type AiProvider = import("./providers.js").AiProvider;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * English name of a language code ("fr" → "French"), the code itself when
3
+ * the runtime does not know it.
4
+ * @param {string} code
5
+ */
6
+ export function languageName(code: string): string;
7
+ /**
8
+ * The request message.
9
+ *
10
+ * @param {{
11
+ * purpose?: EmailPurpose|null,
12
+ * details?: string,
13
+ * language?: string,
14
+ * sender?: PromptSender,
15
+ * brand?: {primary?: string, heading?: string, font?: string},
16
+ * variables?: PromptVariable[],
17
+ * }} input
18
+ */
19
+ export function buildUserPrompt({ purpose, details, language, sender, brand, variables }: {
20
+ purpose?: EmailPurpose | null;
21
+ details?: string;
22
+ language?: string;
23
+ sender?: PromptSender;
24
+ brand?: {
25
+ primary?: string;
26
+ heading?: string;
27
+ font?: string;
28
+ };
29
+ variables?: PromptVariable[];
30
+ }): string;
31
+ /**
32
+ * The message that asks the model to correct its email.
33
+ * @param {string[]} problems
34
+ */
35
+ export function buildRepairPrompt(problems: string[]): string;
36
+ export namespace EMAIL_DRAFT_SCHEMA {
37
+ let type: string;
38
+ namespace properties {
39
+ namespace name {
40
+ let type_1: string;
41
+ export { type_1 as type };
42
+ export let description: string;
43
+ }
44
+ namespace subject {
45
+ let type_2: string;
46
+ export { type_2 as type };
47
+ let description_1: string;
48
+ export { description_1 as description };
49
+ }
50
+ namespace preheader {
51
+ let type_3: string;
52
+ export { type_3 as type };
53
+ let description_2: string;
54
+ export { description_2 as description };
55
+ }
56
+ namespace mjml {
57
+ let type_4: string;
58
+ export { type_4 as type };
59
+ let description_3: string;
60
+ export { description_3 as description };
61
+ }
62
+ }
63
+ let required: string[];
64
+ let additionalProperties: boolean;
65
+ }
66
+ export const SYSTEM_PROMPT: string;
67
+ export type EmailPurpose = import("./purposes.js").EmailPurpose;
68
+ export type PromptVariable = {
69
+ key: string;
70
+ label?: string;
71
+ };
72
+ export type PromptSender = {
73
+ name?: string;
74
+ address?: string;
75
+ logoUrl?: string;
76
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A provider by id, or a provider object of one's own.
3
+ * @param {string|Partial<AiProvider>} provider
4
+ * @returns {AiProvider}
5
+ */
6
+ export function resolveProvider(provider: string | Partial<AiProvider>): AiProvider;
7
+ /**
8
+ * @param {{
9
+ * provider?: string|Partial<AiProvider>,
10
+ * apiKey?: string|(() => string|Promise<string>),
11
+ * model?: string,
12
+ * baseUrl?: string,
13
+ * headers?: Record<string, string>,
14
+ * maxTokens?: number,
15
+ * temperature?: number,
16
+ * fetch?: typeof fetch,
17
+ * }} options
18
+ * `apiKey`: the key, or a function that gives it at each call (asked to the
19
+ * user, read from a vault); `model`, `baseUrl`: instead of the provider's;
20
+ * `maxTokens`: the longest answer (an email is about 6 000).
21
+ * @returns {import('./generator.js').EmailModel & {provider: AiProvider, model: string}}
22
+ */
23
+ export function providerEmailModel(options?: {
24
+ provider?: string | Partial<AiProvider>;
25
+ apiKey?: string | (() => string | Promise<string>);
26
+ model?: string;
27
+ baseUrl?: string;
28
+ headers?: Record<string, string>;
29
+ maxTokens?: number;
30
+ temperature?: number;
31
+ fetch?: typeof fetch;
32
+ }): import("./generator.js").EmailModel & {
33
+ provider: AiProvider;
34
+ model: string;
35
+ };
36
+ /** The providers `providerEmailModel` knows, by id. */
37
+ export const AI_PROVIDERS: Readonly<{
38
+ openrouter: AiProvider;
39
+ openai: AiProvider;
40
+ gemini: AiProvider;
41
+ groq: AiProvider;
42
+ mistral: AiProvider;
43
+ deepseek: AiProvider;
44
+ cerebras: AiProvider;
45
+ ollama: AiProvider;
46
+ 'openai-compatible': AiProvider;
47
+ }>;
48
+ export type AnswerFormat = "json_schema" | "json_object" | "text";
49
+ /**
50
+ * `model`: the default (null: to be given); `keyUrl`: where a user gets a
51
+ * key; `keyHint`: what a key starts with; `free`: what can be used without
52
+ * paying, in a few words (null: paid only); `format`: the best answer format
53
+ * the provider takes; `tokens`: how it names the output limit.
54
+ */
55
+ export type AiProvider = {
56
+ id: string;
57
+ name: string;
58
+ baseUrl: string;
59
+ model: string | null;
60
+ keyUrl: string | null;
61
+ keyHint: string;
62
+ free: string | null;
63
+ format: AnswerFormat;
64
+ tokens: "max_tokens" | "max_completion_tokens";
65
+ keyless?: boolean;
66
+ headers?: Record<string, string>;
67
+ };
@@ -0,0 +1,48 @@
1
+ /** @param {string|null|undefined} id */
2
+ export function purposeById(id: string | null | undefined): EmailPurpose | null;
3
+ /** Purposes grouped by category, in category order. */
4
+ export function purposesByCategory(purposes?: EmailPurpose[]): {
5
+ purposes: EmailPurpose[];
6
+ id: string;
7
+ label: string;
8
+ }[];
9
+ /**
10
+ * WHAT AN EMAIL IS FOR — the purposes offered by "Generate".
11
+ *
12
+ * Each purpose carries a short BRIEF for the model: the sections and the
13
+ * information such an email needs (a password reset needs the link's expiry
14
+ * and a "not you?" line; a sale needs the offer, its end date and a code).
15
+ * The brief is the part of the prompt that changes with the purpose; the
16
+ * design and MJML rules stay the same (see `prompt.js`).
17
+ *
18
+ * `kind` sets the footer:
19
+ * marketing sent in bulk: an unsubscribe link is required (CAN-SPAM, GDPR);
20
+ * transactional triggered by the recipient's action: a discreet link to the
21
+ * email preferences;
22
+ * security account security: no unsubscribe link at all, a line says
23
+ * why the message was sent.
24
+ *
25
+ * Plain data: labels are English keys translated by the interface, briefs
26
+ * stay in English (they are written for the model, not shown).
27
+ */
28
+ /**
29
+ * @typedef {'marketing'|'transactional'|'security'} PurposeKind
30
+ * @typedef {{id: string, label: string}} PurposeCategory
31
+ * @typedef {{id: string, label: string, category: string, kind: PurposeKind, brief: string}} EmailPurpose
32
+ */
33
+ /** @type {PurposeCategory[]} */
34
+ export const PURPOSE_CATEGORIES: PurposeCategory[];
35
+ /** @type {EmailPurpose[]} */
36
+ export const EMAIL_PURPOSES: EmailPurpose[];
37
+ export type PurposeKind = "marketing" | "transactional" | "security";
38
+ export type PurposeCategory = {
39
+ id: string;
40
+ label: string;
41
+ };
42
+ export type EmailPurpose = {
43
+ id: string;
44
+ label: string;
45
+ category: string;
46
+ kind: PurposeKind;
47
+ brief: string;
48
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @typedef {{
3
+ * action: string,
4
+ * text: string,
5
+ * format?: 'html'|'plain',
6
+ * targetLanguage?: string,
7
+ * instruction?: string,
8
+ * }} RewriteInput
9
+ * `action`: an id of REWRITE_ACTIONS, `translate` (with `targetLanguage`)
10
+ * or `custom` (with `instruction`).
11
+ */
12
+ /**
13
+ * The request as the rewriter uses it, or an error when there is nothing to do.
14
+ * @param {RewriteInput} input
15
+ */
16
+ export function cleanRewriteInput(input: RewriteInput): {
17
+ format: "html" | "plain";
18
+ text: string;
19
+ change: string;
20
+ };
21
+ /** @param {{format: 'html'|'plain', text: string, change: string}} request */
22
+ export function buildRewritePrompt({ format, text, change }: {
23
+ format: "html" | "plain";
24
+ text: string;
25
+ change: string;
26
+ }): string;
27
+ /** The text in a model's answer: the JSON field, or the bare answer. @param {string} answer */
28
+ export function parseRewrite(answer: string): string;
29
+ /**
30
+ * What is wrong with a rewritten text, for the model (empty: usable).
31
+ * @param {string} original @param {string} rewritten @param {'html'|'plain'} format
32
+ */
33
+ export function reviewRewrite(original: string, rewritten: string, format: "html" | "plain"): string[];
34
+ /**
35
+ * @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options
36
+ * @returns {(input: RewriteInput, options?: {signal?: AbortSignal}) => Promise<{text: string, attempts: number}>}
37
+ */
38
+ export function createRewriter({ model, maxAttempts }: {
39
+ model: import("./generator.js").EmailModel;
40
+ maxAttempts?: number;
41
+ }): (input: RewriteInput, options?: {
42
+ signal?: AbortSignal;
43
+ }) => Promise<{
44
+ text: string;
45
+ attempts: number;
46
+ }>;
47
+ /**
48
+ * The actions offered as buttons, in order. `translate` takes a language,
49
+ * a custom change (`custom`) takes the user's words.
50
+ */
51
+ export const REWRITE_ACTIONS: {
52
+ id: string;
53
+ label: string;
54
+ change: string;
55
+ }[];
56
+ /** Languages offered by "Translate", shown under their own name. */
57
+ export const REWRITE_LANGUAGES: string[];
58
+ export const MAX_REWRITE_TEXT: 6000;
59
+ export const MAX_REWRITE_INSTRUCTION: 300;
60
+ export namespace REWRITE_SCHEMA {
61
+ let type: string;
62
+ namespace properties {
63
+ namespace text {
64
+ let type_1: string;
65
+ export { type_1 as type };
66
+ export let description: string;
67
+ }
68
+ }
69
+ let required: string[];
70
+ let additionalProperties: boolean;
71
+ }
72
+ export 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.";
73
+ /**
74
+ * `action`: an id of REWRITE_ACTIONS, `translate` (with `targetLanguage`)
75
+ * or `custom` (with `instruction`).
76
+ */
77
+ export type RewriteInput = {
78
+ action: string;
79
+ text: string;
80
+ format?: "html" | "plain";
81
+ targetLanguage?: string;
82
+ instruction?: string;
83
+ };
@@ -0,0 +1,9 @@
1
+ export { AI_MESSAGES } from "./i18n/index.js";
2
+ export { createEmailGenerator, cleanGenerateInput, MAX_DETAILS_LENGTH } from "./generator.js";
3
+ export { claudeEmailModel, DEFAULT_CLAUDE_MODEL } from "./claude.js";
4
+ export { AI_PROVIDERS, providerEmailModel, resolveProvider } from "./providers.js";
5
+ export { EmailGenerationError, GENERATION_ERRORS, normalizeGeneratedMjml, parseDraft, reviewDraft } from "./draft.js";
6
+ export { EMAIL_PURPOSES, PURPOSE_CATEGORIES, purposeById, purposesByCategory } from "./purposes.js";
7
+ export { SYSTEM_PROMPT, EMAIL_DRAFT_SCHEMA, buildUserPrompt } from "./prompt.js";
8
+ export { REWRITE_ACTIONS, REWRITE_LANGUAGES, REWRITE_SYSTEM_PROMPT, createRewriter, reviewRewrite } from "./rewrite.js";
9
+ export { SUBJECTS_SYSTEM_PROMPT, createSubjectSuggester, emailWords, reviewSubjects } from "./subjects.js";