@denarolabs/email-template 1.0.5 → 1.0.6

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 (52) hide show
  1. package/dist/index.d.ts +425 -8
  2. package/dist/index.js +9841 -7
  3. package/package.json +16 -23
  4. package/dist/components/ai-email-editor/AIEmailEditor.d.ts +0 -3
  5. package/dist/components/ai-email-editor/AIEmailEditor.js +0 -207
  6. package/dist/components/ai-email-editor/chat-message.d.ts +0 -36
  7. package/dist/components/ai-email-editor/chat-message.js +0 -49
  8. package/dist/components/ai-email-editor/images.d.ts +0 -2
  9. package/dist/components/ai-email-editor/images.js +0 -14
  10. package/dist/components/ai-email-editor/model-picker.d.ts +0 -7
  11. package/dist/components/ai-email-editor/model-picker.js +0 -9
  12. package/dist/components/ai-email-editor/preview.d.ts +0 -35
  13. package/dist/components/ai-email-editor/preview.js +0 -728
  14. package/dist/components/ai-email-editor/send-test-email-modal.d.ts +0 -13
  15. package/dist/components/ai-email-editor/send-test-email-modal.js +0 -70
  16. package/dist/components/ai-email-editor/stream.d.ts +0 -20
  17. package/dist/components/ai-email-editor/stream.js +0 -57
  18. package/dist/components/ai-email-editor/use-ai-email-editor.d.ts +0 -117
  19. package/dist/components/ai-email-editor/use-ai-email-editor.js +0 -1308
  20. package/dist/components/ai-email-editor/view-html-modal.d.ts +0 -9
  21. package/dist/components/ai-email-editor/view-html-modal.js +0 -37
  22. package/dist/lib/ai-stream-contract.d.ts +0 -99
  23. package/dist/lib/ai-stream-contract.js +0 -35
  24. package/dist/lib/build-default-system-prompt.d.ts +0 -2
  25. package/dist/lib/build-default-system-prompt.js +0 -37
  26. package/dist/lib/capture-email-preview.d.ts +0 -5
  27. package/dist/lib/capture-email-preview.js +0 -73
  28. package/dist/lib/cn.d.ts +0 -2
  29. package/dist/lib/cn.js +0 -5
  30. package/dist/lib/merge-tag-validation.d.ts +0 -3
  31. package/dist/lib/merge-tag-validation.js +0 -45
  32. package/dist/lib/rasterize-image-client.d.ts +0 -4
  33. package/dist/lib/rasterize-image-client.js +0 -47
  34. package/dist/lib/strip-html-code-fences.d.ts +0 -1
  35. package/dist/lib/strip-html-code-fences.js +0 -6
  36. package/dist/schemas/aiEmail.d.ts +0 -224
  37. package/dist/schemas/aiEmail.js +0 -29
  38. package/dist/schemas/aiEmailResponse.d.ts +0 -15
  39. package/dist/schemas/aiEmailResponse.js +0 -15
  40. package/dist/types.d.ts +0 -57
  41. package/dist/types.js +0 -1
  42. package/dist/ui/button.d.ts +0 -11
  43. package/dist/ui/button.js +0 -34
  44. package/dist/ui/dialog.d.ts +0 -33
  45. package/dist/ui/dialog.js +0 -39
  46. package/dist/ui/dropdown-menu.d.ts +0 -8
  47. package/dist/ui/dropdown-menu.js +0 -23
  48. package/dist/ui/input.d.ts +0 -2
  49. package/dist/ui/input.js +0 -6
  50. package/dist/ui/textarea.d.ts +0 -2
  51. package/dist/ui/textarea.js +0 -7
  52. package/src/styles.css +0 -197
