@promptbook/cli 0.104.0-1 → 0.104.0-11

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 (229) hide show
  1. package/apps/agents-server/config.ts +1 -3
  2. package/apps/agents-server/next.config.ts +2 -2
  3. package/apps/agents-server/package.json +7 -3
  4. package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
  5. package/apps/agents-server/public/swagger.json +115 -0
  6. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
  7. package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
  8. package/apps/agents-server/src/app/AddAgentButton.tsx +47 -21
  9. package/apps/agents-server/src/app/actions.ts +22 -5
  10. package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +211 -0
  11. package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
  12. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
  13. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
  14. package/apps/agents-server/src/app/admin/files/FilesGalleryClient.tsx +263 -0
  15. package/apps/agents-server/src/app/admin/files/actions.ts +61 -0
  16. package/apps/agents-server/src/app/admin/files/page.tsx +13 -0
  17. package/apps/agents-server/src/app/admin/image-generator-test/ImageGeneratorTestClient.tsx +169 -0
  18. package/apps/agents-server/src/app/admin/image-generator-test/page.tsx +13 -0
  19. package/apps/agents-server/src/app/admin/images/ImagesGalleryClient.tsx +256 -0
  20. package/apps/agents-server/src/app/admin/images/actions.ts +60 -0
  21. package/apps/agents-server/src/app/admin/images/page.tsx +13 -0
  22. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
  23. package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
  24. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
  25. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
  26. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
  27. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
  28. package/apps/agents-server/src/app/admin/search-engine-test/SearchEngineTestClient.tsx +109 -0
  29. package/apps/agents-server/src/app/admin/search-engine-test/actions.ts +17 -0
  30. package/apps/agents-server/src/app/admin/search-engine-test/page.tsx +13 -0
  31. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
  32. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
  33. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
  34. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
  35. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
  36. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  37. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  38. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
  39. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
  40. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
  41. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
  42. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
  43. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  44. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  45. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  46. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  47. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
  48. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -0
  49. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  50. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  51. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +10 -3
  52. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
  53. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
  54. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
  55. package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
  56. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
  57. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
  58. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +10 -3
  59. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
  60. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
  61. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
  62. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -0
  63. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  64. package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +13 -14
  65. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
  66. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
  67. package/apps/agents-server/src/app/api/agents/route.ts +28 -3
  68. package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
  69. package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
  70. package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
  71. package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
  72. package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
  73. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  74. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  75. package/apps/agents-server/src/app/api/images/[filename]/route.ts +128 -0
  76. package/apps/agents-server/src/app/api/messages/route.ts +102 -0
  77. package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
  78. package/apps/agents-server/src/app/api/upload/route.ts +128 -45
  79. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  80. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  81. package/apps/agents-server/src/app/globals.css +140 -33
  82. package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
  83. package/apps/agents-server/src/app/layout.tsx +27 -22
  84. package/apps/agents-server/src/app/page.tsx +54 -6
  85. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  86. package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
  87. package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
  88. package/apps/agents-server/src/app/security.txt/route.ts +1 -1
  89. package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
  90. package/apps/agents-server/src/app/swagger/page.tsx +14 -0
  91. package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +38 -0
  92. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +44 -116
  93. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
  94. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
  95. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  96. package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
  97. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  98. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  99. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  100. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  101. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  102. package/apps/agents-server/src/components/Header/Header.tsx +130 -40
  103. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +150 -23
  104. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
  105. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
  106. package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
  107. package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
  108. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  109. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  110. package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +88 -0
  111. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  112. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  113. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  114. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  115. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
  116. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
  117. package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
  118. package/apps/agents-server/src/database/$getTableName.ts +1 -0
  119. package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
  120. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
  121. package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
  122. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  123. package/apps/agents-server/src/database/migrate.ts +34 -1
  124. package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
  125. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
  126. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  127. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  128. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  129. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  130. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  131. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  132. package/apps/agents-server/src/database/migrations/2025-12-0402-message-table.sql +42 -0
  133. package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
  134. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  135. package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
  136. package/apps/agents-server/src/database/migrations/2025-12-0830-image-purpose.sql +5 -0
  137. package/apps/agents-server/src/database/migrations/2025-12-0890-file-agent-id.sql +5 -0
  138. package/apps/agents-server/src/database/schema.ts +244 -4
  139. package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
  140. package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
  141. package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
  142. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
  143. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
  144. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
  145. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
  146. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
  147. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
  148. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
  149. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
  150. package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
  151. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  152. package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
  153. package/apps/agents-server/src/message-providers/index.ts +13 -0
  154. package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
  155. package/apps/agents-server/src/middleware.ts +19 -23
  156. package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
  157. package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
  158. package/apps/agents-server/src/utils/auth.ts +117 -17
  159. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  160. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  161. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  162. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  163. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
  164. package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
  165. package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
  166. package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
  167. package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
  168. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
  169. package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
  170. package/esm/index.es.js +1534 -1330
  171. package/esm/index.es.js.map +1 -1
  172. package/esm/typings/servers.d.ts +8 -0
  173. package/esm/typings/src/_packages/core.index.d.ts +2 -0
  174. package/esm/typings/src/_packages/types.index.d.ts +16 -2
  175. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +29 -1
  176. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  177. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
  178. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  179. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  180. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  181. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  182. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  183. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +9 -13
  184. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +3 -3
  185. package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +1 -1
  186. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  187. package/esm/typings/src/book-components/icons/AboutIcon.d.ts +1 -1
  188. package/esm/typings/src/book-components/icons/AttachmentIcon.d.ts +1 -1
  189. package/esm/typings/src/book-components/icons/CameraIcon.d.ts +1 -1
  190. package/esm/typings/src/book-components/icons/DownloadIcon.d.ts +1 -1
  191. package/esm/typings/src/book-components/icons/MenuIcon.d.ts +1 -1
  192. package/esm/typings/src/book-components/icons/SaveIcon.d.ts +1 -1
  193. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -12
  194. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +27 -15
  195. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  196. package/esm/typings/src/commitments/index.d.ts +2 -1
  197. package/esm/typings/src/llm-providers/_common/utils/count-total-usage/countUsage.d.ts +1 -1
  198. package/esm/typings/src/llm-providers/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
  199. package/esm/typings/src/llm-providers/agent/Agent.d.ts +6 -1
  200. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  201. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  202. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  203. package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
  204. package/esm/typings/src/remote-server/ui/ServerApp.d.ts +1 -1
  205. package/esm/typings/src/search-engines/SearchEngine.d.ts +9 -0
  206. package/esm/typings/src/search-engines/SearchResult.d.ts +18 -0
  207. package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +15 -0
  208. package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +15 -0
  209. package/esm/typings/src/types/Message.d.ts +49 -0
  210. package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
  211. package/esm/typings/src/types/typeAliases.d.ts +23 -1
  212. package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
  213. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  214. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  215. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  216. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  217. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  218. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  219. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  220. package/esm/typings/src/utils/random/$randomAgentPersona.d.ts +3 -2
  221. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  222. package/esm/typings/src/version.d.ts +1 -1
  223. package/package.json +1 -1
  224. package/umd/index.umd.js +1542 -1338
  225. package/umd/index.umd.js.map +1 -1
  226. package/apps/agents-server/package-lock.json +0 -27
  227. package/apps/agents-server/public/fonts/download-font.js +0 -22
  228. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
  229. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
