@promptbook/cli 0.104.0-5 → 0.104.0-7

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 (33) hide show
  1. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +3 -2
  2. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +6 -2
  3. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +15 -8
  4. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
  5. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +53 -38
  6. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +6 -2
  7. package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +3 -2
  9. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +3 -2
  10. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +3 -2
  11. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +3 -2
  12. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +10 -2
  13. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +8 -4
  14. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +3 -1
  15. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  16. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +11 -2
  17. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +3 -1
  18. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  19. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  20. package/esm/index.es.js +8 -51
  21. package/esm/index.es.js.map +1 -1
  22. package/esm/typings/servers.d.ts +8 -0
  23. package/esm/typings/src/_packages/core.index.d.ts +2 -0
  24. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  25. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  26. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  27. package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
  28. package/esm/typings/src/types/typeAliases.d.ts +11 -1
  29. package/esm/typings/src/version.d.ts +1 -1
  30. package/package.json +1 -1
  31. package/umd/index.umd.js +8 -51
  32. package/umd/index.umd.js.map +1 -1
  33. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { AgentBasicInformation } from '@promptbook-local/types';
3
+ import { AgentBasicInformation, string_agent_name } from '@promptbook-local/types';
4
4
  import { AgentProfile } from '../../../components/AgentProfile/AgentProfile';
5
5
  import { AgentOptionsMenu } from './AgentOptionsMenu';
6
6
 
