@aglyn/plugins-outreach 1.0.0-beta.194 → 1.0.0-beta.196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/curate-step-dialog.d.ts +50 -0
  3. package/src/lib/components/curate-step-dialog.js +235 -0
  4. package/src/lib/components/curate-step-dialog.js.map +1 -0
  5. package/src/lib/components/curated-step-editor.d.ts +80 -0
  6. package/src/lib/components/curated-step-editor.js +255 -0
  7. package/src/lib/components/curated-step-editor.js.map +1 -0
  8. package/src/lib/components/enroll-dialog.d.ts +15 -0
  9. package/src/lib/components/enroll-dialog.js +154 -13
  10. package/src/lib/components/enroll-dialog.js.map +1 -1
  11. package/src/lib/components/enrollments-table.d.ts +2 -0
  12. package/src/lib/components/enrollments-table.js +62 -8
  13. package/src/lib/components/enrollments-table.js.map +1 -1
  14. package/src/lib/components/use-outreach-api.d.ts +8 -1
  15. package/src/lib/components/use-outreach-api.js +12 -0
  16. package/src/lib/components/use-outreach-api.js.map +1 -1
  17. package/src/lib/constants/api-routes.d.ts +8 -0
  18. package/src/lib/constants/api-routes.js +6 -0
  19. package/src/lib/constants/api-routes.js.map +1 -1
  20. package/src/lib/engine/compose.d.ts +12 -2
  21. package/src/lib/engine/compose.js +32 -6
  22. package/src/lib/engine/compose.js.map +1 -1
  23. package/src/lib/engine/curation-prompt.d.ts +73 -0
  24. package/src/lib/engine/curation-prompt.js +151 -0
  25. package/src/lib/engine/curation-prompt.js.map +1 -0
  26. package/src/lib/engine/enrollment-activity.d.ts +24 -0
  27. package/src/lib/engine/enrollment-activity.js +25 -0
  28. package/src/lib/engine/enrollment-activity.js.map +1 -1
  29. package/src/lib/engine/sequence-validation.d.ts +10 -1
  30. package/src/lib/engine/sequence-validation.js +54 -0
  31. package/src/lib/engine/sequence-validation.js.map +1 -1
  32. package/src/lib/enrollment/step-overrides.d.ts +53 -0
  33. package/src/lib/enrollment/step-overrides.js +140 -0
  34. package/src/lib/enrollment/step-overrides.js.map +1 -0
  35. package/src/lib/model/outreach-api.d.ts +69 -2
  36. package/src/lib/model/outreach-api.js.map +1 -1
  37. package/src/lib/model/outreach.types.d.ts +36 -0
  38. package/src/lib/model/outreach.types.js +18 -0
  39. package/src/lib/model/outreach.types.js.map +1 -1
  40. package/src/lib/model/step-preview.d.ts +3 -1
  41. package/src/lib/model/step-preview.js +6 -3
  42. package/src/lib/model/step-preview.js.map +1 -1
  43. package/src/lib/model/stored-records.d.ts +9 -1
  44. package/src/lib/model/stored-records.js +47 -2
  45. package/src/lib/model/stored-records.js.map +1 -1
  46. package/src/lib/routes/curate-routes.d.ts +63 -0
  47. package/src/lib/routes/curate-routes.js +424 -0
  48. package/src/lib/routes/curate-routes.js.map +1 -0
  49. package/src/lib/routes/enroll-routes.d.ts +7 -0
  50. package/src/lib/routes/enroll-routes.js +58 -4
  51. package/src/lib/routes/enroll-routes.js.map +1 -1
  52. package/src/lib/routes/preview-routes.js +15 -1
  53. package/src/lib/routes/preview-routes.js.map +1 -1
  54. package/src/lib/routes/register-routes.js +17 -0
  55. package/src/lib/routes/register-routes.js.map +1 -1
  56. package/src/lib/runtime/send-job.js +2 -0
  57. package/src/lib/runtime/send-job.js.map +1 -1
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { Alert, Button, Checkbox, Chip, FormControlLabel, Paper, Stack, TextField, Typography } from "@mui/material";
20
+ import { useMemo } from "react";
21
+ import { isInThreadEmailStep, validateOutreachCuratedStep } from "../engine/sequence-validation.js";
22
+ import { OUTREACH_CURATION_CONFIRMATION_LABEL } from "../model/outreach.types.js";
23
+ /** A drafted step as the route answered it, ready for the member. */ export function outreachDraftState(draft, steps, source, audit) {
24
+ var _draft_subject;
25
+ const startsThread = !isInThreadEmailStep(steps, draft.stepIndex);
26
+ const subject = startsThread ? (_draft_subject = draft.subject) != null ? _draft_subject : '' : '';
27
+ return _extends({
28
+ stepIndex: draft.stepIndex,
29
+ startsThread,
30
+ subject,
31
+ body: draft.body,
32
+ original: {
33
+ subject,
34
+ body: draft.body
35
+ },
36
+ source,
37
+ confirmed: false,
38
+ decision: 'pending'
39
+ }, audit ? {
40
+ prompt: audit.prompt,
41
+ model: audit.model
42
+ } : {});
43
+ }
44
+ /** The member's own starting point: the step's words, for them to rewrite by hand. */ export function outreachMemberDraftStates(steps, stepIndexes) {
45
+ return stepIndexes.flatMap((stepIndex)=>{
46
+ const step = steps[stepIndex];
47
+ if ((step == null ? void 0 : step.kind) !== 'email') return [];
48
+ return [
49
+ outreachDraftState(// A step that sends a CRM template has no words here to start from.
50
+ {
51
+ stepIndex,
52
+ subject: step.subject,
53
+ body: step.templateId ? '' : step.body
54
+ }, steps, 'member')
55
+ ];
56
+ });
57
+ }
58
+ /** The request a USED draft becomes; `null` for one that was kept or is still open. */ export function outreachDraftToOverride(draft) {
59
+ if (draft.decision !== 'use') return null;
60
+ const edited = draft.source === 'ai' && (draft.body !== draft.original.body || draft.startsThread && draft.subject !== draft.original.subject);
61
+ return _extends({
62
+ stepIndex: draft.stepIndex
63
+ }, draft.startsThread ? {
64
+ subject: draft.subject
65
+ } : {}, {
66
+ body: draft.body,
67
+ source: draft.source
68
+ }, edited ? {
69
+ edited: true
70
+ } : {}, draft.source === 'ai' && draft.prompt ? {
71
+ prompt: draft.prompt
72
+ } : {}, draft.source === 'ai' && draft.model ? {
73
+ model: draft.model
74
+ } : {});
75
+ }
76
+ /** The validator's verdict on the draft as it stands. */ export function outreachDraftIssues(draft) {
77
+ return validateOutreachCuratedStep({
78
+ subject: draft.startsThread ? draft.subject : undefined,
79
+ body: draft.body,
80
+ startsThread: draft.startsThread
81
+ }, `stepOverrides.${draft.stepIndex}`);
82
+ }
83
+ /** Whether the draft may be used as it stands: confirmed, and nothing the validator refuses. */ export function outreachDraftUsable(draft) {
84
+ return draft.confirmed && !outreachDraftIssues(draft).some((issue)=>issue.severity === 'error');
85
+ }
86
+ export function OutreachCuratedStepEditor(props) {
87
+ const { draft, label, onChange } = props;
88
+ const disabled = props.disabled === true;
89
+ const issues = useMemo(()=>outreachDraftIssues(draft), [
90
+ draft
91
+ ]);
92
+ const errors = issues.filter((issue)=>issue.severity === 'error');
93
+ const warnings = issues.filter((issue)=>issue.severity === 'warning');
94
+ const change = (next)=>onChange(_extends({}, draft, next));
95
+ const who = draft.source === 'ai' ? 'AI draft' : 'Your words';
96
+ if (draft.decision !== 'pending') {
97
+ return /*#__PURE__*/ _jsx(Paper, {
98
+ variant: "outlined",
99
+ sx: {
100
+ p: 1.5
101
+ },
102
+ "aria-label": `${label} — ${draft.decision === 'use' ? 'curated' : 'template kept'}`,
103
+ children: /*#__PURE__*/ _jsxs(Stack, {
104
+ direction: "row",
105
+ spacing: 1,
106
+ sx: {
107
+ alignItems: 'center',
108
+ flexWrap: 'wrap',
109
+ rowGap: 1
110
+ },
111
+ children: [
112
+ /*#__PURE__*/ _jsx(Typography, {
113
+ variant: "body2",
114
+ sx: {
115
+ flexGrow: 1
116
+ },
117
+ children: label
118
+ }),
119
+ /*#__PURE__*/ _jsx(Chip, {
120
+ size: "small",
121
+ color: draft.decision === 'use' ? 'success' : 'default',
122
+ label: draft.decision === 'use' ? `Curated · ${who}` : 'Template kept'
123
+ }),
124
+ /*#__PURE__*/ _jsx(Button, {
125
+ size: "small",
126
+ onClick: ()=>change({
127
+ decision: 'pending'
128
+ }),
129
+ disabled: disabled,
130
+ children: "Change"
131
+ })
132
+ ]
133
+ })
134
+ });
135
+ }
136
+ return /*#__PURE__*/ _jsx(Paper, {
137
+ variant: "outlined",
138
+ sx: {
139
+ p: 1.5
140
+ },
141
+ "aria-label": `${label} — draft`,
142
+ children: /*#__PURE__*/ _jsxs(Stack, {
143
+ spacing: 1.5,
144
+ children: [
145
+ /*#__PURE__*/ _jsxs(Stack, {
146
+ direction: "row",
147
+ spacing: 1,
148
+ sx: {
149
+ alignItems: 'center'
150
+ },
151
+ children: [
152
+ /*#__PURE__*/ _jsx(Typography, {
153
+ variant: "subtitle2",
154
+ sx: {
155
+ flexGrow: 1
156
+ },
157
+ children: label
158
+ }),
159
+ /*#__PURE__*/ _jsx(Chip, {
160
+ size: "small",
161
+ variant: "outlined",
162
+ label: who
163
+ })
164
+ ]
165
+ }),
166
+ draft.startsThread ? /*#__PURE__*/ _jsx(TextField, {
167
+ label: "Subject",
168
+ value: draft.subject,
169
+ onChange: (event)=>change({
170
+ subject: event.target.value,
171
+ confirmed: false
172
+ }),
173
+ disabled: disabled,
174
+ size: "small",
175
+ fullWidth: true,
176
+ error: errors.some((issue)=>issue.path.endsWith('.subject'))
177
+ }) : /*#__PURE__*/ _jsx(Typography, {
178
+ variant: "caption",
179
+ color: "text.secondary",
180
+ children: "Sent as a reply in the thread, under the thread’s subject."
181
+ }),
182
+ /*#__PURE__*/ _jsx(TextField, {
183
+ label: "Email",
184
+ value: draft.body,
185
+ onChange: (event)=>change({
186
+ body: event.target.value,
187
+ confirmed: false
188
+ }),
189
+ disabled: disabled,
190
+ multiline: true,
191
+ minRows: 6,
192
+ fullWidth: true,
193
+ error: errors.some((issue)=>issue.path.endsWith('.body')),
194
+ helperText: "Plain text. The footer is added when it’s sent."
195
+ }),
196
+ errors.length ? /*#__PURE__*/ _jsx(Alert, {
197
+ severity: "error",
198
+ children: /*#__PURE__*/ _jsx(Stack, {
199
+ component: "ul",
200
+ spacing: 0.5,
201
+ sx: {
202
+ m: 0,
203
+ pl: 2
204
+ },
205
+ children: errors.map((issue)=>/*#__PURE__*/ _jsx("li", {
206
+ children: issue.message
207
+ }, `${issue.path}:${issue.code}`))
208
+ })
209
+ }) : null,
210
+ warnings.length ? /*#__PURE__*/ _jsx(Typography, {
211
+ variant: "caption",
212
+ color: "text.secondary",
213
+ children: warnings.map((issue)=>issue.message).join(' ')
214
+ }) : null,
215
+ /*#__PURE__*/ _jsx(FormControlLabel, {
216
+ control: /*#__PURE__*/ _jsx(Checkbox, {
217
+ checked: draft.confirmed,
218
+ onChange: (event)=>change({
219
+ confirmed: event.target.checked
220
+ }),
221
+ disabled: disabled || errors.length > 0
222
+ }),
223
+ label: OUTREACH_CURATION_CONFIRMATION_LABEL
224
+ }),
225
+ /*#__PURE__*/ _jsxs(Stack, {
226
+ direction: "row",
227
+ spacing: 1,
228
+ children: [
229
+ /*#__PURE__*/ _jsx(Button, {
230
+ size: "small",
231
+ variant: "contained",
232
+ disabled: disabled || !outreachDraftUsable(draft),
233
+ onClick: ()=>change({
234
+ decision: 'use'
235
+ }),
236
+ children: "Use this"
237
+ }),
238
+ /*#__PURE__*/ _jsx(Button, {
239
+ size: "small",
240
+ disabled: disabled,
241
+ onClick: ()=>change({
242
+ decision: 'keep'
243
+ }),
244
+ children: "Keep the template"
245
+ })
246
+ ]
247
+ })
248
+ ]
249
+ })
250
+ });
251
+ }
252
+ OutreachCuratedStepEditor.displayName = 'OutreachCuratedStepEditor';
253
+ export default OutreachCuratedStepEditor;
254
+
255
+ //# sourceMappingURL=curated-step-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/curated-step-editor.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n FormControlLabel,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n isInThreadEmailStep,\n validateOutreachCuratedStep,\n type OutreachValidationIssue,\n} from '../engine/sequence-validation'\nimport type { OutreachCurateDraft, OutreachStepOverrideRequest } from '../model/outreach-api'\nimport {\n OUTREACH_CURATION_CONFIRMATION_LABEL,\n type OutreachSequenceStep,\n type OutreachStepOverrideSource,\n} from '../model/outreach.types'\n\n/**\n * ONE PERSON'S COPY OF ONE STEP, AS THE MEMBER READS AND DECIDES IT\n * (AGL-3324).\n *\n * The enroll dialog shows one of these under each person for every email\n * step the AI drafted; the Enrollments tab shows one for the next step. The\n * member edits the subject and the body, reads what the validator says,\n * ticks the confirmation — a fourth attestation beside the three a cold\n * contact asks for — and either USES the copy or KEEPS the template. Until\n * they decide, nothing about the draft leaves the browser: the enroll and\n * the save carry only what was used.\n *\n * Validated as they type, with the same rules the route applies again: a\n * draft that breaks one cannot be used as it stands.\n */\n\n/** What the member holds for one drafted step. */\nexport interface OutreachCuratedDraftState {\n stepIndex: number\n /** Whether the email starts a thread, and so has a subject of its own. */\n startsThread: boolean\n subject: string\n body: string\n /** The words as they were drafted, so an edit can be told from a confirmation. */\n original: { subject: string; body: string }\n source: OutreachStepOverrideSource\n /** The member ticked the confirmation. */\n confirmed: boolean\n /** Used, kept as the template, or still open. */\n decision: 'pending' | 'use' | 'keep'\n /** The AI's prompt and model, for the override's audit record. */\n prompt?: string\n model?: string\n}\n\n/** A drafted step as the route answered it, ready for the member. */\nexport function outreachDraftState(\n draft: Pick<OutreachCurateDraft, 'stepIndex' | 'subject' | 'body'>,\n steps: readonly OutreachSequenceStep[],\n source: OutreachStepOverrideSource,\n audit?: { prompt: string; model: string },\n): OutreachCuratedDraftState {\n const startsThread = !isInThreadEmailStep(steps, draft.stepIndex)\n const subject = startsThread ? (draft.subject ?? '') : ''\n return {\n stepIndex: draft.stepIndex,\n startsThread,\n subject,\n body: draft.body,\n original: { subject, body: draft.body },\n source,\n confirmed: false,\n decision: 'pending',\n ...(audit ? { prompt: audit.prompt, model: audit.model } : {}),\n }\n}\n\n/** The member's own starting point: the step's words, for them to rewrite by hand. */\nexport function outreachMemberDraftStates(\n steps: readonly OutreachSequenceStep[],\n stepIndexes: readonly number[],\n): OutreachCuratedDraftState[] {\n return stepIndexes.flatMap((stepIndex) => {\n const step = steps[stepIndex]\n if (step?.kind !== 'email') return []\n return [\n outreachDraftState(\n // A step that sends a CRM template has no words here to start from.\n { stepIndex, subject: step.subject, body: step.templateId ? '' : step.body },\n steps,\n 'member',\n ),\n ]\n })\n}\n\n/** The request a USED draft becomes; `null` for one that was kept or is still open. */\nexport function outreachDraftToOverride(\n draft: OutreachCuratedDraftState,\n): OutreachStepOverrideRequest | null {\n if (draft.decision !== 'use') return null\n const edited =\n draft.source === 'ai' &&\n (draft.body !== draft.original.body || (draft.startsThread && draft.subject !== draft.original.subject))\n return {\n stepIndex: draft.stepIndex,\n ...(draft.startsThread ? { subject: draft.subject } : {}),\n body: draft.body,\n source: draft.source,\n ...(edited ? { edited: true } : {}),\n ...(draft.source === 'ai' && draft.prompt ? { prompt: draft.prompt } : {}),\n ...(draft.source === 'ai' && draft.model ? { model: draft.model } : {}),\n }\n}\n\n/** The validator's verdict on the draft as it stands. */\nexport function outreachDraftIssues(draft: OutreachCuratedDraftState): OutreachValidationIssue[] {\n return validateOutreachCuratedStep(\n { subject: draft.startsThread ? draft.subject : undefined, body: draft.body, startsThread: draft.startsThread },\n `stepOverrides.${draft.stepIndex}`,\n )\n}\n\n/** Whether the draft may be used as it stands: confirmed, and nothing the validator refuses. */\nexport function outreachDraftUsable(draft: OutreachCuratedDraftState): boolean {\n return draft.confirmed && !outreachDraftIssues(draft).some((issue) => issue.severity === 'error')\n}\n\nexport interface OutreachCuratedStepEditorProps {\n draft: OutreachCuratedDraftState\n /** \"Email 1 · step 1\", as the sequence's preview names it. */\n label: string\n onChange(next: OutreachCuratedDraftState): void\n disabled?: boolean\n}\n\nexport function OutreachCuratedStepEditor(props: OutreachCuratedStepEditorProps) {\n const { draft, label, onChange } = props\n const disabled = props.disabled === true\n const issues = useMemo(() => outreachDraftIssues(draft), [draft])\n const errors = issues.filter((issue) => issue.severity === 'error')\n const warnings = issues.filter((issue) => issue.severity === 'warning')\n const change = (next: Partial<OutreachCuratedDraftState>) => onChange({ ...draft, ...next })\n const who = draft.source === 'ai' ? 'AI draft' : 'Your words'\n\n if (draft.decision !== 'pending') {\n return (\n <Paper variant=\"outlined\" sx={{ p: 1.5 }} aria-label={`${label} — ${draft.decision === 'use' ? 'curated' : 'template kept'}`}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {label}\n </Typography>\n <Chip\n size=\"small\"\n color={draft.decision === 'use' ? 'success' : 'default'}\n label={draft.decision === 'use' ? `Curated · ${who}` : 'Template kept'}\n />\n <Button size=\"small\" onClick={() => change({ decision: 'pending' })} disabled={disabled}>\n Change\n </Button>\n </Stack>\n </Paper>\n )\n }\n\n return (\n <Paper variant=\"outlined\" sx={{ p: 1.5 }} aria-label={`${label} — draft`}>\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {label}\n </Typography>\n <Chip size=\"small\" variant=\"outlined\" label={who} />\n </Stack>\n {draft.startsThread ? (\n <TextField\n label=\"Subject\"\n value={draft.subject}\n onChange={(event) => change({ subject: event.target.value, confirmed: false })}\n disabled={disabled}\n size=\"small\"\n fullWidth\n error={errors.some((issue) => issue.path.endsWith('.subject'))}\n />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n Sent as a reply in the thread, under the thread’s subject.\n </Typography>\n )}\n <TextField\n label=\"Email\"\n value={draft.body}\n onChange={(event) => change({ body: event.target.value, confirmed: false })}\n disabled={disabled}\n multiline\n minRows={6}\n fullWidth\n error={errors.some((issue) => issue.path.endsWith('.body'))}\n helperText=\"Plain text. The footer is added when it’s sent.\"\n />\n {errors.length ? (\n <Alert severity=\"error\">\n <Stack component=\"ul\" spacing={0.5} sx={{ m: 0, pl: 2 }}>\n {errors.map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n </Alert>\n ) : null}\n {warnings.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {warnings.map((issue) => issue.message).join(' ')}\n </Typography>\n ) : null}\n <FormControlLabel\n control={\n <Checkbox\n checked={draft.confirmed}\n onChange={(event) => change({ confirmed: event.target.checked })}\n disabled={disabled || errors.length > 0}\n />\n }\n label={OUTREACH_CURATION_CONFIRMATION_LABEL}\n />\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={disabled || !outreachDraftUsable(draft)}\n onClick={() => change({ decision: 'use' })}\n >\n Use this\n </Button>\n <Button size=\"small\" disabled={disabled} onClick={() => change({ decision: 'keep' })}>\n Keep the template\n </Button>\n </Stack>\n </Stack>\n </Paper>\n )\n}\nOutreachCuratedStepEditor.displayName = 'OutreachCuratedStepEditor'\n\nexport default OutreachCuratedStepEditor\n"],"names":["Alert","Button","Checkbox","Chip","FormControlLabel","Paper","Stack","TextField","Typography","useMemo","isInThreadEmailStep","validateOutreachCuratedStep","OUTREACH_CURATION_CONFIRMATION_LABEL","outreachDraftState","draft","steps","source","audit","startsThread","stepIndex","subject","body","original","confirmed","decision","prompt","model","outreachMemberDraftStates","stepIndexes","flatMap","step","kind","templateId","outreachDraftToOverride","edited","outreachDraftIssues","undefined","outreachDraftUsable","some","issue","severity","OutreachCuratedStepEditor","props","label","onChange","disabled","issues","errors","filter","warnings","change","next","who","variant","sx","p","aria-label","direction","spacing","alignItems","flexWrap","rowGap","flexGrow","size","color","onClick","value","event","target","fullWidth","error","path","endsWith","multiline","minRows","helperText","length","component","m","pl","map","li","message","code","join","control","checked","displayName"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type OutreachEnrollPreviewPerson } from '../model/outreach-api';
2
2
  import { type OutreachAttestationKind, type OutreachSequence } from '../model/outreach.types';