@@ -0,0 +1,194 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
3
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { $provideCdnForServer } from '@/src/tools/$provideCdnForServer';
5
+ import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
6
+ import { parseAgentSource } from '@promptbook-local/core';
7
+ import { computeHash, serializeError } from '@promptbook-local/utils';
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { assertsError } from '../../../../../../../../src/errors/assertsError';
10
+ import type { LlmExecutionTools } from '../../../../../../../../src/execution/LlmExecutionTools';
11
+ import { getSingleLlmExecutionTools } from '../../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
12
+ import type { string_url } from '../../../../../../../../src/types/typeAliases';
13
+ import { getAgentDefaultAvatarPrompt } from './getAgentDefaultAvatarPrompt';
14
+
15
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
16
+ try {
17
+ let { agentName } = await params;
18
+ agentName = decodeURIComponent(agentName);
19
+
20
+ if (!agentName) {
21
+ return NextResponse.json({ error: 'Agent name is required' }, { status: 400 });
22
+ }
23
+
24
+ // 1. Fetch agent data first to construct the prompt
25
+ const collection = await $provideAgentCollectionForServer();
26
+ let agentSource;
27
+ try {
28
+ agentSource = await collection.getAgentSource(agentName);
29
+ } catch (error) {
30
+ assertsError(error);
31
+
32
+ return NextResponse.json({ error: serializeError(error) }, { status: 500 });
33
+
34
+ //> // If agent not found, redirect to pravatar with the agent name as unique identifier
35
+ //> const pravaratUrl = `https://i.pravatar.cc/1024?u=${encodeURIComponent(agentName)}`;
36
+ //> return NextResponse.redirect(pravaratUrl);
37
+ }
38
+
39
+ const agentProfile = parseAgentSource(agentSource);
40
+
41
+ const prompt = getAgentDefaultAvatarPrompt(agentProfile);
42
+
43
+ // Use hash of the prompt as cache key - this ensures regeneration when prompt changes
44
+ const promptHash = computeHash(prompt);
45
+ const internalFilename = `agent-avatar-${promptHash}.png`;
46
+
47
+ const supabase = $provideSupabaseForServer();
48
+ const lockKey = `agent-avatar-${promptHash}`;
49
+
50
+ // Loop to acquire lock or wait for image
51
+ // We try for 60 seconds
52
+ for (let attempt = 0; attempt < 60; attempt++) {
53
+ // 1. Check if image with this prompt hash already exists in database
54
+ const { data: existingImage, error: selectError } = await supabase
55
+ .from(await $getTableName(`Image`))
56
+ .select('cdnUrl')
57
+ .eq('filename', internalFilename)
58
+ .single();
59
+
60
+ if (selectError && selectError.code !== 'PGRST116') {
61
+ // PGRST116 is "not found"
62
+ throw selectError;
63
+ }
64
+
65
+ if (existingImage) {
66
+ // Image exists, fetch from CDN and return directly
67
+ const imageResponse = await fetch(existingImage.cdnUrl as string_url);
68
+ if (!imageResponse.ok) {
69
+ console.warn(`Failed to fetch image from CDN: ${imageResponse.status}`);
70
+ return NextResponse.redirect(existingImage.cdnUrl);
71
+ }
72
+ const imageBuffer = await imageResponse.arrayBuffer();
73
+ return new NextResponse(imageBuffer, {
74
+ status: 200,
75
+ headers: {
76
+ 'Content-Type': 'image/png',
77
+ 'Cache-Control': 'public, max-age=31536000, immutable',
78
+ },
79
+ });
80
+ }
81
+
82
+ // 2. Try to acquire lock to generate it
83
+ const { error: lockError } = await supabase.from(await $getTableName('GenerationLock')).insert({
84
+ lockKey,
85
+ expiresAt: new Date(Date.now() + 1000 * 60 * 5).toISOString(), // 5 minutes
86
+ });
87
+
88
+ if (!lockError) {
89
+ // Lock acquired!
90
+ try {
91
+ // 2. Generate image
92
+ const executionTools = await $provideExecutionToolsForServer();
93
+ const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
94
+
95
+ if (!llmTools.callImageGenerationModel) {
96
+ throw new Error('Image generation is not supported by the current LLM configuration');
97
+ }
98
+
99
+ const imageResult = await llmTools.callImageGenerationModel({
100
+ title: `Generate default avatar for ${agentName}`,
101
+ content: prompt,
102
+ parameters: {},
103
+ modelRequirements: {
104
+ modelVariant: 'IMAGE_GENERATION',
105
+ modelName: 'dall-e-3',
106
+ size: '1024x1792', // <- Vertical orientation
107
+ // <- TODO: [🤐] DRY
108
+ quality: 'hd',
109
+ style: 'natural',
110
+ },
111
+ });
112
+
113
+ if (!imageResult.content) {
114
+ throw new Error('Failed to generate image: no content returned');
115
+ }
116
+
117
+ // 3. Download and Upload to CDN
118
+ const imageResponse = await fetch(imageResult.content);
119
+ if (!imageResponse.ok) {
120
+ throw new Error(`Failed to download generated image: ${imageResponse.status}`);
121
+ }
122
+
123
+ const imageBuffer = await imageResponse.arrayBuffer();
124
+ const buffer = Buffer.from(imageBuffer);
125
+
126
+ const cdn = $provideCdnForServer();
127
+ const cdnKey = `generated-images/${internalFilename}`;
128
+ await cdn.setItem(cdnKey, {
129
+ type: 'image/png',
130
+ data: buffer,
131
+ });
132
+
133
+ const cdnUrl = cdn.getItemUrl(cdnKey);
134
+
135
+ // 4. Save to database
136
+ const { error: insertError } = await supabase.from(await $getTableName(`Image`)).insert({
137
+ filename: internalFilename,
138
+ prompt,
139
+ cdnUrl: cdnUrl.href,
140
+ cdnKey,
141
+ });
142
+
143
+ if (insertError) {
144
+ throw insertError;
145
+ }
146
+
147
+ // Return the newly created image directly
148
+ const finalImageResponse = await fetch(cdnUrl.href);
149
+ if (!finalImageResponse.ok) {
150
+ console.warn(`Failed to fetch newly created image from CDN: ${finalImageResponse.status}`);
151
+ return NextResponse.redirect(cdnUrl.href);
152
+ }
153
+ const finalImageBuffer = await finalImageResponse.arrayBuffer();
154
+ return new NextResponse(finalImageBuffer, {
155
+ status: 200,
156
+ headers: {
157
+ 'Content-Type': 'image/png',
158
+ 'Cache-Control': 'public, max-age=31536000, immutable',
159
+ },
160
+ });
161
+ } finally {
162
+ // Release lock
163
+ await supabase.from(await $getTableName('GenerationLock')).delete().eq('lockKey', lockKey);
164
+ }
165
+ }
166
+
167
+ // Lock failed, someone else is generating
168
+ // Check if expired
169
+ const { data: lockData } = await supabase
170
+ .from(await $getTableName('GenerationLock'))
171
+ .select('expiresAt')
172
+ .eq('lockKey', lockKey)
173
+ .single();
174
+
175
+ if (lockData && new Date(lockData.expiresAt) < new Date()) {
176
+ // Expired, delete and retry loop immediately
177
+ await supabase.from(await $getTableName('GenerationLock')).delete().eq('lockKey', lockKey);
178
+ continue;
179
+ }
180
+
181
+ // Wait and retry
182
+ await new Promise((resolve) => setTimeout(resolve, 1000));
183
+ }
184
+
185
+ throw new Error('Timeout waiting for image generation');
186
+ } catch (error) {
187
+ assertsError(error);
188
+ console.error('Error serving default avatar:', error);
189
+ return new Response(JSON.stringify(serializeError(error), null, 4), {
190
+ status: 500,
191
+ headers: { 'Content-Type': 'application/json' },
192
+ });
193
+ }
194
+ }
@@ -1,4 +1,5 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { $provideServer } from '@/src/tools/$provideServer';
2
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
4
5
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -18,6 +19,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
18
19
  const agentName = await getAgentName(params);
