@promptbook/cli 0.104.0-0 → 0.104.0-2

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 (118) hide show
  1. package/apps/agents-server/next.config.ts +2 -2
  2. package/apps/agents-server/package.json +6 -1
  3. package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
  4. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +50 -0
  5. package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
  6. package/apps/agents-server/src/app/AddAgentButton.tsx +4 -3
  7. package/apps/agents-server/src/app/actions.ts +17 -5
  8. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +15 -11
  9. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -7
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +32 -2
  11. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +2 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +18 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  14. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  15. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +17 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +1 -1
  17. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +1 -1
  18. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  19. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  20. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  21. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  22. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +66 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +211 -0
  24. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  25. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  26. package/apps/agents-server/src/app/agents/[agentName]/integration/WebsiteIntegrationTabs.tsx +26 -0
  27. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +23 -6
  28. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +2 -2
  29. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +12 -6
  30. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +87 -0
  31. package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +35 -18
  32. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  33. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +19 -0
  34. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +42 -0
  35. package/apps/agents-server/src/app/api/agents/route.ts +29 -4
  36. package/apps/agents-server/src/app/api/docs/book.md/route.ts +58 -0
  37. package/apps/agents-server/src/app/api/embed.js/route.ts +87 -67
  38. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  39. package/apps/agents-server/src/app/api/images/[filename]/route.ts +107 -0
  40. package/apps/agents-server/src/app/api/upload/route.ts +119 -45
  41. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  42. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  43. package/apps/agents-server/src/app/embed/layout.tsx +31 -0
  44. package/apps/agents-server/src/app/embed/page.tsx +22 -9
  45. package/apps/agents-server/src/app/globals.css +140 -33
  46. package/apps/agents-server/src/app/layout.tsx +27 -22
  47. package/apps/agents-server/src/app/page.tsx +50 -4
  48. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  49. package/apps/agents-server/src/app/recycle-bin/page.tsx +25 -41
  50. package/apps/agents-server/src/app/sitemap.xml/route.ts +6 -3
  51. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +6 -97
  52. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  53. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  54. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  55. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  56. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  57. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  58. package/apps/agents-server/src/components/Header/Header.tsx +79 -35
  59. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +85 -20
  60. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +72 -12
  61. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +50 -0
  62. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  63. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  64. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  65. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  66. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  67. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  68. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  69. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  70. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  71. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  72. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  73. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  74. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  75. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  76. package/apps/agents-server/src/database/schema.ts +109 -0
  77. package/apps/agents-server/src/generated/reservedPaths.ts +27 -0
  78. package/apps/agents-server/src/middleware.ts +7 -20
  79. package/apps/agents-server/src/tools/$provideCdnForServer.ts +6 -1
  80. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  81. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  82. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  83. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +33 -0
  84. package/apps/agents-server/src/utils/handleChatCompletion.ts +60 -4
  85. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +21 -0
  86. package/apps/agents-server/src/utils/validateApiKey.ts +2 -1
  87. package/esm/index.es.js +140 -27
  88. package/esm/index.es.js.map +1 -1
  89. package/esm/typings/src/_packages/types.index.d.ts +6 -2
  90. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
  91. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  92. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  93. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  94. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  95. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +2 -2
  96. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  97. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +13 -7
  98. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +6 -0
  99. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  100. package/esm/typings/src/commitments/index.d.ts +2 -1
  101. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  102. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  103. package/esm/typings/src/types/typeAliases.d.ts +12 -0
  104. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  105. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  106. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  107. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  108. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  109. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  110. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  111. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  112. package/esm/typings/src/version.d.ts +1 -1
  113. package/package.json +1 -1
  114. package/umd/index.umd.js +146 -33
  115. package/umd/index.umd.js.map +1 -1
  116. package/apps/agents-server/package-lock.json +0 -27
  117. package/apps/agents-server/public/fonts/download-font.js +0 -22
  118. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