@@ -8,7 +8,7 @@ type AgentProfileWrapperProps = {
8
8
  agent: AgentBasicInformation;
9
9
  agentUrl: string;
10
10
  agentEmail: string;
11
- agentName: string;
11
+ agentName: string_agent_name;
12
12
  brandColorHex: string;
13
13
  isAdmin: boolean;
14
14
  isHeadless: boolean;
@@ -27,6 +27,7 @@ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
27
27
  <AgentProfile
28
28
  agent={agent}
29
29
  agentUrl={agentUrl}
30
+ permanentId={permanentId || agentName}
30
31
  agentEmail={agentEmail}
31
32
  isHeadless={isHeadless}
32
33
  renderMenu={({ onShowQrCode }) => (
@@ -13,7 +13,11 @@ export async function getAgentName(params: Promise<{ agentName: string }>) {
13
13
  export async function getAgentProfile(agentName: string) {
14
14
  const collection = await $provideAgentCollectionForServer();
15
15
  const agentSource = await collection.getAgentSource(agentName);
16
- return parseAgentSource(agentSource);
16
+ const agentProfile = parseAgentSource(agentSource);
17
+
18
+ console.log('!!!!', { agentSource, agentProfile });
19
+
20
+ return agentProfile;
17
21
  }
18
22
 
19
23
  export async function isAgentDeleted(agentName: string): Promise<boolean> {
@@ -33,5 +37,5 @@ export async function isAgentDeleted(agentName: string): Promise<boolean> {
33
37
  }
34
38
 
35
39
  /**
36
- * TODO: Split to multiple files
40
+ * TODO: Split to multiple files, refactor
37
41
  */
@@ -1,6 +1,9 @@
1
1
  'use client';
2
2
 
3
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
3
4
  import Editor from '@monaco-editor/react';
5
+ import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
6
+ import { AgentBasicInformation } from '@promptbook-local/types';
4
7
  import { ArrowLeftIcon, ChevronDownIcon, CodeIcon } from 'lucide-react';
5
8
  import Link from 'next/link';
6
9
  import { useCallback, useEffect, useState } from 'react';
@@ -29,7 +32,7 @@ function getLanguageFromTranspiler(transpilerName?: string): string {
29
32
 
30
33
  export default function AgentCodePage({ params }: { params: Promise<{ agentName: string }> }) {
31
34
  const [agentName, setAgentName] = useState<string>('');
32
- const [agentProfile, setAgentProfile] = useState<unknown>(null);
35
+ const [agentProfile, setAgentProfile] = useState<AgentBasicInformation | null>(null);
33
36
  const [transpilers, setTranspilers] = useState<Transpiler[]>([]);
34
37
  const [selectedTranspiler, setSelectedTranspiler] = useState<Transpiler | null>(null);
35
38
  const [transpiledCode, setTranspiledCode] = useState<string>('');
@@ -114,13 +117,17 @@ export default function AgentCodePage({ params }: { params: Promise<{ agentName:
114
117
  {/* Header */}
115
118
  <div className="p-6 border-b border-gray-200 flex items-center gap-4">
116
119
  {/* eslint-disable @typescript-eslint/no-explicit-any, @next/next/no-img-element */}
117
- {(agentProfile as any)?.meta?.image && (
118
- <img
119
- src={(agentProfile as any).meta.image as string}
120
- alt={(agentProfile as any).meta.fullname || agentName}
121
- className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
122
- />
123
- )}
120
+
121
+ <img
122
+ src={
123
+ agentProfile.meta.image ||
124
+ agentProfile.permanentId ||
125
+ generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)
126
+ }
127
+ alt={agentProfile.meta.fullname || agentName}
128
+ className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
129
+ />
130
+
124
131
  <div className="flex-1">
125
132
  {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
126
133
  <h1 className="text-2xl font-bold text-gray-900">
@@ -0,0 +1,31 @@
1
+ import { AgentBasicInformation } from '@promptbook-local/types';
2
+ import spaceTrim from 'spacetrim';
3
+ import { string_prompt_image } from '../../../../../../../../src/types/typeAliases';
4
+
5
+ export function getAgentDefaultAvatarPrompt(agent: AgentBasicInformation): string_prompt_image {
6
+ const {
7
+ agentName,
8
+ personaDescription,
9
+ meta: { fullname, color },
10
+ } = agent;
11
+
12
+ return spaceTrim(
13
+ (block) => `
14
+ Professional corporate headshot of ${fullname || agentName}
15
+
16
+ ${block(personaDescription || '')}
17
+
18
+ - Professional business portrait photograph
19
+ - Photorealistic, studio quality lighting
20
+ - Shot with 85mm lens, shallow depth of field
21
+ - Neutral gray or soft gradient background
22
+ - Subject wearing professional attire with accent colors: ${color}
23
+ - Confident, approachable expression with slight smile
24
+ - Eye-level camera angle, centered composition
25
+ - Soft diffused lighting, subtle rim light
26
+ - Sharp focus on eyes, cinematic color grading
27
+ - 8K resolution, ultra detailed
28
+
29
+ `,
30
+ );
31
+ }
@@ -4,12 +4,13 @@ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentColle
4
4
  import { $provideCdnForServer } from '@/src/tools/$provideCdnForServer';
5
5
  import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
6
6
  import { parseAgentSource } from '@promptbook-local/core';
7
- import { serializeError } from '@promptbook-local/utils';
7
+ import { computeHash, serializeError } from '@promptbook-local/utils';
8
8
  import { NextRequest, NextResponse } from 'next/server';
9
9
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
10
- import { getSingleLlmExecutionTools } from '../../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
11
10
  import type { LlmExecutionTools } from '../../../../../../../../src/execution/LlmExecutionTools';
11
+ import { getSingleLlmExecutionTools } from '../../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
12
12
  import type { string_url } from '../../../../../../../../src/types/typeAliases';
13
+ import { getAgentDefaultAvatarPrompt } from './getAgentDefaultAvatarPrompt';
13
14
 
14
15
  export async function GET(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
15
16
  try {
@@ -20,13 +21,28 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
20
21
  return NextResponse.json({ error: 'Agent name is required' }, { status: 400 });
21
22
  }
22
23
 
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`;
24
+ // 1. Fetch agent data first to construct the prompt
25
+ const collection = await $provideAgentCollectionForServer();
26
+ let agentSource;
27
+ try {
28
+ agentSource = await collection.getAgentSource(agentName);
29
+ } catch (error) {
30
+ // If agent not found, redirect to pravatar with the agent name as unique identifier
31
+ const pravaratUrl = `https://i.pravatar.cc/1024?u=${encodeURIComponent(agentName)}`;
32
+ return NextResponse.redirect(pravaratUrl);
33
+ }
34
+
35
+ const agentProfile = parseAgentSource(agentSource);
36
+
37
+ const prompt = getAgentDefaultAvatarPrompt(agentProfile);
38
+
39
+ // Use hash of the prompt as cache key - this ensures regeneration when prompt changes
40
+ const promptHash = computeHash(prompt);
41
+ const internalFilename = `agent-avatar-${promptHash}.png`;
26
42
 
27
43
  const supabase = $provideSupabaseForServer();
28
44
 
29
- // Check if image already exists in database
45
+ // Check if image with this prompt hash already exists in database
30
46
  const { data: existingImage, error: selectError } = await supabase
31
47
  .from(await $getTableName(`Image`))
32
48
  .select('cdnUrl')
@@ -39,35 +55,23 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
39
55
  }
40
56
 
41
57
  if (existingImage) {
42
- // Image exists, redirect to CDN
43
- return NextResponse.redirect(existingImage.cdnUrl as string_url);
58
+ // Image exists, fetch from CDN and return directly
59
+ const imageResponse = await fetch(existingImage.cdnUrl as string_url);
60
+ if (!imageResponse.ok) {
61
+ throw new Error(`Failed to fetch image from CDN: ${imageResponse.status}`);
62
+ }
63
+ const imageBuffer = await imageResponse.arrayBuffer();
64
+ return new NextResponse(imageBuffer, {
65
+ status: 200,
66
+ headers: {
67
+ 'Content-Type': 'image/png',
68
+ 'Cache-Control': 'public, max-age=31536000, immutable',
69
+ },
70
+ });
44
71
  }
45
72
 
46
73
  // Image doesn't exist, generate it
47
74
 
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
75
  // 2. Generate image
72
76
  const executionTools = await $provideExecutionToolsForServer();
73
77
  const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
@@ -79,12 +83,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
79
83
  const imageResult = await llmTools.callImageGenerationModel({
80
84
  title: `Generate default avatar for ${agentName}`,
81
85
  content: prompt,
82
- parameters: {
83
- size: '1024x1792', // Vertical orientation
84
- },
86
+ parameters: {},
85
87
  modelRequirements: {
86
88
  modelVariant: 'IMAGE_GENERATION',
87
- modelName: 'dall-e-3',
89
+ modelName: 'dall-e-3',
90
+ size: '1024x1792', // <- Vertical orientation
91
+ quality: 'hd',
92
+ style: 'natural',
88
93
  },
89
94
  });
90
95
 
@@ -125,9 +130,19 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
125
130
  throw insertError;
126
131
  }
127
132
 
128
- // Redirect to the newly created image
129
- return NextResponse.redirect(cdnUrl.href as string_url);
130
-
133
+ // Return the newly created image directly
134
+ const finalImageResponse = await fetch(cdnUrl.href);
135
+ if (!finalImageResponse.ok) {
136
+ throw new Error(`Failed to fetch newly created image from CDN: ${finalImageResponse.status}`);
137
+ }
138
+ const finalImageBuffer = await finalImageResponse.arrayBuffer();
139
+ return new NextResponse(finalImageBuffer, {
140
+ status: 200,
141
+ headers: {
142
+ 'Content-Type': 'image/png',
143
+ 'Cache-Control': 'public, max-age=31536000, immutable',
144
+ },
145
+ });
131
146
  } catch (error) {
132
147
  assertsError(error);
133
148
  console.error('Error serving default avatar:', error);
@@ -1,10 +1,11 @@
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';
5
5
  import { Color } from '../../../../../../../../src/utils/color/Color';
6
6
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
7
7
  import { getAgentName, getAgentProfile } from '../../_utils';
8
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
8
9
 
9
10
  const size = {
10
11
  width: 256,
@@ -45,7 +46,10 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
45
46
  >
46
47
  {/* Note: `next/image` is not working propperly with `next/og` */}
47
48
  {/* eslint-disable-next-line @next/next/no-img-element */}
48
- <img src={agentProfile.meta.image!} alt="Agent Icon" />
49
+ <img
50
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
51
+ alt="Agent Icon"
52
+ />
49
53
  </div>
50
54
  </div>
51
55
  ),
@@ -0,0 +1,200 @@
1
+ 'use server';
2
+
3
+ import { saturate } from '@promptbook-local/color';
4
+ import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
5
+ import Link from 'next/link';
6
+ import { Color } from '../../../../../../../src/utils/color/Color';
7
+ import { getAgentName, getAgentProfile } from '../_utils';
8
+
9
+ /**
10
+ * Available image types for agents with their descriptions and sizes
11
+ */
12
+ const AGENT_IMAGES = [
13
+ {
14
+ name: 'default-avatar.png',
15
+ title: 'Default Avatar',
16
+ description: 'AI-generated avatar image based on the agent profile. Vertical orientation (1024x1792).',
17
+ size: '1024×1792',
18
+ },
19
+ {
20
+ name: 'icon-256.png',
21
+ title: 'Icon (256×256)',
22
+ description: 'Small circular icon suitable for profile pictures and thumbnails.',
23
+ size: '256×256',
24
+ },
25
+ {
26
+ name: 'screenshot-fullhd.png',
27
+ title: 'Screenshot Full HD',
28
+ description: 'Landscape screenshot showing the agent with name. Suitable for desktop previews.',
29
+ size: '1920×1080',
30
+ },
31
+ {
32
+ name: 'screenshot-phone.png',
33
+ title: 'Screenshot Phone',
34
+ description: 'Portrait screenshot optimized for mobile devices.',
35
+ size: '1080×1920',
36
+ },
37
+ ] as const;
38
+
39
+ export default async function AgentImagesPage({ params }: { params: Promise<{ agentName: string }> }) {
40
+ const agentName = await getAgentName(params);
41
+ const agentProfile = await getAgentProfile(agentName);
42
+
43
+ const brandColor = Color.fromSafe(agentProfile.meta.color || PROMPTBOOK_COLOR);
44
+ const brandColorHex = brandColor.then(saturate(-0.5)).toHex();
45
+
46
+ const fullname = (agentProfile.meta.fullname || agentProfile.agentName || 'Agent') as string;
47
+
48
+ return (
49
+ <div
50
+ style={{
51
+ minHeight: '100vh',
52
+ backgroundColor: '#f5f5f5',
53
+ padding: '2rem',
54
+ }}
55
+ >
56
+ <div
57
+ style={{
58
+ maxWidth: '1200px',
59
+ margin: '0 auto',
60
+ }}
61
+ >
62
+ <header
63
+ style={{
64
+ marginBottom: '2rem',
65
+ padding: '1.5rem',
66
+ backgroundColor: brandColorHex,
67
+ borderRadius: '12px',
68
+ color: 'white',
69
+ }}
70
+ >
71
+ <h1 style={{ margin: 0, fontSize: '2rem' }}>
72
+ Images for <strong>{fullname}</strong>
73
+ </h1>
74
+ <p style={{ margin: '0.5rem 0 0', opacity: 0.9 }}>
75
+ All available image assets for agent{' '}
76
+ <code
77
+ style={{
78
+ backgroundColor: 'rgba(255,255,255,0.2)',
79
+ padding: '2px 6px',
80
+ borderRadius: '4px',
81
+ }}
82
+ >
83
+ {agentName}
84
+ </code>
85
+ </p>
86
+ </header>
87
+
88
+ <div
89
+ style={{
90
+ display: 'grid',
91
+ gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
92
+ gap: '1.5rem',
93
+ }}
94
+ >
95
+ {AGENT_IMAGES.map((image) => {
96
+ const imageUrl = `/agents/${encodeURIComponent(agentName)}/images/${image.name}`;
97
+ return (
98
+ <div
99
+ key={image.name}
100
+ style={{
101
+ backgroundColor: 'white',
102
+ borderRadius: '12px',
103
+ overflow: 'hidden',
104
+ boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
105
+ }}
106
+ >
107
+ <div
108
+ style={{
109
+ aspectRatio: '16/9',
110
+ backgroundColor: '#e0e0e0',
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ overflow: 'hidden',
115
+ }}
116
+ >
117
+ {/* eslint-disable-next-line @next/next/no-img-element */}
118
+ <img
119
+ src={imageUrl}
120
+ alt={image.title}
121
+ style={{
122
+ maxWidth: '100%',
123
+ maxHeight: '100%',
124
+ objectFit: 'contain',
125
+ }}
126
+ />
127
+ </div>
128
+ <div style={{ padding: '1rem' }}>
129
+ <h2 style={{ margin: '0 0 0.5rem', fontSize: '1.25rem' }}>{image.title}</h2>
130
+ <p style={{ margin: '0 0 0.5rem', color: '#666', fontSize: '0.9rem' }}>
131
+ {image.description}
132
+ </p>
133
+ <div
134
+ style={{
135
+ display: 'flex',
136
+ justifyContent: 'space-between',
137
+ alignItems: 'center',
138
+ marginTop: '1rem',
139
+ }}
140
+ >
141
+ <span
142
+ style={{
143
+ backgroundColor: '#f0f0f0',
144
+ padding: '4px 8px',
145
+ borderRadius: '4px',
146
+ fontSize: '0.85rem',
147
+ color: '#555',
148
+ }}
149
+ >
150
+ {image.size}
151
+ </span>
152
+ <Link
153
+ href={imageUrl}
154
+ target="_blank"
155
+ style={{
156
+ backgroundColor: brandColorHex,
157
+ color: 'white',
158
+ padding: '8px 16px',
159
+ borderRadius: '6px',
160
+ textDecoration: 'none',
161
+ fontSize: '0.9rem',
162
+ }}
163
+ >
164
+ Open Image
165
+ </Link>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ );
170
+ })}
171
+ </div>
172
+
173
+ <footer
174
+ style={{
175
+ marginTop: '2rem',
176
+ padding: '1rem',
177
+ backgroundColor: 'white',
178
+ borderRadius: '12px',
179
+ textAlign: 'center',
180
+ color: '#666',
181
+ }}
182
+ >
183
+ <p style={{ margin: 0 }}>
184
+ <Link
185
+ href={`/agents/${encodeURIComponent(agentName)}`}
186
+ style={{ color: brandColorHex, textDecoration: 'none' }}
187
+ >
188
+ ← Back to {fullname}
189
+ </Link>
190
+ </p>
191
+ </footer>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ /**
198
+ * TODO: [🦚] Add download button functionality
199
+ * TODO: [🦚] Add image regeneration option for default-avatar
200
+ */
@@ -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';
@@ -7,6 +7,7 @@ import { textColor } from '../../../../../../../../src/utils/color/operators/fur
7
7
  import { grayscale } from '../../../../../../../../src/utils/color/operators/grayscale';
8
8
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
9
9
  import { getAgentName, getAgentProfile } from '../../_utils';
10
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
10
11
 
11
12
  const size = {
12
13
  width: 1920,
@@ -51,7 +52,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
51
52
  backgroundColor: agentColor.toHex(),
52
53
  borderRadius: '50%',
53
54
  }}
54
- src={agentProfile.meta.image!}
55
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
55
56
  alt="Agent Icon"
56
57
  />
57
58
  </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';
@@ -7,6 +7,7 @@ import { textColor } from '../../../../../../../../src/utils/color/operators/fur
7
7
  import { grayscale } from '../../../../../../../../src/utils/color/operators/grayscale';
8
8
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
9
9
  import { getAgentName, getAgentProfile } from '../../_utils';
10
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
10
11
 
11
12
  const size = {
12
13
  width: 1080,
@@ -51,7 +52,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
51
52
  backgroundColor: agentColor.toHex(),
52
53
  borderRadius: '50%',
53
54
  }}
54
- src={agentProfile.meta.image!}
55
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
55
56
  alt="Agent Icon"
56
57
  />
57
58
  </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';
@@ -20,6 +20,7 @@ import { CopyField } from '../CopyField';
20
20
  import { generateAgentMetadata } from '../generateAgentMetadata';
21
21
  import { SdkCodeTabs } from './SdkCodeTabs';
22
22
  import { WebsiteIntegrationTabs } from './WebsiteIntegrationTabs';
23
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
23
24
 
24
25
  export const generateMetadata = generateAgentMetadata;
25
26
 
@@ -187,7 +188,7 @@ export default async function AgentIntegrationPage({ params }: { params: Promise
187
188
  {agentProfile.meta.image && (
188
189
  // eslint-disable-next-line @next/next/no-img-element
189
190
  <img
190
- src={agentProfile.meta.image as string}
191
+ src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
191
192
  alt={agentProfile.meta.fullname || agentName}
192
193
  className="w-16 h-16 rounded-full object-cover border-2"
193
194
  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';
@@ -13,6 +13,7 @@ import { getAgentName, getAgentProfile } from '../_utils';
13
13
  import { getAgentExternalLinks, getAgentLinks } from '../agentLinks';
14
14
  import { CopyField } from '../CopyField';
15
15
  import { generateAgentMetadata } from '../generateAgentMetadata';
16
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
16
17
 
17
18
  export const generateMetadata = generateAgentMetadata;
18
19
 
@@ -56,7 +57,7 @@ export default async function AgentLinksPage({ params }: { params: Promise<{ age
56
57
  {agentProfile.meta.image && (
57
58
  // eslint-disable-next-line @next/next/no-img-element
58
59
  <img
59
- src={agentProfile.meta.image as string}
60
+ src={agentProfile.meta.image || agentProfile.permanentId || generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
60
61
  alt={agentProfile.meta.fullname || agentName}
61
62
  className="w-16 h-16 rounded-full object-cover border-2"
62
63
  style={{ borderColor: primaryColor }}
@@ -1,4 +1,5 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
2
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
4
5
  import { assertsError } from '../../../../../../src/errors/assertsError';
@@ -54,7 +55,14 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
54
55
  backgroundColor: agentColor.toHex(),
55
56
  borderRadius: '50%',
56
57
  }}
57
- src={agentProfile.meta.image!}
58
+ src={
59
+ agentProfile.meta.image ||
60
+ agentProfile.permanentId ||
61
+ generatePlaceholderAgentProfileImageUrl(
62
+ agentProfile.permanentId || agentName,
63
+ NEXT_PUBLIC_SITE_URL,
64
+ )
65
+ }
58
66
  alt="Agent Icon"
59
67
  />
60
68
  </div>
@@ -1,19 +1,19 @@
1
1
  'use server';
2
2
 
3
+ import { NEXT_PUBLIC_SITE_URL } from '@/config';
3
4
  import { $provideServer } from '@/src/tools/$provideServer';
4
5
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
5
6
  import { saturate } from '@promptbook-local/color';
6
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
7
- import { NotFoundError } from '@promptbook-local/core';
7
+ import { generatePlaceholderAgentProfileImageUrl, NotFoundError, PROMPTBOOK_COLOR } from '@promptbook-local/core';
8
8
  import { notFound } from 'next/navigation';
9
9
  import { Color } from '../../../../../../src/utils/color/Color';
10
+ import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
10
11
  import { getAgentName, getAgentProfile, isAgentDeleted } from './_utils';
11
12
  import { getAgentLinks } from './agentLinks';
12
13
  import { AgentProfileChat } from './AgentProfileChat';
13
14
  import { AgentProfileWrapper } from './AgentProfileWrapper';
14
15
  import { generateAgentMetadata } from './generateAgentMetadata';
15
16
  import { ServiceWorkerRegister } from './ServiceWorkerRegister';
16
- import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
17
17
 
18
18
  export const generateMetadata = generateAgentMetadata;
19
19
 
@@ -96,7 +96,11 @@ export default async function AgentPage({
96
96
  agentName={agentName}
97
97
  fullname={fullname}
98
98
  brandColorHex={brandColorHex}
99
- avatarSrc={agentProfile.meta.image!}
99
+ avatarSrc={
100
+ agentProfile.meta.image ||
101
+ agentProfile.permanentId ||
102
+ generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)
103
+ }
100
104
  isDeleted={isDeleted}
101
105
  />
102
106
  </AgentProfileWrapper>