@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.
Files changed (45) hide show
  1. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
  2. package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
  3. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
  4. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
  5. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +3 -2
  7. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +6 -2
  8. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +4 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +14 -8
  10. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +139 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +5 -2
  12. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +2 -2
  13. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +2 -2
  14. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +2 -2
  15. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +2 -2
  16. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +6 -2
  17. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +7 -4
  18. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +2 -1
  19. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  20. package/apps/agents-server/src/app/api/messages/route.ts +102 -0
  21. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +10 -2
  22. package/apps/agents-server/src/components/Header/Header.tsx +4 -0
  23. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +2 -1
  24. package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
  25. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
  26. package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
  27. package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
  28. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
  29. package/apps/agents-server/src/database/schema.ts +95 -4
  30. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  31. package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +32 -24
  32. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  33. package/apps/agents-server/src/utils/messages/sendMessage.ts +7 -7
  34. package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
  35. package/esm/index.es.js +8021 -8062
  36. package/esm/index.es.js.map +1 -1
  37. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  38. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  39. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +18 -15
  40. package/esm/typings/src/types/typeAliases.d.ts +4 -0
  41. package/esm/typings/src/version.d.ts +1 -1
  42. package/package.json +1 -1
  43. package/umd/index.umd.js +8015 -8056
  44. package/umd/index.umd.js.map +1 -1
  45. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
@@ -0,0 +1,139 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
3
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { $provideCdnForServer } from '@/src/tools/$provideCdnForServer';
5
+ import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
6
+ import { parseAgentSource } from '@promptbook-local/core';
7
+ import { serializeError } from '@promptbook-local/utils';
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { assertsError } from '../../../../../../../../src/errors/assertsError';
10
+ import { getSingleLlmExecutionTools } from '../../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
11
+ import type { LlmExecutionTools } from '../../../../../../../../src/execution/LlmExecutionTools';
12
+ import type { string_url } from '../../../../../../../../src/types/typeAliases';
13
+
14
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
15
+ try {
16
+ let { agentName } = await params;
17
+ agentName = decodeURIComponent(agentName);
18
+
19
+ if (!agentName) {
20
+ return NextResponse.json({ error: 'Agent name is required' }, { status: 400 });
21
+ }
22
+
23
+ // Define a unique filename/key for this agent's default avatar
24
+ // This is used for DB lookup and CDN storage, distinct from generic images
25
+ const internalFilename = `agent-${agentName}-default-avatar.png`;
26
+
27
+ const supabase = $provideSupabaseForServer();
28
+
29
+ // Check if image already exists in database
30
+ const { data: existingImage, error: selectError } = await supabase
31
+ .from(await $getTableName(`Image`))
32
+ .select('cdnUrl')
33
+ .eq('filename', internalFilename)
34
+ .single();
35
+
36
+ if (selectError && selectError.code !== 'PGRST116') {
37
+ // PGRST116 is "not found"
38
+ throw selectError;
39
+ }
40
+
41
+ if (existingImage) {
42
+ // Image exists, redirect to CDN
43
+ return NextResponse.redirect(existingImage.cdnUrl as string_url);
44
+ }
45
+
46
+ // Image doesn't exist, generate it
47
+
48
+ // 1. Fetch agent data
49
+ const collection = await $provideAgentCollectionForServer();
50
+ let agentSource;
51
+ try {
52
+ agentSource = await collection.getAgentSource(agentName);
53
+ } catch (error) {
54
+ // If agent not found, return 404 or default generic image?
55
+ // User said: "Use the ... instead of Gravatar for agents that do not have custom uploaded avatar"
56
+ // If agent doesn't exist, we probably can't generate a specific avatar.
57
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 });
58
+ }
59
+
60
+ const agentProfile = parseAgentSource(agentSource);
61
+
62
+ // Extract required fields
63
+ const name = agentProfile.meta?.title || agentProfile.agentName || agentName;
64
+ const persona = agentProfile.personaDescription || 'an AI agent';
65
+ const color = agentProfile.meta?.color || 'blue';
66
+
67
+ // Construct prompt
68
+ // "Image of {agent.name}, {agent.persona}, portrait, use color ${agent.meta.color}, detailed, high quality"
69
+ const prompt = `Image of ${name}, ${persona}, portrait, use color ${color}, detailed, high quality`;
70
+
71
+ // 2. Generate image
72
+ const executionTools = await $provideExecutionToolsForServer();
73
+ const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
74
+
75
+ if (!llmTools.callImageGenerationModel) {
76
+ throw new Error('Image generation is not supported by the current LLM configuration');
77
+ }
78
+
79
+ const imageResult = await llmTools.callImageGenerationModel({
80
+ title: `Generate default avatar for ${agentName}`,
81
+ content: prompt,
82
+ parameters: {
83
+ size: '1024x1792', // Vertical orientation
84
+ },
85
+ modelRequirements: {
86
+ modelVariant: 'IMAGE_GENERATION',
87
+ modelName: 'dall-e-3',
88
+ },
89
+ });
90
+
91
+ if (!imageResult.content) {
92
+ throw new Error('Failed to generate image: no content returned');
93
+ }
94
+
95
+ // 3. Download and Upload to CDN
96
+ const imageResponse = await fetch(imageResult.content);
97
+ if (!imageResponse.ok) {
98
+ throw new Error(`Failed to download generated image: ${imageResponse.status}`);
99
+ }
100
+
101
+ const imageBuffer = await imageResponse.arrayBuffer();
102
+ const buffer = Buffer.from(imageBuffer);
103
+
104
+ const cdn = $provideCdnForServer();
105
+ const cdnKey = `generated-images/${internalFilename}`;
106
+ await cdn.setItem(cdnKey, {
107
+ type: 'image/png',
108
+ data: buffer,
109
+ });
110
+
111
+ const cdnUrl = cdn.getItemUrl(cdnKey);
112
+
113
+ // 4. Save to database
114
+ const { error: insertError } = await supabase.from(await $getTableName(`Image`)).insert({
115
+ filename: internalFilename,
116
+ prompt,
117
+ cdnUrl: cdnUrl.href,
118
+ cdnKey,
119
+ });
120
+
121
+ if (insertError) {
122
+ // Use upsert or handle race condition if needed, but insert is fine for now
123
+ // If parallel requests happen, one might fail. We can ignore dup key error or retry.
124
+ // But simple insert is what generic route does.
125
+ throw insertError;
126
+ }
127
+
128
+ // Redirect to the newly created image
129
+ return NextResponse.redirect(cdnUrl.href as string_url);
130
+
131
+ } catch (error) {
132
+ assertsError(error);
133
+ console.error('Error serving default avatar:', error);
134
+ return new Response(JSON.stringify(serializeError(error), null, 4), {
135
+ status: 500,
136
+ headers: { 'Content-Type': 'application/json' },
137
+ });
138
+ }
139
+ }
@@ -1,4 +1,4 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
2
  import { serializeError } from '@promptbook-local/utils';
