@promptbook/cli 0.104.0-7 → 0.104.0-8

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 (39) hide show
  1. package/apps/agents-server/config.ts +1 -3
  2. package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +85 -0
  3. package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
  4. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +49 -10
  5. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +0 -3
  6. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +4 -2
  7. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +5 -2
  8. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +10 -2
  9. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +5 -5
  10. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +5 -5
  11. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +8 -2
  12. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +8 -2
  13. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +4 -3
  14. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +3 -5
  15. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +15 -4
  16. package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
  17. package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
  18. package/apps/agents-server/src/app/page.tsx +4 -2
  19. package/apps/agents-server/src/app/recycle-bin/page.tsx +3 -1
  20. package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
  21. package/apps/agents-server/src/app/security.txt/route.ts +1 -1
  22. package/apps/agents-server/src/app/sitemap.xml/route.ts +4 -5
  23. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +22 -14
  24. package/apps/agents-server/src/components/Header/Header.tsx +4 -0
  25. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +46 -10
  26. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +32 -14
  27. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +22 -6
  28. package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
  29. package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
  30. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
  31. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
  32. package/apps/agents-server/src/tools/$provideBrowserForServer.ts +29 -0
  33. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  34. package/esm/index.es.js +1 -1
  35. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +2 -2
  36. package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
  37. package/esm/typings/src/version.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/umd/index.umd.js +1 -1
@@ -29,9 +29,7 @@ const config = ConfigChecker.from({
29
29
  *
30
30
  * Note: When `SERVERS` are used, this URL will be overridden by the server URL.
31
31
  */
32
- export const NEXT_PUBLIC_SITE_URL = config
33
- .get('NEXT_PUBLIC_SITE_URL')
34
- .url()./* <- TODO: !!!! Is it ok not to be required().*/ value;
32
+ export const NEXT_PUBLIC_SITE_URL = config.get('NEXT_PUBLIC_SITE_URL').url().value;
35
33
 
36
34
  /**
37
35
  * [♐️] Vercel environment: "development" | "preview" | "production"
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { Card } from '../../../components/Homepage/Card';
5
+
6
+ export function BrowserTestClient() {
7
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
8
+
9
+ useEffect(() => {
10
+ return () => {
11
+ if (imageUrl) {
12
+ URL.revokeObjectURL(imageUrl);
13
+ }
14
+ };
15
+ }, [imageUrl]);
16
+ const [isLoading, setIsLoading] = useState(false);
17
+ const [error, setError] = useState<string | null>(null);
18
+
19
+ const handleTakeScreenshot = async () => {
20
+ setIsLoading(true);
21
+ setError(null);
22
+ try {
23
+ const response = await fetch('/api/browser-test/screenshot');
24
+ if (!response.ok) {
25
+ const text = await response.text();
26
+ let errorMessage;
27
+ try {
28
+ const json = JSON.parse(text);
29
+ errorMessage = json.error || response.statusText;
30
+ } catch {
31
+ errorMessage = text || response.statusText;
32
+ }
33
+ throw new Error(`Error: ${response.status} ${errorMessage}`);
34
+ }
35
+ const blob = await response.blob();
36
+ const url = URL.createObjectURL(blob);
37
+ setImageUrl(url);
38
+ } catch (err) {
39
+ setError(String(err));
40
+ } finally {
41
+ setIsLoading(false);
42
+ }
43
+ };
44
+
45
+ return (
46
+ <div className="container mx-auto px-4 py-8 space-y-6">
47
+ <div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
48
+ <div>
49
+ <h1 className="text-3xl text-gray-900 font-light">Browser Test</h1>
50
+ <p className="mt-1 text-sm text-gray-500">
51
+ Launch a browser instance and take a screenshot to verify functionality.
52
+ </p>
53
+ </div>
54
+ </div>
55
+
56
+ <Card>
57
+ <div className="mb-4">
58
+ <p className="mb-2">Click the button below to launch a browser instance (if not running), navigate to ptbk.io, and take a screenshot.</p>
59
+ <button
60
+ onClick={handleTakeScreenshot}
61
+ disabled={isLoading}
62
+ className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
63
+ >
64
+ {isLoading ? 'Taking Screenshot...' : 'Take Screenshot'}
65
+ </button>
66
+ </div>
67
+
68
+ {error && (
69
+ <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4" role="alert">
70
+ <strong className="font-bold">Error: </strong>
71
+ <span className="block sm:inline">{error}</span>
72
+ </div>
73
+ )}
74
+
75
+ {imageUrl && (
76
+ <div className="border rounded shadow-lg overflow-hidden">
77
+ <h2 className="text-xl font-semibold p-2 bg-gray-100">Screenshot</h2>
78
+ {/* eslint-disable-next-line @next/next/no-img-element */}
79
+ <img src={imageUrl} alt="Screenshot of ptbk.io" className="w-full h-auto" />
80
+ </div>
81
+ )}
82
+ </Card>
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,13 @@
1
+ import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
2
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
3
+ import { BrowserTestClient } from './BrowserTestClient';
4
+
5
+ export default async function BrowserTestPage() {
6
+ const isAdmin = await isUserAdmin();
7
+
8
+ if (!isAdmin) {
9
+ return <ForbiddenPage />;
10
+ }
11
+
12
+ return <BrowserTestClient />;
13
+ }
@@ -5,19 +5,57 @@ import { AgentProfile } from '../../../components/AgentProfile/AgentProfile';
5
5
  import { AgentOptionsMenu } from './AgentOptionsMenu';
