@promptbook/cli 0.103.0-46 → 0.103.0-48

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 (109) hide show
  1. package/apps/agents-server/TODO.txt +2 -2
  2. package/apps/agents-server/config.ts.todo +5 -279
  3. package/apps/agents-server/next.config.ts +3 -0
  4. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +1 -1
  5. package/apps/agents-server/src/app/agents/[agentName]/api/book/test.http +2 -2
  6. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +13 -5
  7. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/TODO.txt +1 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +53 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +45 -0
  10. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +54 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +84 -18
  12. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -5
  13. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{SelfLearningBook.tsx → AgentBookAndChatComponent.tsx} +5 -48
  14. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +7 -4
  15. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +38 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +23 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +8 -6
  18. package/apps/agents-server/src/app/agents/page.tsx +11 -0
  19. package/apps/agents-server/src/app/api/chat/route.ts +1 -1
  20. package/apps/agents-server/src/app/api/chat-streaming/route.ts +5 -1
  21. package/apps/agents-server/src/app/api/upload/route.ts +71 -0
  22. package/apps/agents-server/src/{supabase/getSupabase.ts → database/$provideSupabase.ts} +11 -7
  23. package/apps/agents-server/src/{supabase/getSupabaseForBrowser.ts → database/$provideSupabaseForBrowser.ts} +9 -5
  24. package/apps/agents-server/src/{supabase/getSupabaseForServer.ts → database/$provideSupabaseForServer.ts} +4 -4
  25. package/apps/agents-server/src/{supabase/getSupabaseForWorker.ts → database/$provideSupabaseForWorker.ts} +5 -4
  26. package/apps/agents-server/src/database/schema.sql +131 -0
  27. package/apps/agents-server/src/database/schema.ts +217 -0
  28. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +3 -2
  29. package/apps/agents-server/src/tools/$provideCdnForServer.ts +28 -0
  30. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +1 -1
  31. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +119 -0
  32. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +32 -0
  33. package/apps/agents-server/src/utils/cdn/interfaces/IStorage.ts +14 -0
  34. package/apps/agents-server/src/utils/cdn/utils/getUserFileCdnKey.ts +28 -0
  35. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +9 -0
  36. package/apps/agents-server/src/utils/cdn/utils/nextRequestToNodeRequest.ts +27 -0
  37. package/apps/agents-server/src/utils/validators/validateMimeType.ts +24 -0
  38. package/apps/agents-server/tsconfig.json +1 -1
  39. package/esm/index.es.js +319 -181
  40. package/esm/index.es.js.map +1 -1
  41. package/esm/typings/servers.d.ts +1 -7
  42. package/esm/typings/src/_packages/components.index.d.ts +4 -0
  43. package/esm/typings/src/_packages/core.index.d.ts +22 -14
  44. package/esm/typings/src/_packages/types.index.d.ts +14 -6
  45. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +7 -3
  46. package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +6 -1
  47. package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +3 -2
  48. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.d.ts +8 -0
  49. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.test.d.ts +1 -0
  50. package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
  51. package/esm/typings/src/book-2.0/agent-source/createDefaultAgentName.d.ts +8 -0
  52. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.d.ts +9 -0
  53. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.test.d.ts +1 -0
  54. package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.d.ts +1 -1
  55. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +14 -0
  56. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.test.d.ts +1 -0
  57. package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +13 -0
  58. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +1 -60
  59. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +57 -32
  60. package/esm/typings/src/{book-2.0/commitments → commitments}/ACTION/ACTION.d.ts +1 -1
  61. package/esm/typings/src/{book-2.0/commitments → commitments}/DELETE/DELETE.d.ts +1 -1
  62. package/esm/typings/src/{book-2.0/commitments → commitments}/FORMAT/FORMAT.d.ts +1 -1
  63. package/esm/typings/src/{book-2.0/commitments → commitments}/GOAL/GOAL.d.ts +1 -1
  64. package/esm/typings/src/{book-2.0/commitments → commitments}/KNOWLEDGE/KNOWLEDGE.d.ts +1 -5
  65. package/esm/typings/src/{book-2.0/commitments → commitments}/MEMORY/MEMORY.d.ts +1 -1
  66. package/esm/typings/src/{book-2.0/commitments → commitments}/MESSAGE/MESSAGE.d.ts +1 -1
  67. package/esm/typings/src/{book-2.0/commitments → commitments}/META/META.d.ts +1 -1
  68. package/esm/typings/src/{book-2.0/commitments → commitments}/META_IMAGE/META_IMAGE.d.ts +1 -1
  69. package/esm/typings/src/{book-2.0/commitments → commitments}/META_LINK/META_LINK.d.ts +1 -1
  70. package/esm/typings/src/{book-2.0/commitments → commitments}/MODEL/MODEL.d.ts +1 -1
  71. package/esm/typings/src/{book-2.0/commitments → commitments}/NOTE/NOTE.d.ts +1 -1
  72. package/esm/typings/src/{book-2.0/commitments → commitments}/PERSONA/PERSONA.d.ts +1 -1
  73. package/esm/typings/src/{book-2.0/commitments → commitments}/RULE/RULE.d.ts +1 -1
  74. package/esm/typings/src/{book-2.0/commitments → commitments}/SAMPLE/SAMPLE.d.ts +1 -1
  75. package/esm/typings/src/{book-2.0/commitments → commitments}/SCENARIO/SCENARIO.d.ts +1 -1
  76. package/esm/typings/src/{book-2.0/commitments → commitments}/STYLE/STYLE.d.ts +1 -1
  77. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BaseCommitmentDefinition.d.ts +1 -1
  78. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/CommitmentDefinition.d.ts +1 -1
  79. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/NotYetImplementedCommitmentDefinition.d.ts +1 -1
  80. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/createEmptyAgentModelRequirements.d.ts +1 -1
  81. package/esm/typings/src/execution/LlmExecutionTools.d.ts +1 -1
  82. package/esm/typings/src/llm-providers/_common/utils/assertUniqueModels.d.ts +12 -0
  83. package/esm/typings/src/llm-providers/agent/Agent.d.ts +10 -9
  84. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +5 -1
  85. package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +1 -1
  86. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +32 -0
  87. package/esm/typings/src/llm-providers/agent/RemoteAgentOptions.d.ts +11 -0
  88. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +29 -4
  89. package/esm/typings/src/llm-providers/openai/openai-models.test.d.ts +4 -0
  90. package/esm/typings/src/remote-server/startAgentServer.d.ts +1 -1
  91. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
  92. package/esm/typings/src/storage/_common/PromptbookStorage.d.ts +1 -0
  93. package/esm/typings/src/transpilers/openai-sdk/register.d.ts +1 -1
  94. package/esm/typings/src/types/typeAliases.d.ts +12 -0
  95. package/esm/typings/src/utils/color/internal-utils/checkChannelValue.d.ts +0 -3
  96. package/esm/typings/src/utils/normalization/normalize-to-kebab-case.d.ts +2 -0
  97. package/esm/typings/src/utils/normalization/normalizeTo_PascalCase.d.ts +3 -0
  98. package/esm/typings/src/utils/normalization/normalizeTo_camelCase.d.ts +2 -0
  99. package/esm/typings/src/utils/normalization/titleToName.d.ts +2 -0
  100. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +2 -2
  101. package/esm/typings/src/utils/random/$randomFullnameWithColor.d.ts +1 -1
  102. package/esm/typings/src/version.d.ts +1 -1
  103. package/package.json +1 -1
  104. package/umd/index.umd.js +320 -182
  105. package/umd/index.umd.js.map +1 -1
  106. package/apps/agents-server/src/supabase/TODO.txt +0 -1
  107. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BookCommitment.d.ts +0 -0
  108. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/ParsedCommitment.d.ts +0 -0
  109. /package/esm/typings/src/{book-2.0/commitments → commitments}/index.d.ts +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { BookEditor } from '@promptbook-local/components';
