@promptbook/cli 0.103.0-48 → 0.103.0-49

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 (110) 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 +6 -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 +100 -24
  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 +6 -7
  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 +109 -106
  30. package/apps/agents-server/src/app/agents/page.tsx +1 -1
  31. package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
  32. package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
  33. package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
  34. package/apps/agents-server/src/app/api/upload/route.ts +7 -3
  35. package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
  36. package/apps/agents-server/src/app/api/users/route.ts +71 -0
  37. package/apps/agents-server/src/app/globals.css +35 -1
  38. package/apps/agents-server/src/app/layout.tsx +43 -23
  39. package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
  40. package/apps/agents-server/src/app/metadata/page.tsx +13 -0
  41. package/apps/agents-server/src/app/not-found.tsx +5 -0
  42. package/apps/agents-server/src/app/page.tsx +84 -46
  43. package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
  44. package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
  45. package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
  46. package/apps/agents-server/src/components/Header/Header.tsx +146 -0
  47. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
  48. package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
  49. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
  50. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
  51. package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
  52. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
  53. package/apps/agents-server/src/database/$getTableName.ts +18 -0
  54. package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
  55. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
  56. package/apps/agents-server/src/database/getMetadata.ts +31 -0
  57. package/apps/agents-server/src/database/metadataDefaults.ts +32 -0
  58. package/apps/agents-server/src/database/schema.sql +81 -33
  59. package/apps/agents-server/src/database/schema.ts +35 -1
  60. package/apps/agents-server/src/middleware.ts +162 -0
  61. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
  62. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  63. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
  64. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
  65. package/apps/agents-server/src/tools/$provideServer.ts +39 -0
  66. package/apps/agents-server/src/utils/auth.ts +33 -0
  67. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
  68. package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
  69. package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
  70. package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
  71. package/apps/agents-server/src/utils/session.ts +50 -0
  72. package/apps/agents-server/tailwind.config.ts +2 -0
  73. package/esm/index.es.js +147 -31
  74. package/esm/index.es.js.map +1 -1
  75. package/esm/typings/servers.d.ts +1 -0
  76. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  77. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  78. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
  79. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  80. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  81. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  82. package/esm/typings/src/commitments/index.d.ts +2 -1
  83. package/esm/typings/src/config.d.ts +1 -0
  84. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  85. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  86. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  87. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  88. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  89. package/esm/typings/src/llm-providers/agent/Agent.d.ts +11 -3
  90. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
  91. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
  92. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
  93. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  94. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  95. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  96. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  97. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  98. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  99. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  100. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  101. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  102. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  103. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  104. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  105. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  106. package/esm/typings/src/version.d.ts +1 -1
  107. package/package.json +1 -1
  108. package/umd/index.umd.js +147 -31
  109. package/umd/index.umd.js.map +1 -1
  110. package/apps/agents-server/config.ts.todo +0 -38
@@ -1,3 +1,3 @@
1
1
  # 🔠 Promptbook Agents server
2
2
 
3
- - TODO: !!! Search for [🔠] to find all places where new app should be registered
3
+ - TODO: [🐱‍🚀] Search for [🔠] to find all places where new app should be registered
@@ -1,6 +1,7 @@
1
- TODO: !!!! Tests of the agents server should be in main testing pipeline
2
- TODO: !!!! Make the agents live here and act here
3
- TODO: !!!! Publish `agents-server` this as Docker container
4
- TODO: !!!! Publish `@promptbook/agents-server` to npm
5
- TODO: !!!! Work `ptbk start-pipelines-server` / `ptbk start-agents-server` CLI command to start this server
1
+ TODO: [🐱‍🚀] Theese are TODOs connected with the agents and Agents server
2
+ TODO: [🐱‍🚀] Tests of the agents server should be in main testing pipeline
3
+ TODO: [🐱‍🚀] Make the agents live here and act here
4
+ TODO: [🐱‍🚀] Publish `agents-server` this as Docker container
5
+ TODO: [🐱‍🚀] Publish `@promptbook/agents-server` to npm
6
+ TODO: [🐱‍🚀] Work `ptbk start-pipelines-server` / `ptbk start-agents-server` CLI command to start this server
6
7
  TODO: [🧠][🚙] `AgentXxx` vs `AgentsXxx` naming convention
