@promptbook/cli 0.103.0-56 → 0.103.0-66

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 (50) hide show
  1. package/apps/agents-server/TODO.txt +5 -1
  2. package/apps/agents-server/package-lock.json +1220 -47
  3. package/apps/agents-server/package.json +4 -1
  4. package/apps/agents-server/src/app/actions.ts +3 -1
  5. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +72 -6
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +20 -7
  7. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +44 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +7 -3
  9. package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +41 -0
  10. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +47 -100
  11. package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +11 -2
  12. package/apps/agents-server/src/app/embed/page.tsx +2 -2
  13. package/apps/agents-server/src/app/layout.tsx +8 -24
  14. package/apps/agents-server/src/app/manifest.ts +8 -3
  15. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +334 -0
  16. package/apps/agents-server/src/components/AgentProfile/AgentProfileFromSource.tsx +23 -0
  17. package/apps/agents-server/src/{app/agents/[agentName] → components/AgentProfile}/AgentQrCode.tsx +8 -1
  18. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +7 -6
  19. package/apps/agents-server/src/database/metadataDefaults.ts +6 -0
  20. package/esm/index.es.js +65 -10
  21. package/esm/index.es.js.map +1 -1
  22. package/esm/typings/src/_packages/components.index.d.ts +2 -2
  23. package/esm/typings/src/_packages/types.index.d.ts +6 -0
  24. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -1
  25. package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
  26. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +3 -0
  27. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  28. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentIntegration.d.ts +52 -0
  29. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentSeamlessIntegration.d.ts +14 -0
  30. package/esm/typings/src/book-components/icons/SendIcon.d.ts +3 -0
  31. package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +4 -0
  32. package/esm/typings/src/commitments/CLOSED/CLOSED.test.d.ts +4 -0
  33. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +4 -0
  34. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +6 -0
  35. package/esm/typings/src/llm-providers/agent/Agent.d.ts +3 -1
  36. package/esm/typings/src/other/templates/getTemplatesPipelineCollection.d.ts +1 -1
  37. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  38. package/esm/typings/src/utils/color/Color.d.ts +1 -1
  39. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +6 -0
  40. package/esm/typings/src/utils/random/CzechNamePool.d.ts +7 -0
  41. package/esm/typings/src/utils/random/EnglishNamePool.d.ts +7 -0
  42. package/esm/typings/src/utils/random/NamePool.d.ts +17 -0
  43. package/esm/typings/src/utils/random/getNamePool.d.ts +10 -0
  44. package/esm/typings/src/version.d.ts +1 -1
  45. package/package.json +2 -2
  46. package/umd/index.umd.js +65 -10
  47. package/umd/index.umd.js.map +1 -1
  48. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileView.tsx +0 -233
  49. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +0 -29
  50. /package/apps/agents-server/src/{app/agents/[agentName] → components/AgentProfile}/QrCodeModal.tsx +0 -0
@@ -10,7 +10,10 @@
10
10
  "postinstall": "cd ../../ && npm ci"
11
11
  },
12
12
  "devDependencies": {
13
- "@tailwindcss/typography": "^0.5.19"
13
+ "@tailwindcss/typography": "^0.5.19",
14
+ "autoprefixer": "^10.4.21",
15
+ "postcss": "^8.5.6",
16
+ "tailwindcss": "^3.4.18"
14
17
  },