6
6
 
7
7
  type AgentProfileWrapperProps = {
8
- agent: AgentBasicInformation;
9
- agentUrl: string;
10
- agentEmail: string;
11
- agentName: string_agent_name;
12
- brandColorHex: string;
13
- isAdmin: boolean;
14
- isHeadless: boolean;
15
- actions: React.ReactNode;
16
- children: React.ReactNode;
8
+ /***
9
+ * @@@
10
+ */
11
+ readonly agent: AgentBasicInformation;
12
+
13
+ /***
14
+ * @@@
15
+ */
16
+ readonly agentUrl: string;
17
+
18
+ /**
19
+ * Base URL of the agents server
20
+ */
21
+ readonly publicUrl: URL;
22
+
23
+ /***
24
+ * @@@
25
+ */
26
+ readonly agentEmail: string;
27
+
28
+ /***
29
+ * @@@
30
+ */
31
+ readonly agentName: string_agent_name;
32
+
33
+ /***
34
+ * @@@
35
+ */
36
+ readonly brandColorHex: string;
37
+
38
+ /***
39
+ * @@@
40
+ */
41
+ readonly isAdmin: boolean;
42
+
43
+ /***
44
+ * @@@
45
+ */
46
+ readonly isHeadless: boolean;
47
+
48
+ readonly actions: React.ReactNode;
49
+
50
+ /***
51
+ * @@@
52
+ */
53
+ readonly children: React.ReactNode;
17
54
  };
18
55
 
19
56
  export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
20
- const { agent, agentUrl, agentEmail, agentName, brandColorHex, isAdmin, isHeadless, actions, children } = props;
57
+ const { agent, agentUrl, publicUrl, agentEmail, agentName, brandColorHex, isAdmin, isHeadless, actions, children } =
58
+ props;
21
59
 
22
60
  // Derived agentName from agent data
23
61
  const derivedAgentName = agent.agentName;
@@ -27,6 +65,7 @@ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
27
65
  <AgentProfile
28
66
  agent={agent}
29
67
  agentUrl={agentUrl}
68
+ publicUrl={publicUrl}
30
69
  permanentId={permanentId || agentName}
31
70
  agentEmail={agentEmail}
32
71
  isHeadless={isHeadless}
@@ -14,9 +14,6 @@ export async function getAgentProfile(agentName: string) {
14
14
  const collection = await $provideAgentCollectionForServer();
15
15
  const agentSource = await collection.getAgentSource(agentName);
16
16
  const agentProfile = parseAgentSource(agentSource);
17
-
18
- console.log('!!!!', { agentSource, agentProfile });
19
-
20
17
  return agentProfile;
21
18
  }
22
19
 
