@promptbook/cli 0.104.0-1 → 0.104.0-10

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 (199) 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/messages/MessagesClient.tsx +294 -0
  15. package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
  16. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
  17. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
  18. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
  19. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
  20. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
  21. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
  22. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
  23. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
  24. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
  25. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  26. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  27. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
  28. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
  30. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
  31. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
  32. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  33. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  34. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  35. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  36. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
  37. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -0
  38. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  39. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  40. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +10 -3
  41. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
  42. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
  43. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
  44. package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
  45. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
  46. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
  47. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +10 -3
  48. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
  49. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
  50. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
  51. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -0
  52. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  53. package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +13 -14
  54. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
  55. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
  56. package/apps/agents-server/src/app/api/agents/route.ts +28 -3
  57. package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
  58. package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
  59. package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
  60. package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
  61. package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
  62. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  63. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  64. package/apps/agents-server/src/app/api/images/[filename]/route.ts +107 -0
  65. package/apps/agents-server/src/app/api/messages/route.ts +102 -0
  66. package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
  67. package/apps/agents-server/src/app/api/upload/route.ts +128 -45
  68. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  69. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  70. package/apps/agents-server/src/app/globals.css +140 -33
  71. package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
  72. package/apps/agents-server/src/app/layout.tsx +27 -22
  73. package/apps/agents-server/src/app/page.tsx +54 -6
  74. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  75. package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
  76. package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
  77. package/apps/agents-server/src/app/security.txt/route.ts +1 -1
  78. package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
  79. package/apps/agents-server/src/app/swagger/page.tsx +14 -0
  80. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +41 -116
  81. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
  82. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
  83. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  84. package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
  85. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  86. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  87. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  88. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  89. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  90. package/apps/agents-server/src/components/Header/Header.tsx +114 -40
  91. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +145 -23
  92. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
  93. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
  94. package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
  95. package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
  96. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  97. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  98. package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +88 -0
  99. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  100. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  101. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  102. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  103. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
  104. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
  105. package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
  106. package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
  107. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
  108. package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
  109. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  110. package/apps/agents-server/src/database/migrate.ts +34 -1
  111. package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
  112. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
  113. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  114. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  115. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  116. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  117. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  118. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  119. package/apps/agents-server/src/database/migrations/2025-12-0402-message-table.sql +42 -0
  120. package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
  121. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  122. package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
  123. package/apps/agents-server/src/database/schema.ts +231 -4
  124. package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
  125. package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
  126. package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
  127. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
  128. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
  129. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
  130. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
  131. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
  132. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
  133. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
  134. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
  135. package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
  136. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  137. package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
  138. package/apps/agents-server/src/message-providers/index.ts +13 -0
  139. package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
  140. package/apps/agents-server/src/middleware.ts +19 -23
  141. package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
  142. package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
  143. package/apps/agents-server/src/utils/auth.ts +117 -17
  144. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  145. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  146. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  147. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  148. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
  149. package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
  150. package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
  151. package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
  152. package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
  153. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
  154. package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
  155. package/esm/index.es.js +2890 -2737
  156. package/esm/index.es.js.map +1 -1
  157. package/esm/typings/servers.d.ts +8 -0
  158. package/esm/typings/src/_packages/core.index.d.ts +2 -0
  159. package/esm/typings/src/_packages/types.index.d.ts +10 -2
  160. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
  161. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  162. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
  163. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  164. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  165. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  166. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  167. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  168. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +7 -11
  169. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +2 -2
  170. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  171. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +21 -11
  172. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +80 -14
  173. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  174. package/esm/typings/src/commitments/index.d.ts +2 -1
  175. package/esm/typings/src/llm-providers/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
  176. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  177. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  178. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  179. package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
  180. package/esm/typings/src/types/Message.d.ts +49 -0
  181. package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
  182. package/esm/typings/src/types/typeAliases.d.ts +23 -1
  183. package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
  184. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  185. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  186. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  187. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  188. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  189. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  190. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  191. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  192. package/esm/typings/src/version.d.ts +1 -1
  193. package/package.json +1 -1
  194. package/umd/index.umd.js +4018 -3865
  195. package/umd/index.umd.js.map +1 -1
  196. package/apps/agents-server/package-lock.json +0 -27
  197. package/apps/agents-server/public/fonts/download-font.js +0 -22
  198. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
  199. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