3
+ import { type OutreachCuratedDraftState } from './curated-step-editor';
3
4
  import type { OutreachApi } from './use-outreach-api';
4
5
  export interface OutreachEnrollDialogProps {
5
6
  open: boolean;
@@ -11,11 +12,25 @@ export interface OutreachEnrollDialogProps {
11
12
  uid: string | null;
12
13
  api: OutreachApi;
13
14
  }
15
+ /** Where one person's curated drafts stand (AGL-3324). */
16
+ type Curation = {
17
+ status: 'idle';
18
+ } | {
19
+ status: 'drafting';
20
+ } | {
21
+ status: 'failed';
22
+ message: string;
23
+ } | {
24
+ status: 'ready';
25
+ };
14
26
  /** What the rep has supplied for one person. */
15
27
  interface Supplied {
16
28
  include: boolean;
17
29
  personalLine: string;
18
30
  attestations: OutreachAttestationKind[];
31
+ /** The email steps drafted for this person, by step index; absent until they curate. */
32
+ drafts?: Record<number, OutreachCuratedDraftState>;
33
+ curation?: Curation;
19
34
  }
20
35
  /** Whether a person may be sent to Confirm as the rep has left them. */
21
36
  export declare function outreachPersonReady(person: OutreachEnrollPreviewPerson, supplied: Supplied | undefined): boolean;
@@ -22,6 +22,8 @@ import { Alert, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, Di
22
22
  import { useMemo, useState } from "react";
23
23
  import { OUTREACH_ENROLL_BATCH_MAX } from "../model/outreach-api.js";
24
24
  import { OUTREACH_ATTESTATION_LABELS, OUTREACH_PERSONAL_LINE_MAX } from "../model/outreach.types.js";
25
+ import { outreachEmailStepLabel } from "./curate-step-dialog.js";
26
+ import { OutreachCuratedStepEditor, outreachDraftState, outreachDraftToOverride, outreachMemberDraftStates } from "./curated-step-editor.js";
25
27
  import { OutreachLoading, OutreachLoadProblem } from "./outreach-ui.js";
26
28
  import { useOutreachContactSearch, useOutreachLeadSearch, useOutreachSavedViews } from "./use-outreach-crm.js";
27
29
  const STATUS_LABELS = {
@@ -34,10 +36,21 @@ const STATUS_COLORS = {
34
36
  needs_confirmation: 'warning',
35
37
  blocked: 'default'
36
38
  };
39
+ /** The drafts the rep used, as the enroll and the preview carry them. */ function usedOverrides(supplied) {
40
+ var _ref;
41
+ return Object.values((_ref = supplied == null ? void 0 : supplied.drafts) != null ? _ref : {}).map(outreachDraftToOverride).filter((override)=>override !== null);
42
+ }
43
+ /** Whether a drafted step is still waiting on the rep to use it or keep the template. */ function hasOpenDrafts(supplied) {
44
+ var _ref;
45
+ return Object.values((_ref = supplied == null ? void 0 : supplied.drafts) != null ? _ref : {}).some((draft)=>draft.decision === 'pending');
46
+ }
37
47
  /** Whether a person may be sent to Confirm as the rep has left them. */ export function outreachPersonReady(person, supplied) {
38
48
  if (person.status === 'blocked' || !(supplied == null ? void 0 : supplied.include)) return false;
39
49
  const line = supplied.personalLine.trim();
40
50
  if (line.length > OUTREACH_PERSONAL_LINE_MAX) return false;
51
+ // A draft the rep has read but neither used nor kept holds them back
52
+ // (AGL-3324): nothing is enrolled with a decision still open.
53
+ if (hasOpenDrafts(supplied)) return false;
41
54
  if (person.status === 'eligible') return true;
42
55
  if (person.requires.personalLine && !line) return false;
43
56
  return person.requires.attestations.every((kind)=>supplied.attestations.includes(kind));
@@ -133,7 +146,7 @@ const STATUS_COLORS = {
133
146
  ]);
134
147
  const waiting = answer ? answer.people.filter((person)=>{
135
148
  var _supplied_person_personId;
136
- return person.status === 'needs_confirmation' && ((_supplied_person_personId = supplied[person.personId]) == null ? void 0 : _supplied_person_personId.include) && !outreachPersonReady(person, supplied[person.personId]);
149
+ return person.status !== 'blocked' && ((_supplied_person_personId = supplied[person.personId]) == null ? void 0 : _supplied_person_personId.include) && !outreachPersonReady(person, supplied[person.personId]);
137
150
  }).length : 0;
138
151
  const enroll = async ()=>{
139
152
  if (!answer || !ready.length) return;
@@ -153,7 +166,9 @@ const STATUS_COLORS = {
153
166
  }, {
154
167
  personalLine: (_ref = (_supplied_person_personId = supplied[person.personId]) == null ? void 0 : _supplied_person_personId.personalLine.trim()) != null ? _ref : '',
155
168
  attestations: (_ref1 = (_supplied_person_personId1 = supplied[person.personId]) == null ? void 0 : _supplied_person_personId1.attestations) != null ? _ref1 : []
156
- });
169
+ }, usedOverrides(supplied[person.personId]).length ? {
170
+ stepOverrides: usedOverrides(supplied[person.personId])
171
+ } : {});
157
172
  }));
