@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,169 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Card } from '../../../components/Homepage/Card';
5
+
6
+ export function ImageGeneratorTestClient() {
7
+ const [prompt, setPrompt] = useState<string>('');
8
+ const [modelName, setModelName] = useState<string>('dall-e-3');
9
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
10
+ const [rawResult, setRawResult] = useState<unknown | null>(null);
11
+ const [isLoading, setIsLoading] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+ const [generatedFilename, setGeneratedFilename] = useState<string | null>(null);
14
+
15
+ const handleGenerateImage = () => {
16
+ if (!prompt) return;
17
+
18
+ setIsLoading(true);
19
+ setError(null);
20
+ setImageUrl(null);
21
+ setRawResult(null);
22
+ setGeneratedFilename(null);
23
+
24
+ try {
25
+ // detailed-painting-of-a-cute-cat.png
26
+ const filename = prompt
27
+ .trim()
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9]+/g, '-')
30
+ .replace(/^-+|-+$/g, '') + '.png';
31
+
32
+ setGeneratedFilename(filename);
33
+
34
+ const queryParams = new URLSearchParams();
35
+ if (modelName) queryParams.set('modelName', modelName);
36
+ queryParams.set('raw', 'true');
37
+
38
+ fetch(`/api/images/${filename}?${queryParams.toString()}`)
39
+ .then(async (response) => {
40
+ const contentType = response.headers.get('content-type');
41
+
42
+ if (!response.ok) {
43
+ const text = await response.text();
44
+ let errorMessage;
45
+ try {
46
+ const json = JSON.parse(text);
47
+ errorMessage = json.error || response.statusText;
48
+ } catch {
49
+ errorMessage = text || response.statusText;
50
+ }
51
+ throw new Error(`Error: ${response.status} ${errorMessage}`);
52
+ }
53
+
54
+ if (contentType && contentType.includes('application/json')) {
55
+ const data = await response.json();
56
+ setRawResult(data);
57
+ if (data.cdnUrl) {
58
+ setImageUrl(data.cdnUrl);
59
+ }
60
+ } else {
61
+ // Fallback if it returns blob/image directly (shouldn't with raw=true)
62
+ const blob = await response.blob();
63
+ const url = URL.createObjectURL(blob);
64
+ setImageUrl(url);
65
+ }
66
+ })
67
+ .catch((err) => {
68
+ setError(String(err));
69
+ })
70
+ .finally(() => {
71
+ setIsLoading(false);
72
+ });
73
+
74
+ } catch (err) {
75
+ setError(String(err));
76
+ setIsLoading(false);
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="container mx-auto px-4 py-8 space-y-6">
82
+ <div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
83
+ <div>
84
+ <h1 className="text-3xl text-gray-900 font-light">Image Generator Test</h1>
85
+ <p className="mt-1 text-sm text-gray-500">
86
+ Test the image generation capabilities by providing a prompt.
87
+ </p>
88
+ </div>
89
+ </div>
90
+
91
+ <Card>
92
+ <div className="mb-4 space-y-4">
93
+ <div className="space-y-2">
94
+ <label className="block text-sm font-medium text-gray-700">Image Prompt</label>
95
+ <input
96
+ type="text"
97
+ value={prompt}
98
+ onChange={(e) => setPrompt(e.target.value)}
99
+ placeholder="e.g., A futuristic city with flying cars"
100
+ className="w-full p-2 border border-gray-300 rounded"
101
+ disabled={isLoading}
102
+ onKeyDown={(e) => {
103
+ if (e.key === 'Enter') {
104
+ handleGenerateImage();
105
+ }
106
+ }}
107
+ />
108
+ </div>
109
+
110
+ <div className="space-y-2">
111
+ <label className="block text-sm font-medium text-gray-700">Model Name</label>
112
+ <input
113
+ type="text"
114
+ value={modelName}
115
+ onChange={(e) => setModelName(e.target.value)}
116
+ placeholder="e.g., dall-e-3"
117
+ className="w-full p-2 border border-gray-300 rounded"
118
+ disabled={isLoading}
119
+ />
120
+ <p className="text-xs text-gray-500">
121
+ Available models depend on the configured LLM provider. Common options: dall-e-3, dall-e-2, midjourney
122
+ </p>
123
+ </div>
124
+
125
+ <div className="flex justify-end">
126
+ <button
127
+ onClick={handleGenerateImage}
128
+ disabled={isLoading || !prompt}
129
+ className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
130
+ >
131
+ {isLoading ? 'Generating...' : 'Generate Image'}
132
+ </button>
133
+ </div>
134
+
135
+ {generatedFilename && (
136
+ <p className="text-xs text-gray-500">
137
+ Generated filename: <code className="bg-gray-100 px-1 rounded">{generatedFilename}</code>
138
+ </p>
139
+ )}
140
+ </div>
141
+
142
+ {error && (
143
+ <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4" role="alert">
144
+ <strong className="font-bold">Error: </strong>
145
+ <span className="block sm:inline">{error}</span>
146
+ </div>
147
+ )}
148
+
149
+ {imageUrl && (
150
+ <div className="mb-6 border rounded shadow-lg overflow-hidden bg-gray-50 flex justify-center items-center min-h-[200px]">
151
+ {/* eslint-disable-next-line @next/next/no-img-element */}
152
+ <img src={imageUrl} alt={prompt} className="max-w-full h-auto" />
153
+ </div>
154
+ )}
155
+
156
+ {rawResult !== null && (
157
+ <div className="border rounded-md overflow-hidden">
158
+ <div className="bg-gray-100 px-4 py-2 border-b">
159
+ <h3 className="text-sm font-semibold text-gray-700">Raw Result</h3>
160
+ </div>
161
+ <pre className="p-4 bg-gray-50 text-xs overflow-auto max-h-[500px]">
162
+ {JSON.stringify(rawResult, null, 2)}
163
+ </pre>
164
+ </div>
165
+ )}
166
+ </Card>
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,13 @@
1
+ import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
2
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
3
+ import { ImageGeneratorTestClient } from './ImageGeneratorTestClient';
4
+
5
+ export default async function ImageGeneratorTestPage() {
6
+ const isAdmin = await isUserAdmin();
7
+
8
+ if (!isAdmin) {
9
+ return <ForbiddenPage />;
10
+ }
11
+
12
+ return <ImageGeneratorTestClient />;
13
+ }
@@ -0,0 +1,256 @@
1
+ 'use client';
2
+
3
+ import { ChevronLeft, ChevronRight, Grid, LayoutList, Loader2 } from 'lucide-react';
4
+ import Link from 'next/link';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { ImageWithAgent, listImages } from './actions';
7
+
8
+ type ViewMode = 'TABLE' | 'GRID';
9
+
10
+ export function ImagesGalleryClient() {
11
+ const [viewMode, setViewMode] = useState<ViewMode>('GRID');
12
+ const [images, setImages] = useState<ImageWithAgent[]>([]);
13
+ const [total, setTotal] = useState(0);
14
+ const [isLoading, setIsLoading] = useState(false);
15
+ const [page, setPage] = useState(1);
16
+ const [limit] = useState(20);
17
+ const [hasMore, setHasMore] = useState(true);
18
+
19
+ const loadImages = async (pageNum: number, isNewView: boolean) => {
20
+ setIsLoading(true);
21
+ try {
22
+ const result = await listImages({ page: pageNum, limit });
23
+ if (isNewView) {
24
+ setImages(result.images);
25
+ } else {
26
+ setImages((prev) => [...prev, ...result.images]);
27
+ }
28
+ setTotal(result.total);
29
+ setHasMore(result.images.length === limit);
30
+ } catch (error) {
31
+ console.error(error);
32
+ } finally {
33
+ setIsLoading(false);
34
+ }
35
+ };
36
+
37
+ useEffect(() => {
38
+ loadImages(1, true);
39
+ setPage(1);
40
+ setHasMore(true);
41
+ }, [viewMode]);
42
+
43
+ const handleLoadMore = () => {
44
+ if (!isLoading && hasMore) {
45
+ const nextPage = page + 1;
46
+ setPage(nextPage);
47
+ loadImages(nextPage, false);
48
+ }
49
+ };
50
+
51
+ // Table view pagination
52
+ const handlePageChange = (newPage: number) => {
53
+ setPage(newPage);
54
+ loadImages(newPage, true);
55
+ };
56
+
57
+ const observerTarget = useRef<HTMLDivElement>(null);
58
+
59
+ useEffect(() => {
60
+ const observer = new IntersectionObserver(
61
+ (entries) => {
62
+ if (entries[0].isIntersecting && hasMore && !isLoading && viewMode === 'GRID') {
63
+ handleLoadMore();
64
+ }
65
+ },
66
+ { threshold: 0.1 }
67
+ );
68
+
69
+ if (observerTarget.current) {
70
+ observer.observe(observerTarget.current);
71
+ }
72
+
73
+ return () => {
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ if (observerTarget.current) {
76
+ observer.unobserve(observerTarget.current);
77
+ }
78
+ };
79
+ }, [hasMore, isLoading, viewMode, page]);
80
+
81
+ return (
82
+ <div className="container mx-auto px-4 py-8 space-y-6">
83
+ <div className="flex justify-between items-center mt-20 mb-4">
84
+ <h1 className="text-3xl text-gray-900 font-light">Images Gallery</h1>
85
+ <div className="flex items-center gap-2 bg-gray-100 p-1 rounded-lg">
86
+ <button
87
+ onClick={() => setViewMode('TABLE')}
88
+ className={`p-2 rounded-md transition-colors ${
89
+ viewMode === 'TABLE' ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500 hover:text-gray-900'
90
+ }`}
91
+ title="Table View"
92
+ >
93
+ <LayoutList className="w-5 h-5" />
94
+ </button>
95
+ <button
96
+ onClick={() => setViewMode('GRID')}
97
+ className={`p-2 rounded-md transition-colors ${
98
+ viewMode === 'GRID' ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500 hover:text-gray-900'
99
+ }`}
100
+ title="Grid View"
101
+ >
102
+ <Grid className="w-5 h-5" />
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ {viewMode === 'TABLE' ? (
108
+ <div className="bg-white border rounded-lg overflow-hidden shadow-sm">
109
+ <div className="overflow-x-auto">
110
+ <table className="w-full text-sm text-left text-gray-500">
111
+ <thead className="text-xs text-gray-700 uppercase bg-gray-50 border-b">
112
+ <tr>
113
+ <th className="px-6 py-3 w-32">Image</th>
114
+ <th className="px-6 py-3">Prompt</th>
115
+ <th className="px-6 py-3">Agent</th>
116
+ <th className="px-6 py-3">Purpose</th>
117
+ <th className="px-6 py-3">Created At</th>
118
+ </tr>
119
+ </thead>
120
+ <tbody>
121
+ {images.map((image) => (
122
+ <tr key={image.id} className="bg-white border-b hover:bg-gray-50">
123
+ <td className="px-6 py-4">
124
+ <a href={image.cdnUrl} target="_blank" rel="noopener noreferrer" className="block w-20 h-20 relative rounded overflow-hidden border bg-gray-100">
125
+ {/* eslint-disable-next-line @next/next/no-img-element */}
126
+ <img src={image.cdnUrl} alt={image.prompt} className="object-cover w-full h-full" />
127
+ </a>
128
+ </td>
129
+ <td className="px-6 py-4">
130
+ <div className="max-w-md truncate" title={image.prompt}>
131
+ {image.prompt}
132
+ </div>
133
+ <div className="text-xs text-gray-400 mt-1">{image.filename}</div>
134
+ </td>
135
+ <td className="px-6 py-4">
136
+ {image.agent ? (
137
+ <Link href={`/${image.agent.agentName}`} className="text-blue-600 hover:underline">
138
+ {image.agent.agentName}
139
+ </Link>
140
+ ) : (
141
+ <span className="text-gray-400">-</span>
142
+ )}
143
+ </td>
144
+ <td className="px-6 py-4">
145
+ {image.purpose ? (
146
+ <span className={`px-2 py-1 rounded-full text-xs font-medium ${
147
+ image.purpose === 'AVATAR' ? 'bg-purple-100 text-purple-800' : 'bg-yellow-100 text-yellow-800'
148
+ }`}>
149
+ {image.purpose}
150
+ </span>
151
+ ) : (
152
+ <span className="text-gray-400">-</span>
153
+ )}
154
+ </td>
155
+ <td className="px-6 py-4 whitespace-nowrap">
156
+ {new Date(image.createdAt).toLocaleString()}
157
+ </td>
158
+ </tr>
159
+ ))}
160
+ {images.length === 0 && !isLoading && (
161
+ <tr>
162
+ <td colSpan={5} className="px-6 py-4 text-center text-gray-500">
163
+ No images found.
164
+ </td>
165
+ </tr>
166
+ )}
167
+ </tbody>
168
+ </table>
169
+ </div>
170
+ {/* Pagination for Table */}
171
+ <div className="flex items-center justify-between px-6 py-4 border-t bg-gray-50">
172
+ <span className="text-sm text-gray-700">
173
+ Showing <span className="font-medium">{(page - 1) * limit + 1}</span> to <span className="font-medium">{Math.min(page * limit, total)}</span> of <span className="font-medium">{total}</span> results
174
+ </span>
175
+ <div className="flex gap-2">
176
+ <button
177
+ onClick={() => handlePageChange(page - 1)}
178
+ disabled={page === 1 || isLoading}
179
+ className="p-2 border rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
180
+ >
181
+ <ChevronLeft className="w-4 h-4" />
182
+ </button>
183
+ <button
184
+ onClick={() => handlePageChange(page + 1)}
185
+ disabled={page * limit >= total || isLoading}
186
+ className="p-2 border rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
187
+ >
188
+ <ChevronRight className="w-4 h-4" />
189
+ </button>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ ) : (
194
+ <>
195
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
196
+ {images.map((image) => (
197
+ <div key={image.id} className="group relative border rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow">
198
+ <a href={image.cdnUrl} target="_blank" rel="noopener noreferrer" className="block aspect-square relative bg-gray-100 overflow-hidden">
199
+ {/* eslint-disable-next-line @next/next/no-img-element */}
200
+ <img
201
+ src={image.cdnUrl}
202
+ alt={image.prompt}
203
+ className="object-cover w-full h-full transition-transform duration-300 group-hover:scale-105"
204
+ loading="lazy"
205
+ />
206
+ {image.purpose && (
207
+ <div className="absolute top-2 right-2">
208
+ <span className={`px-2 py-0.5 rounded-full text-[10px] font-bold shadow-sm ${
209
+ image.purpose === 'AVATAR' ? 'bg-purple-100 text-purple-800' : 'bg-yellow-100 text-yellow-800'
210
+ }`}>
211
+ {image.purpose}
212
+ </span>
213
+ </div>
214
+ )}
215
+ </a>
216
+ <div className="p-3">
217
+ <div className="flex items-center justify-between gap-2 mb-1">
218
+ {image.agent ? (
219
+ <Link href={`/${image.agent.agentName}`} className="text-xs font-medium text-blue-600 hover:underline truncate">
220
+ {image.agent.agentName}
221
+ </Link>
222
+ ) : (
223
+ <span className="text-xs text-gray-400">No agent</span>
224
+ )}
225
+ <span className="text-[10px] text-gray-400 whitespace-nowrap">
226
+ {new Date(image.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}
227
+ </span>
228
+ </div>
229
+ <p className="text-xs text-gray-600 line-clamp-2" title={image.prompt}>
230
+ {image.prompt}
231
+ </p>
232
+ </div>
233
+ </div>
234
+ ))}
235
+ </div>
236
+
237
+ {images.length === 0 && !isLoading && (
238
+ <div className="text-center text-gray-500 py-12">
239
+ No images found.
240
+ </div>
241
+ )}
242
+
243
+ {/* Infinite Scroll Loader */}
244
+ <div className="py-8 flex justify-center" ref={observerTarget}>
245
+ {isLoading && (
246
+ <Loader2 className="w-8 h-8 animate-spin text-blue-500" />
247
+ )}
248
+ {!isLoading && !hasMore && images.length > 0 && (
249
+ <p className="text-gray-400 text-sm">No more images</p>
250
+ )}
251
+ </div>
252
+ </>
253
+ )}
254
+ </div>
255
+ );
256
+ }
@@ -0,0 +1,60 @@
1
+ 'use server';
2
+
3
+ import { $getTableName } from '@/src/database/$getTableName';
4
+ import { TODO_any } from '@promptbook-local/types';
5
+ import { $provideSupabaseForServer } from '../../../database/$provideSupabaseForServer';
6
+
7
+ export type ImageWithAgent = {
8
+ id: number;
9
+ createdAt: string;
10
+ updatedAt: string;
11
+ filename: string;
12
+ prompt: string;
13
+ cdnUrl: string;
14
+ cdnKey: string;
15
+ agentId: number | null;
16
+ purpose: 'AVATAR' | 'TESTING' | null;
17
+ agent: {
18
+ id: number;
19
+ agentName: string;
20
+ } | null;
21
+ };
22
+
23
+ export async function listImages(options: {
24
+ page: number;
25
+ limit: number;
26
+ }): Promise<{ images: ImageWithAgent[]; total: number }> {
27
+ const { page, limit } = options;
28
+ const offset = (page - 1) * limit;
29
+
30
+ const supabase = $provideSupabaseForServer();
31
+
32
+ const {
33
+ data: images,
34
+ error,
35
+ count,
36
+ } = await supabase
37
+ .from(await $getTableName('Image'))
38
+ .select(
39
+ `
40
+ *,
41
+ agent:agentId (
42
+ id,
43
+ agentName
44
+ )
45
+ `,
46
+ { count: 'exact' },
47
+ )
48
+ .range(offset, offset + limit - 1)
49
+ .order('createdAt', { ascending: false });
50
+
51
+ if (error) {
52
+ console.error('Error fetching images:', error);
53
+ throw new Error(error.message);
54
+ }
55
+
56
+ return {
57
+ images: (images as TODO_any[]) || [],
58
+ total: count || 0,
59
+ };
60
+ }
@@ -0,0 +1,13 @@
1
+ import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
2
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
3
+ import { ImagesGalleryClient } from './ImagesGalleryClient';
4
+
5
+ export default async function ImagesGalleryPage() {
6
+ const isAdmin = await isUserAdmin();
7
+
8
+ if (!isAdmin) {
9
+ return <ForbiddenPage />;
10
+ }
11
+
12
+ return <ImagesGalleryClient />;
13
+ }