@promptbook/cli 0.103.0-55 → 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 (91) hide show
  1. package/apps/agents-server/TODO.txt +5 -1
  2. package/apps/agents-server/package-lock.json +2336 -0
  3. package/apps/agents-server/package.json +9 -0
  4. package/apps/agents-server/src/app/actions.ts +3 -1
  5. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +3 -1
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +282 -0
  7. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +91 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +44 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +4 -4
  10. package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +2 -2
  11. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +80 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +3 -1
  13. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +11 -1
  14. package/apps/agents-server/src/app/agents/[agentName]/api/openai/models/route.ts +93 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/chat/completions/route.ts +10 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/models/route.ts +93 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +4 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +9 -2
  19. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +7 -3
  20. package/apps/agents-server/src/app/agents/[agentName]/integration/SdkCodeTabs.tsx +31 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +271 -30
  22. package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +41 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +61 -97
  24. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +47 -157
  25. package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +70 -0
  26. package/apps/agents-server/src/app/api/openai/v1/chat/completions/route.ts +6 -0
  27. package/apps/agents-server/src/app/api/openai/v1/models/route.ts +65 -0
  28. package/apps/agents-server/src/app/docs/[docId]/page.tsx +12 -32
  29. package/apps/agents-server/src/app/docs/page.tsx +42 -17
  30. package/apps/agents-server/src/app/embed/page.tsx +2 -2
  31. package/apps/agents-server/src/app/globals.css +129 -0
  32. package/apps/agents-server/src/app/layout.tsx +16 -26
  33. package/apps/agents-server/src/app/manifest.ts +9 -4
  34. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +334 -0
  35. package/apps/agents-server/src/components/AgentProfile/AgentProfileFromSource.tsx +23 -0
  36. package/apps/agents-server/src/{app/agents/[agentName] → components/AgentProfile}/AgentQrCode.tsx +8 -1
  37. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +90 -0
  38. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +87 -0
  39. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +7 -6
  40. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +20 -0
  41. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +18 -0
  42. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +18 -0
  43. package/apps/agents-server/src/database/metadataDefaults.ts +6 -0
  44. package/apps/agents-server/src/database/migrations/2025-12-0070-chat-history-source.sql +2 -0
  45. package/apps/agents-server/src/database/schema.ts +6 -0
  46. package/apps/agents-server/src/utils/handleChatCompletion.ts +186 -14
  47. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +13 -6
  48. package/apps/agents-server/src/utils/validateApiKey.ts +128 -0
  49. package/apps/agents-server/tailwind.config.ts +1 -1
  50. package/esm/index.es.js +953 -474
  51. package/esm/index.es.js.map +1 -1
  52. package/esm/typings/src/_packages/components.index.d.ts +2 -2
  53. package/esm/typings/src/_packages/core.index.d.ts +6 -8
  54. package/esm/typings/src/_packages/types.index.d.ts +7 -1
  55. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -1
  56. package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
  57. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +3 -0
  58. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  59. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +5 -0
  60. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentIntegration.d.ts +52 -0
  61. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentSeamlessIntegration.d.ts +14 -0
  62. package/esm/typings/src/book-components/icons/SendIcon.d.ts +3 -0
  63. package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +4 -0
  64. package/esm/typings/src/commitments/CLOSED/CLOSED.test.d.ts +4 -0
  65. package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +6 -0
  66. package/esm/typings/src/commitments/META_FONT/META_FONT.d.ts +42 -0
  67. package/esm/typings/src/commitments/USE/USE.d.ts +53 -0
  68. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +42 -0
  69. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.test.d.ts +1 -0
  70. package/esm/typings/src/commitments/{IMPORTANT/IMPORTANT.d.ts → USE_MCP/USE_MCP.d.ts} +16 -5
  71. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +38 -0
  72. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +6 -0
  73. package/esm/typings/src/commitments/index.d.ts +93 -1
  74. package/esm/typings/src/llm-providers/agent/Agent.d.ts +3 -1
  75. package/esm/typings/src/other/templates/getTemplatesPipelineCollection.d.ts +1 -1
  76. package/esm/typings/src/playground/playground.d.ts +3 -0
  77. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  78. package/esm/typings/src/utils/color/Color.d.ts +9 -1
  79. package/esm/typings/src/utils/color/css-colors.d.ts +1 -0
  80. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +6 -0
  81. package/esm/typings/src/utils/random/CzechNamePool.d.ts +7 -0
  82. package/esm/typings/src/utils/random/EnglishNamePool.d.ts +7 -0
  83. package/esm/typings/src/utils/random/NamePool.d.ts +17 -0
  84. package/esm/typings/src/utils/random/getNamePool.d.ts +10 -0
  85. package/esm/typings/src/version.d.ts +1 -1
  86. package/package.json +2 -2
  87. package/umd/index.umd.js +902 -423
  88. package/umd/index.umd.js.map +1 -1
  89. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +0 -29
  90. package/esm/typings/src/commitments/registry.d.ts +0 -68
  91. package/esm/typings/src/playground/playground1.d.ts +0 -2
