@promptbook/cli 0.103.0-46 → 0.103.0-47

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 (83) hide show
  1. package/apps/agents-server/config.ts.todo +4 -4
  2. package/apps/agents-server/next.config.ts +3 -0
  3. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +1 -1
  4. package/apps/agents-server/src/app/agents/[agentName]/api/book/test.http +2 -2
  5. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +6 -4
  6. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/TODO.txt +1 -0
  7. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +53 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +45 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +54 -0
  10. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +83 -17
  11. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -5
  12. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{SelfLearningBook.tsx → AgentBookAndChatComponent.tsx} +5 -46
  13. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +7 -4
  14. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +38 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +23 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +6 -4
  17. package/apps/agents-server/src/app/agents/page.tsx +11 -0
  18. package/apps/agents-server/src/app/api/chat/route.ts +1 -1
  19. package/apps/agents-server/src/app/api/chat-streaming/route.ts +5 -1
  20. package/apps/agents-server/src/app/api/upload/route.ts +75 -0
  21. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +1 -0
  22. package/apps/agents-server/src/tools/$provideCdnForServer.ts +28 -0
  23. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +119 -0
  24. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +32 -0
  25. package/apps/agents-server/src/utils/cdn/interfaces/IStorage.ts +14 -0
  26. package/apps/agents-server/src/utils/cdn/utils/getUserFileCdnKey.ts +27 -0
  27. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +9 -0
  28. package/apps/agents-server/src/utils/cdn/utils/nextRequestToNodeRequest.ts +27 -0
  29. package/apps/agents-server/src/utils/validators/validateMimeType.ts +24 -0
  30. package/apps/agents-server/tsconfig.json +1 -1
  31. package/esm/index.es.js +154 -161
  32. package/esm/index.es.js.map +1 -1
  33. package/esm/typings/servers.d.ts +1 -7
  34. package/esm/typings/src/_packages/components.index.d.ts +4 -0
  35. package/esm/typings/src/_packages/core.index.d.ts +16 -14
  36. package/esm/typings/src/_packages/types.index.d.ts +12 -6
  37. package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +6 -1
  38. package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +1 -1
  39. package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
  40. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +14 -0
  41. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.test.d.ts +1 -0
  42. package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +13 -0
  43. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +1 -60
  44. package/esm/typings/src/{book-2.0/commitments → commitments}/ACTION/ACTION.d.ts +1 -1
  45. package/esm/typings/src/{book-2.0/commitments → commitments}/DELETE/DELETE.d.ts +1 -1
  46. package/esm/typings/src/{book-2.0/commitments → commitments}/FORMAT/FORMAT.d.ts +1 -1
  47. package/esm/typings/src/{book-2.0/commitments → commitments}/GOAL/GOAL.d.ts +1 -1
  48. package/esm/typings/src/{book-2.0/commitments → commitments}/KNOWLEDGE/KNOWLEDGE.d.ts +1 -5
  49. package/esm/typings/src/{book-2.0/commitments → commitments}/MEMORY/MEMORY.d.ts +1 -1
  50. package/esm/typings/src/{book-2.0/commitments → commitments}/MESSAGE/MESSAGE.d.ts +1 -1
  51. package/esm/typings/src/{book-2.0/commitments → commitments}/META/META.d.ts +1 -1
  52. package/esm/typings/src/{book-2.0/commitments → commitments}/META_IMAGE/META_IMAGE.d.ts +1 -1
  53. package/esm/typings/src/{book-2.0/commitments → commitments}/META_LINK/META_LINK.d.ts +1 -1
  54. package/esm/typings/src/{book-2.0/commitments → commitments}/MODEL/MODEL.d.ts +1 -1
  55. package/esm/typings/src/{book-2.0/commitments → commitments}/NOTE/NOTE.d.ts +1 -1
  56. package/esm/typings/src/{book-2.0/commitments → commitments}/PERSONA/PERSONA.d.ts +1 -1
  57. package/esm/typings/src/{book-2.0/commitments → commitments}/RULE/RULE.d.ts +1 -1
  58. package/esm/typings/src/{book-2.0/commitments → commitments}/SAMPLE/SAMPLE.d.ts +1 -1
  59. package/esm/typings/src/{book-2.0/commitments → commitments}/SCENARIO/SCENARIO.d.ts +1 -1
  60. package/esm/typings/src/{book-2.0/commitments → commitments}/STYLE/STYLE.d.ts +1 -1
  61. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BaseCommitmentDefinition.d.ts +1 -1
  62. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/CommitmentDefinition.d.ts +1 -1
  63. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/NotYetImplementedCommitmentDefinition.d.ts +1 -1
  64. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/createEmptyAgentModelRequirements.d.ts +1 -1
  65. package/esm/typings/src/execution/LlmExecutionTools.d.ts +1 -1
  66. package/esm/typings/src/llm-providers/agent/Agent.d.ts +3 -7
  67. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  68. package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +1 -1
  69. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +32 -0
  70. package/esm/typings/src/llm-providers/agent/RemoteAgentOptions.d.ts +11 -0
  71. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +5 -1
  72. package/esm/typings/src/storage/_common/PromptbookStorage.d.ts +1 -0
  73. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  74. package/esm/typings/src/utils/color/internal-utils/checkChannelValue.d.ts +0 -3
  75. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +2 -2
  76. package/esm/typings/src/utils/random/$randomFullnameWithColor.d.ts +1 -1
  77. package/esm/typings/src/version.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/umd/index.umd.js +154 -161
  80. package/umd/index.umd.js.map +1 -1
  81. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BookCommitment.d.ts +0 -0
  82. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/ParsedCommitment.d.ts +0 -0
  83. /package/esm/typings/src/{book-2.0/commitments → commitments}/index.d.ts +0 -0