@@ -121,8 +121,10 @@ export default function AgentCodePage({ params }: { params: Promise<{ agentName:
121
121
  <img
122
122
  src={
123
123
  agentProfile.meta.image ||
124
- agentProfile.permanentId ||
125
- generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)
124
+ generatePlaceholderAgentProfileImageUrl(
125
+ agentProfile.permanentId || agentName,
126
+ NEXT_PUBLIC_SITE_URL, // <- TODO: !!!! Use here `const { publicUrl } = await $provideServer();`
127
+ )
126
128
  }
127
129
  alt={agentProfile.meta.fullname || agentName}
128
130
  className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
@@ -58,7 +58,8 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
58
58
  // Image exists, fetch from CDN and return directly
59
59
  const imageResponse = await fetch(existingImage.cdnUrl as string_url);
60
60
  if (!imageResponse.ok) {
61
- throw new Error(`Failed to fetch image from CDN: ${imageResponse.status}`);
61
+ console.warn(`Failed to fetch image from CDN: ${imageResponse.status}`);
62
+ return NextResponse.redirect(existingImage.cdnUrl);
62
63
  }
63
64
  const imageBuffer = await imageResponse.arrayBuffer();
64
65
  return new NextResponse(imageBuffer, {
@@ -88,6 +89,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
88
89
  modelVariant: 'IMAGE_GENERATION',
89
90
  modelName: 'dall-e-3',
90
91
  size: '1024x1792', // <- Vertical orientation
92
+ // <- TODO: [🤐] DRY
91
93
  quality: 'hd',
92
94
  style: 'natural',
93
95
  },
@@ -133,7 +135,8 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
133
135
  // Return the newly created image directly
134
136
  const finalImageResponse = await fetch(cdnUrl.href);
135
137
  if (!finalImageResponse.ok) {
136
- throw new Error(`Failed to fetch newly created image from CDN: ${finalImageResponse.status}`);
138
+ console.warn(`Failed to fetch newly created image from CDN: ${finalImageResponse.status}`);
139
+ return NextResponse.redirect(cdnUrl.href);
137
140
  }
138
141
  const finalImageBuffer = await finalImageResponse.arrayBuffer();
139
142
  return new NextResponse(finalImageBuffer, {
@@ -1,3 +1,4 @@
1
+ import { $provideServer } from '@/src/tools/$provideServer';
1
2
  import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
@@ -5,7 +6,6 @@ import { assertsError } from '../../../../../../../../src/errors/assertsError';
5
6
  import { Color } from '../../../../../../../../src/utils/color/Color';
6
7
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
7
8
  import { getAgentName, getAgentProfile } from '../../_utils';
8
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
9
9
 
10
10
  const size = {
11
11
  width: 256,
@@ -19,6 +19,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
19
19
  const agentName = await getAgentName(params);
20
20
  const agentProfile = await getAgentProfile(agentName);
21
21
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
22
+ const { publicUrl } = await $provideServer();
22
23
 
23
24
  return new ImageResponse(
24
25
  (
@@ -31,6 +32,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
31
32
  alignItems: 'center',
32
33
  justifyContent: 'center',
33
34
  borderRadius: '50%',
35
+ aspectRatio: '1 / 1',
34
36
  overflow: 'hidden',
35
37
  }}
36
38
  >
@@ -47,7 +49,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
47
49
  {/* Note: `next/image` is not working propperly with `next/og` */}
48
50
  {/* eslint-disable-next-line @next/next/no-img-element */}
49
51
  <img
50
- src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
52
+ src={
53
+ agentProfile.meta.image ||
54
+ generatePlaceholderAgentProfileImageUrl(
55
+ agentProfile.permanentId || agentName,
56
+ publicUrl,
57
+ )
58
+ }
51
59
  alt="Agent Icon"
52
60
  />
53
61
  </div>
@@ -1,4 +1,5 @@
1
- import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { $provideServer } from '@/src/tools/$provideServer';
2
+ import { 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';
@@ -7,7 +8,6 @@ import { textColor } from '../../../../../../../../src/utils/color/operators/fur
7
8
  import { grayscale } from '../../../../../../../../src/utils/color/operators/grayscale';
8
9
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
9
10
  import { getAgentName, getAgentProfile } from '../../_utils';
10
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
11
11
 
12
12
  const size = {
13
13
  width: 1920,
@@ -22,6 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
22
22
  const agentProfile = await getAgentProfile(agentName);
23
23
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
24
24
  const backgroundColor = agentColor.then(grayscale(0.5));
25
+ const { publicUrl } = await $provideServer();
25
26
 
26
27
  return new ImageResponse(
27
28
  (
@@ -49,10 +50,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
49
50
  <img
50
51
  style={{
51
52
  width: '80%',
52
- backgroundColor: agentColor.toHex(),
53
- borderRadius: '50%',
53
+ // backgroundColor: agentColor.toHex(),
54
54
  }}
55
- src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
55
+ src={`${publicUrl.href}agents/${agentProfile.permanentId || agentName}/images/icon-256.png`}
56
56
  alt="Agent Icon"
57
57
  />
58
58
  </div>
@@ -1,4 +1,5 @@
1
- import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { $provideServer } from '@/src/tools/$provideServer';
2
+ import { 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';
@@ -7,7 +8,6 @@ import { textColor } from '../../../../../../../../src/utils/color/operators/fur
7
8
  import { grayscale } from '../../../../../../../../src/utils/color/operators/grayscale';
8
9
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
9
10
  import { getAgentName, getAgentProfile } from '../../_utils';
10
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
11
11
 
12
12
  const size = {
13
13
  width: 1080,
@@ -22,6 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
22
22
  const agentProfile = await getAgentProfile(agentName);
23
23
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
24
24
  const backgroundColor = agentColor.then(grayscale(0.5));
25
+ const { publicUrl } = await $provideServer();
25
26
 
26
27
  return new ImageResponse(
27
28
  (
@@ -49,10 +50,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
49
50
  <img
50
51
  style={{
51
52
  width: '80%',
52
- backgroundColor: agentColor.toHex(),
53
- borderRadius: '50%',
53
+ // backgroundColor: agentColor.toHex(),
54
54
  }}
55
- src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
55
+ src={`${publicUrl.href}agents/${agentProfile.permanentId || agentName}/images/icon-256.png`}
56
56
  alt="Agent Icon"
57
57
  />
58
58
  </div>
@@ -20,12 +20,12 @@ 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';
24
23
 
25
24
  export const generateMetadata = generateAgentMetadata;
26
25
 
27
26
  export default async function AgentIntegrationPage({ params }: { params: Promise<{ agentName: string }> }) {
28
27
  $sideEffect(headers());
28
+
29
29
  const agentName = await getAgentName(params);
30
30
  const isAdmin = await isUserAdmin();
31
31
 
@@ -188,7 +188,13 @@ export default async function AgentIntegrationPage({ params }: { params: Promise
188
188
  {agentProfile.meta.image && (
189
189
  // eslint-disable-next-line @next/next/no-img-element
190
190
  <img
191
- src={agentProfile.meta.image || agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
191
+ src={
192
+ agentProfile.meta.image ||
193
+ generatePlaceholderAgentProfileImageUrl(
194
+ agentProfile.permanentId || agentName,
195
+ publicUrl,
196
+ )
197
+ }
192
198
  alt={agentProfile.meta.fullname || agentName}
193
199
  className="w-16 h-16 rounded-full object-cover border-2"
194
200
  style={{ borderColor: primaryColor }}
@@ -13,12 +13,12 @@ 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';
17
16
 
18
17
  export const generateMetadata = generateAgentMetadata;
19
18
 
20
19
  export default async function AgentLinksPage({ params }: { params: Promise<{ agentName: string }> }) {
21
20
  $sideEffect(headers());
21
+
22
22
  const agentName = await getAgentName(params);
23
23
 
24
24
  let agentProfile;
@@ -57,7 +57,13 @@ export default async function AgentLinksPage({ params }: { params: Promise<{ age
57
57
  {agentProfile.meta.image && (
58
58
  // eslint-disable-next-line @next/next/no-img-element
59
59
  <img
60
- src={agentProfile.meta.image || agentProfile.permanentId || generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
60
+ src={
61
+ agentProfile.meta.image ||
62
+ generatePlaceholderAgentProfileImageUrl(
63
+ agentProfile.permanentId || agentName,
64
+ publicUrl,
65
+ )
66
+ }
61
67
  alt={agentProfile.meta.fullname || agentName}
62
68
  className="w-16 h-16 rounded-full object-cover border-2"
63
69
  style={{ borderColor: primaryColor }}
@@ -1,4 +1,4 @@
1
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
1
+ import { $provideServer } from '@/src/tools/$provideServer';
2
2
  import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
3
3
  import { serializeError } from '@promptbook-local/utils';
4
4
  import { ImageResponse } from 'next/og';
@@ -25,6 +25,7 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
25
25
  const agentProfile = await getAgentProfile(agentName);
26
26
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
27
27
  const backgroundColor = agentColor.then(grayscale(0.5));
28
+ const { publicUrl } = await $provideServer();
28
29
 
29
30
  return new ImageResponse(
30
31
  (
@@ -54,13 +55,13 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
54
55
  width: '80%',
55
56
  backgroundColor: agentColor.toHex(),
56
57
  borderRadius: '50%',
58
+ aspectRatio: '1 / 1',
57
59
  }}
58
60
  src={
59
61
  agentProfile.meta.image ||
60
- agentProfile.permanentId ||
61
62
  generatePlaceholderAgentProfileImageUrl(
62
63
  agentProfile.permanentId || agentName,
63
- NEXT_PUBLIC_SITE_URL,
64
+ publicUrl,
64
65
  )
65
66
  }
66
67
  alt="Agent Icon"
@@ -1,6 +1,5 @@
1
1
  'use server';
2
2
 
3
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
4
3
  import { $provideServer } from '@/src/tools/$provideServer';
5
4
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
6
5
  import { saturate } from '@promptbook-local/color';
@@ -28,6 +27,7 @@ export default async function AgentPage({
28
27
  const isAdmin = await isUserAdmin();
29
28
  const { headless: headlessParam } = await searchParams;
30
29
  const isHeadless = headlessParam !== undefined;
30
+ const { publicUrl } = await $provideServer();
31
31
 
32
32
  let agentProfile;
33
33
  try {
@@ -45,8 +45,6 @@ export default async function AgentPage({
45
45
  throw error;
46
46
  }
47
47
 
48
- const { publicUrl } = await $provideServer();
49
-
50
48
  // Build agent page URL for QR and copy
51
49
  const agentUrl = `${publicUrl.href}${encodeURIComponent(agentName)}`;
52
50
  // <- TODO: [🐱‍🚀] Better
@@ -65,6 +63,7 @@ export default async function AgentPage({
65
63
  <AgentProfileWrapper
66
64
  agent={agentProfile}
67
65
  agentUrl={agentUrl}
66
+ publicUrl={publicUrl}
68
67
  agentEmail={agentEmail}
69
68
  agentName={agentName}
70
69
  brandColorHex={brandColorHex}
@@ -98,8 +97,7 @@ export default async function AgentPage({
98
97
  brandColorHex={brandColorHex}
99
98
  avatarSrc={
100
99
  agentProfile.meta.image ||
101
- agentProfile.permanentId ||
102
- generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)
100
+ generatePlaceholderAgentProfileImageUrl(agentProfile.permanentId || agentName, publicUrl)
103
101
  }
104
102
  isDeleted={isDeleted}
105
103
  />
@@ -1,6 +1,8 @@
1
1
  'use server';
2
2
 
3
3
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { $provideServer } from '@/src/tools/$provideServer';
5
+ import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
4
6
  import { ArrowLeftIcon, FileTextIcon } from 'lucide-react';
5
7
  import { headers } from 'next/headers';
6
8
  import Link from 'next/link';
@@ -8,13 +10,14 @@ import { notFound } from 'next/navigation';
8
10
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
9
11
  import { getAgentName, getAgentProfile } from '../_utils';
10
12
  import { generateAgentMetadata } from '../generateAgentMetadata';
11
- import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
12
- import { NEXT_PUBLIC_SITE_URL } from '@/config';
13
13
 
14
14
  export const generateMetadata = generateAgentMetadata;
15
15
 
16
16
  export default async function AgentSystemMessagePage({ params }: { params: Promise<{ agentName: string }> }) {
17
17
  $sideEffect(headers());
18
+
19
+ const { publicUrl } = await $provideServer();
20
+
18
21
  const agentName = await getAgentName(params);
19
22
 
20
23
  let agentProfile;
@@ -46,7 +49,13 @@ export default async function AgentSystemMessagePage({ params }: { params: Promi
46
49
  {agentProfile.meta.image && (
47
50
  // eslint-disable-next-line @next/next/no-img-element
48
51
  <img
49
- src={agentProfile.meta.image|| agentProfile.permanentId ||generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)}
52
+ src={
53
+ agentProfile.meta.image ||
54
+ generatePlaceholderAgentProfileImageUrl(
55
+ agentProfile.permanentId || agentName,
56
+ publicUrl,
57
+ )
58
+ }
50
59
  alt={agentProfile.meta.fullname || agentName}
51
60
  className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
52
61
  />
@@ -78,7 +87,9 @@ export default async function AgentSystemMessagePage({ params }: { params: Promi
78
87
  <div className="mt-6 bg-blue-50 rounded-lg p-4 border border-blue-200">
79
88
  <h3 className="text-md font-semibold text-blue-900 mb-2">Model Requirements</h3>
80
89
  <div className="text-sm text-blue-800">
81
- <p><strong>Model Variant:</strong> CHAT</p>
90
+ <p>
91
+ <strong>Model Variant:</strong> CHAT
92
+ </p>
82
93
  {/* TODO: [🧠] Add more model requirements if available */}
83
94
  </div>
84
95
  </div>
@@ -0,0 +1,30 @@
1
+ import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
2
+ import { serializeError } from '@promptbook-local/utils';
3
+ import { NextResponse } from 'next/server';
4
+ import { assertsError } from '../../../../../../../src/errors/assertsError';
5
+
6
+ export async function GET() {
7
+ try {
8
+ const browserContext = await $provideBrowserForServer();
9
+
10
+ const page = await browserContext.newPage();
11
+
12
+ await page.goto('https://ptbk.io');
13
+ const screenshotBuffer = await page.screenshot();
14
+
15
+ // await page.close();
16
+ // Do not close browser
17
+ // <- TODO: !!!! Fix
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ return new NextResponse(new Blob([screenshotBuffer as any]), {
21
+ headers: {
22
+ 'Content-Type': 'image/png',
23
+ },
24
+ });
25
+ } catch (error) {
26
+ assertsError(error);
27
+ console.error('Error taking screenshot:', error);
28
+ return NextResponse.json({ error: serializeError(error) }, { status: 500 });
29
+ }
30
+ }
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
6
6
  export const dynamic = 'force-dynamic';
7
7
 
8
8
  export async function GET() {
9
- const txt = generateHumansTxt();
9
+ const txt = await generateHumansTxt();
10
10
  return new NextResponse(txt, {
11
11
  headers: {
12
12
  'Content-Type': 'text/plain',
@@ -34,6 +34,8 @@ const calendarWithSeconds = {
34
34
  export default async function HomePage() {
35
35
  $sideEffect(/* Note: [🐶] This will ensure dynamic rendering of page and avoid Next.js pre-render */ headers());
36
36
 
37
+ const { publicUrl } = await $provideServer();
38
+
37
39
  const currentUser = await getCurrentUser();
38
40
  const isAdmin = await isUserAdmin(); /* <- TODO: [👹] Here should be user permissions */
39
41
 
@@ -94,9 +96,9 @@ export default async function HomePage() {
94
96
  return (
95
97
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
96
98
  <div className="container mx-auto px-4 py-16">
97
- <AgentsList agents={[...agents]} isAdmin={isAdmin} />
99
+ <AgentsList agents={[...agents]} isAdmin={isAdmin} publicUrl={publicUrl} />
98
100
 
99
- <ExternalAgentsSectionClient />
101
+ <ExternalAgentsSectionClient publicUrl={publicUrl} />
100
102
 
101
103
  {isAdmin && <UsersList allowCreate={false} />}
102
104
 
@@ -1,4 +1,5 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { $provideServer } from '@/src/tools/$provideServer';
2
3
  import { TrashIcon } from 'lucide-react';
3
4
  import Link from 'next/link';
4
5
  import { DeletedAgentsList } from '../../components/Homepage/DeletedAgentsList';
@@ -9,6 +10,7 @@ export const metadata = {
9
10
  };
10
11
 
11
12
  export default async function RecycleBinPage() {
13
+ const { publicUrl } = await $provideServer();
12
14
  const collection = await $provideAgentCollectionForServer();
13
15
  const deletedAgents = await collection.listDeletedAgents();
14
16
  const isAdmin = await isUserAdmin();
@@ -34,7 +36,7 @@ export default async function RecycleBinPage() {
34
36
  </Link>
35
37
  </div>
36
38
  ) : (
37
- <DeletedAgentsList agents={deletedAgents} isAdmin={isAdmin} />
39
+ <DeletedAgentsList agents={deletedAgents} isAdmin={isAdmin} publicUrl={publicUrl} />
38
40
  )}
39
41
  </div>
40
42
  </div>
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
6
6
  export const dynamic = 'force-dynamic';
7
7
 
8
8
  export async function GET() {
9
- const txt = generateRobotsTxt();
9
+ const txt = await generateRobotsTxt();
10
10
  return new NextResponse(txt, {
11
11
  headers: {
12
12
  'Content-Type': 'text/plain',
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
6
6
  export const dynamic = 'force-dynamic';
7
7
 
8
8
  export async function GET() {
9
- const txt = generateSecurityTxt();
9
+ const txt = await generateSecurityTxt();
10
10
  return new NextResponse(txt, {
11
11
  headers: {
12
12
  'Content-Type': 'text/plain',