3
3
  import { ImageResponse } from 'next/og';
4
4
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -45,7 +45,10 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
45
45
  >
46
46
  {/* Note: `next/image` is not working propperly with `next/og` */}
47
47
  {/* eslint-disable-next-line @next/next/no-img-element */}
48
- <img src={agentProfile.meta.image!} alt="Agent Icon" />
48
+ <img
49
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
50
+ alt="Agent Icon"
51
+ />
49
52
  </div>
50
53
  </div>
51
54
  ),
@@ -1,4 +1,4 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
2
  import { serializeError } from '@promptbook-local/utils';
3
3
  import { ImageResponse } from 'next/og';
4
4
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -51,7 +51,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
51
51
  backgroundColor: agentColor.toHex(),
52
52
  borderRadius: '50%',
53
53
  }}
54
- src={agentProfile.meta.image!}
54
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
55
55
  alt="Agent Icon"
56
56
  />
57
57
  </div>
@@ -1,4 +1,4 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
2
  import { serializeError } from '@promptbook-local/utils';
3
3
  import { ImageResponse } from 'next/og';
4
4
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -51,7 +51,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
51
51
  backgroundColor: agentColor.toHex(),
52
52
  borderRadius: '50%',
53
53
  }}
54
- src={agentProfile.meta.image!}
54
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
55
55
  alt="Agent Icon"
56
56
  />
57
57
  </div>