@@ -187,21 +187,21 @@ export const AZURE_COMPUTER_VISION_KEY = config.get('AZURE_COMPUTER_VISION_KEY')
187
187
 
188
188
  // TODO: [🧠] How to do required only on server
189
189
  export const CDN_BUCKET = config.get('CDN_BUCKET') /*.required([📐])*/.value;
190
- export const CDN_PATH_PREFIX = config.get('CDN_PATH_PREFIX') /*.required([📐])*/.value;
190
+ export const NEXT_PUBLIC_CDN_PATH_PREFIX = config.get('NEXT_PUBLIC_CDN_PATH_PREFIX') /*.required([📐])*/.value;
191
191
  export const CDN_ENDPOINT = config.get('CDN_ENDPOINT') /*.required([📐])*/.value;
192
192
  export const CDN_ACCESS_KEY_ID = config.get('CDN_ACCESS_KEY_ID') /*.required([📐])*/.value;
193
193
  export const CDN_SECRET_ACCESS_KEY = config.get('CDN_SECRET_ACCESS_KEY') /*.required([📐])*/.value;
194
- export const CDN_PUBLIC_URL = config.get('CDN_PUBLIC_URL').url() /*.required([📐])*/.value;
194
+ export const NEXT_PUBLIC_CDN_PUBLIC_URL = config.get('NEXT_PUBLIC_CDN_PUBLIC_URL').url() /*.required([📐])*/.value;
195
195
 
196
196
  export const CDN = (CDN_BUCKET &&
197
197
  // [📐]
198
198
  new DigitalOceanSpaces({
199
199
  bucket: CDN_BUCKET!,
200
- pathPrefix: CDN_PATH_PREFIX!,
200
+ pathPrefix: NEXT_PUBLIC_CDN_PATH_PREFIX!,
201
201
  endpoint: CDN_ENDPOINT!,
202
202
  accessKeyId: CDN_ACCESS_KEY_ID!,
203
203
  secretAccessKey: CDN_SECRET_ACCESS_KEY!,
204
- cdnPublicUrl: CDN_PUBLIC_URL!,
204
+ cdnPublicUrl: NEXT_PUBLIC_CDN_PUBLIC_URL!,
205
205
  gzip: false /* <- TODO: Maybe just remove this functionality from WebGPT repository */,
206
206
  })) as DigitalOceanSpaces;
207
207
 
@@ -2,6 +2,9 @@ import type { NextConfig } from 'next';
2
2
  import path from 'path';
3
3
 
