@one-million-lines/email-builder 0.1.4 → 0.2.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 CHANGED
@@ -6,6 +6,27 @@ This project adheres to [Semantic Versioning](https://semver.org/) and the
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Added
10
+ - **Gallery module** (`src/gallery/`) — pluggable packs of extra, ready-to-drop
11
+ blocks that render on top of each category in the left sidebar (badged "New")
12
+ and can be registered/removed at runtime with live UI updates. New exports:
13
+ `galleryRegistry`, `galleryPlugin`, `sampleGallery`, types `GalleryDefinition`
14
+ and `GalleryItem`. New `galleries` prop on `<EmailBuilder>` and a dedicated
15
+ **Gallery** sidebar tab. See `src/gallery/README.md`.
16
+ - **AI assistant module** (`src/ai/`) — optional chat-driven editing. When an AI
17
+ provider is configured an **AI** tab appears in the left sidebar. The client
18
+ sends a catalog of real modules (built-ins + gallery items) so the assistant
19
+ only ever assembles valid, renderable emails; every response is validated with
20
+ `documentSchema` and gets fresh ids before being applied. New exports:
21
+ `aiAssistantPlugin`, `createHttpAIProvider`, `buildCatalog`, `applyAIResponse`,
22
+ `mockAIProvider`, `applyAIActions`, `validateAIDocument`, and the related
23
+ types. New `aiEndpoint` prop on `<EmailBuilder>`. See `src/ai/README.md`.
24
+ - **Python AI backend** (`backend/`) — a small Flask service (`app.py`,
25
+ `ai_service.py`) exposing `GET /health` and `POST /ai/generate`. Works with any
26
+ OpenAI-compatible endpoint and falls back to a deterministic offline planner
27
+ when no API key is set. See `backend/README.md`.
28
+
29
+
9
30
  ## [0.1.4] — 2026-07-02
10
31
 
11
32
  ### Changed
package/README.md CHANGED
@@ -15,6 +15,10 @@ Marketing teams often need a reusable editor that produces email-safe HTML witho
15
15
  - Visual editor with top bar, left sidebar, canvas, and right sidebar
16
16
  - JSON-first email document model
17
17
  - Built-in modules, templates, and themes
18
+ - **Gallery module** — pluggable packs of extra blocks that appear on top of each
19
+ category and can be loaded at runtime
20
+ - **AI assistant module** — optional chat-driven editing backed by a simple
21
+ Python service
18
22
  - Table-based HTML rendering for email output
19
23
  - Local autosave via Zustand store
20
24
  - React component API and vanilla JS factory
@@ -45,12 +49,15 @@ src/
45
49
  core/ document types, renderer, validation, AI actions, plugins
46
50
  editor/ top bar, sidebars, canvas
47
51
  modules/ module registry and built-in modules
52
+ gallery/ gallery registry + packs of extra blocks (see gallery/README.md)
53
+ ai/ AI assistant module: provider, catalog, chat panel (see ai/README.md)
48
54
  recommendations/ recommendation and fallback logic
49
55
  store/ editor state and persistence
50
56
  templates/ built-in email templates
51
57
  themes/ theme definitions
52
58
  plugins/ extension points, including image uploader helpers
53
59
  index.ts public API for embedding
60
+ backend/ Python (Flask) AI service (see backend/README.md)
54
61
  ```
55
62
 
56
63
  ## Install
@@ -131,6 +138,46 @@ Vue and Angular mount the React-based editor through the framework-neutral
131
138
  `createEmailBuilder()` factory (`getDocument` / `exportHtml` / `exportJson` /
132
139
  `destroy`). React and ReactDOM remain peer dependencies in all cases.
133
140
 
141
+ ### Gallery module
142
+
143
+ Galleries are pluggable packs of extra, ready-to-drop blocks. Their items appear
144
+ **on top of** each category in the left sidebar (badged "New") and can be loaded
145
+ at runtime.
146
+
147
+ ```tsx
148
+ import { EmailBuilder, sampleGallery } from "@one-million-lines/email-builder";
149
+
150
+ <EmailBuilder galleries={[sampleGallery]} />
151
+ ```
152
+
153
+ ```ts
154
+ import { galleryRegistry } from "@one-million-lines/email-builder";
155
+ // Load dynamically — the sidebar updates live.
156
+ galleryRegistry.registerGallery(await fetch("/api/galleries").then((r) => r.json()));
157
+ ```
158
+
159
+ See [`src/gallery/README.md`](./src/gallery/README.md) for authoring galleries.
160
+
161
+ ### AI assistant module
162
+
163
+ Enable chat-driven editing by pointing the builder at the Python AI service in
164
+ [`backend/`](./backend). When configured, an **AI** tab appears in the left
165
+ sidebar.
166
+
167
+ ```tsx
168
+ <EmailBuilder aiEndpoint="http://localhost:3001/ai/generate" />
169
+ ```
170
+
171
+ ```ts
172
+ import { registerPlugin, aiAssistantPlugin } from "@one-million-lines/email-builder";
173
+ registerPlugin(aiAssistantPlugin({ endpoint: "http://localhost:3001/ai/generate" }));
174
+ ```
175
+
176
+ The assistant only ever assembles emails from real, renderable modules, and every
177
+ response is validated with Zod before it is applied. See
178
+ [`src/ai/README.md`](./src/ai/README.md) and
179
+ [`backend/README.md`](./backend/README.md).
180
+
134
181
  ### Styling & isolation
135
182
 
136
183
  The stylesheet at `@one-million-lines/email-builder/styles.css` is designed to
@@ -212,6 +259,20 @@ npm run validate:pack # build + npm pack --dry-run
212
259
  npm run build:demo # build the standalone demo app
213
260
  ```
214
261
 
262
+ ### AI backend (optional)
263
+
264
+ The AI assistant needs the Python service in [`backend/`](./backend):
265
+
266
+ ```bash
267
+ cd backend
268
+ python -m venv .venv && source .venv/bin/activate
269
+ pip install -r requirements.txt
270
+ python app.py # offline heuristic mode; set AI_API_KEY for a real model
271
+ ```
272
+
273
+ Then set `VITE_AI_ENDPOINT=http://localhost:3001/ai/generate` in a root `.env`
274
+ to enable the AI tab in `npm run dev`.
275
+
215
276
  ## Building & publishing
216
277
 
217
278
  ```bash
@@ -0,0 +1,3 @@
1
+ /** Hook: is an AI provider currently configured? Reactive. */
2
+ export declare function useAIAvailable(): boolean;
3
+ export declare function AIChatPanel(): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { EmailDocument } from '../core/types';
2
+ import { AIResponse } from '../core/aiActions';
3
+ export interface ApplyResult {
4
+ document: EmailDocument;
5
+ /** Assistant chat text, if any. */
6
+ text?: string;
7
+ /** Short human summary of what changed, for the chat log. */
8
+ summary: string;
9
+ }
10
+ /**
11
+ * Apply an {@link AIResponse} to `doc`. Returns the next document plus a summary,
12
+ * or an error string when the result fails validation.
13
+ */
14
+ export declare function applyAIResponse(doc: EmailDocument, res: AIResponse): {
15
+ ok: true;
16
+ result: ApplyResult;
17
+ } | {
18
+ ok: false;
19
+ error: string;
20
+ };
@@ -0,0 +1,17 @@
1
+ import { EmailModule } from '../core/types';
2
+ export interface CatalogEntry {
3
+ type: string;
4
+ category: string;
5
+ name: string;
6
+ description: string;
7
+ tags: string[];
8
+ /** Whether the module comes from a gallery (vs a built-in pack). */
9
+ source: "module" | "gallery";
10
+ /** A concrete, renderable instance of the module (ids included). */
11
+ sample: EmailModule;
12
+ }
13
+ /**
14
+ * Build the catalog sent to the AI backend. Gallery items are listed first so
15
+ * the model is nudged to prefer freshly added styles, mirroring the sidebar.
16
+ */
17
+ export declare function buildCatalog(): CatalogEntry[];
@@ -0,0 +1,22 @@
1
+ import { Plugin } from '../core/plugins';
2
+ import { HttpAIProviderOptions } from './provider';
3
+ export { createHttpAIProvider } from './provider';
4
+ export type { HttpAIProviderOptions } from './provider';
5
+ export { buildCatalog } from './catalog';
6
+ export type { CatalogEntry } from './catalog';
7
+ export { applyAIResponse } from './applyResponse';
8
+ export type { ApplyResult } from './applyResponse';
9
+ export { AIChatPanel, useAIAvailable } from './AIChatPanel';
10
+ export { getAIProvider as getActiveAIProvider, setAIProvider as setActiveAIProvider, } from './state';
11
+ export interface AIAssistantOptions extends HttpAIProviderOptions {
12
+ }
13
+ /**
14
+ * Plugin factory that wires an HTTP-backed AI provider into the builder and
15
+ * enables the chat panel.
16
+ *
17
+ * @example
18
+ * import { registerPlugin } from "@one-million-lines/email-builder";
19
+ * import { aiAssistantPlugin } from "@one-million-lines/email-builder";
20
+ * registerPlugin(aiAssistantPlugin({ endpoint: "http://localhost:3001/ai/generate" }));
21
+ */
22
+ export declare function aiAssistantPlugin(opts: AIAssistantOptions): Plugin;
@@ -0,0 +1,23 @@
1
+ import { AIProvider, AIResponse } from '../core/aiActions';
2
+ export interface HttpAIProviderOptions {
3
+ /** Absolute or same-origin URL that implements POST /ai/generate. Required. */
4
+ endpoint: string;
5
+ /** Extra headers (e.g. Authorization). */
6
+ headers?: Record<string, string>;
7
+ /** Send cookies with the request. Default: false. */
8
+ withCredentials?: boolean;
9
+ /** Abort the request after this many ms. Default: 60000. */
10
+ timeoutMs?: number;
11
+ /**
12
+ * Map a raw server JSON body to an `AIResponse`.
13
+ * Default expects the body to already be an `AIResponse`.
14
+ */
15
+ transformResponse?: (body: unknown) => AIResponse;
16
+ }
17
+ /**
18
+ * Create an {@link AIProvider} backed by an HTTP endpoint.
19
+ *
20
+ * @example
21
+ * const provider = createHttpAIProvider({ endpoint: "http://localhost:3001/ai/generate" });
22
+ */
23
+ export declare function createHttpAIProvider(opts: HttpAIProviderOptions): AIProvider;
@@ -0,0 +1,11 @@
1
+ import { AIProvider } from '../core/aiActions';
2
+ type Listener = () => void;
3
+ /** Set (or clear) the active AI provider. Notifies subscribers. */
4
+ export declare function setAIProvider(next: AIProvider | null): void;
5
+ /** The active AI provider, or null when AI is not configured. */
6
+ export declare function getAIProvider(): AIProvider | null;
7
+ /** Subscribe to provider changes (for `useSyncExternalStore`). */
8
+ export declare const subscribeAIProvider: (listener: Listener) => (() => void);
9
+ /** Monotonic version; changes whenever the provider is set or cleared. */
10
+ export declare const getAIProviderVersion: () => number;
11
+ export {};