@@ -4,7 +4,7 @@ import { $getTableName } from '@/src/database/$getTableName';
4
4
  import { $provideSupabase } from '@/src/database/$provideSupabase';
5
5
  import { $provideServer } from '@/src/tools/$provideServer';
6
6
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
7
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
7
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
8
8
  import { ArrowLeftIcon, BoxIcon, CodeIcon, GlobeIcon, ServerIcon, TerminalIcon } from 'lucide-react';
9
9
  import { headers } from 'next/headers';
10
10
  import Link from 'next/link';
@@ -187,7 +187,7 @@ export default async function AgentIntegrationPage({ params }: { params: Promise
187
187
  {agentProfile.meta.image && (
188
188
  // eslint-disable-next-line @next/next/no-img-element
189
189
  <img
190
- src={agentProfile.meta.image as string}
190
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
191
191
  alt={agentProfile.meta.fullname || agentName}
192
192
  className="w-16 h-16 rounded-full object-cover border-2"
193
193
  style={{ borderColor: primaryColor }}
@@ -1,7 +1,7 @@
1
1
  'use server';
2
2
 
3
3
  import { $provideServer } from '@/src/tools/$provideServer';
4
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
4
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
5
5
  import { ArrowLeftIcon, CodeIcon, HomeIcon, LinkIcon, ShareIcon } from 'lucide-react';
6
6
  import { headers } from 'next/headers';
7
7
  import Link from 'next/link';
@@ -56,7 +56,7 @@ export default async function AgentLinksPage({ params }: { params: Promise<{ age
56
56
  {agentProfile.meta.image && (
57
57
  // eslint-disable-next-line @next/next/no-img-element
58
58
  <img
59
- src={agentProfile.meta.image as string}
59
+ src={agentProfile.meta.image || agentProfile.permanentId || generatePlaceholderAgentProfileImageUrl(agentName)}
60
60
  alt={agentProfile.meta.fullname || agentName}
61
61
  className="w-16 h-16 rounded-full object-cover border-2"
62
62
  style={{ borderColor: primaryColor }}
@@ -1,4 +1,4 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
2
  import { serializeError } from '@promptbook-local/utils';
3
3
  import { ImageResponse } from 'next/og';
4
4
  import { assertsError } from '../../../../../../src/errors/assertsError';
@@ -54,7 +54,11 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
54
54
  backgroundColor: agentColor.toHex(),
55
55
  borderRadius: '50%',
56
56
  }}
57
- src={agentProfile.meta.image!}
57
+ src={
58
+ agentProfile.meta.image ||
59
+ agentProfile.permanentId ||
60
+ generatePlaceholderAgentProfileImageUrl(agentProfile.permanentId || agentName)
61
+ }
58
62
  alt="Agent Icon"
59
63
  />
60
64
  </div>
@@ -3,17 +3,16 @@
3
3
  import { $provideServer } from '@/src/tools/$provideServer';
4
4
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
5
5
  import { saturate } from '@promptbook-local/color';
6
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
7
- import { NotFoundError } from '@promptbook-local/core';
6
+ import { generatePlaceholderAgentProfileImageUrl, NotFoundError, PROMPTBOOK_COLOR } from '@promptbook-local/core';
8
7
  import { notFound } from 'next/navigation';
9
8
  import { Color } from '../../../../../../src/utils/color/Color';
9
+ import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
10
10
  import { getAgentName, getAgentProfile, isAgentDeleted } from './_utils';
11
11
  import { getAgentLinks } from './agentLinks';
12
12
  import { AgentProfileChat } from './AgentProfileChat';
13
13
  import { AgentProfileWrapper } from './AgentProfileWrapper';
14
14
  import { generateAgentMetadata } from './generateAgentMetadata';
15
15
  import { ServiceWorkerRegister } from './ServiceWorkerRegister';
16
- import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
17
16
 
18
17
  export const generateMetadata = generateAgentMetadata;
19
18
 
@@ -96,7 +95,11 @@ export default async function AgentPage({
96
95
  agentName={agentName}
97
96
  fullname={fullname}
98
97
  brandColorHex={brandColorHex}
99
- avatarSrc={agentProfile.meta.image!}
98
+ avatarSrc={
99
+ agentProfile.meta.image ||
100
+ agentProfile.permanentId ||
101
+ generatePlaceholderAgentProfileImageUrl(agentName)
102
+ }
100
103
  isDeleted={isDeleted}
101
104
  />
102
105
  </AgentProfileWrapper>
@@ -8,6 +8,7 @@ import { notFound } from 'next/navigation';
8
8
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
9
9
  import { getAgentName, getAgentProfile } from '../_utils';
10
10
  import { generateAgentMetadata } from '../generateAgentMetadata';
11
+ import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
11
12
 
12
13
  export const generateMetadata = generateAgentMetadata;
13
14
 
@@ -44,7 +45,7 @@ export default async function AgentSystemMessagePage({ params }: { params: Promi
44
45
  {agentProfile.meta.image && (
45
46
  // eslint-disable-next-line @next/next/no-img-element
46
47
  <img
47
- src={agentProfile.meta.image as string}
48
+ src={agentProfile.meta.image|| agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
48
49
  alt={agentProfile.meta.fullname || agentName}
49
50
  className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
50
51
  />
@@ -0,0 +1,48 @@
1
+ import { $getTableName } from '../../../../../database/$getTableName';
2
+ import { $provideSupabaseForServer } from '../../../../../database/$provideSupabaseForServer';
3
+ import { parseInboundSendgridEmail } from '../../../../../message-providers/email/sendgrid/parseInboundSendgridEmail';
4
+ import { NextRequest, NextResponse } from 'next/server';
5
+
6
+ export async function POST(request: NextRequest) {
7
+ try {
8
+ const formData = await request.formData();
9
+ const rawEmail = formData.get('email');
10
+
11
+ if (typeof rawEmail !== 'string') {
12
+ return NextResponse.json({ error: 'Missing email field' }, { status: 400 });
13
+ }
14
+
15
+ const email = await parseInboundSendgridEmail(rawEmail);
16
+
17
+ const supabase = await $provideSupabaseForServer();
18
+ const { error } = await supabase
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ .from(await $getTableName('Message'))
21
+ .insert({
22
+ channel: 'EMAIL',
23
+ direction: 'INBOUND',
24
+ sender: email.sender,
25
+ recipients: email.recipients,
26
+ content: email.content,
27
+ metadata: {
28
+ subject: email.subject,
29
+ cc: email.cc,
30
+ ...email.metadata,
31
+ },
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ } as any);
34
+
35
+ if (error) {
36
+ console.error('Failed to insert message', error);
37
+ return NextResponse.json({ error: error.message }, { status: 500 });
38
+ }
39
+
40
+ return NextResponse.json({ success: true });
41
+ } catch (error) {
42
+ console.error('Error processing inbound email', error);
43
+ return NextResponse.json(
44
+ { error: error instanceof Error ? error.message : 'Unknown error' },
45
+ { status: 500 },
46
+ );
47
+ }
48
+ }
@@ -0,0 +1,102 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { $getTableName } from '../../../database/$getTableName';
3
+ import { $provideSupabase } from '../../../database/$provideSupabase';
4
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
5
+
6
+ const DEFAULT_PAGE_SIZE = 20;
7
+ const MAX_PAGE_SIZE = 100;
8
+
9
+ function parsePositiveInt(value: string | null, fallback: number): number {
10
+ if (!value) return fallback;
11
+ const parsed = parseInt(value, 10);
12
+ if (Number.isNaN(parsed) || parsed <= 0) return fallback;
13
+ return parsed;
14
+ }
15
+
16
+ /**
17
+ * List messages with filters, search and pagination.
18
+ */
19
+ export async function GET(request: NextRequest) {
20
+ if (!(await isUserAdmin())) {
21
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
22
+ }
23
+
24
+ try {
25
+ const searchParams = request.nextUrl.searchParams;
26
+
27
+ const page = parsePositiveInt(searchParams.get('page'), 1);
28
+ const pageSize = Math.min(MAX_PAGE_SIZE, parsePositiveInt(searchParams.get('pageSize'), DEFAULT_PAGE_SIZE));
29
+ const search = searchParams.get('search')?.trim() || '';
30
+ const channel = searchParams.get('channel');
31
+ const direction = searchParams.get('direction');
32
+
33
+ const supabase = $provideSupabase();
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ let query = supabase.from(await $getTableName('Message')).select('*', { count: 'exact' });
37
+
38
+ if (channel) {
39
+ query = query.eq('channel', channel);
40
+ }
41
+
42
+ if (direction) {
43
+ query = query.eq('direction', direction);
44
+ }
45
+
46
+ if (search) {
47
+ // Search in content, subject (if in metadata?), sender/recipient emails
48
+ // Note: sender and recipients are JSONB, so ilike might not work directly on them unless cast to text
49
+ // Content is TEXT.
50
+ const escaped = search.replace(/%/g, '\\%').replace(/_/g, '\\_');
51
+ // Assuming simple search on content for now to avoid complexity with JSONB search in generic supabase client
52
+ query = query.ilike('content', `%${escaped}%`);
53
+ }
54
+
55
+ // Default sort by createdAt desc
56
+ query = query.order('createdAt', { ascending: false });
57
+
58
+ const from = (page - 1) * pageSize;
59
+ const to = from + pageSize - 1;
60
+
61
+ query = query.range(from, to);
62
+
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const { data: messages, error, count } = (await query) as { data: any[]; error: any; count: number };
65
+
66
+ if (error) {
67
+ console.error('List messages error:', error);
68
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
69
+ }
70
+
71
+ // Fetch attempts for these messages
72
+ if (messages && messages.length > 0) {
73
+ const messageIds = messages.map((m) => m.id);
74
+ const { data: attempts, error: attemptsError } = await supabase
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ .from(await $getTableName('MessageSendAttempt'))
77
+ .select('*')
78
+ .in('messageId', messageIds);
79
+
80
+ if (attemptsError) {
81
+ console.error('Fetch message attempts error:', attemptsError);
82
+ // We don't fail the whole request, just log it.
83
+ } else {
84
+ // Attach attempts to messages
85
+ for (const message of messages) {
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ (message as any).sendAttempts = attempts?.filter((a: any) => a.messageId === message.id) || [];
88
+ }
89
+ }
90
+ }
91
+
92
+ return NextResponse.json({
93
+ items: messages ?? [],
94
+ total: count ?? 0,
95
+ page,
96
+ pageSize,
97
+ });
98
+ } catch (error) {
99
+ console.error('List messages error:', error);
100
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
101
+ }
102
+ }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { AgentBasicInformation } from '@promptbook-local/types';
3
+ import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
4
+ import { AgentBasicInformation, string_agent_permanent_id } from '@promptbook-local/types';
4
5
  import { RepeatIcon } from 'lucide-react';
5
6
  import { useState } from 'react';
6
7
  import { AgentQrCode } from './AgentQrCode';
@@ -13,6 +14,11 @@ type AgentProfileProps = {
13
14
  */
14
15
  readonly agent: AgentBasicInformation;
15
16
 
17
+ /**
18
+ * The permanent ID of the agent
19
+ */
20
+ readonly permanentId: string_agent_permanent_id;
21
+
16
22
  /**
17
23
  * URL of the agent page
18
24
  *
@@ -58,16 +64,18 @@ export function AgentProfile(props: AgentProfileProps) {
58
64
  agent,
59
65
  agentUrl = '',
60
66
  agentEmail = '',
67
+ permanentId,
61
68
  renderMenu,
62
69
  children,
63
70
  actions,
64
71
  isHeadless = false,
65
72
  className,
66
73
  } = props;
74
+ console.log('!!!!', { agent });
67
75
  const { meta, agentName } = agent;
68
76
  const fullname = (meta.fullname as string) || agentName || 'Agent';
69
77
  const personaDescription = agent.personaDescription || '';
70
- const imageUrl = (meta.image as string) || null;
78
+ const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(permanentId);
71
79
 
72
80
  const [isQrModalOpen, setIsQrModalOpen] = useState(false);
73
81
  const [isFlipped, setIsFlipped] = useState(false);
@@ -297,6 +297,10 @@ export function Header(props: HeaderProps) {
297
297
  label: 'Chat history',
298
298
  href: '/admin/chat-history',
299
299
  },
300
+ {
301
+ label: 'Messages & Emails',
302
+ href: '/admin/messages',
303
+ },
300
304
  {
301
305
  label: 'Chat feedback',
302
306
  href: '/admin/chat-feedback',
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
3
4
  import { really_any } from '@promptbook-local/types';
4
5
  import { EyeIcon, EyeOffIcon, RotateCcwIcon } from 'lucide-react';
5
6
  import Link from 'next/link';
@@ -32,7 +33,7 @@ export function AgentCard({
32
33
  }: AgentCardProps) {
33
34
  const { meta, agentName } = agent;
34
35
  const fullname = (meta.fullname as string) || agentName || 'Agent';
35
- const imageUrl = (meta.image as string) || null;
36
+ const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(agentName);
36
37
  const personaDescription = agent.personaDescription || '';
37
38
 
38
39
  const { brandColorLightHex, brandColorDarkHex, backgroundImage } = useAgentBackground(meta.color);
@@ -1,6 +1,6 @@
1
- import { AgentsDatabaseSchema } from '@promptbook-local/types';
2
1
  import { $isRunningInBrowser } from '@promptbook-local/utils';
3
2
  import { createClient, SupabaseClient } from '@supabase/supabase-js';
3
+ import { AgentsServerDatabase } from './schema';
4
4
 
5
5
  /**
6
6
  * Internal cache for `$provideSupabaseForBrowser`
@@ -8,7 +8,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
8
8
  * @private
9
9
  * @singleton
10
10
  */
11
- let supabase: SupabaseClient<AgentsDatabaseSchema>;
11
+ let supabase: SupabaseClient<AgentsServerDatabase>;
12
12
 
13
13
  /**
14
14
  * Get supabase client
@@ -27,7 +27,7 @@ export function $provideSupabaseForBrowser(): typeof supabase {
27
27
 
28
28
  if (!supabase) {
29
29
  // Create a single supabase client for interacting with your database
30
- supabase = createClient<AgentsDatabaseSchema>(
30
+ supabase = createClient<AgentsServerDatabase>(
31
31
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
32
32
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
33
33
  );
@@ -18,7 +18,7 @@ let supabase: SupabaseClient<AgentsServerDatabase>;
18
18
  *
19
19
  * @returns instance of supabase client
20
20
  */
21
- export function $provideSupabaseForServer(): typeof supabase {
21
+ export function $provideSupabaseForServer(): SupabaseClient<AgentsServerDatabase> {
22
22
  if (!$isRunningInNode()) {
23
23
  throw new Error(
24
24
  'Function `$provideSupabaseForServer` can not be used in browser, use `$provideSupabaseForBrowser` instead.',
@@ -1,6 +1,6 @@
1
- import { AgentsDatabaseSchema } from '@promptbook-local/types';
2
1
  import { $isRunningInWebWorker } from '@promptbook-local/utils';
3
2
  import { createClient, SupabaseClient } from '@supabase/supabase-js';
3
+ import { AgentsServerDatabase } from './schema';
4
4
 
5
5
  /**
6
6
  * Internal cache for `$provideSupabaseForWorker`
@@ -8,7 +8,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
8
8
  * @private
9
9
  * @singleton
10
10
  */
11
- let supabase: SupabaseClient<AgentsDatabaseSchema>;
11
+ let supabase: SupabaseClient<AgentsServerDatabase>;
12
12
 
13
13
  /**
14
14
  * Get supabase client
@@ -27,7 +27,7 @@ export function $provideSupabaseForWorker(): typeof supabase {
27
27
 
28
28
  if (!supabase) {
29
29
  // Create a single supabase client for interacting with your database
30
- supabase = createClient<AgentsDatabaseSchema>(
30
+ supabase = createClient<AgentsServerDatabase>(
31
31
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
32
32
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
33
33
  );
@@ -1,6 +1,4 @@
1
- -- Note: This is primary source of truth for the database schema
2
- -- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
3
- -- To update, search for [💽]
1
+
4
2
 
5
3
 
6
4
  CREATE TABLE IF NOT EXISTS "prefix_Agent" (
@@ -1,6 +1,4 @@
1
- -- Note: This is primary source of truth for the database schema
2
- -- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
3
- -- To update, search for [💽]
1
+
4
2
 
5
3
 
6
4
  CREATE TABLE IF NOT EXISTS "prefix_Metadata" (