15
18
  "dependencies": {
16
19
  "remark-gfm": "^4.0.1"
@@ -4,6 +4,7 @@ import { $generateBookBoilerplate } from '@promptbook-local/core';
4
4
  import { string_agent_name } from '@promptbook-local/types';
5
5
  import { revalidatePath } from 'next/cache';
6
6
  import { cookies } from 'next/headers';
7
+ import { getMetadata } from '../database/getMetadata';
7
8
  import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
8
9
  import { authenticateUser } from '../utils/authenticateUser';
9
10
  import { isUserAdmin } from '../utils/isUserAdmin';
@@ -16,7 +17,8 @@ export async function $createAgentAction(): Promise<string_agent_name> {
16
17
  }
17
18
 
18
19
  const collection = await $provideAgentCollectionForServer();
19
- const agentSource = $generateBookBoilerplate();
20
+ const namePool = (await getMetadata('NAME_POOL')) || 'ENGLISH';
21
+ const agentSource = $generateBookBoilerplate({ namePool });
20
22
 
21
23
  const { agentName } = await collection.createAgent(agentSource);
22
24
 
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { TODO_any } from '@promptbook-local/types';
3
4
  import {
4
5
  CopyIcon,
5
6
  CopyPlusIcon,
@@ -10,12 +11,25 @@ import {
10
11
  MessageSquareShareIcon,
11
12
  MoreHorizontalIcon,
12
13
  QrCodeIcon,
14
+ SmartphoneIcon,
13
15
  SquareSplitHorizontalIcon,
14
16
  } from 'lucide-react';
15
- import { useEffect, useRef, useState } from 'react';
17
+ import { Barlow_Condensed } from 'next/font/google';
18
+ import { useCallback, useEffect, useRef, useState } from 'react';
16
19
  import { string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
17
20
  import { getAgentLinks } from './agentLinks';
18
21
 
22
+ type BeforeInstallPromptEvent = Event & {
23
+ prompt: () => Promise<void>;
24
+ userChoice: Promise<{ outcome: 'accepted' | 'dismissed'; platform: string }>;
25
+ };
26
+
27
+ const barlowCondensed = Barlow_Condensed({
28
+ subsets: ['latin'],
29
+ weight: ['300', '400', '500', '600', '700'],
30
+ variable: '--font-barlow-condensed',
31
+ });
32
+
19
33
  type AgentOptionsMenuProps = {
20
34
  agentName: string;
21
35
  agentUrl: string;
@@ -39,6 +53,45 @@ export function AgentOptionsMenu({
39
53
  const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
40
54
  const menuRef = useRef<HTMLDivElement>(null);
41
55
 
56
+ // PWA Install state
57
+ const [installPromptEvent, setInstallPromptEvent] = useState<BeforeInstallPromptEvent | null>(null);
58
+ const [isInstalled, setIsInstalled] = useState(false);
59
+
60
+ useEffect(() => {
61
+ function handleBeforeInstallPrompt(e: Event) {
62
+ e.preventDefault();
63
+ setInstallPromptEvent(e as BeforeInstallPromptEvent);
64
+ }
65
+
66
+ function updateInstalledStatus() {
67
+ const mediaMatch = window.matchMedia('(display-mode: standalone)');
68
+ const standalone = mediaMatch.matches || (window.navigator as TODO_any).standalone === true;
69
+ setIsInstalled(standalone);
70
+ }
71
+
72
+ window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
73
+ updateInstalledStatus();
74
+ window.matchMedia('(display-mode: standalone)').addEventListener('change', updateInstalledStatus);
75
+
76
+ return () => {
77
+ window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
78
+ window.matchMedia('(display-mode: standalone)').removeEventListener('change', updateInstalledStatus);
79
+ };
80
+ }, []);
81
+
82
+ const handleInstallApp = useCallback(async () => {
83
+ if (!installPromptEvent) return;
84
+ try {
85
+ installPromptEvent.prompt();
86
+ const choice = await installPromptEvent.userChoice.catch(() => null);
87
+ if (choice?.outcome === 'accepted') {
88
+ setIsInstalled(true);
89
+ }
90
+ } finally {
91
+ setInstallPromptEvent(null);
92
+ }
93
+ }, [installPromptEvent]);
94
+
42
95
  useEffect(() => {
43
96
  function handleClickOutside(event: MouseEvent) {
44
97
  if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
@@ -123,6 +176,17 @@ export function AgentOptionsMenu({
123
176
  label: 'Show QR Code',
124
177
  onClick: onShowQrCode,
125
178
  },
179
+ // Install App - only show if PWA is installable and not already installed
180
+ ...(!isInstalled && installPromptEvent
181
+ ? [
182
+ {
183
+ type: 'action' as const,
184
+ icon: SmartphoneIcon,
185
+ label: 'Install App',
186
+ onClick: handleInstallApp,
187
+ },
188
+ ]
189
+ : []),
126
190
  // Admin-only items
127
191
  ...(isAdmin
128
192
  ? [
@@ -162,18 +226,20 @@ export function AgentOptionsMenu({
162
226
  ];
163
227
 
164
228
  return (
165
- <div ref={menuRef} className="relative">
229
+ <div ref={menuRef} className="relative z-[9999]">
166
230
  <button
167
231
  onClick={() => setIsOpen(!isOpen)}
168
- className="p-3 rounded-full bg-white/20 hover:bg-white/30 transition-all duration-200 backdrop-blur-sm"
169
- style={{ backgroundColor: brandColorHex }}
232
+ className="p-3 rounded-full hover:bg-white/30 transition-all duration-200"
233
+ // style={{ backgroundColor: brandColorHex }}
170
234
  aria-label="More options"
171
235
  >
172
- <MoreHorizontalIcon className="w-5 h-5 text-white" />
236
+ <MoreHorizontalIcon className="w-5 h-5 text-black" />
173
237
  </button>
174
238
 
175
239
  {isOpen && (
176
- <div className="absolute right-0 top-full mt-2 w-56 bg-white rounded-xl shadow-2xl border border-gray-100 py-2 z-50 animate-in fade-in slide-in-from-top-2 duration-200">
240
+ <div
241
+ className={`absolute right-0 top-full mt-2 w-56 bg-white rounded-xl shadow-2xl border border-gray-100 py-2 z-[9999] animate-in fade-in slide-in-from-top-2 duration-200 ${barlowCondensed.className}`}
242
+ >
177
243
  {menuItems.map((item, index) => {
178
244
  if (item.type === 'divider') {
179
245
  return <div key={index} className="h-px bg-gray-100 my-2" />;
@@ -1,20 +1,22 @@
1
1
  'use client';
2
2
 
3
+ import { usePromise } from '@common/hooks/usePromise';
3
4
  import { Chat } from '@promptbook-local/components';
4
5
  import { RemoteAgent } from '@promptbook-local/core';
5
- import spaceTrim from 'spacetrim';
6
- import { useCallback, useMemo } from 'react';
7
- import { usePromise } from '@common/hooks/usePromise';
8
6
  import { useRouter } from 'next/navigation';
9
- import { string_agent_url } from '../../../../../../src/types/typeAliases';
7
+ import { useCallback, useMemo } from 'react';
8
+ import spaceTrim from 'spacetrim';
9
+ import { string_agent_url, string_color } from '../../../../../../src/types/typeAliases';
10
10
 
11
11
  type AgentProfileChatProps = {
12
12
  agentUrl: string_agent_url;
13
13
  agentName: string;
14
14
  fullname: string;
15
+ brandColorHex: string_color;
16
+ avatarSrc: string;
15
17
  };
16
18
 
17
- export function AgentProfileChat({ agentUrl, agentName, fullname }: AgentProfileChatProps) {
19
+ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex, avatarSrc }: AgentProfileChatProps) {
18
20
  const router = useRouter();
19
21
 
20
22
  const agentPromise = useMemo(
@@ -56,21 +58,32 @@ export function AgentProfileChat({ agentUrl, agentName, fullname }: AgentProfile
56
58
 
57
59
  return (
58
60
  <div className="w-full h-[400px] md:h-[500px]">
59
- <Chat
61
+ <Chat
60
62
  title={`Chat with ${fullname}`}
63
+ participants={[
64
+ {
65
+ name: 'AGENT',
66
+ fullname,
67
+ isMe: false,
68
+ color: brandColorHex,
69
+ avatarSrc,
70
+ // <- TODO: [🧠] Maybe this shouldnt be there
71
+ },
72
+ ]}
61
73
  messages={[
62
74
  {
63
75
  from: 'AGENT',
64
76
  content: initialMessage,
65
77
  date: new Date(),
66
78
  id: 'initial-message',
67
- isComplete: true
79
+ isComplete: true,
68
80
  },
69
81
  ]}
70
82
  onMessage={handleMessage}
71
83
  isSaveButtonEnabled={false}
72
84
  isCopyButtonEnabled={false}
73
85
  className="bg-transparent"
86
+ buttonColor={brandColorHex}
74
87
  style={{ background: 'transparent' }}
75
88
  />
76
89
  </div>
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { AgentBasicInformation } from '@promptbook-local/types';
4
+ import { AgentProfile } from '../../../components/AgentProfile/AgentProfile';
5
+ import { AgentOptionsMenu } from './AgentOptionsMenu';
6
+
7
+ type AgentProfileWrapperProps = {
8
+ agent: AgentBasicInformation;
9
+ agentUrl: string;
10
+ agentEmail: string;
11
+ agentName: string;
12
+ brandColorHex: string;
13
+ isAdmin: boolean;
14
+ isHeadless: boolean;
15
+ actions: React.ReactNode;
16
+ children: React.ReactNode;
17
+ };
18
+
19
+ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
20
+ const { agent, agentUrl, agentEmail, agentName, brandColorHex, isAdmin, isHeadless, actions, children } = props;
21
+
22
+ return (
23
+ <AgentProfile
24
+ agent={agent}
25
+ agentUrl={agentUrl}
26
+ agentEmail={agentEmail}
27
+ isHeadless={isHeadless}
28
+ renderMenu={({ onShowQrCode }) => (
29
+ <AgentOptionsMenu
30
+ agentName={agentName}
31
+ agentUrl={agentUrl}
32
+ agentEmail={agentEmail}
33
+ brandColorHex={brandColorHex}
34
+ isAdmin={isAdmin}
35
+ onShowQrCode={onShowQrCode}
36
+ backgroundImage="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYJh39z8ABJgCe/ZvAS4AAAAASUVORK5CYII="
37
+ />
38
+ )}
39
+ actions={actions}
40
+ >
41
+ {children}
42
+ </AgentProfile>
43
+ );
44
+ }
@@ -12,14 +12,18 @@ export async function generateAgentMetadata({ params }: { params: Promise<{ agen
12
12
  const title = agentProfile.meta.fullname || agentProfile.agentName;
13
13
  const description = agentProfile.meta.description || agentProfile.personaDescription || undefined;
14
14
 
15
- // Extract image from meta
16
- const image = agentProfile.meta.image;
15
+ // Use the agent's icon-256.png as the favicon
16
+ const iconUrl = `/agents/${encodeURIComponent(agentName)}/images/icon-256.png`;
17
17
 
18
18
  const metadata = {
19
19
  metadataBase: publicUrl,
20
20
  title,
21
21
  description,
22
- icons: image ? { icon: image } : undefined,
22
+ icons: {
23
+ icon: iconUrl,
24
+ shortcut: iconUrl,
25
+ apple: iconUrl,
26
+ },
23
27
  openGraph: {
24
28
  title,
25
29
  description,
@@ -0,0 +1,41 @@
1
+ 'use server';
2
+
3
+ import type { Metadata } from 'next';
4
+ import { getAgentName, getAgentProfile } from './_utils';
5
+
6
+ export async function generateMetadata({ params }: { params: Promise<{ agentName: string }> }): Promise<Metadata> {
7
+ const agentName = await getAgentName(params);
8
+
9
+ try {
10
+ const agentProfile = await getAgentProfile(agentName);
11
+
12
+ const title = agentProfile.meta.fullname || agentProfile.agentName;
13
+ const description = agentProfile.meta.description || agentProfile.personaDescription || undefined;
14
+
15
+ // Use the agent's icon-256.png as the favicon for all agent pages and subpages
16
+ const iconUrl = `/agents/${encodeURIComponent(agentName)}/images/icon-256.png`;
17
+
18
+ return {
19
+ title,
20
+ description,
21
+ icons: {
22
+ icon: iconUrl,
23
+ shortcut: iconUrl,
24
+ apple: iconUrl,
25
+ },
26
+ };
27
+ } catch (error) {
28
+ console.warn(`Failed to generate metadata for agent ${agentName}`, error);
29
+ return {
30
+ title: agentName,
31
+ };
32
+ }
33
+ }
34
+
35
+ export default async function AgentLayout({
36
+ children,
37
+ }: Readonly<{
38
+ children: React.ReactNode;
39
+ }>) {
40
+ return <>{children}</>;
41
+ }
@@ -2,22 +2,30 @@
2
2
 
3
3
  import { $provideServer } from '@/src/tools/$provideServer';
4
4
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
5
+ import { saturate } from '@promptbook-local/color';
5
6
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
6
7
  import { notFound } from 'next/navigation';
7
- import spaceTrim from 'spacetrim';
8
8
  import { Color } from '../../../../../../src/utils/color/Color';
9
- import { darken } from '../../../../../../src/utils/color/operators/darken';
10
- import { lighten } from '../../../../../../src/utils/color/operators/lighten';
11
9
  import { getAgentName, getAgentProfile } from './_utils';
12
- import { AgentProfileView } from './AgentProfileView';
10
+ import { getAgentLinks } from './agentLinks';
11
+ import { AgentProfileChat } from './AgentProfileChat';
12
+ import { AgentProfileWrapper } from './AgentProfileWrapper';
13
13
  import { generateAgentMetadata } from './generateAgentMetadata';
14
14
  import { ServiceWorkerRegister } from './ServiceWorkerRegister';
15
15
 
16
16
  export const generateMetadata = generateAgentMetadata;
17
17
 
18
- export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
18
+ export default async function AgentPage({
19
+ params,
20
+ searchParams,
21
+ }: {
22
+ params: Promise<{ agentName: string }>;
23
+ searchParams: Promise<{ headless?: string }>;
24
+ }) {
19
25
  const agentName = await getAgentName(params);
20
26
  const isAdmin = await isUserAdmin();
27
+ const { headless: headlessParam } = await searchParams;
28
+ const isHeadless = headlessParam !== undefined;
21
29
 
22
30
  let agentProfile;
23
31
  try {
@@ -42,111 +50,50 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
42
50
 
43
51
  const agentEmail = `${agentName}@${publicUrl.hostname}`;
44
52
 
45
- // Extract brand color from meta and create color variations
46
- const brandColorString = agentProfile.meta.color || PROMPTBOOK_COLOR.toHex();
47
- const brandColors = brandColorString.split(',').map((c) => Color.fromSafe(c.trim()));
48
-
49
- // Ensure at least one color
50
- if (brandColors.length === 0) {
51
- brandColors.push(PROMPTBOOK_COLOR);
52
- }
53
-
54
- const brandColor = brandColors[0]!;
55
- const brandColorHex = brandColor.toHex();
56
- const brandColorLightHex = brandColor.then(lighten(0.2)).toHex();
57
- const brandColorDarkHex = brandColor.then(darken(0.15)).toHex();
58
- const brandColorsHex = brandColors.map((c) => c.toHex());
59
-
60
- // Generate Noisy SVG Background
61
- const color1 = brandColors[0]!;
62
- const color2 = brandColors[1] || brandColors[0]!; // Use secondary color or fallback to primary
63
-
64
- // [🧠] Make colors much lighter for the background as per feedback
65
- const color1Light = color1.then(lighten(0.3)).toHex();
66
- const color1Main = color1.toHex();
67
- const color1Dark = color1.then(darken(0.3)).toHex();
68
-
69
- const color2Light = color2.then(lighten(0.3)).toHex();
70
- const color2Main = color2.toHex();
71
- const color2Dark = color2.then(darken(0.3)).toHex();
72
-
73
- const svgContent = spaceTrim(`
74
- <svg xmlns="http://www.w3.org/2000/svg"
75
- viewBox="0 0 1920 1080"
76
- width="1920" height="1080"
77
- preserveAspectRatio="xMidYMid slice">
78
- <defs>
79
- <!-- Bottom-left -->
80
- <radialGradient id="grad1" cx="0%" cy="100%" r="90%">
81
- <stop offset="0%" stop-color="${color1Light}" />
82
- <stop offset="50%" stop-color="${color1Main}" />
83
- <stop offset="100%" stop-color="${color1Dark}" />
84
- </radialGradient>
85
-
86
- <!-- Bottom-right -->
87
- <radialGradient id="grad2" cx="100%" cy="100%" r="90%">
88
- <stop offset="0%" stop-color="${color2Light}" />
89
- <stop offset="50%" stop-color="${color2Main}" />
90
- <stop offset="100%" stop-color="${color2Dark}" />
91
- </radialGradient>
92
-
93
- <!-- White top fade -->
94
- <linearGradient id="whiteTopGrad" x1="0%" y1="0%" x2="0%" y2="100%">
95
- <stop offset="0%" stop-color="#ffffff" stop-opacity="1" />
96
- <stop offset="100%" stop-color="#ffffff" stop-opacity="0.3" />
97
- </linearGradient>
98
-
99
- <!-- Strong grain -->
100
- <filter id="grain" x="-10%" y="-10%" width="120%" height="120%">
101
- <feTurbulence type="fractalNoise" baseFrequency="3.5" numOctaves="3" seed="8" result="noise" />
102
- <feComponentTransfer>
103
- <feFuncR type="linear" slope="3.5" intercept="-1.2" />
104
- <feFuncG type="linear" slope="3.5" intercept="-1.2" />
105
- <feFuncB type="linear" slope="3.5" intercept="-1.2" />
106
- <feFuncA type="table" tableValues="0 0.8" />
107
- </feComponentTransfer>
108
- </filter>
109
- </defs>
110
-
111
- <!-- White base -->
112
- <rect width="100%" height="100%" fill="#ffffff" />
113
-
114
- <!-- Gradients -->
115
- <rect width="100%" height="100%" fill="url(#grad1)" />
116
- <rect width="100%" height="100%" fill="url(#grad2)" style="mix-blend-mode:screen; opacity:0.85" />
117
-
118
- <!-- White fade on top -->
119
- <rect width="100%" height="100%" fill="url(#whiteTopGrad)" />
120
-
121
- <!-- Strong visible noise -->
122
- <rect width="100%" height="100%" filter="url(#grain)"
123
- style="mix-blend-mode:soft-light; opacity:1.2" />
124
- </svg>
125
- `);
126
-
127
- const backgroundImage = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
53
+ const brandColor = Color.fromSafe(agentProfile.meta.color || PROMPTBOOK_COLOR);
54
+ const brandColorHex = brandColor.then(saturate(-0.5)).toHex();
128
55
 
129
56
  const fullname = (agentProfile.meta.fullname || agentProfile.agentName || 'Agent') as string;
130
- const imageUrl = (agentProfile.meta.image as string) || null;
131
57
 
132
58
  return (
133
59
  <>
134
60
  <ServiceWorkerRegister scope={`/agents/${encodeURIComponent(agentName)}/`} />
135
- <AgentProfileView
136
- agentName={agentName}
137
- fullname={fullname}
138
- personaDescription={agentProfile.personaDescription || ''}
139
- imageUrl={imageUrl}
61
+ <AgentProfileWrapper
62
+ agent={agentProfile}
140
63
  agentUrl={agentUrl}
141
64
  agentEmail={agentEmail}
65
+ agentName={agentName}
142
66
  brandColorHex={brandColorHex}
143
- brandColorLightHex={brandColorLightHex}
144
- brandColorDarkHex={brandColorDarkHex}
145
- brandColorsHex={brandColorsHex}
146
- backgroundImage={backgroundImage}
147
- meta={agentProfile.meta}
148
67
  isAdmin={isAdmin}
149
- />
68
+ isHeadless={isHeadless}
69
+ actions={
70
+ <>
71
+ {getAgentLinks(agentName)
72
+ .filter((link) => ['Edit Book', 'Integration', 'All Links'].includes(link.title))
73
+ .map((link) => (
74
+ <a
75
+ key={link.href}
76
+ href={link.href}
77
+ className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors group"
78
+ title={link.title}
79
+ >
80
+ <div className="p-2 rounded-full bg-white/40 group-hover:bg-white/60 transition-colors shadow-sm">
81
+ <link.icon className="w-5 h-5" />
82
+ </div>
83
+ <span className="font-medium text-sm">{link.title}</span>
84
+ </a>
85
+ ))}
86
+ </>
87
+ }
88
+ >
89
+ <AgentProfileChat
90
+ agentUrl={agentUrl}
91
+ agentName={agentName}
92
+ fullname={fullname}
93
+ brandColorHex={brandColorHex}
94
+ avatarSrc={agentProfile.meta.image!}
95
+ />
96
+ </AgentProfileWrapper>
150
97
  </>
151
98
  );
152
99
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
4
  import { $provideServer } from '@/src/tools/$provideServer';
5
- import { PromptbookAgent } from '@promptbook-local/components';
5
+ import { PromptbookAgentIntegration } from '@promptbook-local/components';
6
6
  import { parseAgentSource } from '@promptbook-local/core';
7
7
  import { headers } from 'next/headers';
8
8
  import spaceTrim from 'spacetrim';
@@ -50,7 +50,16 @@ export default async function WebsiteIntegrationAgentPage({ params }: { params:
50
50
  </p>
51
51
 
52
52
  <CodePreview code={code} />
53
- <PromptbookAgent agentUrl={agentUrl} meta={meta} />
53
+ <PromptbookAgentIntegration
54
+ formfactor="profile"
55
+ agentUrl={agentUrl}
56
+ meta={meta}
57
+ style={{
58
+ width: '400px',
59
+ height: '600px',
60
+ // outline: `2px solid red`
61
+ }}
62
+ />
54
63
  </main>
55
64
  );
56
65
  }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { PromptbookAgent } from '@promptbook-local/components';
3
+ import { PromptbookAgentIntegration } from '@promptbook-local/components';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
 
6
6
  export default function EmbedPage() {
@@ -13,7 +13,7 @@ export default function EmbedPage() {
13
13
 
14
14
  return (
15
15
  <div className="w-full h-full bg-transparent">
16
- <PromptbookAgent
16
+ <PromptbookAgentIntegration
17
17
  agentUrl={agentUrl}
18
18
  onOpenChange={(isOpen) => {
19
19
  window.parent.postMessage({ type: 'PROMPTBOOK_AGENT_RESIZE', isOpen }, '*');
@@ -5,8 +5,8 @@ import { Barlow_Condensed, Poppins } from 'next/font/google';
5
5
  import { getMetadata } from '../database/getMetadata';
6
6
  import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
7
7
  import { $provideServer } from '../tools/$provideServer';
8
- import { isUserAdmin } from '../utils/isUserAdmin';
9
8
  import { getCurrentUser } from '../utils/getCurrentUser';
9
+ import { isUserAdmin } from '../utils/isUserAdmin';
10
10
  import './globals.css';
11
11
 
12
12
  const barlowCondensed = Barlow_Condensed({
@@ -25,12 +25,18 @@ export async function generateMetadata(): Promise<Metadata> {
25
25
  const { publicUrl } = await $provideServer();
26
26
  const serverName = (await getMetadata('SERVER_NAME')) || 'Promptbook Agents Server';
27
27
  const serverDescription = (await getMetadata('SERVER_DESCRIPTION')) || 'Agents server powered by Promptbook';
28
+ const serverFaviconUrl = (await getMetadata('SERVER_FAVICON_URL')) || faviconLogoImage.src;
28
29
 
29
30
  return {
30
31
  title: serverName,
31
32
  description: serverDescription,
32
33
  // TODO: keywords: ['@@@'],
33
34
  authors: [{ name: 'Promptbook Team' }],
35
+ icons: {
36
+ icon: serverFaviconUrl,
37
+ shortcut: serverFaviconUrl,
38
+ apple: serverFaviconUrl,
39
+ },
34
40
  openGraph: {
35
41
  title: serverName,
36
42
  description: serverDescription,
@@ -66,7 +72,6 @@ export default async function RootLayout({
66
72
  const currentUser = await getCurrentUser();
67
73
  const serverName = (await getMetadata('SERVER_NAME')) || 'Promptbook Agents Server';
68
74
  const serverLogoUrl = (await getMetadata('SERVER_LOGO_URL')) || null;
69
- const serverFaviconUrl = (await getMetadata('SERVER_FAVICON_URL')) || faviconLogoImage.src;
70
75
  const isFooterShown = ((await getMetadata('IS_FOOTER_SHOWN')) || 'true') === 'true';
71
76
 
72
77
  let footerLinks = [];
@@ -82,28 +87,7 @@ export default async function RootLayout({
82
87
 
83
88
  return (
84
89
  <html lang="en">
85
- <head>
86
- {/* Favicon for light mode */}
87
- {/*
88
- <link
89
- rel="icon"
90
- href="https://www.ptbk.io/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo-blue-transparent-256.493b7e49.png&w=64&q=75"
91
- media="(prefers-color-scheme: light)"
92
- type="image/svg+xml"
93
- />
94
- */}
95
- {/* Favicon for dark mode */}
96
- {/*
97
- <link
98
- rel="icon"
99
- href="https://www.ptbk.io/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo-blue-transparent-256.493b7e49.png&w=64&q=75"
100
- media="(prefers-color-scheme: dark)"
101
- type="image/svg+xml"
102
- />
103
- */}
104
- {/* Default favicon as a fallback */}
105
- <link rel="icon" href={serverFaviconUrl} type="image/x-icon" />
106
- </head>
90
+ {/* Note: Icon is set via metadata to allow agent-page specific icons to override it */}
107
91
  <body className={`${barlowCondensed.variable} ${poppins.variable} antialiased bg-white text-gray-900`}>
108
92
  <LayoutWrapper
109
93
  isAdmin={isAdmin}
@@ -6,6 +6,9 @@ import { getMetadata } from '../database/getMetadata';
6
6
  import { $provideServer } from '../tools/$provideServer';
7
7
  import { getAgentProfile } from './agents/[agentName]/_utils';
8
8
 
9
+ /**
10
+ * Manifest for PWA Progressive Web App
11
+ */
9
12
  export default async function manifest(): Promise<MetadataRoute.Manifest> {
10
13
  const { publicUrl } = await $provideServer();
11
14
  const serverName = (await getMetadata('SERVER_NAME')) || 'Promptbook Agents Server';
@@ -19,7 +22,7 @@ export default async function manifest(): Promise<MetadataRoute.Manifest> {
19
22
  name: serverName,
20
23
  short_name: serverName,
21
24
  description: serverDescription,
22
- start_url: publicUrl.href,
25
+ start_url: publicUrl.href + '?utm_source=pwa&utm_medium=install&utm_campaign=agents_server_app',
23
26
  display_override: ['fullscreen', 'minimal-ui'],
24
27
  display: 'standalone',
25
28
  background_color: PROMPTBOOK_COLOR.toHex(),
@@ -84,7 +87,7 @@ export default async function manifest(): Promise<MetadataRoute.Manifest> {
84
87
  name,
85
88
  short_name,
86
89
  description,
87
- start_url: `${agentUrl}/chat`,
90
+ start_url: `${agentUrl}?headless&utm_source=pwa&utm_medium=install&utm_campaign=agent_app`,
88
91
  scope: agentUrl,
89
92
  display_override: ['fullscreen', 'minimal-ui'],
90
93
  display: 'standalone',
@@ -98,7 +101,9 @@ export default async function manifest(): Promise<MetadataRoute.Manifest> {
98
101
  return {
99
102
  name: agentName,
100
103
  short_name: agentName,
101
- start_url: `${publicUrl.href}agents/${encodeURIComponent(agentName)}/chat`,
104
+ start_url: `${publicUrl.href}agents/${encodeURIComponent(
105
+ agentName,
106
+ )}?headless&utm_source=pwa&utm_medium=install&utm_campaign=agent_app`,
102
107
  display_override: ['fullscreen', 'minimal-ui'],
103
108
  display: 'standalone',
104
109
  background_color: '#ffffff',