@@ -0,0 +1,107 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { serializeError } from '@promptbook-local/utils';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import { assertsError } from '../../../../../../../src/errors/assertsError';
5
+ import type { LlmExecutionTools } from '../../../../../../../src/execution/LlmExecutionTools';
6
+ import { getSingleLlmExecutionTools } from '../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
7
+ import { string_url } from '../../../../../../../src/types/typeAliases';
8
+ import { $provideSupabaseForServer } from '../../../../database/$provideSupabaseForServer';
9
+ import { $provideCdnForServer } from '../../../../tools/$provideCdnForServer';
10
+ import { $provideExecutionToolsForServer } from '../../../../tools/$provideExecutionToolsForServer';
11
+ import { filenameToPrompt } from '../../../../utils/normalization/filenameToPrompt';
12
+
13
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ filename: string }> }) {
14
+ try {
15
+ const { filename } = await params;
16
+
17
+ if (!filename) {
18
+ return NextResponse.json({ error: 'Filename is required' }, { status: 400 });
19
+ }
20
+
21
+ const supabase = $provideSupabaseForServer();
22
+
23
+ // Check if image already exists in database
24
+ const { data: existingImage, error: selectError } = await supabase
25
+ .from(await $getTableName(`Image`))
26
+ .select('cdnUrl')
27
+ .eq('filename', filename)
28
+ .single();
29
+
30
+ if (selectError && selectError.code !== 'PGRST116') {
31
+ // PGRST116 is "not found"
32
+ throw selectError;
33
+ }
34
+
35
+ if (existingImage) {
36
+ // Image exists, redirect to CDN
37
+ return NextResponse.redirect(existingImage.cdnUrl as string_url);
38
+ }
39
+
40
+ // Image doesn't exist, generate it
41
+ const prompt = filenameToPrompt(filename);
42
+
43
+ const executionTools = await $provideExecutionToolsForServer();
44
+ const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
45
+
46
+ if (!llmTools.callImageGenerationModel) {
47
+ throw new Error('Image generation is not supported by the current LLM configuration');
48
+ }
49
+
50
+ const imageResult = await llmTools.callImageGenerationModel({
51
+ title: `Generate image for ${filename}`,
52
+ content: prompt,
53
+ parameters: {},
54
+ modelRequirements: {
55
+ modelVariant: 'IMAGE_GENERATION',
56
+ modelName: 'dall-e-3', // Use DALL-E 3 for high quality
57
+ },
58
+ });
59
+
60
+ if (!imageResult.content) {
61
+ throw new Error('Failed to generate image: no content returned');
62
+ }
63
+
64
+ // Download the generated image
65
+ const imageResponse = await fetch(imageResult.content);
66
+ if (!imageResponse.ok) {
67
+ throw new Error(`Failed to download generated image: ${imageResponse.status}`);
68
+ }
69
+
70
+ const imageBuffer = await imageResponse.arrayBuffer();
71
+ const buffer = Buffer.from(imageBuffer);
72
+
73
+ // Upload to CDN
74
+ const cdn = $provideCdnForServer();
75
+ const cdnKey = `generated-images/${filename}`;
76
+ await cdn.setItem(cdnKey, {
77
+ type: 'image/png', // DALL-E generates PNG
78
+ data: buffer,
79
+ });
80
+
81
+ const cdnUrl = cdn.getItemUrl(cdnKey);
82
+
83
+ // Save to database
84
+ const { error: insertError } = await supabase.from(await $getTableName(`Image`)).insert({
85
+ filename,
86
+ prompt,
87
+ cdnUrl: cdnUrl.href,
88
+ cdnKey,
89
+ });
90
+
91
+ if (insertError) {
92
+ throw insertError;
93
+ }
94
+
95
+ // Redirect to the newly created image
96
+ return NextResponse.redirect(cdnUrl.href as string_url);
97
+ } catch (error) {
98
+ assertsError(error);
99
+
100
+ console.error('Error serving image:', error);
101
+
102
+ return new Response(JSON.stringify(serializeError(error), null, 4), {
103
+ status: 500,
104
+ headers: { 'Content-Type': 'application/json' },
105
+ });
106
+ }
107
+ }
@@ -1,70 +1,144 @@
1
- import { nextRequestToNodeRequest } from '@/src/utils/cdn/utils/nextRequestToNodeRequest';
2
- import { TODO_any } from '@promptbook-local/types';
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabase } from '@/src/database/$provideSupabase';
3
3
  import { serializeError } from '@promptbook-local/utils';
