@yrpri/api 9.0.82 → 9.0.83

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.
@@ -1,15 +1,24 @@
1
- <%- include('../header.html.ejs') %>
1
+ <% if (!skipHeaderAndFooter) { %>
2
+ <%- include('../header.html.ejs') %>
3
+ <h2 style="font-weight: 600;line-height: 1.1em;margin: 0;font-size: 14pt;padding-top: 8px;">
4
+ <%- header %>
5
+ </h2>
6
+ <% } %>
2
7
 
3
- <h2 style="font-weight: 600;line-height: 1.1em;margin: 0;font-size: 14pt;padding-top: 8px;">
4
- <%- header %>
5
- </h2>
8
+ <% if (!skipHeaderAndFooter) { %>
9
+ <p style="margin-top: 8px;margin-bottom: 8px;">
10
+ <%- content %>
11
+ </p>
12
+ <% } %>
6
13
 
7
- <p style="margin-top: 8px;margin-bottom: 8px;">
8
- <%- content %>
9
- </p>
14
+ <% if (skipHeaderAndFooter) { %>
15
+ <%- content %>
16
+ <% } %>
10
17
 
11
- <p style="margin-top: 5px;padding-top: 8px;">
12
- <%- link %>
13
- </p>
14
18
 
15
- <%- include('../footer.html.ejs') %>
19
+ <% if (!skipHeaderAndFooter) { %>
20
+ <p style="margin-top: 5px;padding-top: 8px;">
21
+ <%- link %>
22
+ </p>
23
+ <%- include('../footer.html.ejs') %>
24
+ <% } %>
@@ -1,9 +1,12 @@
1
- <%- include('../header.text.ejs') %>
2
1
 
3
- <%- header %>
2
+ <% if (!skipHeaderAndFooter) { %>
3
+ <%- include('../header.text.ejs') %>
4
+ <%- header %>
5
+ <% } %>
4
6
 
5
7
  <%- content %>
6
8
 