@@ -0,0 +1,130 @@
1
+ import { ConfigChecker } from 'configchecker';
2
+
3
+ const config = ConfigChecker.from({
4
+ ...process.env,
5
+
6
+ // Note: To expose env variables to the browser, using this seemingly strange syntax:
7
+ // @see https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#exposing-environment-variables-to-the-browser
8
+ NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,
9
+
10
+ // Note: [🌇] Defa
11
+ NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV,
12
+ NEXT_PUBLIC_VERCEL_TARGET_ENV: process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV,
13
+ NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
14
+ NEXT_PUBLIC_VERCEL_BRANCH_URL: process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL,
15
+ NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL: process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL,
16
+ NEXT_PUBLIC_VERCEL_GIT_PROVIDER: process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER,
17
+ NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER,
18
+ NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG,
19
+ NEXT_PUBLIC_VERCEL_GIT_REPO_ID: process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_ID,
20
+ NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
21
+ NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE,
22
+ NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF,
23
+ NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME,
24
+ NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN,
25
+ NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA: process.env.NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA,
26
+ NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID: process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID,
27
+ });
28
+
29
+ /**
30
+ * Public URL of the deployment, e.g. "https://my-app.vercel.app"
31
+ *
32
+ * Note: When `SERVERS` are used, this URL will be overridden by the server URL.
33
+ */
34
+ export const NEXT_PUBLIC_URL = config.get('NEXT_PUBLIC_URL').url().value;
35
+
36
+ /**
37
+ * [♐️] Vercel environment: "development" | "preview" | "production"
38
+ */
39
+ export const NEXT_PUBLIC_VERCEL_ENV = config.get('NEXT_PUBLIC_VERCEL_ENV').value;
40
+
41
+ /**
42
+ * [♐️] Target environment – can be system or custom
43
+ */
44
+ export const NEXT_PUBLIC_VERCEL_TARGET_ENV = config.get('NEXT_PUBLIC_VERCEL_TARGET_ENV').value;
45
+
46
+ /**
47
+ * [♐️] Deployment URL (without https://), e.g. "my-app-abc123.vercel.app"
48
+ */
49
+ export const NEXT_PUBLIC_VERCEL_URL = config.get('NEXT_PUBLIC_VERCEL_URL').value;
50
+
51
+ /**
52
+ * [♐️] Branch URL (without https://), only for branch deployments
53
+ */
54
+ export const NEXT_PUBLIC_VERCEL_BRANCH_URL = config.get('NEXT_PUBLIC_VERCEL_BRANCH_URL').value;
55
+
56
+ /**
57
+ * [♐️] Production domain of the project
58
+ */
59
+ export const NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL = config.get('NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL').value;
60
+
61
+ /**
62
+ * [♐️] Git provider (github | gitlab | bitbucket)
63
+ */
64
+ export const NEXT_PUBLIC_VERCEL_GIT_PROVIDER = config.get('NEXT_PUBLIC_VERCEL_GIT_PROVIDER').value;
65
+
66
+ /**
67
+ * [♐️] Repository owner (e.g. "hejny")
68
+ */
69
+ export const NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER = config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER').value;
70
+
71
+ /**
72
+ * [♐️] Repository slug (e.g. "my-project")
73
+ */
74
+ export const NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG = config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG').value;
75
+
76
+ /**
77
+ * [♐️] Repository internal ID
78
+ */
79
+ export const NEXT_PUBLIC_VERCEL_GIT_REPO_ID = config.get('NEXT_PUBLIC_VERCEL_GIT_REPO_ID').value;
80
+
81
+ /**
82
+ * [♐️] Git commit SHA (short or long)
83
+ */
84
+ export const NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA = config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA').value;
85
+
86
+ /**
87
+ * [♐️] Commit message used for this deployment
88
+ */
89
+ export const NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE = config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE').value;
90
+
91
+ /**
92
+ * [♐️] Branch name (ref), e.g. "main"
93
+ */
94
+ export const NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF = config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF').value;
95
+
96
+ /**
97
+ * Author name of the commit
98
+ */
99
+ export const NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME = config.get('NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME').value;
100
+
101
+ /**
102
+ * [♐️] Author login/username
103
+ */
104
+ export const NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN = config.get(
105
+ 'NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN',
106
+ ).value;
107
+
108
+ /**
109
+ * [♐️] Previous deployment commit SHA (if exists)
110
+ */
111
+ export const NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA = config.get('NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA').value;
112
+
113
+ /**
114
+ * [♐️] Pull Request ID for PR-based deployments
115
+ */
116
+ export const NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID = config.get('NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID').value;
117
+
118
+ /**
119
+ * List of servers where agents can be hosted
120
+ *
121
+ * List of domains where the agents-server is deployed, this overrides the `NEXT_PUBLIC_URL` and `SUPABASE_TABLE_PREFIX` for each server.
122
+ */
123
+ export const SERVERS = config.get('SERVERS').list().value;
124
+
125
+ /**
126
+ * Supabase table prefix
127
+ *
128
+ * Note: When `SERVERS` are used, this prefix will be overridden by the server `server_<server_id>_`.
129
+ */
130
+ export const SUPABASE_TABLE_PREFIX = config.get('SUPABASE_TABLE_PREFIX').default('').value;
@@ -3,7 +3,7 @@ import path from 'path';
3
3
 
