best-email-builder 0.5.1 → 0.7.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 +63 -0
- package/README.md +74 -8
- package/dist/ai-server.js +116 -1
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +639 -176
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +4 -4
- package/dist/{chunk-2Y7CDXZM.js → chunk-3RPWH5PC.js} +3 -3
- package/dist/{chunk-H4W65GBX.js → chunk-C6WHIBGH.js} +2 -2
- package/dist/{chunk-H4W65GBX.js.map → chunk-C6WHIBGH.js.map} +1 -1
- package/dist/{chunk-BAA5XMWY.js → chunk-CTZ56YJG.js} +252 -28
- package/dist/chunk-CTZ56YJG.js.map +1 -0
- package/dist/{chunk-7QQH6MTV.js → chunk-QH5OI43L.js} +4 -4
- package/dist/{chunk-N7DEVAIP.js → chunk-QRUMXCGW.js} +2 -2
- package/dist/{chunk-2RKVJQSD.js → chunk-UMVBT7NW.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/library.js +4 -4
- package/dist/server.js +4 -4
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +5 -0
- package/dist/types/ai/ModelPicker.d.ts +34 -0
- package/dist/types/ai/agent.d.ts +31 -0
- package/dist/types/ai/agentEndpoint.d.ts +69 -0
- package/dist/types/ai/claude.d.ts +31 -5
- package/dist/types/ai/createAi.d.ts +5 -3
- package/dist/types/ai/draft.d.ts +10 -4
- package/dist/types/ai/generator.d.ts +10 -4
- package/dist/types/ai/index.d.ts +3 -2
- package/dist/types/ai/keys.d.ts +28 -26
- package/dist/types/ai/providers.d.ts +58 -1
- package/dist/types/ai/rewrite.d.ts +2 -1
- package/dist/types/ai/server.d.ts +3 -1
- package/dist/types/ai/subjects.d.ts +2 -1
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/server/handler.d.ts +3 -0
- package/package.json +1 -1
- package/dist/chunk-BAA5XMWY.js.map +0 -1
- /package/dist/{chunk-2Y7CDXZM.js.map → chunk-3RPWH5PC.js.map} +0 -0
- /package/dist/{chunk-7QQH6MTV.js.map → chunk-QH5OI43L.js.map} +0 -0
- /package/dist/{chunk-N7DEVAIP.js.map → chunk-QRUMXCGW.js.map} +0 -0
- /package/dist/{chunk-2RKVJQSD.js.map → chunk-UMVBT7NW.js.map} +0 -0
package/dist/ai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/keys.js","../src/ai/ConnectAi.jsx","../src/ai/GenerateButton.jsx","../src/ai/chip.js","../src/ai/RewriteText.jsx","../src/ai/SubjectIdeas.jsx","../src/ai/createAi.js"],"sourcesContent":["// @ts-check\n/**\n * THE USER'S KEY — when the AI runs in the page with `askKey: true`, each\n * user connects their own provider key, once.\n *\n * The first AI action asks for it (the Connect dialog, `ConnectAi.jsx`); it is\n * kept in this browser (localStorage, one entry per provider) and sent only to\n * the provider. A key the provider refuses is forgotten, and asked again.\n *\n * ai.key.get() the key in use ('' when none)\n * ai.key.set(key) connect a key without the dialog\n * ai.key.forget() disconnect: the next AI action asks again\n * ai.key.request() ask the user now → the key, or '' when they cancel\n * ai.key.subscribe(fn) called on every change; returns the unsubscribe\n * ai.key.provider the provider's name, where to get a key, what is free\n */\n\n/** @typedef {import('./providers.js').AiProvider} AiProvider */\n\nconst STORAGE_PREFIX = 'best-email-builder:ai-key:';\n\n/** localStorage when there is one that works (not in private modes that throw, not on the server). */\nfunction browserStorage() {\n try {\n const storage = globalThis.localStorage;\n return storage && typeof storage.getItem === 'function' ? storage : null;\n } catch {\n return null;\n }\n}\n\n/**\n * @typedef {{\n * provider: Readonly<{id: string, name: string, keyUrl: string|null, keyHint: string, free: string|null}>,\n * get: () => string,\n * set: (key: string) => void,\n * forget: () => void,\n * request: () => Promise<string>,\n * subscribe: (listener: () => void) => () => void,\n * state: () => Readonly<{asking: boolean, refused: boolean, connected: boolean}>,\n * answer: (key: string|null) => void,\n * refuse: () => void,\n * }} AiKey\n * `state`, `answer` and `refuse` serve the Connect dialog and the module:\n * whether the user is being asked, whether the last key was refused.\n */\n\n/**\n * @param {AiProvider} provider\n * @returns {AiKey}\n */\nexport function createKeyStore(provider) {\n const slot = STORAGE_PREFIX + provider.id;\n /** Kept here too: a browser that does not store still works for the page's life. */\n let memory = '';\n let refused = false;\n /** @type {{promise: Promise<string>, resolve: (key: string) => void}|null} */\n let pending = null;\n /** @type {Set<() => void>} */\n const listeners = new Set();\n /** @type {ReturnType<AiKey['state']>} */\n let snapshot;\n\n const read = () => {\n try {\n return browserStorage()?.getItem(slot) || '';\n } catch {\n return '';\n }\n };\n /** @param {string} value */\n const write = (value) => {\n try {\n const storage = browserStorage();\n if (value) storage?.setItem(slot, value);\n else storage?.removeItem(slot);\n } catch {\n // Full, or blocked: the key stays in memory.\n }\n };\n const get = () => memory || read();\n const changed = () => {\n snapshot = Object.freeze({ asking: Boolean(pending), refused, connected: Boolean(get()) });\n for (const listener of [...listeners]) listener();\n };\n snapshot = Object.freeze({ asking: false, refused: false, connected: Boolean(get()) });\n\n /** @param {string} key */\n const answer = (key) => {\n const value = String(key || '').trim();\n if (value) {\n memory = value;\n refused = false;\n write(value);\n }\n const waiting = pending;\n pending = null;\n changed();\n waiting?.resolve(value);\n };\n\n return Object.freeze({\n provider: Object.freeze({ id: provider.id, name: provider.name, keyUrl: provider.keyUrl, keyHint: provider.keyHint, free: provider.free }),\n get,\n set: (/** @type {string} */ key) => {\n if (typeof key !== 'string' || !key.trim()) throw new TypeError('ai.key.set: a non-empty key');\n answer(key);\n },\n forget: () => {\n memory = '';\n write('');\n changed();\n },\n request: () => {\n if (!pending) {\n /** @type {(key: string) => void} */\n let resolve = () => {};\n const promise = new Promise((done) => (resolve = done));\n pending = { promise, resolve };\n changed();\n }\n return pending.promise;\n },\n subscribe: (/** @type {() => void} */ listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n state: () => snapshot,\n answer: (/** @type {string|null} */ key) => answer(key || ''),\n refuse: () => {\n memory = '';\n write('');\n refused = true;\n changed();\n },\n });\n}\n","import { useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport { ArrowUpRight, Eye, EyeOff, Gift, KeyRound, ShieldCheck, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input, Label } from '../ui/primitives/fields.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/primitives/overlays.jsx';\n\n/**\n * CONNECT — the dialog that asks the user for their provider key, when the AI\n * runs in the page with `createAi({ provider, askKey: true })`.\n *\n * It opens by itself when an AI action needs a key and there is none, or the\n * provider refused the last one; the action goes on once the key is given.\n * Cancel ends the action (\"Connect an AI provider first\"). The key is kept in\n * this browser and sent only to the provider (see keys.js).\n */\nexport default function ConnectAi({ aiKey }) {\n const { t } = useUi();\n const state = useSyncExternalStore(aiKey.subscribe, aiKey.state, aiKey.state);\n const [value, setValue] = useState('');\n const [shown, setShown] = useState(false);\n const inputRef = useRef(/** @type {HTMLInputElement|null} */ (null));\n const inputId = useId();\n const hintId = useId();\n const { name, keyUrl, keyHint, free } = aiKey.provider;\n\n // Each time it opens: an empty field, the key hidden.\n useEffect(() => {\n if (!state.asking) return;\n setValue('');\n setShown(false);\n }, [state.asking]);\n\n const key = value.trim();\n // A key from elsewhere (another provider's) is the usual mistake: said, not blocked.\n const unusual = Boolean(key && keyHint && !key.startsWith(keyHint));\n\n const submit = (/** @type {React.FormEvent} */ event) => {\n event.preventDefault();\n if (key) aiKey.answer(key);\n };\n\n return (\n <Dialog open={state.asking} onOpenChange={(open) => !open && aiKey.answer(null)}>\n <DialogContent\n className=\"eb:gap-0 eb:overflow-hidden eb:p-0 eb:sm:max-w-md\"\n data-testid=\"eb-ai-connect\"\n onOpenAutoFocus={(event) => {\n event.preventDefault();\n inputRef.current?.focus();\n }}\n >\n <form onSubmit={submit}>\n <div className=\"eb:flex eb:flex-col eb:gap-5 eb:px-6 eb:pt-6 eb:pb-5\">\n <DialogHeader className=\"eb:flex-row eb:items-start eb:gap-3.5 eb:pr-6\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-full eb:bg-primary/10 eb:text-primary\">\n <KeyRound className=\"eb:size-5\" aria-hidden=\"true\" />\n </span>\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\">\n <DialogTitle>{t('Connect {provider}', { provider: name })}</DialogTitle>\n <DialogDescription className=\"eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter\">\n {t('The AI writes with your own {provider} key. Connect it once: this browser remembers it.', { provider: name })}\n </DialogDescription>\n </div>\n </DialogHeader>\n\n {state.refused && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-2 eb:rounded-md eb:border eb:border-destructive/30 eb:bg-destructive/10 eb:px-3 eb:py-2 eb:text-[12.5px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {t('{provider} refused the last key. Paste another one.', { provider: name })}\n </p>\n )}\n\n {(keyUrl || free) && (\n <div className=\"eb:flex eb:flex-wrap eb:items-center eb:gap-x-3 eb:gap-y-2\">\n {keyUrl && (\n <Button asChild variant=\"outline\" size=\"sm-compact\">\n <a href={keyUrl} target=\"_blank\" rel=\"noopener noreferrer\" data-testid=\"eb-ai-connect-get-key\">\n {t('Get a key')}\n <ArrowUpRight aria-hidden=\"true\" />\n </a>\n </Button>\n )}\n {free && (\n <span className=\"eb:inline-flex eb:items-center eb:gap-1.5 eb:rounded-full eb:bg-success/10 eb:px-2.5 eb:py-1 eb:text-[11.5px] eb:font-medium eb:text-success\">\n <Gift className=\"eb:size-3.5\" aria-hidden=\"true\" />\n {t(free)}\n </span>\n )}\n </div>\n )}\n\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\">\n <Label htmlFor={inputId}>{t('API key')}</Label>\n <div className=\"eb:relative\">\n <Input\n ref={inputRef}\n id={inputId}\n type={shown ? 'text' : 'password'}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n placeholder={keyHint ? `${keyHint}…` : t('Paste the key')}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n aria-describedby={hintId}\n className=\"eb:h-9 eb:pr-10 eb:font-mono eb:text-[12.5px]\"\n data-testid=\"eb-ai-connect-key\"\n />\n <button\n type=\"button\"\n className=\"eb:absolute eb:top-1/2 eb:right-1 eb:flex eb:size-7 eb:-translate-y-1/2 eb:items-center eb:justify-center eb:rounded eb:text-foreground-muted eb:transition-colors eb:outline-none eb:hover:bg-muted eb:hover:text-foreground eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50\"\n onClick={() => setShown((current) => !current)}\n aria-label={shown ? t('Hide the key') : t('Show the key')}\n aria-pressed={shown}\n >\n {shown ? <EyeOff className=\"eb:size-4\" aria-hidden=\"true\" /> : <Eye className=\"eb:size-4\" aria-hidden=\"true\" />}\n </button>\n </div>\n <p id={hintId} className=\"eb:min-h-4 eb:text-[11.5px] eb:text-foreground-muted\">\n {unusual ? (\n <span className=\"eb:text-warning\">{t('{provider} keys usually start with {prefix}.', { provider: name, prefix: keyHint })}</span>\n ) : keyHint ? (\n t('{provider} keys start with {prefix}.', { provider: name, prefix: keyHint })\n ) : null}\n </p>\n </div>\n </div>\n\n <div className=\"eb:flex eb:items-start eb:gap-2 eb:border-t eb:border-border eb:bg-surface-75 eb:px-6 eb:py-3 eb:text-[11.5px] eb:leading-relaxed eb:text-foreground-lighter\">\n <ShieldCheck className=\"eb:mt-0.5 eb:size-3.5 eb:shrink-0 eb:text-foreground-muted\" aria-hidden=\"true\" />\n <span>{t('The key stays in this browser and is sent only to {provider}.', { provider: name })}</span>\n </div>\n\n <DialogFooter className=\"eb:border-t eb:border-border eb:px-6 eb:py-4\">\n <Button type=\"button\" variant=\"ghost\" onClick={() => aiKey.answer(null)}>\n {t('Cancel')}\n </Button>\n <Button type=\"submit\" disabled={!key} data-testid=\"eb-ai-connect-submit\">\n {t('Connect')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Check, Loader2, Search, Sparkles, TriangleAlert } from 'lucide-react';\nimport { SEARCH_KEYS, SearchKeyHint, useSearchShortcut } from '../ui/primitives/searchShortcut.jsx';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input, Textarea } from '../ui/primitives/fields.jsx';\nimport { SectionLabel } from '../ui/primitives/misc.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/primitives/overlays.jsx';\nimport { chipClassName } from './chip.js';\nimport { MAX_DETAILS_LENGTH } from './generator.js';\nimport { purposesByCategory } from './purposes.js';\nimport { brandTemplate, hasBrand } from '../features/brand/brand.js';\n\n/**\n * GENERATE — toolbar button and dialog: an email written from scratch.\n *\n * One decision for the user: what the email is for (a badge). Details are\n * optional; the language is the interface's, the sender and the variables\n * are the builder's own. The dialog says up front that the email in the\n * editor will be replaced — no confirmation after a minute of waiting.\n *\n * The result REPLACES the email in the editor, subject and preview text\n * included (they belong to the new email); the name is filled when empty.\n * A saved template counts as modified: the next save keeps it.\n */\n\n/** Lowercase, without accents: \"Été\" matches \"ete\". @param {string} text */\nconst fold = (text) =>\n text\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n\nexport default function GenerateButton({ context, generate: generateEmail }) {\n const { editor, ready, isDirty, markDirty, template, setMeta, variables, sender, brand } = context;\n const { t, locale, notify } = useUi();\n const [open, setOpen] = useState(false);\n const [purpose, setPurpose] = useState(/** @type {string|null} */ (null));\n const [details, setDetails] = useState('');\n const [query, setQuery] = useState('');\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState('');\n const [seconds, setSeconds] = useState(0);\n const [dirty, setDirty] = useState(false);\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const searchRef = useRef(/** @type {HTMLInputElement|null} */ (null));\n useSearchShortcut(searchRef);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n useEffect(() => {\n if (!busy) return undefined;\n setSeconds(0);\n const timer = setInterval(() => setSeconds((s) => s + 1), 1000);\n return () => clearInterval(timer);\n }, [busy]);\n\n const groups = useMemo(() => {\n const words = fold(query.trim()).split(/\\s+/).filter(Boolean);\n return purposesByCategory()\n .map((group) => ({\n ...group,\n purposes: group.purposes.filter((entry) => {\n const label = fold(`${t(entry.label)} ${entry.label} ${t(group.label)}`);\n return words.every((word) => label.includes(word));\n }),\n }))\n .filter((group) => group.purposes.length > 0);\n }, [query, t]);\n\n const openDialog = () => {\n setError('');\n setDirty(isDirty());\n setOpen(true);\n };\n\n const close = (next) => {\n if (next) return;\n abortRef.current?.abort();\n setOpen(false);\n };\n\n const generate = async () => {\n if (busy || (!purpose && !details.trim())) return;\n setError('');\n setBusy(true);\n const controller = new AbortController();\n abortRef.current = controller;\n try {\n const email = await generateEmail(\n {\n purpose,\n details: details.trim(),\n language: String(locale || 'en').slice(0, 2),\n sender: sender || {},\n ...(hasBrand(brand) ? { brand: { primary: brand.primary, heading: brand.heading, font: brand.font } } : {}),\n variables: (variables || []).map((variable) => ({ key: variable.key, label: variable.label })),\n },\n { signal: controller.signal }\n );\n if (controller.signal.aborted) return;\n // The brand's logo where the model wrote the name (an uploaded logo\n // is not an https address the model may be given).\n editor.loadMjml(hasBrand(brand) ? brandTemplate(email.mjml, brand, { sender, t }) : email.mjml);\n setMeta((meta) => ({\n ...meta,\n name: meta.name || email.name,\n subject: email.subject || meta.subject,\n preheader: email.preheader || meta.preheader,\n }));\n if (template.id) markDirty();\n setOpen(false);\n notify('success', t('Your email is ready. Edit anything, then save.'));\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('The email could not be generated.'));\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n setBusy(false);\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n setBusy(false);\n };\n\n const canGenerate = Boolean(purpose || details.trim());\n\n return (\n <>\n <Button variant=\"outline\" size=\"sm-compact\" disabled={!ready} onClick={openDialog}>\n <Sparkles />\n {t('Generate')}\n </Button>\n <Dialog open={open} onOpenChange={close}>\n <DialogContent\n className=\"eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl\"\n data-testid=\"eb-generate\"\n onOpenAutoFocus={(event) => {\n // The search field rather than the first badge.\n event.preventDefault();\n searchRef.current?.focus();\n }}\n >\n <DialogHeader className=\"eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-3\">\n <DialogTitle>{t('Generate an email')}</DialogTitle>\n <DialogDescription>{t('Pick what it is for. AI writes a complete email that opens here, ready to edit.')}</DialogDescription>\n </DialogHeader>\n\n <div className=\"eb:px-6 eb:pb-3\">\n <div className=\"eb:relative\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" aria-hidden=\"true\" />\n <Input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('Search: sale, password, birthday…')}\n aria-label={t('Search')}\n aria-keyshortcuts={SEARCH_KEYS}\n className=\"eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden\"\n disabled={busy}\n />\n <SearchKeyHint className=\"eb:absolute eb:top-1/2 eb:right-2 eb:-translate-y-1/2\" />\n </div>\n </div>\n\n {/* The scroll box is a div: a fieldset does not clip its overflow\n as a flex item, and the badges would run under the footer. */}\n <div className=\"eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-4\" data-testid=\"eb-generate-purposes\">\n <fieldset disabled={busy} className=\"eb:m-0 eb:min-w-0 eb:border-0 eb:p-0\">\n <legend className=\"eb:sr-only\">{t('What is the email for?')}</legend>\n {groups.length === 0 && (\n <p className=\"eb:py-6 eb:text-center eb:text-[13px] eb:text-foreground-muted\">\n {t('Nothing matches. Describe your email in the details below.')}\n </p>\n )}\n <div className=\"eb:flex eb:flex-col eb:gap-4\">\n {groups.map((group) => (\n <section key={group.id} className=\"eb:flex eb:flex-col eb:gap-2\" aria-label={t(group.label)}>\n <SectionLabel>{t(group.label)}</SectionLabel>\n <div className=\"eb:flex eb:flex-wrap eb:gap-1.5\">\n {group.purposes.map((entry) => {\n const selected = purpose === entry.id;\n return (\n <button\n key={entry.id}\n type=\"button\"\n aria-pressed={selected}\n data-purpose={entry.id}\n onClick={() => setPurpose(selected ? null : entry.id)}\n className={chipClassName(selected)}\n >\n {selected && <Check className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t(entry.label)}\n </button>\n );\n })}\n </div>\n </section>\n ))}\n </div>\n </fieldset>\n </div>\n\n <div className=\"eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6\">\n <Textarea\n value={details}\n onChange={(event) => setDetails(event.target.value.slice(0, MAX_DETAILS_LENGTH))}\n placeholder={t('Details (optional): product, dates, offer, tone, language…')}\n aria-label={t('Details (optional)')}\n rows={2}\n className=\"eb:min-h-14\"\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) generate();\n }}\n />\n {error ? (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n ) : (\n dirty &&\n !busy && (\n <p className=\"eb:text-[12px] eb:text-foreground-muted\">{t('The email being edited will be replaced, unsaved changes included.')}</p>\n )\n )}\n <div className=\"eb:flex eb:items-center eb:justify-end eb:gap-2\">\n {busy && (\n <p className=\"eb:mr-auto eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Writing your email… {seconds} s', { seconds })}\n </p>\n )}\n {busy && (\n <Button variant=\"outline\" size=\"sm\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n )}\n <Button size=\"sm\" onClick={generate} disabled={!canGenerate || busy} data-testid=\"eb-generate-submit\">\n {busy ? <Loader2 className=\"eb:animate-spin\" /> : <Sparkles />}\n {t('Generate')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n","import { cn } from '../ui/primitives/cn.js';\n\n/**\n * A rounded badge button: the purposes of \"Generate\", the rewrite actions.\n * @param {boolean} [selected]\n */\nexport const chipClassName = (selected = false) =>\n cn(\n 'eb:inline-flex eb:h-7 eb:items-center eb:gap-1 eb:rounded-full eb:border eb:px-3 eb:text-[12px] eb:font-medium eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50 eb:disabled:opacity-50',\n selected\n ? 'eb:border-primary eb:bg-primary eb:text-primary-foreground'\n : 'eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5'\n );\n","import { useEffect, useRef, useState } from 'react';\nimport { ChevronDown, Languages, Loader2, Sparkles, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input } from '../ui/primitives/fields.jsx';\nimport { SectionLabel } from '../ui/primitives/misc.jsx';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/primitives/overlays.jsx';\nimport { cleanRichText } from '../engine/richText.js';\nimport { chipClassName } from './chip.js';\nimport { MAX_REWRITE_INSTRUCTION, REWRITE_ACTIONS, REWRITE_LANGUAGES } from './rewrite.js';\n\n/**\n * REWRITE WITH AI — in the properties of a selected text or button.\n *\n * One click: shorter, longer, fixed, friendlier, more formal, translated —\n * or a change in the user's words. The result replaces the text at once, in\n * one step of the history: Ctrl+Z brings the previous text back, so there is\n * nothing to accept or compare.\n *\n * Mounted per element (keyed by its id): selecting another element cancels a\n * rewrite in progress. If the text is edited while the AI works, the user's\n * words win and the answer is dropped.\n */\n\n/** A language under its own name: \"Deutsch\", \"Українська\". @param {string} code */\nfunction nativeName(code) {\n try {\n const name = new Intl.DisplayNames([code], { type: 'language' }).of(code) || code;\n return name.charAt(0).toLocaleUpperCase(code) + name.slice(1);\n } catch {\n return code;\n }\n}\n\nexport default function RewriteText({ editor, selection, rewrite }) {\n const { t, notify } = useUi();\n const [running, setRunning] = useState(/** @type {string|null} */ (null));\n const [error, setError] = useState('');\n const [instruction, setInstruction] = useState('');\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const label = selection.tagName === 'mj-button';\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n /** The text as stored: a button's label, a text block's HTML. */\n const currentText = () => {\n const element = editor.getElement(selection.id);\n return (label ? element?.content : element?.html) ?? '';\n };\n\n const run = async (action, extra = {}) => {\n if (running) return;\n const text = currentText();\n setError('');\n setRunning(action);\n const controller = new AbortController();\n abortRef.current = controller;\n try {\n const result = await rewrite({ action, text, format: label ? 'plain' : 'html', ...extra }, { signal: controller.signal });\n if (controller.signal.aborted) return;\n if (currentText() !== text) {\n setError(t('The text changed in the meantime, so it was left as it is.'));\n return;\n }\n editor.setContent(label ? result.text : cleanRichText(result.text, document), selection.id);\n if (action === 'custom') setInstruction('');\n notify('success', t('Text rewritten. Ctrl+Z brings back the previous one.'));\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('The text could not be rewritten.'));\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n setRunning(null);\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n setRunning(null);\n };\n\n const busy = Boolean(running);\n const spinner = <Loader2 className=\"eb:size-3 eb:animate-spin\" aria-hidden=\"true\" />;\n\n return (\n <section className=\"eb:space-y-2.5\" aria-label={t('Rewrite with AI')} data-testid=\"eb-rewrite\">\n <SectionLabel className=\"eb:flex eb:items-center eb:gap-1.5\">\n <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />\n {t('Rewrite with AI')}\n </SectionLabel>\n <div className=\"eb:flex eb:flex-wrap eb:gap-1.5\">\n {REWRITE_ACTIONS.map((action) => (\n <button key={action.id} type=\"button\" className={chipClassName()} disabled={busy} onClick={() => run(action.id)}>\n {running === action.id && spinner}\n {t(action.label)}\n </button>\n ))}\n <DropdownMenu>\n <DropdownMenuTrigger asChild disabled={busy}>\n <button type=\"button\" className={chipClassName()}>\n {running === 'translate' ? spinner : <Languages className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('Translate')}\n <ChevronDown className=\"eb:size-3\" aria-hidden=\"true\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" aria-label={t('Translate into')}>\n {REWRITE_LANGUAGES.map((code) => (\n <DropdownMenuItem key={code} lang={code} onSelect={() => run('translate', { targetLanguage: code })}>\n {nativeName(code)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <form\n className=\"eb:flex eb:gap-1.5\"\n onSubmit={(event) => {\n event.preventDefault();\n if (instruction.trim()) run('custom', { instruction: instruction.trim() });\n }}\n >\n <Input\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n maxLength={MAX_REWRITE_INSTRUCTION}\n placeholder={t('Or say what to change…')}\n aria-label={t('What to change')}\n disabled={busy}\n />\n <Button type=\"submit\" variant=\"outline\" size=\"icon-sm\" aria-label={t('Rewrite')} disabled={busy || !instruction.trim()}>\n {running === 'custom' ? <Loader2 className=\"eb:animate-spin\" /> : <Sparkles />}\n </Button>\n </form>\n {running && (\n <p className=\"eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Rewriting…')}\n <Button type=\"button\" variant=\"link\" size=\"xs\" className=\"eb:h-auto eb:px-0\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n </p>\n )}\n {error && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n )}\n </section>\n );\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { Check, Loader2, Sparkles, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { cn } from '../ui/primitives/cn.js';\nimport { chipClassName } from './chip.js';\nimport { emailWords } from './subjects.js';\n\n/** Ideas already shown that \"More ideas\" asks the model not to repeat (the server keeps 12). */\nconst MAX_AVOID = 12;\n\n/**\n * SUGGEST SUBJECTS — under the subject of the email.\n *\n * One click: three other subject lines, drawn from the words of the email.\n * They are a list to pick from, the user's own subject first: a click puts\n * one in the field, another click another one, the first row brings the\n * user's subject back. \"More ideas\" asks for three new ones (none of those\n * already shown; the one in the field stays in the list); \"Done\" closes the\n * list and keeps the subject in the field.\n *\n * \"Yours\" is what the user wrote: typed in the field while the list is open\n * (or while ideas are on their way), it becomes the new \"yours\". When the\n * email itself is replaced (Generate, a template), the list closes.\n *\n * Keyboard: the list is a radio group — one Tab stop, the arrows move and\n * pick. Focus goes to the list when it opens, back to the button after Done or\n * Cancel.\n */\nexport default function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {\n const { t, locale } = useUi();\n const [running, setRunning] = useState(false);\n const [error, setError] = useState('');\n const [ideas, setIdeas] = useState(/** @type {string[]|null} */ (null));\n const [original, setOriginal] = useState('');\n const shownRef = useRef(/** @type {string[]} */ ([]));\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const subjectRef = useRef(meta.subject);\n subjectRef.current = meta.subject;\n const rootRef = useRef(/** @type {HTMLDivElement|null} */ (null));\n /** Where focus goes after the next render: the list, or the button that opened it. */\n const focusRef = useRef(/** @type {'list'|'button'|null} */ (null));\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n // Another email loaded in the editor: these ideas were for the previous one.\n useEffect(\n () =>\n editor.on('load', () => {\n abortRef.current?.abort();\n abortRef.current = null;\n shownRef.current = [];\n setRunning(false);\n setIdeas(null);\n setError('');\n setOriginal('');\n }),\n [editor]\n );\n\n // Typed in the field while the list is open: that is the user's subject now.\n const typed = meta.subject.trim();\n useEffect(() => {\n if (ideas && typed && !ideas.includes(meta.subject) && typed !== original) setOriginal(typed);\n }, [ideas, meta.subject, typed, original]);\n\n useLayoutEffect(() => {\n const target = focusRef.current;\n if (!target || running) return;\n focusRef.current = null;\n const root = rootRef.current;\n const element =\n target === 'list'\n ? root?.querySelector('[role=\"radio\"][tabindex=\"0\"]')\n : root?.querySelector('[data-subject-ideas-button]');\n /** @type {HTMLElement|null|undefined} */ (element)?.focus();\n });\n\n const ready = editor.status === 'ready';\n\n const run = async () => {\n if (running || editor.status !== 'ready') return;\n const first = !ideas;\n // The user's subject: as it was before the first idea was picked, or typed since.\n const yours = first ? subjectRef.current.trim() : original;\n const controller = new AbortController();\n abortRef.current = controller;\n setError('');\n setRunning(true);\n try {\n const result = await suggest(\n {\n subject: yours,\n preheader: meta.preheader,\n text: emailWords(editor.exportMjml()),\n language: locale,\n variables: variables?.map(({ key, label, kind }) => ({ key, label, kind })),\n avoid: shownRef.current.slice(-MAX_AVOID),\n },\n { signal: controller.signal }\n );\n if (controller.signal.aborted) return;\n const current = subjectRef.current;\n // The idea in the field stays in the list; the others give way to the new ones.\n const kept = ideas?.includes(current) ? [current] : [];\n const next = [...kept, ...result.subjects.filter((subject) => subject !== current)];\n shownRef.current = [...shownRef.current, ...result.subjects];\n // Typed during the wait: that is \"yours\" now.\n setOriginal(current.trim() && !ideas?.includes(current) ? current.trim() : yours);\n setIdeas(next);\n if (first) focusRef.current = 'list';\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('No subject could be suggested.'));\n } finally {\n // A request cancelled earlier must not end the one that replaced it.\n if (abortRef.current === controller) {\n abortRef.current = null;\n setRunning(false);\n }\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n abortRef.current = null;\n setRunning(false);\n focusRef.current = 'button';\n };\n\n const done = () => {\n setIdeas(null);\n setError('');\n shownRef.current = [];\n focusRef.current = 'button';\n };\n\n const options = ideas ? [...(original ? [{ text: original, yours: true }] : []), ...ideas.map((text) => ({ text, yours: false }))] : [];\n const checked = options.findIndex((option) => option.text === meta.subject);\n const tabStop = checked === -1 ? 0 : checked;\n\n /** Arrows move through the ideas and pick, as in any radio group. */\n const onKeyDown = (/** @type {React.KeyboardEvent} */ event) => {\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n const to = step ? (tabStop + step + options.length) % options.length : event.key === 'Home' ? 0 : event.key === 'End' ? options.length - 1 : -1;\n if (to === -1 || !options.length) return;\n event.preventDefault();\n onSubject(options[to].text);\n /** @type {HTMLElement|undefined} */ (rootRef.current?.querySelectorAll('[role=\"radio\"]')[to])?.focus();\n };\n\n const spinner = <Loader2 className=\"eb:size-3 eb:animate-spin\" aria-hidden=\"true\" />;\n\n return (\n <div className=\"eb:space-y-2\" data-testid=\"eb-subject-ideas\" ref={rootRef}>\n {!ideas && (\n <button type=\"button\" className={chipClassName()} disabled={running || !ready} onClick={run} data-subject-ideas-button>\n {running ? spinner : <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('Suggest subjects')}\n </button>\n )}\n {ideas && (\n <div className=\"eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5\">\n <div role=\"radiogroup\" aria-label={t('Subject ideas')} className=\"eb:space-y-0.5\" onKeyDown={onKeyDown}>\n {options.map((option, index) => {\n const chosen = index === checked;\n return (\n <button\n key={`${option.yours ? 'yours' : 'idea'}:${option.text}`}\n type=\"button\"\n role=\"radio\"\n aria-checked={chosen}\n tabIndex={index === tabStop ? 0 : -1}\n className={cn(\n 'eb:flex eb:w-full eb:items-start eb:gap-2 eb:rounded eb:px-2 eb:py-1.5 eb:text-left eb:text-[12.5px] eb:leading-snug eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n chosen ? 'eb:bg-primary/10 eb:text-foreground' : 'eb:text-foreground-lighter eb:hover:bg-muted'\n )}\n onClick={() => onSubject(option.text)}\n >\n <Check className={cn('eb:mt-0.5 eb:size-3.5 eb:shrink-0', chosen ? 'eb:text-primary' : 'eb:invisible')} aria-hidden=\"true\" />\n <span className=\"eb:min-w-0 eb:flex-1 eb:break-words\">{option.text}</span>\n {option.yours && <span className=\"eb:shrink-0 eb:text-[11px] eb:text-foreground-muted\">{t('yours')}</span>}\n </button>\n );\n })}\n </div>\n <div className=\"eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5\">\n <button type=\"button\" className={chipClassName()} disabled={running || !ready} onClick={run} data-subject-ideas-button>\n {running ? spinner : <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('More ideas')}\n </button>\n <Button type=\"button\" variant=\"ghost\" size=\"xs\" className=\"eb:ml-auto\" disabled={running} onClick={done}>\n {t('Done')}\n </Button>\n </div>\n </div>\n )}\n {running && (\n <p className=\"eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Reading the email…')}\n <Button type=\"button\" variant=\"link\" size=\"xs\" className=\"eb:h-auto eb:px-0\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n </p>\n )}\n {error && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n )}\n </div>\n );\n}\n","// @ts-check\n/**\n * THE AI MODULE — `createAi()`, given to the builder as `ai`.\n *\n * import { createAi } from 'best-email-builder/ai';\n * <EmailBuilder storage={storage} ai={createAi()} />\n *\n * Without it, the builder has no AI button, and none of the AI's code or\n * texts is in the application's bundle: the core never imports this folder.\n * With it, the builder gets what the module can do:\n *\n * generate the Generate button (an email written from a purpose)\n * rewrite \"Rewrite with AI\" in the properties of a text or a button\n * suggestSubjects \"Suggest subjects\" under the subject\n *\n * Where the model runs:\n *\n * createAi() the routes of createEmailBuilderHandler({ generator })\n * createAi({ baseUrl, headers }) the same routes elsewhere, or with the user's credentials\n * createAi({ provider, askKey: true }) the AI runs in the page with each user's own key\n * (OpenRouter, Gemini, Groq… — see providers.js);\n * the builder asks for it once (the Connect dialog)\n * createAi({ provider, apiKey }) the same with one key for all (prototypes: the key\n * is then in the page)\n * createAi({ model }) the generator runs in the page with this model\n * createAi({ generate, rewrite, … }) functions of your own: only those given are on\n *\n * Each of `generate`, `rewrite` and `suggestSubjects` can also be `false`,\n * to leave that part out — its button with it.\n */\n\nimport { createHttpCaller } from '../storage/client.js';\nimport { createEmailGenerator } from './generator.js';\nimport { AI_MESSAGES } from './i18n/index.js';\nimport { createKeyStore } from './keys.js';\nimport { providerEmailModel, resolveProvider } from './providers.js';\nimport ConnectAi from './ConnectAi.jsx';\nimport GenerateButton from './GenerateButton.jsx';\nimport RewriteText from './RewriteText.jsx';\nimport SubjectIdeas from './SubjectIdeas.jsx';\n\n/**\n * @typedef {import('./generator.js').GenerateInput} GenerateInput\n * @typedef {import('./generator.js').GeneratedEmail} GeneratedEmail\n * @typedef {import('./rewrite.js').RewriteInput} RewriteInput\n * @typedef {import('./subjects.js').SubjectsInput} SubjectsInput\n * @typedef {{signal?: AbortSignal}} CallOptions\n * @typedef {(input: GenerateInput, options?: CallOptions) => Promise<GeneratedEmail | Omit<GeneratedEmail, 'warnings'|'attempts'>>} GenerateFn\n * @typedef {(input: RewriteInput, options?: CallOptions) => Promise<{text: string}>} RewriteFn\n * @typedef {(input: SubjectsInput, options?: CallOptions) => Promise<{subjects: string[]}>} SuggestSubjectsFn\n *\n * @typedef {{\n * baseUrl?: string,\n * headers?: Record<string, string> | (() => Record<string, string>),\n * fetch?: typeof fetch,\n * model?: import('./generator.js').EmailModel,\n * provider?: string | Partial<import('./providers.js').AiProvider>,\n * apiKey?: string,\n * askKey?: boolean,\n * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,\n * maxAttempts?: number,\n * generate?: GenerateFn | boolean,\n * rewrite?: RewriteFn | boolean,\n * suggestSubjects?: SuggestSubjectsFn | boolean,\n * }} AiOptions\n * `provider`: an id of `AI_PROVIDERS` ('openrouter', 'gemini', 'groq'…), or\n * `{ id, model, baseUrl, headers }` for another model or an endpoint of your\n * own. `apiKey`: one key for every user. `askKey`: each user connects their\n * own (off by default). `compile`: with `provider` or `model`, MJML's own\n * warnings fed back to the model (see createEmailGenerator).\n *\n * @typedef {Readonly<{\n * generate: GenerateFn | null,\n * rewrite: RewriteFn | null,\n * suggestSubjects: SuggestSubjectsFn | null,\n * key: import('./keys.js').AiKey | null,\n * messages: Record<string, Record<string, string>>,\n * components: Readonly<{Generate: typeof GenerateButton, Rewrite: typeof RewriteText, Subjects: typeof SubjectIdeas, Connect: typeof ConnectAi | null}>,\n * }>} EmailBuilderAi\n * What the builder reads. `key`: the user's key, with `askKey` (keys.js).\n * `messages`: the module's own translations, by locale, merged under the\n * host's `messages`. `components.Connect`: the dialog that asks for the key.\n */\n\nconst PARTS = /** @type {const} */ (['generate', 'rewrite', 'suggestSubjects']);\nconst ROUTES = { generate: '/generate', rewrite: '/rewrite', suggestSubjects: '/subjects' };\n\n/**\n * @param {AiOptions} [options]\n * @returns {EmailBuilderAi}\n */\nexport function createAi(options = {}) {\n if (!options || typeof options !== 'object') throw new TypeError('createAi: options must be an object');\n const { model, maxAttempts, compile, baseUrl, headers, fetch: fetchImpl, provider: providerOption, apiKey, askKey = false } = options;\n const usesProvider = providerOption !== undefined;\n const http = baseUrl !== undefined || headers !== undefined || (fetchImpl !== undefined && !usesProvider);\n if (model !== undefined && typeof model !== 'function') throw new TypeError('createAi: `model` must be a function');\n if (model && http) throw new TypeError('createAi: pass `model` (the AI runs in the page) or `baseUrl` (on the server), not both');\n if (usesProvider && (model || http)) {\n throw new TypeError('createAi: `provider` runs the AI in the page — not with `model`, `baseUrl` or `headers` (give the provider `{ id, model, baseUrl, headers }`)');\n }\n if (!usesProvider && (apiKey !== undefined || options.askKey !== undefined)) {\n throw new TypeError('createAi: `apiKey` and `askKey` go with a `provider` (e.g. provider: \"openrouter\")');\n }\n if (apiKey !== undefined && (typeof apiKey !== 'string' || !apiKey)) throw new TypeError('createAi: `apiKey` must be a non-empty string');\n if (compile !== undefined && (typeof compile !== 'function' || (!model && !usesProvider))) {\n throw new TypeError('createAi: `compile` must be a function, and goes with `provider` or `model` (the AI in the page)');\n }\n if (typeof askKey !== 'boolean') throw new TypeError('createAi: `askKey` must be true or false');\n if (apiKey && askKey) throw new TypeError('createAi: `apiKey` (one key for all) or `askKey` (each user\\'s own), not both');\n const provider = usesProvider ? resolveProvider(providerOption) : null;\n if (provider && !apiKey && !askKey && !provider.keyless) {\n throw new TypeError(`createAi: ${provider.name} needs a key — \\`apiKey\\`, or \\`askKey: true\\` to let each user connect their own`);\n }\n for (const part of PARTS) {\n const value = options[part];\n if (value !== undefined && typeof value !== 'boolean' && typeof value !== 'function') {\n throw new TypeError(`createAi: \\`${part}\\` must be a function, true or false`);\n }\n }\n\n // Functions of the host's own and nothing else: only those are on.\n const ownOnly = !model && !http && PARTS.some((part) => typeof options[part] === 'function');\n /** @type {Partial<Record<typeof PARTS[number], Function>>} */\n let transport = {};\n const key = provider && askKey ? createKeyStore(provider) : null;\n if (provider) {\n const generator = createEmailGenerator({\n model: providerEmailModel({\n provider,\n fetch: fetchImpl,\n // The user's key, asked when there is none ('' when they cancel: KEY_MISSING).\n apiKey: key ? async () => key.get() || (await key.request()) : apiKey,\n }),\n maxAttempts,\n compile,\n });\n for (const part of PARTS) transport[part] = key ? askedAgain(generator[part], key) : generator[part];\n } else if (model) {\n transport = createEmailGenerator({ model, maxAttempts, compile });\n } else if (!ownOnly) {\n const { call } = createHttpCaller({ baseUrl, headers, fetch: fetchImpl });\n for (const part of PARTS) {\n transport[part] = (/** @type {object} */ input, /** @type {CallOptions} */ { signal } = {}) =>\n call(ROUTES[part], { method: 'POST', body: JSON.stringify(input), signal });\n }\n }\n\n /** @param {typeof PARTS[number]} part */\n const resolve = (part) => {\n const value = options[part];\n if (typeof value === 'function') return value;\n if (value === false) return null;\n return transport[part] || null;\n };\n\n return Object.freeze({\n generate: /** @type {GenerateFn|null} */ (resolve('generate')),\n rewrite: /** @type {RewriteFn|null} */ (resolve('rewrite')),\n suggestSubjects: /** @type {SuggestSubjectsFn|null} */ (resolve('suggestSubjects')),\n key,\n messages: AI_MESSAGES,\n components: Object.freeze({ Generate: GenerateButton, Rewrite: RewriteText, Subjects: SubjectIdeas, Connect: key ? ConnectAi : null }),\n });\n}\n\n/**\n * A key the provider refuses is forgotten and asked again, once, in the same\n * action: the user pastes a good one and the email still comes.\n * @param {Function} run\n * @param {import('./keys.js').AiKey} key\n */\nfunction askedAgain(run, key) {\n return async (/** @type {any} */ input, /** @type {any} */ options) => {\n try {\n return await run(input, options);\n } catch (error) {\n if (/** @type {any} */ (error)?.code !== 'KEY_REFUSED' || options?.signal?.aborted) throw error;\n key.refuse();\n try {\n return await run(input, options);\n } catch (again) {\n if (/** @type {any} */ (again)?.code === 'KEY_REFUSED') key.refuse();\n throw again;\n }\n }\n };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","TriangleAlert","jsx","jsxs","useState","useRef","useEffect","TriangleAlert","useEffect","useRef","useState","Loader2","Sparkles","TriangleAlert","jsx","jsxs","useState","useRef","useEffect","Loader2","Sparkles","TriangleAlert","useEffect","useRef","useState","Check","Loader2","Sparkles","TriangleAlert","jsx","jsxs","useState","useRef","useEffect","Loader2","Sparkles","Check","TriangleAlert"]}
|
|
1
|
+
{"version":3,"sources":["../src/ai/keys.js","../src/ai/ConnectAi.jsx","../src/ai/ModelPicker.jsx","../src/ai/GenerateButton.jsx","../src/ai/chip.js","../src/ai/RewriteText.jsx","../src/ai/SubjectIdeas.jsx","../src/ai/createAi.js"],"sourcesContent":["// @ts-check\n/**\n * THE USER'S KEY — when the AI runs in the page with `askKey: true`, each\n * user connects their own provider key, once.\n *\n * The first AI action asks for it (the Connect dialog, `ConnectAi.jsx`); it is\n * kept in this browser (localStorage, one entry per provider) and sent only to\n * the provider. A key the provider refuses is forgotten, and asked again.\n * Given several providers, the user chooses one in the same dialog; the\n * choice is kept too, and so is the model chosen for each provider.\n *\n * ai.key.get(id?) the key in use, or the one kept for that provider ('' when none)\n * ai.key.set(key) connect a key without the dialog\n * ai.key.forget() disconnect: the next AI action asks again\n * ai.key.request() ask the user now → the key, or '' when they cancel\n * ai.key.choose(id) use another of the providers (its kept key, if any)\n * ai.key.model(id?) the model chosen for that provider (its own by default)\n * ai.key.chooseModel(model, id?) write with another of its models\n * ai.key.listModels(id?, { apiKey, signal }) the models it offers (providers.js)\n * ai.key.subscribe(fn) called on every change; returns the unsubscribe\n * ai.key.provider the provider in use: name, model, where to get a key, what is free\n * ai.key.providers all those the user can choose from\n */\n\nimport { listProviderModels } from './providers.js';\n\n/** @typedef {import('./providers.js').AiProvider} AiProvider */\n/** @typedef {import('./providers.js').ModelChoice} ModelChoice */\n\nconst STORAGE_PREFIX = 'best-email-builder:ai-key:';\nconst CHOICE_SLOT = 'best-email-builder:ai-provider';\nconst MODEL_PREFIX = 'best-email-builder:ai-model:';\n\n/** localStorage when there is one that works (not in private modes that throw, not on the server). */\nfunction browserStorage() {\n try {\n const storage = globalThis.localStorage;\n return storage && typeof storage.getItem === 'function' ? storage : null;\n } catch {\n return null;\n }\n}\n\n/**\n * @typedef {Readonly<{id: string, name: string, model: string|null, keyUrl: string|null, keyHint: string, free: string|null, publicModels: boolean}>} ProviderChoice\n * @typedef {{\n * provider: ProviderChoice,\n * providers: ReadonlyArray<ProviderChoice>,\n * get: (id?: string) => string,\n * set: (key: string) => void,\n * forget: () => void,\n * request: () => Promise<string>,\n * choose: (id: string) => void,\n * model: (id?: string) => string,\n * chooseModel: (model: string, id?: string) => void,\n * listModels: (id?: string, options?: {apiKey?: string, signal?: AbortSignal}) => Promise<ModelChoice[]>,\n * subscribe: (listener: () => void) => () => void,\n * state: () => Readonly<{asking: boolean, refused: boolean, connected: boolean}>,\n * answer: (key: string|null) => void,\n * refuse: () => void,\n * }} AiKey\n * `state`, `answer` and `refuse` serve the Connect dialog and the module:\n * whether the user is being asked, whether the last key was refused.\n * `listModels`: with the key given, else the one kept (none is needed for\n * a provider whose list is public, OpenRouter's).\n */\n\n/** @param {AiProvider} provider @returns {ProviderChoice} */\nconst choiceOf = (provider) =>\n Object.freeze({\n id: provider.id,\n name: provider.name,\n model: provider.model ?? null,\n keyUrl: provider.keyUrl,\n keyHint: provider.keyHint,\n free: provider.free,\n publicModels: Boolean(provider.publicModels),\n });\n\n/**\n * @param {AiProvider | AiProvider[]} providerOrList one provider, or those the user chooses from\n * @param {{fetch?: typeof fetch}} [options] the fetch that lists the models\n * @returns {AiKey}\n */\nexport function createKeyStore(providerOrList, { fetch: fetchImpl } = {}) {\n const originals = Array.isArray(providerOrList) ? providerOrList : [providerOrList];\n const providers = Object.freeze(originals.map(choiceOf));\n if (!providers.length) throw new TypeError('createKeyStore: at least one provider');\n const known = (/** @type {unknown} */ id) => providers.some((provider) => provider.id === id);\n /** Kept here too: a browser that does not store still works for the page's life. */\n const memory = new Map();\n let refused = false;\n /** @type {{promise: Promise<string>, resolve: (key: string) => void}|null} */\n let pending = null;\n /** @type {Set<() => void>} */\n const listeners = new Set();\n /** @type {ReturnType<AiKey['state']>} */\n let snapshot;\n\n /** @param {string} slot */\n const read = (slot) => {\n try {\n return browserStorage()?.getItem(slot) || '';\n } catch {\n return '';\n }\n };\n /** @param {string} slot @param {string} value */\n const write = (slot, value) => {\n try {\n const storage = browserStorage();\n if (value) storage?.setItem(slot, value);\n else storage?.removeItem(slot);\n } catch {\n // Full, or blocked: the key stays in memory.\n }\n };\n /** @param {string} id */\n const keyOf = (id) => (memory.has(id) ? memory.get(id) : read(STORAGE_PREFIX + id));\n /** @param {string} id @param {string} value */\n const keep = (id, value) => {\n memory.set(id, value);\n write(STORAGE_PREFIX + id, value);\n };\n // The provider chosen last time; else the first one with a key; else the first.\n const kept = read(CHOICE_SLOT);\n let current = known(kept) ? kept : (providers.find((provider) => keyOf(provider.id)) || providers[0]).id;\n /** @param {string} [id] */\n const get = (id) => keyOf(id === undefined ? current : String(id));\n /** @param {string} [id] */\n const providerId = (id) => {\n if (id === undefined) return current;\n if (!known(id)) throw new TypeError(`ai.key: no provider \"${id}\" — one of ${providers.map((provider) => provider.id).join(', ')}`);\n return id;\n };\n /** The models listed, by provider and key: a dialog opened again shows them at once. */\n const lists = new Map();\n const changed = () => {\n snapshot = Object.freeze({ asking: Boolean(pending), refused, connected: Boolean(get()) });\n for (const listener of [...listeners]) listener();\n };\n snapshot = Object.freeze({ asking: false, refused: false, connected: Boolean(get()) });\n\n /** @param {string} key */\n const answer = (key) => {\n const value = String(key || '').trim();\n if (value) {\n keep(current, value);\n refused = false;\n }\n const waiting = pending;\n pending = null;\n changed();\n waiting?.resolve(value);\n };\n\n return Object.freeze({\n get provider() {\n return /** @type {ProviderChoice} */ (providers.find((provider) => provider.id === current));\n },\n providers,\n get,\n set: (/** @type {string} */ key) => {\n if (typeof key !== 'string' || !key.trim()) throw new TypeError('ai.key.set: a non-empty key');\n answer(key);\n },\n forget: () => {\n keep(current, '');\n changed();\n },\n choose: (/** @type {string} */ id) => {\n if (!known(id)) throw new TypeError(`ai.key.choose: one of ${providers.map((provider) => provider.id).join(', ')}`);\n if (id === current) return;\n current = id;\n refused = false;\n write(CHOICE_SLOT, id);\n changed();\n },\n model: (/** @type {string|undefined} */ id) => {\n const choice = /** @type {ProviderChoice} */ (providers.find((provider) => provider.id === providerId(id)));\n const kept = memory.get(MODEL_PREFIX + choice.id) ?? read(MODEL_PREFIX + choice.id);\n return kept || choice.model || '';\n },\n chooseModel: (/** @type {string} */ model, /** @type {string|undefined} */ id) => {\n if (typeof model !== 'string' || !model.trim()) throw new TypeError('ai.key.chooseModel: a model id');\n const choice = /** @type {ProviderChoice} */ (providers.find((provider) => provider.id === providerId(id)));\n // The provider's own model is kept as no choice: a new default reaches the user.\n const value = model.trim() === choice.model ? '' : model.trim();\n memory.set(MODEL_PREFIX + choice.id, value);\n write(MODEL_PREFIX + choice.id, value);\n changed();\n },\n listModels: async (/** @type {string|undefined} */ id, { apiKey, signal } = {}) => {\n const at = providers.findIndex((provider) => provider.id === providerId(id));\n const key = String(apiKey ?? keyOf(providers[at].id)).trim();\n const slot = `${providers[at].id}\\n${key}`;\n if (!lists.has(slot)) lists.set(slot, await listProviderModels({ provider: originals[at], apiKey: key, fetch: fetchImpl, signal }));\n return lists.get(slot);\n },\n request: () => {\n if (!pending) {\n /** @type {(key: string) => void} */\n let resolve = () => {};\n const promise = new Promise((done) => (resolve = done));\n pending = { promise, resolve };\n changed();\n }\n return pending.promise;\n },\n subscribe: (/** @type {() => void} */ listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n state: () => snapshot,\n answer: (/** @type {string|null} */ key) => answer(key || ''),\n refuse: () => {\n keep(current, '');\n refused = true;\n changed();\n },\n });\n}\n","import { useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport { ArrowUpRight, Eye, EyeOff, Gift, KeyRound, ShieldCheck, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { cn } from '../ui/primitives/cn.js';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input, Label } from '../ui/primitives/fields.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/primitives/overlays.jsx';\nimport ModelPicker from './ModelPicker.jsx';\n\n/** A key this long is complete enough to ask the provider for its models. */\nconst LISTABLE_KEY = 20;\n\n/**\n * CONNECT — the dialog that asks the user for their provider key, when the AI\n * runs in the page with `createAi({ provider, askKey: true })`.\n *\n * It opens by itself when an AI action needs a key and there is none, or the\n * provider refused the last one; the action goes on once the key is given.\n * Cancel ends the action (\"Connect an AI provider first\"). The key is kept in\n * this browser and sent only to the provider (see keys.js).\n *\n * Given several providers (`createAi({ provider: [...] })`), the user chooses\n * one at the top; a provider whose key this browser already keeps needs no\n * new one. Under the key, the model: the provider's own, or another of its\n * list (ModelPicker.jsx).\n */\nexport default function ConnectAi({ aiKey }) {\n const { t } = useUi();\n const state = useSyncExternalStore(aiKey.subscribe, aiKey.state, aiKey.state);\n const [value, setValue] = useState('');\n const [shown, setShown] = useState(false);\n const [chosenId, setChosenId] = useState(aiKey.provider.id);\n /** The models chosen in the dialog, by provider, until Connect. */\n const [picks, setPicks] = useState(/** @type {Record<string, string>} */ ({}));\n const [modelsOpen, setModelsOpen] = useState(false);\n const inputRef = useRef(/** @type {HTMLInputElement|null} */ (null));\n const choicesRef = useRef(/** @type {HTMLDivElement|null} */ (null));\n const inputId = useId();\n const hintId = useId();\n const choices = aiKey.providers;\n const chosen = choices.find((provider) => provider.id === chosenId) || aiKey.provider;\n const { name, keyUrl, keyHint, free } = chosen;\n\n // Each time it opens: the provider in use, an empty field, the key hidden.\n useEffect(() => {\n if (!state.asking) return;\n setChosenId(aiKey.provider.id);\n setValue('');\n setShown(false);\n setPicks({});\n setModelsOpen(false);\n }, [state.asking, aiKey]);\n\n const key = value.trim();\n // The key this browser keeps for the provider chosen (none for one refused).\n const saved = aiKey.get(chosen.id);\n // A key from elsewhere (another provider's) is the usual mistake: said, not blocked.\n const unusual = Boolean(key && keyHint && !key.startsWith(keyHint));\n const refused = state.refused && chosen.id === aiKey.provider.id;\n /** @param {string} id */\n const modelFor = (id) => picks[id] ?? aiKey.model(id);\n\n const submit = (/** @type {React.FormEvent} */ event) => {\n event.preventDefault();\n const answer = key || saved;\n if (!answer) return;\n if (chosen.id !== aiKey.provider.id) aiKey.choose(chosen.id);\n if (picks[chosen.id] && picks[chosen.id] !== aiKey.model(chosen.id)) aiKey.chooseModel(picks[chosen.id], chosen.id);\n aiKey.answer(answer);\n };\n\n /** @param {string} id */\n const choose = (id) => {\n setChosenId(id);\n setValue('');\n setModelsOpen(false);\n };\n\n /** Arrows move through the providers and choose, as in any radio group. */\n const onChoicesKeyDown = (/** @type {React.KeyboardEvent} */ event) => {\n const at = choices.findIndex((provider) => provider.id === chosen.id);\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n const to = step ? (at + step + choices.length) % choices.length : event.key === 'Home' ? 0 : event.key === 'End' ? choices.length - 1 : -1;\n if (to === -1) return;\n event.preventDefault();\n choose(choices[to].id);\n /** @type {HTMLElement|undefined} */ (choicesRef.current?.querySelectorAll('[role=\"radio\"]')[to])?.focus();\n };\n\n return (\n <Dialog open={state.asking} onOpenChange={(open) => !open && aiKey.answer(null)}>\n <DialogContent\n className={cn('eb:max-h-[calc(100dvh-2rem)] eb:gap-0 eb:overflow-y-auto eb:p-0', choices.length > 1 ? 'eb:sm:max-w-xl' : 'eb:sm:max-w-md')}\n data-testid=\"eb-ai-connect\"\n onOpenAutoFocus={(event) => {\n event.preventDefault();\n inputRef.current?.focus();\n }}\n // Esc closes the list of models first, then the dialog. From inside\n // the list, the list closes itself (and gives the focus back to its field).\n onEscapeKeyDown={(event) => {\n if (!modelsOpen) return;\n event.preventDefault();\n if (!(event.target instanceof Element && event.target.closest('[data-eb-models]'))) setModelsOpen(false);\n }}\n >\n {/* min-w-0: the long names of the list of models do not widen the dialog. */}\n <form onSubmit={submit} className=\"eb:min-w-0\">\n <div className=\"eb:flex eb:flex-col eb:gap-5 eb:px-6 eb:pt-6 eb:pb-5\">\n <DialogHeader className=\"eb:flex-row eb:items-start eb:gap-3.5 eb:pr-6\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-full eb:bg-primary/10 eb:text-primary\">\n <KeyRound className=\"eb:size-5\" aria-hidden=\"true\" />\n </span>\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\">\n <DialogTitle>{t('Connect {provider}', { provider: name })}</DialogTitle>\n <DialogDescription className=\"eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter\">\n {t('The AI writes with your own {provider} key. Connect it once: this browser remembers it.', { provider: name })}\n </DialogDescription>\n </div>\n </DialogHeader>\n\n {choices.length > 1 && (\n <div\n ref={choicesRef}\n role=\"radiogroup\"\n aria-label={t('AI provider')}\n className=\"eb:grid eb:grid-cols-2 eb:gap-1.5 eb:sm:grid-cols-3\"\n onKeyDown={onChoicesKeyDown}\n data-testid=\"eb-ai-connect-providers\"\n >\n {choices.map((provider) => {\n const checked = provider.id === chosen.id;\n return (\n <button\n key={provider.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={checked}\n tabIndex={checked ? 0 : -1}\n onClick={() => choose(provider.id)}\n className={cn(\n 'eb:flex eb:min-w-0 eb:flex-col eb:gap-0.5 eb:rounded-md eb:border eb:px-2.5 eb:py-2 eb:text-left eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n checked ? 'eb:border-primary eb:bg-primary/10' : 'eb:border-border eb:hover:bg-muted'\n )}\n >\n <span className=\"eb:flex eb:items-center eb:gap-1.5 eb:text-[13px] eb:leading-tight eb:font-medium eb:text-foreground\">\n <span className=\"eb:truncate\">{provider.name}</span>\n {provider.free && <Gift className=\"eb:size-3 eb:shrink-0 eb:text-success\" aria-label={t(provider.free)} />}\n {aiKey.get(provider.id) && (\n <span className=\"eb:ml-auto eb:size-1.5 eb:shrink-0 eb:rounded-full eb:bg-success\" title={t('Key saved')} aria-label={t('Key saved')} />\n )}\n </span>\n {modelFor(provider.id) && (\n <span className=\"eb:truncate eb:font-mono eb:text-[10.5px] eb:text-foreground-muted\" title={modelFor(provider.id)}>\n {modelFor(provider.id)}\n </span>\n )}\n </button>\n );\n })}\n </div>\n )}\n\n {refused && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-2 eb:rounded-md eb:border eb:border-destructive/30 eb:bg-destructive/10 eb:px-3 eb:py-2 eb:text-[12.5px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {t('{provider} refused the last key. Paste another one.', { provider: name })}\n </p>\n )}\n\n {(keyUrl || free) && (\n <div className=\"eb:flex eb:flex-wrap eb:items-center eb:gap-x-3 eb:gap-y-2\">\n {keyUrl && (\n <Button asChild variant=\"outline\" size=\"sm-compact\">\n <a\n href={keyUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n // A new tab, always — even where a link's target is ignored (webviews).\n onClick={(event) => {\n event.preventDefault();\n window.open(keyUrl, '_blank', 'noopener,noreferrer');\n }}\n data-testid=\"eb-ai-connect-get-key\"\n >\n {t('Get a key')}\n <ArrowUpRight aria-hidden=\"true\" />\n </a>\n </Button>\n )}\n {free && (\n <span className=\"eb:inline-flex eb:items-center eb:gap-1.5 eb:rounded-full eb:bg-success/10 eb:px-2.5 eb:py-1 eb:text-[11.5px] eb:font-medium eb:text-success\">\n <Gift className=\"eb:size-3.5\" aria-hidden=\"true\" />\n {t(free)}\n </span>\n )}\n </div>\n )}\n\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\">\n <Label htmlFor={inputId}>{t('API key')}</Label>\n <div className=\"eb:relative\">\n <Input\n ref={inputRef}\n id={inputId}\n type={shown ? 'text' : 'password'}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n placeholder={keyHint ? `${keyHint}…` : t('Paste the key')}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n aria-describedby={hintId}\n className=\"eb:h-9 eb:pr-10 eb:font-mono eb:text-[12.5px]\"\n data-testid=\"eb-ai-connect-key\"\n />\n <button\n type=\"button\"\n className=\"eb:absolute eb:top-1/2 eb:right-1 eb:flex eb:size-7 eb:-translate-y-1/2 eb:items-center eb:justify-center eb:rounded eb:text-foreground-muted eb:transition-colors eb:outline-none eb:hover:bg-muted eb:hover:text-foreground eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50\"\n onClick={() => setShown((current) => !current)}\n aria-label={shown ? t('Hide the key') : t('Show the key')}\n aria-pressed={shown}\n >\n {shown ? <EyeOff className=\"eb:size-4\" aria-hidden=\"true\" /> : <Eye className=\"eb:size-4\" aria-hidden=\"true\" />}\n </button>\n </div>\n <p id={hintId} className=\"eb:min-h-4 eb:text-[11.5px] eb:text-foreground-muted\">\n {unusual ? (\n <span className=\"eb:text-warning\">{t('{provider} keys usually start with {prefix}.', { provider: name, prefix: keyHint })}</span>\n ) : saved && !key ? (\n t('A key for {provider} is saved in this browser: Connect uses it, or paste another one.', { provider: name })\n ) : keyHint ? (\n t('{provider} keys start with {prefix}.', { provider: name, prefix: keyHint })\n ) : null}\n </p>\n </div>\n\n <ModelPicker\n aiKey={aiKey}\n provider={chosen}\n apiKey={key.length >= LISTABLE_KEY ? key : saved}\n delay={key.length >= LISTABLE_KEY ? 350 : 0}\n value={modelFor(chosen.id)}\n onChange={(model) => setPicks((current) => ({ ...current, [chosen.id]: model }))}\n open={modelsOpen}\n onOpenChange={setModelsOpen}\n />\n </div>\n\n <div className=\"eb:flex eb:items-start eb:gap-2 eb:border-t eb:border-border eb:bg-surface-75 eb:px-6 eb:py-3 eb:text-[11.5px] eb:leading-relaxed eb:text-foreground-lighter\">\n <ShieldCheck className=\"eb:mt-0.5 eb:size-3.5 eb:shrink-0 eb:text-foreground-muted\" aria-hidden=\"true\" />\n <span>{t('The key stays in this browser and is sent only to {provider}.', { provider: name })}</span>\n </div>\n\n <DialogFooter className=\"eb:border-t eb:border-border eb:px-6 eb:py-4\">\n <Button type=\"button\" variant=\"ghost\" onClick={() => aiKey.answer(null)}>\n {t('Cancel')}\n </Button>\n <Button type=\"submit\" disabled={!key && !saved} data-testid=\"eb-ai-connect-submit\">\n {t('Connect')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n","import { useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { Check, ChevronDown, Gift, LoaderCircle, Search } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { cn } from '../ui/primitives/cn.js';\n\n/**\n * MODEL — the model of the provider chosen, in the Connect dialog: a field\n * that opens the provider's list of models in place (not over the dialog, so\n * the list scrolls like the rest of it).\n *\n * The list comes from the provider with the key (OpenRouter's needs none):\n * the default model first, then the free ones, then the newest. Type to\n * filter, ↑/↓ to move, Enter to choose, Esc to close. An id the list does not\n * have can be used as it is (\"Use “…”\"); without the list, the default model\n * still works.\n *\n * @param {{\n * aiKey: import('./keys.js').AiKey,\n * provider: import('./keys.js').ProviderChoice,\n * apiKey: string,\n * delay?: number,\n * value: string,\n * onChange: (model: string) => void,\n * open: boolean,\n * onOpenChange: (open: boolean) => void,\n * }} props\n * `apiKey`: the key the list is asked with ('' when there is none yet);\n * `delay`: how long to wait after it changes (a key being pasted).\n */\nexport default function ModelPicker({ aiKey, provider, apiKey, delay = 0, value, onChange, open, onOpenChange }) {\n const { t } = useUi();\n const [list, setList] = useState(/** @type {{status: 'loading'|'ready'|'needs-key'|'refused'|'failed', models: import('./providers.js').ModelChoice[]}} */ ({ status: 'loading', models: [] }));\n const [query, setQuery] = useState('');\n const [freeOnly, setFreeOnly] = useState(false);\n const [active, setActive] = useState(0);\n const triggerRef = useRef(/** @type {HTMLButtonElement|null} */ (null));\n const listRef = useRef(/** @type {HTMLDivElement|null} */ (null));\n const labelId = useId();\n const valueId = useId();\n const listId = useId();\n const hintId = useId();\n\n // The list of the provider, asked again when the key changes.\n useEffect(() => {\n if (!apiKey && !provider.publicModels) {\n setList({ status: 'needs-key', models: [] });\n return undefined;\n }\n const controller = new AbortController();\n setList({ status: 'loading', models: [] });\n const timer = setTimeout(() => {\n aiKey.listModels(provider.id, { apiKey, signal: controller.signal }).then(\n (models) => !controller.signal.aborted && setList({ status: 'ready', models }),\n (error) => !controller.signal.aborted && setList({ status: error?.code === 'KEY_REFUSED' ? 'refused' : 'failed', models: [] })\n );\n }, delay);\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n }, [aiKey, provider.id, provider.publicModels, apiKey, delay]);\n\n // Each time it opens: no search, the model in use under the keys.\n useEffect(() => {\n if (!open) return;\n setQuery('');\n setFreeOnly(false);\n }, [open]);\n\n // The default model first, then the one in use, then the list.\n const models = useMemo(() => {\n const listed = new Map(list.models.map((model) => [model.id, model]));\n const seen = new Set();\n /** @type {import('./providers.js').ModelChoice[]} */\n const all = [];\n const add = (/** @type {import('./providers.js').ModelChoice} */ model) => {\n if (model.id && !seen.has(model.id)) seen.add(model.id) && all.push(model);\n };\n for (const id of [provider.model, value]) if (id) add(listed.get(id) || { id, name: id, free: false });\n list.models.forEach(add);\n return all;\n }, [list.models, provider.model, value]);\n const hasFree = list.models.some((model) => model.free);\n const typed = query.trim();\n const matches = useMemo(() => {\n const words = typed.toLowerCase().split(/\\s+/).filter(Boolean);\n return models.filter((model) => (!freeOnly || model.free) && words.every((word) => `${model.id} ${model.name}`.toLowerCase().includes(word)));\n }, [models, typed, freeOnly]);\n // An id of one's own: a model the list does not have (or not yet).\n const custom = typed && !/\\s/.test(typed) && !models.some((model) => model.id === typed) ? typed : '';\n const count = matches.length + (custom ? 1 : 0);\n const current = models.find((model) => model.id === value);\n\n useEffect(() => setActive(Math.max(0, matches.findIndex((model) => model.id === value))), [matches, value]);\n useEffect(() => {\n if (open) listRef.current?.querySelector('[data-active=\"true\"]')?.scrollIntoView({ block: 'nearest' });\n }, [active, open]);\n\n const close = () => {\n triggerRef.current?.focus();\n onOpenChange(false);\n };\n /** @param {string} id */\n const pick = (id) => {\n onChange(id);\n close();\n };\n\n const onKeyDown = (/** @type {React.KeyboardEvent} */ event) => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n setActive((at) => (count ? (at + (event.key === 'ArrowDown' ? 1 : -1) + count) % count : 0));\n } else if (event.key === 'Enter') {\n // The model, not the form: Connect is the button's.\n event.preventDefault();\n const id = active < matches.length ? matches[active]?.id : custom;\n if (id) pick(id);\n }\n };\n\n const optionId = (/** @type {number} */ at) => `${listId}-${at}`;\n const badges = (/** @type {import('./providers.js').ModelChoice} */ model) => (\n <>\n {model.free && (\n <span className=\"eb:inline-flex eb:shrink-0 eb:items-center eb:gap-1 eb:rounded-full eb:bg-success/10 eb:px-1.5 eb:py-px eb:text-[10.5px] eb:font-medium eb:text-success\">\n <Gift className=\"eb:size-3\" aria-hidden=\"true\" />\n {t('Free')}\n </span>\n )}\n {model.id === provider.model && (\n <span className=\"eb:shrink-0 eb:rounded-full eb:bg-muted eb:px-1.5 eb:py-px eb:text-[10.5px] eb:font-medium eb:text-foreground-lighter\">{t('Default')}</span>\n )}\n </>\n );\n\n const status = {\n loading: (\n <>\n <LoaderCircle className=\"eb:size-3 eb:shrink-0 eb:animate-spin\" aria-hidden=\"true\" />\n {t('Loading the models of {provider}…', { provider: provider.name })}\n </>\n ),\n 'needs-key': t('Paste the key to see all the models of {provider}.', { provider: provider.name }),\n refused: <span className=\"eb:text-warning\">{t('{provider} refused this key: its models could not be listed.', { provider: provider.name })}</span>,\n failed: t('The list of models could not be loaded. The default model works, or type the id of another.'),\n ready: list.models.length\n ? hasFree\n ? t('{count} models, the free ones first.', { count: list.models.length })\n : t('{count} models.', { count: list.models.length })\n : t('{provider} lists no models here: type the id of the one you want.', { provider: provider.name }),\n }[list.status];\n\n return (\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\" data-testid=\"eb-ai-connect-model-field\">\n <span id={labelId} className=\"eb:text-[12.5px] eb:leading-none eb:font-medium eb:text-foreground\">\n {t('Model')}\n </span>\n <button\n ref={triggerRef}\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-labelledby={`${labelId} ${valueId}`}\n aria-describedby={hintId}\n onClick={() => onOpenChange(!open)}\n className={cn(\n 'eb:flex eb:h-9 eb:w-full eb:min-w-0 eb:items-center eb:gap-2 eb:rounded-md eb:border eb:border-input eb:bg-transparent eb:px-3 eb:text-left eb:shadow-xs eb:transition-[color,box-shadow] eb:outline-none eb:focus-visible:border-ring eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n open && 'eb:border-ring'\n )}\n data-testid=\"eb-ai-connect-model\"\n >\n <span id={valueId} className=\"eb:min-w-0 eb:truncate eb:font-mono eb:text-[12.5px] eb:text-foreground\">{value || t('Choose a model')}</span>\n {current && badges(current)}\n <ChevronDown className={cn('eb:ml-auto eb:size-4 eb:shrink-0 eb:text-foreground-muted eb:transition-transform', open && 'eb:rotate-180')} aria-hidden=\"true\" />\n </button>\n\n {open && (\n <div\n className=\"eb:flex eb:flex-col eb:overflow-hidden eb:rounded-md eb:border eb:border-border eb:bg-background\"\n onKeyDown={(event) => event.key === 'Escape' && close()}\n data-eb-models=\"\"\n data-testid=\"eb-ai-connect-models\"\n >\n <div className=\"eb:flex eb:items-center eb:gap-2 eb:border-b eb:border-border eb:p-2\">\n <div className=\"eb:relative eb:min-w-0 eb:flex-1\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" aria-hidden=\"true\" />\n <input\n autoFocus\n role=\"combobox\"\n aria-expanded=\"true\"\n aria-controls={listId}\n aria-autocomplete=\"list\"\n aria-activedescendant={count ? optionId(active) : undefined}\n aria-label={t('Search the models')}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={onKeyDown}\n placeholder={t('Search the models…')}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n className=\"eb:h-8 eb:w-full eb:rounded-md eb:bg-surface-75 eb:pr-2 eb:pl-7 eb:text-[13px] eb:text-foreground eb:outline-none eb:placeholder:text-foreground-muted eb:focus-visible:ring-2 eb:focus-visible:ring-ring/40\"\n data-testid=\"eb-ai-connect-model-search\"\n />\n </div>\n {hasFree && (\n <button\n type=\"button\"\n aria-pressed={freeOnly}\n onClick={() => setFreeOnly((on) => !on)}\n className={cn(\n 'eb:inline-flex eb:h-8 eb:shrink-0 eb:items-center eb:gap-1.5 eb:rounded-md eb:border eb:px-2.5 eb:text-[12px] eb:font-medium eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n freeOnly ? 'eb:border-success/40 eb:bg-success/10 eb:text-success' : 'eb:border-border eb:text-foreground-lighter eb:hover:bg-muted'\n )}\n >\n <Gift className=\"eb:size-3.5\" aria-hidden=\"true\" />\n {t('Free only')}\n </button>\n )}\n </div>\n <div ref={listRef} id={listId} role=\"listbox\" aria-labelledby={labelId} className=\"eb:max-h-56 eb:overflow-y-auto eb:p-1\">\n {matches.map((model, at) => (\n <button\n key={model.id}\n id={optionId(at)}\n type=\"button\"\n role=\"option\"\n tabIndex={-1}\n aria-selected={model.id === value}\n data-active={at === active}\n data-model={model.id}\n onClick={() => pick(model.id)}\n onMouseMove={() => at !== active && setActive(at)}\n className={cn(\n 'eb:flex eb:w-full eb:min-w-0 eb:items-start eb:gap-2 eb:rounded-md eb:px-2 eb:py-1.5 eb:text-left',\n at === active ? 'eb:bg-accent eb:text-accent-foreground' : ''\n )}\n >\n <Check className={cn('eb:mt-0.5 eb:size-3.5 eb:shrink-0 eb:text-primary', model.id !== value && 'eb:invisible')} aria-hidden=\"true\" />\n <span className=\"eb:flex eb:min-w-0 eb:flex-1 eb:flex-col eb:gap-0.5\">\n <span className=\"eb:flex eb:min-w-0 eb:items-center eb:gap-1.5\">\n <span className=\"eb:truncate eb:text-[13px] eb:text-foreground\">{model.name}</span>\n {badges(model)}\n </span>\n {model.name !== model.id && <span className=\"eb:truncate eb:font-mono eb:text-[10.5px] eb:text-foreground-muted\">{model.id}</span>}\n </span>\n </button>\n ))}\n {custom && (\n <button\n id={optionId(matches.length)}\n type=\"button\"\n role=\"option\"\n tabIndex={-1}\n aria-selected={false}\n data-active={active === matches.length}\n onClick={() => pick(custom)}\n onMouseMove={() => active !== matches.length && setActive(matches.length)}\n className={cn(\n 'eb:flex eb:w-full eb:min-w-0 eb:items-center eb:gap-2 eb:rounded-md eb:px-2 eb:py-1.5 eb:text-left eb:text-[13px] eb:text-primary',\n active === matches.length ? 'eb:bg-accent' : ''\n )}\n data-testid=\"eb-ai-connect-model-custom\"\n >\n <span className=\"eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n <span className=\"eb:min-w-0 eb:truncate\">{t('Use “{model}”', { model: custom })}</span>\n </button>\n )}\n {!count && <p className=\"eb:px-2 eb:py-3 eb:text-center eb:text-[12px] eb:text-foreground-muted\">{t('No model matches.')}</p>}\n </div>\n </div>\n )}\n\n <p id={hintId} role=\"status\" className=\"eb:flex eb:min-h-4 eb:items-center eb:gap-1.5 eb:text-[11.5px] eb:text-foreground-muted\" data-testid=\"eb-ai-connect-models-status\">\n {status}\n </p>\n </div>\n );\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Check, Loader2, Search, Sparkles, TriangleAlert } from 'lucide-react';\nimport { SEARCH_KEYS, SearchKeyHint, useSearchShortcut } from '../ui/primitives/searchShortcut.jsx';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input, Textarea } from '../ui/primitives/fields.jsx';\nimport { SectionLabel } from '../ui/primitives/misc.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/primitives/overlays.jsx';\nimport { chipClassName } from './chip.js';\nimport { MAX_DETAILS_LENGTH } from './generator.js';\nimport { purposesByCategory } from './purposes.js';\nimport { brandTemplate, hasBrand } from '../features/brand/brand.js';\n\n/**\n * GENERATE — toolbar button and dialog: an email written from scratch.\n *\n * One decision for the user: what the email is for (a badge). Details are\n * optional; the language is the interface's, the sender and the variables\n * are the builder's own. The dialog says up front that the email in the\n * editor will be replaced — no confirmation after a minute of waiting.\n *\n * The result REPLACES the email in the editor, subject and preview text\n * included (they belong to the new email); the name is filled when empty.\n * A saved template counts as modified: the next save keeps it.\n */\n\n/** Lowercase, without accents: \"Été\" matches \"ete\". @param {string} text */\nconst fold = (text) =>\n text\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n\nexport default function GenerateButton({ context, generate: generateEmail }) {\n const { editor, ready, isDirty, markDirty, template, setMeta, variables, sender, brand } = context;\n const { t, locale, notify } = useUi();\n const [open, setOpen] = useState(false);\n const [purpose, setPurpose] = useState(/** @type {string|null} */ (null));\n const [details, setDetails] = useState('');\n const [query, setQuery] = useState('');\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState('');\n const [seconds, setSeconds] = useState(0);\n const [dirty, setDirty] = useState(false);\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const searchRef = useRef(/** @type {HTMLInputElement|null} */ (null));\n useSearchShortcut(searchRef);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n useEffect(() => {\n if (!busy) return undefined;\n setSeconds(0);\n const timer = setInterval(() => setSeconds((s) => s + 1), 1000);\n return () => clearInterval(timer);\n }, [busy]);\n\n const groups = useMemo(() => {\n const words = fold(query.trim()).split(/\\s+/).filter(Boolean);\n return purposesByCategory()\n .map((group) => ({\n ...group,\n purposes: group.purposes.filter((entry) => {\n const label = fold(`${t(entry.label)} ${entry.label} ${t(group.label)}`);\n return words.every((word) => label.includes(word));\n }),\n }))\n .filter((group) => group.purposes.length > 0);\n }, [query, t]);\n\n const openDialog = () => {\n setError('');\n setDirty(isDirty());\n setOpen(true);\n };\n\n const close = (next) => {\n if (next) return;\n abortRef.current?.abort();\n setOpen(false);\n };\n\n const generate = async () => {\n if (busy || (!purpose && !details.trim())) return;\n setError('');\n setBusy(true);\n const controller = new AbortController();\n abortRef.current = controller;\n try {\n const email = await generateEmail(\n {\n purpose,\n details: details.trim(),\n language: String(locale || 'en').slice(0, 2),\n sender: sender || {},\n ...(hasBrand(brand) ? { brand: { primary: brand.primary, heading: brand.heading, font: brand.font } } : {}),\n variables: (variables || []).map((variable) => ({ key: variable.key, label: variable.label })),\n },\n { signal: controller.signal }\n );\n if (controller.signal.aborted) return;\n // The brand's logo where the model wrote the name (an uploaded logo\n // is not an https address the model may be given).\n editor.loadMjml(hasBrand(brand) ? brandTemplate(email.mjml, brand, { sender, t }) : email.mjml);\n setMeta((meta) => ({\n ...meta,\n name: meta.name || email.name,\n subject: email.subject || meta.subject,\n preheader: email.preheader || meta.preheader,\n }));\n if (template.id) markDirty();\n setOpen(false);\n notify('success', t('Your email is ready. Edit anything, then save.'));\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('The email could not be generated.'));\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n setBusy(false);\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n setBusy(false);\n };\n\n const canGenerate = Boolean(purpose || details.trim());\n\n return (\n <>\n <Button variant=\"outline\" size=\"sm-compact\" disabled={!ready} onClick={openDialog}>\n <Sparkles />\n {t('Generate')}\n </Button>\n <Dialog open={open} onOpenChange={close}>\n <DialogContent\n className=\"eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl\"\n data-testid=\"eb-generate\"\n onOpenAutoFocus={(event) => {\n // The search field rather than the first badge.\n event.preventDefault();\n searchRef.current?.focus();\n }}\n >\n <DialogHeader className=\"eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-3\">\n <DialogTitle>{t('Generate an email')}</DialogTitle>\n <DialogDescription>{t('Pick what it is for. AI writes a complete email that opens here, ready to edit.')}</DialogDescription>\n </DialogHeader>\n\n <div className=\"eb:px-6 eb:pb-3\">\n <div className=\"eb:relative\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" aria-hidden=\"true\" />\n <Input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('Search: sale, password, birthday…')}\n aria-label={t('Search')}\n aria-keyshortcuts={SEARCH_KEYS}\n className=\"eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden\"\n disabled={busy}\n />\n <SearchKeyHint className=\"eb:absolute eb:top-1/2 eb:right-2 eb:-translate-y-1/2\" />\n </div>\n </div>\n\n {/* The scroll box is a div: a fieldset does not clip its overflow\n as a flex item, and the badges would run under the footer. */}\n <div className=\"eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-4\" data-testid=\"eb-generate-purposes\">\n <fieldset disabled={busy} className=\"eb:m-0 eb:min-w-0 eb:border-0 eb:p-0\">\n <legend className=\"eb:sr-only\">{t('What is the email for?')}</legend>\n {groups.length === 0 && (\n <p className=\"eb:py-6 eb:text-center eb:text-[13px] eb:text-foreground-muted\">\n {t('Nothing matches. Describe your email in the details below.')}\n </p>\n )}\n <div className=\"eb:flex eb:flex-col eb:gap-4\">\n {groups.map((group) => (\n <section key={group.id} className=\"eb:flex eb:flex-col eb:gap-2\" aria-label={t(group.label)}>\n <SectionLabel>{t(group.label)}</SectionLabel>\n <div className=\"eb:flex eb:flex-wrap eb:gap-1.5\">\n {group.purposes.map((entry) => {\n const selected = purpose === entry.id;\n return (\n <button\n key={entry.id}\n type=\"button\"\n aria-pressed={selected}\n data-purpose={entry.id}\n onClick={() => setPurpose(selected ? null : entry.id)}\n className={chipClassName(selected)}\n >\n {selected && <Check className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t(entry.label)}\n </button>\n );\n })}\n </div>\n </section>\n ))}\n </div>\n </fieldset>\n </div>\n\n <div className=\"eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6\">\n <Textarea\n value={details}\n onChange={(event) => setDetails(event.target.value.slice(0, MAX_DETAILS_LENGTH))}\n placeholder={t('Details (optional): product, dates, offer, tone, language…')}\n aria-label={t('Details (optional)')}\n rows={2}\n className=\"eb:min-h-14\"\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) generate();\n }}\n />\n {error ? (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n ) : (\n dirty &&\n !busy && (\n <p className=\"eb:text-[12px] eb:text-foreground-muted\">{t('The email being edited will be replaced, unsaved changes included.')}</p>\n )\n )}\n <div className=\"eb:flex eb:items-center eb:justify-end eb:gap-2\">\n {busy && (\n <p className=\"eb:mr-auto eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Writing your email… {seconds} s', { seconds })}\n </p>\n )}\n {busy && (\n <Button variant=\"outline\" size=\"sm\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n )}\n <Button size=\"sm\" onClick={generate} disabled={!canGenerate || busy} data-testid=\"eb-generate-submit\">\n {busy ? <Loader2 className=\"eb:animate-spin\" /> : <Sparkles />}\n {t('Generate')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n","import { cn } from '../ui/primitives/cn.js';\n\n/**\n * A rounded badge button: the purposes of \"Generate\", the rewrite actions.\n * @param {boolean} [selected]\n */\nexport const chipClassName = (selected = false) =>\n cn(\n 'eb:inline-flex eb:h-7 eb:items-center eb:gap-1 eb:rounded-full eb:border eb:px-3 eb:text-[12px] eb:font-medium eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50 eb:disabled:opacity-50',\n selected\n ? 'eb:border-primary eb:bg-primary eb:text-primary-foreground'\n : 'eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5'\n );\n","import { useEffect, useRef, useState } from 'react';\nimport { ChevronDown, Languages, Loader2, Sparkles, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { Input } from '../ui/primitives/fields.jsx';\nimport { SectionLabel } from '../ui/primitives/misc.jsx';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/primitives/overlays.jsx';\nimport { cleanRichText } from '../engine/richText.js';\nimport { chipClassName } from './chip.js';\nimport { MAX_REWRITE_INSTRUCTION, REWRITE_ACTIONS, REWRITE_LANGUAGES } from './rewrite.js';\n\n/**\n * REWRITE WITH AI — in the properties of a selected text or button.\n *\n * One click: shorter, longer, fixed, friendlier, more formal, translated —\n * or a change in the user's words. The result replaces the text at once, in\n * one step of the history: Ctrl+Z brings the previous text back, so there is\n * nothing to accept or compare.\n *\n * Mounted per element (keyed by its id): selecting another element cancels a\n * rewrite in progress. If the text is edited while the AI works, the user's\n * words win and the answer is dropped.\n */\n\n/** A language under its own name: \"Deutsch\", \"Українська\". @param {string} code */\nfunction nativeName(code) {\n try {\n const name = new Intl.DisplayNames([code], { type: 'language' }).of(code) || code;\n return name.charAt(0).toLocaleUpperCase(code) + name.slice(1);\n } catch {\n return code;\n }\n}\n\nexport default function RewriteText({ editor, selection, rewrite }) {\n const { t, notify } = useUi();\n const [running, setRunning] = useState(/** @type {string|null} */ (null));\n const [error, setError] = useState('');\n const [instruction, setInstruction] = useState('');\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const label = selection.tagName === 'mj-button';\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n /** The text as stored: a button's label, a text block's HTML. */\n const currentText = () => {\n const element = editor.getElement(selection.id);\n return (label ? element?.content : element?.html) ?? '';\n };\n\n const run = async (action, extra = {}) => {\n if (running) return;\n const text = currentText();\n setError('');\n setRunning(action);\n const controller = new AbortController();\n abortRef.current = controller;\n try {\n const result = await rewrite({ action, text, format: label ? 'plain' : 'html', ...extra }, { signal: controller.signal });\n if (controller.signal.aborted) return;\n if (currentText() !== text) {\n setError(t('The text changed in the meantime, so it was left as it is.'));\n return;\n }\n editor.setContent(label ? result.text : cleanRichText(result.text, document), selection.id);\n if (action === 'custom') setInstruction('');\n notify('success', t('Text rewritten. Ctrl+Z brings back the previous one.'));\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('The text could not be rewritten.'));\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n setRunning(null);\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n setRunning(null);\n };\n\n const busy = Boolean(running);\n const spinner = <Loader2 className=\"eb:size-3 eb:animate-spin\" aria-hidden=\"true\" />;\n\n return (\n <section className=\"eb:space-y-2.5\" aria-label={t('Rewrite with AI')} data-testid=\"eb-rewrite\">\n <SectionLabel className=\"eb:flex eb:items-center eb:gap-1.5\">\n <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />\n {t('Rewrite with AI')}\n </SectionLabel>\n <div className=\"eb:flex eb:flex-wrap eb:gap-1.5\">\n {REWRITE_ACTIONS.map((action) => (\n <button key={action.id} type=\"button\" className={chipClassName()} disabled={busy} onClick={() => run(action.id)}>\n {running === action.id && spinner}\n {t(action.label)}\n </button>\n ))}\n <DropdownMenu>\n <DropdownMenuTrigger asChild disabled={busy}>\n <button type=\"button\" className={chipClassName()}>\n {running === 'translate' ? spinner : <Languages className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('Translate')}\n <ChevronDown className=\"eb:size-3\" aria-hidden=\"true\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" aria-label={t('Translate into')}>\n {REWRITE_LANGUAGES.map((code) => (\n <DropdownMenuItem key={code} lang={code} onSelect={() => run('translate', { targetLanguage: code })}>\n {nativeName(code)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <form\n className=\"eb:flex eb:gap-1.5\"\n onSubmit={(event) => {\n event.preventDefault();\n if (instruction.trim()) run('custom', { instruction: instruction.trim() });\n }}\n >\n <Input\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n maxLength={MAX_REWRITE_INSTRUCTION}\n placeholder={t('Or say what to change…')}\n aria-label={t('What to change')}\n disabled={busy}\n />\n <Button type=\"submit\" variant=\"outline\" size=\"icon-sm\" aria-label={t('Rewrite')} disabled={busy || !instruction.trim()}>\n {running === 'custom' ? <Loader2 className=\"eb:animate-spin\" /> : <Sparkles />}\n </Button>\n </form>\n {running && (\n <p className=\"eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Rewriting…')}\n <Button type=\"button\" variant=\"link\" size=\"xs\" className=\"eb:h-auto eb:px-0\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n </p>\n )}\n {error && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n )}\n </section>\n );\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { Check, Loader2, Sparkles, TriangleAlert } from 'lucide-react';\nimport { useUi } from '../ui/context.jsx';\nimport { Button } from '../ui/primitives/button.jsx';\nimport { cn } from '../ui/primitives/cn.js';\nimport { chipClassName } from './chip.js';\nimport { emailWords } from './subjects.js';\n\n/** Ideas already shown that \"More ideas\" asks the model not to repeat (the server keeps 12). */\nconst MAX_AVOID = 12;\n\n/**\n * SUGGEST SUBJECTS — under the subject of the email.\n *\n * One click: three other subject lines, drawn from the words of the email.\n * They are a list to pick from, the user's own subject first: a click puts\n * one in the field, another click another one, the first row brings the\n * user's subject back. \"More ideas\" asks for three new ones (none of those\n * already shown; the one in the field stays in the list); \"Done\" closes the\n * list and keeps the subject in the field.\n *\n * \"Yours\" is what the user wrote: typed in the field while the list is open\n * (or while ideas are on their way), it becomes the new \"yours\". When the\n * email itself is replaced (Generate, a template), the list closes.\n *\n * Keyboard: the list is a radio group — one Tab stop, the arrows move and\n * pick. Focus goes to the list when it opens, back to the button after Done or\n * Cancel.\n */\nexport default function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {\n const { t, locale } = useUi();\n const [running, setRunning] = useState(false);\n const [error, setError] = useState('');\n const [ideas, setIdeas] = useState(/** @type {string[]|null} */ (null));\n const [original, setOriginal] = useState('');\n const shownRef = useRef(/** @type {string[]} */ ([]));\n const abortRef = useRef(/** @type {AbortController|null} */ (null));\n const subjectRef = useRef(meta.subject);\n subjectRef.current = meta.subject;\n const rootRef = useRef(/** @type {HTMLDivElement|null} */ (null));\n /** Where focus goes after the next render: the list, or the button that opened it. */\n const focusRef = useRef(/** @type {'list'|'button'|null} */ (null));\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n // Another email loaded in the editor: these ideas were for the previous one.\n useEffect(\n () =>\n editor.on('load', () => {\n abortRef.current?.abort();\n abortRef.current = null;\n shownRef.current = [];\n setRunning(false);\n setIdeas(null);\n setError('');\n setOriginal('');\n }),\n [editor]\n );\n\n // Typed in the field while the list is open: that is the user's subject now.\n const typed = meta.subject.trim();\n useEffect(() => {\n if (ideas && typed && !ideas.includes(meta.subject) && typed !== original) setOriginal(typed);\n }, [ideas, meta.subject, typed, original]);\n\n useLayoutEffect(() => {\n const target = focusRef.current;\n if (!target || running) return;\n focusRef.current = null;\n const root = rootRef.current;\n const element =\n target === 'list'\n ? root?.querySelector('[role=\"radio\"][tabindex=\"0\"]')\n : root?.querySelector('[data-subject-ideas-button]');\n /** @type {HTMLElement|null|undefined} */ (element)?.focus();\n });\n\n const ready = editor.status === 'ready';\n\n const run = async () => {\n if (running || editor.status !== 'ready') return;\n const first = !ideas;\n // The user's subject: as it was before the first idea was picked, or typed since.\n const yours = first ? subjectRef.current.trim() : original;\n const controller = new AbortController();\n abortRef.current = controller;\n setError('');\n setRunning(true);\n try {\n const result = await suggest(\n {\n subject: yours,\n preheader: meta.preheader,\n text: emailWords(editor.exportMjml()),\n language: locale,\n variables: variables?.map(({ key, label, kind }) => ({ key, label, kind })),\n avoid: shownRef.current.slice(-MAX_AVOID),\n },\n { signal: controller.signal }\n );\n if (controller.signal.aborted) return;\n const current = subjectRef.current;\n // The idea in the field stays in the list; the others give way to the new ones.\n const kept = ideas?.includes(current) ? [current] : [];\n const next = [...kept, ...result.subjects.filter((subject) => subject !== current)];\n shownRef.current = [...shownRef.current, ...result.subjects];\n // Typed during the wait: that is \"yours\" now.\n setOriginal(current.trim() && !ideas?.includes(current) ? current.trim() : yours);\n setIdeas(next);\n if (first) focusRef.current = 'list';\n } catch (err) {\n if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t('No subject could be suggested.'));\n } finally {\n // A request cancelled earlier must not end the one that replaced it.\n if (abortRef.current === controller) {\n abortRef.current = null;\n setRunning(false);\n }\n }\n };\n\n const cancel = () => {\n abortRef.current?.abort();\n abortRef.current = null;\n setRunning(false);\n focusRef.current = 'button';\n };\n\n const done = () => {\n setIdeas(null);\n setError('');\n shownRef.current = [];\n focusRef.current = 'button';\n };\n\n const options = ideas ? [...(original ? [{ text: original, yours: true }] : []), ...ideas.map((text) => ({ text, yours: false }))] : [];\n const checked = options.findIndex((option) => option.text === meta.subject);\n const tabStop = checked === -1 ? 0 : checked;\n\n /** Arrows move through the ideas and pick, as in any radio group. */\n const onKeyDown = (/** @type {React.KeyboardEvent} */ event) => {\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n const to = step ? (tabStop + step + options.length) % options.length : event.key === 'Home' ? 0 : event.key === 'End' ? options.length - 1 : -1;\n if (to === -1 || !options.length) return;\n event.preventDefault();\n onSubject(options[to].text);\n /** @type {HTMLElement|undefined} */ (rootRef.current?.querySelectorAll('[role=\"radio\"]')[to])?.focus();\n };\n\n const spinner = <Loader2 className=\"eb:size-3 eb:animate-spin\" aria-hidden=\"true\" />;\n\n return (\n <div className=\"eb:space-y-2\" data-testid=\"eb-subject-ideas\" ref={rootRef}>\n {!ideas && (\n <button type=\"button\" className={chipClassName()} disabled={running || !ready} onClick={run} data-subject-ideas-button>\n {running ? spinner : <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('Suggest subjects')}\n </button>\n )}\n {ideas && (\n <div className=\"eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5\">\n <div role=\"radiogroup\" aria-label={t('Subject ideas')} className=\"eb:space-y-0.5\" onKeyDown={onKeyDown}>\n {options.map((option, index) => {\n const chosen = index === checked;\n return (\n <button\n key={`${option.yours ? 'yours' : 'idea'}:${option.text}`}\n type=\"button\"\n role=\"radio\"\n aria-checked={chosen}\n tabIndex={index === tabStop ? 0 : -1}\n className={cn(\n 'eb:flex eb:w-full eb:items-start eb:gap-2 eb:rounded eb:px-2 eb:py-1.5 eb:text-left eb:text-[12.5px] eb:leading-snug eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n chosen ? 'eb:bg-primary/10 eb:text-foreground' : 'eb:text-foreground-lighter eb:hover:bg-muted'\n )}\n onClick={() => onSubject(option.text)}\n >\n <Check className={cn('eb:mt-0.5 eb:size-3.5 eb:shrink-0', chosen ? 'eb:text-primary' : 'eb:invisible')} aria-hidden=\"true\" />\n <span className=\"eb:min-w-0 eb:flex-1 eb:break-words\">{option.text}</span>\n {option.yours && <span className=\"eb:shrink-0 eb:text-[11px] eb:text-foreground-muted\">{t('yours')}</span>}\n </button>\n );\n })}\n </div>\n <div className=\"eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5\">\n <button type=\"button\" className={chipClassName()} disabled={running || !ready} onClick={run} data-subject-ideas-button>\n {running ? spinner : <Sparkles className=\"eb:size-3\" aria-hidden=\"true\" />}\n {t('More ideas')}\n </button>\n <Button type=\"button\" variant=\"ghost\" size=\"xs\" className=\"eb:ml-auto\" disabled={running} onClick={done}>\n {t('Done')}\n </Button>\n </div>\n </div>\n )}\n {running && (\n <p className=\"eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted\" role=\"status\" aria-live=\"polite\">\n {t('Reading the email…')}\n <Button type=\"button\" variant=\"link\" size=\"xs\" className=\"eb:h-auto eb:px-0\" onClick={cancel}>\n {t('Cancel')}\n </Button>\n </p>\n )}\n {error && (\n <p role=\"alert\" className=\"eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive\">\n <TriangleAlert className=\"eb:mt-px eb:size-3.5 eb:shrink-0\" aria-hidden=\"true\" />\n {error}\n </p>\n )}\n </div>\n );\n}\n","// @ts-check\n/**\n * THE AI MODULE — `createAi()`, given to the builder as `ai`.\n *\n * import { createAi } from 'best-email-builder/ai';\n * <EmailBuilder storage={storage} ai={createAi()} />\n *\n * Without it, the builder has no AI button, and none of the AI's code or\n * texts is in the application's bundle: the core never imports this folder.\n * With it, the builder gets what the module can do:\n *\n * generate the Generate button (an email written from a purpose)\n * rewrite \"Rewrite with AI\" in the properties of a text or a button\n * suggestSubjects \"Suggest subjects\" under the subject\n *\n * Where the model runs:\n *\n * createAi() the routes of createEmailBuilderHandler({ generator })\n * createAi({ baseUrl, headers }) the same routes elsewhere, or with the user's credentials\n * createAi({ provider, askKey: true }) the AI runs in the page with each user's own key\n * (OpenRouter, Gemini, Groq… — see providers.js);\n * the builder asks for it once (the Connect dialog)\n * createAi({ provider: [...], askKey: true })\n * the same, the user choosing the provider in that\n * dialog (Claude too: claudeProvider in claude.js),\n * and the model among those the provider lists\n * createAi({ provider, apiKey }) the same with one key for all (prototypes: the key\n * is then in the page)\n * createAi({ model }) the generator runs in the page with this model\n * createAi({ generate, rewrite, … }) functions of your own: only those given are on\n *\n * Each of `generate`, `rewrite` and `suggestSubjects` can also be `false`,\n * to leave that part out — its button with it.\n */\n\nimport { createHttpCaller } from '../storage/client.js';\nimport { createEmailGenerator } from './generator.js';\nimport { AI_MESSAGES } from './i18n/index.js';\nimport { createKeyStore } from './keys.js';\nimport { providerEmailModel, resolveProvider } from './providers.js';\nimport ConnectAi from './ConnectAi.jsx';\nimport GenerateButton from './GenerateButton.jsx';\nimport RewriteText from './RewriteText.jsx';\nimport SubjectIdeas from './SubjectIdeas.jsx';\n\n/**\n * @typedef {import('./generator.js').GenerateInput} GenerateInput\n * @typedef {import('./generator.js').GeneratedEmail} GeneratedEmail\n * @typedef {import('./rewrite.js').RewriteInput} RewriteInput\n * @typedef {import('./subjects.js').SubjectsInput} SubjectsInput\n * @typedef {{signal?: AbortSignal}} CallOptions\n * @typedef {(input: GenerateInput, options?: CallOptions) => Promise<GeneratedEmail | Omit<GeneratedEmail, 'warnings'|'attempts'>>} GenerateFn\n * @typedef {(input: RewriteInput, options?: CallOptions) => Promise<{text: string}>} RewriteFn\n * @typedef {(input: SubjectsInput, options?: CallOptions) => Promise<{subjects: string[]}>} SuggestSubjectsFn\n *\n * @typedef {{\n * baseUrl?: string,\n * headers?: Record<string, string> | (() => Record<string, string>),\n * fetch?: typeof fetch,\n * model?: import('./generator.js').EmailModel,\n * provider?: ProviderOption | ProviderOption[],\n * apiKey?: string,\n * askKey?: boolean,\n * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,\n * maxAttempts?: number,\n * generate?: GenerateFn | boolean,\n * rewrite?: RewriteFn | boolean,\n * suggestSubjects?: SuggestSubjectsFn | boolean,\n * }} AiOptions\n * `provider`: an id of `AI_PROVIDERS` ('openrouter', 'gemini', 'groq'…), or\n * `{ id, model, baseUrl, headers }` for another model or an endpoint of your\n * own — or a list of them, with `askKey`: the user chooses (the first is\n * offered first). `apiKey`: one key for every user. `askKey`: each user\n * connects their own (off by default). `compile`: with `provider` or `model`, MJML's own\n * warnings fed back to the model (see createEmailGenerator).\n *\n * @typedef {Readonly<{\n * generate: GenerateFn | null,\n * rewrite: RewriteFn | null,\n * suggestSubjects: SuggestSubjectsFn | null,\n * key: import('./keys.js').AiKey | null,\n * messages: Record<string, Record<string, string>>,\n * components: Readonly<{Generate: typeof GenerateButton, Rewrite: typeof RewriteText, Subjects: typeof SubjectIdeas, Connect: typeof ConnectAi | null}>,\n * }>} EmailBuilderAi\n * What the builder reads. `key`: the user's key, with `askKey` (keys.js).\n * `messages`: the module's own translations, by locale, merged under the\n * host's `messages`. `components.Connect`: the dialog that asks for the key.\n */\n\n/** @typedef {string | Partial<import('./providers.js').AiProvider>} ProviderOption */\n\nconst PARTS = /** @type {const} */ (['generate', 'rewrite', 'suggestSubjects']);\nconst ROUTES = { generate: '/generate', rewrite: '/rewrite', suggestSubjects: '/subjects' };\n\n/**\n * @param {AiOptions} [options]\n * @returns {EmailBuilderAi}\n */\nexport function createAi(options = {}) {\n if (!options || typeof options !== 'object') throw new TypeError('createAi: options must be an object');\n const { model, maxAttempts, compile, baseUrl, headers, fetch: fetchImpl, provider: providerOption, apiKey, askKey = false } = options;\n const usesProvider = providerOption !== undefined;\n const http = baseUrl !== undefined || headers !== undefined || (fetchImpl !== undefined && !usesProvider);\n if (model !== undefined && typeof model !== 'function') throw new TypeError('createAi: `model` must be a function');\n if (model && http) throw new TypeError('createAi: pass `model` (the AI runs in the page) or `baseUrl` (on the server), not both');\n if (usesProvider && (model || http)) {\n throw new TypeError('createAi: `provider` runs the AI in the page — not with `model`, `baseUrl` or `headers` (give the provider `{ id, model, baseUrl, headers }`)');\n }\n if (!usesProvider && (apiKey !== undefined || options.askKey !== undefined)) {\n throw new TypeError('createAi: `apiKey` and `askKey` go with a `provider` (e.g. provider: \"openrouter\")');\n }\n if (apiKey !== undefined && (typeof apiKey !== 'string' || !apiKey)) throw new TypeError('createAi: `apiKey` must be a non-empty string');\n if (compile !== undefined && (typeof compile !== 'function' || (!model && !usesProvider))) {\n throw new TypeError('createAi: `compile` must be a function, and goes with `provider` or `model` (the AI in the page)');\n }\n if (typeof askKey !== 'boolean') throw new TypeError('createAi: `askKey` must be true or false');\n if (apiKey && askKey) throw new TypeError('createAi: `apiKey` (one key for all) or `askKey` (each user\\'s own), not both');\n const choice = Array.isArray(providerOption);\n if (choice && (!providerOption.length || !askKey)) {\n throw new TypeError('createAi: a list of providers is for `askKey: true` — each user chooses one and connects their own key');\n }\n /** @type {import('./providers.js').AiProvider[]} */\n const providers = usesProvider ? (choice ? providerOption : [providerOption]).map(providerOf) : [];\n if (new Set(providers.map((provider) => provider.id)).size !== providers.length) throw new TypeError('createAi: each provider once');\n const [provider = null] = providers;\n if (provider && !apiKey && !askKey && !provider.keyless) {\n throw new TypeError(`createAi: ${provider.name} needs a key — \\`apiKey\\`, or \\`askKey: true\\` to let each user connect their own`);\n }\n for (const part of PARTS) {\n const value = options[part];\n if (value !== undefined && typeof value !== 'boolean' && typeof value !== 'function') {\n throw new TypeError(`createAi: \\`${part}\\` must be a function, true or false`);\n }\n }\n\n // Functions of the host's own and nothing else: only those are on.\n const ownOnly = !model && !http && PARTS.some((part) => typeof options[part] === 'function');\n /** @type {Partial<Record<typeof PARTS[number], Function>>} */\n let transport = {};\n const key = provider && askKey ? createKeyStore(providers, { fetch: fetchImpl }) : null;\n if (provider) {\n /** @type {Map<string, import('./generator.js').EmailModel>} */\n const models = new Map();\n const keyFor = () => (key ? key.get() : apiKey || '');\n /** The model the user chose of the provider in use, made once. @param {string} id */\n const modelOf = (id) => {\n const chosen = /** @type {import('./providers.js').AiProvider} */ (providers.find((item) => item.id === id));\n const name = key ? key.model(id) : chosen.model;\n const slot = `${id}\\n${name}`;\n if (!models.has(slot)) {\n models.set(\n slot,\n chosen.createModel\n ? chosen.createModel({ apiKey: keyFor, fetch: fetchImpl, ...(name ? { model: name } : {}) })\n : providerEmailModel({ provider: chosen, fetch: fetchImpl, apiKey: keyFor, ...(name ? { model: name } : {}) })\n );\n }\n return /** @type {import('./generator.js').EmailModel} */ (models.get(slot));\n };\n /** @type {import('./generator.js').EmailModel} */\n const model = async (request) => {\n // The user's key, asked when there is none — they may choose another\n // provider there ('' when they cancel: KEY_MISSING).\n if (key && !key.get() && !providers.find((item) => item.id === key.provider.id)?.keyless) await key.request();\n return modelOf(key ? key.provider.id : provider.id)(request);\n };\n const generator = createEmailGenerator({ model, maxAttempts, compile });\n for (const part of PARTS) transport[part] = key ? askedAgain(generator[part], key) : generator[part];\n } else if (model) {\n transport = createEmailGenerator({ model, maxAttempts, compile });\n } else if (!ownOnly) {\n const { call } = createHttpCaller({ baseUrl, headers, fetch: fetchImpl });\n for (const part of PARTS) {\n transport[part] = (/** @type {object} */ input, /** @type {CallOptions} */ { signal } = {}) =>\n call(ROUTES[part], { method: 'POST', body: JSON.stringify(input), signal });\n }\n }\n\n /** @param {typeof PARTS[number]} part */\n const resolve = (part) => {\n const value = options[part];\n if (typeof value === 'function') return value;\n if (value === false) return null;\n return transport[part] || null;\n };\n\n return Object.freeze({\n generate: /** @type {GenerateFn|null} */ (resolve('generate')),\n rewrite: /** @type {RewriteFn|null} */ (resolve('rewrite')),\n suggestSubjects: /** @type {SuggestSubjectsFn|null} */ (resolve('suggestSubjects')),\n key,\n messages: AI_MESSAGES,\n components: Object.freeze({ Generate: GenerateButton, Rewrite: RewriteText, Subjects: SubjectIdeas, Connect: key ? ConnectAi : null }),\n });\n}\n\n/**\n * A provider of the list: an id, `{ id, model, … }`, or one with its own\n * `createModel` (claudeProvider) taken as it is.\n * @param {ProviderOption} option\n * @returns {import('./providers.js').AiProvider}\n */\nfunction providerOf(option) {\n if (option && typeof option === 'object' && typeof option.createModel === 'function') {\n if (!option.id || !option.name) throw new TypeError('createAi: a provider with `createModel` needs an `id` and a `name`');\n return /** @type {import('./providers.js').AiProvider} */ (option);\n }\n return resolveProvider(/** @type {any} */ (option));\n}\n\n/**\n * A key the provider refuses is forgotten and asked again, once, in the same\n * action: the user pastes a good one and the email still comes.\n * @param {Function} run\n * @param {import('./keys.js').AiKey} key\n */\nfunction askedAgain(run, key) {\n return async (/** @type {any} */ input, /** @type {any} */ options) => {\n try {\n return await run(input, options);\n } catch (error) {\n if (/** @type {any} */ (error)?.code !== 'KEY_REFUSED' || options?.signal?.aborted) throw error;\n key.refuse();\n try {\n return await run(input, options);\n } catch (again) {\n if (/** @type {any} */ (again)?.code === 'KEY_REFUSED') key.refuse();\n throw again;\n }\n }\n };\n}\n"],"mappings":"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","names":["kept","useEffect","useId","useRef","useState","Gift","models","jsx","jsxs","useState","useRef","useId","useEffect","Gift","useEffect","useMemo","useRef","useState","Check","Search","TriangleAlert","Fragment","jsx","jsxs","useState","useRef","useEffect","useMemo","Search","Check","TriangleAlert","useEffect","useRef","useState","ChevronDown","Loader2","Sparkles","TriangleAlert","jsx","jsxs","useState","useRef","useEffect","Loader2","Sparkles","ChevronDown","TriangleAlert","useEffect","useRef","useState","Check","Loader2","Sparkles","TriangleAlert","jsx","jsxs","useState","useRef","useEffect","Loader2","Sparkles","Check","TriangleAlert","provider","model"]}
|
package/dist/builder.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EmailBuilder,
|
|
3
3
|
EmailBuilderBoundary
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QH5OI43L.js";
|
|
5
5
|
import {
|
|
6
6
|
STARTER_MJML,
|
|
7
7
|
TEMPLATE_GALLERY,
|
|
8
8
|
buildGallery,
|
|
9
9
|
createStarterMjml
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UMVBT7NW.js";
|
|
11
11
|
import "./chunk-4U7L2NYQ.js";
|
|
12
12
|
import "./chunk-UFKCLU2D.js";
|
|
13
13
|
import "./chunk-SAIW46IC.js";
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
StorageError,
|
|
16
16
|
createHttpStorage,
|
|
17
17
|
createMemoryStorage
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-QRUMXCGW.js";
|
|
19
19
|
import {
|
|
20
20
|
EMAIL_BUILDER_VERSION
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-C6WHIBGH.js";
|
|
22
22
|
import "./chunk-OWVXKMLI.js";
|
|
23
23
|
import "./chunk-W424KPCR.js";
|
|
24
24
|
import {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useBrand,
|
|
6
6
|
useLayoutWidth,
|
|
7
7
|
withPlaceholderImages
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UMVBT7NW.js";
|
|
9
9
|
import {
|
|
10
10
|
Button,
|
|
11
11
|
Input,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
createHttpStorage,
|
|
19
19
|
useSearchShortcut,
|
|
20
20
|
useUi
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-QRUMXCGW.js";
|
|
22
22
|
import {
|
|
23
23
|
brandTemplate,
|
|
24
24
|
brandedSender
|
|
@@ -524,4 +524,4 @@ function TemplateLibrary({ locale = "en", messages, onNotify, ...props }) {
|
|
|
524
524
|
export {
|
|
525
525
|
TemplateLibrary
|
|
526
526
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-3RPWH5PC.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-JI36FNWE.js";
|
|
4
4
|
|
|
5
5
|
// src/core/constants.js
|
|
6
|
-
var EMAIL_BUILDER_VERSION = "0.
|
|
6
|
+
var EMAIL_BUILDER_VERSION = "0.7.0";
|
|
7
7
|
var EMAIL_DOCUMENT_SCHEMA_VERSION = 1;
|
|
8
8
|
var EMAIL_DEVICES = ["desktop", "mobile"];
|
|
9
9
|
var EMAIL_MOBILE_WIDTH = 375;
|
|
@@ -116,4 +116,4 @@ export {
|
|
|
116
116
|
detectImage,
|
|
117
117
|
imageUploadError
|
|
118
118
|
};
|
|
119
|
-
//# sourceMappingURL=chunk-
|
|
119
|
+
//# sourceMappingURL=chunk-C6WHIBGH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/validation/images.js"],"sourcesContent":["// @ts-check\n/**\n * Email builder constants.\n *\n * `EMAIL_BUILDER_VERSION` versions THIS package (UI, blocks, rendering);\n * `EMAIL_DOCUMENT_SCHEMA_VERSION` versions the stored JSON envelope (the\n * project JSON). Both are written with every template, so a document read by\n * a newer version knows whether it must be migrated (see `core/document.js`).\n */\n\nexport const EMAIL_BUILDER_VERSION = '0.
|
|
1
|
+
{"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/validation/images.js"],"sourcesContent":["// @ts-check\n/**\n * Email builder constants.\n *\n * `EMAIL_BUILDER_VERSION` versions THIS package (UI, blocks, rendering);\n * `EMAIL_DOCUMENT_SCHEMA_VERSION` versions the stored JSON envelope (the\n * project JSON). Both are written with every template, so a document read by\n * a newer version knows whether it must be migrated (see `core/document.js`).\n */\n\nexport const EMAIL_BUILDER_VERSION = '0.7.0';\n\nexport const EMAIL_DOCUMENT_SCHEMA_VERSION = 1;\n\n/** @typedef {'desktop'|'mobile'} EmailDevice */\n\n/** @type {EmailDevice[]} */\nexport const EMAIL_DEVICES = ['desktop', 'mobile'];\n\n/** Content column width — the email client standard. */\nexport const EMAIL_CONTENT_WIDTH = 600;\n\n/** Mobile preview frame width (standard iPhone, the common worst case). */\nexport const EMAIL_MOBILE_WIDTH = 375;\n","// @ts-check\n/**\n * DOCUMENT envelope — what gets stored (the project JSON).\n *\n * HTML is only a compilation output; what gets edited is the engine's\n * project. It is not stored bare but wrapped with:\n *\n * schemaVersion the shape of THIS envelope (migrations, see MIGRATIONS);\n * engine the engine that can read `project` back;\n * engineVersion its version at write time;\n * builderVersion the builder version that wrote it;\n * project the engine data, opaque to the rest of the code.\n *\n * A document from another engine, a future schema or a malformed one is\n * REJECTED on read rather than half-loaded: a template silently emptied and\n * then saved again would be lost.\n *\n * Dependency-free module: testable outside the browser.\n */\n\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from './constants.js';\n\n/**\n * @typedef {{\n * schemaVersion: number,\n * engine: string,\n * engineVersion: string,\n * builderVersion: string,\n * project: Record<string, any>,\n * }} EmailDocument\n */\n\n/**\n * Envelope migrations, from N to N+1. Empty while only one schema exists; the\n * first entry will be `1: (doc) => ({ ...doc, schemaVersion: 2 })`.\n *\n * @type {Record<number, (document: any) => any>}\n */\nconst MIGRATIONS = {};\n\nexport class EmailDocumentError extends Error {\n /** @param {string} message */\n constructor(message) {\n super(message);\n this.name = 'EmailDocumentError';\n this.status = 422;\n }\n}\n\nfunction isPlainObject(value) {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * @param {{engine: string, engineVersion: string, project: object}} parts\n * @returns {EmailDocument}\n */\nexport function createDocument({ engine, engineVersion, project }) {\n if (!engine) throw new EmailDocumentError('Document: missing engine');\n if (!isPlainObject(project)) throw new EmailDocumentError('Document: invalid engine project');\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine,\n engineVersion: String(engineVersion || ''),\n builderVersion: EMAIL_BUILDER_VERSION,\n project: /** @type {Record<string, any>} */ (project),\n };\n}\n\n/**\n * Reads a document (object or JSON string), migrates it to the current schema\n * and validates it.\n *\n * @param {unknown} raw\n * @returns {EmailDocument}\n * @throws {EmailDocumentError}\n */\nexport function readDocument(raw) {\n let value = raw;\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch {\n throw new EmailDocumentError('Unreadable document: invalid JSON');\n }\n }\n if (!isPlainObject(value)) throw new EmailDocumentError('Unreadable document: object expected');\n\n /** @type {any} */\n let document = { ...(/** @type {object} */ (value)) };\n const version = Number(document.schemaVersion);\n if (!Number.isInteger(version) || version < 1) {\n throw new EmailDocumentError('Unreadable document: missing schema version');\n }\n if (version > EMAIL_DOCUMENT_SCHEMA_VERSION) {\n throw new EmailDocumentError(\n `Document written by a newer version of the builder (schema ${version}) — ` +\n 'reload the page'\n );\n }\n for (let v = version; v < EMAIL_DOCUMENT_SCHEMA_VERSION; v += 1) {\n const migrate = MIGRATIONS[v];\n if (!migrate) throw new EmailDocumentError(`Document: no migration from schema ${v}`);\n document = migrate(document);\n }\n\n if (typeof document.engine !== 'string' || !document.engine) {\n throw new EmailDocumentError('Unreadable document: missing engine');\n }\n if (!isPlainObject(document.project)) {\n throw new EmailDocumentError('Unreadable document: missing engine project');\n }\n\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine: document.engine,\n engineVersion: String(document.engineVersion || ''),\n builderVersion: String(document.builderVersion || ''),\n project: document.project,\n };\n}\n\n/** @param {EmailDocument} document */\nexport function serializeDocument(document) {\n return JSON.stringify(readDocument(document));\n}\n","// @ts-check\n/**\n * Images UPLOADED in the builder — one rule shared by the browser (immediate\n * rejection, storage/client.js) and the server (storage/server/assetFile.js).\n *\n * PNG, JPEG, GIF: the three formats every email client displays. WebP and SVG\n * do not display in Outlook, and an SVG can carry script.\n * The server reads the type from the file BYTES; the browser, from `file.type`.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Maximum image size: beyond it, the email becomes slow to open. */\nexport const MAX_IMAGE_BYTES = 2 * 1024 * 1024;\n\n/** Accepted types → extension of the public key. */\nexport const IMAGE_TYPES = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif' };\n\n/** Public key: 32 hex characters of the hash + extension. */\nexport const ASSET_KEY_PATTERN = /^[a-f0-9]{32}\\.(png|jpg|gif)$/;\n\nconst SIGNATURES = [\n { type: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] },\n { type: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },\n { type: 'image/gif', bytes: [0x47, 0x49, 0x46, 0x38] },\n];\n\n/**\n * Image type detected from the file signature, or `null`.\n * @param {Uint8Array} bytes\n * @returns {{type: string, ext: string}|null}\n */\nexport function detectImage(bytes) {\n if (!bytes || bytes.length < 8) return null;\n const found = SIGNATURES.find((s) => s.bytes.every((b, i) => bytes[i] === b));\n return found ? { type: found.type, ext: IMAGE_TYPES[found.type] } : null;\n}\n\n/**\n * Immediate rejection of a file the server would refuse, or `null`.\n * @param {{type?: string, size?: number}} file\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function imageUploadError(file, { t = identity } = {}) {\n if (!(String(file?.type || '') in IMAGE_TYPES)) return t('Format rejected: PNG, JPEG or GIF expected');\n if (Number(file?.size || 0) > MAX_IMAGE_BYTES) {\n return t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) });\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|