@promptbook/cli 0.103.0-45 → 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 (89) 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 +86 -0
  4. package/apps/agents-server/src/app/agents/[agentName]/api/book/test.http +37 -0
  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 +140 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +25 -0
  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 +13 -6
  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 +191 -165
  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-2.0/agent-source/padBook.d.ts +2 -0
  41. package/esm/typings/src/book-2.0/agent-source/string_book.d.ts +2 -0
  42. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +14 -0
  43. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.test.d.ts +1 -0
  44. package/esm/typings/src/book-components/Chat/AgentChat/AgentChatProps.d.ts +13 -0
  45. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +5 -60
  46. package/esm/typings/src/{book-2.0/commitments → commitments}/ACTION/ACTION.d.ts +1 -1
  47. package/esm/typings/src/{book-2.0/commitments → commitments}/DELETE/DELETE.d.ts +1 -1
  48. package/esm/typings/src/{book-2.0/commitments → commitments}/FORMAT/FORMAT.d.ts +1 -1
  49. package/esm/typings/src/{book-2.0/commitments → commitments}/GOAL/GOAL.d.ts +1 -1
  50. package/esm/typings/src/{book-2.0/commitments → commitments}/KNOWLEDGE/KNOWLEDGE.d.ts +1 -5
  51. package/esm/typings/src/{book-2.0/commitments → commitments}/MEMORY/MEMORY.d.ts +1 -1
  52. package/esm/typings/src/{book-2.0/commitments → commitments}/MESSAGE/MESSAGE.d.ts +1 -1
  53. package/esm/typings/src/{book-2.0/commitments → commitments}/META/META.d.ts +1 -1
  54. package/esm/typings/src/{book-2.0/commitments → commitments}/META_IMAGE/META_IMAGE.d.ts +1 -1
  55. package/esm/typings/src/{book-2.0/commitments → commitments}/META_LINK/META_LINK.d.ts +1 -1
  56. package/esm/typings/src/{book-2.0/commitments → commitments}/MODEL/MODEL.d.ts +1 -1
  57. package/esm/typings/src/{book-2.0/commitments → commitments}/NOTE/NOTE.d.ts +1 -1
  58. package/esm/typings/src/{book-2.0/commitments → commitments}/PERSONA/PERSONA.d.ts +1 -1
  59. package/esm/typings/src/{book-2.0/commitments → commitments}/RULE/RULE.d.ts +1 -1
  60. package/esm/typings/src/{book-2.0/commitments → commitments}/SAMPLE/SAMPLE.d.ts +1 -1
  61. package/esm/typings/src/{book-2.0/commitments → commitments}/SCENARIO/SCENARIO.d.ts +1 -1
  62. package/esm/typings/src/{book-2.0/commitments → commitments}/STYLE/STYLE.d.ts +1 -1
  63. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BaseCommitmentDefinition.d.ts +1 -1
  64. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/CommitmentDefinition.d.ts +1 -1
  65. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/NotYetImplementedCommitmentDefinition.d.ts +1 -1
  66. package/esm/typings/src/{book-2.0/commitments → commitments}/_base/createEmptyAgentModelRequirements.d.ts +1 -1
  67. package/esm/typings/src/conversion/validation/validatePipeline.d.ts +2 -0
  68. package/esm/typings/src/execution/LlmExecutionTools.d.ts +1 -1
  69. package/esm/typings/src/execution/utils/validatePromptResult.d.ts +2 -0
  70. package/esm/typings/src/llm-providers/agent/Agent.d.ts +3 -7
  71. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  72. package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +1 -1
  73. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +32 -0
  74. package/esm/typings/src/llm-providers/agent/RemoteAgentOptions.d.ts +11 -0
  75. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +5 -1
  76. package/esm/typings/src/pipeline/validatePipelineString.d.ts +2 -0
  77. package/esm/typings/src/storage/_common/PromptbookStorage.d.ts +1 -0
  78. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  79. package/esm/typings/src/utils/color/internal-utils/checkChannelValue.d.ts +0 -3
  80. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +2 -2
  81. package/esm/typings/src/utils/random/$randomFullnameWithColor.d.ts +1 -1
  82. package/esm/typings/src/utils/validators/parameterName/validateParameterName.d.ts +2 -0
  83. package/esm/typings/src/version.d.ts +1 -1
  84. package/package.json +1 -1
  85. package/umd/index.umd.js +191 -165
  86. package/umd/index.umd.js.map +1 -1
  87. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/BookCommitment.d.ts +0 -0
  88. /package/esm/typings/src/{book-2.0/commitments → commitments}/_base/ParsedCommitment.d.ts +0 -0
  89. /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
  },
