@promptbook/cli 0.103.0-48 → 0.103.0-50

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 (114) hide show
  1. package/apps/agents-server/README.md +1 -1
  2. package/apps/agents-server/TODO.txt +6 -5
  3. package/apps/agents-server/config.ts +130 -0
  4. package/apps/agents-server/next.config.ts +1 -1
  5. package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
  6. package/apps/agents-server/public/fonts/download-font.js +22 -0
  7. package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +11 -0
  8. package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
  9. package/apps/agents-server/src/app/actions.ts +37 -2
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
  13. package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
  15. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +121 -25
  16. package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
  18. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
  19. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +29 -10
  20. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +4 -5
  21. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
  22. package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +4 -4
  24. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
  25. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
  27. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
  28. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +117 -106
  30. package/apps/agents-server/src/app/agents/page.tsx +1 -1
  31. package/apps/agents-server/src/app/api/agents/route.ts +34 -0
  32. package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
  33. package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
  34. package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
  35. package/apps/agents-server/src/app/api/upload/route.ts +7 -3
  36. package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
  37. package/apps/agents-server/src/app/api/users/route.ts +71 -0
  38. package/apps/agents-server/src/app/globals.css +35 -1
  39. package/apps/agents-server/src/app/layout.tsx +43 -23
  40. package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
  41. package/apps/agents-server/src/app/metadata/page.tsx +13 -0
  42. package/apps/agents-server/src/app/not-found.tsx +5 -0
  43. package/apps/agents-server/src/app/page.tsx +117 -46
  44. package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
  45. package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
  46. package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
  47. package/apps/agents-server/src/components/Header/Header.tsx +146 -0
  48. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
  49. package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
  50. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
  51. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
  52. package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
  53. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
  54. package/apps/agents-server/src/database/$getTableName.ts +18 -0
  55. package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
  56. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
  57. package/apps/agents-server/src/database/getMetadata.ts +31 -0
  58. package/apps/agents-server/src/database/metadataDefaults.ts +37 -0
  59. package/apps/agents-server/src/database/schema.sql +81 -33
  60. package/apps/agents-server/src/database/schema.ts +35 -1
  61. package/apps/agents-server/src/middleware.ts +200 -0
  62. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
  63. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  64. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
  65. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
  66. package/apps/agents-server/src/tools/$provideServer.ts +39 -0
  67. package/apps/agents-server/src/utils/auth.ts +33 -0
  68. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
  69. package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
  70. package/apps/agents-server/src/utils/getFederatedAgents.ts +66 -0
  71. package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
  72. package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
  73. package/apps/agents-server/src/utils/session.ts +50 -0
  74. package/apps/agents-server/tailwind.config.ts +2 -0
  75. package/esm/index.es.js +147 -31
  76. package/esm/index.es.js.map +1 -1
  77. package/esm/typings/servers.d.ts +1 -0
  78. package/esm/typings/src/_packages/components.index.d.ts +2 -0
  79. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  80. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  81. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
  82. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +20 -0
  83. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  84. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  85. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  86. package/esm/typings/src/commitments/index.d.ts +2 -1
  87. package/esm/typings/src/config.d.ts +1 -0
  88. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  89. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  90. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  91. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  92. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  93. package/esm/typings/src/llm-providers/agent/Agent.d.ts +19 -3
  94. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +13 -1
  95. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +11 -2
  96. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
  97. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  98. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  99. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  100. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  101. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  102. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  103. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  104. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  105. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  106. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  107. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  108. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  109. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  110. package/esm/typings/src/version.d.ts +1 -1
  111. package/package.json +1 -1
  112. package/umd/index.umd.js +147 -31
  113. package/umd/index.umd.js.map +1 -1
  114. package/apps/agents-server/config.ts.todo +0 -38
@@ -1,15 +1,34 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
1
3
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
4
  import { $provideOpenAiAssistantExecutionToolsForServer } from '@/src/tools/$provideOpenAiAssistantExecutionToolsForServer';
