@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
@@ -2,10 +2,11 @@ import { $getTableName } from '@/src/database/$getTableName';
2
2
  import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
3
3
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
4
  import { $provideOpenAiAssistantExecutionToolsForServer } from '@/src/tools/$provideOpenAiAssistantExecutionToolsForServer';
5
- import { Agent, computeAgentHash, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
5
+ import { Agent, computeAgentHash, parseAgentSource, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
6
6
  import { ChatMessage, ChatPromptResult, Prompt, string_book, TODO_any } from '@promptbook-local/types';
7
7
  import { computeHash } from '@promptbook-local/utils';
8
8
  import { NextRequest, NextResponse } from 'next/server';
9
+ import { isAgentDeleted } from '../app/agents/[agentName]/_utils';
9
10
  import { validateApiKey } from './validateApiKey';
10
11
 
11
12
  export async function handleChatCompletion(
@@ -60,6 +61,19 @@ export async function handleChatCompletion(
60
61
  );
61
62
  }
62
63
 
64
+ // Check if agent is deleted
65
+ if (await isAgentDeleted(agentName)) {
66
+ return NextResponse.json(
67
+ {
68
+ error: {
69
+ message: 'This agent has been deleted. You can restore it from the Recycle Bin.',
70
+ type: 'agent_deleted',
71
+ },
72
+ },
73
+ { status: 410 }, // Gone - indicates the resource is no longer available
74
+ );
75
+ }
76
+
63
77
  const collection = await $provideAgentCollectionForServer();
64
78
  let agentSource: string_book;
65
79
  try {
@@ -99,7 +113,54 @@ export async function handleChatCompletion(
99
113
  );
100
114
  }
101
115
 
102
- const openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
116
+ const agentHash = computeAgentHash(agentSource);
117
+ const supabase = $provideSupabaseForServer();
118
+ const { data: assistantCache } = await supabase
119
+ .from(await $getTableName('OpenAiAssistantCache'))
120
+ .select('assistantId')
121
+ .eq('agentHash', agentHash)
122
+ .single();
123
+
124
+ let openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
125
+
126
+ if (assistantCache?.assistantId) {
127
+ console.log(
128
+ `[🐱‍🚀] Reusing assistant ${assistantCache.assistantId} for agent ${agentName} (hash: ${agentHash})`,
129
+ );
130
+ openAiAssistantExecutionTools = openAiAssistantExecutionTools.getAssistant(assistantCache.assistantId);
131
+ } else {
132
+ console.log(`[🐱‍🚀] Creating NEW assistant for agent ${agentName} (hash: ${agentHash})`);
133
+ // Parse to get instructions and name
134
+ const parsed = parseAgentSource(agentSource);
135
+ const name = parsed.agentName || agentName;
136
+ // Extract PERSONA
137
+ const baseInstructions = parsed.personaDescription || 'You are a helpful assistant.';
138
+
139
+ // Note: Append context to instructions
140
+ const contextLines = agentSource.split('\n').filter((line) => line.startsWith('CONTEXT '));
141
+ const contextInstructions = contextLines.join('\n');
142
+ const instructions = contextInstructions
143
+ ? `${baseInstructions}\n\n${contextInstructions}`
144
+ : baseInstructions;
145
+
146
+ // Create assistant
147
+ const newAssistantTools = await openAiAssistantExecutionTools.createNewAssistant({
148
+ name,
149
+ instructions,
150
+ // knowledgeSources?
151
+ });
152
+
153
+ // Save to cache
154
+ const newAssistantId = newAssistantTools.assistantId;
155
+ if (newAssistantId) {
156
+ await supabase.from(await $getTableName('OpenAiAssistantCache')).insert({
157
+ agentHash,
158
+ assistantId: newAssistantId,
159
+ });
160
+ openAiAssistantExecutionTools = newAssistantTools;
161
+ }
162
+ }
163
+
103
164
  const agent = new Agent({
104
165
  agentSource,
105
166
  executionTools: {
@@ -108,7 +169,6 @@ export async function handleChatCompletion(
108
169
  isVerbose: true, // or false
109
170
  });
110
171
 
111
- const agentHash = computeAgentHash(agentSource);
112
172
  const userAgent = request.headers.get('user-agent');
113
173
  const ip =
114
174
  request.headers.get('x-forwarded-for') ||
@@ -125,8 +185,9 @@ export async function handleChatCompletion(
125
185
  const previousMessages = threadMessages.slice(0, -1);
126
186
 
127
187
  const thread: ChatMessage[] = previousMessages.map((msg: TODO_any, index: number) => ({
188
+ // channel: 'PROMPTBOOK_CHAT',
128
189
  id: `msg-${index}`, // Placeholder ID
129
- from: msg.role === 'assistant' ? 'agent' : 'user', // Mapping standard OpenAI roles
190
+ sender: msg.role === 'assistant' ? 'agent' : 'user', // Mapping standard OpenAI roles
130
191
  content: msg.content,
131
192
  isComplete: true,
132
193
  date: new Date(), // We don't have the real date, using current
@@ -138,7 +199,6 @@ export async function handleChatCompletion(
138
199
  content: lastMessage.content,
139
200
  };
140
201
 
141
- const supabase = $provideSupabaseForServer();
142
202
  await supabase.from(await $getTableName('ChatHistory')).insert({
143
203
  createdAt: new Date().toISOString(),
144
204
  messageHash: computeHash(userMessageContent),
@@ -0,0 +1,91 @@
1
+ import type { really_any } from '@promptbook-local/types';
2
+ import { serializeError } from '@promptbook-local/utils';
3
+ import { assertsError } from '../../../../../src/errors/assertsError';
4
+ import { $getTableName } from '../../database/$getTableName';
5
+ import { $provideSupabaseForServer } from '../../database/$provideSupabaseForServer';
6
+ import { EMAIL_PROVIDERS } from '../../message-providers';
7
+ import { OutboundEmail } from '../../message-providers/email/_common/Email';
8
+
9
+ /**
10
+ * Sends a message
11
+ */
12
+ export async function sendMessage(message: OutboundEmail): Promise<void> {
13
+ const supabase = await $provideSupabaseForServer();
14
+
15
+ // 1. Insert message
16
+ const { data: insertedMessage, error: insertError } = await supabase
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ .from(await $getTableName('Message'))
19
+ .insert({
20
+ channel: message.channel || 'UNKNOWN',
21
+ direction: message.direction || 'OUTBOUND',
22
+ sender: message.sender,
23
+ recipients: message.recipients,
24
+ content: message.content,
25
+ threadId: message.threadId,
26
+ metadata: message.metadata,
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ } as any)
29
+ .select()
30
+ .single();
31
+
32
+ if (insertError) {
33
+ throw new Error(`Failed to insert message: ${insertError.message}`);
34
+ }
35
+
36
+ if (!insertedMessage) {
37
+ throw new Error('Failed to insert message: No data returned');
38
+ }
39
+
40
+ // 2. If outbound and email, try to send
41
+ if (message.direction === 'OUTBOUND' && message.channel === 'EMAIL') {
42
+ const providers = Object.keys(EMAIL_PROVIDERS);
43
+
44
+ if (providers.length === 0) {
45
+ console.warn('No email providers configured');
46
+ return;
47
+ }
48
+
49
+ let isSent = false;
50
+
51
+ for (const providerName of providers) {
52
+ const provider = EMAIL_PROVIDERS[providerName];
53
+ let isSuccessful = false;
54
+ let raw: really_any = null;
55
+
56
+ try {
57
+ console.log(`📤 Sending email via ${providerName}`);
58
+ raw = await provider.send(message);
59
+ isSuccessful = true;
60
+ isSent = true;
61
+ } catch (error) {
62
+ assertsError(error);
63
+ console.error(`Failed to send email via ${providerName}`, error);
64
+ raw = { error: serializeError(error) };
65
+ }
66
+
67
+ // 3. Log attempt
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ await supabase.from(await $getTableName('MessageSendAttempt')).insert({
70
+ // @ts-expect-error: insertedMessage is any
71
+ messageId: insertedMessage.id,
72
+ providerName,
73
+ isSuccessful,
74
+ raw,
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ } as any);
77
+
78
+ if (isSuccessful) {
79
+ break;
80
+ }
81
+ }
82
+
83
+ if (!isSent) {
84
+ throw new Error('Failed to send email via any provider');
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * TODO: !!!! Move to `message-providers` and rename `message-providers` -> `messages`
91
+ */
@@ -0,0 +1,72 @@
1
+ import type { Json } from '../database/schema';
2
+
3
+ export type MessageRow = {
4
+ id: number;
5
+ createdAt: string;
6
+ channel: string;
7
+ direction: string;
8
+ sender: Json;
9
+ recipients: Json;
10
+ content: string;
11
+ threadId: string | null;
12
+ metadata: Json;
13
+ // Joined fields
14
+ sendAttempts?: MessageSendAttemptRow[];
15
+ };
16
+
17
+ export type MessageSendAttemptRow = {
18
+ id: number;
19
+ createdAt: string;
20
+ messageId: number;
21
+ providerName: string;
22
+ isSuccessful: boolean;
23
+ raw: Json;
24
+ };
25
+
26
+ export type MessagesListResponse = {
27
+ items: MessageRow[];
28
+ total: number;
29
+ page: number;
30
+ pageSize: number;
31
+ };
32
+
33
+ export type MessagesListParams = {
34
+ page?: number;
35
+ pageSize?: number;
36
+ search?: string;
37
+ channel?: string;
38
+ direction?: string;
39
+ };
40
+
41
+ /**
42
+ * Build query string for messages listing.
43
+ */
44
+ function buildQuery(params: MessagesListParams): string {
45
+ const searchParams = new URLSearchParams();
46
+
47
+ if (params.page && params.page > 0) searchParams.set('page', String(params.page));
48
+ if (params.pageSize && params.pageSize > 0) searchParams.set('pageSize', String(params.pageSize));
49
+ if (params.search) searchParams.set('search', params.search);
50
+ if (params.channel) searchParams.set('channel', params.channel);
51
+ if (params.direction) searchParams.set('direction', params.direction);
52
+
53
+ const qs = searchParams.toString();
54
+ return qs ? `?${qs}` : '';
55
+ }
56
+
57
+ /**
58
+ * Fetch messages from the admin API.
59
+ */
60
+ export async function $fetchMessages(params: MessagesListParams = {}): Promise<MessagesListResponse> {
61
+ const qs = buildQuery(params);
62
+ const response = await fetch(`/api/messages${qs}`, {
63
+ method: 'GET',
64
+ });
65
+
66
+ if (!response.ok) {
67
+ const data = await response.json().catch(() => ({}));
68
+ throw new Error(data.error || 'Failed to load messages');
69
+ }
70
+
71
+ return (await response.json()) as MessagesListResponse;
72
+ }
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { filenameToPrompt } from './filenameToPrompt';
3
+
4
+ describe('how filenameToPrompt works', () => {
5
+ it('will convert filename with dashes', () => {
6
+ expect(filenameToPrompt('cat-sitting-on-keyboard.png')).toEqual('Cat sitting on keyboard');
7
+ expect(filenameToPrompt('hello-world.jpg')).toEqual('Hello world');
8
+ });
9
+
10
+ it('will convert filename with underscores', () => {
11
+ expect(filenameToPrompt('cat_sitting_on_keyboard.png')).toEqual('Cat sitting on keyboard');
12
+ expect(filenameToPrompt('hello_world.jpg')).toEqual('Hello world');
13
+ });
14
+
15
+ it('will convert filename with mixed separators', () => {
16
+ expect(filenameToPrompt('cat-sitting_on-keyboard.png')).toEqual('Cat sitting on keyboard');
17
+ });
18
+
19
+ it('will handle single word filename', () => {
20
+ expect(filenameToPrompt('cat.png')).toEqual('Cat');
21
+ expect(filenameToPrompt('HELLO.jpg')).toEqual('HELLO');
22
+ });
23
+
24
+ it('will handle filename without extension', () => {
25
+ expect(filenameToPrompt('cat-sitting-on-keyboard')).toEqual('Cat sitting on keyboard');
26
+ });
27
+
28
+ it('will handle filename with multiple dots', () => {
29
+ expect(filenameToPrompt('cat.sitting.on.keyboard.png')).toEqual('Cat.sitting.on.keyboard');
30
+ });
31
+
32
+ it('will handle capitalized words after first word', () => {
33
+ expect(filenameToPrompt('Cat-Sitting-On-Keyboard.png')).toEqual('Cat sitting on keyboard');
34
+ expect(filenameToPrompt('HELLO-WORLD.png')).toEqual('HELLO world');
35
+ });
36
+ });
@@ -0,0 +1,25 @@
1
+ import { capitalize } from '@promptbook/utils';
2
+
3
+ /**
4
+ * Converts a filename like "cat-sitting-on-keyboard.png" to a prompt like "Cat sitting on keyboard"
5
+ *
6
+ * @param filename - The filename to convert
7
+ * @returns The normalized prompt
8
+ */
9
+ export function filenameToPrompt(filename: string): string {
10
+ // Remove file extension
11
+ const withoutExtension = filename.replace(/\.[^/.]+$/, '');
12
+
13
+ // Replace dashes and underscores with spaces
14
+ const withSpaces = withoutExtension.replace(/[-_]/g, ' ');
15
+
16
+ // Capitalize each word
17
+ const words = withSpaces.split(' ');
18
+ const capitalizedWords = words.map((word, index) => (index === 0 ? capitalize(word) : word.toLowerCase()));
19
+
20
+ return capitalizedWords.join(' ');
21
+ }
22
+
23
+ /**
24
+ * TODO: [🧠][🏰] Make standard normalization function exported from `@promptbook/utils`
25
+ */
@@ -1,15 +1,6 @@
1
1
  import { createClient } from '@supabase/supabase-js';
2
2
  import { NextRequest } from 'next/server';
3
- import { SERVERS, SUPABASE_TABLE_PREFIX } from '../../config';
4
-
5
- // Note: Re-implementing normalizeTo_PascalCase to avoid importing from @promptbook-local/utils which might have Node.js dependencies
6
- function normalizeTo_PascalCase(text: string): string {
7
- return text
8
- .replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => {
9
- return word.toUpperCase();
10
- })
11
- .replace(/\s+/g, '');
12
- }
3
+ import { $getTableName } from '../database/$getTableName';
13
4
 
14
5
  export type ApiKeyValidationResult = {
15
6
  isValid: boolean;
@@ -62,10 +53,14 @@ export async function validateApiKey(request: NextRequest): Promise<ApiKeyValida
62
53
  };
63
54
  }
64
55
 
56
+ /*
57
+ Note: [🐔] This code was commented out because results of it are unused
58
+
65
59
  // Determine the table prefix based on the host
66
60
  const host = request.headers.get('host');
67
61
  let tablePrefix = SUPABASE_TABLE_PREFIX;
68
62
 
63
+
69
64
  if (host && SERVERS && SERVERS.length > 0) {
70
65
  if (SERVERS.some((server) => server === host)) {
71
66
  let serverName = host;
@@ -74,6 +69,7 @@ export async function validateApiKey(request: NextRequest): Promise<ApiKeyValida
74
69
  tablePrefix = `server_${serverName}_`;
75
70
  }
76
71
  }
72
+ */
77
73
 
78
74
  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
79
75
  const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
@@ -95,7 +91,7 @@ export async function validateApiKey(request: NextRequest): Promise<ApiKeyValida
95
91
  });
96
92
 
97
93
  const { data, error } = await supabase
98
- .from(`${tablePrefix}ApiTokens`)
94
+ .from(await $getTableName(`ApiTokens`))
99
95
  .select('id, isRevoked')
100
96
  .eq('token', token)
101
97
  .single();