158
173
  setStage({
159
174
  kind: 'done',
@@ -180,6 +195,7 @@ const STATUS_COLORS = {
180
195
  try {
181
196
  var _ref;
182
197
  var _supplied_person_personId;
198
+ const overrides = usedOverrides(supplied[person.personId]);
183
199
  const result = await api.previewEmail(_extends({
184
200
  sequenceId: sequence.id
185
201
  }, person.target === 'lead' && person.leadId ? {
@@ -188,7 +204,9 @@ const STATUS_COLORS = {
188
204
  contactId: person.contactId
189
205
  }, {
190
206
  personalLine: (_ref = (_supplied_person_personId = supplied[person.personId]) == null ? void 0 : _supplied_person_personId.personalLine.trim()) != null ? _ref : ''
191
- }));
207
+ }, overrides.length ? {
208
+ stepOverrides: overrides
209
+ } : {}));
192
210
  setEmailPreview((previous)=>_extends({}, previous, {
193
211
  [person.personId]: {
194
212
  status: 'ready',
@@ -204,6 +222,63 @@ const STATUS_COLORS = {
204
222
  }));
205
223
  }
206
224
  };
225
+ /**
226
+ * Curate for this person (AGL-3324): every email step drafted from their
227
+ * record, the sequence's steps and the personal line as written so far,
228
+ * shown under them for the rep to edit and confirm. Nothing is stored
229
+ * until Enroll carries the drafts they used.
230
+ */ const curate = async (person)=>{
231
+ supply(person.personId, {
232
+ curation: {
233
+ status: 'drafting'
234
+ }
235
+ });
236
+ try {
237
+ var _ref;
238
+ var _supplied_person_personId;
239
+ const answer = await api.curateDrafts(_extends({
240
+ sequenceId: sequence.id
241
+ }, person.target === 'lead' && person.leadId ? {
242
+ leadId: person.leadId
243
+ } : {
244
+ contactId: person.contactId
245
+ }, {
246
+ personalLine: (_ref = (_supplied_person_personId = supplied[person.personId]) == null ? void 0 : _supplied_person_personId.personalLine.trim()) != null ? _ref : ''
247
+ }));
248
+ const audit = {
249
+ prompt: answer.prompt,
250
+ model: answer.model
251
+ };
252
+ supply(person.personId, {
253
+ curation: {
254
+ status: 'ready'
255
+ },
256
+ drafts: Object.fromEntries(answer.drafts.map((draft)=>[
257
+ draft.stepIndex,
258
+ outreachDraftState(draft, sequence.steps, 'ai', audit)
259
+ ]))
260
+ });
261
+ } catch (error) {
262
+ supply(person.personId, {
263
+ curation: {
264
+ status: 'failed',
265
+ message: error.message
266
+ }
267
+ });
268
+ }
269
+ };
270
+ /** The rep's own words for every email step, when the AI cannot draft. */ const writeThemselves = (person)=>{
271
+ const stepIndexes = sequence.steps.map((step, index)=>step.kind === 'email' ? index : -1).filter((index)=>index >= 0);
272
+ supply(person.personId, {
273
+ curation: {
274
+ status: 'ready'
275
+ },
276
+ drafts: Object.fromEntries(outreachMemberDraftStates(sequence.steps, stepIndexes).map((draft)=>[
277
+ draft.stepIndex,
278
+ draft
279
+ ]))
280
+ });
281
+ };
207
282
  const title = `Enroll people in ${sequence.name}`;
208
283
  return /*#__PURE__*/ _jsxs(Dialog, {
209
284
  open: props.open,
@@ -464,17 +539,20 @@ const STATUS_COLORS = {
464
539
  "aria-label": "People",
465
540
  children: answer.people.map((person)=>/*#__PURE__*/ _jsx(PersonRow, {
466
541
  person: person,
542
+ steps: sequence.steps,
467
543
  supplied: supplied[person.personId],
468
544
  onSupply: (next)=>supply(person.personId, next),
469
545
  emailPreview: emailPreview[person.personId],
470
546
  onPreviewEmail: ()=>void previewEmail(person),
547
+ onCurate: ()=>void curate(person),
548
+ onWriteThemselves: ()=>writeThemselves(person),
471
549
  disabled: stage.kind === 'enrolling'
472
550
  }, person.personId))
473
551
  }),
474
552
  waiting ? /*#__PURE__*/ _jsx(Typography, {
475
553
  variant: "body2",
476
554
  color: "text.secondary",
477
- children: `${waiting} ${waiting === 1 ? 'person still needs' : 'people still need'} a personal line or a confirmation, and won't be enrolled until they have one.`
555
+ children: `${waiting} ${waiting === 1 ? 'person still needs' : 'people still need'} a personal line or a confirmation, or a decision on a draft, and won't be enrolled until they have one.`
478
556
  }) : null
479
557
  ]
480
558
  }) : null,