@@ -8,5 +8,14 @@
8
8
  "start": "next start -p 4440",
9
9
  "lint": "next lint",
10
10
  "postinstall": "cd ../../ && npm ci"
11
+ },
12
+ "devDependencies": {
13
+ "@tailwindcss/typography": "^0.5.19",
14
+ "autoprefixer": "^10.4.21",
15
+ "postcss": "^8.5.6",
16
+ "tailwindcss": "^3.4.18"
17
+ },
18
+ "dependencies": {
19
+ "remark-gfm": "^4.0.1"
11
20
  }
12
21
  }
@@ -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
 
@@ -9,12 +9,13 @@ import { string_agent_url } from '../../../../../../src/types/typeAliases';
9
9
  type AgentChatWrapperProps = {
10
10
  agentUrl: string_agent_url;
11
11
  defaultMessage?: string;
12
+ autoExecuteMessage?: string;
12
13
  };
13
14
 
14
15
  // TODO: [🐱‍🚀] Rename to AgentChatSomethingWrapper
15
16
 
16
17
  export function AgentChatWrapper(props: AgentChatWrapperProps) {
17
- const { agentUrl, defaultMessage } = props;
18
+ const { agentUrl, defaultMessage, autoExecuteMessage } = props;
18
19
 
19
20
  const agentPromise = useMemo(
20
21
  () =>
@@ -67,6 +68,7 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
67
68
  agent={agent}
68
69
  onFeedback={handleFeedback}
69
70
  defaultMessage={defaultMessage}
71
+ autoExecuteMessage={autoExecuteMessage}
70
72
  />
71
73
  );
72
74
  }
