@promptbook/cli 0.103.0-47 → 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.
- package/apps/agents-server/README.md +1 -1
- package/apps/agents-server/TODO.txt +6 -5
- package/apps/agents-server/config.ts +130 -0
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
- package/apps/agents-server/public/fonts/download-font.js +22 -0
- package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +6 -0
- package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
- package/apps/agents-server/src/app/actions.ts +37 -2
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
- package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +100 -18
- package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +6 -7
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +6 -7
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +6 -8
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +111 -108
- package/apps/agents-server/src/app/agents/page.tsx +1 -1
- package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
- package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
- package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
- package/apps/agents-server/src/app/api/upload/route.ts +7 -7
- package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
- package/apps/agents-server/src/app/api/users/route.ts +71 -0
- package/apps/agents-server/src/app/globals.css +35 -1
- package/apps/agents-server/src/app/layout.tsx +43 -23
- package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
- package/apps/agents-server/src/app/metadata/page.tsx +13 -0
- package/apps/agents-server/src/app/not-found.tsx +5 -0
- package/apps/agents-server/src/app/page.tsx +84 -46
- package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
- package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
- package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
- package/apps/agents-server/src/components/Header/Header.tsx +146 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
- package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
- package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
- package/apps/agents-server/src/database/$getTableName.ts +18 -0
- package/apps/agents-server/src/database/$provideSupabase.ts +29 -0
- package/apps/agents-server/src/{supabase/getSupabaseForBrowser.ts → database/$provideSupabaseForBrowser.ts} +9 -5
- package/apps/agents-server/src/{supabase/getSupabaseForServer.ts → database/$provideSupabaseForServer.ts} +7 -7
- package/apps/agents-server/src/{supabase/getSupabaseForWorker.ts → database/$provideSupabaseForWorker.ts} +5 -4
- package/apps/agents-server/src/database/getMetadata.ts +31 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +32 -0
- package/apps/agents-server/src/database/schema.sql +179 -0
- package/apps/agents-server/src/database/schema.ts +251 -0
- package/apps/agents-server/src/middleware.ts +162 -0
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +14 -10
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
- package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
- package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
- package/apps/agents-server/src/tools/$provideServer.ts +39 -0
- package/apps/agents-server/src/utils/auth.ts +33 -0
- package/apps/agents-server/src/utils/cdn/utils/getUserFileCdnKey.ts +2 -1
- package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
- package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
- package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
- package/apps/agents-server/src/utils/session.ts +50 -0
- package/apps/agents-server/tailwind.config.ts +2 -0
- package/esm/index.es.js +310 -49
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +1 -0
- package/esm/typings/src/_packages/core.index.d.ts +6 -0
- package/esm/typings/src/_packages/types.index.d.ts +4 -0
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +17 -3
- package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +2 -1
- package/esm/typings/src/book-2.0/agent-source/computeAgentHash.d.ts +8 -0
- package/esm/typings/src/book-2.0/agent-source/computeAgentHash.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/agent-source/createDefaultAgentName.d.ts +8 -0
- package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.d.ts +9 -0
- package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.d.ts +1 -1
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +57 -32
- package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/index.d.ts +2 -1
- package/esm/typings/src/config.d.ts +1 -0
- package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
- package/esm/typings/src/errors/WrappedError.d.ts +2 -2
- package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
- package/esm/typings/src/llm-providers/_common/utils/assertUniqueModels.d.ts +12 -0
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +17 -4
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +10 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +30 -4
- package/esm/typings/src/llm-providers/openai/openai-models.test.d.ts +4 -0
- package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
- package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
- package/esm/typings/src/transpilers/openai-sdk/register.d.ts +1 -1
- package/esm/typings/src/types/typeAliases.d.ts +6 -0
- package/esm/typings/src/utils/color/Color.d.ts +7 -0
- package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
- package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
- package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
- package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
- package/esm/typings/src/utils/normalization/normalize-to-kebab-case.d.ts +2 -0
- package/esm/typings/src/utils/normalization/normalizeTo_PascalCase.d.ts +3 -0
- package/esm/typings/src/utils/normalization/normalizeTo_camelCase.d.ts +2 -0
- package/esm/typings/src/utils/normalization/titleToName.d.ts +2 -0
- package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
- package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
- package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
- package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
- package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
- package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
- package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +311 -50
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/config.ts.todo +0 -312
- package/apps/agents-server/src/supabase/TODO.txt +0 -1
- package/apps/agents-server/src/supabase/getSupabase.ts +0 -25
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
TODO:
|
|
2
|
-
TODO:
|
|
3
|
-
TODO:
|
|
4
|
-
TODO:
|
|
5
|
-
TODO:
|
|
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;
|
|
Binary file
|
|
@@ -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 @@
|
|
|
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:
|
|
13
|
-
* TODO:
|
|
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({
|
|
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(
|
|
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={
|
|
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:
|
|
29
|
+
// <- TODO: [🐱🚀] Rename `serializeError` to `errorToJson`
|
|
30
30
|
null,
|
|
31
31
|
4,
|
|
32
|
-
// <- TODO:
|
|
32
|
+
// <- TODO: [🐱🚀] Allow to configure pretty print for agent server
|
|
33
33
|
),
|
|
34
34
|
{
|
|
35
|
-
status: 400, // <- TODO:
|
|
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:
|
|
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:
|
|
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:
|
|
75
|
+
// <- TODO: [🐱🚀] Rename `serializeError` to `errorToJson`
|
|
76
76
|
null,
|
|
77
77
|
4,
|
|
78
|
-
// <- TODO:
|
|
78
|
+
// <- TODO: [🐱🚀] Allow to configure pretty print for agent server
|
|
79
79
|
),
|
|
80
80
|
{
|
|
81
|
-
status: 400, // <- TODO:
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
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:
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
96
|
+
|
|
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
|
+
});
|
|
38
120
|
|
|
39
|
-
return new Response(
|
|
121
|
+
return new Response(readableStream, {
|
|
40
122
|
status: 200,
|
|
41
123
|
headers: { 'Content-Type': 'text/markdown' },
|
|
42
124
|
});
|
|
@@ -48,13 +130,13 @@ 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:
|
|
133
|
+
// <- TODO: [🐱🚀] Rename `serializeError` to `errorToJson`
|
|
52
134
|
null,
|
|
53
135
|
4,
|
|
54
|
-
// <- TODO:
|
|
136
|
+
// <- TODO: [🐱🚀] Allow to configure pretty print for agent server
|
|
55
137
|
),
|
|
56
138
|
{
|
|
57
|
-
status: 400, // <- TODO:
|
|
139
|
+
status: 400, // <- TODO: [🐱🚀] Make `errorToHttpStatusCode`
|
|
58
140
|
headers: { 'Content-Type': 'application/json' },
|
|
59
141
|
},
|
|
60
142
|
);
|