@@ -541,12 +619,17 @@ function summary(answer) {
541
619
  return `${count('eligible')} eligible · ${count('needs_confirmation')} need you · ${count('blocked')} blocked`;
542
620
  }
543
621
  function PersonRow(props) {
544
- var _ref, _ref1;
622
+ var _ref, _ref1, _ref2, _ref3;
545
623
  var _props_emailPreview, _props_emailPreview1, _props_emailPreview2;
546
624
  const { person, supplied, disabled } = props;
547
625
  const name = person.name || person.email || person.personId;
548
626
  const line = (_ref = supplied == null ? void 0 : supplied.personalLine) != null ? _ref : '';
549
627
  const tooLong = line.trim().length > OUTREACH_PERSONAL_LINE_MAX;
628
+ const curation = (_ref1 = supplied == null ? void 0 : supplied.curation) != null ? _ref1 : {
629
+ status: 'idle'
630
+ };
631
+ const drafts = Object.values((_ref2 = supplied == null ? void 0 : supplied.drafts) != null ? _ref2 : {}).sort((a, b)=>a.stepIndex - b.stepIndex);
632
+ const usedCount = drafts.filter((draft)=>draft.decision === 'use').length;
550
633
  return /*#__PURE__*/ _jsx(Paper, {
551
634
  component: "li",
552
635
  variant: "outlined",
@@ -630,7 +713,7 @@ function PersonRow(props) {
630
713
  children: [
631
714
  /*#__PURE__*/ _jsx(FormControlLabel, {
632
715
  control: /*#__PURE__*/ _jsx(Checkbox, {
633
- checked: (_ref1 = supplied == null ? void 0 : supplied.include) != null ? _ref1 : true,
716
+ checked: (_ref3 = supplied == null ? void 0 : supplied.include) != null ? _ref3 : true,
634
717
  onChange: (event)=>props.onSupply({
635
718
  include: event.target.checked
636
719
  }),
@@ -676,15 +759,73 @@ function PersonRow(props) {
676
759
  }, kind);
677
760
  })
678
761
  }) : null,
679
- /*#__PURE__*/ _jsx(Stack, {
762
+ /*#__PURE__*/ _jsxs(Stack, {
680
763
  direction: "row",
681
- children: /*#__PURE__*/ _jsx(Button, {
682
- size: "small",
683
- onClick: props.onPreviewEmail,
684
- disabled: disabled,
685
- children: "Preview the first email"
686
- })
764
+ spacing: 1,
765
+ sx: {
766
+ flexWrap: 'wrap',
767
+ rowGap: 1
768
+ },
769
+ children: [
770
+ /*#__PURE__*/ _jsx(Button, {
771
+ size: "small",
772
+ onClick: props.onPreviewEmail,
773
+ disabled: disabled,
774
+ children: "Preview the first email"
775
+ }),
776
+ /*#__PURE__*/ _jsx(Button, {
777
+ size: "small",
778
+ onClick: props.onCurate,
779
+ disabled: disabled || curation.status === 'drafting',
780
+ children: drafts.length ? 'Curate again' : 'Curate for this person'
781
+ })
782
+ ]
687
783
  }),
784
+ curation.status === 'drafting' ? /*#__PURE__*/ _jsx(OutreachLoading, {
785
+ label: "Drafting their emails…"
786
+ }) : null,
787
+ curation.status === 'failed' ? /*#__PURE__*/ _jsxs(Stack, {
788
+ spacing: 1,
789
+ children: [
790
+ /*#__PURE__*/ _jsx(Alert, {
791
+ severity: "warning",
792
+ children: curation.message
793
+ }),
794
+ /*#__PURE__*/ _jsx(Stack, {
795
+ direction: "row",
796
+ children: /*#__PURE__*/ _jsx(Button, {
797
+ size: "small",
798
+ onClick: props.onWriteThemselves,
799
+ disabled: disabled,
800
+ children: "Write them yourself"
801
+ })
802
+ })
803
+ ]
804
+ }) : null,
805
+ drafts.length ? /*#__PURE__*/ _jsxs(Stack, {
806
+ spacing: 1,
807
+ "aria-label": `Curated emails for ${name}`,
808
+ children: [
809
+ /*#__PURE__*/ _jsx(Typography, {
810
+ variant: "caption",
811
+ color: "text.secondary",
812
+ children: usedCount ? `${usedCount} of ${drafts.length} ${drafts.length === 1 ? 'email' : 'emails'} curated for ${name}. Nothing is sent that you haven't confirmed.` : `Their emails, rewritten for them. Read each one, edit it, and use it or keep the template.`
813
+ }),
814
+ drafts.map((draft)=>/*#__PURE__*/ _jsx(OutreachCuratedStepEditor, {
815
+ draft: draft,
816
+ label: outreachEmailStepLabel(props.steps, draft.stepIndex),
817
+ onChange: (next)=>{
818
+ var _ref;
819
+ return props.onSupply({
820
+ drafts: _extends({}, (_ref = supplied == null ? void 0 : supplied.drafts) != null ? _ref : {}, {
821
+ [next.stepIndex]: next
822
+ })
823
+ });
824
+ },
825
+ disabled: disabled
826
+ }, draft.stepIndex))
827
+ ]
828
+ }) : null,
688
829
  ((_props_emailPreview = props.emailPreview) == null ? void 0 : _props_emailPreview.status) === 'loading' ? /*#__PURE__*/ _jsx(OutreachLoading, {
689
830
  label: "Writing the email…"
690
831
  }) : null,