@@ -0,0 +1,282 @@
1
+ 'use client';
2
+
3
+ import { TODO_any } from '@promptbook-local/types';
4
+ import {
5
+ CopyIcon,
6
+ CopyPlusIcon,
7
+ DownloadIcon,
8
+ MailIcon,
9
+ MessageCircleQuestionIcon,
10
+ MessageSquareIcon,
11
+ MessageSquareShareIcon,
12
+ MoreHorizontalIcon,
13
+ QrCodeIcon,
14
+ SmartphoneIcon,
15
+ SquareSplitHorizontalIcon,
16
+ } from 'lucide-react';
17
+ import { Barlow_Condensed } from 'next/font/google';
18
+ import { useCallback, useEffect, useRef, useState } from 'react';
19
+ import { string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
20
+ import { getAgentLinks } from './agentLinks';
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
+
33
+ type AgentOptionsMenuProps = {
34
+ agentName: string;
35
+ agentUrl: string;
36
+ agentEmail: string;
37
+ brandColorHex: string;
38
+ isAdmin?: boolean;
39
+ backgroundImage: string_url_image & string_data_url;
40
+ onShowQrCode?: () => void;
41
+ };
42
+
43
+ export function AgentOptionsMenu({
44
+ agentName,
45
+ agentUrl,
46
+ agentEmail,
47
+ brandColorHex,
48
+ isAdmin = false,
49
+ backgroundImage,
50
+ onShowQrCode,
51
+ }: AgentOptionsMenuProps) {
52
+ const [isOpen, setIsOpen] = useState(false);
53
+ const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
54
+ const menuRef = useRef<HTMLDivElement>(null);
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
+
95
+ useEffect(() => {
96
+ function handleClickOutside(event: MouseEvent) {
97
+ if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
98
+ setIsOpen(false);
99
+ }
100
+ }
101
+
102
+ document.addEventListener('mousedown', handleClickOutside);
103
+ return () => document.removeEventListener('mousedown', handleClickOutside);
104
+ }, []);
105
+
106
+ const handleCopy = async (value: string, label: string) => {
107
+ try {
108
+ await navigator.clipboard.writeText(value);
109
+ setCopyFeedback(label);
110
+ setTimeout(() => setCopyFeedback(null), 2000);
111
+ } catch (error) {
112
+ console.error('Failed to copy:', error);
113
+ }
114
+ };
115
+
116
+ const links = getAgentLinks(agentName);
117
+ const editBookLink = links.find((l) => l.title === 'Edit Book')!;
118
+ const integrationLink = links.find((l) => l.title === 'Integration')!;
119
+ const historyLink = links.find((l) => l.title === 'History & Feedback')!;
120
+ const allLinksLink = links.find((l) => l.title === 'All Links')!;
121
+
122
+ const menuItems = [
123
+ {
124
+ type: 'link' as const,
125
+ href: `/agents/${encodeURIComponent(agentName)}/chat`,
126
+ icon: MessageSquareShareIcon,
127
+ label: 'Standalone Chat',
128
+ },
129
+ {
130
+ type: 'link' as const,
131
+ href: `/agents/${encodeURIComponent(agentName)}/book+chat`,
132
+ icon: SquareSplitHorizontalIcon,
133
+ label: 'Edit Book & Chat',
134
+ },
135
+ {
136
+ type: 'link' as const,
137
+ href: editBookLink.href,
138
+ icon: editBookLink.icon,
139
+ label: editBookLink.title,
140
+ },
141
+ { type: 'divider' as const },
142
+ {
143
+ type: 'link' as const,
144
+ href: integrationLink.href,
145
+ icon: integrationLink.icon,
146
+ label: integrationLink.title,
147
+ },
148
+ {
149
+ type: 'link' as const,
150
+ href: historyLink.href,
151
+ icon: historyLink.icon,
152
+ label: historyLink.title, // 'History & Feedback'
153
+ },
154
+ {
155
+ type: 'link' as const,
156
+ href: allLinksLink.href,
157
+ icon: allLinksLink.icon,
158
+ label: allLinksLink.title,
159
+ },
160
+ { type: 'divider' as const },
161
+ {
162
+ type: 'action' as const,
163
+ icon: CopyIcon,
164
+ label: copyFeedback === 'URL' ? 'Copied!' : 'Copy Agent URL',
165
+ onClick: () => handleCopy(agentUrl, 'URL'),
166
+ },
167
+ {
168
+ type: 'action' as const,
169
+ icon: MailIcon,
170
+ label: copyFeedback === 'Email' ? 'Copied!' : 'Copy Agent Email',
171
+ onClick: () => handleCopy(agentEmail, 'Email'),
172
+ },
173
+ {
174
+ type: 'action' as const,
175
+ icon: QrCodeIcon,
176
+ label: 'Show QR Code',
177
+ onClick: onShowQrCode,
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
+ : []),
190
+ // Admin-only items
191
+ ...(isAdmin
192
+ ? [
193
+ { type: 'divider' as const },
194
+ {
195
+ type: 'link' as const,
196
+ href: `/admin/chat-history?agentName=${encodeURIComponent(agentName)}`,
197
+ icon: MessageSquareIcon,
198
+ label: 'Chat History',
199
+ },
200
+ {
201
+ type: 'link' as const,
202
+ href: `/admin/chat-feedback?agentName=${encodeURIComponent(agentName)}`,
203
+ icon: MessageCircleQuestionIcon,
204
+ label: 'Chat Feedback',
205
+ },
206
+ {
207
+ type: 'link' as const,
208
+ href: `/agents/${encodeURIComponent(agentName)}/clone`,
209
+ icon: CopyPlusIcon,
210
+ label: 'Clone Agent',
211
+ },
212
+ {
213
+ type: 'link' as const,
214
+ href: `/agents/${encodeURIComponent(agentName)}/export`,
215
+ icon: DownloadIcon,
216
+ label: 'Export Agent',
217
+ },
218
+ // {
219
+ // type: 'link' as const,
220
+ // href: backgroundImage,
221
+ // icon: DownloadIcon,
222
+ // label: 'Download Background Image',
223
+ // },
224
+ ]
225
+ : []),
226
+ ];
227
+
228
+ return (
229
+ <div ref={menuRef} className="relative z-[9999]">
230
+ <button
231
+ onClick={() => setIsOpen(!isOpen)}
232
+ className="p-3 rounded-full hover:bg-white/30 transition-all duration-200"
233
+ // style={{ backgroundColor: brandColorHex }}
234
+ aria-label="More options"
235
+ >
236
+ <MoreHorizontalIcon className="w-5 h-5 text-black" />
237
+ </button>
238
+
239
+ {isOpen && (
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
+ >
243
+ {menuItems.map((item, index) => {
244
+ if (item.type === 'divider') {
245
+ return <div key={index} className="h-px bg-gray-100 my-2" />;
246
+ }
247
+
248
+ if (item.type === 'link') {
249
+ return (
250
+ <a
251
+ key={index}
252
+ href={item.href}
253
+ className="flex items-center gap-3 px-4 py-2.5 text-gray-700 hover:bg-gray-50 transition-colors"
254
+ onClick={() => setIsOpen(false)}
255
+ >
256
+ <item.icon className="w-4 h-4 text-gray-500" />
257
+ <span className="text-sm font-medium">{item.label}</span>
258
+ </a>
259
+ );
260
+ }
261
+
262
+ return (
263
+ <button
264
+ key={index}
265
+ onClick={() => {
266
+ item.onClick?.();
267
+ if (item.label !== 'Show QR Code') {
268
+ // Keep menu open for copy feedback
269
+ }
270
+ }}
271
+ className="flex items-center gap-3 px-4 py-2.5 text-gray-700 hover:bg-gray-50 transition-colors w-full text-left"
272
+ >
273
+ <item.icon className="w-4 h-4 text-gray-500" />
274
+ <span className="text-sm font-medium">{item.label}</span>
275
+ </button>
276
+ );
277
+ })}
278
+ </div>
279
+ )}
280
+ </div>
281
+ );
282
+ }
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import { usePromise } from '@common/hooks/usePromise';
4
+ import { Chat } from '@promptbook-local/components';
5
+ import { RemoteAgent } from '@promptbook-local/core';
6
+ import { useRouter } from 'next/navigation';
7
+ import { useCallback, useMemo } from 'react';
8
+ import spaceTrim from 'spacetrim';
9
+ import { string_agent_url, string_color } from '../../../../../../src/types/typeAliases';
10
+
11
+ type AgentProfileChatProps = {
12
+ agentUrl: string_agent_url;
13
+ agentName: string;
14
+ fullname: string;
15
+ brandColorHex: string_color;
16
+ avatarSrc: string;
17
+ };
18
+
19
+ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex, avatarSrc }: AgentProfileChatProps) {
20
+ const router = useRouter();
21
+
22
+ const agentPromise = useMemo(
23
+ () =>
24
+ RemoteAgent.connect({
25
+ agentUrl,
26
+ isVerbose: true,
27
+ }),
28
+ [agentUrl],
29
+ );
30
+
31
+ const { value: agent } = usePromise(agentPromise, [agentPromise]);
32
+
33
+ const handleMessage = useCallback(
34
+ async (message: string) => {
35
+ // Redirect to chat page with the message
36
+ router.push(`/agents/${encodeURIComponent(agentName)}/chat?message=${encodeURIComponent(message)}`);
37
+ },
38
+ [agentName, router],
39
+ );
40
+
41
+ const initialMessage = useMemo(() => {
42
+ if (!agent) {
43
+ return 'Loading...';
44
+ }
45
+ return (
46
+ agent.initialMessage ||
47
+ spaceTrim(`
48
+ Hello! I am ${fullname || agentName || 'an AI Agent'}.
49
+
50
+ [Hello](?message=Hello, can you tell me about yourself?)
51
+ `)
52
+ );
53
+ }, [agent, fullname, agentName]);
54
+
55
+ // If agent is not loaded yet, we can show a skeleton or just the default Chat structure
56
+ // But to match "same initial message", we need the agent loaded or at least the default fallback.
57
+ // The fallback above matches AgentChat.tsx default.
58
+
59
+ return (
60
+ <div className="w-full h-[400px] md:h-[500px]">
61
+ <Chat
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
+ ]}
73
+ messages={[
74
+ {
75
+ from: 'AGENT',
76
+ content: initialMessage,
77
+ date: new Date(),
78
+ id: 'initial-message',
79
+ isComplete: true,
80
+ },
81
+ ]}
82
+ onMessage={handleMessage}
83
+ isSaveButtonEnabled={false}
84
+ isCopyButtonEnabled={false}
85
+ className="bg-transparent"
86
+ buttonColor={brandColorHex}
87
+ style={{ background: 'transparent' }}
88
+ />
89
+ </div>
90
+ );
91
+ }
@@ -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,10 +12,10 @@ export function CloneAgentButton({ agentName }: CloneAgentButtonProps) {
12
12
 
13
13
  const handleClone = async () => {
14
14
  if (!window.confirm(`Clone agent "${agentName}"?`)) return;
15
-
15
+
16
16
  try {
17
17
  const response = await fetch(`/api/agents/${encodeURIComponent(agentName)}/clone`, { method: 'POST' });
18
-
18
+
19
19
  if (!response.ok) {
20
20
  throw new Error('Failed to clone agent');
21
21
  }
@@ -32,9 +32,9 @@ export function CloneAgentButton({ agentName }: CloneAgentButtonProps) {
32
32
  return (
33
33
  <button
34
34
  onClick={handleClone}
35
- className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
35
+ className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
36
36
  >
37
- <CopyIcon className="ml-2 w-4 h-4 mr-2" />
37
+ <CopyIcon className="mr-2 w-3 h-3" />
38
38
  Clone
39
39
  </button>
40
40
  );
@@ -58,7 +58,7 @@ export function InstallPwaButton() {
58
58
  <button
59
59
  type="button"
60
60
  onClick={onInstall}
61
- className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
61
+ className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
62
62
  style={{
63
63
  opacity: installPromptEvent ? 1 : 0.5,
64
64
  cursor: installPromptEvent ? 'pointer' : 'wait',
@@ -67,7 +67,7 @@ export function InstallPwaButton() {
67
67
  disabled={!installPromptEvent}
68
68
  >
69
69
  {/* Simple icon substitute: download arrow */}
70
- <ShoppingBagIcon className="ml-2 w-4 h-4 mr-2" />
70
+ <ShoppingBagIcon className="mr-2 w-3 h-3" />
71
71
  Install
72
72
  </button>
73
73
  );
@@ -0,0 +1,80 @@
1
+ import {
2
+ BookOpenIcon,
3
+ CodeIcon,
4
+ GlobeIcon,
5
+ HistoryIcon,
6
+ LinkIcon,
7
+ MessageSquareIcon,
8
+ NotebookPenIcon,
9
+ } from 'lucide-react';
10
+ import type { ComponentType } from 'react';
11
+
12
+ type AgentLink = {
13
+ title: string;
14
+ href: string;
15
+ icon: ComponentType<{ className?: string }>;
16
+ description?: string;
17
+ target?: '_blank' | '_self';
18
+ rel?: string;
19
+ };
20
+
21
+ export const getAgentLinks = (agentName: string): AgentLink[] => {
22
+ const encodedName = encodeURIComponent(agentName);
23
+ return [
24
+ {
25
+ title: 'Chat with Agent',
26
+ href: `/agents/${encodedName}`,
27
+ icon: MessageSquareIcon,
28
+ description: 'Direct interface to converse with the agent.',
29
+ },
30
+ {
31
+ title: 'Edit Book',
32
+ href: `/agents/${encodedName}/book`,
33
+ icon: NotebookPenIcon,
34
+ description: "Edit the agent's knowledge book.",
35
+ },
36
+ {
37
+ title: 'Integration',
38
+ href: `/agents/${encodedName}/integration`,
39
+ icon: CodeIcon,
40
+ description: 'Learn how to integrate this agent into your applications.',
41
+ },
42
+ {
43
+ title: 'History & Feedback',
44
+ href: `/agents/${encodedName}/history`,
45
+ icon: HistoryIcon,
46
+ description: 'View past conversations and provide feedback.',
47
+ },
48
+ {
49
+ title: 'All Links',
50
+ href: `/agents/${encodedName}/links`,
51
+ icon: LinkIcon,
52
+ description: 'Signpost & Links',
53
+ },
54
+ {
55
+ title: 'Website Integration',
56
+ href: `/agents/${encodedName}/website-integration`,
57
+ icon: GlobeIcon,
58
+ description: 'Embed the agent chat widget directly into your React application.',
59
+ },
60
+ ];
61
+ };
62
+
63
+ export const getAgentExternalLinks = (): AgentLink[] => [
64
+ {
65
+ title: 'Promptbook Studio',
66
+ href: 'https://promptbook.studio',
67
+ icon: BookOpenIcon,
68
+ description: 'Create and manage your own agents',
69
+ target: '_blank',
70
+ rel: 'noopener noreferrer',
71
+ },
72
+ {
73
+ title: 'GitHub Repository',
74
+ href: 'https://github.com/webgptorg/promptbook',
75
+ icon: CodeIcon,
76
+ description: 'Star us and contribute to the project',
77
+ target: '_blank',
78
+ rel: 'noopener noreferrer',
79
+ },
80
+ ];
@@ -1,4 +1,5 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { resolveInheritedAgentSource } from '@/src/utils/resolveInheritedAgentSource';
2
3
  import { padBook, validateBook } from '@promptbook-local/core';
3
4
  import { serializeError } from '@promptbook-local/utils';
4
5
  import spaceTrim from 'spacetrim';
@@ -13,8 +14,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
13
14
  try {
14
15
  const collection = await $provideAgentCollectionForServer();
15
16
  const agentSource = await collection.getAgentSource(agentName);
17
+ const effectiveAgentSource = await resolveInheritedAgentSource(agentSource, collection);
16
18
 
17
- return new Response(agentSource, {
19
+ return new Response(effectiveAgentSource, {
18
20
  status: 200,
19
21
  headers: { 'Content-Type': 'text/plain' /* <- TODO: [🎳] Mime type of book */ },
20
22
  });
@@ -77,11 +77,15 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
77
77
  userAgent,
78
78
  language,
79
79
  platform,
80
+ source: 'AGENT_PAGE_CHAT',
81
+ apiKey: null,
80
82
  });
81
83
 
82
84
  const encoder = new TextEncoder();
83
85
  const readableStream = new ReadableStream({
84
86
  start(controller) {
87
+ let previousContent = '';
88
+
85
89
  agent.callChatModelStream!(
86
90
  {
87
91
  title: `Chat with agent ${
@@ -95,7 +99,11 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
95
99
  thread,
96
100
  },
97
101
  (chunk) => {
98
- controller.enqueue(encoder.encode(chunk.content));
102
+ const fullContent = chunk.content;
103
+ const deltaContent = fullContent.substring(previousContent.length);
104
+ previousContent = fullContent;
105
+
106
+ controller.enqueue(encoder.encode(deltaContent));
99
107
  },
100
108
  )
101
109
  .then(async (response) => {
@@ -119,6 +127,8 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
119
127
  userAgent,
120
128
  language,
121
129
  platform,
130
+ source: 'AGENT_PAGE_CHAT',
131
+ apiKey: null,
122
132
  });
123
133
 
124
134
  // Note: [🐱‍🚀] Save the learned data