best-email-builder 0.9.0 → 0.11.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 +110 -0
- package/README.md +7 -2
- package/dist/ai-server.js +21 -32
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +309 -254
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +9 -11
- package/dist/chunk-47WQSNER.js +875 -0
- package/dist/chunk-47WQSNER.js.map +1 -0
- package/dist/{chunk-AS2EPMA6.js → chunk-7HDOSA5G.js} +695 -170
- package/dist/chunk-7HDOSA5G.js.map +1 -0
- package/dist/{chunk-62HBUBBI.js → chunk-A2MEYOR3.js} +301 -768
- package/dist/chunk-A2MEYOR3.js.map +1 -0
- package/dist/{chunk-D4JBPAJ5.js → chunk-ACAQ2QTU.js} +83 -48
- package/dist/chunk-ACAQ2QTU.js.map +1 -0
- package/dist/chunk-COZXOBR3.js +294 -0
- package/dist/chunk-COZXOBR3.js.map +1 -0
- package/dist/{chunk-LUM7J5FF.js → chunk-NV6IV4CU.js} +2 -2
- package/dist/{chunk-P7ABBLX6.js → chunk-ONYPTZFK.js} +2 -2
- package/dist/{chunk-AXIM6E7T.js → chunk-PFGTD5CS.js} +2 -2
- package/dist/{chunk-VJKF6SR4.js → chunk-PYD5FOL2.js} +464 -1511
- package/dist/chunk-PYD5FOL2.js.map +1 -0
- package/dist/chunk-SQJ3TKEL.js +855 -0
- package/dist/chunk-SQJ3TKEL.js.map +1 -0
- package/dist/{chunk-G4U6PSF6.js → chunk-YQGPBUZ3.js} +2 -2
- package/dist/index.js +32 -28
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -10
- package/dist/server.js +182 -26
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ProofreadButton.d.ts +4 -0
- package/dist/types/ai/SubjectIdeas.d.ts +1 -1
- package/dist/types/ai/agent.d.ts +1 -1
- package/dist/types/ai/agentEndpoint.d.ts +3 -11
- package/dist/types/ai/claude.d.ts +3 -3
- package/dist/types/ai/createAi.d.ts +9 -14
- package/dist/types/ai/diff.d.ts +24 -0
- package/dist/types/ai/{draft.d.ts → errors.d.ts} +30 -81
- package/dist/types/ai/generator.d.ts +25 -91
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/proofread.d.ts +99 -0
- package/dist/types/ai/rewrite.d.ts +27 -8
- package/dist/types/ai/server.d.ts +4 -4
- package/dist/types/ai/subjects.d.ts +6 -0
- package/dist/types/core/EmailEditor.d.ts +10 -0
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/features/tags/tags.d.ts +55 -0
- package/dist/types/features/templates/gallery.d.ts +4 -14
- package/dist/types/features/templates/thumbnails.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/library.d.ts +2 -1
- package/dist/types/server/handler.d.ts +6 -3
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/tags.d.ts +64 -0
- package/dist/types/storage/client.d.ts +33 -4
- package/dist/types/ui/EmailStudio.d.ts +8 -8
- package/dist/types/ui/library/TagBadge.d.ts +19 -0
- package/dist/types/ui/library/TagsDialog.d.ts +43 -0
- package/dist/types/ui/library/TemplateLibrary.d.ts +6 -14
- package/dist/types/ui/primitives/misc.d.ts +9 -0
- package/dist/types/ui/primitives/searchShortcut.d.ts +2 -2
- package/package.json +1 -1
- package/dist/chunk-4IXP5YVN.js +0 -119
- package/dist/chunk-4IXP5YVN.js.map +0 -1
- package/dist/chunk-62HBUBBI.js.map +0 -1
- package/dist/chunk-AS2EPMA6.js.map +0 -1
- package/dist/chunk-D4JBPAJ5.js.map +0 -1
- package/dist/chunk-FFSSAMHH.js +0 -527
- package/dist/chunk-FFSSAMHH.js.map +0 -1
- package/dist/chunk-GWXY3MWQ.js +0 -595
- package/dist/chunk-GWXY3MWQ.js.map +0 -1
- package/dist/chunk-OWVXKMLI.js +0 -121
- package/dist/chunk-OWVXKMLI.js.map +0 -1
- package/dist/chunk-VJKF6SR4.js.map +0 -1
- package/dist/types/ai/GenerateButton.d.ts +0 -4
- package/dist/types/ai/prompt.d.ts +0 -76
- package/dist/types/ai/purposes.d.ts +0 -48
- /package/dist/{chunk-LUM7J5FF.js.map → chunk-NV6IV4CU.js.map} +0 -0
- /package/dist/{chunk-P7ABBLX6.js.map → chunk-ONYPTZFK.js.map} +0 -0
- /package/dist/{chunk-AXIM6E7T.js.map → chunk-PFGTD5CS.js.map} +0 -0
- /package/dist/{chunk-G4U6PSF6.js.map → chunk-YQGPBUZ3.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-ONYPTZFK.js";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
@@ -15,47 +15,37 @@ import {
|
|
|
15
15
|
DropdownMenuTrigger,
|
|
16
16
|
Input,
|
|
17
17
|
Label,
|
|
18
|
-
SEARCH_KEYS,
|
|
19
|
-
SearchKeyHint,
|
|
20
18
|
SectionLabel,
|
|
21
|
-
Textarea,
|
|
22
19
|
cn,
|
|
23
20
|
createHttpCaller,
|
|
24
|
-
useSearchShortcut,
|
|
25
21
|
useUi
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-
|
|
22
|
+
} from "./chunk-ACAQ2QTU.js";
|
|
23
|
+
import "./chunk-47WQSNER.js";
|
|
28
24
|
import {
|
|
29
25
|
AGENT_ERROR_CODES,
|
|
30
26
|
AGENT_PROTOCOL,
|
|
31
27
|
AI_MESSAGES,
|
|
32
28
|
AI_PROVIDERS,
|
|
33
29
|
DEFAULT_CLAUDE_MODEL,
|
|
34
|
-
EMAIL_PURPOSES,
|
|
35
30
|
EmailGenerationError,
|
|
36
31
|
GENERATION_ERRORS,
|
|
37
|
-
MAX_DETAILS_LENGTH,
|
|
38
32
|
MAX_REWRITE_INSTRUCTION,
|
|
39
|
-
PURPOSE_CATEGORIES,
|
|
40
33
|
REWRITE_ACTIONS,
|
|
41
34
|
REWRITE_LANGUAGES,
|
|
42
35
|
agentEmailModel,
|
|
43
36
|
claudeEmailModel,
|
|
44
37
|
claudeProvider,
|
|
45
38
|
createEmailGenerator,
|
|
39
|
+
diffWords,
|
|
46
40
|
emailWords,
|
|
47
41
|
failureCode,
|
|
48
42
|
listProviderModels,
|
|
49
43
|
providerEmailModel,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} from "./chunk-
|
|
53
|
-
import "./chunk-
|
|
54
|
-
import "./chunk-
|
|
55
|
-
import {
|
|
56
|
-
brandTemplate,
|
|
57
|
-
hasBrand
|
|
58
|
-
} from "./chunk-62HBUBBI.js";
|
|
44
|
+
resolveProvider,
|
|
45
|
+
visibleText
|
|
46
|
+
} from "./chunk-PYD5FOL2.js";
|
|
47
|
+
import "./chunk-PFGTD5CS.js";
|
|
48
|
+
import "./chunk-A2MEYOR3.js";
|
|
59
49
|
|
|
60
50
|
// src/ai/keys.js
|
|
61
51
|
var STORAGE_PREFIX = "best-email-builder:ai-key:";
|
|
@@ -733,42 +723,45 @@ function ConnectAi({ aiKey }) {
|
|
|
733
723
|
) });
|
|
734
724
|
}
|
|
735
725
|
|
|
736
|
-
// src/ai/
|
|
737
|
-
import { useEffect as useEffect3,
|
|
738
|
-
import { Check as Check2,
|
|
739
|
-
|
|
740
|
-
// src/ai/chip.js
|
|
741
|
-
var chipClassName = (selected = false) => cn(
|
|
742
|
-
"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",
|
|
743
|
-
selected ? "eb:border-primary eb:bg-primary eb:text-primary-foreground" : "eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5"
|
|
744
|
-
);
|
|
745
|
-
|
|
746
|
-
// src/ai/GenerateButton.jsx
|
|
726
|
+
// src/ai/ProofreadButton.jsx
|
|
727
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
728
|
+
import { Check as Check2, CircleCheck, Loader2, SpellCheck, TriangleAlert as TriangleAlert2 } from "lucide-react";
|
|
747
729
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
748
|
-
var
|
|
749
|
-
|
|
750
|
-
|
|
730
|
+
var LABEL_TAGS = { "mj-button": "button", "mj-navbar-link": "link" };
|
|
731
|
+
var KIND_LABELS = { subject: "Subject", preheader: "Preview text", text: "Text", button: "Button", link: "Menu link" };
|
|
732
|
+
function textsOf(editor, meta) {
|
|
733
|
+
const texts = [];
|
|
734
|
+
if (meta.subject.trim()) texts.push({ id: "subject", kind: "subject", format: "plain", text: meta.subject.trim() });
|
|
735
|
+
if (meta.preheader.trim()) texts.push({ id: "preheader", kind: "preheader", format: "plain", text: meta.preheader.trim() });
|
|
736
|
+
const walk = (node) => {
|
|
737
|
+
if (!node || typeof node !== "object") return;
|
|
738
|
+
const element = node.tag === "mj-text" || LABEL_TAGS[node.tag] ? editor.getElement(node.id) : null;
|
|
739
|
+
if (node.tag === "mj-text" && element?.html) texts.push({ id: node.id, kind: "text", format: "html", text: element.html });
|
|
740
|
+
else if (LABEL_TAGS[node.tag] && element?.content) texts.push({ id: node.id, kind: LABEL_TAGS[node.tag], format: "plain", text: element.content });
|
|
741
|
+
for (const child of node.children || []) walk(child);
|
|
742
|
+
};
|
|
743
|
+
walk(editor.getDocument().project?.root);
|
|
744
|
+
return texts;
|
|
745
|
+
}
|
|
746
|
+
function currentText(editor, meta, id) {
|
|
747
|
+
if (id === "subject" || id === "preheader") return meta[id].trim();
|
|
748
|
+
const element = editor.getElement(id);
|
|
749
|
+
return (element?.tagName === "mj-text" ? element?.html : element?.content) ?? null;
|
|
750
|
+
}
|
|
751
|
+
function ProofreadButton({ context, proofread }) {
|
|
752
|
+
const { editor, ready, meta, setMeta } = context;
|
|
751
753
|
const { t, locale, notify } = useUi();
|
|
752
754
|
const [open, setOpen] = useState3(false);
|
|
753
|
-
const [purpose, setPurpose] = useState3(
|
|
754
|
-
/** @type {string|null} */
|
|
755
|
-
null
|
|
756
|
-
);
|
|
757
|
-
const [details, setDetails] = useState3("");
|
|
758
|
-
const [query, setQuery] = useState3("");
|
|
759
755
|
const [busy, setBusy] = useState3(false);
|
|
760
756
|
const [error, setError] = useState3("");
|
|
761
757
|
const [seconds, setSeconds] = useState3(0);
|
|
762
|
-
const [
|
|
758
|
+
const [fixes, setFixes] = useState3(null);
|
|
763
759
|
const abortRef = useRef3(
|
|
764
760
|
/** @type {AbortController|null} */
|
|
765
761
|
null
|
|
766
762
|
);
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
null
|
|
770
|
-
);
|
|
771
|
-
useSearchShortcut(searchRef);
|
|
763
|
+
const metaRef = useRef3(meta);
|
|
764
|
+
metaRef.current = meta;
|
|
772
765
|
useEffect3(() => () => abortRef.current?.abort(), []);
|
|
773
766
|
useEffect3(() => {
|
|
774
767
|
if (!busy) return void 0;
|
|
@@ -776,168 +769,178 @@ function GenerateButton({ context, generate: generateEmail }) {
|
|
|
776
769
|
const timer = setInterval(() => setSeconds((s) => s + 1), 1e3);
|
|
777
770
|
return () => clearInterval(timer);
|
|
778
771
|
}, [busy]);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const openDialog = () => {
|
|
790
|
-
setError("");
|
|
791
|
-
setDirty(isDirty());
|
|
792
|
-
setOpen(true);
|
|
793
|
-
};
|
|
794
|
-
const close = (next) => {
|
|
795
|
-
if (next) return;
|
|
796
|
-
abortRef.current?.abort();
|
|
797
|
-
setOpen(false);
|
|
798
|
-
};
|
|
799
|
-
const generate = async () => {
|
|
800
|
-
if (busy || !purpose && !details.trim()) return;
|
|
772
|
+
useEffect3(
|
|
773
|
+
() => editor.on("load", () => {
|
|
774
|
+
abortRef.current?.abort();
|
|
775
|
+
setOpen(false);
|
|
776
|
+
}),
|
|
777
|
+
[editor]
|
|
778
|
+
);
|
|
779
|
+
const run = async () => {
|
|
780
|
+
if (busy) return;
|
|
781
|
+
const texts = textsOf(editor, metaRef.current);
|
|
801
782
|
setError("");
|
|
783
|
+
setFixes(null);
|
|
784
|
+
if (!texts.length) {
|
|
785
|
+
setError(t("The email has no text to check yet."));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
802
788
|
setBusy(true);
|
|
803
789
|
const controller = new AbortController();
|
|
804
790
|
abortRef.current = controller;
|
|
805
791
|
try {
|
|
806
|
-
const
|
|
807
|
-
{
|
|
808
|
-
purpose,
|
|
809
|
-
details: details.trim(),
|
|
810
|
-
language: String(locale || "en").slice(0, 2),
|
|
811
|
-
sender: sender || {},
|
|
812
|
-
...hasBrand(brand) ? { brand: { primary: brand.primary, heading: brand.heading, font: brand.font } } : {},
|
|
813
|
-
variables: (variables || []).map((variable) => ({ key: variable.key, label: variable.label }))
|
|
814
|
-
},
|
|
792
|
+
const result = await proofread(
|
|
793
|
+
{ texts: texts.map(({ id, kind, format, text }) => ({ id, kind, format, text })), language: String(locale || "en").slice(0, 2) },
|
|
815
794
|
{ signal: controller.signal }
|
|
816
795
|
);
|
|
817
796
|
if (controller.signal.aborted) return;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
797
|
+
const sent = new Map(texts.map((text) => [text.id, text]));
|
|
798
|
+
setFixes(
|
|
799
|
+
result.fixes.filter((fix) => sent.has(fix.id)).map((fix) => {
|
|
800
|
+
const text = (
|
|
801
|
+
/** @type {(typeof texts)[number]} */
|
|
802
|
+
sent.get(fix.id)
|
|
803
|
+
);
|
|
804
|
+
const stale = currentText(editor, metaRef.current, fix.id) !== text.text;
|
|
805
|
+
return { id: fix.id, kind: text.kind, format: text.format, before: text.text, after: fix.text, state: stale ? "stale" : "open" };
|
|
806
|
+
})
|
|
807
|
+
);
|
|
828
808
|
} catch (err) {
|
|
829
|
-
if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t("The email could not be
|
|
809
|
+
if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t("The email could not be checked."));
|
|
830
810
|
} finally {
|
|
831
|
-
if (abortRef.current === controller)
|
|
832
|
-
|
|
811
|
+
if (abortRef.current === controller) {
|
|
812
|
+
abortRef.current = null;
|
|
813
|
+
setBusy(false);
|
|
814
|
+
}
|
|
833
815
|
}
|
|
834
816
|
};
|
|
835
|
-
|
|
817
|
+
useEffect3(() => {
|
|
818
|
+
if (!open) return void 0;
|
|
819
|
+
run();
|
|
820
|
+
return () => abortRef.current?.abort();
|
|
821
|
+
}, [open]);
|
|
822
|
+
const openDialog = () => setOpen(true);
|
|
823
|
+
const close = (next) => {
|
|
824
|
+
if (next) return;
|
|
836
825
|
abortRef.current?.abort();
|
|
826
|
+
abortRef.current = null;
|
|
837
827
|
setBusy(false);
|
|
828
|
+
setOpen(false);
|
|
838
829
|
};
|
|
839
|
-
const
|
|
830
|
+
const apply = (ids) => {
|
|
831
|
+
if (!fixes) return;
|
|
832
|
+
const wanted = new Set(ids);
|
|
833
|
+
const contents = [];
|
|
834
|
+
const metaChanges = {};
|
|
835
|
+
const next = fixes.map((fix) => {
|
|
836
|
+
if (!wanted.has(fix.id) || fix.state !== "open") return fix;
|
|
837
|
+
if (currentText(editor, metaRef.current, fix.id) !== fix.before) return { ...fix, state: (
|
|
838
|
+
/** @type {const} */
|
|
839
|
+
"stale"
|
|
840
|
+
) };
|
|
841
|
+
if (fix.id === "subject" || fix.id === "preheader") metaChanges[fix.id] = fix.after;
|
|
842
|
+
else contents.push({ id: fix.id, content: fix.format === "html" ? cleanRichText(fix.after, document) : fix.after });
|
|
843
|
+
return { ...fix, state: (
|
|
844
|
+
/** @type {const} */
|
|
845
|
+
"applied"
|
|
846
|
+
) };
|
|
847
|
+
});
|
|
848
|
+
if (contents.length) editor.setContents(contents);
|
|
849
|
+
if (Object.keys(metaChanges).length) setMeta((current) => ({ ...current, ...metaChanges }));
|
|
850
|
+
setFixes(next);
|
|
851
|
+
const applied = next.filter((fix, index) => fix.state === "applied" && fixes[index].state !== "applied").length;
|
|
852
|
+
if (applied && next.every((fix) => fix.state !== "open")) {
|
|
853
|
+
setOpen(false);
|
|
854
|
+
notify("success", t("Corrections applied: {count}.", { count: next.filter((fix) => fix.state === "applied").length }));
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
const remaining = (fixes || []).filter((fix) => fix.state === "open");
|
|
840
858
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
841
859
|
/* @__PURE__ */ jsxs3(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, children: [
|
|
842
|
-
/* @__PURE__ */ jsx3(
|
|
843
|
-
t("
|
|
860
|
+
/* @__PURE__ */ jsx3(SpellCheck, {}),
|
|
861
|
+
t("Proofread")
|
|
844
862
|
] }),
|
|
845
|
-
/* @__PURE__ */ jsx3(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs3(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
className: "eb:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
/* @__PURE__ */ jsx3(
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
]
|
|
896
|
-
},
|
|
897
|
-
entry.id
|
|
898
|
-
);
|
|
899
|
-
}) })
|
|
900
|
-
] }, group.id)) })
|
|
901
|
-
] }) }),
|
|
902
|
-
/* @__PURE__ */ jsxs3("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
|
|
903
|
-
/* @__PURE__ */ jsx3(
|
|
904
|
-
Textarea,
|
|
905
|
-
{
|
|
906
|
-
value: details,
|
|
907
|
-
onChange: (event) => setDetails(event.target.value.slice(0, MAX_DETAILS_LENGTH)),
|
|
908
|
-
placeholder: t("Details (optional): product, dates, offer, tone, language\u2026"),
|
|
909
|
-
"aria-label": t("Details (optional)"),
|
|
910
|
-
rows: 2,
|
|
911
|
-
className: "eb:min-h-14",
|
|
912
|
-
disabled: busy,
|
|
913
|
-
onKeyDown: (event) => {
|
|
914
|
-
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) generate();
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
),
|
|
918
|
-
error ? /* @__PURE__ */ jsxs3("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
919
|
-
/* @__PURE__ */ jsx3(TriangleAlert2, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
920
|
-
error
|
|
921
|
-
] }) : 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.") }),
|
|
922
|
-
/* @__PURE__ */ jsxs3("div", { className: "eb:flex eb:items-center eb:justify-end eb:gap-2", children: [
|
|
923
|
-
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 }) }),
|
|
924
|
-
busy && /* @__PURE__ */ jsx3(Button, { variant: "outline", size: "sm", onClick: cancel, children: t("Cancel") }),
|
|
925
|
-
/* @__PURE__ */ jsxs3(Button, { size: "sm", onClick: generate, disabled: !canGenerate || busy, "data-testid": "eb-generate-submit", children: [
|
|
926
|
-
busy ? /* @__PURE__ */ jsx3(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx3(Sparkles, {}),
|
|
927
|
-
t("Generate")
|
|
928
|
-
] })
|
|
929
|
-
] })
|
|
930
|
-
] })
|
|
931
|
-
]
|
|
932
|
-
}
|
|
933
|
-
) })
|
|
863
|
+
/* @__PURE__ */ jsx3(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs3(DialogContent, { className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-xl", "data-testid": "eb-proofread", children: [
|
|
864
|
+
/* @__PURE__ */ jsxs3(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-4", children: [
|
|
865
|
+
/* @__PURE__ */ jsx3(DialogTitle, { children: t("Proofread the email") }),
|
|
866
|
+
/* @__PURE__ */ jsx3(DialogDescription, { children: t("AI looks for spelling, grammar and punctuation mistakes in every text. Nothing changes until you apply it.") })
|
|
867
|
+
] }),
|
|
868
|
+
/* @__PURE__ */ jsxs3("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-t eb:border-border eb:px-6 eb:py-4", children: [
|
|
869
|
+
busy && /* @__PURE__ */ jsxs3("p", { className: "eb:flex eb:items-center eb:gap-2 eb:py-6 eb:text-[13px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
|
|
870
|
+
/* @__PURE__ */ jsx3(Loader2, { className: "eb:size-4 eb:animate-spin", "aria-hidden": "true" }),
|
|
871
|
+
t("Reading the email\u2026 {seconds} s", { seconds })
|
|
872
|
+
] }),
|
|
873
|
+
error && /* @__PURE__ */ jsxs3("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:py-2 eb:text-[13px] eb:text-destructive", children: [
|
|
874
|
+
/* @__PURE__ */ jsx3(TriangleAlert2, { className: "eb:mt-0.5 eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
875
|
+
error
|
|
876
|
+
] }),
|
|
877
|
+
fixes && !fixes.length && /* @__PURE__ */ jsxs3("p", { className: "eb:flex eb:items-center eb:gap-2 eb:py-6 eb:text-[13px] eb:text-foreground", role: "status", children: [
|
|
878
|
+
/* @__PURE__ */ jsx3(CircleCheck, { className: "eb:size-4 eb:text-success", "aria-hidden": "true" }),
|
|
879
|
+
t("No mistake found.")
|
|
880
|
+
] }),
|
|
881
|
+
fixes && fixes.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "eb:flex eb:flex-col eb:gap-2.5", "aria-label": t("Corrections"), children: fixes.map((fix) => /* @__PURE__ */ jsxs3(
|
|
882
|
+
"li",
|
|
883
|
+
{
|
|
884
|
+
className: cn("eb:rounded-md eb:border eb:border-border eb:p-3", fix.state !== "open" && "eb:bg-muted/40"),
|
|
885
|
+
"data-testid": "eb-proofread-fix",
|
|
886
|
+
"data-state": fix.state,
|
|
887
|
+
children: [
|
|
888
|
+
/* @__PURE__ */ jsxs3("div", { className: "eb:mb-1.5 eb:flex eb:items-center eb:gap-2", children: [
|
|
889
|
+
/* @__PURE__ */ jsx3("span", { className: "eb:text-[11px] eb:font-medium eb:tracking-wide eb:text-foreground-muted eb:uppercase", children: t(KIND_LABELS[fix.kind]) }),
|
|
890
|
+
/* @__PURE__ */ jsxs3("span", { className: "eb:ml-auto", children: [
|
|
891
|
+
fix.state === "open" && /* @__PURE__ */ jsx3(Button, { variant: "outline", size: "xs", onClick: () => apply([fix.id]), children: t("Apply") }),
|
|
892
|
+
fix.state === "applied" && /* @__PURE__ */ jsxs3("span", { className: "eb:flex eb:items-center eb:gap-1 eb:text-[12px] eb:text-success", children: [
|
|
893
|
+
/* @__PURE__ */ jsx3(Check2, { className: "eb:size-3.5", "aria-hidden": "true" }),
|
|
894
|
+
t("Applied")
|
|
895
|
+
] }),
|
|
896
|
+
fix.state === "stale" && /* @__PURE__ */ jsx3("span", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Text changed since \u2014 left as it is") })
|
|
897
|
+
] })
|
|
898
|
+
] }),
|
|
899
|
+
/* @__PURE__ */ jsx3(Changes, { before: fix.before, after: fix.after, html: fix.format === "html" })
|
|
900
|
+
]
|
|
901
|
+
},
|
|
902
|
+
fix.id
|
|
903
|
+
)) })
|
|
904
|
+
] }),
|
|
905
|
+
/* @__PURE__ */ jsx3("div", { className: "eb:flex eb:items-center eb:justify-end eb:gap-2 eb:border-t eb:border-border eb:px-6 eb:py-4", children: busy ? /* @__PURE__ */ jsx3(Button, { variant: "outline", size: "sm", onClick: () => close(false), children: t("Cancel") }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
906
|
+
/* @__PURE__ */ jsx3(Button, { variant: "ghost", size: "sm", className: "eb:mr-auto", onClick: run, children: t("Check again") }),
|
|
907
|
+
remaining.length > 0 ? /* @__PURE__ */ jsxs3(Button, { size: "sm", onClick: () => apply(remaining.map((fix) => fix.id)), "data-testid": "eb-proofread-apply-all", children: [
|
|
908
|
+
/* @__PURE__ */ jsx3(Check2, {}),
|
|
909
|
+
t("Apply all ({count})", { count: remaining.length })
|
|
910
|
+
] }) : /* @__PURE__ */ jsx3(Button, { variant: "outline", size: "sm", onClick: () => close(false), children: t("Close") })
|
|
911
|
+
] }) })
|
|
912
|
+
] }) })
|
|
934
913
|
] });
|
|
935
914
|
}
|
|
915
|
+
function Changes({ before, after, html }) {
|
|
916
|
+
const parts = diffWords(html ? visibleText(before) : before, html ? visibleText(after) : after);
|
|
917
|
+
return /* @__PURE__ */ jsx3("p", { className: "eb:text-[13px] eb:leading-relaxed eb:break-words eb:whitespace-pre-line eb:text-foreground", children: parts.map(
|
|
918
|
+
(part, index) => part.type === "same" ? /* @__PURE__ */ jsx3("span", { children: around(part.text, index === 0, index === parts.length - 1) }, index) : part.type === "removed" ? /* @__PURE__ */ jsx3("del", { className: "eb:rounded-sm eb:bg-destructive/10 eb:text-destructive eb:line-through", children: part.text }, index) : /* @__PURE__ */ jsx3("ins", { className: "eb:rounded-sm eb:bg-success/15 eb:text-success eb:no-underline", children: part.text }, index)
|
|
919
|
+
) });
|
|
920
|
+
}
|
|
921
|
+
var CONTEXT = 48;
|
|
922
|
+
function around(text, first, last) {
|
|
923
|
+
if (text.length <= CONTEXT * 2 + 8) return text;
|
|
924
|
+
const head = (value) => value.slice(0, CONTEXT).replace(/\s\S*$/, "");
|
|
925
|
+
const tail = (value) => value.slice(-CONTEXT).replace(/^\S*\s/, "");
|
|
926
|
+
if (first && last) return text;
|
|
927
|
+
if (first) return `\u2026${tail(text)}`;
|
|
928
|
+
if (last) return `${head(text)}\u2026`;
|
|
929
|
+
return `${head(text)} \u2026 ${tail(text)}`;
|
|
930
|
+
}
|
|
936
931
|
|
|
937
932
|
// src/ai/RewriteText.jsx
|
|
938
933
|
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
939
|
-
import { ChevronDown as ChevronDown2, Languages, Loader2 as Loader22, Sparkles
|
|
940
|
-
|
|
934
|
+
import { ChevronDown as ChevronDown2, Languages, Loader2 as Loader22, PenLine, Sparkles, TriangleAlert as TriangleAlert3 } from "lucide-react";
|
|
935
|
+
|
|
936
|
+
// src/ai/chip.js
|
|
937
|
+
var chipClassName = (selected = false) => cn(
|
|
938
|
+
"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",
|
|
939
|
+
selected ? "eb:border-primary eb:bg-primary eb:text-primary-foreground" : "eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5"
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
// src/ai/RewriteText.jsx
|
|
943
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
941
944
|
function nativeName(code) {
|
|
942
945
|
try {
|
|
943
946
|
const name = new Intl.DisplayNames([code], { type: "language" }).of(code) || code;
|
|
@@ -947,26 +950,27 @@ function nativeName(code) {
|
|
|
947
950
|
}
|
|
948
951
|
}
|
|
949
952
|
function RewriteText({ editor, selection, rewrite }) {
|
|
950
|
-
const { t, notify } = useUi();
|
|
953
|
+
const { t, locale, notify } = useUi();
|
|
951
954
|
const [running, setRunning] = useState4(
|
|
952
955
|
/** @type {string|null} */
|
|
953
956
|
null
|
|
954
957
|
);
|
|
955
958
|
const [error, setError] = useState4("");
|
|
956
959
|
const [instruction, setInstruction] = useState4("");
|
|
960
|
+
const [topic, setTopic] = useState4("");
|
|
957
961
|
const abortRef = useRef4(
|
|
958
962
|
/** @type {AbortController|null} */
|
|
959
963
|
null
|
|
960
964
|
);
|
|
961
965
|
const label = selection.tagName === "mj-button";
|
|
962
966
|
useEffect4(() => () => abortRef.current?.abort(), []);
|
|
963
|
-
const
|
|
967
|
+
const currentText2 = () => {
|
|
964
968
|
const element = editor.getElement(selection.id);
|
|
965
969
|
return (label ? element?.content : element?.html) ?? "";
|
|
966
970
|
};
|
|
967
971
|
const run = async (action, extra = {}) => {
|
|
968
972
|
if (running) return;
|
|
969
|
-
const text =
|
|
973
|
+
const text = currentText2();
|
|
970
974
|
setError("");
|
|
971
975
|
setRunning(action);
|
|
972
976
|
const controller = new AbortController();
|
|
@@ -974,15 +978,18 @@ function RewriteText({ editor, selection, rewrite }) {
|
|
|
974
978
|
try {
|
|
975
979
|
const result = await rewrite({ action, text, format: label ? "plain" : "html", ...extra }, { signal: controller.signal });
|
|
976
980
|
if (controller.signal.aborted) return;
|
|
977
|
-
if (
|
|
981
|
+
if (currentText2() !== text) {
|
|
978
982
|
setError(t("The text changed in the meantime, so it was left as it is."));
|
|
979
983
|
return;
|
|
980
984
|
}
|
|
981
985
|
editor.setContent(label ? result.text : cleanRichText(result.text, document), selection.id);
|
|
982
986
|
if (action === "custom") setInstruction("");
|
|
983
|
-
|
|
987
|
+
if (action === "write") setTopic("");
|
|
988
|
+
notify("success", action === "write" ? t("Text written. Ctrl+Z brings back the previous one.") : t("Text rewritten. Ctrl+Z brings back the previous one."));
|
|
984
989
|
} catch (err) {
|
|
985
|
-
if (!controller.signal.aborted)
|
|
990
|
+
if (!controller.signal.aborted) {
|
|
991
|
+
setError(err?.message ? t(err.message) : action === "write" ? t("The text could not be written.") : t("The text could not be rewritten."));
|
|
992
|
+
}
|
|
986
993
|
} finally {
|
|
987
994
|
if (abortRef.current === controller) abortRef.current = null;
|
|
988
995
|
setRunning(null);
|
|
@@ -992,65 +999,110 @@ function RewriteText({ editor, selection, rewrite }) {
|
|
|
992
999
|
abortRef.current?.abort();
|
|
993
1000
|
setRunning(null);
|
|
994
1001
|
};
|
|
1002
|
+
const write = () => {
|
|
1003
|
+
const subject = topic.trim();
|
|
1004
|
+
if (!subject) return;
|
|
1005
|
+
const size = Number.parseFloat(String(editor.getElement(selection.id)?.attributes?.["font-size"] || ""));
|
|
1006
|
+
const own = visibleText(currentText2());
|
|
1007
|
+
const words = emailWords(editor.exportMjml());
|
|
1008
|
+
run("write", {
|
|
1009
|
+
instruction: subject,
|
|
1010
|
+
context: own ? words.replace(own, "").trim() : words,
|
|
1011
|
+
role: size >= 20 ? "heading" : "text",
|
|
1012
|
+
language: String(locale || "en").slice(0, 2)
|
|
1013
|
+
});
|
|
1014
|
+
};
|
|
995
1015
|
const busy = Boolean(running);
|
|
996
1016
|
const spinner = /* @__PURE__ */ jsx4(Loader22, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
997
|
-
return /* @__PURE__ */ jsxs4(
|
|
998
|
-
/* @__PURE__ */ jsxs4(
|
|
999
|
-
/* @__PURE__ */
|
|
1000
|
-
|
|
1017
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1018
|
+
!label && /* @__PURE__ */ jsxs4("section", { className: "eb:space-y-2.5", "aria-label": t("Write with AI"), "data-testid": "eb-write", children: [
|
|
1019
|
+
/* @__PURE__ */ jsxs4(SectionLabel, { className: "eb:flex eb:items-center eb:gap-1.5", children: [
|
|
1020
|
+
/* @__PURE__ */ jsx4(PenLine, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1021
|
+
t("Write with AI")
|
|
1022
|
+
] }),
|
|
1023
|
+
/* @__PURE__ */ jsxs4(
|
|
1024
|
+
"form",
|
|
1025
|
+
{
|
|
1026
|
+
className: "eb:flex eb:gap-1.5",
|
|
1027
|
+
onSubmit: (event) => {
|
|
1028
|
+
event.preventDefault();
|
|
1029
|
+
write();
|
|
1030
|
+
},
|
|
1031
|
+
children: [
|
|
1032
|
+
/* @__PURE__ */ jsx4(
|
|
1033
|
+
Input,
|
|
1034
|
+
{
|
|
1035
|
+
value: topic,
|
|
1036
|
+
onChange: (event) => setTopic(event.target.value),
|
|
1037
|
+
maxLength: MAX_REWRITE_INSTRUCTION,
|
|
1038
|
+
placeholder: t("What should it say? e.g. the sale ends Sunday"),
|
|
1039
|
+
"aria-label": t("What the text is about"),
|
|
1040
|
+
disabled: busy
|
|
1041
|
+
}
|
|
1042
|
+
),
|
|
1043
|
+
/* @__PURE__ */ jsx4(Button, { type: "submit", variant: "outline", size: "icon-sm", "aria-label": t("Write"), disabled: busy || !topic.trim(), children: running === "write" ? /* @__PURE__ */ jsx4(Loader22, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx4(PenLine, {}) })
|
|
1044
|
+
]
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1001
1047
|
] }),
|
|
1002
|
-
/* @__PURE__ */ jsxs4("
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
t(
|
|
1006
|
-
] }
|
|
1007
|
-
/* @__PURE__ */ jsxs4(
|
|
1008
|
-
|
|
1009
|
-
running ===
|
|
1010
|
-
t(
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1048
|
+
/* @__PURE__ */ jsxs4("section", { className: "eb:space-y-2.5", "aria-label": t("Rewrite with AI"), "data-testid": "eb-rewrite", children: [
|
|
1049
|
+
/* @__PURE__ */ jsxs4(SectionLabel, { className: "eb:flex eb:items-center eb:gap-1.5", children: [
|
|
1050
|
+
/* @__PURE__ */ jsx4(Sparkles, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1051
|
+
t("Rewrite with AI")
|
|
1052
|
+
] }),
|
|
1053
|
+
/* @__PURE__ */ jsxs4("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: [
|
|
1054
|
+
REWRITE_ACTIONS.map((action) => /* @__PURE__ */ jsxs4("button", { type: "button", className: chipClassName(), disabled: busy, onClick: () => run(action.id), children: [
|
|
1055
|
+
running === action.id && spinner,
|
|
1056
|
+
t(action.label)
|
|
1057
|
+
] }, action.id)),
|
|
1058
|
+
/* @__PURE__ */ jsxs4(DropdownMenu, { children: [
|
|
1059
|
+
/* @__PURE__ */ jsx4(DropdownMenuTrigger, { asChild: true, disabled: busy, children: /* @__PURE__ */ jsxs4("button", { type: "button", className: chipClassName(), children: [
|
|
1060
|
+
running === "translate" ? spinner : /* @__PURE__ */ jsx4(Languages, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1061
|
+
t("Translate"),
|
|
1062
|
+
/* @__PURE__ */ jsx4(ChevronDown2, { className: "eb:size-3", "aria-hidden": "true" })
|
|
1063
|
+
] }) }),
|
|
1064
|
+
/* @__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)) })
|
|
1065
|
+
] })
|
|
1066
|
+
] }),
|
|
1067
|
+
/* @__PURE__ */ jsxs4(
|
|
1068
|
+
"form",
|
|
1069
|
+
{
|
|
1070
|
+
className: "eb:flex eb:gap-1.5",
|
|
1071
|
+
onSubmit: (event) => {
|
|
1072
|
+
event.preventDefault();
|
|
1073
|
+
if (instruction.trim()) run("custom", { instruction: instruction.trim() });
|
|
1074
|
+
},
|
|
1075
|
+
children: [
|
|
1076
|
+
/* @__PURE__ */ jsx4(
|
|
1077
|
+
Input,
|
|
1078
|
+
{
|
|
1079
|
+
value: instruction,
|
|
1080
|
+
onChange: (event) => setInstruction(event.target.value),
|
|
1081
|
+
maxLength: MAX_REWRITE_INSTRUCTION,
|
|
1082
|
+
placeholder: t("Or say what to change\u2026"),
|
|
1083
|
+
"aria-label": t("What to change"),
|
|
1084
|
+
disabled: busy
|
|
1085
|
+
}
|
|
1086
|
+
),
|
|
1087
|
+
/* @__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(Sparkles, {}) })
|
|
1088
|
+
]
|
|
1089
|
+
}
|
|
1090
|
+
),
|
|
1091
|
+
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: [
|
|
1092
|
+
running === "write" ? t("Writing\u2026") : t("Rewriting\u2026"),
|
|
1093
|
+
/* @__PURE__ */ jsx4(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
1094
|
+
] }),
|
|
1095
|
+
error && /* @__PURE__ */ jsxs4("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
1096
|
+
/* @__PURE__ */ jsx4(TriangleAlert3, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
1097
|
+
error
|
|
1014
1098
|
] })
|
|
1015
|
-
] }),
|
|
1016
|
-
/* @__PURE__ */ jsxs4(
|
|
1017
|
-
"form",
|
|
1018
|
-
{
|
|
1019
|
-
className: "eb:flex eb:gap-1.5",
|
|
1020
|
-
onSubmit: (event) => {
|
|
1021
|
-
event.preventDefault();
|
|
1022
|
-
if (instruction.trim()) run("custom", { instruction: instruction.trim() });
|
|
1023
|
-
},
|
|
1024
|
-
children: [
|
|
1025
|
-
/* @__PURE__ */ jsx4(
|
|
1026
|
-
Input,
|
|
1027
|
-
{
|
|
1028
|
-
value: instruction,
|
|
1029
|
-
onChange: (event) => setInstruction(event.target.value),
|
|
1030
|
-
maxLength: MAX_REWRITE_INSTRUCTION,
|
|
1031
|
-
placeholder: t("Or say what to change\u2026"),
|
|
1032
|
-
"aria-label": t("What to change"),
|
|
1033
|
-
disabled: busy
|
|
1034
|
-
}
|
|
1035
|
-
),
|
|
1036
|
-
/* @__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, {}) })
|
|
1037
|
-
]
|
|
1038
|
-
}
|
|
1039
|
-
),
|
|
1040
|
-
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: [
|
|
1041
|
-
t("Rewriting\u2026"),
|
|
1042
|
-
/* @__PURE__ */ jsx4(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
|
|
1043
|
-
] }),
|
|
1044
|
-
error && /* @__PURE__ */ jsxs4("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
|
|
1045
|
-
/* @__PURE__ */ jsx4(TriangleAlert3, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
|
|
1046
|
-
error
|
|
1047
1099
|
] })
|
|
1048
1100
|
] });
|
|
1049
1101
|
}
|
|
1050
1102
|
|
|
1051
1103
|
// src/ai/SubjectIdeas.jsx
|
|
1052
1104
|
import { useEffect as useEffect5, useLayoutEffect, useRef as useRef5, useState as useState5 } from "react";
|
|
1053
|
-
import { Check as Check3, Loader2 as Loader23, Sparkles as
|
|
1105
|
+
import { Check as Check3, Loader2 as Loader23, Sparkles as Sparkles2, TriangleAlert as TriangleAlert4 } from "lucide-react";
|
|
1054
1106
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1055
1107
|
var MAX_AVOID = 12;
|
|
1056
1108
|
function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
@@ -1169,7 +1221,7 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
1169
1221
|
const spinner = /* @__PURE__ */ jsx5(Loader23, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
|
|
1170
1222
|
return /* @__PURE__ */ jsxs5("div", { className: "eb:space-y-2", "data-testid": "eb-subject-ideas", ref: rootRef, children: [
|
|
1171
1223
|
!ideas && /* @__PURE__ */ jsxs5("button", { type: "button", className: chipClassName(), disabled: running || !ready, onClick: run, "data-subject-ideas-button": true, children: [
|
|
1172
|
-
running ? spinner : /* @__PURE__ */ jsx5(
|
|
1224
|
+
running ? spinner : /* @__PURE__ */ jsx5(Sparkles2, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1173
1225
|
t("Suggest subjects")
|
|
1174
1226
|
] }),
|
|
1175
1227
|
ideas && /* @__PURE__ */ jsxs5("div", { className: "eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5", children: [
|
|
@@ -1198,7 +1250,7 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
1198
1250
|
}) }),
|
|
1199
1251
|
/* @__PURE__ */ jsxs5("div", { className: "eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5", children: [
|
|
1200
1252
|
/* @__PURE__ */ jsxs5("button", { type: "button", className: chipClassName(), disabled: running || !ready, onClick: run, "data-subject-ideas-button": true, children: [
|
|
1201
|
-
running ? spinner : /* @__PURE__ */ jsx5(
|
|
1253
|
+
running ? spinner : /* @__PURE__ */ jsx5(Sparkles2, { className: "eb:size-3", "aria-hidden": "true" }),
|
|
1202
1254
|
t("More ideas")
|
|
1203
1255
|
] }),
|
|
1204
1256
|
/* @__PURE__ */ jsx5(Button, { type: "button", variant: "ghost", size: "xs", className: "eb:ml-auto", disabled: running, onClick: done, children: t("Done") })
|
|
@@ -1218,12 +1270,12 @@ function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
|
|
|
1218
1270
|
// src/ai/createAi.js
|
|
1219
1271
|
var PARTS = (
|
|
1220
1272
|
/** @type {const} */
|
|
1221
|
-
["
|
|
1273
|
+
["proofread", "rewrite", "suggestSubjects"]
|
|
1222
1274
|
);
|
|
1223
|
-
var ROUTES = {
|
|
1275
|
+
var ROUTES = { proofread: "/proofread", rewrite: "/rewrite", suggestSubjects: "/subjects" };
|
|
1224
1276
|
function createAi(options = {}) {
|
|
1225
1277
|
if (!options || typeof options !== "object") throw new TypeError("createAi: options must be an object");
|
|
1226
|
-
const { model, maxAttempts,
|
|
1278
|
+
const { model, maxAttempts, baseUrl, headers, fetch: fetchImpl, provider: providerOption, apiKey, askKey = false } = options;
|
|
1227
1279
|
const usesProvider = providerOption !== void 0;
|
|
1228
1280
|
const http = baseUrl !== void 0 || headers !== void 0 || fetchImpl !== void 0 && !usesProvider;
|
|
1229
1281
|
if (model !== void 0 && typeof model !== "function") throw new TypeError("createAi: `model` must be a function");
|
|
@@ -1235,8 +1287,12 @@ function createAi(options = {}) {
|
|
|
1235
1287
|
throw new TypeError('createAi: `apiKey` and `askKey` go with a `provider` (e.g. provider: "openrouter")');
|
|
1236
1288
|
}
|
|
1237
1289
|
if (apiKey !== void 0 && (typeof apiKey !== "string" || !apiKey)) throw new TypeError("createAi: `apiKey` must be a non-empty string");
|
|
1238
|
-
|
|
1239
|
-
|
|
1290
|
+
const { generate } = (
|
|
1291
|
+
/** @type {any} */
|
|
1292
|
+
options
|
|
1293
|
+
);
|
|
1294
|
+
if (generate !== void 0 && generate !== false) {
|
|
1295
|
+
throw new TypeError("createAi: `generate` was removed in 0.10 \u2014 the AI checks and writes the texts of an email (`proofread`, `rewrite`), not whole emails");
|
|
1240
1296
|
}
|
|
1241
1297
|
if (typeof askKey !== "boolean") throw new TypeError("createAi: `askKey` must be true or false");
|
|
1242
1298
|
if (apiKey && askKey) throw new TypeError("createAi: `apiKey` (one key for all) or `askKey` (each user's own), not both");
|
|
@@ -1290,10 +1346,10 @@ ${address}`;
|
|
|
1290
1346
|
}
|
|
1291
1347
|
return modelOf(key ? key.provider.id : provider.id)(request);
|
|
1292
1348
|
};
|
|
1293
|
-
const generator = createEmailGenerator({ model: model2, maxAttempts
|
|
1349
|
+
const generator = createEmailGenerator({ model: model2, maxAttempts });
|
|
1294
1350
|
for (const part of PARTS) transport[part] = key ? askedAgain(generator[part], key) : generator[part];
|
|
1295
1351
|
} else if (model) {
|
|
1296
|
-
transport = createEmailGenerator({ model, maxAttempts
|
|
1352
|
+
transport = createEmailGenerator({ model, maxAttempts });
|
|
1297
1353
|
} else if (!ownOnly) {
|
|
1298
1354
|
const { call } = createHttpCaller({ baseUrl, headers, fetch: fetchImpl });
|
|
1299
1355
|
for (const part of PARTS) {
|
|
@@ -1307,9 +1363,9 @@ ${address}`;
|
|
|
1307
1363
|
return transport[part] || null;
|
|
1308
1364
|
};
|
|
1309
1365
|
return Object.freeze({
|
|
1310
|
-
|
|
1311
|
-
/** @type {
|
|
1312
|
-
resolve("
|
|
1366
|
+
proofread: (
|
|
1367
|
+
/** @type {ProofreadFn|null} */
|
|
1368
|
+
resolve("proofread")
|
|
1313
1369
|
),
|
|
1314
1370
|
rewrite: (
|
|
1315
1371
|
/** @type {RewriteFn|null} */
|
|
@@ -1321,7 +1377,7 @@ ${address}`;
|
|
|
1321
1377
|
),
|
|
1322
1378
|
key,
|
|
1323
1379
|
messages: AI_MESSAGES,
|
|
1324
|
-
components: Object.freeze({
|
|
1380
|
+
components: Object.freeze({ Proofread: ProofreadButton, Rewrite: RewriteText, Subjects: SubjectIdeas, Connect: key ? ConnectAi : null })
|
|
1325
1381
|
});
|
|
1326
1382
|
}
|
|
1327
1383
|
function providerOf(option) {
|
|
@@ -1365,10 +1421,8 @@ export {
|
|
|
1365
1421
|
AI_MESSAGES,
|
|
1366
1422
|
AI_PROVIDERS,
|
|
1367
1423
|
DEFAULT_CLAUDE_MODEL,
|
|
1368
|
-
EMAIL_PURPOSES,
|
|
1369
1424
|
EmailGenerationError,
|
|
1370
1425
|
GENERATION_ERRORS,
|
|
1371
|
-
PURPOSE_CATEGORIES,
|
|
1372
1426
|
REWRITE_ACTIONS,
|
|
1373
1427
|
REWRITE_LANGUAGES,
|
|
1374
1428
|
agentEmailModel,
|
|
@@ -1376,6 +1430,7 @@ export {
|
|
|
1376
1430
|
claudeProvider,
|
|
1377
1431
|
createAi,
|
|
1378
1432
|
createEmailGenerator,
|
|
1433
|
+
diffWords,
|
|
1379
1434
|
emailWords,
|
|
1380
1435
|
failureCode,
|
|
1381
1436
|
listProviderModels,
|