@promptbook/cli 0.104.0-4 → 0.104.0-6
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/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
- package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
- package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
- package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +3 -2
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +6 -2
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +4 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +14 -8
- package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +139 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +5 -2
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +6 -2
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +7 -4
- package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +2 -1
- package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
- package/apps/agents-server/src/app/api/messages/route.ts +102 -0
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +10 -2
- package/apps/agents-server/src/components/Header/Header.tsx +4 -0
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +2 -1
- package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
- package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
- package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
- package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
- package/apps/agents-server/src/database/schema.ts +95 -4
- package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
- package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +32 -24
- package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
- package/apps/agents-server/src/utils/messages/sendMessage.ts +7 -7
- package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
- package/esm/index.es.js +8021 -8062
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
- package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +18 -15
- package/esm/typings/src/types/typeAliases.d.ts +4 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +8015 -8056
- package/umd/index.umd.js.map +1 -1
- package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Source of truth: `schema.sql` *(do not edit table structure here manually)*
|
|
4
4
|
*
|
|
5
5
|
* [💽] Prompt:
|
|
6
|
-
* Re-generate supabase typescript schema from
|
|
6
|
+
* Re-generate supabase typescript schema from `./migrations/*.sql`
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Json helper (Supabase style)
|
|
@@ -120,7 +120,14 @@ export type AgentsServerDatabase = {
|
|
|
120
120
|
agentSource?: string;
|
|
121
121
|
promptbookEngineVersion?: string;
|
|
122
122
|
};
|
|
123
|
-
Relationships: [
|
|
123
|
+
Relationships: [
|
|
124
|
+
{
|
|
125
|
+
foreignKeyName: 'AgentHistory_agentName_fkey';
|
|
126
|
+
columns: ['agentName'];
|
|
127
|
+
referencedRelation: 'Agent';
|
|
128
|
+
referencedColumns: ['agentName'];
|
|
129
|
+
},
|
|
130
|
+
];
|
|
124
131
|
};
|
|
125
132
|
ChatHistory: {
|
|
126
133
|
Row: {
|
|
@@ -174,7 +181,14 @@ export type AgentsServerDatabase = {
|
|
|
174
181
|
source?: 'AGENT_PAGE_CHAT' | 'OPENAI_API_COMPATIBILITY' | null;
|
|
175
182
|
apiKey?: string | null;
|
|
176
183
|
};
|
|
177
|
-
Relationships: [
|
|
184
|
+
Relationships: [
|
|
185
|
+
{
|
|
186
|
+
foreignKeyName: 'ChatHistory_agentName_fkey';
|
|
187
|
+
columns: ['agentName'];
|
|
188
|
+
referencedRelation: 'Agent';
|
|
189
|
+
referencedColumns: ['agentName'];
|
|
190
|
+
},
|
|
191
|
+
];
|
|
178
192
|
};
|
|
179
193
|
ChatFeedback: {
|
|
180
194
|
Row: {
|
|
@@ -228,7 +242,14 @@ export type AgentsServerDatabase = {
|
|
|
228
242
|
language?: string | null;
|
|
229
243
|
platform?: string | null;
|
|
230
244
|
};
|
|
231
|
-
Relationships: [
|
|
245
|
+
Relationships: [
|
|
246
|
+
{
|
|
247
|
+
foreignKeyName: 'ChatFeedback_agentName_fkey';
|
|
248
|
+
columns: ['agentName'];
|
|
249
|
+
referencedRelation: 'Agent';
|
|
250
|
+
referencedColumns: ['agentName'];
|
|
251
|
+
},
|
|
252
|
+
];
|
|
232
253
|
};
|
|
233
254
|
User: {
|
|
234
255
|
Row: {
|
|
@@ -408,6 +429,76 @@ export type AgentsServerDatabase = {
|
|
|
408
429
|
},
|
|
409
430
|
];
|
|
410
431
|
};
|
|
432
|
+
Message: {
|
|
433
|
+
Row: {
|
|
434
|
+
id: number;
|
|
435
|
+
createdAt: string;
|
|
436
|
+
channel: string;
|
|
437
|
+
direction: string;
|
|
438
|
+
sender: Json;
|
|
439
|
+
recipients: Json | null;
|
|
440
|
+
content: string;
|
|
441
|
+
threadId: string | null;
|
|
442
|
+
metadata: Json | null;
|
|
443
|
+
};
|
|
444
|
+
Insert: {
|
|
445
|
+
id?: number;
|
|
446
|
+
createdAt?: string;
|
|
447
|
+
channel: string;
|
|
448
|
+
direction: string;
|
|
449
|
+
sender: Json;
|
|
450
|
+
recipients?: Json | null;
|
|
451
|
+
content: string;
|
|
452
|
+
threadId?: string | null;
|
|
453
|
+
metadata?: Json | null;
|
|
454
|
+
};
|
|
455
|
+
Update: {
|
|
456
|
+
id?: number;
|
|
457
|
+
createdAt?: string;
|
|
458
|
+
channel?: string;
|
|
459
|
+
direction?: string;
|
|
460
|
+
sender?: Json;
|
|
461
|
+
recipients?: Json | null;
|
|
462
|
+
content?: string;
|
|
463
|
+
threadId?: string | null;
|
|
464
|
+
metadata?: Json | null;
|
|
465
|
+
};
|
|
466
|
+
Relationships: [];
|
|
467
|
+
};
|
|
468
|
+
MessageSendAttempt: {
|
|
469
|
+
Row: {
|
|
470
|
+
id: number;
|
|
471
|
+
createdAt: string;
|
|
472
|
+
messageId: number;
|
|
473
|
+
providerName: string;
|
|
474
|
+
isSuccessful: boolean;
|
|
475
|
+
raw: Json | null;
|
|
476
|
+
};
|
|
477
|
+
Insert: {
|
|
478
|
+
id?: number;
|
|
479
|
+
createdAt?: string;
|
|
480
|
+
messageId: number;
|
|
481
|
+
providerName: string;
|
|
482
|
+
isSuccessful: boolean;
|
|
483
|
+
raw?: Json | null;
|
|
484
|
+
};
|
|
485
|
+
Update: {
|
|
486
|
+
id?: number;
|
|
487
|
+
createdAt?: string;
|
|
488
|
+
messageId?: number;
|
|
489
|
+
providerName?: string;
|
|
490
|
+
isSuccessful?: boolean;
|
|
491
|
+
raw?: Json | null;
|
|
492
|
+
};
|
|
493
|
+
Relationships: [
|
|
494
|
+
{
|
|
495
|
+
foreignKeyName: 'MessageSendAttempt_messageId_fkey';
|
|
496
|
+
columns: ['messageId'];
|
|
497
|
+
referencedRelation: 'Message';
|
|
498
|
+
referencedColumns: ['id'];
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
};
|
|
411
502
|
};
|
|
412
503
|
Views: Record<string, never>;
|
|
413
504
|
Functions: Record<string, never>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { string_markdown } from '@promptbook-local/types';
|
|
2
|
+
import { simpleParser } from 'mailparser';
|
|
3
|
+
import TurndownService from 'turndown';
|
|
4
|
+
import { extractBodyContentFromHtml } from '../../../utils/content/extractBodyContentFromHtml';
|
|
5
|
+
import type { InboundEmail } from '../_common/Email';
|
|
6
|
+
import { parseEmailAddress } from '../_common/utils/parseEmailAddress';
|
|
7
|
+
import { parseEmailAddresses } from '../_common/utils/parseEmailAddresses';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Function parseInboundSendgridEmail will parse raw inbound email from Sendgrid and return Email object
|
|
11
|
+
*/
|
|
12
|
+
export async function parseInboundSendgridEmail(rawEmail: string): Promise<InboundEmail> {
|
|
13
|
+
const parsedEmail = await simpleParser(rawEmail);
|
|
14
|
+
|
|
15
|
+
const toArray = !Array.isArray(parsedEmail.to)
|
|
16
|
+
? parsedEmail.to === undefined
|
|
17
|
+
? []
|
|
18
|
+
: [parsedEmail.to]
|
|
19
|
+
: parsedEmail.to;
|
|
20
|
+
const to = toArray.flatMap((_) => parseEmailAddresses(_.text));
|
|
21
|
+
|
|
22
|
+
const ccArray = !Array.isArray(parsedEmail.cc)
|
|
23
|
+
? parsedEmail.cc === undefined
|
|
24
|
+
? []
|
|
25
|
+
: [parsedEmail.cc]
|
|
26
|
+
: parsedEmail.cc;
|
|
27
|
+
const cc = ccArray.flatMap((_) => parseEmailAddresses(_.text));
|
|
28
|
+
|
|
29
|
+
const turndownService = new TurndownService();
|
|
30
|
+
|
|
31
|
+
const content = (parsedEmail.html
|
|
32
|
+
? turndownService.turndown(extractBodyContentFromHtml(parsedEmail.html))
|
|
33
|
+
: parsedEmail.text || '') as string_markdown;
|
|
34
|
+
|
|
35
|
+
const email: InboundEmail = {
|
|
36
|
+
channel: 'EMAIL',
|
|
37
|
+
direction: 'INBOUND',
|
|
38
|
+
sender: parseEmailAddress(parsedEmail.from?.text || '').fullEmail,
|
|
39
|
+
recipients: to.map((_) => _.fullEmail),
|
|
40
|
+
cc,
|
|
41
|
+
subject: parsedEmail.subject || '',
|
|
42
|
+
content,
|
|
43
|
+
attachments: [
|
|
44
|
+
/* <- TODO: [📯] Parse attachments */
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return email;
|
|
49
|
+
}
|
package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts
CHANGED
|
@@ -10,34 +10,42 @@ export class ZeptomailMessageProvider implements MessageProvider {
|
|
|
10
10
|
constructor(private readonly apiKey: string) {}
|
|
11
11
|
|
|
12
12
|
public async send(message: OutboundEmail): Promise<really_any> {
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
const client = new SendMailClient({ url: 'api.zeptomail.com/', token: this.apiKey });
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const sender = message.sender as really_any;
|
|
17
|
+
const recipients = (Array.isArray(message.recipients) ? message.recipients : [message.recipients]).filter(
|
|
18
|
+
Boolean,
|
|
19
|
+
) as really_any[];
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const textbody = removeMarkdownFormatting(message.content);
|
|
22
|
+
const htmlbody = await marked.parse(message.content);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
to: recipients.map((r) => ({
|
|
29
|
-
email_address: {
|
|
30
|
-
address: r.email || r.baseEmail || r,
|
|
31
|
-
name: r.name || r.fullName || undefined,
|
|
24
|
+
const response = await client.sendMail({
|
|
25
|
+
from: {
|
|
26
|
+
address: sender.email || sender.baseEmail || sender,
|
|
27
|
+
name: sender.name || sender.fullName || undefined,
|
|
32
28
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
to: recipients.map((r) => ({
|
|
30
|
+
email_address: {
|
|
31
|
+
address: r.email || r.baseEmail || r,
|
|
32
|
+
name: r.name || r.fullName || undefined,
|
|
33
|
+
},
|
|
34
|
+
})),
|
|
35
|
+
subject: message.metadata?.subject || 'No Subject',
|
|
36
|
+
textbody,
|
|
37
|
+
htmlbody,
|
|
38
|
+
track_clicks: true,
|
|
39
|
+
track_opens: true,
|
|
40
|
+
});
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
return response;
|
|
43
|
+
} catch (raw: really_any) {
|
|
44
|
+
if (!('error' in raw)) {
|
|
45
|
+
throw raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(raw.error.message, raw.error.details);
|
|
49
|
+
}
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { string_html } from '@promptbook-local/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract the first heading from HTML
|
|
5
|
+
*
|
|
6
|
+
* @param contentText HTML
|
|
7
|
+
* @returns heading
|
|
8
|
+
*/
|
|
9
|
+
export function extractBodyContentFromHtml(html: string_html): string_html {
|
|
10
|
+
// Note: Not using DOMParser, because it's overkill for this simple task
|
|
11
|
+
|
|
12
|
+
const match = html.match(/<body[^>]*>(?<bodyContent>[\s\S]*)<\/body>/s);
|
|
13
|
+
|
|
14
|
+
if (!match) {
|
|
15
|
+
return html;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return match.groups!.bodyContent!;
|
|
19
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { really_any } from '@promptbook-local/types';
|
|
2
|
+
import { serializeError } from '@promptbook-local/utils';
|
|
3
|
+
import { assertsError } from '../../../../../src/errors/assertsError';
|
|
2
4
|
import { $getTableName } from '../../database/$getTableName';
|
|
3
5
|
import { $provideSupabaseForServer } from '../../database/$provideSupabaseForServer';
|
|
4
6
|
import { EMAIL_PROVIDERS } from '../../message-providers';
|
|
@@ -9,15 +11,11 @@ import { OutboundEmail } from '../../message-providers/email/_common/Email';
|
|
|
9
11
|
*/
|
|
10
12
|
export async function sendMessage(message: OutboundEmail): Promise<void> {
|
|
11
13
|
const supabase = await $provideSupabaseForServer();
|
|
12
|
-
// @ts-expect-error: Tables are not yet in types
|
|
13
|
-
const messageTable = await $getTableName('Message');
|
|
14
|
-
// @ts-expect-error: Tables are not yet in types
|
|
15
|
-
const messageSendAttemptTable = await $getTableName('MessageSendAttempt');
|
|
16
14
|
|
|
17
15
|
// 1. Insert message
|
|
18
16
|
const { data: insertedMessage, error: insertError } = await supabase
|
|
19
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
.from(
|
|
18
|
+
.from(await $getTableName('Message'))
|
|
21
19
|
.insert({
|
|
22
20
|
channel: message.channel || 'UNKNOWN',
|
|
23
21
|
direction: message.direction || 'OUTBOUND',
|
|
@@ -56,17 +54,19 @@ export async function sendMessage(message: OutboundEmail): Promise<void> {
|
|
|
56
54
|
let raw: really_any = null;
|
|
57
55
|
|
|
58
56
|
try {
|
|
57
|
+
console.log(`📤 Sending email via ${providerName}`);
|
|
59
58
|
raw = await provider.send(message);
|
|
60
59
|
isSuccessful = true;
|
|
61
60
|
isSent = true;
|
|
62
61
|
} catch (error) {
|
|
62
|
+
assertsError(error);
|
|
63
63
|
console.error(`Failed to send email via ${providerName}`, error);
|
|
64
|
-
raw = { error:
|
|
64
|
+
raw = { error: serializeError(error) };
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// 3. Log attempt
|
|
68
68
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
-
await supabase.from(
|
|
69
|
+
await supabase.from(await $getTableName('MessageSendAttempt')).insert({
|
|
70
70
|
// @ts-expect-error: insertedMessage is any
|
|
71
71
|
messageId: insertedMessage.id,
|
|
72
72
|
providerName,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Json } from '../database/schema';
|
|
2
|
+
|
|
3
|
+
export type MessageRow = {
|
|
4
|
+
id: number;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
channel: string;
|
|
7
|
+
direction: string;
|
|
8
|
+
sender: Json;
|
|
9
|
+
recipients: Json;
|
|
10
|
+
content: string;
|
|
11
|
+
threadId: string | null;
|
|
12
|
+
metadata: Json;
|
|
13
|
+
// Joined fields
|
|
14
|
+
sendAttempts?: MessageSendAttemptRow[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type MessageSendAttemptRow = {
|
|
18
|
+
id: number;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
messageId: number;
|
|
21
|
+
providerName: string;
|
|
22
|
+
isSuccessful: boolean;
|
|
23
|
+
raw: Json;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type MessagesListResponse = {
|
|
27
|
+
items: MessageRow[];
|
|
28
|
+
total: number;
|
|
29
|
+
page: number;
|
|
30
|
+
pageSize: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type MessagesListParams = {
|
|
34
|
+
page?: number;
|
|
35
|
+
pageSize?: number;
|
|
36
|
+
search?: string;
|
|
37
|
+
channel?: string;
|
|
38
|
+
direction?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build query string for messages listing.
|
|
43
|
+
*/
|
|
44
|
+
function buildQuery(params: MessagesListParams): string {
|
|
45
|
+
const searchParams = new URLSearchParams();
|
|
46
|
+
|
|
47
|
+
if (params.page && params.page > 0) searchParams.set('page', String(params.page));
|
|
48
|
+
if (params.pageSize && params.pageSize > 0) searchParams.set('pageSize', String(params.pageSize));
|
|
49
|
+
if (params.search) searchParams.set('search', params.search);
|
|
50
|
+
if (params.channel) searchParams.set('channel', params.channel);
|
|
51
|
+
if (params.direction) searchParams.set('direction', params.direction);
|
|
52
|
+
|
|
53
|
+
const qs = searchParams.toString();
|
|
54
|
+
return qs ? `?${qs}` : '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetch messages from the admin API.
|
|
59
|
+
*/
|
|
60
|
+
export async function $fetchMessages(params: MessagesListParams = {}): Promise<MessagesListResponse> {
|
|
61
|
+
const qs = buildQuery(params);
|
|
62
|
+
const response = await fetch(`/api/messages${qs}`, {
|
|
63
|
+
method: 'GET',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const data = await response.json().catch(() => ({}));
|
|
68
|
+
throw new Error(data.error || 'Failed to load messages');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (await response.json()) as MessagesListResponse;
|
|
72
|
+
}
|