19
20
  const agentProfile = await getAgentProfile(agentName);
20
21
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
22
+ const { publicUrl } = await $provideServer();
21
23
 
22
24
  return new ImageResponse(
23
25
  (
@@ -30,6 +32,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
30
32
  alignItems: 'center',
31
33
  justifyContent: 'center',
32
34
  borderRadius: '50%',
35
+ aspectRatio: '1 / 1',
33
36
  overflow: 'hidden',
34
37
  }}
35
38
  >
@@ -45,7 +48,16 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
45
48
  >
46
49
  {/* Note: `next/image` is not working propperly with `next/og` */}
47
50
  {/* eslint-disable-next-line @next/next/no-img-element */}
48
- <img src={agentProfile.meta.image!} alt="Agent Icon" />
51
+ <img
52
+ src={
53
+ agentProfile.meta.image ||
54
+ generatePlaceholderAgentProfileImageUrl(
55
+ agentProfile.permanentId || agentName,
56
+ publicUrl,
57
+ )
58
+ }
59
+ alt="Agent Icon"
60
+ />
49
61
  </div>
50
62
  </div>
51
63
  ),
@@ -0,0 +1,200 @@
1
+ 'use server';
2
+
3
+ import { saturate } from '@promptbook-local/color';
4
+ import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
5
+ import Link from 'next/link';
6
+ import { Color } from '../../../../../../../src/utils/color/Color';
7
+ import { getAgentName, getAgentProfile } from '../_utils';
8
+
9
+ /**
10
+ * Available image types for agents with their descriptions and sizes
11
+ */
12
+ const AGENT_IMAGES = [
13
+ {
14
+ name: 'default-avatar.png',
15
+ title: 'Default Avatar',
16
+ description: 'AI-generated avatar image based on the agent profile. Vertical orientation (1024x1792).',
17
+ size: '1024×1792',
18
+ },
19
+ {
20
+ name: 'icon-256.png',
21
+ title: 'Icon (256×256)',
22
+ description: 'Small circular icon suitable for profile pictures and thumbnails.',
23
+ size: '256×256',
24
+ },
25
+ {
26
+ name: 'screenshot-fullhd.png',
27
+ title: 'Screenshot Full HD',
28
+ description: 'Landscape screenshot showing the agent with name. Suitable for desktop previews.',
29
+ size: '1920×1080',
30
+ },
31
+ {
32
+ name: 'screenshot-phone.png',
33
+ title: 'Screenshot Phone',
34
+ description: 'Portrait screenshot optimized for mobile devices.',
35
+ size: '1080×1920',
36
+ },
37
+ ] as const;
38
+
39
+ export default async function AgentImagesPage({ params }: { params: Promise<{ agentName: string }> }) {
40
+ const agentName = await getAgentName(params);
41
+ const agentProfile = await getAgentProfile(agentName);
42
+
43
+ const brandColor = Color.fromSafe(agentProfile.meta.color || PROMPTBOOK_COLOR);
44
+ const brandColorHex = brandColor.then(saturate(-0.5)).toHex();
45
+
46
+ const fullname = (agentProfile.meta.fullname || agentProfile.agentName || 'Agent') as string;
47
+
48
+ return (
49
+ <div
50
+ style={{
51
+ minHeight: '100vh',
52
+ backgroundColor: '#f5f5f5',
53
+ padding: '2rem',
54
+ }}
55
+ >
56
+ <div
57
+ style={{
58
+ maxWidth: '1200px',
59
+ margin: '0 auto',
60
+ }}
61
+ >
62
+ <header
63
+ style={{
64
+ marginBottom: '2rem',
65
+ padding: '1.5rem',
66
+ backgroundColor: brandColorHex,
67
+ borderRadius: '12px',
68
+ color: 'white',
69
+ }}
70
+ >
71
+ <h1 style={{ margin: 0, fontSize: '2rem' }}>
72
+ Images for <strong>{fullname}</strong>
73
+ </h1>
74
+ <p style={{ margin: '0.5rem 0 0', opacity: 0.9 }}>
75
+ All available image assets for agent{' '}
76
+ <code
77
+ style={{
78
+ backgroundColor: 'rgba(255,255,255,0.2)',
79
+ padding: '2px 6px',
80
+ borderRadius: '4px',
81
+ }}
82
+ >
83
+ {agentName}
84
+ </code>
85
+ </p>
86
+ </header>
87
+
88
+ <div
89
+ style={{
90
+ display: 'grid',
91
+ gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
92
+ gap: '1.5rem',
93
+ }}
94
+ >
95
+ {AGENT_IMAGES.map((image) => {
96
+ const imageUrl = `/agents/${encodeURIComponent(agentName)}/images/${image.name}`;
97
+ return (
98
+ <div
99
+ key={image.name}
100
+ style={{
101
+ backgroundColor: 'white',
102
+ borderRadius: '12px',
103
+ overflow: 'hidden',
104
+ boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
105
+ }}
106
+ >
107
+ <div
108
+ style={{
109
+ aspectRatio: '16/9',
110
+ backgroundColor: '#e0e0e0',
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ overflow: 'hidden',
115
+ }}
116
+ >
117
+ {/* eslint-disable-next-line @next/next/no-img-element */}
118
+ <img
119
+ src={imageUrl}
120
+ alt={image.title}
121
+ style={{
122
+ maxWidth: '100%',
123
+ maxHeight: '100%',
124
+ objectFit: 'contain',
125
+ }}
126
+ />
127
+ </div>
128
+ <div style={{ padding: '1rem' }}>
129
+ <h2 style={{ margin: '0 0 0.5rem', fontSize: '1.25rem' }}>{image.title}</h2>
130
+ <p style={{ margin: '0 0 0.5rem', color: '#666', fontSize: '0.9rem' }}>
131
+ {image.description}
132
+ </p>
133
+ <div
134
+ style={{
135
+ display: 'flex',
136
+ justifyContent: 'space-between',
137
+ alignItems: 'center',
138
+ marginTop: '1rem',
139
+ }}
140
+ >
141
+ <span
142
+ style={{
143
+ backgroundColor: '#f0f0f0',
144
+ padding: '4px 8px',
145
+ borderRadius: '4px',
146
+ fontSize: '0.85rem',
147
+ color: '#555',
148
+ }}
149
+ >
150
+ {image.size}
151
+ </span>
152
+ <Link
153
+ href={imageUrl}
154
+ target="_blank"
155
+ style={{
156
+ backgroundColor: brandColorHex,
157
+ color: 'white',
158
+ padding: '8px 16px',
159
+ borderRadius: '6px',
160
+ textDecoration: 'none',
161
+ fontSize: '0.9rem',
162
+ }}
163
+ >
164
+ Open Image
165
+ </Link>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ );
170
+ })}
171
+ </div>
172
+
173
+ <footer
174
+ style={{
175
+ marginTop: '2rem',
176
+ padding: '1rem',
177
+ backgroundColor: 'white',
178
+ borderRadius: '12px',
179
+ textAlign: 'center',
180
+ color: '#666',
181
+ }}
182
+ >
183
+ <p style={{ margin: 0 }}>
184
+ <Link
185
+ href={`/agents/${encodeURIComponent(agentName)}`}
186
+ style={{ color: brandColorHex, textDecoration: 'none' }}
187
+ >
188
+ ← Back to {fullname}
189
+ </Link>
190
+ </p>
191
+ </footer>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ /**
198
+ * TODO: [🦚] Add download button functionality
199
+ * TODO: [🦚] Add image regeneration option for default-avatar
200
+ */
@@ -1,3 +1,4 @@
1
+ import { $provideServer } from '@/src/tools/$provideServer';
1
2
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
@@ -21,6 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
21
22
  const agentProfile = await getAgentProfile(agentName);
22
23
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
23
24
  const backgroundColor = agentColor.then(grayscale(0.5));
25
+ const { publicUrl } = await $provideServer();
24
26
 
25
27
  return new ImageResponse(
26
28
  (
@@ -48,10 +50,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
48
50
  <img
49
51
  style={{
50
52
  width: '80%',
51
- backgroundColor: agentColor.toHex(),
52
- borderRadius: '50%',
53
+ // backgroundColor: agentColor.toHex(),
53
54
  }}
54
- src={agentProfile.meta.image!}
55
+ src={`${publicUrl.href}agents/${agentProfile.permanentId || agentName}/images/icon-256.png`}
55
56
  alt="Agent Icon"
56
57
  />
57
58
  </div>
@@ -1,3 +1,4 @@
1
+ import { $provideServer } from '@/src/tools/$provideServer';
1
2
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
@@ -21,6 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
21
22
  const agentProfile = await getAgentProfile(agentName);
22
23
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
23
24
  const backgroundColor = agentColor.then(grayscale(0.5));
25
+ const { publicUrl } = await $provideServer();
24
26
 
25
27
  return new ImageResponse(
26
28
  (
@@ -48,10 +50,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
48
50
  <img
49
51
  style={{
50
52
  width: '80%',
51
- backgroundColor: agentColor.toHex(),
52
- borderRadius: '50%',
53
+ // backgroundColor: agentColor.toHex(),
53
54
  }}
54
- src={agentProfile.meta.image!}
55
+ src={`${publicUrl.href}agents/${agentProfile.permanentId || agentName}/images/icon-256.png`}
55
56
  alt="Agent Icon"
56
57
  />
57
58
  </div>
@@ -4,7 +4,7 @@ import { $getTableName } from '@/src/database/$getTableName';
4
4
  import { $provideSupabase } from '@/src/database/$provideSupabase';
5
5
  import { $provideServer } from '@/src/tools/$provideServer';
6
6
  import { isUserAdmin } from '@/src/utils/isUserAdmin';
7
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
7
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
8
8
  import { ArrowLeftIcon, BoxIcon, CodeIcon, GlobeIcon, ServerIcon, TerminalIcon } from 'lucide-react';
9
9
  import { headers } from 'next/headers';
10
10
  import Link from 'next/link';
@@ -25,6 +25,7 @@ export const generateMetadata = generateAgentMetadata;
25
25
 
26
26
  export default async function AgentIntegrationPage({ params }: { params: Promise<{ agentName: string }> }) {
27
27
  $sideEffect(headers());
28
+
28
29
  const agentName = await getAgentName(params);
29
30
  const isAdmin = await isUserAdmin();
30
31
 
@@ -170,7 +171,7 @@ export default async function AgentIntegrationPage({ params }: { params: Promise
170
171
  }
171
172
  `);
172
173
 
173
- const agentLinks = getAgentLinks(agentName);
174
+ const agentLinks = getAgentLinks(agentProfile.permanentId || agentName);
174
175
  const chatLink = agentLinks.find((l) => l.title === 'Chat with Agent')!;
175
176
  const websiteIntegrationLink = agentLinks.find((l) => l.title === 'Website Integration')!;
176
177
 
@@ -187,7 +188,13 @@ export default async function AgentIntegrationPage({ params }: { params: Promise
187
188
  {agentProfile.meta.image && (
188
189
  // eslint-disable-next-line @next/next/no-img-element
189
190
  <img
190
- src={agentProfile.meta.image as string}
191
+ src={
192
+ agentProfile.meta.image ||
193
+ generatePlaceholderAgentProfileImageUrl(
194
+ agentProfile.permanentId || agentName,
195
+ publicUrl,
196
+ )
197
+ }
191
198
  alt={agentProfile.meta.fullname || agentName}
192
199
  className="w-16 h-16 rounded-full object-cover border-2"
193
200
  style={{ borderColor: primaryColor }}
@@ -1,7 +1,7 @@
1
1
  'use server';
2
2
 
3
3
  import { $provideServer } from '@/src/tools/$provideServer';
4
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
4
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
5
5
  import { ArrowLeftIcon, CodeIcon, HomeIcon, LinkIcon, ShareIcon } from 'lucide-react';
6
6
  import { headers } from 'next/headers';
7
7
  import Link from 'next/link';
@@ -9,15 +9,16 @@ import { notFound } from 'next/navigation';
9
9
  import { Color } from '../../../../../../../src/utils/color/Color';
10
10
  import { withAlpha } from '../../../../../../../src/utils/color/operators/withAlpha';
11
11
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
12
+ import { getAgentName, getAgentProfile } from '../_utils';
12
13
  import { getAgentExternalLinks, getAgentLinks } from '../agentLinks';
13
14
  import { CopyField } from '../CopyField';
14
- import { getAgentName, getAgentProfile } from '../_utils';
15
15
  import { generateAgentMetadata } from '../generateAgentMetadata';
16
16
 
17
17
  export const generateMetadata = generateAgentMetadata;
18
18
 
19
19
  export default async function AgentLinksPage({ params }: { params: Promise<{ agentName: string }> }) {
20
20
  $sideEffect(headers());
21
+
21
22
  const agentName = await getAgentName(params);
22
23
 
23
24
  let agentProfile;
@@ -56,7 +57,13 @@ export default async function AgentLinksPage({ params }: { params: Promise<{ age
56
57
  {agentProfile.meta.image && (
57
58
  // eslint-disable-next-line @next/next/no-img-element
58
59
  <img
59
- src={agentProfile.meta.image as string}
60
+ src={
61
+ agentProfile.meta.image ||
62
+ generatePlaceholderAgentProfileImageUrl(
63
+ agentProfile.permanentId || agentName,
64
+ publicUrl,
65
+ )
66
+ }
60
67
  alt={agentProfile.meta.fullname || agentName}
61
68
  className="w-16 h-16 rounded-full object-cover border-2"
62
69
  style={{ borderColor: primaryColor }}
@@ -129,7 +136,7 @@ export default async function AgentLinksPage({ params }: { params: Promise<{ age
129
136
  Agent Resources
130
137
  </h2>
131
138
  <div className="grid md:grid-cols-2 gap-4">
132
- {getAgentLinks(agentName)
139
+ {getAgentLinks(agentProfile.permanentId || agentName)
133
140
  .filter((link) =>
134
141
  ['Chat with Agent', 'History & Feedback', 'Integration'].includes(link.title),
135
142
  )
@@ -1,4 +1,5 @@
1
- import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
1
+ import { $provideServer } from '@/src/tools/$provideServer';
2
+ import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
2
3
  import { serializeError } from '@promptbook-local/utils';
3
4
  import { ImageResponse } from 'next/og';
4
5
  import { assertsError } from '../../../../../../src/errors/assertsError';
@@ -24,6 +25,7 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
24
25
  const agentProfile = await getAgentProfile(agentName);
25
26
  const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
26
27
  const backgroundColor = agentColor.then(grayscale(0.5));
28
+ const { publicUrl } = await $provideServer();
27
29
 
28
30
  return new ImageResponse(
29
31
  (
@@ -53,8 +55,15 @@ export default async function Image({ params }: { params: Promise<{ agentName: s
53
55
  width: '80%',
54
56
  backgroundColor: agentColor.toHex(),
55
57
  borderRadius: '50%',
58
+ aspectRatio: '1 / 1',
56
59
  }}
57
- src={agentProfile.meta.image!}
60
+ src={
61
+ agentProfile.meta.image ||
62
+ generatePlaceholderAgentProfileImageUrl(
63
+ agentProfile.permanentId || agentName,
64
+ publicUrl,
65
+ )
66
+ }
58
67
  alt="Agent Icon"
59
68
  />
60
69
  </div>