4
4
  import { string_book } from '@promptbook-local/types';
5
- import { useState } from 'react';
5
+ import { useEffect, useRef, useState } from 'react';
6
6
 
7
7
  type BookEditorWrapperProps = {
8
8
  agentName: string;
@@ -15,23 +15,22 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
15
15
  const [agentSource, setAgentSource] = useState<string_book>(initialAgentSource);
16
16
  const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
17
17
 
18
- const handleChange = async (newSource: string_book) => {
19
- setAgentSource(newSource);
20
- setSaveStatus('saving');
18
+ // Debounce timer ref so we can clear previous pending save
19
+ const debounceTimerRef = useRef<number | null>(null);
20
+ // Configurable debounce delay (ms) - tweak if needed
21
+ const DEBOUNCE_DELAY = 1000;
21
22
 
23
+ const performSave = async (sourceToSave: string_book) => {
24
+ setSaveStatus('saving');
22
25
  try {
23
26
  const response = await fetch(`/agents/${encodeURIComponent(agentName)}/api/book`, {
24
27
  method: 'PUT',
25
- headers: {
26
- 'Content-Type': 'text/plain',
27
- },
28
- body: newSource,
28
+ headers: { 'Content-Type': 'text/plain' },
29
+ body: sourceToSave,
29
30
  });
30
-
31
31
  if (!response.ok) {
32
32
  throw new Error(`Failed to save: ${response.statusText}`);
33
33
  }
34
-
35
34
  setSaveStatus('saved');
36
35
  setTimeout(() => setSaveStatus('idle'), 2000); // Reset status after 2 seconds
37
36
  } catch (error) {
@@ -41,11 +40,39 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
41
40
  }
42
41
  };
43
42
 
43
+ const scheduleSave = (nextSource: string_book) => {
44
+ // Clear existing pending save
45
+ if (debounceTimerRef.current) {
46
+ clearTimeout(debounceTimerRef.current);
47
+ }
48
+ // We stay 'idle' while typing; could add a 'pending' status in future if desired
49
+ // Schedule new save
50
+ debounceTimerRef.current = window.setTimeout(() => {
51
+ performSave(nextSource);
52
+ }, DEBOUNCE_DELAY);
53
+ };
54
+
55
+ const handleChange = (newSource: string_book) => {
56
+ setAgentSource(newSource);
57
+ scheduleSave(newSource);
58
+ };
59
+
60
+ // Cleanup on unmount to avoid lingering timeouts
61
+ useEffect(() => {
62
+ return () => {
63
+ if (debounceTimerRef.current) {
64
+ clearTimeout(debounceTimerRef.current);
65
+ }
66
+ };
67
+ }, []);
68
+
44
69
  return (
45
- <div className="w-full h-screen flex flex-col">
70
+ <div className="w-full h-full">
46
71
  {saveStatus !== 'idle' && (
47
72
  <div
48
- className={`px-4 py-2 text-sm ${
73
+ role="status"
74
+ aria-live="polite"
75
+ className={`fixed top-5 right-28 z-50 px-4 py-2 text-sm rounded shadow-md ${
49
76
  saveStatus === 'saving'
50
77
  ? 'bg-blue-100 text-blue-800'
51
78
  : saveStatus === 'saved'
@@ -54,21 +81,60 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
54
81
  }`}
55
82
  >
56
83
  {saveStatus === 'saving' && '💾 Saving...'}
57
- {saveStatus === 'saved' && '✅ Saved successfully'}
84
+ {saveStatus === 'saved' && '✅ Saved'}
58
85
  {saveStatus === 'error' && '❌ Failed to save'}
59
86
  </div>
60
87
  )}
61
- <div className="flex-1">
62
- <BookEditor value={agentSource} onChange={handleChange} />
63
- </div>
88
+
89
+ <BookEditor
90
+ className="w-full h-full"
91
+ isBorderRadiusDisabled
92
+ height={null}
93
+ value={agentSource}
94
+ onChange={handleChange}
95
+ onFileUpload={async (file) => {
96
+ const formData = new FormData();
97
+ formData.append('file', file);
98
+
99
+ const response = await fetch('/api/upload', {
100
+ method: 'POST',
101
+ body: formData,
102
+ });
103
+
104
+ if (!response.ok) {
105
+ throw new Error(`Failed to upload file: ${response.statusText}`);
106
+ }
107
+
108
+ const { fileUrl: longFileUrl } = await response.json();
109
+
110
+ const LONG_URL = `${process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!}/${process.env
111
+ .NEXT_PUBLIC_CDN_PATH_PREFIX!}/user/files/`;
112
+ const SHORT_URL = `https://ptbk.io/k/`;
113
+ // <- TODO: [🌍] Unite this logic in one place
114
+
115
+ const shortFileUrl = longFileUrl.split(LONG_URL).join(SHORT_URL);
116
+
117
+ console.log(`File uploaded:`, {
118
+ LONG_URL,
119
+ SHORT_URL,
120
+ longFileUrl,
121
+ shortFileUrl,
122
+ file,
123
+ formData,
124
+ response,
125
+ });
126
+
127
+ return shortFileUrl;
128
+ }}
129
+ // <- TODO: !!!! Create two-state solution for `<BookEditor onFileUpload={...} />`
130
+ />
64
131
  </div>
65
132
  );
66
133
  }
67
134
 
68
135
  /**
69
136
  * TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
70
- * TODO: !!! Implement debouncing for auto-save
137
+ * DONE: Implement debouncing for auto-save (600ms delay) ✅
71
138
  * TODO: !!! Add error handling and retry logic
72
139
  * TODO: !!! Show save status indicator
73
- * TODO: !!!!! Add file upload capability
74
140
  */
@@ -5,15 +5,19 @@ import { headers } from 'next/headers';
5
5
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
6
6
  import { BookEditorWrapper } from './BookEditorWrapper';
7
7
 
8
- export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
8
+ export default async function AgentBookPage({ params }: { params: Promise<{ agentName: string }> }) {
9
9
  $sideEffect(headers());
10
10
 
11
- const { agentName } = await params;
11
+ let { agentName } = await params;
12
+ agentName = decodeURIComponent(agentName);
12
13
  const collection = await $provideAgentCollectionForServer();
13
- const agentSourceSubject = await collection.getAgentSource(decodeURIComponent(agentName));
14
- const agentSource = agentSourceSubject.getValue();
14
+ const agentSource = await collection.getAgentSource(decodeURIComponent(agentName));
15
15
 
16
- return <BookEditorWrapper agentName={agentName} initialAgentSource={agentSource} />;
16
+ return (
17
+ <main className={`w-screen h-screen`}>
18
+ <BookEditorWrapper agentName={agentName} initialAgentSource={agentSource} />
19
+ </main>
20
+ );
17
21
  }
18
22
 
19
23
  /**
@@ -2,15 +2,14 @@
2
2
 
3
3
  import { ResizablePanelsAuto } from '@common/components/ResizablePanelsAuto/ResizablePanelsAuto';
4
4
  import { useStateInLocalStorage } from '@common/hooks/useStateInLocalStorage';
5
- import { BookEditor, LlmChat } from '@promptbook-local/components';
5
+ import { AgentChat, BookEditor } from '@promptbook-local/components';
6
6
  import { Agent, book } from '@promptbook-local/core';
7
7
  // import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
8
8
  import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
9
9
  import type { string_book } from '@promptbook-local/types';
10
- import { spaceTrim } from '@promptbook-local/utils';
11
10
  import { useMemo, useState } from 'react';
12
11
 
13
- export function SelfLearningBook() {
12
+ export function AgentBookAndChatComponent() {
14
13
  const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
15
14
  const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
16
15
  const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
@@ -85,11 +84,6 @@ export function SelfLearningBook() {
85
84
  return agent;
86
85
  }, [agentSource, setAgentSource, apiKey]);
87
86
 
88
- const llmTools = useMemo(() => {
89
- const llmTools = agent.getLlmExecutionTools();
90
- return llmTools;
91
- }, [agent]);
92
-
93
87
  return (
94
88
  <div className="min-h-screen relative">
95
89
  {/* Floating API Key Configuration */}
@@ -143,55 +137,18 @@ export function SelfLearningBook() {
143
137
  <ResizablePanelsAuto name="two-editors">
144
138
  <BookEditor
145
139
  className="w-full h-full"
140
+ isBorderRadiusDisabled
146
141
  height={null}
147
142
  value={agentSource}
148
143
  onChange={setAgentSource}
149
144
  // className={styles.BookEditor}
150
145
  isVerbose={false}
151
- isBorderRadiusDisabled
152
146
  onFileUpload={(file) => {
153
147
  return file.name;
154
148
  }}
155
149
  />
156
- {/*
157
- <AgentChat className="w-full h-full" persistenceKey="marigold-chat" {...{ agent }} />
158
- TODO: !!! Move to `AgentChat` component
159
- */}
160
- <LlmChat
161
- title={`Chat with ${agent.agentName || 'Agent'}`}
162
- // TODO: !!! Pass persistenceKey="chat-with-pavol-hejny"
163
- userParticipantName="USER"
164
- llmParticipantName="AGENT" // <- TODO: [🧠] Maybe dynamic agent id
165
- initialMessages={[
166
- {
167
- from: 'AGENT',
168
- content: spaceTrim(`
169
-
170
- Hello! I am ${agent.agentName || 'an AI Agent'}.
171
-
172
- [Hello](?message=Hello, can you tell me about yourself?)
173
- `),
174
- },
175
- ]}
176
- participants={[
177
- {
178
- name: 'AGENT',
179
- fullname: agent.agentName || 'Agent',
180
- avatarSrc: agent.meta.image,
181
- color: agent.meta.color,
182
- isMe: false,
183
- agentSource,
184
- },
185
- {
186
- name: 'USER',
187
- fullname: 'User',
188
- color: '#115EB6',
189
- isMe: true,
190
- },
191
- ]}
192
- {...{ llmTools }}
193
- className={`h-full flex flex-col`}
194
- />
150
+
151
+ <AgentChat className={`h-full flex flex-col`} {...{ agent }} />
195
152
  </ResizablePanelsAuto>
196
153
  </div>
197
154
  );
@@ -2,11 +2,14 @@
2
2
 
3
3
  import dynamic from 'next/dynamic';
4
4
 
5
- const SelfLearningBook = dynamic(() => import('./SelfLearningBook').then((module) => module.SelfLearningBook), {
6
- ssr: false,
7
- });
5
+ const SelfLearningBook = dynamic(
6
+ () => import('./AgentBookAndChatComponent').then((module) => module.AgentBookAndChatComponent),
7
+ {
8
+ ssr: false,
9
+ },
10
+ );
8
11
 
9
- export default function SelfLearningBookPage() {
12
+ export default function AgentBookAndChatPage() {
10
13
  return <SelfLearningBook />;
11
14
  }
12
15
 
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ import { usePromise } from '@common/hooks/usePromise';
4
+ import { AgentChat } from '@promptbook-local/components';
5
+ import { RemoteAgent } from '@promptbook-local/core';
6
+ import { useMemo } from 'react';
7
+ import { string_agent_url } from '../../../../../../../src/types/typeAliases';
8
+
9
+ type AgentChatWrapperProps = {
10
+ agentUrl: string_agent_url;
11
+ };
12
+
13
+ // TODO: !!!! Rename to AgentChatSomethingWrapper
14
+
15
+ export function AgentChatWrapper(props: AgentChatWrapperProps) {
16
+ const { agentUrl } = props;
17
+
18
+ const agentPromise = useMemo(
19
+ () =>
20
+ RemoteAgent.connect({
21
+ agentUrl,
22
+ isVerbose: true,
23
+ }),
24
+ [agentUrl],
25
+ );
26
+
27
+ const { value: agent } = usePromise(agentPromise, [agentPromise]);
28
+
29
+ if (!agent) {
30
+ return <>{/* <- TODO: !!! <PromptbookLoading /> */}</>;
31
+ }
32
+
33
+ return <AgentChat className={`w-full h-full`} agent={agent} />;
34
+ }
35
+
36
+ /**
37
+ * TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
38
+ */
@@ -0,0 +1,23 @@
1
+ 'use server';
2
+
3
+ import { headers } from 'next/headers';
4
+ import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
5
+ import { AgentChatWrapper } from './AgentChatWrapper';
6
+
7
+ export default async function AgentChatPage({ params }: { params: Promise<{ agentName: string }> }) {
8
+ $sideEffect(headers());
9
+ let { agentName } = await params;
10
+ agentName = decodeURIComponent(agentName);
11
+
12
+ const agentUrl = `/agents/${agentName}`;
13
+
14
+ return (
15
+ <main className={`w-screen h-screen`}>
16
+ <AgentChatWrapper agentUrl={agentUrl} />
17
+ </main>
18
+ );
19
+ }
20
+
21
+ /**
22
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
23
+ */
@@ -18,14 +18,15 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
18
18
 
19
19
  $sideEffect(headers());
20
20
 
21
- const { agentName } = await params;
21
+ let { agentName } = await params;
22
+ agentName = decodeURIComponent(agentName);
23
+
22
24
  const collection = await $provideAgentCollectionForServer();
23
- const agentSourceSubject = await collection.getAgentSource(decodeURIComponent(agentName));
24
- const agentSource = agentSourceSubject.getValue();
25
+ const agentSource = await collection.getAgentSource(agentName);
25
26
  const agentProfile = parseAgentSource(agentSource);
26
27
 
27
28
  // Build agent page URL for QR and copy
28
- const pageUrl = `https://s6.ptbk.io/agents/${encodeURIComponent(agentName)}`;
29
+ const pageUrl = `${process.env.NEXT_PUBLIC_URL}/agents/${encodeURIComponent(agentName)}`;
29
30
  // <- TODO: !!! Better
30
31
 
31
32
  // Extract brand color from meta
@@ -126,14 +127,14 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
126
127
  <div className="flex gap-4 mt-6">
127
128
  <a
128
129
  href={`${pageUrl}/chat`}
129
- // <- !!!! Can I append path like this on current browser URL in href?
130
+ // <- TODO: [🧠] Can I append path like this on current browser URL in href?
130
131
  className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow font-semibold transition"
131
132
  >
132
133
  💬 Chat
133
134
  </a>
134
135
  <a
135
136
  href={`${pageUrl}/book`}
136
- // <- !!!! Can I append path like this on current browser URL in href?
137
+ // <- TODO: [🧠] Can I append path like this on current browser URL in href?
137
138
  className="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded shadow font-semibold transition"
138
139
  >
139
140
  ✏️ Edit Agent Book
@@ -157,4 +158,5 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
157
158
  * TODO: !!! Make this page look nice - 🃏
158
159
  * TODO: !!! Show usage of LLM
159
160
  * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
161
+ * TODO: [🎣][🧠] Maybe do API / Page for transpilers, Allow to export each agent
160
162
  */
@@ -0,0 +1,11 @@
1
+ 'use server';
2
+
3
+ import HomePage from '../page';
4
+
5
+ export default async function AgentsPage() {
6
+ return <HomePage />;
7
+ }
8
+
9
+ /**
10
+ * TODO: !!! Distinguish between `/` and `/agents` pages
11
+ */
@@ -27,6 +27,6 @@ export async function GET(request: Request) {
27
27
 
28
28
  return new Response(response.content, {
29
29
  status: 200,
30
- headers: { 'Content-Type': 'text/plain' },
30
+ headers: { 'Content-Type': 'text/markdown' },
31
31
  });
32
32
  }
@@ -39,6 +39,10 @@ export async function GET(request: Request) {
39
39
 
40
40
  return new Response(readableStream, {
41
41
  status: 200,
42
- headers: { 'Content-Type': 'text/plain' },
42
+ headers: { 'Content-Type': 'text/markdown' },
43
43
  });
44
44
  }
45
+
46
+ /**
47
+ * Note: [🐚] This is how streaming is implemented correctly
48
+ */
@@ -0,0 +1,71 @@
1
+ import { nextRequestToNodeRequest } from '@/src/utils/cdn/utils/nextRequestToNodeRequest';
2
+ import { TODO_any } from '@promptbook-local/types';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import formidable from 'formidable';
5
+ import { readFile } from 'fs/promises';
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ import { assertsError } from '../../../../../../src/errors/assertsError';
8
+ import { string_url } from '../../../../../../src/types/typeAliases';
9
+ import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
10
+ import { $provideCdnForServer } from '../../../../src/tools/$provideCdnForServer';
11
+ import { getUserFileCdnKey } from '../../../../src/utils/cdn/utils/getUserFileCdnKey';
12
+ import { validateMimeType } from '../../../../src/utils/validators/validateMimeType';
13
+
14
+ export async function POST(request: NextRequest) {
15
+ try {
16
+ const nodeRequest = await nextRequestToNodeRequest(request);
17
+
18
+ const files = await new Promise<formidable.Files>((resolve, reject) => {
19
+ const form = formidable({});
20
+ form.parse(nodeRequest as TODO_any, (error, fields, files) => {
21
+ keepUnused(fields);
22
+
23
+ if (error) {
24
+ return reject(error);
25
+ }
26
+ resolve(files);
27
+ });
28
+ });
29
+
30
+ const uploadedFiles = files.file;
31
+
32
+ if (!uploadedFiles || uploadedFiles.length !== 1) {
33
+ return NextResponse.json(
34
+ { message: 'In form data there is not EXACTLY one "file" field' },
35
+ { status: 400 },
36
+ );
37
+ }
38
+
39
+ const uploadedFile = uploadedFiles[0]!;
40
+ const fileBuffer = await readFile(uploadedFile.filepath);
41
+ const cdn = $provideCdnForServer();
42
+ const key = getUserFileCdnKey(fileBuffer, uploadedFile.originalFilename || uploadedFile.newFilename);
43
+
44
+ await cdn.setItem(key, {
45
+ type: validateMimeType(uploadedFile.mimetype),
46
+ data: fileBuffer,
47
+ });
48
+
49
+ const fileUrl = cdn.getItemUrl(key);
50
+
51
+ return NextResponse.json({ fileUrl: fileUrl.href as string_url }, { status: 201 });
52
+ } catch (error) {
53
+ assertsError(error);
54
+
55
+ console.error(error);
56
+
57
+ return new Response(
58
+ JSON.stringify(
59
+ serializeError(error),
60
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
61
+ null,
62
+ 4,
63
+ // <- TODO: !!! Allow to configure pretty print for agent server
64
+ ),
65
+ {
66
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
67
+ headers: { 'Content-Type': 'application/json' },
68
+ },
69
+ );
70
+ }
71
+ }
@@ -1,9 +1,9 @@
1
1
  import type { AgentsDatabaseSchema } from '@promptbook-local/types';
2
2
  import { $isRunningInBrowser, $isRunningInNode, $isRunningInWebWorker } from '@promptbook-local/utils';
3
3
  import type { SupabaseClient } from '@supabase/supabase-js';
4
- import { getSupabaseForBrowser } from './getSupabaseForBrowser';
5
- import { getSupabaseForServer } from './getSupabaseForServer';
6
- import { getSupabaseForWorker } from './getSupabaseForWorker';
4
+ import { $provideSupabaseForBrowser } from './$provideSupabaseForBrowser';
5
+ import { $provideSupabaseForServer } from './$provideSupabaseForServer';
6
+ import { $provideSupabaseForWorker } from './$provideSupabaseForWorker';
7
7
 
8
8
  /**
9
9
  * Get supabase client in any environment
@@ -12,14 +12,18 @@ import { getSupabaseForWorker } from './getSupabaseForWorker';
12
12
  *
13
13
  * @returns instance of supabase client
14
14
  */
15
- export function getSupabase(): SupabaseClient<AgentsDatabaseSchema> {
15
+ export function $provideSupabase(): SupabaseClient<AgentsDatabaseSchema> {
16
16
  if ($isRunningInNode()) {
17
- return getSupabaseForServer();
17
+ return $provideSupabaseForServer();
18
18
  } else if ($isRunningInBrowser()) {
19
- return getSupabaseForBrowser();
19
+ return $provideSupabaseForBrowser();
20
20
  } else if ($isRunningInWebWorker()) {
21
- return getSupabaseForWorker();
21
+ return $provideSupabaseForWorker();
22
22
  } else {
23
23
  throw new Error('Unknown environment, cannot determine how to get Supabase client');
24
24
  }
25
25
  }
26
+
27
+ /**
28
+ * Note: [🎇] Promptbook Agents Server is not using Supabase in Browser so maybe remove this file
29
+ */
@@ -1,9 +1,9 @@
1
- import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
1
  import { AgentsDatabaseSchema } from '@promptbook-local/types';
3
2
  import { $isRunningInBrowser } from '@promptbook-local/utils';
3
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
4
4
 
5
5
  /**
6
- * Internal cache for `getSupabaseForBrowser`
6
+ * Internal cache for `$provideSupabaseForBrowser`
7
7
  *
8
8
  * @private
9
9
  * @singleton
@@ -14,14 +14,14 @@ let supabase: SupabaseClient<AgentsDatabaseSchema>;
14
14
  * Get supabase client
15
15
  *
16
16
  * Note: The client is cached, so it's safe to call this function multiple times
17
- * Note: This function is available ONLY in browser, use getSupabaseForServer in node
17
+ * Note: This function is available ONLY in browser, use $provideSupabaseForServer in node
18
18
  *
19
19
  * @returns instance of supabase client
20
20
  */
21
- export function getSupabaseForBrowser(): typeof supabase {
21
+ export function $provideSupabaseForBrowser(): typeof supabase {
22
22
  if (!$isRunningInBrowser()) {
23
23
  throw new Error(
24
- 'Function `getSupabaseForBrowser` can not be used on server or worker, use `getSupabaseForServer` or `getSupabaseForWorker` instead.',
24
+ 'Function `$provideSupabaseForBrowser` can not be used on server or worker, use `$provideSupabaseForServer` or `$provideSupabaseForWorker` instead.',
25
25
  );
26
26
  }
27
27
 
@@ -35,3 +35,7 @@ export function getSupabaseForBrowser(): typeof supabase {
35
35
 
36
36
  return supabase;
37
37
  }
38
+
39
+ /**
40
+ * Note: [🎇] Promptbook Agents Server is not using Supabase in Browser so maybe remove this file
41
+ */
@@ -3,7 +3,7 @@ import { $isRunningInNode } from '@promptbook-local/utils';
3
3
  import { createClient, SupabaseClient } from '@supabase/supabase-js';
4
4
 
5
5
  /**
6
- * Internal cache for `getSupabaseForServer`
6
+ * Internal cache for `$provideSupabaseForServer`
7
7
  *
8
8
  * @private
9
9
  * @singleton
@@ -14,14 +14,14 @@ let supabase: SupabaseClient<AgentsDatabaseSchema>;
14
14
  * Get supabase client
15
15
  *
16
16
  * Note: The client is cached, so it's safe to call this function multiple times
17
- * Note: This function is available ONLY on server/node, use getSupabaseForClient in browser
17
+ * Note: This function is available ONLY on server/node, use $provideSupabaseForClient in browser
18
18
  *
19
19
  * @returns instance of supabase client
20
20
  */
21
- export function getSupabaseForServer(): typeof supabase {
21
+ export function $provideSupabaseForServer(): typeof supabase {
22
22
  if (!$isRunningInNode()) {
23
23
  throw new Error(
24
- 'Function `getSupabaseForServer` can not be used in browser, use `getSupabaseForBrowser` instead.',
24
+ 'Function `$provideSupabaseForServer` can not be used in browser, use `$provideSupabaseForBrowser` instead.',
25
25
  );
26
26
  }
27
27
 
@@ -3,7 +3,7 @@ import { $isRunningInWebWorker } from '@promptbook-local/utils';
3
3
  import { createClient, SupabaseClient } from '@supabase/supabase-js';
4
4
 
5
5
  /**
6
- * Internal cache for `getSupabaseForWorker`
6
+ * Internal cache for `$provideSupabaseForWorker`
7
7
  *
8
8
  * @private
9
9
  * @singleton
@@ -14,14 +14,14 @@ let supabase: SupabaseClient<AgentsDatabaseSchema>;
14
14
  * Get supabase client
15
15
  *
16
16
  * Note: The client is cached, so it's safe to call this function multiple times
17
- * Note: This function is available ONLY in worker, use getSupabaseForBrowser for main thread
17
+ * Note: This function is available ONLY in worker, use $provideSupabaseForBrowser for main thread
18
18
  *
19
19
  * @returns instance of supabase client
20
20
  */
21
- export function getSupabaseForWorker(): typeof supabase {
21
+ export function $provideSupabaseForWorker(): typeof supabase {
22
22
  if (!$isRunningInWebWorker) {
23
23
  throw new Error(
24
- 'Function `getSupabaseForWorker` can not be used in browser, use `getSupabaseForBrowser` instead.',
24
+ 'Function `$provideSupabaseForWorker` can not be used in browser, use `$provideSupabaseForBrowser` instead.',
25
25
  );
26
26
  }
27
27
 
@@ -37,6 +37,7 @@ export function getSupabaseForWorker(): typeof supabase {
37
37
  }
38
38
 
39
39
  /**
40
+ * Note: [🎇] Promptbook Agents Server is not using Supabase in Browser so maybe remove this file
40
41
  * TODO: Fix> No storage option exists to persist the session, which may result in unexpected behavior when using auth.
41
42
  If you want to set persistSession to true, please provide a storage option or you may set persistSession to false to disable this warning.
42
43
  */