@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
|
@@ -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
|
|
48
|
+
<img
|
|
49
|
+
src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName)}
|
|
50
|
+
alt="Agent Icon"
|
|
51
|
+
/>
|
|
49
52
|
</div>
|
|
50
53
|
</div>
|
|
51
54
|
),
|
package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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={
|
|
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={
|
|
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
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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<
|
|
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<
|
|
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():
|
|
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<
|
|
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<
|
|
30
|
+
supabase = createClient<AgentsServerDatabase>(
|
|
31
31
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
32
32
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
33
33
|
);
|