@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
@@ -0,0 +1,334 @@
1
+ 'use client';
2
+
3
+ import { AgentBasicInformation } from '@promptbook-local/types';
4
+ import { RepeatIcon } from 'lucide-react';
5
+ import { useMemo, useState } from 'react';
6
+ import spaceTrim from 'spacetrim';
7
+ import { Color } from '../../../../../src/utils/color/Color';
8
+ import { darken } from '../../../../../src/utils/color/operators/darken';
9
+ import { lighten } from '../../../../../src/utils/color/operators/lighten';
10
+ import { AgentQrCode } from './AgentQrCode';
11
+ import { QrCodeModal } from './QrCodeModal';
12
+
13
+ type AgentProfileProps = {
14
+ /**
15
+ * The agent to display
16
+ */
17
+ readonly agent: AgentBasicInformation;
18
+
19
+ /**
20
+ * URL of the agent page
21
+ *
22
+ * @default undefined - If not provided, some features like QR code for link might be disabled or use generic link
23
+ */
24
+ readonly agentUrl?: string;
25
+
26
+ /**
27
+ * Email of the agent
28
+ */
29
+ readonly agentEmail?: string;
30
+
31
+ /**
32
+ * Content for the menu (top right)
33
+ *
34
+ * @param props.onShowQrCode - Function to open QR code modal
35
+ */
36
+ readonly renderMenu?: (props: { onShowQrCode: () => void }) => React.ReactNode;
37
+
38
+ /**
39
+ * Content for the chat area
40
+ */
41
+ readonly children?: React.ReactNode;
42
+
43
+ /**
44
+ * Content for the secondary actions (links)
45
+ */
46
+ readonly actions?: React.ReactNode;
47
+
48
+ /**
49
+ * If true, hides the menu and actions for fullscreen/embedded view
50
+ */
51
+ readonly isHeadless?: boolean;
52
+
53
+ /**
54
+ * CSS class name
55
+ */
56
+ readonly className?: string;
57
+ };
58
+
59
+ export function AgentProfile(props: AgentProfileProps) {
60
+ const {
61
+ agent,
62
+ agentUrl = '',
63
+ agentEmail = '',
64
+ renderMenu,
65
+ children,
66
+ actions,
67
+ isHeadless = false,
68
+ className,
69
+ } = props;
70
+ const { meta, agentName } = agent;
71
+ const fullname = (meta.fullname as string) || agentName || 'Agent';
72
+ const personaDescription = agent.personaDescription || '';
73
+ const imageUrl = (meta.image as string) || null;
74
+
75
+ const [isQrModalOpen, setIsQrModalOpen] = useState(false);
76
+ const [isFlipped, setIsFlipped] = useState(false);
77
+
78
+ // Dynamic Font Loading
79
+ const fontString = meta.font;
80
+ let fontStyle: React.CSSProperties = {};
81
+
82
+ if (fontString) {
83
+ // [🧠] TODO: Properly parse font string to get family name
84
+ const primaryFont = fontString.split(',')[0].trim().replace(/['"]/g, '');
85
+ fontStyle = {
86
+ fontFamily: fontString,
87
+ };
88
+ }
89
+
90
+ // Compute Colors and Background
91
+ const { brandColorHex, brandColorLightHex, brandColorDarkHex, backgroundImage } = useMemo(() => {
92
+ // [🧠] Default color should be imported constant, but for now hardcoded fallback
93
+ const PROMPTBOOK_COLOR_HEX = '#f15b24'; // TODO: Import PROMPTBOOK_COLOR
94
+ const brandColorString = meta.color || PROMPTBOOK_COLOR_HEX;
95
+
96
+ let brandColor;
97
+ try {
98
+ brandColor = Color.fromSafe(brandColorString.split(',')[0].trim());
99
+ } catch {
100
+ brandColor = Color.fromHex(PROMPTBOOK_COLOR_HEX);
101
+ }
102
+
103
+ const brandColorHex = brandColor.toHex();
104
+ const brandColorLightHex = brandColor.then(lighten(0.2)).toHex();
105
+ const brandColorDarkHex = brandColor.then(darken(0.15)).toHex();
106
+
107
+ // Generate Noisy SVG Background
108
+ const color1 = brandColor;
109
+ // const color2 = brandColors[1] || brandColors[0]!; // Use secondary color if available?
110
+ // For simplicity using primary color for now or derive second one
111
+ const color2 = brandColor;
112
+
113
+ // [🧠] Make colors much lighter for the background
114
+ const color1Light = color1.then(lighten(0.3)).toHex();
115
+ const color1Main = color1.toHex();
116
+ const color1Dark = color1.then(darken(0.3)).toHex();
117
+
118
+ const color2Light = color2.then(lighten(0.3)).toHex();
119
+ const color2Main = color2.toHex();
120
+ const color2Dark = color2.then(darken(0.3)).toHex();
121
+
122
+ const svgContent = spaceTrim(`
123
+ <svg xmlns="http://www.w3.org/2000/svg"
124
+ viewBox="0 0 1920 1080"
125
+ width="1920" height="1080"
126
+ preserveAspectRatio="xMidYMid slice">
127
+ <defs>
128
+ <!-- Bottom-left -->
129
+ <radialGradient id="grad1" cx="0%" cy="100%" r="90%">
130
+ <stop offset="0%" stop-color="${color1Light}" />
131
+ <stop offset="50%" stop-color="${color1Main}" />
132
+ <stop offset="100%" stop-color="${color1Dark}" />
133
+ </radialGradient>
134
+
135
+ <!-- Bottom-right -->
136
+ <radialGradient id="grad2" cx="100%" cy="100%" r="90%">
137
+ <stop offset="0%" stop-color="${color2Light}" />
138
+ <stop offset="50%" stop-color="${color2Main}" />
139
+ <stop offset="100%" stop-color="${color2Dark}" />
140
+ </radialGradient>
141
+
142
+ <!-- White top fade -->
143
+ <linearGradient id="whiteTopGrad" x1="0%" y1="0%" x2="0%" y2="100%">
144
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="1" />
145
+ <stop offset="100%" stop-color="#ffffff" stop-opacity="0.3" />
146
+ </linearGradient>
147
+
148
+ <!-- Strong grain -->
149
+ <filter id="grain" x="-10%" y="-10%" width="120%" height="120%">
150
+ <feTurbulence type="fractalNoise" baseFrequency="3.5" numOctaves="3" seed="8" result="noise" />
151
+ <feComponentTransfer>
152
+ <feFuncR type="linear" slope="3.5" intercept="-1.2" />
153
+ <feFuncG type="linear" slope="3.5" intercept="-1.2" />
154
+ <feFuncB type="linear" slope="3.5" intercept="-1.2" />
155
+ <feFuncA type="table" tableValues="0 0.8" />
156
+ </feComponentTransfer>
157
+ </filter>
158
+ </defs>
159
+
160
+ <!-- White base -->
161
+ <rect width="100%" height="100%" fill="#ffffff" />
162
+
163
+ <!-- Gradients -->
164
+ <rect width="100%" height="100%" fill="url(#grad1)" />
165
+ <rect width="100%" height="100%" fill="url(#grad2)" style="mix-blend-mode:screen; opacity:0.85" />
166
+
167
+ <!-- White fade on top -->
168
+ <rect width="100%" height="100%" fill="url(#whiteTopGrad)" />
169
+
170
+ <!-- Strong visible noise -->
171
+ <rect width="100%" height="100%" filter="url(#grain)"
172
+ style="mix-blend-mode:soft-light; opacity:1.2" />
173
+ </svg>
174
+ `);
175
+
176
+ const backgroundImage = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
177
+
178
+ return { brandColorHex, brandColorLightHex, brandColorDarkHex, backgroundImage };
179
+ }, [meta.color]);
180
+
181
+ return (
182
+ <>
183
+ {fontString && (
184
+ <style jsx global>{`
185
+ @import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(
186
+ fontString.split(',')[0].trim().replace(/['"]/g, ''),
187
+ )}:wght@400;600;700&display=swap');
188
+ `}</style>
189
+ )}
190
+
191
+ {/* Full-screen background with agent color */}
192
+ <div
193
+ className={`w-full flex flex-col items-center justify-center p-6 md:p-12 relative overflow-hidden ${
194
+ isHeadless ? 'min-h-screen' : 'min-h-[calc(100vh-60px)]'
195
+ } ${className || ''}`}
196
+ style={{
197
+ background: `url("${backgroundImage}")`,
198
+ backgroundSize: 'cover',
199
+ backgroundPosition: 'center',
200
+ ...fontStyle,
201
+ }}
202
+ >
203
+ {/* Options menu in top right */}
204
+ {!isHeadless && renderMenu && (
205
+ <div className="absolute top-4 right-4 z-[9999]">
206
+ {renderMenu({ onShowQrCode: () => setIsQrModalOpen(true) })}
207
+ </div>
208
+ )}
209
+
210
+ {/* Main profile content */}
211
+ <div className="relative z-10 flex flex-col md:flex-row items-center md:items-start gap-8 md:gap-12 max-w-5xl w-full">
212
+ {/* Agent image card (Flippable) */}
213
+ <div className="flex-shrink-0 perspective-1000 group" style={{ perspective: '1000px' }}>
214
+ <div
215
+ className="relative w-72 md:w-80 transition-all duration-700 transform-style-3d cursor-pointer"
216
+ style={{
217
+ aspectRatio: '1 / 1.618', // Golden Ratio
218
+ transformStyle: 'preserve-3d',
219
+ transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
220
+ }}
221
+ onClick={() => setIsFlipped(!isFlipped)}
222
+ >
223
+ {/* Front of Card (Image) */}
224
+ <div
225
+ className="absolute inset-0 w-full h-full backface-hidden rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm"
226
+ style={{
227
+ backfaceVisibility: 'hidden',
228
+ backgroundColor: brandColorDarkHex,
229
+ boxShadow: `0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px ${brandColorLightHex}40`,
230
+ }}
231
+ >
232
+ {imageUrl ? (
233
+ // eslint-disable-next-line @next/next/no-img-element
234
+ <img src={imageUrl} alt={fullname} className="w-full h-full object-cover" />
235
+ ) : (
236
+ <div
237
+ className="w-full h-full flex items-center justify-center text-8xl font-bold text-white/80"
238
+ style={{ backgroundColor: brandColorDarkHex }}
239
+ >
240
+ {fullname.charAt(0).toUpperCase()}
241
+ </div>
242
+ )}
243
+
244
+ {/* Flip hint icon */}
245
+ <div className="absolute bottom-4 right-4 bg-black/30 p-2 rounded-full text-white/80 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity">
246
+ <RepeatIcon className="w-5 h-5" />
247
+ </div>
248
+ </div>
249
+
250
+ {/* Back of Card (QR Code) */}
251
+ <div
252
+ className="absolute inset-0 w-full h-full backface-hidden rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm flex flex-col items-center justify-center p-6"
253
+ style={{
254
+ backfaceVisibility: 'hidden',
255
+ transform: 'rotateY(180deg)',
256
+ background: `linear-gradient(135deg, ${brandColorLightHex} 0%, #ffffff 100%)`,
257
+ boxShadow: `0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px ${brandColorLightHex}40`,
258
+ }}
259
+ >
260
+ <div className="transform scale-90 md:scale-100">
261
+ <AgentQrCode
262
+ agentName={agentName}
263
+ agentUrl={agentUrl}
264
+ agentEmail={agentEmail}
265
+ personaDescription={personaDescription}
266
+ meta={meta}
267
+ isJustVcardShown
268
+ />
269
+ </div>
270
+
271
+ {/* Flip hint icon */}
272
+ <div className="absolute bottom-4 right-4 bg-black/10 p-2 rounded-full text-black/50 backdrop-blur-md">
273
+ <RepeatIcon className="w-5 h-5" />
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ {/* Agent info */}
280
+ <div className="flex flex-col items-center md:items-start text-center md:text-left gap-6">
281
+ {/* Agent name with custom font */}
282
+ <h1
283
+ className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 tracking-tight"
284
+ style={{
285
+ textShadow: '0 2px 20px rgba(255, 255, 255, 0.5)',
286
+ }}
287
+ >
288
+ {fullname}
289
+ </h1>
290
+
291
+ {/* Short description */}
292
+ <p className="text-lg md:text-xl text-gray-700 max-w-lg leading-relaxed font-medium">
293
+ {personaDescription}
294
+ </p>
295
+
296
+ {/* Chat */}
297
+ <div className="w-full">{children}</div>
298
+
299
+ {/* Secondary Actions */}
300
+ {!isHeadless && (
301
+ <div className="flex flex-wrap justify-center md:justify-start items-center gap-4 md:gap-6 mt-2">
302
+ {actions}
303
+ </div>
304
+ )}
305
+ </div>
306
+ </div>
307
+
308
+ {/* Subtle gradient overlay at bottom */}
309
+ <div
310
+ className="absolute bottom-0 left-0 right-0 h-32 pointer-events-none"
311
+ style={{
312
+ background: `linear-gradient(to top, ${brandColorDarkHex}40, transparent)`,
313
+ }}
314
+ />
315
+ </div>
316
+
317
+ {/* QR Code Modal */}
318
+ <QrCodeModal
319
+ isOpen={isQrModalOpen}
320
+ onClose={() => setIsQrModalOpen(false)}
321
+ agentName={agentName}
322
+ meta={meta}
323
+ personaDescription={personaDescription}
324
+ agentUrl={agentUrl}
325
+ agentEmail={agentEmail}
326
+ brandColorHex={brandColorHex}
327
+ />
328
+ </>
329
+ );
330
+ }
331
+
332
+ /**
333
+ * TODO: !!!! Use 3D badge @see https://vercel.com/blog/building-an-interactive-3d-event-badge-with-react-three-fiber
334
+ */
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { string_book } from '@promptbook-local/types';
4
+ import { useMemo } from 'react';
5
+ import { parseAgentSource } from '../../../../../src/book-2.0/agent-source/parseAgentSource';
6
+ import { AgentProfile } from './AgentProfile';
7
+
8
+ type AgentProfileFromSourceProps = Omit<React.ComponentProps<typeof AgentProfile>, 'agent'> & {
9
+ /**
10
+ * Source code of the agent (book)
11
+ */
12
+ readonly source: string_book;
13
+ };
14
+
15
+ export function AgentProfileFromSource(props: AgentProfileFromSourceProps) {
16
+ const { source, ...rest } = props;
17
+
18
+ const agent = useMemo(() => {
19
+ return parseAgentSource(source);
20
+ }, [source]);
21
+
22
+ return <AgentProfile agent={agent} {...rest} />;
23
+ }
@@ -8,9 +8,12 @@ import spaceTrim from 'spacetrim';
8
8
  type AgentQrCodeProps = Pick<AgentBasicInformation, 'agentName' | 'personaDescription' | 'meta'> & {
9
9
  agentUrl: string;
10
10
  agentEmail: string;
11
+
12
+ isJustVcardShown?: boolean;
11
13
  };
12
14
 
13
- export function AgentQrCode({ agentName, agentUrl, agentEmail, personaDescription, meta }: AgentQrCodeProps) {
15
+ export function AgentQrCode(props: AgentQrCodeProps) {
16
+ const { agentName, agentUrl, agentEmail, personaDescription, meta, isJustVcardShown } = props;
14
17
  const [mode, setMode] = useState<'contact' | 'link'>('contact');
15
18
 
16
19
  // TODO: [🧠] Should we include more info in VCARD?
@@ -27,6 +30,10 @@ export function AgentQrCode({ agentName, agentUrl, agentEmail, personaDescriptio
27
30
  const qrValue = mode === 'contact' ? vcard : agentUrl;
28
31
  const label = mode === 'contact' ? 'Scan to add contact' : 'Scan to open agent';
29
32
 
33
+ if (isJustVcardShown) {
34
+ return <PromptbookQrCode value={vcard} className="" size={250} />;
35
+ }
36
+
30
37
  return (
31
38
  <div className="flex flex-col items-center">
32
39
  <div className="flex bg-gray-100 p-1 rounded-lg mb-4">
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import { AgentBasicInformation } from '@promptbook-local/types';
4
+ import { XIcon } from 'lucide-react';
5
+ import { useEffect } from 'react';
6
+ import { AgentQrCode } from './AgentQrCode';
7
+
8
+ type QrCodeModalProps = {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ agentName: string;
12
+ meta: AgentBasicInformation['meta'];
13
+ personaDescription: string;
14
+ agentUrl: string;
15
+ agentEmail: string;
16
+ brandColorHex: string;
17
+ };
18
+
19
+ export function QrCodeModal({
20
+ isOpen,
21
+ onClose,
22
+ agentName,
23
+ meta,
24
+ personaDescription,
25
+ agentUrl,
26
+ agentEmail,
27
+ brandColorHex,
28
+ }: QrCodeModalProps) {
29
+ useEffect(() => {
30
+ if (isOpen) {
31
+ document.body.style.overflow = 'hidden';
32
+ } else {
33
+ document.body.style.overflow = '';
34
+ }
35
+ return () => {
36
+ document.body.style.overflow = '';
37
+ };
38
+ }, [isOpen]);
39
+
40
+ useEffect(() => {
41
+ function handleEscape(event: KeyboardEvent) {
42
+ if (event.key === 'Escape') {
43
+ onClose();
44
+ }
45
+ }
46
+
47
+ if (isOpen) {
48
+ document.addEventListener('keydown', handleEscape);
49
+ }
50
+ return () => document.removeEventListener('keydown', handleEscape);
51
+ }, [isOpen, onClose]);
52
+
53
+ if (!isOpen) return null;
54
+
55
+ return (
56
+ <div
57
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
58
+ onClick={onClose}
59
+ >
60
+ <div
61
+ className="relative bg-white rounded-2xl shadow-2xl p-8 max-w-sm w-full mx-4 animate-in zoom-in-95 duration-200"
62
+ onClick={(e) => e.stopPropagation()}
63
+ >
64
+ <button
65
+ onClick={onClose}
66
+ className="absolute top-4 right-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
67
+ aria-label="Close"
68
+ >
69
+ <XIcon className="w-5 h-5 text-gray-500" />
70
+ </button>
71
+
72
+ <h3 className="text-xl font-semibold text-gray-900 mb-6 text-center">Scan to Chat</h3>
73
+
74
+ <div className="flex justify-center">
75
+ <AgentQrCode
76
+ agentName={agentName}
77
+ meta={meta}
78
+ personaDescription={personaDescription}
79
+ agentUrl={agentUrl}
80
+ agentEmail={agentEmail}
81
+ />
82
+ </div>
83
+
84
+ <p className="mt-6 text-sm text-gray-500 text-center">
85
+ Scan this QR code to start chatting with {(meta.fullname as string) || agentName}
86
+ </p>
87
+ </div>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,87 @@
1
+ import ReactMarkdown from 'react-markdown';
2
+ import remarkGfm from 'remark-gfm';
3
+ import { string_book } from '../../../../../src/book-2.0/agent-source/string_book';
4
+ import { BookEditor } from '../../../../../src/book-components/BookEditor/BookEditor';
5
+ import { OpenMojiIcon } from '../OpenMojiIcon/OpenMojiIcon';
6
+
7
+ type DocumentationContentProps = {
8
+ primary: {
9
+ type: string;
10
+ icon: string;
11
+ description?: string;
12
+ documentation: string;
13
+ };
14
+ aliases?: string[];
15
+ isPrintOnly?: boolean;
16
+ };
17
+
18
+ export function DocumentationContent({ primary, aliases = [], isPrintOnly = false }: DocumentationContentProps) {
19
+ return (
20
+ <div className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden ${isPrintOnly ? 'shadow-none border-none' : ''}`}>
21
+ <div className={`p-8 border-b border-gray-100 bg-gray-50/50 ${isPrintOnly ? 'border-none bg-white p-0 mb-4' : ''}`}>
22
+ <div className="flex items-center gap-4 mb-4">
23
+ <h1 className="text-4xl font-bold text-gray-900 tracking-tight">
24
+ <OpenMojiIcon icon={primary.icon} className="mr-3" />
25
+ {primary.type}
26
+ {aliases.length > 0 && (
27
+ <span className="text-gray-400 font-normal ml-4 text-2xl">
28
+ / {aliases.join(' / ')}
29
+ </span>
30
+ )}
31
+ </h1>
32
+ {!isPrintOnly && (
33
+ <span className="px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700">
34
+ Commitment
35
+ </span>
36
+ )}
37
+ </div>
38
+ {primary.description && (
39
+ <p className="text-xl text-gray-600 leading-relaxed max-w-3xl">
40
+ {primary.description}
41
+ </p>
42
+ )}
43
+ </div>
44
+
45
+ <div className={`p-8 ${isPrintOnly ? 'p-0' : ''}`}>
46
+ <article className="prose prose-lg prose-slate max-w-none prose-headings:font-bold prose-headings:tracking-tight prose-headings:text-gray-900 prose-h1:text-4xl prose-h1:mb-8 prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:pb-2 prose-h2:border-b prose-h2:border-gray-200 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 prose-h3:text-gray-800 prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-6 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:text-blue-700 hover:prose-a:underline prose-a:transition-colors prose-strong:font-bold prose-strong:text-gray-900 prose-code:text-blue-600 prose-code:bg-blue-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none prose-code:font-medium prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:shadow-lg prose-pre:rounded-xl prose-pre:p-6 prose-ul:list-disc prose-ul:pl-6 prose-li:marker:text-gray-400 prose-li:mb-2 prose-ol:list-decimal prose-ol:pl-6 prose-li:mb-2 prose-blockquote:border-l-4 prose-blockquote:border-blue-500 prose-blockquote:bg-blue-50/50 prose-blockquote:py-2 prose-blockquote:px-4 prose-blockquote:rounded-r-lg prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-blockquote:my-8 prose-img:rounded-xl prose-img:shadow-md prose-img:my-8 prose-hr:border-gray-200 prose-hr:my-10 prose-table:w-full prose-th:text-left prose-th:py-2 prose-th:px-3 prose-th:bg-gray-100 prose-th:font-semibold prose-th:text-gray-900 prose-td:py-2 prose-td:px-3 prose-td:border-b prose-td:border-gray-200 prose-tr:hover:bg-gray-50">
47
+ <ReactMarkdown
48
+ remarkPlugins={[remarkGfm]}
49
+ components={{
50
+ code(props) {
51
+ const { children, className, node, ...rest } = props;
52
+ const match = /language-(\w+)/.exec(className || '');
53
+ if (match && match[1] === 'book') {
54
+ const value = String(children).replace(/\n$/, '');
55
+ // Estimate height: lines * 30px + padding
56
+ const lineCount = value.split('\n').length;
57
+ const height = lineCount * 30 + 40; // 30px per line + 40px buffer
58
+
59
+ return (
60
+ <div className="not-prose my-6 rounded-lg overflow-hidden border border-gray-200 shadow-sm print:border-gray-300">
61
+ <BookEditor
62
+ value={value as string_book}
63
+ isReadonly={true}
64
+ isVerbose={false}
65
+ height={`${height}px`}
66
+ isDownloadButtonShown={false}
67
+ isAboutButtonShown={false}
68
+ isFullscreenButtonShown={true}
69
+ />
70
+ </div>
71
+ );
72
+ }
73
+ return (
74
+ <code className={className} {...rest}>
75
+ {children}
76
+ </code>
77
+ );
78
+ },
79
+ }}
80
+ >
81
+ {primary.documentation}
82
+ </ReactMarkdown>
83
+ </article>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { usePathname } from 'next/navigation';
3
+ import { usePathname, useSearchParams } from 'next/navigation';
4
4
  import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
5
5
  import type { UserInfo } from '../../utils/getCurrentUser';
6
6
  import { Footer, type FooterLink } from '../Footer/Footer';
@@ -28,12 +28,13 @@ export function LayoutWrapper({
28
28
  footerLinks,
29
29
  }: LayoutWrapperProps) {
30
30
  const pathname = usePathname();
31
- const isAdminChatPage =
32
- pathname?.startsWith('/admin/chat-history') || pathname?.startsWith('/admin/chat-feedback');
33
- const isHeaderHidden = pathname?.includes('/chat') && !isAdminChatPage;
34
- const isFooterHiddenOnPage = pathname ? /^\/agents\/[^/]+\/book(\+chat)?$/.test(pathname) : false;
31
+ const searchParams = useSearchParams();
32
+ const isHeadless = searchParams.has('headless');
33
+ // const isAdminChatPage = pathname?.startsWith('/admin/chat-history') || pathname?.startsWith('/admin/chat-feedback');
34
+ const isHeaderHidden = false; // pathname?.includes('/chat') && !isAdminChatPage;
35
+ const isFooterHiddenOnPage = pathname ? /^\/agents\/[^/]+\/(book|chat|book\+chat)$/.test(pathname) : false;
35
36
 
36
- if (isHeaderHidden) {
37
+ if (isHeaderHidden || isHeadless) {
37
38
  return <main className={`pt-0`}>{children}</main>;
38
39
  }
39
40
 
@@ -0,0 +1,20 @@
1
+ import { DetailedHTMLProps, HTMLAttributes } from 'react';
2
+
3
+ type OpenMojiIconProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
4
+ icon: string;
5
+ };
6
+
7
+ /**
8
+ * Renders an emoji using the OpenMoji black and white font
9
+ */
10
+ export function OpenMojiIcon({ icon, className, style, ...rest }: OpenMojiIconProps) {
11
+ return (
12
+ <span
13
+ className={className}
14
+ style={{ ...style, fontFamily: '"OpenMojiBlack", sans-serif' }}
15
+ {...rest}
16
+ >
17
+ {icon}
18
+ </span>
19
+ );
20
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { OpenMojiIcon } from '../OpenMojiIcon/OpenMojiIcon';
4
+
5
+ export function PrintButton() {
6
+ return (
7
+ <button
8
+ onClick={() => window.print()}
9
+ className="fixed bottom-8 right-8 bg-blue-600 hover:bg-blue-700 text-white rounded-full p-4 shadow-lg transition-all hover:scale-105 print:hidden z-50 flex items-center justify-center gap-2 group"
10
+ title="Print documentation"
11
+ >
12
+ <OpenMojiIcon icon="🖨️" className="text-2xl" />
13
+ <span className="max-w-0 overflow-hidden group-hover:max-w-xs transition-all duration-300 ease-in-out whitespace-nowrap">
14
+ Print
15
+ </span>
16
+ </button>
17
+ );
18
+ }
@@ -0,0 +1,18 @@
1
+ export function PrintHeader({ title }: { title?: string }) {
2
+ return (
3
+ <div className="hidden print:block mb-8 border-b-2 border-blue-600 pb-4">
4
+ <div className="flex justify-between items-end">
5
+ <div>
6
+ <h1 className="text-3xl font-bold text-gray-900 font-poppins">Agents Server</h1>
7
+ <div className="text-sm text-gray-500 mt-1 flex items-center gap-1">
8
+ Powered by <span className="font-semibold text-blue-600">Promptbook</span>
9
+ </div>
10
+ </div>
11
+ {title && <h2 className="text-xl font-semibold text-gray-700">{title}</h2>}
12
+ </div>
13
+ <div className="text-xs text-gray-400 mt-2 text-right">
14
+ {new Date().toLocaleDateString()}
15
+ </div>
16
+ </div>
17
+ );
18
+ }
@@ -61,6 +61,12 @@ export const metadataDefaults = [
61
61
  note: 'Maximum size of file that can be uploaded in MB.',
62
62
  type: 'NUMBER',
63
63
  },
64
+ {
65
+ key: 'NAME_POOL',
66
+ value: 'ENGLISH',
67
+ note: 'Language for generating new agent names. Possible values: ENGLISH, CZECH.',
68
+ type: 'TEXT_SINGLE_LINE',
69
+ },
64
70
  ] as const satisfies ReadonlyArray<{
65
71
  key: string;
66
72
  value: string;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "ChatHistory" ADD COLUMN "source" TEXT CHECK ("source" IN ('AGENT_PAGE_CHAT', 'OPENAI_API_COMPATIBILITY'));
2
+ ALTER TABLE "ChatHistory" ADD COLUMN "apiKey" TEXT;