7
- <%- link %>
8
-
9
- <%- include('../footer.text.ejs') %>
9
+ <% if (!skipHeaderAndFooter) { %>
10
+ <%- link %>
11
+ <%- include('../footer.text.ejs') %>
12
+ <% } %>
@@ -107,8 +107,17 @@ const importPost = (post, done) => {
107
107
  for (let i = 0; i < answers.length; i += 1) {
108
108
  if (answers[i]) {
109
109
  if (answers[i].value) {
110
- description += " ";
111
- description += answers[i].value.trim();
110
+ try {
111
+ description += " ";
112
+ description += answers[i].value.trim();
113
+ }
114
+ catch (error) {
115
+ description += answers[i].value;
116
+ console.warn(`Error trimming answer to description: ${answers[i].value}`);
117
+ }
118
+ }
119
+ else {
120
+ console.error(`No value for answer in adding to description: ${answers[i]}`);
112
121
  }
113
122
  }
114
123
  }
@@ -403,7 +403,13 @@ export class AgentTools extends BaseAssistantTools {
403
403
  }
404
404
  async renderAgentRunWidget(agent, run) {
405
405
  const subscription = await this.assistant.getCurrentSubscription();
406
- const workflowBase64 = btoa(JSON.stringify(run.workflow));
406
+ const workflowCopy = JSON.parse(JSON.stringify(run.workflow));
407
+ if (workflowCopy.steps) {
408
+ workflowCopy.steps.forEach((step) => {
409
+ step.emailInstructions = "";
410
+ });
411
+ }
412
+ const workflowBase64 = btoa(JSON.stringify(workflowCopy));
407
413
  return `<yp-agent-run-widget
408
414
  agentProductId="${agent.id}"
409
415
  runId="${run.id}"
@@ -592,6 +592,63 @@ export class PolicySynthAgentsController {
592
592
  else {
593
593
  console.log("Test o1 1712 models already exist");
594
594
  }
595
+ const geminiProModel = await PsAiModel.findOne({
596
+ where: {
597
+ name: "Gemini 1.5 Pro 2",
598
+ },
599
+ });
600
+ if (!geminiProModel) {
601
+ const geminiProConfig = {
602
+ type: PsAiModelType.Text,
603
+ modelSize: PsAiModelSize.Medium,
604
+ provider: "google",
605
+ prices: {
606
+ costInTokensPerMillion: 1.25,
607
+ costOutTokensPerMillion: 5.0,
608
+ currency: "USD",
609
+ },
610
+ maxTokensOut: 8192,
611
+ defaultTemperature: 0.0,
612
+ model: "gemini-1.5-pro-002",
613
+ active: true,
614
+ };
615
+ const geminiPro = await PsAiModel.create({
616
+ name: "Gemini 1.5 Pro 2",
617
+ organization_id: 1,
618
+ user_id: userId,
619
+ configuration: geminiProConfig,
620
+ });
621
+ }
622
+ else {
623
+ console.log("Gemini 1.5 Pro 2 models already exist");
624
+ }
625
+ const geminiPro15FlashModel = await PsAiModel.findOne({
626
+ where: {
627
+ name: "Gemini 1.5 Flash 2",
628
+ },
629
+ });
630
+ if (!geminiPro15FlashModel) {
631
+ const geminiPro15FlashConfig = {
632
+ type: PsAiModelType.Text,
633
+ modelSize: PsAiModelSize.Small,
634
+ provider: "google",
635
+ prices: {
636
+ costInTokensPerMillion: 0.075,
637
+ costOutTokensPerMillion: 0.3,
638
+ currency: "USD",
639
+ },
640
+ maxTokensOut: 8192,
641
+ defaultTemperature: 0.0,
642
+ model: "gemini-1.5-flash-002",
643
+ active: true,
644
+ };
645
+ const geminiPro15Flash = await PsAiModel.create({
646
+ name: "Gemini 1.5 Flash 2",
647
+ organization_id: 1,
648
+ user_id: userId,
649
+ configuration: geminiPro15FlashConfig,
650
+ });
651
+ }
595
652
  }
596
653
  initializeRoutes() {
597
654
  this.router.get("/:groupId", auth.can("view group"), this.getAgent);
@@ -631,7 +688,8 @@ PolicySynthAgentsController.setupApiKeysForGroup = async (group) => {
631
688
  const anthropicSonnet = await findLatestActiveModel("Anthropic Sonnet 3.5");
632
689
  const openAiGpt4 = await findLatestActiveModel("GPT-4o");
633
690
  const openAiGpt4Mini = await findLatestActiveModel("GPT-4o Mini");
634
- const geminiPro = await findLatestActiveModel("Gemini 1.5 Pro");
691
+ const geminiPro = await findLatestActiveModel("Gemini 1.5 Pro 2");
692
+ const geminiPro15Flash = await findLatestActiveModel("Gemini 1.5 Flash 2");
635
693
  const openAio1Preview = await findLatestActiveModel("o1 Preview");
636
694
  const openAio1Mini = await findLatestActiveModel("o1 Mini");
637
695
  const openAio11712 = await findLatestActiveModel("o1 24");
@@ -672,6 +730,12 @@ PolicySynthAgentsController.setupApiKeysForGroup = async (group) => {
672
730
  apiKey: process.env.GEMINI_API_KEY,
673
731
  });
674
732
  }
733
+ if (geminiPro15Flash && process.env.GEMINI_API_KEY) {
734
+ groupAccessConfig.push({
735
+ aiModelId: geminiPro15Flash.id,
736
+ apiKey: process.env.GEMINI_API_KEY,
737
+ });
738
+ }
675
739
  if (openAio11712 && process.env.OPENAI_API_KEY) {
676
740
  groupAccessConfig.push({
677
741
  aiModelId: openAio11712.id,
@@ -679,5 +743,6 @@ PolicySynthAgentsController.setupApiKeysForGroup = async (group) => {
679
743
  });
680
744
  }
681
745
  group.set("private_access_configuration", groupAccessConfig);
746
+ group.changed("private_access_configuration", true);
682
747
  await group.save();
683
748
  };
@@ -0,0 +1,55 @@
1
+ // AgentNotificationEmailService.ts
2
+ import models from "../../models/index.cjs";
3
+ import { EmailTemplateRenderer } from "./emailTemplateRenderer.js";
4
+ import queue from "../../active-citizen/workers/queue.cjs";
5
+ import { NotificationAgentQueueManager } from "./notificationAgentQueueManager.js";
6
+ const dbModels = models;
7
+ const Group = dbModels.Group;
8
+ const User = dbModels.User;
9
+ const Community = dbModels.Community;
10
+ const Domain = dbModels.Domain;
11
+ export class AgentInviteManager {
12
+ /**
13
+ * Send a notification email to group admins about the current workflow step or completion.
14
+ */
15
+ static async sendInviteEmail(link, agentRunId, groupId, user) {
16
+ try {
17
+ const agentRun = await NotificationAgentQueueManager.getAgentRun(agentRunId);
18
+ if (!agentRun) {
19
+ console.error("Agent run not found");
20
+ return;
21
+ }
22
+ const subject = `${agentRun?.Subscription?.Plan?.AgentProduct?.name} - ${agentRun?.workflow?.steps[agentRun?.workflow?.currentStepIndex]?.shortName} invite`;
23
+ // 2. Extract bundleId and userId if needed
24
+ const bundleId = agentRun?.Subscription?.Plan?.AgentProduct?.AgentBundles?.[0]?.id || 1;
25
+ const group = (await Group.findOne({
26
+ where: { id: groupId },
27
+ include: [
28
+ {
29
+ model: Community,
30
+ attributes: ["id", "name"],
31
+ include: [{ model: Domain, attributes: ["id", "name", "domain_name"] }],
32
+ },
33
+ ],
34
+ }));
35
+ const emailContent = EmailTemplateRenderer.renderEmail("", user.name, agentRun.Subscription?.Plan?.AgentProduct?.name || "", agentRun.workflow, link, "https://evoly.ai/is/amplifier/img/amplifier-logo.png", "https://evoly.ai/is/amplifier/img/evoly-bw-logo.png", "https://evoly.ai/", "© 2024 Evoly ehf, Vegmuli 8, 108, Reykjavik, Iceland");
36
+ queue.add("send-one-email", {
37
+ subject: subject,
38
+ template: "general_user_notification",
39
+ user: user,
40
+ domain: group.Community?.Domain,
41
+ group: group,
42
+ object: {},
43
+ header: "",
44
+ content: emailContent,
45
+ link: link,
46
+ skipHeaderAndFooter: true,
47
+ }, { priority: "high" });
48
+ console.log("AgentInviteManager: Email enqueued successfully");
49
+ }
50
+ catch (error) {
51
+ console.error("AgentInviteManager: Error sending notification email", error);
52
+ throw error;
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,362 @@
1
+ /**
2
+ * A flexible renderer for "Competitor Agent" (or any) workflow emails.
3
+ *
4
+ * - The CTA block (button + instructions) is inserted *within* the loop,
5
+ * right after the step that is currently active (currentStepIndex).
6
+ * - Branding (logo, brand name, footer text, etc.) are all passed in,
7
+ * so they are not hardcoded in the template.
8
+ */
9
+ export class EmailTemplateRenderer {
10
+ /**
11
+ * Render the dynamic email with steps, highlighting the current step,
12
+ * and inserting the CTA block immediately after the current step only.
13
+ *
14
+ * @param recipientName - e.g. "Petur".
15
+ * @param invitedBy - e.g. "Robert Bjarnason".
16
+ * @param agentName - e.g. "Competitor Agent".
17
+ * @param workflow - The workflow with steps + currentStepIndex.
18
+ * @param ctaLink - Destination URL when clicking the CTA button.
19
+ * @param brandLogoUrl - URL to your brand’s logo.
20
+ * @param brandName - Display name of the brand (e.g., "amplifier powered by Evoly").
21
+ * @param footerText - Footer text, e.g. "© 2024 Evoly ehf, Vegmuli 8..."
22
+ * @param footerEmailSettingsLink - Link to "Change email settings".
23
+ */
24
+ static renderEmail(recipientName, invitedBy, agentName, workflow, ctaLink, brandLogoUrl, brandName, footerText, footerEmailSettingsLink) {
25
+ // Identify the current step
26
+ const currentStepIndex = workflow.currentStepIndex;
27
+ const currentStep = workflow.steps[currentStepIndex] || {};
28
+ // Pull out CTA text + instructions from the current step
29
+ const ctaText = currentStep.emailCallForAction || "Continue";
30
+ const instructionsText = currentStep.emailInstructions || "";
31
+ // We'll build the steps as HTML in a single array, so that we can:
32
+ // - Render each step
33
+ // - If it's the current step, we insert the CTA block right afterward
34
+ const stepsHtmlArray = [];
35
+ workflow.steps.forEach((step, idx) => {
36
+ const isActive = idx === currentStepIndex;
37
+ // Bubble color
38
+ const bubbleBg = isActive ? "#e74c3c" : "#eeeeee";
39
+ const bubbleTextColor = isActive ? "#ffffff" : "#666666";
40
+ // Step text color
41
+ const stepTextColor = isActive ? "#1d211c" : "rgba(29, 33, 28, 0.5)";
42
+ // Single step row
43
+ stepsHtmlArray.push(`
44
+ <tr>
45
+ <td
46
+ style="
47
+ width: 25px;
48
+ text-align: center;
49
+ vertical-align: top;
50
+ "
51
+ >
52
+ <table
53
+ role="presentation"
54
+ style="
55
+ width: 25px;
56
+ height: 25px;
57
+ border-spacing: 0;
58
+ border-collapse: collapse;
59
+ margin-top: 2px;
60
+ "
61
+ >
62
+ <tr>
63
+ <td
64
+ style="
65
+ width: 25px;
66
+ height: 25px;
67
+ border-radius: 50%;
68
+ background-color: ${bubbleBg};
69
+ color: ${bubbleTextColor};
70
+ font-weight: 500;
71
+ font-size: 14px;
72
+ text-align: center;
73
+ line-height: 25px;
74
+ font-family: 'Prompt', Arial, sans-serif;
75
+ "
76
+ >
77
+ ${idx + 1}
78
+ </td>
79
+ </tr>
80
+ </table>
81
+ </td>
82
+ <td
83
+ style="
84
+ padding: 0 0 0 16px;
85
+ color: #1d211c;
86
+ font-weight: normal;
87
+ vertical-align: middle;
88
+ "
89
+ >
90
+ <strong style="color:${stepTextColor}">
91
+ ${step.shortName}
92
+ </strong>:
93
+ <span style="color:${stepTextColor}">
94
+ ${step.shortDescription}
95
+ </span>
96
+ </td>
97
+ </tr>
98
+ <tr><td style="height: 8px; line-height: 8px; font-size: 0"></td></tr>
99
+ `);
100
+ // If this step is the current step, we drop in the CTA block immediately after:
101
+ if (isActive) {
102
+ stepsHtmlArray.push(`
103
+ <!-- CTA/instructions block for the current step only -->
104
+ <tr>
105
+ <td colspan="2">
106
+ <table
107
+ role="presentation"
108
+ style="
109
+ width: 100%;
110
+ border-spacing: 0;
111
+ border-collapse: collapse;
112
+ background-color: rgba(255, 220, 47, 0.1);
113
+ border-radius: 8px;
114
+ padding: 150px;
115
+ text-align: center;
116
+ "
117
+ >
118
+ <!-- Button Section -->
119
+ <tr>
120
+ <td align="center" style="padding: 36px 0 0 0">
121
+ <table
122
+ role="presentation"
123
+ style="
124
+ border-spacing: 0;
125
+ border-collapse: collapse;
126
+ margin: 0 auto;
127
+ "
128
+ >
129
+ <tr>
130
+ <td
131
+ style="
132
+ background-color: #ffdc2f;
133
+ padding: 10px 20px;
134
+ border-radius: 50px;
135
+ text-align: center;
136
+ font-weight: 600;
137
+ font-size: 14px;
138
+ font-family: 'Prompt', Arial, sans-serif;
139
+ "
140
+ >
141
+ <a
142
+ href="${ctaLink}"
143
+ style="
144
+ text-decoration: none;
145
+ color: #1d211c;
146
+ display: inline-block;
147
+ "
148
+ >
149
+ ${ctaText}
150
+ </a>
151
+ </td>
152
+ </tr>
153
+ </table>
154
+ </td>
155
+ </tr>
156
+
157
+ <!-- Instructions below the button -->
158
+ <tr>
159
+ <td
160
+ style="
161
+ padding: 22px 0 36px 0;
162
+ color: rgba(29, 33, 28, 1);
163
+ font-weight: normal;
164
+ line-height: 1.5;
165
+ text-align: center;
166
+ "
167
+ >
168
+ <table
169
+ role="presentation"
170
+ style="
171
+ width: 100%;
172
+ border-spacing: 0;
173
+ border-collapse: collapse;
174
+ "
175
+ >
176
+ <tr>
177
+ <td
178
+ style="
179
+ max-width: 540px;
180
+ margin: 0 auto;
181
+ text-align: center;
182
+ display: inline-block;
183
+ line-height: 1.5;
184
+ color: rgba(29, 33, 28, 0.5);
185
+ font-size: 15px;
186
+ padding: 0 20px;
187
+ "
188
+ >
189
+ ${instructionsText}
190
+ </td>
191
+ </tr>
192
+ </table>
193
+ </td>
194
+ </tr>
195
+ </table>
196
+ </td>
197
+ </tr>
198
+ <tr><td style="height: 16px; line-height: 16px; font-size: 0"></td></tr>
199
+ `);
200
+ }
201
+ });
202
+ // Join the array into one big HTML string
203
+ const stepsHtml = stepsHtmlArray.join("");
204
+ // Now wrap that in the main email layout, using the brand parameters
205
+ const outputHtml = `
206
+ <!DOCTYPE html>
207
+ <html lang="en">
208
+ <head>
209
+ <meta charset="UTF-8" />
210
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
211
+ <link
212
+ href="https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"
213
+ rel="stylesheet"
214
+ />
215
+ <link
216
+ href="https://fonts.googleapis.com/css2?family=Prompt:wght@100;200;300;400;500;600;700;800;900&display=swap"
217
+ rel="stylesheet"
218
+ />
219
+ <title>Email Notification</title>
220
+ </head>
221
+ <body
222
+ style="
223
+ margin: 0;
224
+ padding: 0;
225
+ background-color: #ffffff;
226
+ font-family: 'Poppins', Arial, sans-serif;
227
+ font-size: 15px;
228
+ color: #1d211c;
229
+ "
230
+ >
231
+ <table role="presentation" style="border-spacing: 0; width: 100%;">
232
+ <tr>
233
+ <td align="center">
234
+ <table
235
+ style="
236
+ max-width: 680px;
237
+ margin: 0 auto;
238
+ border: 1px solid rgba(29, 33, 28, 0.25);
239
+ padding: 0 30px 0 30px;
240
+ "
241
+ role="presentation"
242
+ >
243
+ <!-- BRANDING / LOGO -->
244
+ <tr>
245
+ <td style="padding-top: 40px; text-align: left">
246
+ <img
247
+ src="${brandLogoUrl}"
248
+ alt="${brandName} logo"
249
+ width="120"
250
+ style="border: 0; display: block"
251
+ />
252
+ </td>
253
+ </tr>
254
+
255
+ <!-- SALUTATION -->
256
+ <tr>
257
+ <td style="padding: 24px 0 0 0">
258
+ <div style="line-height: 1.5; margin-bottom: 20px">
259
+ Hi ${recipientName ? recipientName : "there"},
260
+ </div>
261
+ </td>
262
+ </tr>
263
+
264
+ ${invitedBy
265
+ ? `
266
+ <!-- INVITE LINE -->
267
+ <tr>
268
+ <td style="padding: 20px 0 0 0">
269
+ <div style="line-height: 1.5; margin-bottom: 20px">
270
+ <strong>${invitedBy}</strong> invited you to participate
271
+ in the <strong>${agentName}</strong> workflow.
272
+ </div>
273
+ </td>
274
+ </tr>
275
+ `
276
+ : `
277
+ <tr>
278
+ <td style="padding: 20px 0 0 0">
279
+ <div style="line-height: 1.5; margin-bottom: 20px">
280
+ The next step in the <strong>${agentName}</strong> workflow is ready.
281
+ </div>
282
+ </td>
283
+ </tr>`}
284
+
285
+ <!-- STEPS + CTA (inserted in the loop) -->
286
+ <tr>
287
+ <td>
288
+ <table align="left" style="width: auto">
289
+ ${stepsHtml}
290
+ </table>
291
+ </td>
292
+ </tr>
293
+
294
+ <!-- SPACER -->
295
+ <tr>
296
+ <td style="height: 60px; line-height: 60px; font-size: 0">
297
+ &nbsp;
298
+ </td>
299
+ </tr>
300
+
301
+ <!-- FOOTER with brand text -->
302
+ <tr>
303
+ <td align="center" style="padding: 30px 0; text-align: center">
304
+ <table
305
+ role="presentation"
306
+ style="
307
+ width: 100%;
308
+ border-spacing: 0;
309
+ border-collapse: collapse;
310
+ color: rgba(29, 33, 28, 0.5);
311
+ "
312
+ >
313
+ <tr>
314
+ <td style="padding-bottom: 12px">
315
+ <!-- If you want a simpler brand name or a second logo,
316
+ place it here. Or remove if not needed. -->
317
+ <img
318
+ src="${brandLogoUrl}"
319
+ alt="${brandName}"
320
+ width="80"
321
+ style="
322
+ display: block;
323
+ margin: 0 auto;
324
+ border: 0;
325
+ opacity: 0.5;
326
+ "
327
+ />
328
+ </td>
329
+ </tr>
330
+ <tr>
331
+ <td style="font-size: 12px; padding-bottom: 6px">
332
+ ${footerText}
333
+ </td>
334
+ </tr>
335
+ <tr>
336
+ <td>
337
+ <a
338
+ href="${footerEmailSettingsLink}"
339
+ style="
340
+ font-size: 12px;
341
+ text-decoration: underline;
342
+ color: inherit;
343
+ "
344
+ >
345
+ Change email settings
346
+ </a>
347
+ </td>
348
+ </tr>
349
+ </table>
350
+ </td>
351
+ </tr>
352
+
353
+ </table>
354
+ </td>
355
+ </tr>
356
+ </table>
357
+ </body>
358
+ </html>
359
+ `;
360
+ return outputHtml;
361
+ }
362
+ }
@@ -9,6 +9,7 @@ import { YpAgentProduct } from "../models/agentProduct.js";
9
9
  import { YpSubscription } from "../models/subscription.js";
10
10
  import { YpAgentProductBundle } from "../models/agentProductBundle.js";
11
11
  import queue from "../../active-citizen/workers/queue.cjs";
12
+ import { EmailTemplateRenderer } from "./emailTemplateRenderer.js";
12
13
  const dbModels = models;
13
14
  const Group = dbModels.Group;
14
15
  const User = dbModels.User;
@@ -38,14 +39,19 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
38
39
  else {
39
40
  console.error(`NotificationAgentQueueManager: WebSocket client with ID ${wsClientId} not found`);
40
41
  }
41
- const currentWorkflowStep = updatedWorkflow?.steps[updatedWorkflow?.currentStepIndex];
42
+ if (updatedWorkflow) {
43
+ await this.sendNotificationEmail(agent, agentRun, updatedWorkflow);
44
+ }
45
+ else {
46
+ console.error("NotificationAgentQueueManager: No updated workflow found");
47
+ }
48
+ }
49
+ async sendNotificationEmail(agent, agentRun, updatedWorkflow) {
42
50
  // Send email notification
43
- const subject = `${agentRun.Subscription?.Plan?.AgentProduct?.name} - ${currentWorkflowStep?.shortName} ready`;
44
- const content = `Next workflow step is ready: ${currentWorkflowStep?.description}`;
51
+ const subject = `${agentRun.Subscription?.Plan?.AgentProduct?.name} - ${updatedWorkflow?.steps[updatedWorkflow?.currentStepIndex]?.shortName} ready`;
45
52
  const bundleId = agentRun.Subscription?.Plan?.AgentProduct?.AgentBundles?.[0]?.id || 1;
46
- await this.sendNotificationEmail(agent, subject, content, bundleId);
47
- }
48
- async sendNotificationEmail(agent, subject, content, bundleId) {
53
+ const currentWorkflowStep = updatedWorkflow?.steps[updatedWorkflow?.currentStepIndex];
54
+ const userId = agentRun.Subscription?.user_id;
49
55
  const group = (await Group.findOne({
50
56
  where: { id: agent.group_id },
51
57
  include: [
@@ -61,14 +67,13 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
61
67
  }));
62
68
  const link = `https://app.${group.Community?.Domain?.domain_name}/agent_bundle/${bundleId}?needsLogin=true`;
63
69
  if (group && group.GroupAdmins && group.GroupAdmins.length > 0) {
64
- const admins = group.GroupAdmins.filter((admin) => admin.email);
65
- const emailContent = `
66
- <div>
67
- <h1>${subject}</h1>
68
- <p>${content}</p>
69
- </div>
70
- `;
70
+ let admins = group.GroupAdmins.filter((admin) => admin.email);
71
+ const emailContent = EmailTemplateRenderer.renderEmail("", "", agentRun.Subscription?.Plan?.AgentProduct?.name || "", updatedWorkflow, link, "https://evoly.ai/is/amplifier/img/amplifier-logo.png", "https://evoly.ai/is/amplifier/img/evoly-bw-logo.png", "https://evoly.ai/", "© 2024 Evoly ehf, Vegmuli 8, 108, Reykjavik, Iceland");
71
72
  const MAX_ADMIN_EMAILS = 25;
73
+ // Deduplicate admins using Set
74
+ admins = Array.from(new Set(admins.map((admin) => admin.email)))
75
+ .map((email) => admins.find((admin) => admin.email === email))
76
+ .filter((admin) => admin !== undefined);
72
77
  for (let u = 0; u < Math.min(admins.length, MAX_ADMIN_EMAILS); u++) {
73
78
  queue.add("send-one-email", {
74
79
  subject: subject,
@@ -80,6 +85,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
80
85
  header: "",
81
86
  content: emailContent,
82
87
  link: link,
88
+ skipHeaderAndFooter: true,
83
89
  }, { priority: "high" });
84
90
  }
85
91
  }
@@ -129,6 +135,13 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
129
135
  // Get the agent run record
130
136
  const agentRun = await YpAgentProductRun.findByPk(agentRunId, {
131
137
  attributes: ["id", "workflow", "status"],
138
+ include: [
139
+ {
140
+ model: YpSubscription,
141
+ as: "Subscription",
142
+ attributes: ["id", "user_id"],
143
+ },
144
+ ],
132
145
  });
133
146
  if (!agentRun || !agentRun.workflow) {
134
147
  console.error(`NotificationAgentQueueManager: Agent run ${agentRunId} or its workflow not found`);
@@ -178,7 +191,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
178
191
  console.error(`NotificationAgentQueueManager: Error in advanceWorkflowStepOrCompleteAgentRun:`, error);
179
192
  }
180
193
  }
181
- async getAgentRun(agentRunId) {
194
+ static async getAgentRun(agentRunId) {
182
195
  return await YpAgentProductRun.findOne({
183
196
  where: { id: agentRunId },
184
197
  attributes: ["id", "status", "workflow"],
@@ -186,7 +199,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
186
199
  {
187
200
  model: YpSubscription,
188
201
  as: "Subscription",
189
- attributes: ["id"],
202
+ attributes: ["id", "user_id"],
190
203
  include: [
191
204
  {
192
205
  model: YpSubscriptionPlan,
@@ -250,7 +263,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
250
263
  const agent = await PsAgent.findByPk(agentId, {
251
264
  include: [{ model: PsAgentClass, as: "Class" }],
252
265
  });
253
- const agentRun = await this.getAgentRun(agentRunId);
266
+ const agentRun = await NotificationAgentQueueManager.getAgentRun(agentRunId);
254
267
  if (!agentRun) {
255
268
  console.error(`NotificationAgentQueueManager: Agent run with ID ${agentRunId} not found.`);
256
269
  return;
@@ -306,7 +319,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
306
319
  const agent = await PsAgent.findByPk(agentId, {
307
320
  include: [{ model: PsAgentClass, as: "Class" }],
308
321
  });
309
- const agentRun = await this.getAgentRun(agentRunId);
322
+ const agentRun = await NotificationAgentQueueManager.getAgentRun(agentRunId);
310
323
  if (agent && agentRun) {
311
324
  // Send notification email
312
325
  //TODO: Fix this, the agent run should not be marked as failed if the job failed
@@ -389,7 +402,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
389
402
  console.error(`NotificationAgentQueueManager: Agent or Agent Class not found for agent ${agentId}`);
390
403
  return undefined;
391
404
  }
392
- const agentRun = await this.getAgentRun(agentRunId);
405
+ const agentRun = await NotificationAgentQueueManager.getAgentRun(agentRunId);
393
406
  if (!agentRun) {
394
407
  console.error(`NotificationAgentQueueManager: Agent run with ID ${agentRunId} not found.`);
395
408
  return undefined;
@@ -420,7 +433,7 @@ export class NotificationAgentQueueManager extends AgentQueueManager {
420
433
  if (!agent || !agent.Class) {
421
434
  throw Error(`NotificationAgentQueueManager: Agent or Agent Class not found for agent ${agentId}`);
422
435
  }
423
- const agentRun = await this.getAgentRun(agentRunId);
436
+ const agentRun = await NotificationAgentQueueManager.getAgentRun(agentRunId);
424
437
  if (!agentRun) {
425
438
  console.error(`NotificationAgentQueueManager: Agent run with ID ${agentRunId} not found.`);
426
439
  return false;
@@ -253,6 +253,8 @@ export class SubscriptionManager {
253
253
  }
254
254
  // Update the workflow group configuration
255
255
  workflowGroup.configuration.agents.topLevelAgentId = clonedTopLevelAgent.id;
256
+ console.log(`Set top level agent id to ${clonedTopLevelAgent.id} for workflow group ${workflowGroup.id}`);
257
+ workflowGroup.changed('configuration', true);
256
258
  await workflowGroup.save();
257
259
  if (agentProduct.configuration.structuredAnswersOverride) {
258
260
  for (const override of agentProduct.configuration
@@ -16,13 +16,15 @@ async function setupAgentProductsConfiguration() {
16
16
  },
17
17
  {
18
18
  name: "Competitor Analysis People Review",
19
- shortName: "Vetting competitors",
20
- description: "People Review to vet key competitors",
21
- shortDescription: "People Review to vet key competitors",
19
+ shortName: "Vet top 10 competitors",
20
+ description: "People review to vet top 10 key competitors",
21
+ shortDescription: "People review to vet top 10 key competitors",
22
22
  agentClassUuid: "a1b2c3d4-e5f6-c7c8-a9c0-c1225354f516",
23
23
  type: "engagmentFromOutputConnector",
24
24
  stepBackgroundColor: "#e74c3c",
25
25
  stepTextColor: "#ffffff",
26
+ emailCallForAction: "Start Vetting Competitors",
27
+ emailInstructions: "Use up 👍 and down 👎 thumbs to vet competitors. You can choose as many as you want but <b>only top 10 will go forward</b>, to the next round!"
26
28
  },
27
29
  {
28
30
  name: "Competitor Analysis Detailed Search",
@@ -35,14 +37,16 @@ async function setupAgentProductsConfiguration() {
35
37
  stepTextColor: "#ffffff",
36
38
  },
37
39
  {
38
- name: "Competitor Analysis Detailed Search People Review",
39
- shortName: "Key competitors",
40
- description: "People review of key competitors",
41
- shortDescription: "People review of key competitors",
40
+ name: "Competitor Analysis Comment on Key Competitors",
41
+ shortName: "Competitors comments",
42
+ description: "Comment on key competitors before creating report",
43
+ shortDescription: "Comment on key competitors before report",
42
44
  agentClassUuid: "c6e99ac4-e5f6-c7c1-a1c0-c1ab53c4ff16",
43
45
  type: "engagmentFromOutputConnector",
44
46
  stepBackgroundColor: "#2ecc71",
45
47
  stepTextColor: "#ffffff",
48
+ emailCallForAction: "Comment On Key Competitors",
49
+ emailInstructions: "View the details for key competitors and add your insights before the final report is created."
46
50
  },
47
51
  {
48
52
  name: "Competitors Report",
@@ -53,6 +57,8 @@ async function setupAgentProductsConfiguration() {
53
57
  type: "agentOps",
54
58
  stepBackgroundColor: "#d486da",
55
59
  stepTextColor: "#ffffff",
60
+ emailCallForAction: "View the Competitor Report",
61
+ emailInstructions: "View the final report on the state of the market based on the competitors analysis."
56
62
  },
57
63
  ],
58
64
  };
@@ -95,6 +101,8 @@ async function setupAgentProductsConfiguration() {
95
101
  type: "engagmentFromOutputConnector",
96
102
  stepBackgroundColor: "#e74c3c",
97
103
  stepTextColor: "#ffffff",
104
+ emailCallForAction: "Vet Investors",
105
+ emailInstructions: "Use up 👍 and down 👎 thumbs to vet investors. You can choose as many as you want but <b>only top 10 will go forward</b>, to the next round!"
98
106
  },
99
107
  {
100
108
  name: "Funding Agent Detailed Search",
@@ -115,6 +123,8 @@ async function setupAgentProductsConfiguration() {
115
123
  type: "engagmentFromOutputConnector",
116
124
  stepBackgroundColor: "#2ecc71",
117
125
  stepTextColor: "#ffffff",
126
+ emailCallForAction: "Comment On Key Investors",
127
+ emailInstructions: "View the details for key investors and add your insights before the final report is created."
118
128
  },
119
129
  {
120
130
  name: "Funding Agent Report",
@@ -125,6 +135,8 @@ async function setupAgentProductsConfiguration() {
125
135
  type: "agentOps",
126
136
  stepBackgroundColor: "#d486da",
127
137
  stepTextColor: "#ffffff",
138
+ emailCallForAction: "View the Funding Report",
139
+ emailInstructions: "View the final report on investors and funding opportunities."
128
140
  },
129
141
  ],
130
142
  };
@@ -1004,6 +1004,7 @@ router.post("/:groupId/sendEmailInvitesForAnons", auth.can("edit group"), async
1004
1004
  invalidEmails: emailArray.filter((email) => !validEmails.includes(email)),
1005
1005
  });
1006
1006
  }
1007
+ const { AgentInviteManager } = await import("../agents/managers/emailInvitesManager.js");
1007
1008
  for (const email of validEmails) {
1008
1009
  const token = crypto.randomBytes(20).toString("hex");
1009
1010
  const invite = await models.Invite.create({
@@ -1013,30 +1014,8 @@ router.post("/:groupId/sendEmailInvitesForAnons", auth.can("edit group"), async
1013
1014
  community_id: group.community_id,
1014
1015
  from_user_id: req.user.id,
1015
1016
  });
1016
- const invite_link = `/group/${group.id}?anonInvite=1&token=${token}&forAgentBundle=1`;
1017
- const createActivityPromise = new Promise((resolve, reject) => {
1018
- models.AcActivity.inviteCreated({
1019
- email: email,
1020
- user_id: null,
1021
- sender_user_id: req.user.id,
1022
- sender_name: req.user.name,
1023
- group_id: group.id,
1024
- community_id: group.community_id,
1025
- domain_id: req.ypDomain.id,
1026
- invite_id: invite.id,
1027
- invite_link: invite_link,
1028
- invite_type: models.Invite.INVITE_TO_COMMUNITY_AND_GROUP_AS_ANON,
1029
- token: token,
1030
- }, function (error) {
1031
- if (error) {
1032
- reject(error);
1033
- }
1034
- else {
1035
- resolve();
1036
- }
1037
- });
1038
- });
1039
- await createActivityPromise;
1017
+ const invite_link = `https://app.${req.ypDomain.domain_name}/group/${group.id}?anonInvite=1&token=${token}&forAgentBundle=1`;
1018
+ await AgentInviteManager.sendInviteEmail(invite_link, req.body.agentRunId, group.id, req.user);
1040
1019
  log.info("Invite Created", {
1041
1020
  email,
1042
1021
  inviteId: invite.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrpri/api",
3
- "version": "9.0.82",
3
+ "version": "9.0.83",
4
4
  "license": "MIT",
5
5
  "author": "Robert Bjarnason & Citizens Foundation",
6
6
  "repository": {