@aglyn/plugins-outreach 1.0.0-beta.195 → 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.
- package/package.json +14 -14
- package/src/lib/components/curate-step-dialog.d.ts +50 -0
- package/src/lib/components/curate-step-dialog.js +235 -0
- package/src/lib/components/curate-step-dialog.js.map +1 -0
- package/src/lib/components/curated-step-editor.d.ts +80 -0
- package/src/lib/components/curated-step-editor.js +255 -0
- package/src/lib/components/curated-step-editor.js.map +1 -0
- package/src/lib/components/enroll-dialog.d.ts +15 -0
- package/src/lib/components/enroll-dialog.js +154 -13
- package/src/lib/components/enroll-dialog.js.map +1 -1
- package/src/lib/components/enrollments-table.d.ts +2 -0
- package/src/lib/components/enrollments-table.js +62 -8
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/use-outreach-api.d.ts +8 -1
- package/src/lib/components/use-outreach-api.js +12 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +8 -0
- package/src/lib/constants/api-routes.js +6 -0
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/compose.d.ts +12 -2
- package/src/lib/engine/compose.js +32 -6
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/curation-prompt.d.ts +73 -0
- package/src/lib/engine/curation-prompt.js +151 -0
- package/src/lib/engine/curation-prompt.js.map +1 -0
- package/src/lib/engine/enrollment-activity.d.ts +24 -0
- package/src/lib/engine/enrollment-activity.js +25 -0
- package/src/lib/engine/enrollment-activity.js.map +1 -1
- package/src/lib/engine/sequence-validation.d.ts +10 -1
- package/src/lib/engine/sequence-validation.js +54 -0
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/enrollment/step-overrides.d.ts +53 -0
- package/src/lib/enrollment/step-overrides.js +140 -0
- package/src/lib/enrollment/step-overrides.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +69 -2
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +36 -0
- package/src/lib/model/outreach.types.js +18 -0
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/step-preview.d.ts +3 -1
- package/src/lib/model/step-preview.js +6 -3
- package/src/lib/model/step-preview.js.map +1 -1
- package/src/lib/model/stored-records.d.ts +9 -1
- package/src/lib/model/stored-records.js +47 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/curate-routes.d.ts +63 -0
- package/src/lib/routes/curate-routes.js +424 -0
- package/src/lib/routes/curate-routes.js.map +1 -0
- package/src/lib/routes/enroll-routes.d.ts +7 -0
- package/src/lib/routes/enroll-routes.js +58 -4
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/preview-routes.js +15 -1
- package/src/lib/routes/preview-routes.js.map +1 -1
- package/src/lib/routes/register-routes.js +17 -0
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/runtime/send-job.js +2 -0
- 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
|
|
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: (
|
|
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__*/
|
|
762
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
680
763
|
direction: "row",
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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,
|