4
- import formidable from 'formidable';
5
- import { readFile } from 'fs/promises';
4
+ import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
6
5
  import { NextRequest, NextResponse } from 'next/server';
7
- import { forTime } from 'waitasecond';
8
6
  import { assertsError } from '../../../../../../src/errors/assertsError';
9
- import { string_url } from '../../../../../../src/types/typeAliases';
10
- import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
11
- import { $provideCdnForServer } from '../../../../src/tools/$provideCdnForServer';
12
- import { getUserFileCdnKey } from '../../../../src/utils/cdn/utils/getUserFileCdnKey';
13
- import { validateMimeType } from '../../../../src/utils/validators/validateMimeType';
7
+ import { getUserIdFromRequest } from '../../../../src/utils/getUserIdFromRequest';
14
8
  import { getMetadata } from '../../../database/getMetadata';
15
9
 
16
10
  export async function POST(request: NextRequest) {
17
11
  try {
18
- await forTime(1);
19
- // await forTime(5000);
12
+ const body = (await request.json()) as HandleUploadBody;
13
+ const userId = await getUserIdFromRequest(request);
14
+ const supabase = $provideSupabase();
20
15
 
21
- const nodeRequest = await nextRequestToNodeRequest(request);
22
- let maxFileSizeMb = Number((await getMetadata('MAX_FILE_UPLOAD_SIZE_MB')) || '50'); // <- TODO: [🌲] To /config.ts
16
+ // Handle Vercel Blob client upload protocol
17
+ const jsonResponse = await handleUpload({
18
+ body,
19
+ request,
20
+ token: process.env.VERCEL_BLOB_READ_WRITE_TOKEN!,
21
+ onBeforeGenerateToken: async (pathname, clientPayload) => {
22
+ // Authenticate user and validate upload
23
23
 
24
- if (Number.isNaN(maxFileSizeMb)) {
25
- maxFileSizeMb = 50; // <- TODO: [🌲] To /config.ts
26
- }
24
+ // Parse client payload for additional metadata
25
+ const payload = clientPayload ? JSON.parse(clientPayload) : {};
26
+ const { purpose, contentType } = payload;
27
27
 
28
- const maxFileSize = maxFileSizeMb * 1024 * 1024;
28
+ let maxFileSizeMb = Number((await getMetadata('MAX_FILE_UPLOAD_SIZE_MB')) || '50'); // <- TODO: [🌲] To /config.ts
29
+ if (Number.isNaN(maxFileSizeMb)) {
30
+ maxFileSizeMb = 50; // <- TODO: [🌲] To /config.ts
31
+ }
32
+ const maxFileSize = maxFileSizeMb * 1024 * 1024;
33
+
34
+ // Generate the proper path with prefix
35
+ // Note: With client uploads, we use the original filename provided by the client
36
+ // The file will be stored at: {pathPrefix}/user/files/{filename}
37
+ const pathPrefix = process.env.NEXT_PUBLIC_CDN_PATH_PREFIX || '';
29
38
 
30
- const files = await new Promise<formidable.Files>((resolve, reject) => {
31
- const form = formidable({ maxFileSize });
32
- form.parse(nodeRequest as TODO_any, (error, fields, files) => {
33
- keepUnused(fields);
39
+ // Create a DB record at the start of the upload to track it
40
+ const uploadPurpose = purpose || 'GENERIC_UPLOAD';
41
+ const { data: insertedFile, error: insertError } = await supabase
42
+ .from(await $getTableName('File'))
43
+ .insert({
44
+ userId: userId || null,
45
+ fileName: pathname,
46
+ fileSize: 0, // <- Will be updated when upload completes
47
+ fileType: contentType || 'application/octet-stream',
48
+ storageUrl: null, // <- To be updated on completion
49
+ shortUrl: null, // <- To be updated on completion
50
+ purpose: uploadPurpose,
51
+ status: 'UPLOADING',
52
+ })
53
+ .select('id')
54
+ .single();
34
55
 
35
- if (error) {
36
- return reject(error);
56
+ if (insertError) {
57
+ console.error('🔼 Failed to create file record:', insertError);
37
58
  }
38
- resolve(files);
39
- });
40
- });
41
59
 
42
- const uploadedFiles = files.file;
60
+ console.info('🔼 Upload started, tracking file:', {
61
+ pathname,
62
+ fileId: insertedFile?.id,
63
+ purpose: uploadPurpose,
64
+ });
43
65
 
44
- if (!uploadedFiles || uploadedFiles.length !== 1) {
45
- return NextResponse.json(
46
- { message: 'In form data there is not EXACTLY one "file" field' },
47
- { status: 400 },
48
- );
49
- }
66
+ return {
67
+ allowedContentTypes: contentType ? [contentType] : undefined,
68
+ maximumSizeInBytes: maxFileSize,
69
+ addRandomSuffix: true, // Add random suffix to avoid filename collisions since we can't hash content
70
+ tokenPayload: JSON.stringify({
71
+ userId: userId || null,
72
+ purpose: uploadPurpose,
73
+ fileId: insertedFile?.id || null,
74
+ uploadPath: pathname,
75
+ pathPrefix,
76
+ }),
77
+ };
78
+ },
79
+ onUploadCompleted: async ({ blob, tokenPayload }) => {
80
+ // !!!!
81
+ // ⚠️ IMPORTANT: This callback is a WEBHOOK called by Vercel's servers AFTER the upload completes
82
+ // - It runs in a DIFFERENT request context (not the original user request)
83
+ // - It WON'T work in local development (Vercel can't reach localhost)
84
+ // - All data must come from tokenPayload (userId, fileId, etc.)
85
+ // - Need to create a fresh supabase client here
86
+ console.info('🔼 Upload completed (webhook callback):', { blob, tokenPayload });
50
87
 
51
- const uploadedFile = uploadedFiles[0]!;
52
- const fileBuffer = await readFile(uploadedFile.filepath);
53
- const cdn = $provideCdnForServer();
54
- const key = getUserFileCdnKey(fileBuffer, uploadedFile.originalFilename || uploadedFile.newFilename);
88
+ try {
89
+ const payload = tokenPayload ? JSON.parse(tokenPayload) : {};
90
+ const { fileId, userId: tokenUserId, purpose: tokenPurpose, uploadPath } = payload;
55
91
 
56
- await cdn.setItem(key, {
57
- type: validateMimeType(uploadedFile.mimetype),
58
- data: fileBuffer,
59
- });
92
+ // Create fresh supabase client for this webhook context
93
+ const supabase = $provideSupabase();
60
94
 
61
- const fileUrl = cdn.getItemUrl(key);
95
+ if (fileId) {
96
+ // Update the existing record by ID
97
+ const { error: updateError } = await supabase
98
+ .from(await $getTableName('File'))
99
+ .update({
100
+ userId: tokenUserId || null,
101
+ fileSize: 0, // <- !!!!
102
+ fileType: blob.contentType,
103
+ storageUrl: blob.url,
104
+ // <- TODO: !!!! Split between storageUrl and shortUrl
105
+ purpose: tokenPurpose || 'GENERIC_UPLOAD',
106
+ status: 'COMPLETED',
107
+ })
108
+ .eq('id', fileId);
109
+
110
+ if (updateError) {
111
+ console.error('🔼 Failed to update file record:', updateError);
112
+ } else {
113
+ console.info('🔼 File record updated successfully:', { fileId, shortUrl: blob.url });
114
+ }
115
+ } else if (uploadPath) {
116
+ // Fallback: Update by uploadPath if fileId is not available
117
+ const { error: updateError } = await supabase
118
+ .from(await $getTableName('File'))
119
+ .update({
120
+ fileSize: 0, // <- !!!!
121
+ fileType: blob.contentType,
122
+ storageUrl: blob.url,
123
+ status: 'COMPLETED',
124
+ })
125
+ .eq('id', fileId);
126
+
127
+ if (updateError) {
128
+ console.error('🔼 Failed to update file record by uploadPath:', updateError);
129
+ }
130
+ }
131
+ } catch (error) {
132
+ console.error('🔼 Error in onUploadCompleted:', error);
133
+ }
134
+ },
135
+ });
62
136
 
63
- return NextResponse.json({ fileUrl: fileUrl.href as string_url }, { status: 201 });
137
+ return NextResponse.json(jsonResponse);
64
138
  } catch (error) {
65
139
  assertsError(error);
66
140
 
67
- console.error(error);
141
+ console.error('🔼', error);
68
142
 
69
143
  return new Response(
70
144
  JSON.stringify(
@@ -1,7 +1,7 @@
1
1
  import { notFound } from 'next/navigation';
2
2
  import { BookCommitment } from '../../../../../../src/commitments/_base/BookCommitment';
3
3
  import { getVisibleCommitmentDefinitions } from '../../../utils/getVisibleCommitmentDefinitions';
4
- import { PrintButton } from '../../../components/PrintButton/PrintButton';
4
+ import { DocsToolbar } from '../../../components/DocsToolbar/DocsToolbar';
5
5
  import { PrintHeader } from '../../../components/PrintHeader/PrintHeader';
6
6
  import { DocumentationContent } from '../../../components/DocumentationContent/DocumentationContent';
7
7
 
@@ -27,9 +27,8 @@ export default async function DocPage(props: DocPageProps) {
27
27
 
28
28
  return (
29
29
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
30
- <PrintButton />
31
-
32
30
  <div className="container mx-auto px-4 py-16">
31
+ <DocsToolbar />
33
32
  <PrintHeader title={primary.type} />
34
33
 
35
34
  <DocumentationContent
@@ -1,20 +1,20 @@
1
+ import { MarkdownContent } from '@promptbook-local/components';
1
2
  import Link from 'next/link';
3
+ import { DocsToolbar } from '../../components/DocsToolbar/DocsToolbar';
4
+ import { DocumentationContent } from '../../components/DocumentationContent/DocumentationContent';
2
5
  import { Card } from '../../components/Homepage/Card';
3
6
  import { Section } from '../../components/Homepage/Section';
4
7
  import { OpenMojiIcon } from '../../components/OpenMojiIcon/OpenMojiIcon';
5
- import { getVisibleCommitmentDefinitions } from '../../utils/getVisibleCommitmentDefinitions';
6
- import { PrintButton } from '../../components/PrintButton/PrintButton';
7
8
  import { PrintHeader } from '../../components/PrintHeader/PrintHeader';
8
- import { DocumentationContent } from '../../components/DocumentationContent/DocumentationContent';
9
+ import { getVisibleCommitmentDefinitions } from '../../utils/getVisibleCommitmentDefinitions';
9
10
 
10
11
  export default function DocsPage() {
11
12
  const groupedCommitments = getVisibleCommitmentDefinitions();
12
13
 
13
14
  return (
14
15
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
15
- <PrintButton />
16
-
17
16
  <div className="container mx-auto px-4 py-16">
17
+ <DocsToolbar />
18
18
  <PrintHeader title="Full Documentation" />
19
19
 
20
20
  {/* Screen view: Cards */}
@@ -24,7 +24,7 @@ export default function DocsPage() {
24
24
  <Link key={primary.type} href={`/docs/${primary.type}`} className="block h-full group">
25
25
  <Card className="h-full group-hover:border-blue-500 transition-colors">
26
26
  <h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
27
- <OpenMojiIcon icon={primary.icon} className="mr-2" />
27
+ <OpenMojiIcon icon={primary.icon} variant="color" className="mr-2" />
28
28
  {primary.type}
29
29
  {aliases.length > 0 && (
30
30
  <span className="text-gray-400 font-normal text-lg">
@@ -33,7 +33,11 @@ export default function DocsPage() {
33
33
  </span>
34
34
  )}
35
35
  </h3>
36
- {primary.description && <p className="text-gray-600 line-clamp-3">{primary.description}</p>}
36
+ {primary.description && (
37
+ <p className="text-gray-600 line-clamp-3">
38
+ <MarkdownContent content={primary.description} />
39
+ </p>
40
+ )}
37
41
  </Card>
38
42
  </Link>
39
43
  ))}
@@ -44,11 +48,7 @@ export default function DocsPage() {
44
48
  <div className="hidden print:block space-y-12">
45
49
  {groupedCommitments.map(({ primary, aliases }) => (
46
50
  <div key={primary.type} className="break-inside-avoid page-break-after-always">
47
- <DocumentationContent
48
- primary={primary}
49
- aliases={aliases}
50
- isPrintOnly={true}
51
- />
51
+ <DocumentationContent primary={primary} aliases={aliases} isPrintOnly={true} />
52
52
  <hr className="my-8 border-gray-200" />
53
53
  </div>
54
54
  ))}
@@ -0,0 +1,31 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ title: 'Promptbook Agent Embed',
5
+ description: 'Embedded agent chat widget',
6
+ };
7
+
8
+ /**
9
+ * Minimal layout for the embed page - no header, footer, or other UI elements
10
+ * This layout is completely transparent and only renders the chat widget
11
+ */
12
+ export default function EmbedLayout({
13
+ children,
14
+ }: Readonly<{
15
+ children: React.ReactNode;
16
+ }>) {
17
+ return (
18
+ <html lang="en">
19
+ <body
20
+ style={{
21
+ margin: 0,
22
+ padding: 0,
23
+ background: 'transparent',
24
+ overflow: 'hidden',
25
+ }}
26
+ >
27
+ {children}
28
+ </body>
29
+ </html>
30
+ );
31
+ }
@@ -2,23 +2,36 @@
2
2
 
3
3
  import { PromptbookAgentIntegration } from '@promptbook-local/components';
4
4
  import { useSearchParams } from 'next/navigation';
5
+ import { useMemo } from 'react';
5
6
 
6
7
  export default function EmbedPage() {
7
8
  const searchParams = useSearchParams();
8
9
  const agentUrl = searchParams.get('agentUrl');
10
+ const metaParam = searchParams.get('meta');
11
+
12
+ const meta = useMemo(() => {
13
+ if (!metaParam) {
14
+ return undefined;
15
+ }
16
+ try {
17
+ return JSON.parse(metaParam);
18
+ } catch (e) {
19
+ console.error('[🔌] Failed to parse meta parameter:', e);
20
+ return undefined;
21
+ }
22
+ }, [metaParam]);
9
23
 
10
24
  if (!agentUrl) {
11
- return <div className="text-red-500">Missing agentUrl parameter</div>;
25
+ return <div style={{ color: 'red' }}>Missing agentUrl parameter</div>;
12
26
  }
13
27
 
14
28
  return (
15
- <div className="w-full h-full bg-transparent">
16
- <PromptbookAgentIntegration
17
- agentUrl={agentUrl}
18
- onOpenChange={(isOpen) => {
19
- window.parent.postMessage({ type: 'PROMPTBOOK_AGENT_RESIZE', isOpen }, '*');
20
- }}
21
- />
22
- </div>
29
+ <PromptbookAgentIntegration
30
+ agentUrl={agentUrl}
31
+ meta={meta}
32
+ onOpenChange={(isOpen) => {
33
+ window.parent.postMessage({ type: 'PROMPTBOOK_AGENT_RESIZE', isOpen }, '*');
34
+ }}
35
+ />
23
36
  );
24
37
  }