4
4
  const nextConfig: NextConfig = {
5
+ output: 'standalone',
6
+ // <- TODO: !!!! How to propperly build Next.js app
7
+
5
8
  experimental: {
6
9
  externalDir: true,
7
10
  },
@@ -14,7 +14,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
14
14
  const collection = await $provideAgentCollectionForServer();
15
15
  const agentSource = await collection.getAgentSource(agentName);
16
16
 
17
- return new Response(agentSource.getValue(), {
17
+ return new Response(agentSource, {
18
18
  status: 200,
19
19
  headers: { 'Content-Type': 'text/plain' /* <- TODO: [🎳] Mime type of book */ },
20
20
  });
@@ -15,7 +15,7 @@ Content-Type: text/plain
15
15
 
16
16
  Gabriel Gray
17
17
 
18
- META COLOR #c9c9c9ff
18
+ META COLOR #c9c9c9
19
19
  PERSONA Friendly and helpful AI agent.
20
20
  RULE Be kind and respectful.
21
21
 
@@ -32,6 +32,6 @@ Content-Type: text/plain
32
32
 
33
33
  Gabriel Gray
34
34
 
35
- META COLOR #242424ff
35
+ META COLOR #242424
36
36
  PERSONA Strict and professional AI agent.
37
37
  RULE Be strict and professional.
@@ -5,7 +5,8 @@ import { serializeError } from '@promptbook-local/utils';
5
5
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
6
6
 
7
7
  export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
- const { agentName } = await params;
8
+ let { agentName } = await params;
9
+ agentName = decodeURIComponent(agentName);
9
10
  const { searchParams } = new URL(request.url);
10
11
  const message = searchParams.get('message') || 'Tell me more about yourself.';
11
12
  // <- TODO: !!!! To configuration DEFAULT_INITIAL_HIDDEN_MESSAGE
@@ -23,9 +24,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
23
24
  },
24
25
  agentSource,
25
26
  });
26
- const llmTools = agent.getLlmExecutionTools();
27
27
 