4
4
  const nextConfig: NextConfig = {
5
5
  output: 'standalone',
6
- // <- TODO: !!!! How to propperly build Next.js app
6
+ // <- TODO: [🐱‍🚀] How to propperly build Next.js app
7
7
 
8
8
  experimental: {
9
9
  externalDir: true,
@@ -0,0 +1,22 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
4
+
5
+ const dir = path.join('apps', 'agents-server', 'public', 'fonts');
6
+ const filePath = path.join(dir, 'OpenMoji-black-glyf.woff2');
7
+
8
+ if (!fs.existsSync(dir)){
9
+ fs.mkdirSync(dir, { recursive: true });
10
+ }
11
+
12
+ const file = fs.createWriteStream(filePath);
13
+ https.get('https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/font/OpenMoji-black-glyf/OpenMoji-black-glyf.woff2', function(response) {
14
+ response.pipe(file);
15
+ file.on('finish', function() {
16
+ file.close();
17
+ console.log('Download completed');
18
+ });
19
+ }).on('error', function(err) {
20
+ fs.unlink(filePath);
21
+ console.error('Error downloading file:', err.message);
22
+ });
@@ -0,0 +1,6 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ export default async function RedirectPage({ params }: { params: Promise<{ agentName: string; rest: string[] }> }) {
4
+ const { agentName, rest } = await params;
5
+ redirect(`/agents/${agentName}/${rest.join('/')}`);
6
+ }
@@ -0,0 +1 @@
1
+ export { generateMetadata, default } from '../agents/[agentName]/page';
@@ -1,14 +1,49 @@
1
1
  'use server';
2
2
 
3
3
  import { $generateBookBoilerplate } from '@promptbook-local/core';
4
+ import { revalidatePath } from 'next/cache';
5
+ import { cookies } from 'next/headers';
4
6
  import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
7
+ import { isUserAdmin } from '../utils/isUserAdmin';
5
8
 
6
9
  export async function $createAgentAction() {
10
+ // TODO: [👹] Check permissions here
11
+ if (!(await isUserAdmin())) {
12
+ throw new Error('You are not authorized to create agents');
13
+ }
14
+
7
15
  const collection = await $provideAgentCollectionForServer();
8
16
  await collection.createAgent($generateBookBoilerplate());
9
17
  }
10
18
 
19
+ export async function loginAction(formData: FormData) {
20
+ const username = formData.get('username') as string;
21
+ const password = formData.get('password') as string;
22
+
23
+ console.info(`Login attempt for user: ${username}`);
24
+
25
+ if (password === process.env.ADMIN_PASSWORD) {
26
+ const cookieStore = await cookies();
27
+ cookieStore.set('adminToken', password, {
28
+ httpOnly: true,
29
+ secure: process.env.NODE_ENV === 'production',
30
+ path: '/',
31
+ maxAge: 60 * 60 * 24 * 30, // 30 days
32
+ });
33
+ revalidatePath('/', 'layout');
34
+ return { success: true };
35
+ } else {
36
+ return { success: false, message: 'Invalid password' };
37
+ }
38
+ }
39
+
40
+ export async function logoutAction() {
41
+ const cookieStore = await cookies();
42
+ cookieStore.delete('adminToken');
43
+ revalidatePath('/', 'layout');
44
+ }
45
+
11
46
  /**
12
- * TODO: !!!! Reorganize actions.ts files
13
- * TODO: !!! [🧠] Study how Next.js actions work
47
+ * TODO: [🐱‍🚀] Reorganize actions.ts files
48
+ * TODO: [🐱‍🚀] [🧠] Study how Next.js actions work
14
49
  */
@@ -0,0 +1,68 @@
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 { useCallback, 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
+ 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
+
59
+ if (!agent) {
60
+ return <>{/* <- TODO: [🐱‍🚀] <PromptbookLoading /> */}</>;
61
+ }
62
+
63
+ return <AgentChat className={`w-full h-full`} agent={agent} onFeedback={handleFeedback} />;
64
+ }
65
+
66
+ /**
67
+ * TODO: [🚗] Transfer the saving logic to `<BookEditor/>` be aware of CRDT / yjs approach to be implementable in future
68
+ */
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { PromptbookQrCode } from '@promptbook-local/components';
4
+ import { AgentBasicInformation } from '@promptbook-local/types';
5
+ import { useState } from 'react';
6
+ import spaceTrim from 'spacetrim';
7
+
8
+ type AgentQrCodeProps = Pick<AgentBasicInformation, 'agentName' | 'personaDescription'> & {
9
+ agentUrl: string;
10
+ agentEmail: string;
11
+ };
12
+
13
+ export function AgentQrCode({ agentName, agentUrl, agentEmail, personaDescription }: AgentQrCodeProps) {
14
+ const [mode, setMode] = useState<'contact' | 'link'>('contact');
15
+
16
+ // TODO: [🧠] Should we include more info in VCARD?
17
+ const vcard = spaceTrim(`
18
+ BEGIN:VCARD
19
+ VERSION:3.0
20
+ FN:${agentName}
21
+ URL:${agentUrl}
22
+ EMAIL:${agentEmail}
23
+ NOTE:${personaDescription}
24
+ END:VCARD
25
+ `);
26
+
27
+ const qrValue = mode === 'contact' ? vcard : agentUrl;
28
+ const label = mode === 'contact' ? 'Scan to add contact' : 'Scan to open agent';
29
+
30
+ return (
31
+ <div className="flex flex-col items-center">
32
+ <div className="flex bg-gray-100 p-1 rounded-lg mb-4">
33
+ <button
34
+ onClick={() => setMode('contact')}
35
+ className={`px-3 py-1 text-xs font-medium rounded-md transition-all ${
36
+ mode === 'contact' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'
37
+ }`}
38
+ >
39
+ Contact
40
+ </button>
41
+ <button
42
+ onClick={() => setMode('link')}
43
+ className={`px-3 py-1 text-xs font-medium rounded-md transition-all ${
44
+ mode === 'link' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'
45
+ }`}
46
+ >
47
+ Link
48
+ </button>
49
+ </div>
50
+
51
+ <PromptbookQrCode value={qrValue} className="" size={250} />
52
+ <span className="mt-2 text-xs text-gray-500">{label}</span>
53
+ </div>
54
+ );
55
+ }
@@ -1,14 +1,14 @@
1
1
  'use client';
2
2
 
3
+ import { string_agent_url } from '@promptbook-local/types';
3
4
  import { useState } from 'react';
4
5
 
5
- export function AgentUrlCopy({ url }: { url: string; }) {
6
+ export function AgentUrlCopy({ agentUrl }: { agentUrl: string_agent_url }) {
6
7
  const [copied, setCopied] = useState(false);
7
8
 
8
-
9
9
  const handleCopy = async () => {
10
10
  try {
11
- await navigator.clipboard.writeText(url);
11
+ await navigator.clipboard.writeText(agentUrl);
12
12
  setCopied(true);
13
13
  setTimeout(() => setCopied(false), 2000);
14
14
  } catch (err) {
@@ -22,7 +22,7 @@ export function AgentUrlCopy({ url }: { url: string; }) {
22
22
  <div className="flex gap-2 items-center">
23
23
  <input
24
24
  type="text"
25
- value={url}
25
+ value={agentUrl}
26
26
  readOnly
27
27
  className="flex-1 px-2 py-1 border rounded text-sm bg-gray-50 text-gray-700"
28
28
  onFocus={(e) => e.target.select()}
@@ -30,7 +30,6 @@ export function AgentUrlCopy({ url }: { url: string; }) {
30
30
  <button
31
31
  type="button"
32
32
  className="px-2 py-1 text-white rounded text-xs font-semibold transition hover:opacity-90"
33
-
34
33
  onClick={handleCopy}
35
34
  >
36
35
  {copied ? '✓ Copied' : 'Copy'}
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ type CopyFieldProps = {
6
+ label: string;
7
+ value: string;
8
+ };
9
+
10
+ export function CopyField({ label, value }: CopyFieldProps) {
11
+ const [copied, setCopied] = useState(false);
12
+
13
+ const handleCopy = async () => {
14
+ try {
15
+ await navigator.clipboard.writeText(value);
16
+ setCopied(true);
17
+ setTimeout(() => setCopied(false), 2000);
18
+ } catch (err) {
19
+ console.error('Failed to copy:', err);
20
+ }
21
+ };
22
+
23
+ return (
24
+ <div className="w-full">
25
+ <label className="block text-xs text-gray-500 font-semibold mb-1">{label}</label>
26
+ <div className="flex gap-2 items-center">
27
+ <input
28
+ type="text"
29
+ value={value}
30
+ readOnly
31
+ className="flex-1 px-2 py-1 border rounded text-sm bg-gray-50 text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500"
32
+ onFocus={(e) => e.target.select()}
33
+ />
34
+ <button
35
+ type="button"
36
+ className="px-3 py-1 bg-gray-800 text-white rounded text-xs font-semibold transition hover:bg-gray-700 active:bg-gray-900"
37
+ onClick={handleCopy}
38
+ >
39
+ {copied ? '✓ Copied' : 'Copy'}
40
+ </button>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
@@ -26,13 +26,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
26
26
  return new Response(
27
27
  JSON.stringify(
28
28
  serializeError(error),
29
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
29
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
30
30
  null,
31
31
  4,
32
- // <- TODO: !!! Allow to configure pretty print for agent server
32
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
33
33
  ),
34
34
  {
35
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
35
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
36
36
  headers: { 'Content-Type': 'application/json' },
37
37
  },
38
38
  );
@@ -51,13 +51,13 @@ export async function PUT(request: Request, { params }: { params: Promise<{ agen
51
51
  agentSource = padBook(agentSource);
52
52
 
53
53
  await collection.updateAgentSource(agentName, agentSource);
54
- // <- TODO: !!! Properly type as string_book
54
+ // <- TODO: [🐱‍🚀] Properly type as string_book
55
55
 
56
56
  return new Response(
57
57
  JSON.stringify({
58
58
  isSuccessful: true,
59
59
  message: `Agent "${agentName}" updated successfully`,
60
- agentSource, // <- TODO: !!! Remove from response
60
+ agentSource, // <- TODO: [🐱‍🚀] Remove from response
61
61
  }),
62
62
  {
63
63
  status: 200,
@@ -72,13 +72,13 @@ export async function PUT(request: Request, { params }: { params: Promise<{ agen
72
72
  return new Response(
73
73
  JSON.stringify(
74
74
  serializeError(error),
75
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
75
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
76
76
  null,
77
77
  4,
78
- // <- TODO: !!! Allow to configure pretty print for agent server
78
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
79
79
  ),
80
80
  {
81
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
81
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
82
82
  headers: { 'Content-Type': 'application/json' },
83
83
  },
84
84
  );
@@ -1,15 +1,23 @@
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 POST(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
8
15
  let { agentName } = await params;
9
16
  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
17
+
18
+ const body = await request.json();
19
+ const { message = 'Tell me more about yourself.', thread } = body;
20
+ // <- TODO: [🐱‍🚀] To configuration DEFAULT_INITIAL_HIDDEN_MESSAGE
13
21
 
14
22
  try {
15
23
  const collection = await $provideAgentCollectionForServer();
@@ -17,7 +25,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
17
25
  const openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
18
26
  const agentSource = await collection.getAgentSource(agentName);
19
27
  const agent = new Agent({
20
- isVerbose: true, // <- TODO: !!! From environment variable
28
+ isVerbose: true, // <- TODO: [🐱‍🚀] From environment variable
21
29
  executionTools: {
22
30
  // [▶️] ...executionTools,
23
31
  llm: openAiAssistantExecutionTools, // Note: Providing the OpenAI Assistant LLM tools to the Agent to be able to create its own Assistants GPTs
@@ -25,18 +33,92 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
25
33
  agentSource,
26
34
  });
27
35
 
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
- },
36
+ const agentHash = computeAgentHash(agentSource);
37
+ const userAgent = request.headers.get('user-agent');
38
+ const ip =
39
+ request.headers.get('x-forwarded-for') ||
40
+ request.headers.get('x-real-ip') ||
41
+ request.headers.get('x-client-ip');
42
+
43
+ // Note: Capture language and platform information
44
+ const language = request.headers.get('accept-language');
45
+ // Simple platform extraction from userAgent parentheses content (e.g., Windows NT 10.0; Win64; x64)
46
+ const platform = userAgent ? userAgent.match(/\(([^)]+)\)/)?.[1] : undefined; // <- TODO: [🧠] Improve platform parsing
47
+
48
+ // Note: Identify the user message
49
+ const userMessageContent = {
50
+ role: 'USER',
34
51
  content: message,
52
+ };
53
+
54
+ // Record the user message
55
+ const supabase = $provideSupabaseForServer();
56
+ await supabase.from(await $getTableName('ChatHistory')).insert({
57
+ createdAt: new Date().toISOString(),
58
+ messageHash: computeHash(userMessageContent),
59
+ previousMessageHash: null, // <- TODO: [🧠] How to handle previous message hash?
60
+ agentName,
61
+ agentHash,
62
+ message: userMessageContent,
63
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
64
+ url: request.url,
65
+ ip,
66
+ userAgent,
67
+ language,
68
+ platform,
35
69
  });
36
70
 
37
- // TODO: [🐚] Implement streaming
71
+ const encoder = new TextEncoder();
72
+ const readableStream = new ReadableStream({
73
+ start(controller) {
74
+ agent.callChatModelStream!(
75
+ {
76
+ title: `Chat with agent ${
77
+ agentName /* <- TODO: [🕛] There should be `agentFullname` not `agentName` */
78
+ }`,
79
+ parameters: {},
80
+ modelRequirements: {
81
+ modelVariant: 'CHAT',
82
+ },
83
+ content: message,
84
+ thread,
85
+ },
86
+ (chunk) => {
87
+ controller.enqueue(encoder.encode(chunk.content));
88
+ },
89
+ )
90
+ .then(async (response) => {
91
+ // Note: Identify the agent message
92
+ const agentMessageContent = {
93
+ role: 'MODEL',
94
+ content: response.content,
95
+ };
38
96
 
39
- return new Response(response.content, {
97
+ // Record the agent message
98
+ await supabase.from(await $getTableName('ChatHistory')).insert({
99
+ createdAt: new Date().toISOString(),
100
+ messageHash: computeHash(agentMessageContent),
101
+ previousMessageHash: computeHash(userMessageContent),
102
+ agentName,
103
+ agentHash,
104
+ message: agentMessageContent,
105
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
106
+ url: request.url,
107
+ ip,
108
+ userAgent,
109
+ language,
110
+ platform,
111
+ });
112
+
113
+ controller.close();
114
+ })
115
+ .catch((error) => {
116
+ controller.error(error);
117
+ });
118
+ },
119
+ });
120
+
121
+ return new Response(readableStream, {
40
122
  status: 200,
41
123
  headers: { 'Content-Type': 'text/markdown' },
42
124
  });
@@ -48,21 +130,15 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
48
130
  return new Response(
49
131
  JSON.stringify(
50
132
  serializeError(error),
51
- // <- TODO: !!! Rename `serializeError` to `errorToJson`
133
+ // <- TODO: [🐱‍🚀] Rename `serializeError` to `errorToJson`
52
134
  null,
53
135
  4,
54
- // <- TODO: !!! Allow to configure pretty print for agent server
136
+ // <- TODO: [🐱‍🚀] Allow to configure pretty print for agent server
55
137
  ),
56
138
  {
57
- status: 400, // <- TODO: !!! Make `errorToHttpStatusCode`
139
+ status: 400, // <- TODO: [🐱‍🚀] Make `errorToHttpStatusCode`
58
140
  headers: { 'Content-Type': 'application/json' },
59
141
  },
60
142
  );
61
143
  }
62
144
  }
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
- */