@@ -54,10 +54,7 @@ function getMessagePreview(message: unknown, maxLength = 120): string {
54
54
  }
55
55
 
56
56
  if (typeof message === 'object') {
57
- const content =
58
- (message as { content?: unknown }).content ??
59
- (message as { text?: unknown }).text ??
60
- message;
57
+ const content = (message as { content?: unknown }).content ?? (message as { text?: unknown }).text ?? message;
61
58
 
62
59
  let text: string;
63
60
 
@@ -303,11 +300,12 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
303
300
  const message = row.message as { role?: string; content?: string };
304
301
  const role = (message.role || 'USER').toUpperCase();
305
302
  return {
303
+ // channel: 'PROMPTBOOK_CHAT',
306
304
  id: String(row.id),
307
- from: role === 'USER' ? 'USER' : 'ASSISTANT',
305
+ sender: role === 'USER' ? 'USER' : 'ASSISTANT',
308
306
  content: message.content || JSON.stringify(message),
309
307
  isComplete: true,
310
- date: new Date(row.createdAt),
308
+ createdAt: new Date(row.createdAt),
311
309
  } satisfies ChatMessage;
312
310
  });
313
311
  }, [items, viewMode]);
@@ -317,17 +315,9 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
317
315
  <div>
318
316
  {total > 0 ? (
319
317
  <>
320
- Showing{' '}
321
- <span className="font-semibold">
322
- {Math.min((page - 1) * pageSize + 1, total)}
323
- </span>{' '}
324
- –{' '}
325
- <span className="font-semibold">
326
- {Math.min(page * pageSize, total)}
327
- </span>{' '}
328
- of{' '}
329
- <span className="font-semibold">{total}</span>{' '}
330
- messages
318
+ Showing <span className="font-semibold">{Math.min((page - 1) * pageSize + 1, total)}</span> –{' '}
319
+ <span className="font-semibold">{Math.min(page * pageSize, total)}</span> of{' '}
320
+ <span className="font-semibold">{total}</span> messages
331
321
  </>
332
322
  ) : (
333
323
  'No messages'
@@ -403,97 +393,90 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
403
393
  </a>
404
394
  </div>
405
395
  <div>
406
- <div className="text-xl font-semibold text-gray-900">
407
- {total.toLocaleString()}
408
- </div>
409
- <div className="text-xs uppercase tracking-wide text-gray-400">
410
- Total messages
411
- </div>
396
+ <div className="text-xl font-semibold text-gray-900">{total.toLocaleString()}</div>
397
+ <div className="text-xs uppercase tracking-wide text-gray-400">Total messages</div>
412
398
  </div>
413
399
  </div>
414
400
  </div>
415
401
  <Card>
416
- <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
417
- <form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
418
- <div className="flex flex-col gap-1">
419
- <label htmlFor="search" className="text-sm font-medium text-gray-700">
420
- Search
421
- </label>
422
- <input
423
- id="search"
424
- type="text"
425
- value={searchInput}
426
- onChange={(event) => setSearchInput(event.target.value)}
427
- placeholder="Search by agent name, URL or IP"
428
- className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
429
- />
430
- </div>
431
- <button
432
- type="submit"
433
- className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
402
+ <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
403
+ <form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
404
+ <div className="flex flex-col gap-1">
405
+ <label htmlFor="search" className="text-sm font-medium text-gray-700">
406
+ Search
407
+ </label>
408
+ <input
409
+ id="search"
410
+ type="text"
411
+ value={searchInput}
412
+ onChange={(event) => setSearchInput(event.target.value)}
413
+ placeholder="Search by agent name, URL or IP"
414
+ className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
415
+ />
416
+ </div>
417
+ <button
418
+ type="submit"
419
+ className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
420
+ >
421
+ Apply
422
+ </button>
423
+ </form>
424
+
425
+ <div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
426
+ <div className="flex flex-col gap-1">
427
+ <label htmlFor="agentFilter" className="text-sm font-medium text-gray-700">
428
+ Agent filter
429
+ </label>
430
+ <select
431
+ id="agentFilter"
432
+ value={agentName}
433
+ onChange={handleAgentChange}
434
+ className="w-full md:w-64 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
434
435
  >
435
- Apply
436
- </button>
437
- </form>
438
-
439
- <div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
440
- <div className="flex flex-col gap-1">
441
- <label htmlFor="agentFilter" className="text-sm font-medium text-gray-700">
442
- Agent filter
443
- </label>
444
- <select
445
- id="agentFilter"
446
- value={agentName}
447
- onChange={handleAgentChange}
448
- className="w-full md:w-64 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
449
- >
450
- <option value="">All agents</option>
451
- {agents.map((agent) => (
452
- <option key={agent.agentName} value={agent.agentName}>
453
- {agent.fullname || agent.agentName}
454
- </option>
455
- ))}
456
- </select>
457
- {agentsLoading && (
458
- <span className="text-xs text-gray-400">Loading agents…</span>
459
- )}
460
- </div>
461
-
462
- <div className="flex flex-col gap-1">
463
- <label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
464
- Page size
465
- </label>
466
- <select
467
- id="pageSize"
468
- value={pageSize}
469
- onChange={handlePageSizeChange}
470
- className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
471
- >
472
- <option value={10}>10</option>
473
- <option value={20}>20</option>
474
- <option value={50}>50</option>
475
- <option value={100}>100</option>
476
- </select>
477
- </div>
436
+ <option value="">All agents</option>
437
+ {agents.map((agent) => (
438
+ <option key={agent.agentName} value={agent.agentName}>
439
+ {agent.fullname || agent.agentName}
440
+ </option>
441
+ ))}
442
+ </select>
443
+ {agentsLoading && <span className="text-xs text-gray-400">Loading agents…</span>}
478
444
  </div>
479
- </div>
480
445
 
481
- {agentName && (
482
- <div className="mt-4 flex items-center justify-between gap-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3">
483
- <p className="text-sm text-amber-800">
484
- Showing chat history for agent{' '}
485
- <span className="font-semibold break-all">{agentName}</span>.
486
- </p>
487
- <button
488
- type="button"
489
- onClick={handleClearAgentHistory}
490
- className="inline-flex items-center justify-center rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50"
446
+ <div className="flex flex-col gap-1">
447
+ <label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
448
+ Page size
449
+ </label>
450
+ <select
451
+ id="pageSize"
452
+ value={pageSize}
453
+ onChange={handlePageSizeChange}
454
+ className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
491
455
  >
492
- Clear history for this agent
493
- </button>
456
+ <option value={10}>10</option>
457
+ <option value={20}>20</option>
458
+ <option value={50}>50</option>
459
+ <option value={100}>100</option>
460
+ </select>
494
461
  </div>
495
- )}
496
- </Card>
462
+ </div>
463
+ </div>
464
+
465
+ {agentName && (
466
+ <div className="mt-4 flex items-center justify-between gap-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3">
467
+ <p className="text-sm text-amber-800">
468
+ Showing chat history for agent <span className="font-semibold break-all">{agentName}</span>.
469
+ </p>
470
+ <button
471
+ type="button"
472
+ onClick={handleClearAgentHistory}
473
+ className="inline-flex items-center justify-center rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50"
474
+ >
475
+ Clear history for this agent
476
+ </button>
477
+ </div>
478
+ )}
479
+ </Card>
497
480
 
498
481
  {viewMode === 'chat' ? (
499
482
  <div className="bg-white rounded-lg shadow border border-gray-200 overflow-hidden flex flex-col">
@@ -505,22 +488,14 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
505
488
  isSaveButtonEnabled={true}
506
489
  />
507
490
  </div>
508
- <div className="p-4 bg-gray-50 border-t border-gray-200">
509
- {pagination}
510
- </div>
491
+ <div className="p-4 bg-gray-50 border-t border-gray-200">{pagination}</div>
511
492
  </div>
512
493
  ) : (
513
494
  <Card>
514
495
  <div className="flex items-center justify-between mb-4">
515
- <h2 className="text-lg font-medium text-gray-900">
516
- Messages ({total})
517
- </h2>
496
+ <h2 className="text-lg font-medium text-gray-900">Messages ({total})</h2>
518
497
  </div>
519
- {error && (
520
- <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">
521
- {error}
522
- </div>
523
- )}
498
+ {error && <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">{error}</div>}
524
499
 
525
500
  {loading && items.length === 0 ? (
526
501
  <div className="py-8 text-center text-gray-500">Loading chat history…</div>
@@ -555,27 +530,13 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
555
530
  )}
556
531
  </button>
557
532
  </th>
558
- <th className="px-4 py-3 text-left font-medium text-gray-500">
559
- Role
560
- </th>
561
- <th className="px-4 py-3 text-left font-medium text-gray-500">
562
- Message
563
- </th>
564
- <th className="px-4 py-3 text-left font-medium text-gray-500">
565
- URL
566
- </th>
567
- <th className="px-4 py-3 text-left font-medium text-gray-500">
568
- IP
569
- </th>
570
- <th className="px-4 py-3 text-left font-medium text-gray-500">
571
- Language
572
- </th>
573
- <th className="px-4 py-3 text-left font-medium text-gray-500">
574
- Platform
575
- </th>
576
- <th className="px-4 py-3 text-right font-medium text-gray-500">
577
- Actions
578
- </th>
533
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Role</th>
534
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Message</th>
535
+ <th className="px-4 py-3 text-left font-medium text-gray-500">URL</th>
536
+ <th className="px-4 py-3 text-left font-medium text-gray-500">IP</th>
537
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Language</th>
538
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Platform</th>
539
+ <th className="px-4 py-3 text-right font-medium text-gray-500">Actions</th>
579
540
  </tr>
580
541
  </thead>
581
542
  <tbody className="divide-y divide-gray-200 bg-white">
@@ -596,9 +557,7 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
596
557
  </div>
597
558
  </td>
598
559
  <td className="max-w-xs px-4 py-3 text-gray-500">
599
- <div className="truncate text-xs">
600
- {row.url || '-'}
601
- </div>
560
+ <div className="truncate text-xs">{row.url || '-'}</div>
602
561
  </td>
603
562
  <td className="whitespace-nowrap px-4 py-3 text-gray-500">
604
563
  {row.ip || '-'}
@@ -607,9 +566,7 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
607
566
  {row.language || '-'}
608
567
  </td>
609
568
  <td className="max-w-xs px-4 py-3 text-gray-500">
610
- <div className="truncate text-xs">
611
- {row.platform || '-'}
612
- </div>
569
+ <div className="truncate text-xs">{row.platform || '-'}</div>
613
570
  </td>
614
571
  <td className="whitespace-nowrap px-4 py-3 text-right text-xs font-medium">
615
572
  <button
@@ -0,0 +1,294 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { Card } from '../../../components/Homepage/Card';
5
+ import {
6
+ $fetchMessages,
7
+ type MessageRow,
8
+ type MessageSendAttemptRow,
9
+ } from '../../../utils/messagesAdmin';
10
+
11
+ function formatDate(dateString: string | null | undefined): string {
12
+ if (!dateString) return '-';
13
+ const date = new Date(dateString);
14
+ if (Number.isNaN(date.getTime())) return dateString;
15
+ return date.toLocaleString();
16
+ }
17
+
18
+ function getMessagePreview(content: string, maxLength = 120): string {
19
+ if (!content) return '-';
20
+ return content.length > maxLength ? `${content.slice(0, maxLength)}…` : content;
21
+ }
22
+
23
+ function getStatusBadge(attempts: MessageSendAttemptRow[] | undefined) {
24
+ if (!attempts || attempts.length === 0) {
25
+ return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">Pending</span>;
26
+ }
27
+ const success = attempts.find(a => a.isSuccessful);
28
+ if (success) {
29
+ return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Sent ({success.providerName})</span>;
30
+ }
31
+ return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800">Failed ({attempts.length})</span>;
32
+ }
33
+
34
+ export function MessagesClient() {
35
+ const [items, setItems] = useState<MessageRow[]>([]);
36
+ const [total, setTotal] = useState(0);
37
+ const [page, setPage] = useState(1);
38
+ const [pageSize, setPageSize] = useState(20);
39
+ const [searchInput, setSearchInput] = useState('');
40
+ const [search, setSearch] = useState('');
41
+ const [channel, setChannel] = useState('');
42
+ const [direction, setDirection] = useState('');
43
+ const [loading, setLoading] = useState(true);
44
+ const [error, setError] = useState<string | null>(null);
45
+
46
+ // Load messages whenever filters / pagination change
47
+ useEffect(() => {
48
+ let isCancelled = false;
49
+
50
+ async function loadData() {
51
+ try {
52
+ setLoading(true);
53
+ setError(null);
54
+
55
+ const response = await $fetchMessages({
56
+ page,
57
+ pageSize,
58
+ search: search || undefined,
59
+ channel: channel || undefined,
60
+ direction: direction || undefined,
61
+ });
62
+
63
+ if (isCancelled) return;
64
+
65
+ setItems(response.items);
66
+ setTotal(response.total);
67
+ } catch (err) {
68
+ if (isCancelled) return;
69
+ setError(err instanceof Error ? err.message : 'Failed to load messages');
70
+ } finally {
71
+ if (!isCancelled) {
72
+ setLoading(false);
73
+ }
74
+ }
75
+ }
76
+
77
+ loadData();
78
+
79
+ return () => {
80
+ isCancelled = true;
81
+ };
82
+ }, [page, pageSize, search, channel, direction]);
83
+
84
+ const totalPages = useMemo(() => {
85
+ if (total <= 0 || pageSize <= 0) return 1;
86
+ return Math.max(1, Math.ceil(total / pageSize));
87
+ }, [total, pageSize]);
88
+
89
+ const handleSearchSubmit = (event: React.FormEvent) => {
90
+ event.preventDefault();
91
+ setPage(1);
92
+ setSearch(searchInput.trim());
93
+ };
94
+
95
+ const handlePageSizeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
96
+ const next = parseInt(event.target.value, 10);
97
+ if (!Number.isNaN(next) && next > 0) {
98
+ setPageSize(next);
99
+ setPage(1);
100
+ }
101
+ };
102
+
103
+ const pagination = (
104
+ <div className="mt-4 flex flex-col items-center justify-between gap-3 border-t border-gray-100 pt-4 text-xs text-gray-600 md:flex-row">
105
+ <div>
106
+ {total > 0 ? (
107
+ <>
108
+ Showing <span className="font-semibold">{Math.min((page - 1) * pageSize + 1, total)}</span> –{' '}
109
+ <span className="font-semibold">{Math.min(page * pageSize, total)}</span> of{' '}
110
+ <span className="font-semibold">{total}</span> messages
111
+ </>
112
+ ) : (
113
+ 'No messages'
114
+ )}
115
+ </div>
116
+ <div className="flex items-center gap-2">
117
+ <button
118
+ type="button"
119
+ onClick={() => setPage((prev) => Math.max(1, prev - 1))}
120
+ disabled={page <= 1}
121
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
122
+ >
123
+ Previous
124
+ </button>
125
+ <span>
126
+ Page <span className="font-semibold">{page}</span> of{' '}
127
+ <span className="font-semibold">{totalPages}</span>
128
+ </span>
129
+ <button
130
+ type="button"
131
+ onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
132
+ disabled={page >= totalPages}
133
+ className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
134
+ >
135
+ Next
136
+ </button>
137
+ </div>
138
+ </div>
139
+ );
140
+
141
+ return (
142
+ <div className="container mx-auto px-4 py-8 space-y-6">
143
+ <div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
144
+ <div>
145
+ <h1 className="text-3xl text-gray-900 font-light">Messages</h1>
146
+ <p className="mt-1 text-sm text-gray-500">
147
+ Inspect all inbound and outbound messages and their statuses.
148
+ </p>
149
+ </div>
150
+ <div className="flex items-end gap-4 text-sm text-gray-500 md:text-right">
151
+ <div>
152
+ <div className="text-xl font-semibold text-gray-900">{total.toLocaleString()}</div>
153
+ <div className="text-xs uppercase tracking-wide text-gray-400">Total messages</div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ <Card>
158
+ <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
159
+ <form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
160
+ <div className="flex flex-col gap-1">
161
+ <label htmlFor="search" className="text-sm font-medium text-gray-700">
162
+ Search
163
+ </label>
164
+ <input
165
+ id="search"
166
+ type="text"
167
+ value={searchInput}
168
+ onChange={(event) => setSearchInput(event.target.value)}
169
+ placeholder="Search in content..."
170
+ className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
171
+ />
172
+ </div>
173
+ <button
174
+ type="submit"
175
+ className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
176
+ >
177
+ Apply
178
+ </button>
179
+ </form>
180
+
181
+ <div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
182
+ <div className="flex flex-col gap-1">
183
+ <label htmlFor="channelFilter" className="text-sm font-medium text-gray-700">
184
+ Channel
185
+ </label>
186
+ <select
187
+ id="channelFilter"
188
+ value={channel}
189
+ onChange={(e) => { setChannel(e.target.value); setPage(1); }}
190
+ className="w-full md:w-40 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
191
+ >
192
+ <option value="">All</option>
193
+ <option value="EMAIL">Email</option>
194
+ <option value="PROMPTBOOK_CHAT">Chat</option>
195
+ </select>
196
+ </div>
197
+
198
+ <div className="flex flex-col gap-1">
199
+ <label htmlFor="directionFilter" className="text-sm font-medium text-gray-700">
200
+ Direction
201
+ </label>
202
+ <select
203
+ id="directionFilter"
204
+ value={direction}
205
+ onChange={(e) => { setDirection(e.target.value); setPage(1); }}
206
+ className="w-full md:w-40 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
207
+ >
208
+ <option value="">All</option>
209
+ <option value="INBOUND">Inbound</option>
210
+ <option value="OUTBOUND">Outbound</option>
211
+ </select>
212
+ </div>
213
+
214
+ <div className="flex flex-col gap-1">
215
+ <label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
216
+ Page size
217
+ </label>
218
+ <select
219
+ id="pageSize"
220
+ value={pageSize}
221
+ onChange={handlePageSizeChange}
222
+ className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
223
+ >
224
+ <option value={10}>10</option>
225
+ <option value={20}>20</option>
226
+ <option value={50}>50</option>
227
+ <option value={100}>100</option>
228
+ </select>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </Card>
233
+
234
+ <Card>
235
+ <div className="flex items-center justify-between mb-4">
236
+ <h2 className="text-lg font-medium text-gray-900">Message list</h2>
237
+ </div>
238
+ {error && <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">{error}</div>}
239
+
240
+ {loading && items.length === 0 ? (
241
+ <div className="py-8 text-center text-gray-500">Loading messages…</div>
242
+ ) : items.length === 0 ? (
243
+ <div className="py-8 text-center text-gray-500">No messages found.</div>
244
+ ) : (
245
+ <div className="overflow-x-auto">
246
+ <table className="min-w-full divide-y divide-gray-200 text-sm">
247
+ <thead className="bg-gray-50">
248
+ <tr>
249
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Time</th>
250
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Channel</th>
251
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Direction</th>
252
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Sender/Recipients</th>
253
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Content</th>
254
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Status</th>
255
+ </tr>
256
+ </thead>
257
+ <tbody className="divide-y divide-gray-200 bg-white">
258
+ {items.map((row) => (
259
+ <tr key={row.id}>
260
+ <td className="whitespace-nowrap px-4 py-3 text-gray-700">
261
+ {formatDate(row.createdAt)}
262
+ </td>
263
+ <td className="whitespace-nowrap px-4 py-3 text-gray-700">
264
+ {row.channel}
265
+ </td>
266
+ <td className="whitespace-nowrap px-4 py-3 text-gray-700">
267
+ {row.direction}
268
+ </td>
269
+ <td className="max-w-xs px-4 py-3 text-gray-700">
270
+ <div className="truncate" title={JSON.stringify({ sender: row.sender, recipients: row.recipients }, null, 2)}>
271
+ {/* Simple preview of sender/recipients */}
272
+ S: {JSON.stringify(row.sender)}<br/>
273
+ R: {JSON.stringify(row.recipients)}
274
+ </div>
275
+ </td>
276
+ <td className="max-w-md px-4 py-3 text-gray-700">
277
+ <div className="max-h-24 overflow-hidden overflow-ellipsis text-xs leading-snug">
278
+ {getMessagePreview(row.content)}
279
+ </div>
280
+ </td>
281
+ <td className="whitespace-nowrap px-4 py-3 text-gray-700">
282
+ {getStatusBadge(row.sendAttempts)}
283
+ </td>
284
+ </tr>
285
+ ))}
286
+ </tbody>
287
+ </table>
288
+ </div>
289
+ )}
290
+ {pagination}
291
+ </Card>
292
+ </div>
293
+ );
294
+ }
@@ -0,0 +1,13 @@
1
+ import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
2
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
3
+ import { MessagesClient } from './MessagesClient';
4
+
5
+ export default async function AdminMessagesPage() {
6
+ const isAdmin = await isUserAdmin();
7
+
8
+ if (!isAdmin) {
9
+ return <ForbiddenPage />;
10
+ }
11
+
12
+ return <MessagesClient />;
13
+ }