28
- const response = await llmTools.callChatModel!({
28
+ const response = await agent.callChatModel!({
29
29
  title: `Chat with agent ${agentName}`,
30
30
  parameters: {},
31
31
  modelRequirements: {
@@ -34,9 +34,11 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
34
34
  content: message,
35
35
  });
36
36
 
37
+ // TODO: [🐚] Implement streaming
38
+
37
39
  return new Response(response.content, {
38
40
  status: 200,
39
- headers: { 'Content-Type': 'text/plain' },
41
+ headers: { 'Content-Type': 'text/markdown' },
40
42
  });
41
43
  } catch (error) {
42
44
  assertsError(error);
@@ -0,0 +1 @@
1
+ TODO: [🎣][🧠] Maybe do API / Page for transpilers, Allow to export each agent
@@ -0,0 +1,53 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { createAgentModelRequirements } from '@promptbook-local/core';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import { assertsError } from '../../../../../../../../src/errors/assertsError';
5
+ import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
6
+
7
+ export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
+ keepUnused(request /* <- Note: We dont need `request` parameter */);
9
+ let { agentName } = await params;
10
+ agentName = decodeURIComponent(agentName);
11
+
12
+ try {
13
+ const collection = await $provideAgentCollectionForServer();
14
+ const agentSource = await collection.getAgentSource(agentName);
15
+ const modelRequirements = await createAgentModelRequirements(agentSource);
16
+
17
+ return new Response(
18
+ JSON.stringify(
19
+ modelRequirements,
20
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
21
+ null,
22
+ 4,
23
+ // <- TODO: !!! Allow to configure pretty print for agent server
24
+ ),
25
+ {
26
+ status: 200,
27
+ headers: { 'Content-Type': 'application/json' },
28
+ },
29
+ );
30
+ } catch (error) {
31
+ assertsError(error);
32
+
33
+ console.error(error);
34
+
35
+ return new Response(
36
+ JSON.stringify(
37
+ serializeError(error),
38
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
39
+ null,
40
+ 4,
41
+ // <- TODO: !!! Allow to configure pretty print for agent server
42
+ ),
43
+ {
44
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
45
+ headers: { 'Content-Type': 'application/json' },
46
+ },
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * TODO: [🍞] DRY - Make some common utility for API on one agent
53
+ */
@@ -0,0 +1,45 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { createAgentModelRequirements } from '@promptbook-local/core';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import { assertsError } from '../../../../../../../../../src/errors/assertsError';
5
+ import { keepUnused } from '../../../../../../../../../src/utils/organization/keepUnused';
6
+
7
+ export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
+ keepUnused(request /* <- Note: We dont need `request` parameter */);
9
+ let { agentName } = await params;
10
+ agentName = decodeURIComponent(agentName);
11
+
12
+ try {
13
+ const collection = await $provideAgentCollectionForServer();
14
+ const agentSource = await collection.getAgentSource(agentName);
15
+ const modelRequirements = await createAgentModelRequirements(agentSource);
16
+ const { systemMessage } = modelRequirements;
17
+
18
+ return new Response(systemMessage, {
19
+ status: 200,
20
+ headers: { 'Content-Type': 'text/markdown' },
21
+ });
22
+ } catch (error) {
23
+ assertsError(error);
24
+
25
+ console.error(error);
26
+
27
+ return new Response(
28
+ JSON.stringify(
29
+ serializeError(error),
30
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
31
+ null,
32
+ 4,
33
+ // <- TODO: !!! Allow to configure pretty print for agent server
34
+ ),
35
+ {
36
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
37
+ headers: { 'Content-Type': 'application/json' },
38
+ },
39
+ );
40
+ }
41
+ }
42
+
43
+ /**
44
+ * TODO: [🍞] DRY - Make some common utility for API on one agent
45
+ */
@@ -0,0 +1,54 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { parseAgentSource } from '@promptbook-local/core';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import { assertsError } from '../../../../../../../../src/errors/assertsError';
5
+ import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
6
+
7
+ export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
+ keepUnused(request /* <- Note: We dont need `request` parameter */);
9
+ let { agentName } = await params;
10
+ agentName = decodeURIComponent(agentName);
11
+
12
+ try {
13
+ const collection = await $provideAgentCollectionForServer();
14
+ const agentSource = await collection.getAgentSource(agentName);
15
+ const agentProfile = parseAgentSource(agentSource);
16
+
17
+ return new Response(
18
+ JSON.stringify(
19
+ agentProfile,
20
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
21
+ null,
22
+ 4,
23
+ // <- TODO: !!! Allow to configure pretty print for agent server
24
+ ),
25
+ {
26
+ status: 200,
27
+ headers: { 'Content-Type': 'application/json' },
28
+ },
29
+ );
30
+ } catch (error) {
31
+ assertsError(error);
32
+
33
+ console.error(error);
34
+
35
+ return new Response(
36
+ JSON.stringify(
37
+ serializeError(error),
38
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
39
+ null,
40
+ 4,
41
+ // <- TODO: !!! Allow to configure pretty print for agent server
42
+ ),
43
+ {
44
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
45
+ headers: { 'Content-Type': 'application/json' },
46
+ },
47
+ );
48
+ }
49
+ }
50
+
51
+
52
+ /**
53
+ * TODO: [🍞] DRY - Make some common utility for API on one agent
54
+ */
@@ -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 = 600;
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,20 +81,59 @@ 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
+ height={null}
92
+ value={agentSource}
93
+ onChange={handleChange}
94
+ onFileUpload={async (file) => {
95
+ const formData = new FormData();
96
+ formData.append('file', file);
97
+
98
+ const response = await fetch('/api/upload', {
99
+ method: 'POST',
100
+ body: formData,
101
+ });
102
+
103
+ if (!response.ok) {
104
+ throw new Error(`Failed to upload file: ${response.statusText}`);
105
+ }
106
+
107
+ const { fileUrl: longFileUrl } = await response.json();
108
+
109
+ const LONG_URL = `${process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!}/${process.env
110
+ .NEXT_PUBLIC_CDN_PATH_PREFIX!}/user/files/`;
111
+ const SHORT_URL = `https://ptbk.io/k/`;
112
+ // <- TODO: [🌍] Unite this logic in one place
113
+
114
+ const shortFileUrl = longFileUrl.split(LONG_URL).join(SHORT_URL);
115
+
116
+ console.log(`File uploaded:`, {
117
+ LONG_URL,
118
+ SHORT_URL,
119
+ longFileUrl,
120
+ shortFileUrl,
121
+ file,
122
+ formData,
123
+ response,
124
+ });
125
+
126
+ return shortFileUrl;
127
+ }}
128
+ // <- TODO: !!!! Create two-state solution for `<BookEditor onFileUpload={...} />`
129
+ />
64
130
  </div>
65
131
  );
66
132
  }
67
133
 
68
134
  /**
69
135
  * 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
136
+ * DONE: Implement debouncing for auto-save (600ms delay) ✅
71
137
  * TODO: !!! Add error handling and retry logic
72
138
  * TODO: !!! Show save status indicator
73
139
  * TODO: !!!!! Add file upload capability
@@ -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,10 +84,7 @@ 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]);
87
+
92
88
 
93
89
  return (
94
90
  <div className="min-h-screen relative">
@@ -153,45 +149,8 @@ export function SelfLearningBook() {
153
149
  return file.name;
154
150
  }}
155
151
  />
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
- />
152
+
153
+ <AgentChat className={`h-full flex flex-col`} {...{ agent }} />
195
154
  </ResizablePanelsAuto>
196
155
  </div>
197
156
  );
@@ -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
@@ -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
+ */