@@ -1,13 +0,0 @@
1
- import type { AIEmailEditorErrorContext, AIEmailEditorSuccessContext, SendTestEmailInput } from "../../types";
2
- export declare function SendTestEmailModal({ open, onOpenChange, getHtml, templateName, defaultMergeTagValues, defaultRecipient, defaultSubject, onSendTestEmail, onError, onSuccess, }: {
3
- open: boolean;
4
- onOpenChange: (open: boolean) => void;
5
- getHtml: () => string;
6
- templateName: string;
7
- defaultMergeTagValues?: Record<string, string>;
8
- defaultRecipient?: string;
9
- defaultSubject?: string;
10
- onSendTestEmail?: (input: SendTestEmailInput) => Promise<void>;
11
- onError: (error: Error, context: AIEmailEditorErrorContext) => void;
12
- onSuccess?: (context: AIEmailEditorSuccessContext) => void;
13
- }): import("react").JSX.Element;
@@ -1,70 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Loader2 } from "lucide-react";
4
- import { useEffect, useMemo, useState } from "react";
5
- import { applyMergeTags, extractMergeTags } from "../../lib/merge-tag-validation";
6
- import { Button } from "../../ui/button";
7
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogTitle, } from "../../ui/dialog";
8
- import { Input } from "../../ui/input";
9
- export function SendTestEmailModal({ open, onOpenChange, getHtml, templateName, defaultMergeTagValues, defaultRecipient, defaultSubject, onSendTestEmail, onError, onSuccess, }) {
10
- const [sourceHtml, setSourceHtml] = useState("");
11
- const [to, setTo] = useState("");
12
- const [subject, setSubject] = useState("");
13
- const [values, setValues] = useState({});
14
- const [sending, setSending] = useState(false);
15
- const tags = useMemo(() => extractMergeTags(sourceHtml), [sourceHtml]);
16
- useEffect(() => {
17
- if (!open)
18
- return;
19
- const html = getHtml();
20
- const nextTags = extractMergeTags(html);
21
- const nextValues = {};
22
- for (const tag of nextTags) {
23
- nextValues[tag] = defaultMergeTagValues?.[tag] ?? "";
24
- }
25
- setSourceHtml(html);
26
- setTo(defaultRecipient ?? "");
27
- setSubject(defaultSubject ?? templateName);
28
- setValues(nextValues);
29
- setSending(false);
30
- }, [open, getHtml, defaultMergeTagValues, defaultRecipient, defaultSubject, templateName]);
31
- const handleSend = async () => {
32
- const trimmedTo = to.trim();
33
- const trimmedSubject = subject.trim();
34
- if (!trimmedTo) {
35
- onError(new Error("Recipient email is required."), { source: "send" });
36
- return;
37
- }
38
- if (!trimmedSubject) {
39
- onError(new Error("Subject is required."), { source: "send" });
40
- return;
41
- }
42
- const html = applyMergeTags(sourceHtml, values);
43
- const input = {
44
- to: trimmedTo,
45
- subject: trimmedSubject,
46
- html,
47
- mergeTagValues: values,
48
- };
49
- if (!onSendTestEmail) {
50
- onError(new Error("onSendTestEmail is not configured."), { source: "send" });
51
- return;
52
- }
53
- setSending(true);
54
- try {
55
- await onSendTestEmail(input);
56
- onSuccess?.({ source: "send", message: "Test email sent" });
57
- onOpenChange(false);
58
- }
59
- catch (error) {
60
- onError(error instanceof Error ? error : new Error("Failed to send test email."), { source: "send" });
61
- }
62
- finally {
63
- setSending(false);
64
- }
65
- };
66
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { showCloseButton: true, onClose: () => onOpenChange(false), className: "flex max-h-[85vh] max-w-lg flex-col gap-0 p-0", children: [_jsxs(DialogHeader, { className: "shrink-0 gap-1.5 px-6 pb-3 pt-4 pr-12", children: [_jsx(DialogTitle, { children: "Send test email" }), _jsx(DialogDescription, { children: "Fill in values for merge tags." })] }), _jsxs(DialogPanel, { className: "space-y-4 px-6 pb-6 pt-0", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { htmlFor: "test-email-to", className: "text-sm font-medium", children: "To" }), _jsx(Input, { id: "test-email-to", type: "email", value: to, onChange: (event) => setTo(event.target.value), placeholder: "you@example.com", disabled: sending })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { htmlFor: "test-email-subject", className: "text-sm font-medium", children: "Subject" }), _jsx(Input, { id: "test-email-subject", value: subject, onChange: (event) => setSubject(event.target.value), placeholder: templateName, disabled: sending })] }), tags.length > 0 ? (tags.map((tag) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { htmlFor: `merge-tag-${tag}`, className: "text-sm font-medium", children: `{{${tag}}}` }), _jsx(Input, { id: `merge-tag-${tag}`, value: values[tag] ?? "", onChange: (event) => setValues((prev) => ({
67
- ...prev,
68
- [tag]: event.target.value,
69
- })), placeholder: `Value for {{${tag}}}`, disabled: sending })] }, tag)))) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "No merge tags." }))] }), _jsxs(DialogFooter, { className: "shrink-0", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: sending, children: "Cancel" }), _jsx(Button, { type: "button", onClick: () => void handleSend(), disabled: sending, children: sending ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Sending\u2026"] })) : ("Send email") })] })] }) }));
70
- }
@@ -1,20 +0,0 @@
1
- import { type DeepPartial } from "ai";
2
- import type { AiEmailResponse } from "../../schemas/aiEmailResponse";
3
- export type PartialAiEmailResponse = DeepPartial<AiEmailResponse>;
4
- export declare function phaseLabel(phase: "thinking" | "planning" | "applying"): string;
5
- export declare function formatPlan(plan: (string | undefined)[] | undefined): string;
6
- export declare function extractStreamParts(partial: PartialAiEmailResponse | undefined): {
7
- narration: string;
8
- suggestions: string[];
9
- html: string;
10
- };
11
- export declare function parseStructuredStream(accumulatedText: string): Promise<{
12
- html?: string | undefined;
13
- plan?: (string | undefined)[] | undefined;
14
- suggestions?: (string | undefined)[] | undefined;
15
- } | undefined>;
16
- export declare function detectPhase(partial: PartialAiEmailResponse | undefined): "thinking" | "planning" | "applying";
17
- export declare const DEFAULT_NEW_TEMPLATE_SUGGESTIONS: string[];
18
- export declare const DEFAULT_EDIT_SUGGESTIONS: string[];
19
- export declare function defaultSuggestions(hasHtml: boolean): string[];
20
- export declare function resolveInitialSuggestions(suggestions?: readonly string[]): readonly string[];
@@ -1,57 +0,0 @@
1
- import { parsePartialJson } from "ai";
2
- const PHASE_LABELS = {
3
- thinking: "Analyzing your request…",
4
- planning: "Planning changes…",
5
- applying: "Applying to preview…",
6
- };
7
- export function phaseLabel(phase) {
8
- return PHASE_LABELS[phase];
9
- }
10
- export function formatPlan(plan) {
11
- if (!plan?.length)
12
- return "";
13
- return plan
14
- .filter((line) => typeof line === "string" && line.trim().length > 0)
15
- .map((line) => (line.startsWith("- ") ? line : `- ${line}`))
16
- .join("\n");
17
- }
18
- export function extractStreamParts(partial) {
19
- const narration = formatPlan(partial?.plan);
20
- const suggestions = (partial?.suggestions ?? [])
21
- .filter((s) => typeof s === "string" && s.trim().length > 0)
22
- .slice(0, 3);
23
- const html = typeof partial?.html === "string" ? partial.html : "";
24
- return { narration, suggestions, html };
25
- }
26
- export async function parseStructuredStream(accumulatedText) {
27
- const { value } = await parsePartialJson(accumulatedText);
28
- return value;
29
- }
30
- export function detectPhase(partial) {
31
- if (!partial)
32
- return "thinking";
33
- const { narration, html } = extractStreamParts(partial);
34
- if (html)
35
- return "applying";
36
- if (narration.trim())
37
- return "planning";
38
- return "thinking";
39
- }
40
- export const DEFAULT_NEW_TEMPLATE_SUGGESTIONS = [
41
- "A welcome email with a clear call-to-action",
42
- "A short update email with a summary and next steps",
43
- "A friendly follow-up with one primary button",
44
- ];
45
- export const DEFAULT_EDIT_SUGGESTIONS = [
46
- "Make the header darker and bolder",
47
- "Shorten the intro to one sentence",
48
- "Add a P.S. about our support line",
49
- ];
50
- export function defaultSuggestions(hasHtml) {
51
- return hasHtml ? DEFAULT_EDIT_SUGGESTIONS : DEFAULT_NEW_TEMPLATE_SUGGESTIONS;
52
- }
53
- export function resolveInitialSuggestions(suggestions) {
54
- if (!suggestions?.length)
55
- return DEFAULT_NEW_TEMPLATE_SUGGESTIONS;
56
- return suggestions;
57
- }
@@ -1,117 +0,0 @@
1
- import type { AIEmailEditorMode, AIEmailEditorErrorSource, AIEmailEditorSuccessSource, AIEmailModelOption, StoredEmailRecord } from "../../types";
2
- import type { ClipboardEvent as ReactClipboardEvent, DragEvent as ReactDragEvent } from "react";
3
- export declare function useAiEmailEditor({ name, savedTemplate, onSave, streamRoute, mergeTags, mode, systemPrompt, onUploadImage, onDeleteImage, onError, onSuccess, open, models, defaultModel, initialSuggestions, }: {
4
- name: string;
5
- savedTemplate: StoredEmailRecord | null;
6
- onSave: (record: StoredEmailRecord) => Promise<void> | void;
7
- streamRoute: string;
8
- mergeTags: string[];
9
- mode?: AIEmailEditorMode;
10
- systemPrompt?: string;
11
- onUploadImage: (file: File | Blob) => Promise<{
12
- url: string;
13
- }>;
14
- onDeleteImage: (url: string) => Promise<void>;
15
- onError: (error: Error, context: {
16
- source: AIEmailEditorErrorSource;
17
- }) => void;
18
- onSuccess?: (context: {
19
- source: AIEmailEditorSuccessSource;
20
- message: string;
21
- }) => void;
22
- open: boolean;
23
- defaultModel?: string;
24
- models?: readonly AIEmailModelOption[];
25
- initialSuggestions?: readonly string[];
26
- }): {
27
- iframeRef: import("react").RefObject<HTMLIFrameElement | null>;
28
- scrollRef: import("react").RefObject<HTMLDivElement | null>;
29
- chatPinSpacerHeight: number;
30
- hasHtml: boolean;
31
- loading: boolean;
32
- saving: boolean;
33
- hasSessionChanges: boolean;
34
- getCleanHtml: () => string;
35
- messages: ({
36
- id: string;
37
- role: "user" | "assistant";
38
- content: string;
39
- timestamp: number;
40
- contextImages?: {
41
- id: string;
42
- url: string;
43
- name?: string | undefined;
44
- }[] | undefined;
45
- appliedHtml?: boolean | undefined;
46
- snapshotId?: string | undefined;
47
- selectedBlockIds?: string[] | undefined;
48
- phase?: "thinking" | "planning" | "applying" | undefined;
49
- } & {
50
- pending?: boolean;
51
- error?: boolean;
52
- })[];
53
- input: string;
54
- setInput: import("react").Dispatch<import("react").SetStateAction<string>>;
55
- contextImages: {
56
- id: string;
57
- url: string;
58
- name?: string | undefined;
59
- }[];
60
- contextImageBusy: boolean;
61
- removeContextImage: (url: string) => Promise<void>;
62
- removeMessageContextImage: (messageId: string, url: string) => Promise<void>;
63
- addContextImageFromFile: (file: File) => Promise<void>;
64
- canRemoveContextImages: boolean;
65
- chatDropActive: boolean;
66
- handleChatPaste: (event: ReactClipboardEvent) => void;
67
- handleChatDragOver: (event: ReactDragEvent) => void;
68
- handleChatDragLeave: (event: ReactDragEvent) => void;
69
- handleChatDrop: (event: ReactDragEvent) => void;
70
- isStreaming: boolean;
71
- editingMessageId: string | null;
72
- editDraft: string;
73
- setEditDraft: import("react").Dispatch<import("react").SetStateAction<string>>;
74
- editContextImages: {
75
- url: string;
76
- label: string;
77
- }[];
78
- addEditContextImageFromFile: (file: File) => Promise<void>;
79
- removeEditContextImage: (url: string) => void;
80
- startEditing: (id: string) => void;
81
- cancelEditing: () => void;
82
- requestSubmitEdit: () => void;
83
- confirmSubmitEdit: () => void;
84
- cancelSubmitConfirm: () => void;
85
- resubmitConfirmOpen: boolean;
86
- handleSend: () => void;
87
- cancelStream: () => void;
88
- canRevertToCheckpoint: (userMessageId: string) => boolean;
89
- requestRevertToCheckpoint: (userMessageId: string) => void;
90
- confirmRevertToCheckpoint: () => void;
91
- cancelRevertConfirm: () => void;
92
- revertConfirmOpen: boolean;
93
- suggestions: string[];
94
- defaultSystemPrompt: string;
95
- effectiveSystemPrompt: string;
96
- applySystemPrompt: (next: string) => boolean;
97
- resetSystemPrompt: () => void;
98
- selectMode: boolean;
99
- exitSelectMode: () => void;
100
- toggleSelectMode: () => void;
101
- selectedBlockIds: string[];
102
- toggleBlockSelection: (blockId: string) => void;
103
- canGoBack: boolean;
104
- requestBack: () => void;
105
- confirmBack: () => void;
106
- cancelBack: () => void;
107
- backConfirmOpen: boolean;
108
- backConfirmSummary: string;
109
- previewDropActive: boolean;
110
- handleSave: () => Promise<boolean>;
111
- handleRestart: () => Promise<void>;
112
- canRestart: boolean;
113
- modelOptions: readonly AIEmailModelOption[];
114
- selectedModel: string;
115
- setSelectedModel: import("react").Dispatch<import("react").SetStateAction<string>>;
116
- showModelSelector: boolean;
117
- };