@promptbook/cli 0.103.0-50 → 0.103.0-51
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/package.json +1 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +7 -6
- package/apps/agents-server/src/app/{metadata → admin/metadata}/MetadataClient.tsx +5 -13
- package/apps/agents-server/src/app/{metadata → admin/metadata}/page.tsx +2 -2
- package/apps/agents-server/src/app/api/embed.js/route.ts +93 -0
- package/apps/agents-server/src/app/embed/page.tsx +24 -0
- package/apps/agents-server/src/app/page.tsx +48 -101
- package/apps/agents-server/src/components/Header/Header.tsx +28 -8
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +28 -0
- package/apps/agents-server/src/components/Homepage/Card.tsx +18 -0
- package/apps/agents-server/src/components/Homepage/ModelCard.tsx +29 -0
- package/apps/agents-server/src/components/Homepage/Section.tsx +17 -0
- package/apps/agents-server/src/components/Homepage/TechInfoCard.tsx +20 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +6 -6
- package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +3 -8
- package/apps/agents-server/src/database/migrate.ts +131 -0
- package/apps/agents-server/src/database/{schema.sql → migrations/2025-11-0001-initial-schema.sql} +1 -17
- package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +16 -0
- package/apps/agents-server/src/middleware.ts +1 -1
- package/esm/index.es.js +1 -1
- package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +4 -1
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +1 -1
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { Card } from '../components/Homepage/Card';
|
|
3
5
|
import { $createAgentAction } from './actions';
|
|
4
6
|
|
|
5
7
|
export function AddAgentButton() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
|
|
6
10
|
const handleAddAgent = async () => {
|
|
7
11
|
await $createAgentAction();
|
|
8
12
|
// TODO: Add proper error handling and UI feedback
|
|
9
|
-
|
|
13
|
+
router.refresh();
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
return (
|
|
13
|
-
<div
|
|
14
|
-
|
|
15
|
-
onClick={handleAddAgent}
|
|
16
|
-
>
|
|
17
|
-
+ Add New Agent
|
|
17
|
+
<div onClick={handleAddAgent} className="cursor-pointer">
|
|
18
|
+
<Card>+ Add New Agent</Card>
|
|
18
19
|
</div>
|
|
19
20
|
);
|
|
20
21
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { metadataDefaults } from '
|
|
4
|
+
import { metadataDefaults } from '../../../database/metadataDefaults';
|
|
5
5
|
|
|
6
6
|
type MetadataEntry = {
|
|
7
7
|
id: number;
|
|
@@ -132,15 +132,11 @@ export function MetadataClient() {
|
|
|
132
132
|
<h1 className="text-3xl font-bold mb-8">Metadata Management</h1>
|
|
133
133
|
|
|
134
134
|
{error && (
|
|
135
|
-
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">
|
|
136
|
-
{error}
|
|
137
|
-
</div>
|
|
135
|
+
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">{error}</div>
|
|
138
136
|
)}
|
|
139
137
|
|
|
140
138
|
<div className="bg-white shadow rounded-lg p-6 mb-8">
|
|
141
|
-
<h2 className="text-xl font-semibold mb-4">
|
|
142
|
-
{editingId ? 'Edit Metadata' : 'Add New Metadata'}
|
|
143
|
-
</h2>
|
|
139
|
+
<h2 className="text-xl font-semibold mb-4">{editingId ? 'Edit Metadata' : 'Add New Metadata'}</h2>
|
|
144
140
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
145
141
|
<div>
|
|
146
142
|
<label htmlFor="key" className="block text-sm font-medium text-gray-700 mb-1">
|
|
@@ -235,12 +231,8 @@ export function MetadataClient() {
|
|
|
235
231
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
236
232
|
{entry.key}
|
|
237
233
|
</td>
|
|
238
|
-
<td className="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">
|
|
239
|
-
|
|
240
|
-
</td>
|
|
241
|
-
<td className="px-6 py-4 text-sm text-gray-500">
|
|
242
|
-
{entry.note || '-'}
|
|
243
|
-
</td>
|
|
234
|
+
<td className="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">{entry.value}</td>
|
|
235
|
+
<td className="px-6 py-4 text-sm text-gray-500">{entry.note || '-'}</td>
|
|
244
236
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
245
237
|
<button
|
|
246
238
|
onClick={() => handleEdit(entry)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ForbiddenPage } from '
|
|
2
|
-
import { isUserAdmin } from '
|
|
1
|
+
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
+
import { isUserAdmin } from '../../../utils/isUserAdmin';
|
|
3
3
|
import { MetadataClient } from './MetadataClient';
|
|
4
4
|
|
|
5
5
|
export default async function MetadataPage() {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
export async function GET(request: NextRequest) {
|
|
4
|
+
const protocol = request.nextUrl.protocol;
|
|
5
|
+
const host = request.nextUrl.host;
|
|
6
|
+
const baseUrl = `${protocol}//${host}`;
|
|
7
|
+
|
|
8
|
+
const script = `
|
|
9
|
+
(function() {
|
|
10
|
+
if (customElements.get('promptbook-agent')) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class PromptbookAgentElement extends HTMLElement {
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.iframe = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get observedAttributes() {
|
|
21
|
+
return ['agent-url'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
this.render();
|
|
26
|
+
window.addEventListener('message', this.handleMessage.bind(this));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
disconnectedCallback() {
|
|
30
|
+
window.removeEventListener('message', this.handleMessage.bind(this));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
34
|
+
if (name === 'agent-url' && oldValue !== newValue) {
|
|
35
|
+
this.render();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
handleMessage(event) {
|
|
40
|
+
if (event.data && event.data.type === 'PROMPTBOOK_AGENT_RESIZE') {
|
|
41
|
+
if (event.data.isOpen) {
|
|
42
|
+
this.iframe.style.width = '450px';
|
|
43
|
+
this.iframe.style.height = '650px';
|
|
44
|
+
this.iframe.style.maxHeight = '90vh';
|
|
45
|
+
this.iframe.style.maxWidth = '90vw';
|
|
46
|
+
this.iframe.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
|
47
|
+
this.iframe.style.borderRadius = '12px';
|
|
48
|
+
} else {
|
|
49
|
+
this.iframe.style.width = '60px';
|
|
50
|
+
this.iframe.style.height = '60px';
|
|
51
|
+
this.iframe.style.boxShadow = 'none';
|
|
52
|
+
this.iframe.style.borderRadius = '0';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render() {
|
|
58
|
+
const agentUrl = this.getAttribute('agent-url');
|
|
59
|
+
if (!agentUrl) return;
|
|
60
|
+
|
|
61
|
+
if (!this.iframe) {
|
|
62
|
+
this.attachShadow({ mode: 'open' });
|
|
63
|
+
this.iframe = document.createElement('iframe');
|
|
64
|
+
this.iframe.style.border = 'none';
|
|
65
|
+
this.iframe.style.position = 'fixed';
|
|
66
|
+
this.iframe.style.bottom = '20px';
|
|
67
|
+
this.iframe.style.right = '20px';
|
|
68
|
+
this.iframe.style.width = '60px';
|
|
69
|
+
this.iframe.style.height = '60px';
|
|
70
|
+
this.iframe.style.zIndex = '2147483647'; // Max z-index
|
|
71
|
+
this.iframe.style.transition = 'width 0.3s ease, height 0.3s ease';
|
|
72
|
+
this.iframe.style.backgroundColor = 'transparent';
|
|
73
|
+
this.iframe.setAttribute('allow', 'microphone'); // Allow microphone if needed for voice
|
|
74
|
+
this.shadowRoot.appendChild(this.iframe);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Construct embed URL pointing to the Next.js page we created
|
|
78
|
+
const embedUrl = '${baseUrl}/embed?agentUrl=' + encodeURIComponent(agentUrl);
|
|
79
|
+
this.iframe.src = embedUrl;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
customElements.define('promptbook-agent', PromptbookAgentElement);
|
|
84
|
+
})();
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
return new NextResponse(script, {
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/javascript',
|
|
90
|
+
'Access-Control-Allow-Origin': '*',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PromptbookAgent } from '@promptbook-local/components';
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
export default function EmbedPage() {
|
|
7
|
+
const searchParams = useSearchParams();
|
|
8
|
+
const agentUrl = searchParams.get('agentUrl');
|
|
9
|
+
|
|
10
|
+
if (!agentUrl) {
|
|
11
|
+
return <div className="text-red-500">Missing agentUrl parameter</div>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="w-full h-full bg-transparent">
|
|
16
|
+
<PromptbookAgent
|
|
17
|
+
agentUrl={agentUrl}
|
|
18
|
+
onOpenChange={(isOpen) => {
|
|
19
|
+
window.parent.postMessage({ type: 'PROMPTBOOK_AGENT_RESIZE', isOpen }, '*');
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import { getSingleLlmExecutionTools } from '@promptbook-local/core';
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
import { headers } from 'next/headers';
|
|
6
|
-
import Link from 'next/link';
|
|
7
|
-
import { AvatarProfile } from '../../../../src/book-components/AvatarProfile/AvatarProfile/AvatarProfile';
|
|
8
6
|
import { AboutPromptbookInformation } from '../../../../src/utils/misc/xAboutPromptbookInformation';
|
|
9
7
|
import { $sideEffect } from '../../../../src/utils/organization/$sideEffect';
|
|
10
8
|
import { AuthControls } from '../components/Auth/AuthControls';
|
|
9
|
+
import { AgentCard } from '../components/Homepage/AgentCard';
|
|
10
|
+
import { ModelCard } from '../components/Homepage/ModelCard';
|
|
11
|
+
import { Section } from '../components/Homepage/Section';
|
|
12
|
+
import { TechInfoCard } from '../components/Homepage/TechInfoCard';
|
|
11
13
|
import { UsersList } from '../components/UsersList/UsersList';
|
|
12
14
|
import VercelDeploymentCard from '../components/VercelDeploymentCard/VercelDeploymentCard';
|
|
13
15
|
import { getMetadata } from '../database/getMetadata';
|
|
@@ -15,8 +17,8 @@ import { getLongRunningTask } from '../deamons/longRunningTask';
|
|
|
15
17
|
import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
|
|
16
18
|
import { $provideExecutionToolsForServer } from '../tools/$provideExecutionToolsForServer';
|
|
17
19
|
import { $provideServer } from '../tools/$provideServer';
|
|
18
|
-
import { getFederatedAgents } from '../utils/getFederatedAgents';
|
|
19
20
|
import { getCurrentUser } from '../utils/getCurrentUser';
|
|
21
|
+
import { getFederatedAgents } from '../utils/getFederatedAgents';
|
|
20
22
|
import { isUserAdmin } from '../utils/isUserAdmin';
|
|
21
23
|
import { AddAgentButton } from './AddAgentButton';
|
|
22
24
|
|
|
@@ -52,7 +54,7 @@ export default async function HomePage() {
|
|
|
52
54
|
const executionTools = await $provideExecutionToolsForServer();
|
|
53
55
|
const models = await getSingleLlmExecutionTools(executionTools.llm).listModels();
|
|
54
56
|
|
|
55
|
-
const host = (await headers()).get('host');
|
|
57
|
+
const host = (await headers()).get('host') || 'unknown';
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
60
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
@@ -61,68 +63,34 @@ export default async function HomePage() {
|
|
|
61
63
|
<AuthControls initialUser={currentUser} />
|
|
62
64
|
</div>
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{...{ agent }}
|
|
71
|
-
style={
|
|
72
|
-
!agent.meta.color
|
|
73
|
-
? {}
|
|
74
|
-
: {
|
|
75
|
-
backgroundColor: `${agent.meta.color}22`, // <- TODO: Use Color object here
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
79
|
-
/>
|
|
80
|
-
</Link>
|
|
81
|
-
))}
|
|
82
|
-
{isAdmin && <AddAgentButton />}
|
|
83
|
-
</div>
|
|
84
|
-
</>
|
|
66
|
+
<Section title={`Agents (${agents.length})`}>
|
|
67
|
+
{agents.map((agent) => (
|
|
68
|
+
<AgentCard key={agent.agentName} agent={agent} href={`/${agent.agentName}`} />
|
|
69
|
+
))}
|
|
70
|
+
{isAdmin && <AddAgentButton />}
|
|
71
|
+
</Section>
|
|
85
72
|
|
|
86
73
|
{externalAgents.length > 0 && (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<AvatarProfile
|
|
93
|
-
{...{ agent }}
|
|
94
|
-
style={
|
|
95
|
-
!agent.meta.color
|
|
96
|
-
? {}
|
|
97
|
-
: {
|
|
98
|
-
backgroundColor: `${agent.meta.color}22`, // <- TODO: Use Color object here
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
102
|
-
/>
|
|
103
|
-
</Link>
|
|
104
|
-
))}
|
|
105
|
-
</div>
|
|
106
|
-
</>
|
|
74
|
+
<Section title={`External Agents (${externalAgents.length})`}>
|
|
75
|
+
{externalAgents.map((agent) => (
|
|
76
|
+
<AgentCard key={agent.url} agent={agent} href={agent.url} />
|
|
77
|
+
))}
|
|
78
|
+
</Section>
|
|
107
79
|
)}
|
|
108
80
|
|
|
109
81
|
{isAdmin && <UsersList />}
|
|
110
82
|
|
|
111
83
|
{isAdmin && (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
</Link>
|
|
123
|
-
))}
|
|
124
|
-
</div>
|
|
125
|
-
</>
|
|
84
|
+
<Section title={`Models (${models.length})`}>
|
|
85
|
+
{models.map(({ modelName, modelTitle, modelDescription }) => (
|
|
86
|
+
<ModelCard
|
|
87
|
+
key={modelName}
|
|
88
|
+
modelName={modelName}
|
|
89
|
+
modelTitle={modelTitle || modelName}
|
|
90
|
+
modelDescription={modelDescription}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</Section>
|
|
126
94
|
)}
|
|
127
95
|
|
|
128
96
|
{isAdmin && (
|
|
@@ -133,48 +101,27 @@ export default async function HomePage() {
|
|
|
133
101
|
)}
|
|
134
102
|
|
|
135
103
|
{isAdmin && (
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<VercelDeploymentCard />
|
|
158
|
-
|
|
159
|
-
<Link
|
|
160
|
-
href={'#'}
|
|
161
|
-
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
162
|
-
>
|
|
163
|
-
<h2 className="text-2xl font-semibold text-gray-900 mb-2">HTTP Information</h2>
|
|
164
|
-
|
|
165
|
-
<p className="text-gray-600">Host: {host}</p>
|
|
166
|
-
</Link>
|
|
167
|
-
|
|
168
|
-
<Link
|
|
169
|
-
href={'#'}
|
|
170
|
-
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
171
|
-
>
|
|
172
|
-
<h2 className="text-2xl font-semibold text-gray-900 mb-2">Server</h2>
|
|
173
|
-
|
|
174
|
-
<pre>{JSON.stringify(await $provideServer(), null, 2)}</pre>
|
|
175
|
-
</Link>
|
|
176
|
-
</div>
|
|
177
|
-
</>
|
|
104
|
+
<Section title="Technical Information">
|
|
105
|
+
<TechInfoCard title={`Long running task ${longRunningTask.taskId}`}>
|
|
106
|
+
<p className="text-gray-600">Tick: {longRunningTask.tick}</p>
|
|
107
|
+
<p className="text-gray-600">
|
|
108
|
+
Created At: {moment(longRunningTask.createdAt).calendar(undefined, calendarWithSeconds)}
|
|
109
|
+
</p>
|
|
110
|
+
<p className="text-gray-600">
|
|
111
|
+
Updated At: {moment(longRunningTask.updatedAt).calendar(undefined, calendarWithSeconds)}
|
|
112
|
+
</p>
|
|
113
|
+
</TechInfoCard>
|
|
114
|
+
|
|
115
|
+
<VercelDeploymentCard />
|
|
116
|
+
|
|
117
|
+
<TechInfoCard title="HTTP Information">
|
|
118
|
+
<p className="text-gray-600">Host: {host}</p>
|
|
119
|
+
</TechInfoCard>
|
|
120
|
+
|
|
121
|
+
<TechInfoCard title="Server">
|
|
122
|
+
<pre>{JSON.stringify(await $provideServer(), null, 2)}</pre>
|
|
123
|
+
</TechInfoCard>
|
|
124
|
+
</Section>
|
|
178
125
|
)}
|
|
179
126
|
</div>
|
|
180
127
|
</div>
|
|
@@ -59,21 +59,41 @@ export function Header(props: HeaderProps) {
|
|
|
59
59
|
{/* Desktop Navigation */}
|
|
60
60
|
<nav className="hidden md:flex items-center gap-8">
|
|
61
61
|
{isAdmin && (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
<>
|
|
63
|
+
<Link
|
|
64
|
+
href="/"
|
|
65
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
66
|
+
>
|
|
67
|
+
Agents
|
|
68
|
+
</Link>
|
|
69
|
+
<Link
|
|
70
|
+
href="/"
|
|
71
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
72
|
+
>
|
|
73
|
+
Models
|
|
74
|
+
</Link>
|
|
75
|
+
<Link
|
|
76
|
+
href="/admin/metadata"
|
|
77
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
78
|
+
>
|
|
79
|
+
Metadata
|
|
80
|
+
</Link>
|
|
81
|
+
<Link
|
|
82
|
+
href="https://ptbk.io/"
|
|
83
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
84
|
+
>
|
|
85
|
+
About
|
|
86
|
+
</Link>
|
|
87
|
+
</>
|
|
68
88
|
)}
|
|
69
89
|
|
|
70
90
|
{just(false /* TODO: [🧠] Figure out what to do with theese links */) && (
|
|
71
91
|
<Link
|
|
72
|
-
href="https://ptbk.io
|
|
92
|
+
href="https://ptbk.io/"
|
|
73
93
|
target="_blank"
|
|
74
94
|
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
75
95
|
>
|
|
76
|
-
|
|
96
|
+
Create your server
|
|
77
97
|
</Link>
|
|
78
98
|
)}
|
|
79
99
|
</nav>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
4
|
+
import { AvatarProfile } from '../../../../../src/book-components/AvatarProfile/AvatarProfile/AvatarProfile';
|
|
5
|
+
import { Card } from './Card';
|
|
6
|
+
|
|
7
|
+
type AgentCardProps = {
|
|
8
|
+
agent: AgentBasicInformation;
|
|
9
|
+
href: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function AgentCard({ agent, href }: AgentCardProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Link href={href}>
|
|
15
|
+
<Card
|
|
16
|
+
style={
|
|
17
|
+
!agent.meta.color
|
|
18
|
+
? {}
|
|
19
|
+
: {
|
|
20
|
+
backgroundColor: `${agent.meta.color}22`,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
>
|
|
24
|
+
<AvatarProfile agent={agent} />
|
|
25
|
+
</Card>
|
|
26
|
+
</Link>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type CardProps = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function Card({ children, className = '', style }: CardProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className={`block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400 ${className}`}
|
|
13
|
+
style={style}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Card } from './Card';
|
|
4
|
+
|
|
5
|
+
type ModelCardProps = {
|
|
6
|
+
modelName: string;
|
|
7
|
+
modelTitle: string;
|
|
8
|
+
modelDescription?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function ModelCard({ modelName, modelTitle, modelDescription }: ModelCardProps) {
|
|
12
|
+
return (
|
|
13
|
+
<Link href={`#[🐱🚀]`}>
|
|
14
|
+
<Card className="h-full flex flex-col">
|
|
15
|
+
<div className="flex justify-between items-start mb-2">
|
|
16
|
+
<h2 className="text-xl font-bold text-gray-900">{modelTitle}</h2>
|
|
17
|
+
<span className="inline-block bg-gray-100 text-gray-600 text-xs px-2 py-1 rounded font-mono">
|
|
18
|
+
{modelName}
|
|
19
|
+
</span>
|
|
20
|
+
</div>
|
|
21
|
+
{modelDescription && (
|
|
22
|
+
<p className="text-gray-600 text-sm mt-2 flex-grow leading-relaxed">
|
|
23
|
+
{modelDescription}
|
|
24
|
+
</p>
|
|
25
|
+
)}
|
|
26
|
+
</Card>
|
|
27
|
+
</Link>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type SectionProps = {
|
|
4
|
+
title: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function Section({ title, children }: SectionProps) {
|
|
9
|
+
return (
|
|
10
|
+
<section className="mt-16 first:mt-4 mb-4">
|
|
11
|
+
<h2 className="text-3xl text-gray-900 mb-6 font-light">{title}</h2>
|
|
12
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
</section>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Card } from './Card';
|
|
4
|
+
|
|
5
|
+
type TechInfoCardProps = {
|
|
6
|
+
title: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
href?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function TechInfoCard({ title, children, href = '#' }: TechInfoCardProps) {
|
|
12
|
+
return (
|
|
13
|
+
<Link href={href}>
|
|
14
|
+
<Card>
|
|
15
|
+
<h2 className="text-2xl font-semibold text-gray-900 mb-2">{title}</h2>
|
|
16
|
+
{children}
|
|
17
|
+
</Card>
|
|
18
|
+
</Link>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Card } from '../Homepage/Card';
|
|
5
|
+
import { Section } from '../Homepage/Section';
|
|
4
6
|
|
|
5
7
|
type User = {
|
|
6
8
|
id: number;
|
|
@@ -111,13 +113,11 @@ export function UsersList() {
|
|
|
111
113
|
|
|
112
114
|
return (
|
|
113
115
|
<div className="space-y-6">
|
|
114
|
-
<h2 className="text-3xl text-gray-900 mt-16 mb-4">Users ({users.length})</h2>
|
|
115
|
-
|
|
116
116
|
{error && <div className="bg-red-100 text-red-700 p-3 rounded">{error}</div>}
|
|
117
117
|
|
|
118
|
-
<
|
|
118
|
+
<Section title={`Users (${users.length})`}>
|
|
119
119
|
{users.map((user) => (
|
|
120
|
-
<
|
|
120
|
+
<Card key={user.id}>
|
|
121
121
|
<div className="flex justify-between items-start">
|
|
122
122
|
<div>
|
|
123
123
|
<h3 className="text-xl font-semibold text-gray-900">{user.username}</h3>
|
|
@@ -139,7 +139,7 @@ export function UsersList() {
|
|
|
139
139
|
</button>
|
|
140
140
|
</div>
|
|
141
141
|
</div>
|
|
142
|
-
</
|
|
142
|
+
</Card>
|
|
143
143
|
))}
|
|
144
144
|
|
|
145
145
|
{/* Create User Form */}
|
|
@@ -184,7 +184,7 @@ export function UsersList() {
|
|
|
184
184
|
</button>
|
|
185
185
|
</form>
|
|
186
186
|
</div>
|
|
187
|
-
</
|
|
187
|
+
</Section>
|
|
188
188
|
</div>
|
|
189
189
|
);
|
|
190
190
|
}
|
|
@@ -16,19 +16,14 @@ import {
|
|
|
16
16
|
NEXT_PUBLIC_VERCEL_TARGET_ENV,
|
|
17
17
|
NEXT_PUBLIC_VERCEL_URL,
|
|
18
18
|
} from '@/config';
|
|
19
|
-
import
|
|
19
|
+
import { TechInfoCard } from '../Homepage/TechInfoCard';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* [♐️] Expose Vercel environment variables to indentify the deployment
|
|
23
23
|
*/
|
|
24
24
|
export default function VercelDeploymentCard() {
|
|
25
25
|
return (
|
|
26
|
-
<
|
|
27
|
-
href="#"
|
|
28
|
-
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
29
|
-
>
|
|
30
|
-
<h2 className="text-2xl font-semibold text-gray-900 mb-4">Vercel Deployment</h2>
|
|
31
|
-
|
|
26
|
+
<TechInfoCard title="Vercel Deployment">
|
|
32
27
|
<p className="text-gray-600">NEXT_PUBLIC_VERCEL_ENV: {NEXT_PUBLIC_VERCEL_ENV}</p>
|
|
33
28
|
<p className="text-gray-600">NEXT_PUBLIC_VERCEL_TARGET_ENV: {NEXT_PUBLIC_VERCEL_TARGET_ENV}</p>
|
|
34
29
|
<p className="text-gray-600">NEXT_PUBLIC_VERCEL_URL: {NEXT_PUBLIC_VERCEL_URL}</p>
|
|
@@ -55,6 +50,6 @@ export default function VercelDeploymentCard() {
|
|
|
55
50
|
<p className="text-gray-600">
|
|
56
51
|
NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID: {NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID}
|
|
57
52
|
</p>
|
|
58
|
-
</
|
|
53
|
+
</TechInfoCard>
|
|
59
54
|
);
|
|
60
55
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { Client } from 'pg';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
async function migrate() {
|
|
9
|
+
console.info('🚀 Starting database migration');
|
|
10
|
+
|
|
11
|
+
// 1. Get configuration
|
|
12
|
+
const prefixesEnv = process.env.SUPABASE_MIGRATION_PREFIXES;
|
|
13
|
+
if (!prefixesEnv) {
|
|
14
|
+
console.warn('⚠️ SUPABASE_MIGRATION_PREFIXES is not defined. Skipping migration.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const prefixes = prefixesEnv
|
|
18
|
+
.split(',')
|
|
19
|
+
.map((p) => p.trim())
|
|
20
|
+
.filter((p) => p !== '');
|
|
21
|
+
|
|
22
|
+
if (prefixes.length === 0) {
|
|
23
|
+
console.warn('⚠️ No prefixes found in SUPABASE_MIGRATION_PREFIXES. Skipping migration.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const connectionString = process.env.POSTGRES_URL || process.env.DATABASE_URL;
|
|
28
|
+
if (!connectionString) {
|
|
29
|
+
console.error('❌ POSTGRES_URL or DATABASE_URL is not defined.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.info(`📋 Found ${prefixes.length} prefixes to migrate: ${prefixes.join(', ')}`);
|
|
34
|
+
|
|
35
|
+
// 2. Connect to database
|
|
36
|
+
const client = new Client({
|
|
37
|
+
connectionString,
|
|
38
|
+
ssl: { rejectUnauthorized: false }, // Required for some Supabase/Heroku connections
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await client.connect();
|
|
43
|
+
console.info('🔌 Connected to database');
|
|
44
|
+
|
|
45
|
+
// 3. Read migration files
|
|
46
|
+
const migrationsDir = path.join(__dirname, 'migrations');
|
|
47
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
48
|
+
console.error(`❌ Migrations directory not found at ${migrationsDir}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const migrationFiles = fs
|
|
53
|
+
.readdirSync(migrationsDir)
|
|
54
|
+
.filter((file) => file.endsWith('.sql'))
|
|
55
|
+
.sort(); // Ensure files are processed in order
|
|
56
|
+
|
|
57
|
+
console.info(`📂 Found ${migrationFiles.length} migration files`);
|
|
58
|
+
|
|
59
|
+
// 4. Iterate over prefixes and apply migrations
|
|
60
|
+
for (const prefix of prefixes) {
|
|
61
|
+
console.info(`\n🏗️ Migrating prefix: "${prefix}"`);
|
|
62
|
+
const migrationsTableName = `${prefix}Migrations`;
|
|
63
|
+
|
|
64
|
+
// 4.1 Create migrations table if not exists
|
|
65
|
+
const createMigrationsTableSql = `
|
|
66
|
+
CREATE TABLE IF NOT EXISTS "${migrationsTableName}" (
|
|
67
|
+
"filename" TEXT PRIMARY KEY,
|
|
68
|
+
"appliedAt" TIMESTAMP WITH TIME ZONE DEFAULT now()
|
|
69
|
+
);
|
|
70
|
+
`;
|
|
71
|
+
await client.query(createMigrationsTableSql);
|
|
72
|
+
|
|
73
|
+
// Enable RLS for migrations table
|
|
74
|
+
const enableRlsSql = `ALTER TABLE "${migrationsTableName}" ENABLE ROW LEVEL SECURITY;`;
|
|
75
|
+
await client.query(enableRlsSql);
|
|
76
|
+
|
|
77
|
+
// 4.2 Get applied migrations
|
|
78
|
+
const { rows: appliedMigrationsRows } = await client.query(
|
|
79
|
+
`SELECT "filename" FROM "${migrationsTableName}"`,
|
|
80
|
+
);
|
|
81
|
+
const appliedMigrations = new Set(appliedMigrationsRows.map((row) => row.filename));
|
|
82
|
+
|
|
83
|
+
// 4.3 Apply new migrations in one big transaction
|
|
84
|
+
let migrationError = null;
|
|
85
|
+
await client.query('BEGIN');
|
|
86
|
+
try {
|
|
87
|
+
for (const file of migrationFiles) {
|
|
88
|
+
if (appliedMigrations.has(file)) {
|
|
89
|
+
// console.info(` ⏭️ Skipping ${file} (already applied)`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.info(` 🚀 Applying ${file}...`);
|
|
94
|
+
const filePath = path.join(migrationsDir, file);
|
|
95
|
+
let sql = fs.readFileSync(filePath, 'utf-8');
|
|
96
|
+
|
|
97
|
+
// Replace prefix placeholder
|
|
98
|
+
sql = sql.replace(/prefix_/g, prefix);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await client.query(sql);
|
|
102
|
+
await client.query(`INSERT INTO "${migrationsTableName}" ("filename") VALUES ($1)`, [file]);
|
|
103
|
+
console.info(` ✅ Applied ${file}`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(` ❌ Failed to apply ${file}:`, error);
|
|
106
|
+
migrationError = error;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (migrationError) {
|
|
111
|
+
await client.query('ROLLBACK');
|
|
112
|
+
throw migrationError;
|
|
113
|
+
} else {
|
|
114
|
+
await client.query('COMMIT');
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
await client.query('ROLLBACK');
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.info('\n🎉 All migrations completed successfully');
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('\n❌ Migration failed:', error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
} finally {
|
|
127
|
+
await client.end();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
migrate();
|
package/apps/agents-server/src/database/{schema.sql → migrations/2025-11-0001-initial-schema.sql}
RENAMED
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
|
|
2
1
|
-- Note: This is primary source of truth for the database schema
|
|
3
2
|
-- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
|
|
4
3
|
-- To update, search for [💽]
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
CREATE TABLE IF NOT EXISTS "prefix_Metadata" (
|
|
8
|
-
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
9
|
-
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
10
|
-
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
11
|
-
"key" TEXT NOT NULL,
|
|
12
|
-
"value" TEXT NOT NULL,
|
|
13
|
-
"note" TEXT NULL,
|
|
14
|
-
CONSTRAINT prefix_Metadata_key_key UNIQUE ("key")
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
ALTER TABLE "prefix_Metadata" ENABLE ROW LEVEL SECURITY;
|
|
18
|
-
|
|
19
|
-
|
|
20
6
|
CREATE TABLE IF NOT EXISTS "prefix_Agent" (
|
|
21
7
|
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
22
8
|
"agentName" TEXT NOT NULL,
|
|
@@ -36,7 +22,7 @@ CREATE TABLE IF NOT EXISTS "prefix_Agent" (
|
|
|
36
22
|
"preparedExternals" JSONB NULL
|
|
37
23
|
);
|
|
38
24
|
-- Ensure uniqueness of agentName even on repeated schema application without duplicate constraint creation
|
|
39
|
-
CREATE UNIQUE INDEX IF NOT EXISTS prefix_agent_agentname_key ON "prefix_Agent" ("agentName");
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "prefix_agent_agentname_key" ON "prefix_Agent" ("agentName");
|
|
40
26
|
COMMENT ON COLUMN "prefix_Agent"."agentName" IS 'The unique name of the agent derived from the first line of the `agentSource`, automatically updated on `agentSource` change';
|
|
41
27
|
COMMENT ON COLUMN "prefix_Agent"."agentHash" IS 'The hash of the `agentSource`, automatically updated on `agentSource` change';
|
|
42
28
|
COMMENT ON COLUMN "prefix_Agent"."agentSource" IS 'The source code of the agent';
|
|
@@ -175,5 +161,3 @@ ALTER TABLE "prefix_ChatFeedback"
|
|
|
175
161
|
FOREIGN KEY ("agentName")
|
|
176
162
|
REFERENCES "prefix_Agent"("agentName")
|
|
177
163
|
ON DELETE CASCADE;
|
|
178
|
-
|
|
179
|
-
-- TODO: [🐱🚀] Create propper database migrations
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Note: This is primary source of truth for the database schema
|
|
2
|
+
-- In future we want to be compatible with more then Supabase so we keep SQL as main schema definition
|
|
3
|
+
-- To update, search for [💽]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "prefix_Metadata" (
|
|
7
|
+
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
8
|
+
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
9
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
10
|
+
"key" TEXT NOT NULL,
|
|
11
|
+
"value" TEXT NOT NULL,
|
|
12
|
+
"note" TEXT NULL,
|
|
13
|
+
CONSTRAINT "prefix_Metadata_key_key" UNIQUE ("key")
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
ALTER TABLE "prefix_Metadata" ENABLE ROW LEVEL SECURITY;
|
|
@@ -96,7 +96,7 @@ export async function middleware(req: NextRequest) {
|
|
|
96
96
|
|
|
97
97
|
if (
|
|
98
98
|
potentialAgentName &&
|
|
99
|
-
!['agents', 'api', '_next', 'favicon.ico'].includes(potentialAgentName) &&
|
|
99
|
+
!['agents', 'api', 'admin', 'embed', '_next', 'favicon.ico'].includes(potentialAgentName) &&
|
|
100
100
|
!potentialAgentName.startsWith('.') &&
|
|
101
101
|
// Note: Other static files are excluded by the matcher configuration below
|
|
102
102
|
true
|
package/esm/index.es.js
CHANGED
|
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
47
47
|
* @generated
|
|
48
48
|
* @see https://github.com/webgptorg/promptbook
|
|
49
49
|
*/
|
|
50
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-
|
|
50
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-51';
|
|
51
51
|
/**
|
|
52
52
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
53
53
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import './PromptbookAgent.css';
|
|
2
1
|
type PromptbookAgentProps = {
|
|
3
2
|
/**
|
|
4
3
|
* URL of the agent to connect to
|
|
@@ -6,6 +5,10 @@ type PromptbookAgentProps = {
|
|
|
6
5
|
* @example "http://s6.ptbk.io/benjamin-white"
|
|
7
6
|
*/
|
|
8
7
|
agentUrl: string;
|
|
8
|
+
/**
|
|
9
|
+
* Callback when the window is opened or closed
|
|
10
|
+
*/
|
|
11
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
9
12
|
};
|
|
10
13
|
/**
|
|
11
14
|
* Renders a floating agent button that opens a chat window with the remote agent.
|
|
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
|
|
|
15
15
|
export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
|
|
16
16
|
/**
|
|
17
17
|
* Represents the version string of the Promptbook engine.
|
|
18
|
-
* It follows semantic versioning (e.g., `0.103.0-
|
|
18
|
+
* It follows semantic versioning (e.g., `0.103.0-50`).
|
|
19
19
|
*
|
|
20
20
|
* @generated
|
|
21
21
|
*/
|
package/package.json
CHANGED
package/umd/index.umd.js
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
* @generated
|
|
57
57
|
* @see https://github.com/webgptorg/promptbook
|
|
58
58
|
*/
|
|
59
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-
|
|
59
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.103.0-51';
|
|
60
60
|
/**
|
|
61
61
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
62
62
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|