3
- import { Agent } from '@promptbook-local/core';
4
- import { serializeError } from '@promptbook-local/utils';
5
+ import { Agent, computeAgentHash, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
6
+ import { computeHash, serializeError } from '@promptbook-local/utils';
5
7
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
6
8
 
7
- export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
9
+ /**
10
+ * Allow long-running streams: set to platform maximum (seconds)
11
+ */
12
+ export const maxDuration = 300;
13
+
14
+ export async function OPTIONS(request: Request) {
15
+ return new Response(null, {
16
+ status: 200,
17
+ headers: {
18
+ 'Access-Control-Allow-Origin': '*',
19
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
20
+ 'Access-Control-Allow-Headers': 'Content-Type',
21
+ },
22
+ });
23
+ }
24
+
25
+ export async function POST(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
26
  let { agentName } = await params;
9
27
  agentName = decodeURIComponent(agentName);
10
- const { searchParams } = new URL(request.url);
11
- const message = searchParams.get('message') || 'Tell me more about yourself.';
12
- // <- TODO: !!!! To configuration DEFAULT_INITIAL_HIDDEN_MESSAGE
28
+
29
+ const body = await request.json();
30
+ const { message = 'Tell me more about yourself.', thread } = body;
31
+ // <- TODO: [🐱‍🚀] To configuration DEFAULT_INITIAL_HIDDEN_MESSAGE
13
32
 
14
33
  try {
15
34
  const collection = await $provideAgentCollectionForServer();
@@ -17,7 +36,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
17
36
  const openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
18
37
  const agentSource = await collection.getAgentSource(agentName);
19
38
  const agent = new Agent({
20
- isVerbose: true, // <- TODO: !!! From environment variable
39
+ isVerbose: true, // <- TODO: [🐱‍🚀] From environment variable
21
40
  executionTools: {
22
41
  // [▶️] ...executionTools,
23
42
  llm: openAiAssistantExecutionTools, // Note: Providing the OpenAI Assistant LLM tools to the Agent to be able to create its own Assistants GPTs
@@ -25,20 +44,103 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
25
44
  agentSource,
26
45
  });
27
46
 
28
- const response = await agent.callChatModel!({
29
- title: `Chat with agent ${agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */}`,
30
- parameters: {},
31
- modelRequirements: {
32
- modelVariant: 'CHAT',
33
- },
47
+ const agentHash = computeAgentHash(agentSource);
48
+ const userAgent = request.headers.get('user-agent');
49
+ const ip =
50
+ request.headers.get('x-forwarded-for') ||
51
+ request.headers.get('x-real-ip') ||
52
+ request.headers.get('x-client-ip');
53
+
54
+ // Note: Capture language and platform information
55
+ const language = request.headers.get('accept-language');
56
+ // Simple platform extraction from userAgent parentheses content (e.g., Windows NT 10.0; Win64; x64)
57
+ const platform = userAgent ? userAgent.match(/\(([^)]+)\)/)?.[1] : undefined; // <- TODO: [🧠] Improve platform parsing
58
+
59
+ // Note: Identify the user message
60
+ const userMessageContent = {
61
+ role: 'USER',
34
62
  content: message,
63
+ };
64
+
65
+ // Record the user message
66
+ const supabase = $provideSupabaseForServer();
67
+ await supabase.from(await $getTableName('ChatHistory')).insert({
68
+ createdAt: new Date().toISOString(),
69
+ messageHash: computeHash(userMessageContent),
70
+ previousMessageHash: null, // <- TODO: [🧠] How to handle previous message hash?
71
+ agentName,
72
+ agentHash,
73
+ message: userMessageContent,
74
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
75
+ url: request.url,
76
+ ip,
77
+ userAgent,
78
+ language,
79
+ platform,
35
80
  });
36
81
 
37
- // TODO: [🐚] Implement streaming
82
+ const encoder = new TextEncoder();
83
+ const readableStream = new ReadableStream({
84
+ start(controller) {
85
+ agent.callChatModelStream!(
86
+ {
87
+ title: `Chat with agent ${
88
+ agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */
89
+ }`,
90
+ parameters: {},
91
+ modelRequirements: {
92
+ modelVariant: 'CHAT',
93
+ },
94
+ content: message,
95
+ thread,
96
+ },
97
+ (chunk) => {
98
+ controller.enqueue(encoder.encode(chunk.content));
99
+ },
100
+ )
101
+ .then(async (response) => {
102
+ // Note: Identify the agent message
103
+ const agentMessageContent = {
104
+ role: 'MODEL',
105
+ content: response.content,
106
+ };
38
107
 
39
- return new Response(response.content, {
108
+ // Record the agent message
109
+ await supabase.from(await $getTableName('ChatHistory')).insert({
110
+ createdAt: new Date().toISOString(),
111
+ messageHash: computeHash(agentMessageContent),
112
+ previousMessageHash: computeHash(userMessageContent),
113
+ agentName,
114
+ agentHash,
115
+ message: agentMessageContent,
116
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
117
+ url: request.url,
118
+ ip,
119
+ userAgent,
120
+ language,
121
+ platform,
122
+ });
123
+
124
+ // Note: [🐱‍🚀] Save the learned data
125
+ const newAgentSource = agent.agentSource.value;
126
+ if (newAgentSource !== agentSource) {
127
+ await collection.updateAgentSource(agentName, newAgentSource);
128
+ }
129
+
130
+ controller.close();
131
+ })
132
+ .catch((error) => {
133
+ controller.error(error);
134
+ });
135
+ },
136
+ });
137
+
138
+ return new Response(readableStream, {
40
139
  status: 200,
41
- headers: { 'Content-Type': 'text/markdown' },
140
+ headers: {
141
+ 'Content-Type': 'text/markdown',
142
+ 'Access-Control-Allow-Origin': '*', // <- Note: Allow embedding on other websites
143
+ },
42
144
  });
43
145
  } catch (error) {
44
146
  assertsError(error);
@@ -48,21 +150,15 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
48
150
  return new Response(
49
151
  JSON.stringify(
50
152
  serializeError(error),
51
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
153
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
52
154
  null,
53
155
  4,
54
- // <- TODO: !!! Allow to configure pretty print for agent server
156
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
55
157
  ),
56
158
  {
57
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
159
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
58
160
  headers: { 'Content-Type': 'application/json' },
59
161
  },
60
162
  );
61
163
  }
62
164
  }
63
-
64
-
65
- /**
66
- * TODO: !!!!!! Record conversations to `ChatHistory`
67
- * TODO: !!!!!! Make api endpoint for feedback on agent responses `/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts` and record to `ChatFeedback`
68
- */
@@ -0,0 +1,54 @@
1
+ import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { PROMPTBOOK_ENGINE_VERSION } from '../../../../../../../../src/version';
4
+ import { $getTableName } from '../../../../../database/$getTableName';
5
+
6
+ type FeedbackRequest = {
7
+ agentHash: string;
8
+ rating?: string;
9
+ textRating?: string;
10
+ chatThread?: string;
11
+ userNote?: string;
12
+ expectedAnswer?: string;
13
+ };
14
+
15
+ export async function POST(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
16
+ try {
17
+ const { agentName } = await params;
18
+ const body = (await request.json()) as FeedbackRequest;
19
+ const { agentHash, rating, textRating, chatThread, userNote, expectedAnswer } = body;
20
+
21
+ if (!agentHash) {
22
+ return NextResponse.json({ message: 'Missing agentHash' }, { status: 400 });
23
+ }
24
+
25
+ const supabase = $provideSupabaseForServer();
26
+
27
+ const { error } = await supabase.from(await $getTableName('ChatFeedback')).insert({
28
+ createdAt: new Date().toISOString(),
29
+ agentName,
30
+ agentHash,
31
+ rating,
32
+ textRating,
33
+ chatThread,
34
+ userNote,
35
+ expectedAnswer,
36
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
37
+ // Telemetry fields can be populated here if available in headers
38
+ // For now we leave them null or let Supabase handle default if any (though schema has them nullable)
39
+ url: request.headers.get('referer') || undefined,
40
+ userAgent: request.headers.get('user-agent') || undefined,
41
+ ip: request.headers.get('x-forwarded-for') || undefined,
42
+ });
43
+
44
+ if (error) {
45
+ console.error('Error inserting feedback:', error);
46
+ return NextResponse.json({ message: 'Failed to save feedback' }, { status: 500 });
47
+ }
48
+
49
+ return NextResponse.json({ message: 'Feedback saved' }, { status: 201 });
50
+ } catch (error) {
51
+ console.error('Unexpected error in feedback route:', error);
52
+ return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
53
+ }
54
+ }
@@ -17,10 +17,10 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
17
17
  return new Response(
18
18
  JSON.stringify(
19
19
  modelRequirements,
20
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
20
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
21
21
  null,
22
22
  4,
23
- // <- TODO: !!! Allow to configure pretty print for agent server
23
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
24
24
  ),
25
25
  {
26
26
  status: 200,
@@ -35,13 +35,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
35
35
  return new Response(
36
36
  JSON.stringify(
37
37
  serializeError(error),
38
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
38
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
39
39
  null,
40
40
  4,
41
- // <- TODO: !!! Allow to configure pretty print for agent server
41
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
42
42
  ),
43
43
  {
44
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
44
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
45
45
  headers: { 'Content-Type': 'application/json' },
46
46
  },
47
47
  );
@@ -50,4 +50,4 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
50
50
 
51
51
  /**
52
52
  * TODO: [🍞] DRY - Make some common utility for API on one agent
53
- */
53
+ */
@@ -27,13 +27,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
27
27
  return new Response(
28
28
  JSON.stringify(
29
29
  serializeError(error),
30
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
30
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
31
31
  null,
32
32
  4,
33
- // <- TODO: !!! Allow to configure pretty print for agent server
33
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
34
34
  ),
35
35
  {
36
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
36
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
37
37
  headers: { 'Content-Type': 'application/json' },
38
38
  },
39
39
  );
@@ -1,9 +1,21 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
- import { parseAgentSource } from '@promptbook-local/core';
2
+ import { computeAgentHash, parseAgentSource } from '@promptbook-local/core';
3
3
  import { serializeError } from '@promptbook-local/utils';
4
4
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
5
5
  import { keepUnused } from '../../../../../../../../src/utils/organization/keepUnused';
6
6
 
7
+ export async function OPTIONS(request: Request) {
8
+ keepUnused(request);
9
+ return new Response(null, {
10
+ status: 200,
11
+ headers: {
12
+ 'Access-Control-Allow-Origin': '*',
13
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
14
+ 'Access-Control-Allow-Headers': 'Content-Type',
15
+ },
16
+ });
17
+ }
18
+
7
19
  export async function GET(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
20
  keepUnused(request /* <- Note: We dont need `request` parameter */);
9
21
  let { agentName } = await params;
@@ -13,18 +25,26 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
13
25
  const collection = await $provideAgentCollectionForServer();
14
26
  const agentSource = await collection.getAgentSource(agentName);
15
27
  const agentProfile = parseAgentSource(agentSource);
28
+ const agentHash = computeAgentHash(agentSource);
16
29
 
17
30
  return new Response(
18
31
  JSON.stringify(
19
- agentProfile,
20
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
32
+ {
33
+ ...agentProfile,
34
+ agentHash,
35
+ parameters: [], // <- TODO: [😰] Implement parameters
36
+ },
37
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
21
38
  null,
22
39
  4,
23
- // <- TODO: !!! Allow to configure pretty print for agent server
40
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
24
41
  ),
25
42
  {
26
43
  status: 200,
27
- headers: { 'Content-Type': 'application/json' },
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Access-Control-Allow-Origin': '*', // <- Note: Allow embedding on other websites
47
+ },
28
48
  },
29
49
  );
30
50
  } catch (error) {
@@ -35,20 +55,19 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
35
55
  return new Response(
36
56
  JSON.stringify(
37
57
  serializeError(error),
38
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
58
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
39
59
  null,
40
60
  4,
41
- // <- TODO: !!! Allow to configure pretty print for agent server
61
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
42
62
  ),
43
63
  {
44
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
64
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
45
65
  headers: { 'Content-Type': 'application/json' },
46
66
  },
47
67
  );
48
68
  }
49
69
  }
50
70
 
51
-
52
71
  /**
53
72
  * TODO: [🍞] DRY - Make some common utility for API on one agent
54
- */
73
+ */
@@ -9,7 +9,7 @@ type BookEditorWrapperProps = {
9
9
  initialAgentSource: string_book;
10
10
  };
11
11
 
12
- // TODO: !!!! Rename to BookEditorSavingWrapper
12
+ // TODO: [🐱‍🚀] Rename to BookEditorSavingWrapper
13
13
 
14
14
  export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorWrapperProps) {
15
15
  const [agentSource, setAgentSource] = useState<string_book>(initialAgentSource);
@@ -126,15 +126,14 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
126
126
 
127
127
  return shortFileUrl;
128
128
  }}
129
- // <- TODO: !!!! Create two-state solution for `<BookEditor onFileUpload={...} />`
130
129
  />
131
130
  </div>
132
131
  );
133
132
  }
134
133
 
135
134
  /**
135
+ * TODO: Prompt: Use `import { debounce } from '@promptbook-local/utils';` instead of custom debounce implementation
136
136
  * TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
137
- * DONE: Implement debouncing for auto-save (600ms delay)
138
- * TODO: !!! Add error handling and retry logic
139
- * TODO: !!! Show save status indicator
137
+ * TODO: [🐱‍🚀] Add error handling and retry logic
138
+ * TODO: [🐱‍🚀] Show save status indicator
140
139
  */
@@ -1,6 +1,8 @@
1
1
  'use server';
2
2
 
3
+ import { ForbiddenPage } from '@/src/components/ForbiddenPage/ForbiddenPage';
3
4
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
5
+ import { isUserAdmin } from '@/src/utils/isUserAdmin';
4
6
  import { headers } from 'next/headers';
5
7
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
6
8
  import { BookEditorWrapper } from './BookEditorWrapper';
@@ -8,15 +10,20 @@ import { BookEditorWrapper } from './BookEditorWrapper';
8
10
  export default async function AgentBookPage({ params }: { params: Promise<{ agentName: string }> }) {
9
11
  $sideEffect(headers());
10
12
 
13
+ if (!(await isUserAdmin())) {
14
+ /* <- TODO: [👹] Here should be user permissions */
15
+ return <ForbiddenPage />;
16
+ }
17
+
11
18
  let { agentName } = await params;
12
19
  agentName = decodeURIComponent(agentName);
13
20
  const collection = await $provideAgentCollectionForServer();
14
21
  const agentSource = await collection.getAgentSource(decodeURIComponent(agentName));
15
22
 
16
23
  return (
17
- <main className={`w-screen h-screen`}>
24
+ <div className={`w-screen h-[calc(100vh-60px)]`}>
18
25
  <BookEditorWrapper agentName={agentName} initialAgentSource={agentSource} />
19
- </main>
26
+ </div>
20
27
  );
21
28
  }
22
29
 
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { ResizablePanelsAuto } from '@common/components/ResizablePanelsAuto/ResizablePanelsAuto';
4
+ import { string_agent_url, string_book } from '@promptbook-local/types';
5
+ import { BookEditorWrapper } from '../book/BookEditorWrapper';
6
+ import { AgentChatWrapper } from '../AgentChatWrapper';
7
+
8
+ type AgentBookAndChatProps = {
9
+ agentName: string;
10
+ initialAgentSource: string_book;
11
+ agentUrl: string_agent_url;
12
+ };
13
+
14
+ export function AgentBookAndChat(props: AgentBookAndChatProps) {
15
+ const { agentName, initialAgentSource, agentUrl } = props;
16
+
17
+ return (
18
+ <ResizablePanelsAuto name={`agent-book-and-chat-${agentName}`} className="w-full h-full">
19
+ <BookEditorWrapper agentName={agentName} initialAgentSource={initialAgentSource} />
20
+ <AgentChatWrapper agentUrl={agentUrl} />
21
+ </ResizablePanelsAuto>
22
+ );
23
+ }
@@ -16,7 +16,7 @@ export function AgentBookAndChatComponent() {
16
16
 
17
17
  const [agentSource, setAgentSource] = useStateInLocalStorage<string_book>(
18
18
  'marigold-agentSource-1',
19
- // TODO: !!! Uplad image to ptbk.io CDN
19
+ // TODO: [🐱‍🚀] Uplad image to ptbk.io CDN
20
20
  () => book`
21
21
  Marigold
22
22
 
@@ -53,7 +53,7 @@ export function AgentBookAndChatComponent() {
53
53
 
54
54
  const agent = useMemo(() => {
55
55
  /*/
56
- // TODO: !!! Try working with `RemoteLlmExecutionTools`
56
+ // TODO: [🐱‍🚀] Try working with `RemoteLlmExecutionTools`
57
57
  const llm = new RemoteLlmExecutionTools({
58
58
  remoteServerUrl: 'https://promptbook.s5.ptbk.io/',
59
59
  identification: {
@@ -66,7 +66,7 @@ export function AgentBookAndChatComponent() {
66
66
  /**/
67
67
  const llm = new OpenAiAssistantExecutionTools({
68
68
  dangerouslyAllowBrowser: true,
69
- isCreatingNewAssistantsAllowed: true, // <- TODO: !!! Test without whether warning is shown
69
+ isCreatingNewAssistantsAllowed: true, // <- TODO: [🐱‍🚀] Test without whether warning is shown
70
70
  apiKey,
71
71
  assistantId: 'asst_xI94Elk27nssnwAUkG2Cmok8', // <- TODO: [🧠] Make dynamic
72
72
  isVerbose: true,
@@ -155,6 +155,6 @@ export function AgentBookAndChatComponent() {
155
155
  }
156
156
 
157
157
  /**
158
- * TODO: !!!! Make self-learning book createAgentLlmExecutionTools, use bidirectional agentSource
158
+ * TODO: [🐱‍🚀] Make self-learning book createAgentLlmExecutionTools, use bidirectional agentSource
159
159
  * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
160
160
  */
@@ -1,21 +1,32 @@
1
- 'use client';
1
+ 'use server';
2
2
 
3
- import dynamic from 'next/dynamic';
3
+ import { ForbiddenPage } from '@/src/components/ForbiddenPage/ForbiddenPage';
4
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
5
+ import { isUserAdmin } from '@/src/utils/isUserAdmin';
6
+ import { headers } from 'next/headers';
7
+ import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
8
+ import { generateAgentMetadata } from '../generateAgentMetadata';
9
+ import { AgentBookAndChat } from './AgentBookAndChat';
4
10
 
5
- const SelfLearningBook = dynamic(
6
- () => import('./AgentBookAndChatComponent').then((module) => module.AgentBookAndChatComponent),
7
- {
8
- ssr: false,
9
- },
10
- );
11
+ export const generateMetadata = generateAgentMetadata;
11
12
 
12
- export default function AgentBookAndChatPage() {
13
- return <SelfLearningBook />;
14
- }
13
+ export default async function AgentBookAndChatPage({ params }: { params: Promise<{ agentName: string }> }) {
14
+ $sideEffect(headers());
15
+
16
+ if (!(await isUserAdmin())) {
17
+ /* <- TODO: [👹] Here should be user permissions */
18
+ return <ForbiddenPage />;
19
+ }
15
20
 
16
- /**
17
- * TODO: !!! Private / public / open agents
18
- * TODO: !!! Allow http://localhost:4440/agents/agent-123.book to download the agent book file
19
- * TODO: !!! BOOK_MIME_TYPE in config
20
- * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
21
- */
21
+ let { agentName } = await params;
22
+ agentName = decodeURIComponent(agentName);
23
+ const collection = await $provideAgentCollectionForServer();
24
+ const agentSource = await collection.getAgentSource(agentName);
25
+ const agentUrl = `/agents/${agentName}`;
26
+
27
+ return (
28
+ <div className={`w-screen h-[calc(100vh-60px)]`}>
29
+ <AgentBookAndChat agentName={agentName} initialAgentSource={agentSource} agentUrl={agentUrl} />
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+
5
+ const SelfLearningBook = dynamic(
6
+ () => import('./AgentBookAndChatComponent').then((module) => module.AgentBookAndChatComponent),
7
+ {
8
+ ssr: false,
9
+ },
10
+ );
11
+
12
+ export default function AgentBookAndChatPage() {
13
+ return <SelfLearningBook />;
14
+ }
15
+
16
+ /**
17
+ * TODO: [🐱‍🚀] Private / public / open agents
18
+ * TODO: [🐱‍🚀] Allow http://localhost:4440/agents/agent-123.book to download the agent book file
19
+ * TODO: [🐱‍🚀] BOOK_MIME_TYPE in config
20
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
21
+ */
@@ -3,14 +3,14 @@
3
3
  import { usePromise } from '@common/hooks/usePromise';
4
4
  import { AgentChat } from '@promptbook-local/components';
5
5
  import { RemoteAgent } from '@promptbook-local/core';
6
- import { useMemo } from 'react';
6
+ import { useCallback, useMemo } from 'react';
7
7
  import { string_agent_url } from '../../../../../../../src/types/typeAliases';
8
8
 
9
9
  type AgentChatWrapperProps = {
10
10
  agentUrl: string_agent_url;
11
11
  };
12
12
 
13
- // TODO: !!!! Rename to AgentChatSomethingWrapper
13
+ // TODO: [🐱‍🚀] Rename to AgentChatSomethingWrapper
14
14
 
15
15
  export function AgentChatWrapper(props: AgentChatWrapperProps) {
16
16
  const { agentUrl } = props;
@@ -26,11 +26,41 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
26
26
 
27
27
  const { value: agent } = usePromise(agentPromise, [agentPromise]);
28
28
 
29
+ const handleFeedback = useCallback(
30
+ async (feedback: {
31
+ rating: number;
32
+ textRating?: string;
33
+ chatThread?: string;
34
+ userNote?: string;
35
+ expectedAnswer?: string | null;
36
+ }) => {
37
+ if (!agent) {
38
+ return;
39
+ }
40
+
41
+ await fetch(`${agentUrl}/api/feedback`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ body: JSON.stringify({
47
+ rating: feedback.rating.toString(),
48
+ textRating: feedback.textRating,
49
+ chatThread: feedback.chatThread,
50
+ userNote: feedback.textRating, // Mapping textRating to userNote as well if needed, or just textRating
51
+ expectedAnswer: feedback.expectedAnswer,
52
+ agentHash: agent.agentHash,
53
+ }),
54
+ });
55
+ },
56
+ [agent, agentUrl],
57
+ );
58
+
29
59
  if (!agent) {
30
- return <>{/* <- TODO: !!! <PromptbookLoading /> */}</>;
60
+ return <>{/* <- TODO: [🐱‍🚀] <PromptbookLoading /> */}</>;
31
61
  }
32
62
 
33
- return <AgentChat className={`w-full h-full`} agent={agent} />;
63
+ return <AgentChat className={`w-full h-full`} agent={agent} onFeedback={handleFeedback} />;
34
64
  }
35
65
 
36
66
  /**
@@ -2,7 +2,10 @@
2
2
 
3
3
  import { headers } from 'next/headers';
4
4
  import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
5
- import { AgentChatWrapper } from './AgentChatWrapper';
5
+ import { generateAgentMetadata } from '../generateAgentMetadata';
6
+ import { AgentChatWrapper } from '../AgentChatWrapper';
7
+
8
+ export const generateMetadata = generateAgentMetadata;
6
9
 
7
10
  export default async function AgentChatPage({ params }: { params: Promise<{ agentName: string }> }) {
8
11
  $sideEffect(headers());