@@ -0,0 +1,86 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { padBook, validateBook } from '@promptbook-local/core';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import spaceTrim from 'spacetrim';
5
+ import { assertsError } from '../../../../../../../../src/errors/assertsError';
6
+ import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
7
+
8
+ export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
9
+ keepUnused(request /* <- Note: We dont need `request` parameter */);
10
+ let { agentName } = await params;
11
+ agentName = decodeURIComponent(agentName);
12
+
13
+ try {
14
+ const collection = await $provideAgentCollectionForServer();
15
+ const agentSource = await collection.getAgentSource(agentName);
16
+
17
+ return new Response(agentSource, {
18
+ status: 200,
19
+ headers: { 'Content-Type': 'text/plain' /* <- TODO: [🎳] Mime type of book */ },
20
+ });
21
+ } catch (error) {
22
+ assertsError(error);
23
+
24
+ console.error(error);
25
+
26
+ return new Response(
27
+ JSON.stringify(
28
+ serializeError(error),
29
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
30
+ null,
31
+ 4,
32
+ // <- TODO: !!! Allow to configure pretty print for agent server
33
+ ),
34
+ {
35
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
36
+ headers: { 'Content-Type': 'application/json' },
37
+ },
38
+ );
39
+ }
40
+ }
41
+
42
+ export async function PUT(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
43
+ let { agentName } = await params;
44
+ agentName = decodeURIComponent(agentName);
45
+
46
+ try {
47
+ const collection = await $provideAgentCollectionForServer();
48
+ let agentSourceUnchecked = await request.text();
49
+ agentSourceUnchecked = spaceTrim(agentSourceUnchecked);
50
+ let agentSource = validateBook(agentSourceUnchecked);
51
+ agentSource = padBook(agentSource);
52
+
53
+ await collection.updateAgentSource(agentName, agentSource);
54
+ // <- TODO: !!! Properly type as string_book
55
+
56
+ return new Response(
57
+ JSON.stringify({
58
+ isSuccessful: true,
59
+ message: `Agent "${agentName}" updated successfully`,
60
+ agentSource, // <- TODO: !!! Remove from response
61
+ }),
62
+ {
63
+ status: 200,
64
+ headers: { 'Content-Type': 'application/json' },
65
+ },
66
+ );
67
+ } catch (error) {
68
+ assertsError(error);
69
+
70
+ console.error(error);
71
+
72
+ return new Response(
73
+ JSON.stringify(
74
+ serializeError(error),
75
+ // <- TODO: !!! Rename `serializeError` to `errorToJson`
76
+ null,
77
+ 4,
78
+ // <- TODO: !!! Allow to configure pretty print for agent server
79
+ ),
80
+ {
81
+ status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
82
+ headers: { 'Content-Type': 'application/json' },
83
+ },
84
+ );
85
+ }
86
+ }
@@ -0,0 +1,37 @@
1
+ ### Get the book
2
+
3
+ GET http://localhost:4440/agents/Gabriel%20Gray/api/book
4
+
5
+
6
+
7
+ # _______________________________________
8
+ ### Update the book 1
9
+
10
+
11
+ PUT http://localhost:4440/agents/Gabriel%20Gray/api/book
12
+ Content-Type: text/plain
13
+ # <- TODO: [🎳] Mime type of book
14
+
15
+
16
+ Gabriel Gray
17
+
18
+ META COLOR #c9c9c9
19
+ PERSONA Friendly and helpful AI agent.
20
+ RULE Be kind and respectful.
21
+
22
+
23
+
24
+
25
+ # _______________________________________
26
+ ### Update the book 2
27
+
28
+
29
+ PUT http://localhost:4440/agents/Gabriel%20Gray/api/book
30
+ Content-Type: text/plain
31
+ # <- TODO: [🎳] Mime type of book
32
+
33
+ Gabriel Gray
34
+
35
+ META COLOR #242424
36
+ PERSONA Strict and professional AI agent.
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
+ */
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import { BookEditor } from '@promptbook-local/components';
4
+ import { string_book } from '@promptbook-local/types';
5
+ import { useEffect, useRef, useState } from 'react';
6
+
7
+ type BookEditorWrapperProps = {
8
+ agentName: string;
9
+ initialAgentSource: string_book;
10
+ };
11
+
12
+ // TODO: !!!! Rename to BookEditorSavingWrapper
13
+
14
+ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorWrapperProps) {
15
+ const [agentSource, setAgentSource] = useState<string_book>(initialAgentSource);
16
+ const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
17
+
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;
22
+
23
+ const performSave = async (sourceToSave: string_book) => {
24
+ setSaveStatus('saving');
25
+ try {
26
+ const response = await fetch(`/agents/${encodeURIComponent(agentName)}/api/book`, {
27
+ method: 'PUT',
28
+ headers: { 'Content-Type': 'text/plain' },
29
+ body: sourceToSave,
30
+ });
31
+ if (!response.ok) {
32
+ throw new Error(`Failed to save: ${response.statusText}`);
33
+ }
34
+ setSaveStatus('saved');
35
+ setTimeout(() => setSaveStatus('idle'), 2000); // Reset status after 2 seconds
36
+ } catch (error) {
37
+ console.error('Error saving agent source:', error);
38
+ setSaveStatus('error');
39
+ setTimeout(() => setSaveStatus('idle'), 3000);
40
+ }
41
+ };
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
+
69
+ return (
70
+ <div className="w-full h-full">
71
+ {saveStatus !== 'idle' && (
72
+ <div
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 ${
76
+ saveStatus === 'saving'
77
+ ? 'bg-blue-100 text-blue-800'
78
+ : saveStatus === 'saved'
79
+ ? 'bg-green-100 text-green-800'
80
+ : 'bg-red-100 text-red-800'
81
+ }`}
82
+ >
83
+ {saveStatus === 'saving' && '💾 Saving...'}
84
+ {saveStatus === 'saved' && '✅ Saved'}
85
+ {saveStatus === 'error' && '❌ Failed to save'}
86
+ </div>
87
+ )}
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
+ />
130
+ </div>
131
+ );
132
+ }
133
+
134
+ /**
135
+ * TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
136
+ * DONE: Implement debouncing for auto-save (600ms delay) ✅
137
+ * TODO: !!! Add error handling and retry logic
138
+ * TODO: !!! Show save status indicator
139
+ * TODO: !!!!! Add file upload capability
140
+ */
@@ -0,0 +1,25 @@
1
+ 'use server';
2
+
3
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { headers } from 'next/headers';
5
+ import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
6
+ import { BookEditorWrapper } from './BookEditorWrapper';
7
+
8
+ export default async function AgentBookPage({ params }: { params: Promise<{ agentName: string }> }) {
9
+ $sideEffect(headers());
10
+
11
+ let { agentName } = await params;
12
+ agentName = decodeURIComponent(agentName);
13
+ const collection = await $provideAgentCollectionForServer();
14
+ const agentSource = await collection.getAgentSource(decodeURIComponent(agentName));
15
+
16
+ return (
17
+ <main className={`w-screen h-screen`}>
18
+ <BookEditorWrapper agentName={agentName} initialAgentSource={agentSource} />
19
+ </main>
20
+ );
21
+ }
22
+
23
+ /**
24
+ * TODO: [🚗] Components and pages here should be just tiny UI wrapper around proper agent logic and components
25
+ */
@@ -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
+ */