best-email-builder 0.6.0 → 0.8.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 +59 -0
- package/README.md +127 -698
- package/dist/ai-server.js +116 -3
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +507 -172
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +8 -8
- package/dist/chunk-62HBUBBI.js +3866 -0
- package/dist/chunk-62HBUBBI.js.map +1 -0
- package/dist/{chunk-NC2YDZSP.js → chunk-6W3QUZR7.js} +8 -8
- package/dist/{chunk-W424KPCR.js → chunk-AXIM6E7T.js} +2 -2
- package/dist/{chunk-JU3OUO6X.js → chunk-DXWIVGHP.js} +3 -3
- package/dist/{chunk-QPAZLCPO.js → chunk-FAEVYSIF.js} +3 -3
- package/dist/{chunk-QPAZLCPO.js.map → chunk-FAEVYSIF.js.map} +1 -1
- package/dist/{chunk-4U7L2NYQ.js → chunk-G4U6PSF6.js} +2 -2
- package/dist/{chunk-5W4TUUPD.js → chunk-ITIG5NTJ.js} +4 -4
- package/dist/{chunk-CNJZLZRH.js → chunk-LUM7J5FF.js} +2 -2
- package/dist/{chunk-SAIW46IC.js → chunk-P7ABBLX6.js} +2 -2
- package/dist/chunk-SHHHYRPC.js +2829 -0
- package/dist/chunk-SHHHYRPC.js.map +1 -0
- package/dist/{chunk-XEFBPP5Y.js → chunk-WM2EEV4Q.js} +4 -4
- package/dist/index.js +10 -10
- package/dist/library.js +5 -5
- package/dist/server.js +8 -8
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +2 -1
- 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 +2 -1
- package/dist/types/ai/draft.d.ts +10 -4
- package/dist/types/ai/generator.d.ts +10 -4
- package/dist/types/ai/i18n/de.d.ts +2 -0
- package/dist/types/ai/i18n/es.d.ts +2 -0
- package/dist/types/ai/i18n/pt.d.ts +2 -0
- package/dist/types/ai/i18n/ru.d.ts +2 -0
- package/dist/types/ai/i18n/zh.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -1
- package/dist/types/ai/keys.d.ts +14 -1
- package/dist/types/ai/providers.d.ts +48 -5
- 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/i18n/de.d.ts +2 -0
- package/dist/types/i18n/es.d.ts +2 -0
- package/dist/types/i18n/pt.d.ts +2 -0
- package/dist/types/i18n/ru.d.ts +2 -0
- package/dist/types/i18n/zh.d.ts +2 -0
- package/dist/types/server/handler.d.ts +3 -0
- package/package.json +2 -2
- package/dist/chunk-JI36FNWE.js +0 -1361
- package/dist/chunk-JI36FNWE.js.map +0 -1
- package/dist/chunk-NZIQF7IW.js +0 -1830
- package/dist/chunk-NZIQF7IW.js.map +0 -1
- /package/dist/{chunk-NC2YDZSP.js.map → chunk-6W3QUZR7.js.map} +0 -0
- /package/dist/{chunk-W424KPCR.js.map → chunk-AXIM6E7T.js.map} +0 -0
- /package/dist/{chunk-JU3OUO6X.js.map → chunk-DXWIVGHP.js.map} +0 -0
- /package/dist/{chunk-4U7L2NYQ.js.map → chunk-G4U6PSF6.js.map} +0 -0
- /package/dist/{chunk-5W4TUUPD.js.map → chunk-ITIG5NTJ.js.map} +0 -0
- /package/dist/{chunk-CNJZLZRH.js.map → chunk-LUM7J5FF.js.map} +0 -0
- /package/dist/{chunk-SAIW46IC.js.map → chunk-P7ABBLX6.js.map} +0 -0
- /package/dist/{chunk-XEFBPP5Y.js.map → chunk-WM2EEV4Q.js.map} +0 -0
package/dist/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanRichText
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-P7ABBLX6.js";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
@@ -23,9 +23,11 @@ import {
|
|
|
23
23
|
createHttpCaller,
|
|
24
24
|
useSearchShortcut,
|
|
25
25
|
useUi
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-
|
|
26
|
+
} from "./chunk-DXWIVGHP.js";
|
|
27
|
+
import "./chunk-FAEVYSIF.js";
|
|
28
28
|
import {
|
|
29
|
+
AGENT_ERROR_CODES,
|
|
30
|
+
AGENT_PROTOCOL,
|
|
29
31
|
AI_MESSAGES,
|
|
30
32
|
AI_PROVIDERS,
|
|
31
33
|
DEFAULT_CLAUDE_MODEL,
|
|
@@ -37,25 +39,28 @@ import {
|
|
|
37
39
|
PURPOSE_CATEGORIES,
|
|
38
40
|
REWRITE_ACTIONS,
|
|
39
41
|
REWRITE_LANGUAGES,
|
|
42
|
+
agentEmailModel,
|
|
40
43
|
claudeEmailModel,
|
|
41
44
|
claudeProvider,
|
|
42
45
|
createEmailGenerator,
|
|
43
46
|
emailWords,
|
|
44
47
|
failureCode,
|
|
48
|
+
listProviderModels,
|
|
45
49
|
providerEmailModel,
|
|
46
50
|
purposesByCategory,
|
|
47
51
|
resolveProvider
|
|
48
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-SHHHYRPC.js";
|
|
49
53
|
import "./chunk-OWVXKMLI.js";
|
|
50
|
-
import "./chunk-
|
|
54
|
+
import "./chunk-AXIM6E7T.js";
|
|
51
55
|
import {
|
|
52
56
|
brandTemplate,
|
|
53
57
|
hasBrand
|
|
54
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-62HBUBBI.js";
|
|
55
59
|
|
|
56
60
|
// src/ai/keys.js
|
|
57
61
|
var STORAGE_PREFIX = "best-email-builder:ai-key:";
|
|
58
62
|
var CHOICE_SLOT = "best-email-builder:ai-provider";
|
|
63
|
+
var MODEL_PREFIX = "best-email-builder:ai-model:";
|
|
59
64
|
function browserStorage() {
|
|
60
65
|
try {
|
|
61
66
|
const storage = globalThis.localStorage;
|
|
@@ -70,10 +75,12 @@ var choiceOf = (provider) => Object.freeze({
|
|
|
70
75
|
model: provider.model ?? null,
|
|
71
76
|
keyUrl: provider.keyUrl,
|
|
72
77
|
keyHint: provider.keyHint,
|
|
73
|
-
free: provider.free
|
|
78
|
+
free: provider.free,
|
|
79
|
+
publicModels: Boolean(provider.publicModels)
|
|
74
80
|
});
|
|
75
|
-
function createKeyStore(providerOrList) {
|
|
76
|
-
const
|
|
81
|
+
function createKeyStore(providerOrList, { fetch: fetchImpl } = {}) {
|
|
82
|
+
const originals = Array.isArray(providerOrList) ? providerOrList : [providerOrList];
|
|
83
|
+
const providers = Object.freeze(originals.map(choiceOf));
|
|
77
84
|
if (!providers.length) throw new TypeError("createKeyStore: at least one provider");
|
|
78
85
|
const known = (id) => providers.some((provider) => provider.id === id);
|
|
79
86
|
const memory = /* @__PURE__ */ new Map();
|
|
@@ -104,6 +111,12 @@ function createKeyStore(providerOrList) {
|
|
|
104
111
|
const kept = read(CHOICE_SLOT);
|
|
105
112
|
let current = known(kept) ? kept : (providers.find((provider) => keyOf(provider.id)) || providers[0]).id;
|
|
106
113
|
const get = (id) => keyOf(id === void 0 ? current : String(id));
|
|
114
|
+
const providerId = (id) => {
|
|
115
|
+
if (id === void 0) return current;
|
|
116
|
+
if (!known(id)) throw new TypeError(`ai.key: no provider "${id}" \u2014 one of ${providers.map((provider) => provider.id).join(", ")}`);
|
|
117
|
+
return id;
|
|
118
|
+
};
|
|
119
|
+
const lists = /* @__PURE__ */ new Map();
|
|
107
120
|
const changed = () => {
|
|
108
121
|
snapshot = Object.freeze({ asking: Boolean(pending), refused, connected: Boolean(get()) });
|
|
109
122
|
for (const listener of [...listeners]) listener();
|
|
@@ -145,6 +158,33 @@ function createKeyStore(providerOrList) {
|
|
|
145
158
|
write(CHOICE_SLOT, id);
|
|
146
159
|
changed();
|
|
147
160
|
},
|
|
161
|
+
model: (id) => {
|
|
162
|
+
const choice = (
|
|
163
|
+
/** @type {ProviderChoice} */
|
|
164
|
+
providers.find((provider) => provider.id === providerId(id))
|
|
165
|
+
);
|
|
166
|
+
const kept2 = memory.get(MODEL_PREFIX + choice.id) ?? read(MODEL_PREFIX + choice.id);
|
|
167
|
+
return kept2 || choice.model || "";
|
|
168
|
+
},
|
|
169
|
+
chooseModel: (model, id) => {
|
|
170
|
+
if (typeof model !== "string" || !model.trim()) throw new TypeError("ai.key.chooseModel: a model id");
|
|
171
|
+
const choice = (
|
|
172
|
+
/** @type {ProviderChoice} */
|
|
173
|
+
providers.find((provider) => provider.id === providerId(id))
|
|
174
|
+
);
|
|
175
|
+
const value = model.trim() === choice.model ? "" : model.trim();
|
|
176
|
+
memory.set(MODEL_PREFIX + choice.id, value);
|
|
177
|
+
write(MODEL_PREFIX + choice.id, value);
|
|
178
|
+
changed();
|
|
179
|
+
},
|
|
180
|
+
listModels: async (id, { apiKey, signal } = {}) => {
|
|
181
|
+
const at = providers.findIndex((provider) => provider.id === providerId(id));
|
|
182
|
+
const key = String(apiKey ?? keyOf(providers[at].id)).trim();
|
|
183
|
+
const slot = `${providers[at].id}
|
|
184
|
+
${key}`;
|
|
185
|
+
if (!lists.has(slot)) lists.set(slot, await listProviderModels({ provider: originals[at], apiKey: key, fetch: fetchImpl, signal }));
|
|
186
|
+
return lists.get(slot);
|
|
187
|
+
},
|
|
148
188
|
request: () => {
|
|
149
189
|
if (!pending) {
|
|
150
190
|
let resolve = () => {
|
|
@@ -170,48 +210,305 @@ function createKeyStore(providerOrList) {
|
|
|
170
210
|
}
|
|
171
211
|
|
|
172
212
|
// src/ai/ConnectAi.jsx
|
|
173
|
-
import { useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
|
|
174
|
-
import { ArrowUpRight, Eye, EyeOff, Gift, KeyRound, ShieldCheck, TriangleAlert } from "lucide-react";
|
|
175
|
-
|
|
213
|
+
import { useEffect as useEffect2, useId as useId2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
|
|
214
|
+
import { ArrowUpRight, Eye, EyeOff, Gift as Gift2, KeyRound, ShieldCheck, TriangleAlert } from "lucide-react";
|
|
215
|
+
|
|
216
|
+
// src/ai/ModelPicker.jsx
|
|
217
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
218
|
+
import { Check, ChevronDown, Gift, LoaderCircle, Search } from "lucide-react";
|
|
219
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
220
|
+
function ModelPicker({ aiKey, provider, apiKey, delay = 0, value, onChange, open, onOpenChange }) {
|
|
221
|
+
const { t } = useUi();
|
|
222
|
+
const [list, setList] = useState(
|
|
223
|
+
/** @type {{status: 'loading'|'ready'|'needs-key'|'refused'|'failed', models: import('./providers.js').ModelChoice[]}} */
|
|
224
|
+
{ status: "loading", models: [] }
|
|
225
|
+
);
|
|
226
|
+
const [query, setQuery] = useState("");
|
|
227
|
+
const [freeOnly, setFreeOnly] = useState(false);
|
|
228
|
+
const [active, setActive] = useState(0);
|
|
229
|
+
const triggerRef = useRef(
|
|
230
|
+
/** @type {HTMLButtonElement|null} */
|
|
231
|
+
null
|
|
232
|
+
);
|
|
233
|
+
const listRef = useRef(
|
|
234
|
+
/** @type {HTMLDivElement|null} */
|
|
235
|
+
null
|
|
236
|
+
);
|
|
237
|
+
const labelId = useId();
|
|
238
|
+
const valueId = useId();
|
|
239
|
+
const listId = useId();
|
|
240
|
+
const hintId = useId();
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (!apiKey && !provider.publicModels) {
|
|
243
|
+
setList({ status: "needs-key", models: [] });
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
setList({ status: "loading", models: [] });
|
|
248
|
+
const timer = setTimeout(() => {
|
|
249
|
+
aiKey.listModels(provider.id, { apiKey, signal: controller.signal }).then(
|
|
250
|
+
(models2) => !controller.signal.aborted && setList({ status: "ready", models: models2 }),
|
|
251
|
+
(error) => !controller.signal.aborted && setList({ status: error?.code === "KEY_REFUSED" ? "refused" : "failed", models: [] })
|
|
252
|
+
);
|
|
253
|
+
}, delay);
|
|
254
|
+
return () => {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
controller.abort();
|
|
257
|
+
};
|
|
258
|
+
}, [aiKey, provider.id, provider.publicModels, apiKey, delay]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!open) return;
|
|
261
|
+
setQuery("");
|
|
262
|
+
setFreeOnly(false);
|
|
263
|
+
}, [open]);
|
|
264
|
+
const models = useMemo(() => {
|
|
265
|
+
const listed = new Map(list.models.map((model) => [model.id, model]));
|
|
266
|
+
const seen = /* @__PURE__ */ new Set();
|
|
267
|
+
const all = [];
|
|
268
|
+
const add = (model) => {
|
|
269
|
+
if (model.id && !seen.has(model.id)) seen.add(model.id) && all.push(model);
|
|
270
|
+
};
|
|
271
|
+
for (const id of [provider.model, value]) if (id) add(listed.get(id) || { id, name: id, free: false });
|
|
272
|
+
list.models.forEach(add);
|
|
273
|
+
return all;
|
|
274
|
+
}, [list.models, provider.model, value]);
|
|
275
|
+
const hasFree = list.models.some((model) => model.free);
|
|
276
|
+
const typed = query.trim();
|
|
277
|
+
const matches = useMemo(() => {
|
|
278
|
+
const words = typed.toLowerCase().split(/\s+/).filter(Boolean);
|
|
279
|
+
return models.filter((model) => (!freeOnly || model.free) && words.every((word) => `${model.id} ${model.name}`.toLowerCase().includes(word)));
|
|
280
|
+
}, [models, typed, freeOnly]);
|
|
281
|
+
const custom = typed && !/\s/.test(typed) && !models.some((model) => model.id === typed) ? typed : "";
|
|
282
|
+
const count = matches.length + (custom ? 1 : 0);
|
|
283
|
+
const current = models.find((model) => model.id === value);
|
|
284
|
+
useEffect(() => setActive(Math.max(0, matches.findIndex((model) => model.id === value))), [matches, value]);
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
if (open) listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
|
|
287
|
+
}, [active, open]);
|
|
288
|
+
const close = () => {
|
|
289
|
+
triggerRef.current?.focus();
|
|
290
|
+
onOpenChange(false);
|
|
291
|
+
};
|
|
292
|
+
const pick = (id) => {
|
|
293
|
+
onChange(id);
|
|
294
|
+
close();
|
|
295
|
+
};
|
|
296
|
+
const onKeyDown = (event) => {
|
|
297
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
setActive((at) => count ? (at + (event.key === "ArrowDown" ? 1 : -1) + count) % count : 0);
|
|
300
|
+
} else if (event.key === "Enter") {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
const id = active < matches.length ? matches[active]?.id : custom;
|
|
303
|
+
if (id) pick(id);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const optionId = (at) => `${listId}-${at}`;
|
|
307
|
+
const badges = (model) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
308
|
+
model.free && /* @__PURE__ */ jsxs("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", children: [
|
|
309
|
+
/* @__PURE__ */ jsx(Gift, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
310
|
+
t("Free")
|
|
311
|
+
] }),
|
|
312
|
+
model.id === provider.model && /* @__PURE__ */ jsx("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", children: t("Default") })
|
|
313
|
+
] });
|
|
314
|
+
const status = {
|
|
315
|
+
loading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
316
|
+
/* @__PURE__ */ jsx(LoaderCircle, { className: "eb:size-3 eb:shrink-0 eb:animate-spin", "aria-hidden": "true" }),
|
|
317
|
+
t("Loading the models of {provider}\u2026", { provider: provider.name })
|
|
318
|
+
] }),
|
|
319
|
+
"needs-key": t("Paste the key to see all the models of {provider}.", { provider: provider.name }),
|
|
320
|
+
refused: /* @__PURE__ */ jsx("span", { className: "eb:text-warning", children: t("{provider} refused this key: its models could not be listed.", { provider: provider.name }) }),
|
|
321
|
+
failed: t("The list of models could not be loaded. The default model works, or type the id of another."),
|
|
322
|
+
ready: list.models.length ? hasFree ? t("{count} models, the free ones first.", { count: list.models.length }) : t("{count} models.", { count: list.models.length }) : t("{provider} lists no models here: type the id of the one you want.", { provider: provider.name })
|
|
323
|
+
}[list.status];
|
|
324
|
+
return /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:flex-col eb:gap-1.5", "data-testid": "eb-ai-connect-model-field", children: [
|
|
325
|
+
/* @__PURE__ */ jsx("span", { id: labelId, className: "eb:text-[12.5px] eb:leading-none eb:font-medium eb:text-foreground", children: t("Model") }),
|
|
326
|
+
/* @__PURE__ */ jsxs(
|
|
327
|
+
"button",
|
|
328
|
+
{
|
|
329
|
+
ref: triggerRef,
|
|
330
|
+
type: "button",
|
|
331
|
+
"aria-haspopup": "listbox",
|
|
332
|
+
"aria-expanded": open,
|
|
333
|
+
"aria-labelledby": `${labelId} ${valueId}`,
|
|
334
|
+
"aria-describedby": hintId,
|
|
335
|
+
onClick: () => onOpenChange(!open),
|
|
336
|
+
className: cn(
|
|
337
|
+
"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",
|
|
338
|
+
open && "eb:border-ring"
|
|
339
|
+
),
|
|
340
|
+
"data-testid": "eb-ai-connect-model",
|
|
341
|
+
children: [
|
|
342
|
+
/* @__PURE__ */ jsx("span", { id: valueId, className: "eb:min-w-0 eb:truncate eb:font-mono eb:text-[12.5px] eb:text-foreground", children: value || t("Choose a model") }),
|
|
343
|
+
current && badges(current),
|
|
344
|
+
/* @__PURE__ */ jsx(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" })
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
),
|
|
348
|
+
open && /* @__PURE__ */ jsxs(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
className: "eb:flex eb:flex-col eb:overflow-hidden eb:rounded-md eb:border eb:border-border eb:bg-background",
|
|
352
|
+
onKeyDown: (event) => event.key === "Escape" && close(),
|
|
353
|
+
"data-eb-models": "",
|
|
354
|
+
"data-testid": "eb-ai-connect-models",
|
|
355
|
+
children: [
|
|
356
|
+
/* @__PURE__ */ jsxs("div", { className: "eb:flex eb:items-center eb:gap-2 eb:border-b eb:border-border eb:p-2", children: [
|
|
357
|
+
/* @__PURE__ */ jsxs("div", { className: "eb:relative eb:min-w-0 eb:flex-1", children: [
|
|
358
|
+
/* @__PURE__ */ jsx(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" }),
|
|
359
|
+
/* @__PURE__ */ jsx(
|
|
360
|
+
"input",
|
|
361
|
+
{
|
|
362
|
+
autoFocus: true,
|
|
363
|
+
role: "combobox",
|
|
364
|
+
"aria-expanded": "true",
|
|
365
|
+
"aria-controls": listId,
|
|
366
|
+
"aria-autocomplete": "list",
|
|
367
|
+
"aria-activedescendant": count ? optionId(active) : void 0,
|
|
368
|
+
"aria-label": t("Search the models"),
|
|
369
|
+
value: query,
|
|
370
|
+
onChange: (event) => setQuery(event.target.value),
|
|
371
|
+
onKeyDown,
|
|
372
|
+
placeholder: t("Search the models\u2026"),
|
|
373
|
+
autoComplete: "off",
|
|
374
|
+
autoCapitalize: "off",
|
|
375
|
+
autoCorrect: "off",
|
|
376
|
+
spellCheck: false,
|
|
377
|
+
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",
|
|
378
|
+
"data-testid": "eb-ai-connect-model-search"
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
] }),
|
|
382
|
+
hasFree && /* @__PURE__ */ jsxs(
|
|
383
|
+
"button",
|
|
384
|
+
{
|
|
385
|
+
type: "button",
|
|
386
|
+
"aria-pressed": freeOnly,
|
|
387
|
+
onClick: () => setFreeOnly((on) => !on),
|
|
388
|
+
className: cn(
|
|
389
|
+
"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",
|
|
390
|
+
freeOnly ? "eb:border-success/40 eb:bg-success/10 eb:text-success" : "eb:border-border eb:text-foreground-lighter eb:hover:bg-muted"
|
|
391
|
+
),
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsx(Gift, { className: "eb:size-3.5", "aria-hidden": "true" }),
|
|
394
|
+
t("Free only")
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
] }),
|
|
399
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, id: listId, role: "listbox", "aria-labelledby": labelId, className: "eb:max-h-56 eb:overflow-y-auto eb:p-1", children: [
|
|
400
|
+
matches.map((model, at) => /* @__PURE__ */ jsxs(
|
|
401
|
+
"button",
|
|
402
|
+
{
|
|
403
|
+
id: optionId(at),
|
|
404
|
+
type: "button",
|
|
405
|
+
role: "option",
|
|
406
|
+
tabIndex: -1,
|
|
407
|
+
"aria-selected": model.id === value,
|
|
408
|
+
"data-active": at === active,
|
|
409
|
+
"data-model": model.id,
|
|
410
|
+
onClick: () => pick(model.id),
|
|
411
|
+
onMouseMove: () => at !== active && setActive(at),
|
|
412
|
+
className: cn(
|
|
413
|
+
"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",
|
|
414
|
+
at === active ? "eb:bg-accent eb:text-accent-foreground" : ""
|
|
415
|
+
),
|
|
416
|
+
children: [
|
|
417
|
+
/* @__PURE__ */ jsx(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" }),
|
|
418
|
+
/* @__PURE__ */ jsxs("span", { className: "eb:flex eb:min-w-0 eb:flex-1 eb:flex-col eb:gap-0.5", children: [
|
|
419
|
+
/* @__PURE__ */ jsxs("span", { className: "eb:flex eb:min-w-0 eb:items-center eb:gap-1.5", children: [
|
|
420
|
+
/* @__PURE__ */ jsx("span", { className: "eb:truncate eb:text-[13px] eb:text-foreground", children: model.name }),
|
|
421
|
+
badges(model)
|
|
422
|
+
] }),
|
|
423
|
+
model.name !== model.id && /* @__PURE__ */ jsx("span", { className: "eb:truncate eb:font-mono eb:text-[10.5px] eb:text-foreground-muted", children: model.id })
|
|
424
|
+
] })
|
|
425
|
+
]
|
|
426
|
+
},
|
|
427
|
+
model.id
|
|
428
|
+
)),
|
|
429
|
+
custom && /* @__PURE__ */ jsxs(
|
|
430
|
+
"button",
|
|
431
|
+
{
|
|
432
|
+
id: optionId(matches.length),
|
|
433
|
+
type: "button",
|
|
434
|
+
role: "option",
|
|
435
|
+
tabIndex: -1,
|
|
436
|
+
"aria-selected": false,
|
|
437
|
+
"data-active": active === matches.length,
|
|
438
|
+
onClick: () => pick(custom),
|
|
439
|
+
onMouseMove: () => active !== matches.length && setActive(matches.length),
|
|
440
|
+
className: cn(
|
|
441
|
+
"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",
|
|
442
|
+
active === matches.length ? "eb:bg-accent" : ""
|
|
443
|
+
),
|
|
444
|
+
"data-testid": "eb-ai-connect-model-custom",
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ jsx("span", { className: "eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
447
|
+
/* @__PURE__ */ jsx("span", { className: "eb:min-w-0 eb:truncate", children: t("Use \u201C{model}\u201D", { model: custom }) })
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
),
|
|
451
|
+
!count && /* @__PURE__ */ jsx("p", { className: "eb:px-2 eb:py-3 eb:text-center eb:text-[12px] eb:text-foreground-muted", children: t("No model matches.") })
|
|
452
|
+
] })
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsx("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", children: status })
|
|
457
|
+
] });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/ai/ConnectAi.jsx
|
|
461
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
462
|
+
var LISTABLE_KEY = 20;
|
|
176
463
|
function ConnectAi({ aiKey }) {
|
|
177
464
|
const { t } = useUi();
|
|
178
465
|
const state = useSyncExternalStore(aiKey.subscribe, aiKey.state, aiKey.state);
|
|
179
|
-
const [value, setValue] =
|
|
180
|
-
const [shown, setShown] =
|
|
181
|
-
const [chosenId, setChosenId] =
|
|
182
|
-
const
|
|
466
|
+
const [value, setValue] = useState2("");
|
|
467
|
+
const [shown, setShown] = useState2(false);
|
|
468
|
+
const [chosenId, setChosenId] = useState2(aiKey.provider.id);
|
|
469
|
+
const [picks, setPicks] = useState2(
|
|
470
|
+
/** @type {Record<string, string>} */
|
|
471
|
+
{}
|
|
472
|
+
);
|
|
473
|
+
const [modelsOpen, setModelsOpen] = useState2(false);
|
|
474
|
+
const inputRef = useRef2(
|
|
183
475
|
/** @type {HTMLInputElement|null} */
|
|
184
476
|
null
|
|
185
477
|
);
|
|
186
|
-
const choicesRef =
|
|
478
|
+
const choicesRef = useRef2(
|
|
187
479
|
/** @type {HTMLDivElement|null} */
|
|
188
480
|
null
|
|
189
481
|
);
|
|
190
|
-
const inputId =
|
|
191
|
-
const hintId =
|
|
482
|
+
const inputId = useId2();
|
|
483
|
+
const hintId = useId2();
|
|
192
484
|
const choices = aiKey.providers;
|
|
193
485
|
const chosen = choices.find((provider) => provider.id === chosenId) || aiKey.provider;
|
|
194
486
|
const { name, keyUrl, keyHint, free } = chosen;
|
|
195
|
-
|
|
487
|
+
useEffect2(() => {
|
|
196
488
|
if (!state.asking) return;
|
|
197
489
|
setChosenId(aiKey.provider.id);
|
|
198
490
|
setValue("");
|
|
199
491
|
setShown(false);
|
|
492
|
+
setPicks({});
|
|
493
|
+
setModelsOpen(false);
|
|
200
494
|
}, [state.asking, aiKey]);
|
|
201
495
|
const key = value.trim();
|
|
202
496
|
const saved = aiKey.get(chosen.id);
|
|
203
497
|
const unusual = Boolean(key && keyHint && !key.startsWith(keyHint));
|
|
204
498
|
const refused = state.refused && chosen.id === aiKey.provider.id;
|
|
499
|
+
const modelFor = (id) => picks[id] ?? aiKey.model(id);
|
|
205
500
|
const submit = (event) => {
|
|
206
501
|
event.preventDefault();
|
|
207
502
|
const answer = key || saved;
|
|
208
503
|
if (!answer) return;
|
|
209
504
|
if (chosen.id !== aiKey.provider.id) aiKey.choose(chosen.id);
|
|
505
|
+
if (picks[chosen.id] && picks[chosen.id] !== aiKey.model(chosen.id)) aiKey.chooseModel(picks[chosen.id], chosen.id);
|
|
210
506
|
aiKey.answer(answer);
|
|
211
507
|
};
|
|
212
508
|
const choose = (id) => {
|
|
213
509
|
setChosenId(id);
|
|
214
510
|
setValue("");
|
|
511
|
+
setModelsOpen(false);
|
|
215
512
|
};
|
|
216
513
|
const onChoicesKeyDown = (event) => {
|
|
217
514
|
const at = choices.findIndex((provider) => provider.id === chosen.id);
|
|
@@ -222,25 +519,30 @@ function ConnectAi({ aiKey }) {
|
|
|
222
519
|
choose(choices[to].id);
|
|
223
520
|
choicesRef.current?.querySelectorAll('[role="radio"]')[to]?.focus();
|
|
224
521
|
};
|
|
225
|
-
return /* @__PURE__ */
|
|
522
|
+
return /* @__PURE__ */ jsx2(Dialog, { open: state.asking, onOpenChange: (open) => !open && aiKey.answer(null), children: /* @__PURE__ */ jsx2(
|
|
226
523
|
DialogContent,
|
|
227
524
|
{
|
|
228
|
-
className: cn("eb:gap-0 eb:overflow-
|
|
525
|
+
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"),
|
|
229
526
|
"data-testid": "eb-ai-connect",
|
|
230
527
|
onOpenAutoFocus: (event) => {
|
|
231
528
|
event.preventDefault();
|
|
232
529
|
inputRef.current?.focus();
|
|
233
530
|
},
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
531
|
+
onEscapeKeyDown: (event) => {
|
|
532
|
+
if (!modelsOpen) return;
|
|
533
|
+
event.preventDefault();
|
|
534
|
+
if (!(event.target instanceof Element && event.target.closest("[data-eb-models]"))) setModelsOpen(false);
|
|
535
|
+
},
|
|
536
|
+
children: /* @__PURE__ */ jsxs2("form", { onSubmit: submit, className: "eb:min-w-0", children: [
|
|
537
|
+
/* @__PURE__ */ jsxs2("div", { className: "eb:flex eb:flex-col eb:gap-5 eb:px-6 eb:pt-6 eb:pb-5", children: [
|
|
538
|
+
/* @__PURE__ */ jsxs2(DialogHeader, { className: "eb:flex-row eb:items-start eb:gap-3.5 eb:pr-6", children: [
|
|
539
|
+
/* @__PURE__ */ jsx2("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", children: /* @__PURE__ */ jsx2(KeyRound, { className: "eb:size-5", "aria-hidden": "true" }) }),
|
|
540
|
+
/* @__PURE__ */ jsxs2("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
541
|
+
/* @__PURE__ */ jsx2(DialogTitle, { children: t("Connect {provider}", { provider: name }) }),
|
|
542
|
+
/* @__PURE__ */ jsx2(DialogDescription, { className: "eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter", children: t("The AI writes with your own {provider} key. Connect it once: this browser remembers it.", { provider: name }) })
|
|
241
543
|
] })
|
|
242
544
|
] }),
|
|
243
|
-
choices.length > 1 && /* @__PURE__ */
|
|
545
|
+
choices.length > 1 && /* @__PURE__ */ jsx2(
|
|
244
546
|
"div",
|
|
245
547
|
{
|
|
246
548
|
ref: choicesRef,
|
|
@@ -251,7 +553,7 @@ function ConnectAi({ aiKey }) {
|
|
|
251
553
|
"data-testid": "eb-ai-connect-providers",
|
|
252
554
|
children: choices.map((provider) => {
|
|
253
555
|
const checked = provider.id === chosen.id;
|
|
254
|
-
return /* @__PURE__ */
|
|
556
|
+
return /* @__PURE__ */ jsxs2(
|
|
255
557
|
"button",
|
|
256
558
|
{
|
|
257
559
|
type: "button",
|
|
@@ -264,12 +566,12 @@ function ConnectAi({ aiKey }) {
|
|
|
264
566
|
checked ? "eb:border-primary eb:bg-primary/10" : "eb:border-border eb:hover:bg-muted"
|
|
265
567
|
),
|
|
266
568
|
children: [
|
|
267
|
-
/* @__PURE__ */
|
|
268
|
-
/* @__PURE__ */
|
|
269
|
-
provider.free && /* @__PURE__ */
|
|
270
|
-
aiKey.get(provider.id) && /* @__PURE__ */
|
|
569
|
+
/* @__PURE__ */ jsxs2("span", { className: "eb:flex eb:items-center eb:gap-1.5 eb:text-[13px] eb:leading-tight eb:font-medium eb:text-foreground", children: [
|
|
570
|
+
/* @__PURE__ */ jsx2("span", { className: "eb:truncate", children: provider.name }),
|
|
571
|
+
provider.free && /* @__PURE__ */ jsx2(Gift2, { className: "eb:size-3 eb:shrink-0 eb:text-success", "aria-label": t(provider.free) }),
|
|
572
|
+
aiKey.get(provider.id) && /* @__PURE__ */ jsx2("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") })
|
|
271
573
|
] }),
|
|
272
|
-
provider.
|
|
574
|
+
modelFor(provider.id) && /* @__PURE__ */ jsx2("span", { className: "eb:truncate eb:font-mono eb:text-[10.5px] eb:text-foreground-muted", title: modelFor(provider.id), children: modelFor(provider.id) })
|
|
273
575
|
]
|
|
274
576
|
},
|
|
275
577
|
provider.id
|
|
@@ -277,24 +579,37 @@ function ConnectAi({ aiKey }) {
|
|
|
277
579
|
})
|
|
278
580
|
}
|
|
279
581
|
),
|
|
280
|
-
refused && /* @__PURE__ */
|
|
281
|
-
/* @__PURE__ */
|
|
582
|
+
refused && /* @__PURE__ */ jsxs2("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", children: [
|
|
583
|
+
/* @__PURE__ */ jsx2(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
282
584
|
t("{provider} refused the last key. Paste another one.", { provider: name })
|
|
283
585
|
] }),
|
|
284
|
-
(keyUrl || free) && /* @__PURE__ */
|
|
285
|
-
keyUrl && /* @__PURE__ */
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
586
|
+
(keyUrl || free) && /* @__PURE__ */ jsxs2("div", { className: "eb:flex eb:flex-wrap eb:items-center eb:gap-x-3 eb:gap-y-2", children: [
|
|
587
|
+
keyUrl && /* @__PURE__ */ jsx2(Button, { asChild: true, variant: "outline", size: "sm-compact", children: /* @__PURE__ */ jsxs2(
|
|
588
|
+
"a",
|
|
589
|
+
{
|
|
590
|
+
href: keyUrl,
|
|
591
|
+
target: "_blank",
|
|
592
|
+
rel: "noopener noreferrer",
|
|
593
|
+
onClick: (event) => {
|
|
594
|
+
event.preventDefault();
|
|
595
|
+
window.open(keyUrl, "_blank", "noopener,noreferrer");
|
|
596
|
+
},
|
|
597
|
+
"data-testid": "eb-ai-connect-get-key",
|
|
598
|
+
children: [
|
|
599
|
+
t("Get a key"),
|
|
600
|
+
/* @__PURE__ */ jsx2(ArrowUpRight, { "aria-hidden": "true" })
|
|
601
|
+
]
|
|
602
|
+
}
|
|
603
|
+
) }),
|
|
604
|
+
free && /* @__PURE__ */ jsxs2("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", children: [
|
|
605
|
+
/* @__PURE__ */ jsx2(Gift2, { className: "eb:size-3.5", "aria-hidden": "true" }),
|
|
291
606
|
t(free)
|
|
292
607
|
] })
|
|
293
608
|
] }),
|
|
294
|
-
/* @__PURE__ */
|
|
295
|
-
/* @__PURE__ */
|
|
296
|
-
/* @__PURE__ */
|
|
297
|
-
/* @__PURE__ */
|
|
609
|
+
/* @__PURE__ */ jsxs2("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
|
|
610
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: inputId, children: t("API key") }),
|
|
611
|
+
/* @__PURE__ */ jsxs2("div", { className: "eb:relative", children: [
|
|
612
|
+
/* @__PURE__ */ jsx2(
|
|
298
613
|
Input,
|
|
299
614
|
{
|
|
300
615
|
ref: inputRef,
|
|
@@ -312,7 +627,7 @@ function ConnectAi({ aiKey }) {
|
|
|
312
627
|
"data-testid": "eb-ai-connect-key"
|
|
313
628
|
}
|
|
314
629
|
),
|
|
315
|
-
/* @__PURE__ */
|
|
630
|
+
/* @__PURE__ */ jsx2(
|
|
316
631
|
"button",
|
|
317
632
|
{
|
|
318
633
|
type: "button",
|
|
@@ -320,20 +635,33 @@ function ConnectAi({ aiKey }) {
|
|
|
320
635
|
onClick: () => setShown((current) => !current),
|
|
321
636
|
"aria-label": shown ? t("Hide the key") : t("Show the key"),
|
|
322
637
|
"aria-pressed": shown,
|
|
323
|
-
children: shown ? /* @__PURE__ */
|
|
638
|
+
children: shown ? /* @__PURE__ */ jsx2(EyeOff, { className: "eb:size-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx2(Eye, { className: "eb:size-4", "aria-hidden": "true" })
|
|
324
639
|
}
|
|
325
640
|
)
|
|
326
641
|
] }),
|
|
327
|
-
/* @__PURE__ */
|
|
328
|
-
] })
|
|
642
|
+
/* @__PURE__ */ jsx2("p", { id: hintId, className: "eb:min-h-4 eb:text-[11.5px] eb:text-foreground-muted", children: unusual ? /* @__PURE__ */ jsx2("span", { className: "eb:text-warning", children: t("{provider} keys usually start with {prefix}.", { provider: name, prefix: keyHint }) }) : saved && !key ? t("A key for {provider} is saved in this browser: Connect uses it, or paste another one.", { provider: name }) : keyHint ? t("{provider} keys start with {prefix}.", { provider: name, prefix: keyHint }) : null })
|
|
643
|
+
] }),
|
|
644
|
+
/* @__PURE__ */ jsx2(
|
|
645
|
+
ModelPicker,
|
|
646
|
+
{
|
|
647
|
+
aiKey,
|
|
648
|
+
provider: chosen,
|
|
649
|
+
apiKey: key.length >= LISTABLE_KEY ? key : saved,
|
|
650
|
+
delay: key.length >= LISTABLE_KEY ? 350 : 0,
|
|
651
|
+
value: modelFor(chosen.id),
|
|
652
|
+
onChange: (model) => setPicks((current) => ({ ...current, [chosen.id]: model })),
|
|
653
|
+
open: modelsOpen,
|
|
654
|
+
onOpenChange: setModelsOpen
|
|
655
|
+
}
|
|
656
|
+
)
|
|
329
657
|
] }),
|
|
330
|
-
/* @__PURE__ */
|
|
331
|
-
/* @__PURE__ */
|
|
332
|
-
/* @__PURE__ */
|
|
658
|
+
/* @__PURE__ */ jsxs2("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", children: [
|
|
659
|
+
/* @__PURE__ */ jsx2(ShieldCheck, { className: "eb:mt-0.5 eb:size-3.5 eb:shrink-0 eb:text-foreground-muted", "aria-hidden": "true" }),
|
|
660
|
+
/* @__PURE__ */ jsx2("span", { children: t("The key stays in this browser and is sent only to {provider}.", { provider: name }) })
|
|
333
661
|
] }),
|
|
334
|
-
/* @__PURE__ */
|
|
335
|
-
/* @__PURE__ */
|
|
336
|
-
/* @__PURE__ */
|
|
662
|
+
/* @__PURE__ */ jsxs2(DialogFooter, { className: "eb:border-t eb:border-border eb:px-6 eb:py-4", children: [
|
|
663
|
+
/* @__PURE__ */ jsx2(Button, { type: "button", variant: "ghost", onClick: () => aiKey.answer(null), children: t("Cancel") }),
|
|
664
|
+
/* @__PURE__ */ jsx2(Button, { type: "submit", disabled: !key && !saved, "data-testid": "eb-ai-connect-submit", children: t("Connect") })
|
|
337
665
|
] })
|
|
338
666
|
] })
|
|
339
667
|
}
|
|
@@ -341,8 +669,8 @@ function ConnectAi({ aiKey }) {
|
|
|
341
669
|
}
|
|
342
670
|
|
|
343
671
|
// src/ai/GenerateButton.jsx
|
|
344
|
-
import { useEffect as
|
|
345
|
-
import { Check, Loader2, Search, Sparkles, TriangleAlert as TriangleAlert2 } from "lucide-react";
|
|
672
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
673
|
+
import { Check as Check2, Loader2, Search as Search2, Sparkles, TriangleAlert as TriangleAlert2 } from "lucide-react";
|
|
346
674
|
|
|
347
675
|
// src/ai/chip.js
|
|
348
676
|
var chipClassName = (selected = false) => cn(
|
|
@@ -351,39 +679,39 @@ var chipClassName = (selected = false) => cn(
|
|
|
351
679
|
);
|
|
352
680
|
|
|
353
681
|
// src/ai/GenerateButton.jsx
|
|
354
|
-
import { Fragment, jsx as
|
|
682
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
355
683
|
var fold = (text) => text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
356
684
|
function GenerateButton({ context, generate: generateEmail }) {
|
|
357
685
|
const { editor, ready, isDirty, markDirty, template, setMeta, variables, sender, brand } = context;
|
|
358
686
|
const { t, locale, notify } = useUi();
|
|
359
|
-
const [open, setOpen] =
|
|
360
|
-
const [purpose, setPurpose] =
|
|
687
|
+
const [open, setOpen] = useState3(false);
|
|
688
|
+
const [purpose, setPurpose] = useState3(
|
|
361
689
|
/** @type {string|null} */
|
|
362
690
|
null
|
|
363
691
|
);
|
|
364
|
-
const [details, setDetails] =
|
|
365
|
-
const [query, setQuery] =
|
|
366
|
-
const [busy, setBusy] =
|
|
367
|
-
const [error, setError] =
|
|
368
|
-
const [seconds, setSeconds] =
|
|
369
|
-
const [dirty, setDirty] =
|
|
370
|
-
const abortRef =
|
|
692
|
+
const [details, setDetails] = useState3("");
|
|
693
|
+
const [query, setQuery] = useState3("");
|
|
694
|
+
const [busy, setBusy] = useState3(false);
|
|
695
|
+
const [error, setError] = useState3("");
|
|
696
|
+
const [seconds, setSeconds] = useState3(0);
|
|
697
|
+
const [dirty, setDirty] = useState3(false);
|
|
698
|
+
const abortRef = useRef3(
|
|
371
699
|
/** @type {AbortController|null} */
|
|
372
700
|
null
|
|
373
701
|
);
|
|
374
|
-
const searchRef =
|
|
702
|
+
const searchRef = useRef3(
|
|
375
703
|
/** @type {HTMLInputElement|null} */
|
|
376
704
|
null
|
|
377
705
|
);
|
|
378
706
|
useSearchShortcut(searchRef);
|
|
379
|
-
|
|
380
|
-
|
|
707
|
+
useEffect3(() => () => abortRef.current?.abort(), []);
|
|
708
|
+
useEffect3(() => {
|
|
381
709
|
if (!busy) return void 0;
|
|
382
710
|
setSeconds(0);
|
|
383
711
|
const timer = setInterval(() => setSeconds((s) => s + 1), 1e3);
|
|
384
712
|
return () => clearInterval(timer);
|
|
385
713
|
}, [busy]);
|
|
386
|
-
const groups =
|
|
714
|
+
const groups = useMemo2(() => {
|
|
387
715
|
const words = fold(query.trim()).split(/\s+/).filter(Boolean);
|
|
388
716
|
return purposesByCategory().map((group) => ({
|
|
389
717
|
...group,
|
|
@@ -444,12 +772,12 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
444
772
|
setBusy(false);
|
|
445
773
|
};
|
|
446
774
|
const canGenerate = Boolean(purpose || details.trim());
|
|
447
|
-
return /* @__PURE__ */
|
|
448
|
-
/* @__PURE__ */
|
|
449
|
-
/* @__PURE__ */
|
|
775
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
776
|
+
/* @__PURE__ */ jsxs3(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, children: [
|
|
777
|
+
/* @__PURE__ */ jsx3(Sparkles, {}),
|
|
450
778
|
t("Generate")
|
|
451
779
|
] }),
|
|
452
|
-
/* @__PURE__ */
|
|
780
|
+
/* @__PURE__ */ jsx3(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs3(
|
|
453
781
|
DialogContent,
|
|
454
782
|
{
|
|
455
783
|
className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl",
|
|
@@ -459,13 +787,13 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
459
787
|
searchRef.current?.focus();
|
|
460
788
|
},
|
|
461
789
|
children: [
|
|
462
|
-
/* @__PURE__ */
|
|
463
|
-
/* @__PURE__ */
|
|
464
|
-
/* @__PURE__ */
|
|
790
|
+
/* @__PURE__ */ jsxs3(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-3", children: [
|
|
791
|
+
/* @__PURE__ */ jsx3(DialogTitle, { children: t("Generate an email") }),
|
|
792
|
+
/* @__PURE__ */ jsx3(DialogDescription, { children: t("Pick what it is for. AI writes a complete email that opens here, ready to edit.") })
|
|
465
793
|
] }),
|
|
466
|
-
/* @__PURE__ */
|
|
467
|
-
/* @__PURE__ */
|
|
468
|
-
/* @__PURE__ */
|
|
794
|
+
/* @__PURE__ */ jsx3("div", { className: "eb:px-6 eb:pb-3", children: /* @__PURE__ */ jsxs3("div", { className: "eb:relative", children: [
|
|
795
|
+
/* @__PURE__ */ jsx3(Search2, { 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" }),
|
|
796
|
+
/* @__PURE__ */ jsx3(
|
|
469
797
|
Input,
|
|
470
798
|
{
|
|
471
799
|
ref: searchRef,
|
|
@@ -479,16 +807,16 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
479
807
|
disabled: busy
|
|
480
808
|
}
|
|
481
809
|
),
|
|
482
|
-
/* @__PURE__ */
|
|
810
|
+
/* @__PURE__ */ jsx3(SearchKeyHint, { className: "eb:absolute eb:top-1/2 eb:right-2 eb:-translate-y-1/2" })
|
|
483
811
|
] }) }),
|
|
484
|
-
/* @__PURE__ */
|
|
485
|
-
/* @__PURE__ */
|
|
486
|
-
groups.length === 0 && /* @__PURE__ */
|
|
487
|
-
/* @__PURE__ */
|
|
488
|
-
/* @__PURE__ */
|
|
489
|
-
/* @__PURE__ */
|
|
812
|
+
/* @__PURE__ */ jsx3("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", children: /* @__PURE__ */ jsxs3("fieldset", { disabled: busy, className: "eb:m-0 eb:min-w-0 eb:border-0 eb:p-0", children: [
|
|
813
|
+
/* @__PURE__ */ jsx3("legend", { className: "eb:sr-only", children: t("What is the email for?") }),
|
|
814
|
+
groups.length === 0 && /* @__PURE__ */ jsx3("p", { className: "eb:py-6 eb:text-center eb:text-[13px] eb:text-foreground-muted", children: t("Nothing matches. Describe your email in the details below.") }),
|
|
815
|
+
/* @__PURE__ */ jsx3("div", { className: "eb:flex eb:flex-col eb:gap-4", children: groups.map((group) => /* @__PURE__ */ jsxs3("section", { className: "eb:flex eb:flex-col eb:gap-2", "aria-label": t(group.label), children: [
|
|
816
|
+
/* @__PURE__ */ jsx3(SectionLabel, { children: t(group.label) }),
|
|
817
|
+
/* @__PURE__ */ jsx3("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: group.purposes.map((entry) => {
|
|
490
818
|
const selected = purpose === entry.id;
|
|
491
|
-
return /* @__PURE__ */
|
|
819
|
+
return /* @__PURE__ */ jsxs3(
|
|
492
820
|
"button",
|
|
493
821
|
{
|
|
494
822
|
type: "button",
|
|
@@ -497,7 +825,7 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
497
825
|
onClick: () => setPurpose(selected ? null : entry.id),
|
|
498
826
|
className: chipClassName(selected),
|
|
499
827
|
children: [
|
|
500
|
-
selected && /* @__PURE__ */
|
|
828
|
+
selected && /* @__PURE__ */ jsx3(Check2, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
501
829
|
t(entry.label)
|
|
502
830
|
]
|
|
503
831
|
},
|
|
@@ -506,8 +834,8 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
506
834
|
}) })
|
|
507
835
|
] }, group.id)) })
|
|
508
836
|
] }) }),
|
|
509
|
-
/* @__PURE__ */
|
|
510
|
-
/* @__PURE__ */
|
|
837
|
+
/* @__PURE__ */ jsxs3("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
|
|
838
|
+
/* @__PURE__ */ jsx3(
|
|
511
839
|
Textarea,
|
|
512
840
|
{
|
|
513
841
|
value: details,
|
|
@@ -522,15 +850,15 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
522
850
|
}
|
|
523
851
|
}
|
|
524
852
|
),
|
|
525
|
-
error ? /* @__PURE__ */
|
|
526
|
-
/* @__PURE__ */
|
|
853
|
+
error ? /* @__PURE__ */ jsxs3("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
854
|
+
/* @__PURE__ */ jsx3(TriangleAlert2, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
527
855
|
error
|
|
528
|
-
] }) : dirty && !busy && /* @__PURE__ */
|
|
529
|
-
/* @__PURE__ */
|
|
530
|
-
busy && /* @__PURE__ */
|
|
531
|
-
busy && /* @__PURE__ */
|
|
532
|
-
/* @__PURE__ */
|
|
533
|
-
busy ? /* @__PURE__ */
|
|
856
|
+
] }) : dirty && !busy && /* @__PURE__ */ jsx3("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The email being edited will be replaced, unsaved changes included.") }),
|
|
857
|
+
/* @__PURE__ */ jsxs3("div", { className: "eb:flex eb:items-center eb:justify-end eb:gap-2", children: [
|
|
858
|
+
busy && /* @__PURE__ */ jsx3("p", { className: "eb:mr-auto eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: t("Writing your email\u2026 {seconds} s", { seconds }) }),
|
|
859
|
+
busy && /* @__PURE__ */ jsx3(Button, { variant: "outline", size: "sm", onClick: cancel, children: t("Cancel") }),
|
|
860
|
+
/* @__PURE__ */ jsxs3(Button, { size: "sm", onClick: generate, disabled: !canGenerate || busy, "data-testid": "eb-generate-submit", children: [
|
|
861
|
+
busy ? /* @__PURE__ */ jsx3(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx3(Sparkles, {}),
|
|
534
862
|
t("Generate")
|
|
535
863
|
] })
|
|
536
864
|
] })
|
|
@@ -542,9 +870,9 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
542
870
|
}
|
|
543
871
|
|
|
544
872
|
// src/ai/RewriteText.jsx
|
|
545
|
-
import { useEffect as
|
|
546
|
-
import { ChevronDown, Languages, Loader2 as Loader22, Sparkles as Sparkles2, TriangleAlert as TriangleAlert3 } from "lucide-react";
|
|
547
|
-
import { jsx as
|
|
873
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
874
|
+
import { ChevronDown as ChevronDown2, Languages, Loader2 as Loader22, Sparkles as Sparkles2, TriangleAlert as TriangleAlert3 } from "lucide-react";
|
|
875
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
548
876
|
function nativeName(code) {
|
|
549
877
|
try {
|
|
550
878
|
const name = new Intl.DisplayNames([code], { type: "language" }).of(code) || code;
|
|
@@ -555,18 +883,18 @@ function nativeName(code) {
|
|
|
555
883
|
}
|
|
556
884
|
function RewriteText({ editor, selection, rewrite }) {
|
|
557
885
|
const { t, notify } = useUi();
|
|
558
|
-
const [running, setRunning] =
|
|
886
|
+
const [running, setRunning] = useState4(
|
|
559
887
|
/** @type {string|null} */
|
|
560
888
|
null
|
|
561
889
|
);
|
|
562
|
-
const [error, setError] =
|
|
563
|
-
const [instruction, setInstruction] =
|
|
564
|
-
const abortRef =
|
|
890
|
+
const [error, setError] = useState4("");
|
|
891
|
+
const [instruction, setInstruction] = useState4("");
|
|
892
|
+
const abortRef = useRef4(
|
|
565
893
|
/** @type {AbortController|null} */
|
|
566
894
|
null
|
|
567
895
|
);
|
|
568
896
|
const label = selection.tagName === "mj-button";
|
|
569
|
-
|
|
897
|
+
useEffect4(() => () => abortRef.current?.abort(), []);
|
|
570
898
|
const currentText = () => {
|
|
571
899
|
const element = editor.getElement(selection.id);
|
|
572
900
|
return (label ? element?.content : element?.html) ?? "";
|
|
@@ -600,27 +928,27 @@ function RewriteText({ editor, selection, rewrite }) {
|
|
|
600
928
|
setRunning(null);
|
|
601
929
|
};
|
|
602
930
|
const busy = Boolean(running);
|
|
603
|
-
const spinner = /* @__PURE__ */
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
/* @__PURE__ */
|
|
606
|
-
/* @__PURE__ */
|
|
931
|
+
const spinner = /* @__PURE__ */ jsx4(Loader22, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
932
|
+
return /* @__PURE__ */ jsxs4("section", { className: "eb:space-y-2.5", "aria-label": t("Rewrite with AI"), "data-testid": "eb-rewrite", children: [
|
|
933
|
+
/* @__PURE__ */ jsxs4(SectionLabel, { className: "eb:flex eb:items-center eb:gap-1.5", children: [
|
|
934
|
+
/* @__PURE__ */ jsx4(Sparkles2, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
607
935
|
t("Rewrite with AI")
|
|
608
936
|
] }),
|
|
609
|
-
/* @__PURE__ */
|
|
610
|
-
REWRITE_ACTIONS.map((action) => /* @__PURE__ */
|
|
937
|
+
/* @__PURE__ */ jsxs4("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: [
|
|
938
|
+
REWRITE_ACTIONS.map((action) => /* @__PURE__ */ jsxs4("button", { type: "button", className: chipClassName(), disabled: busy, onClick: () => run(action.id), children: [
|
|
611
939
|
running === action.id && spinner,
|
|
612
940
|
t(action.label)
|
|
613
941
|
] }, action.id)),
|
|
614
|
-
/* @__PURE__ */
|
|
615
|
-
/* @__PURE__ */
|
|
616
|
-
running === "translate" ? spinner : /* @__PURE__ */
|
|
942
|
+
/* @__PURE__ */ jsxs4(DropdownMenu, { children: [
|
|
943
|
+
/* @__PURE__ */ jsx4(DropdownMenuTrigger, { asChild: true, disabled: busy, children: /* @__PURE__ */ jsxs4("button", { type: "button", className: chipClassName(), children: [
|
|
944
|
+
running === "translate" ? spinner : /* @__PURE__ */ jsx4(Languages, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
617
945
|
t("Translate"),
|
|
618
|
-
/* @__PURE__ */
|
|
946
|
+
/* @__PURE__ */ jsx4(ChevronDown2, { className: "eb:size-3", "aria-hidden": "true" })
|
|
619
947
|
] }) }),
|
|
620
|
-
/* @__PURE__ */
|
|
948
|
+
/* @__PURE__ */ jsx4(DropdownMenuContent, { align: "start", "aria-label": t("Translate into"), children: REWRITE_LANGUAGES.map((code) => /* @__PURE__ */ jsx4(DropdownMenuItem, { lang: code, onSelect: () => run("translate", { targetLanguage: code }), children: nativeName(code) }, code)) })
|
|
621
949
|
] })
|
|
622
950
|
] }),
|
|
623
|
-
/* @__PURE__ */
|
|
951
|
+
/* @__PURE__ */ jsxs4(
|
|
624
952
|
"form",
|
|
625
953
|
{
|
|
626
954
|
className: "eb:flex eb:gap-1.5",
|
|
@@ -629,7 +957,7 @@ function RewriteText({ editor, selection, rewrite }) {
|
|
|
629
957
|
if (instruction.trim()) run("custom", { instruction: instruction.trim() });
|
|
630
958
|
},
|
|
631
959
|
children: [
|
|
632
|
-
/* @__PURE__ */
|
|
960
|
+
/* @__PURE__ */ jsx4(
|
|
633
961
|
Input,
|
|
634
962
|
{
|
|
635
963
|
value: instruction,
|
|
@@ -640,55 +968,55 @@ function RewriteText({ editor, selection, rewrite }) {
|
|
|
640
968
|
disabled: busy
|
|
641
969
|
}
|
|
642
970
|
),
|
|
643
|
-
/* @__PURE__ */
|
|
971
|
+
/* @__PURE__ */ jsx4(Button, { type: "submit", variant: "outline", size: "icon-sm", "aria-label": t("Rewrite"), disabled: busy || !instruction.trim(), children: running === "custom" ? /* @__PURE__ */ jsx4(Loader22, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx4(Sparkles2, {}) })
|
|
644
972
|
]
|
|
645
973
|
}
|
|
646
974
|
),
|
|
647
|
-
running && /* @__PURE__ */
|
|
975
|
+
running && /* @__PURE__ */ jsxs4("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
|
|
648
976
|
t("Rewriting\u2026"),
|
|
649
|
-
/* @__PURE__ */
|
|
977
|
+
/* @__PURE__ */ jsx4(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
650
978
|
] }),
|
|
651
|
-
error && /* @__PURE__ */
|
|
652
|
-
/* @__PURE__ */
|
|
979
|
+
error && /* @__PURE__ */ jsxs4("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
980
|
+
/* @__PURE__ */ jsx4(TriangleAlert3, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
653
981
|
error
|
|
654
982
|
] })
|
|
655
983
|
] });
|
|
656
984
|
}
|
|
657
985
|
|
|
658
986
|
// src/ai/SubjectIdeas.jsx
|
|
659
|
-
import { useEffect as
|
|
660
|
-
import { Check as
|
|
661
|
-
import { jsx as
|
|
987
|
+
import { useEffect as useEffect5, useLayoutEffect, useRef as useRef5, useState as useState5 } from "react";
|
|
988
|
+
import { Check as Check3, Loader2 as Loader23, Sparkles as Sparkles3, TriangleAlert as TriangleAlert4 } from "lucide-react";
|
|
989
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
662
990
|
var MAX_AVOID = 12;
|
|
663
991
|
function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
664
992
|
const { t, locale } = useUi();
|
|
665
|
-
const [running, setRunning] =
|
|
666
|
-
const [error, setError] =
|
|
667
|
-
const [ideas, setIdeas] =
|
|
993
|
+
const [running, setRunning] = useState5(false);
|
|
994
|
+
const [error, setError] = useState5("");
|
|
995
|
+
const [ideas, setIdeas] = useState5(
|
|
668
996
|
/** @type {string[]|null} */
|
|
669
997
|
null
|
|
670
998
|
);
|
|
671
|
-
const [original, setOriginal] =
|
|
672
|
-
const shownRef =
|
|
999
|
+
const [original, setOriginal] = useState5("");
|
|
1000
|
+
const shownRef = useRef5(
|
|
673
1001
|
/** @type {string[]} */
|
|
674
1002
|
[]
|
|
675
1003
|
);
|
|
676
|
-
const abortRef =
|
|
1004
|
+
const abortRef = useRef5(
|
|
677
1005
|
/** @type {AbortController|null} */
|
|
678
1006
|
null
|
|
679
1007
|
);
|
|
680
|
-
const subjectRef =
|
|
1008
|
+
const subjectRef = useRef5(meta.subject);
|
|
681
1009
|
subjectRef.current = meta.subject;
|
|
682
|
-
const rootRef =
|
|
1010
|
+
const rootRef = useRef5(
|
|
683
1011
|
/** @type {HTMLDivElement|null} */
|
|
684
1012
|
null
|
|
685
1013
|
);
|
|
686
|
-
const focusRef =
|
|
1014
|
+
const focusRef = useRef5(
|
|
687
1015
|
/** @type {'list'|'button'|null} */
|
|
688
1016
|
null
|
|
689
1017
|
);
|
|
690
|
-
|
|
691
|
-
|
|
1018
|
+
useEffect5(() => () => abortRef.current?.abort(), []);
|
|
1019
|
+
useEffect5(
|
|
692
1020
|
() => editor.on("load", () => {
|
|
693
1021
|
abortRef.current?.abort();
|
|
694
1022
|
abortRef.current = null;
|
|
@@ -701,7 +1029,7 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
701
1029
|
[editor]
|
|
702
1030
|
);
|
|
703
1031
|
const typed = meta.subject.trim();
|
|
704
|
-
|
|
1032
|
+
useEffect5(() => {
|
|
705
1033
|
if (ideas && typed && !ideas.includes(meta.subject) && typed !== original) setOriginal(typed);
|
|
706
1034
|
}, [ideas, meta.subject, typed, original]);
|
|
707
1035
|
useLayoutEffect(() => {
|
|
@@ -773,16 +1101,16 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
773
1101
|
onSubject(options[to].text);
|
|
774
1102
|
rootRef.current?.querySelectorAll('[role="radio"]')[to]?.focus();
|
|
775
1103
|
};
|
|
776
|
-
const spinner = /* @__PURE__ */
|
|
777
|
-
return /* @__PURE__ */
|
|
778
|
-
!ideas && /* @__PURE__ */
|
|
779
|
-
running ? spinner : /* @__PURE__ */
|
|
1104
|
+
const spinner = /* @__PURE__ */ jsx5(Loader23, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
1105
|
+
return /* @__PURE__ */ jsxs5("div", { className: "eb:space-y-2", "data-testid": "eb-subject-ideas", ref: rootRef, children: [
|
|
1106
|
+
!ideas && /* @__PURE__ */ jsxs5("button", { type: "button", className: chipClassName(), disabled: running || !ready, onClick: run, "data-subject-ideas-button": true, children: [
|
|
1107
|
+
running ? spinner : /* @__PURE__ */ jsx5(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
780
1108
|
t("Suggest subjects")
|
|
781
1109
|
] }),
|
|
782
|
-
ideas && /* @__PURE__ */
|
|
783
|
-
/* @__PURE__ */
|
|
1110
|
+
ideas && /* @__PURE__ */ jsxs5("div", { className: "eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5", children: [
|
|
1111
|
+
/* @__PURE__ */ jsx5("div", { role: "radiogroup", "aria-label": t("Subject ideas"), className: "eb:space-y-0.5", onKeyDown, children: options.map((option, index) => {
|
|
784
1112
|
const chosen = index === checked;
|
|
785
|
-
return /* @__PURE__ */
|
|
1113
|
+
return /* @__PURE__ */ jsxs5(
|
|
786
1114
|
"button",
|
|
787
1115
|
{
|
|
788
1116
|
type: "button",
|
|
@@ -795,28 +1123,28 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
795
1123
|
),
|
|
796
1124
|
onClick: () => onSubject(option.text),
|
|
797
1125
|
children: [
|
|
798
|
-
/* @__PURE__ */
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
option.yours && /* @__PURE__ */
|
|
1126
|
+
/* @__PURE__ */ jsx5(Check3, { className: cn("eb:mt-0.5 eb:size-3.5 eb:shrink-0", chosen ? "eb:text-primary" : "eb:invisible"), "aria-hidden": "true" }),
|
|
1127
|
+
/* @__PURE__ */ jsx5("span", { className: "eb:min-w-0 eb:flex-1 eb:break-words", children: option.text }),
|
|
1128
|
+
option.yours && /* @__PURE__ */ jsx5("span", { className: "eb:shrink-0 eb:text-[11px] eb:text-foreground-muted", children: t("yours") })
|
|
801
1129
|
]
|
|
802
1130
|
},
|
|
803
1131
|
`${option.yours ? "yours" : "idea"}:${option.text}`
|
|
804
1132
|
);
|
|
805
1133
|
}) }),
|
|
806
|
-
/* @__PURE__ */
|
|
807
|
-
/* @__PURE__ */
|
|
808
|
-
running ? spinner : /* @__PURE__ */
|
|
1134
|
+
/* @__PURE__ */ jsxs5("div", { className: "eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5", children: [
|
|
1135
|
+
/* @__PURE__ */ jsxs5("button", { type: "button", className: chipClassName(), disabled: running || !ready, onClick: run, "data-subject-ideas-button": true, children: [
|
|
1136
|
+
running ? spinner : /* @__PURE__ */ jsx5(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
809
1137
|
t("More ideas")
|
|
810
1138
|
] }),
|
|
811
|
-
/* @__PURE__ */
|
|
1139
|
+
/* @__PURE__ */ jsx5(Button, { type: "button", variant: "ghost", size: "xs", className: "eb:ml-auto", disabled: running, onClick: done, children: t("Done") })
|
|
812
1140
|
] })
|
|
813
1141
|
] }),
|
|
814
|
-
running && /* @__PURE__ */
|
|
1142
|
+
running && /* @__PURE__ */ jsxs5("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
|
|
815
1143
|
t("Reading the email\u2026"),
|
|
816
|
-
/* @__PURE__ */
|
|
1144
|
+
/* @__PURE__ */ jsx5(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
817
1145
|
] }),
|
|
818
|
-
error && /* @__PURE__ */
|
|
819
|
-
/* @__PURE__ */
|
|
1146
|
+
error && /* @__PURE__ */ jsxs5("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
1147
|
+
/* @__PURE__ */ jsx5(TriangleAlert4, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
820
1148
|
error
|
|
821
1149
|
] })
|
|
822
1150
|
] });
|
|
@@ -865,24 +1193,27 @@ function createAi(options = {}) {
|
|
|
865
1193
|
}
|
|
866
1194
|
const ownOnly = !model && !http && PARTS.some((part) => typeof options[part] === "function");
|
|
867
1195
|
let transport = {};
|
|
868
|
-
const key = provider && askKey ? createKeyStore(providers) : null;
|
|
1196
|
+
const key = provider && askKey ? createKeyStore(providers, { fetch: fetchImpl }) : null;
|
|
869
1197
|
if (provider) {
|
|
870
1198
|
const models = /* @__PURE__ */ new Map();
|
|
871
1199
|
const keyFor = () => key ? key.get() : apiKey || "";
|
|
872
1200
|
const modelOf = (id) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1201
|
+
const chosen = (
|
|
1202
|
+
/** @type {import('./providers.js').AiProvider} */
|
|
1203
|
+
providers.find((item) => item.id === id)
|
|
1204
|
+
);
|
|
1205
|
+
const name = key ? key.model(id) : chosen.model;
|
|
1206
|
+
const slot = `${id}
|
|
1207
|
+
${name}`;
|
|
1208
|
+
if (!models.has(slot)) {
|
|
878
1209
|
models.set(
|
|
879
|
-
|
|
880
|
-
chosen.createModel ? chosen.createModel({ apiKey: keyFor, fetch: fetchImpl }) : providerEmailModel({ provider: chosen, fetch: fetchImpl, apiKey: keyFor })
|
|
1210
|
+
slot,
|
|
1211
|
+
chosen.createModel ? chosen.createModel({ apiKey: keyFor, fetch: fetchImpl, ...name ? { model: name } : {} }) : providerEmailModel({ provider: chosen, fetch: fetchImpl, apiKey: keyFor, ...name ? { model: name } : {} })
|
|
881
1212
|
);
|
|
882
1213
|
}
|
|
883
1214
|
return (
|
|
884
1215
|
/** @type {import('./generator.js').EmailModel} */
|
|
885
|
-
models.get(
|
|
1216
|
+
models.get(slot)
|
|
886
1217
|
);
|
|
887
1218
|
};
|
|
888
1219
|
const model2 = async (request) => {
|
|
@@ -959,6 +1290,8 @@ function askedAgain(run, key) {
|
|
|
959
1290
|
};
|
|
960
1291
|
}
|
|
961
1292
|
export {
|
|
1293
|
+
AGENT_ERROR_CODES,
|
|
1294
|
+
AGENT_PROTOCOL,
|
|
962
1295
|
AI_MESSAGES,
|
|
963
1296
|
AI_PROVIDERS,
|
|
964
1297
|
DEFAULT_CLAUDE_MODEL,
|
|
@@ -968,12 +1301,14 @@ export {
|
|
|
968
1301
|
PURPOSE_CATEGORIES,
|
|
969
1302
|
REWRITE_ACTIONS,
|
|
970
1303
|
REWRITE_LANGUAGES,
|
|
1304
|
+
agentEmailModel,
|
|
971
1305
|
claudeEmailModel,
|
|
972
1306
|
claudeProvider,
|
|
973
1307
|
createAi,
|
|
974
1308
|
createEmailGenerator,
|
|
975
1309
|
emailWords,
|
|
976
1310
|
failureCode,
|
|
1311
|
+
listProviderModels,
|
|
977
1312
|
providerEmailModel,
|
|
978
1313
|
resolveProvider
|
|
979
1314
|
};
|