@promptbook/cli 0.112.0-97 → 0.112.0-99
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 +3 -3
- package/apps/agents-server/src/app/admin/cli-access/CliAccessClient.tsx +99 -0
- package/apps/agents-server/src/app/admin/cli-access/page.tsx +14 -0
- package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +76 -325
- package/apps/agents-server/src/app/admin/database/page.tsx +1 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
- package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
- package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +7 -64
- package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +3 -3
- package/apps/agents-server/src/app/api/admin/servers/route.ts +4 -4
- package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
- package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
- package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +4 -0
- package/apps/agents-server/src/database/$provideClientSql.ts +17 -4
- package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +24 -3
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -11
- package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +1 -20
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +1 -0
- package/apps/agents-server/src/languages/translations/english.yaml +1 -0
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
- package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
- package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
- package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +77 -237
- package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
- package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
- package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
- package/apps/agents-server/src/utils/serverRegistry.ts +4 -4
- package/apps/agents-server/src/utils/vpsConfiguration.ts +2 -0
- package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
- package/esm/index.es.js +2 -2
- package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
- package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
- package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/book-components/Chat/Chat/Chat.tsx +2 -0
- package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
- package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
- package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
- package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
- package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +706 -736
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
- package/umd/index.umd.js +2 -2
- package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
- package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
- package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/database/$providePostgresPool.ts +0 -27
- package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +0 -1261
- package/src/conversion/validation/_importPipeline.ts +0 -88
- /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
- /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal terminal-session shape used by the shared admin terminal hook.
|
|
7
|
+
*/
|
|
8
|
+
export type AdminTerminalSession = {
|
|
9
|
+
/**
|
|
10
|
+
* Session identifier.
|
|
11
|
+
*/
|
|
12
|
+
readonly id: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether the terminal process is still running.
|
|
16
|
+
*/
|
|
17
|
+
readonly isRunning: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Buffered terminal output.
|
|
21
|
+
*/
|
|
22
|
+
readonly output: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ISO timestamp when the session started.
|
|
26
|
+
*/
|
|
27
|
+
readonly startedAt: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ISO timestamp when the session finished.
|
|
31
|
+
*/
|
|
32
|
+
readonly finishedAt: string | null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Exit code when available.
|
|
36
|
+
*/
|
|
37
|
+
readonly exitCode: number | null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Exit signal when available.
|
|
41
|
+
*/
|
|
42
|
+
readonly signal: string | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shared API response shape used by the admin terminal routes.
|
|
47
|
+
*/
|
|
48
|
+
type AdminTerminalSessionResponse<TSession extends AdminTerminalSession> = {
|
|
49
|
+
/**
|
|
50
|
+
* Loaded or updated session snapshot.
|
|
51
|
+
*/
|
|
52
|
+
readonly session: TSession | null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* API error, when the request failed.
|
|
56
|
+
*/
|
|
57
|
+
readonly error?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Options accepted by the shared admin terminal hook.
|
|
62
|
+
*/
|
|
63
|
+
type UseAdminTerminalSessionOptions = {
|
|
64
|
+
/**
|
|
65
|
+
* Base API route used for GET/POST/PATCH/DELETE requests.
|
|
66
|
+
*/
|
|
67
|
+
readonly basePath: string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fallback error message used when loading the latest session fails.
|
|
71
|
+
*/
|
|
72
|
+
readonly loadErrorMessage: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fallback error message used when starting a new session fails.
|
|
76
|
+
*/
|
|
77
|
+
readonly startErrorMessage: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fallback error message used when sending input fails.
|
|
81
|
+
*/
|
|
82
|
+
readonly sendErrorMessage: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fallback error message used when stopping a session fails.
|
|
86
|
+
*/
|
|
87
|
+
readonly stopErrorMessage: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Success message shown after starting a new session.
|
|
91
|
+
*/
|
|
92
|
+
readonly startSuccessMessage: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Success message shown when the process exits with code `0`.
|
|
96
|
+
*/
|
|
97
|
+
readonly finishSuccessMessage: string;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Error message shown when the process exits with a non-zero code.
|
|
101
|
+
*/
|
|
102
|
+
readonly finishErrorMessage: string;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Shared browser hook for admin pages that start, stream, write to, and stop one terminal session.
|
|
107
|
+
*
|
|
108
|
+
* @param options - API endpoints and user-facing messages for the terminal.
|
|
109
|
+
* @returns Session state, status messages, and imperative helpers.
|
|
110
|
+
*/
|
|
111
|
+
export function useAdminTerminalSession<TSession extends AdminTerminalSession>(
|
|
112
|
+
options: UseAdminTerminalSessionOptions,
|
|
113
|
+
) {
|
|
114
|
+
const [session, setSession] = useState<TSession | null>(null);
|
|
115
|
+
const [input, setInput] = useState('');
|
|
116
|
+
const [isLoadingSession, setIsLoadingSession] = useState(true);
|
|
117
|
+
const [isStarting, setIsStarting] = useState(false);
|
|
118
|
+
const [isSending, setIsSending] = useState(false);
|
|
119
|
+
const [isStopping, setIsStopping] = useState(false);
|
|
120
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
121
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Loads the latest session snapshot for the current terminal.
|
|
125
|
+
*/
|
|
126
|
+
const loadSession = useCallback(async (): Promise<void> => {
|
|
127
|
+
try {
|
|
128
|
+
setIsLoadingSession(true);
|
|
129
|
+
setErrorMessage(null);
|
|
130
|
+
|
|
131
|
+
const response = await fetch(options.basePath, { cache: 'no-store' });
|
|
132
|
+
const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(payload.error || options.loadErrorMessage);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setSession(payload.session);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
setErrorMessage(error instanceof Error ? error.message : options.loadErrorMessage);
|
|
141
|
+
} finally {
|
|
142
|
+
setIsLoadingSession(false);
|
|
143
|
+
}
|
|
144
|
+
}, [options.basePath, options.loadErrorMessage]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
void loadSession();
|
|
148
|
+
}, [loadSession]);
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const sessionId = session?.id;
|
|
152
|
+
if (!sessionId) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const eventSource = new EventSource(`${options.basePath}?sessionId=${encodeURIComponent(sessionId)}&stream=1`);
|
|
157
|
+
|
|
158
|
+
const handleSnapshot = (event: MessageEvent<string>) => {
|
|
159
|
+
const payload = JSON.parse(event.data) as TSession;
|
|
160
|
+
setSession(payload);
|
|
161
|
+
};
|
|
162
|
+
const handleOutput = (event: MessageEvent<string>) => {
|
|
163
|
+
const payload = JSON.parse(event.data) as { readonly chunk: string };
|
|
164
|
+
setSession((currentSession) => {
|
|
165
|
+
if (!currentSession || currentSession.id !== sessionId) {
|
|
166
|
+
return currentSession;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
...currentSession,
|
|
171
|
+
output: currentSession.output + payload.chunk,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
const handleExit = (event: MessageEvent<string>) => {
|
|
176
|
+
const payload = JSON.parse(event.data) as TSession;
|
|
177
|
+
setSession(payload);
|
|
178
|
+
setIsStarting(false);
|
|
179
|
+
setIsSending(false);
|
|
180
|
+
setIsStopping(false);
|
|
181
|
+
|
|
182
|
+
if (payload.exitCode === 0) {
|
|
183
|
+
setSuccessMessage(options.finishSuccessMessage);
|
|
184
|
+
setErrorMessage(null);
|
|
185
|
+
} else {
|
|
186
|
+
setSuccessMessage(null);
|
|
187
|
+
setErrorMessage(options.finishErrorMessage);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
eventSource.close();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
eventSource.addEventListener('snapshot', handleSnapshot as EventListener);
|
|
194
|
+
eventSource.addEventListener('output', handleOutput as EventListener);
|
|
195
|
+
eventSource.addEventListener('exit', handleExit as EventListener);
|
|
196
|
+
eventSource.onerror = () => {
|
|
197
|
+
eventSource.close();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
eventSource.close();
|
|
202
|
+
};
|
|
203
|
+
}, [options.basePath, options.finishErrorMessage, options.finishSuccessMessage, session?.id]);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Starts or reconnects to the managed terminal session.
|
|
207
|
+
*/
|
|
208
|
+
const startSession = useCallback(async (): Promise<void> => {
|
|
209
|
+
try {
|
|
210
|
+
setIsStarting(true);
|
|
211
|
+
setErrorMessage(null);
|
|
212
|
+
setSuccessMessage(null);
|
|
213
|
+
|
|
214
|
+
const response = await fetch(options.basePath, { method: 'POST' });
|
|
215
|
+
const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
|
|
216
|
+
|
|
217
|
+
if (!response.ok || !payload.session) {
|
|
218
|
+
throw new Error(payload.error || options.startErrorMessage);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
setSession(payload.session);
|
|
222
|
+
setSuccessMessage(options.startSuccessMessage);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
setErrorMessage(error instanceof Error ? error.message : options.startErrorMessage);
|
|
225
|
+
} finally {
|
|
226
|
+
setIsStarting(false);
|
|
227
|
+
}
|
|
228
|
+
}, [options.basePath, options.startErrorMessage, options.startSuccessMessage]);
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Sends one raw input chunk to the running terminal session.
|
|
232
|
+
*
|
|
233
|
+
* @param nextInput - Raw text or control characters to write.
|
|
234
|
+
*/
|
|
235
|
+
const sendInput = useCallback(
|
|
236
|
+
async (nextInput: string): Promise<void> => {
|
|
237
|
+
if (!session) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
setIsSending(true);
|
|
243
|
+
setErrorMessage(null);
|
|
244
|
+
|
|
245
|
+
const response = await fetch(options.basePath, {
|
|
246
|
+
method: 'PATCH',
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify({
|
|
251
|
+
sessionId: session.id,
|
|
252
|
+
input: nextInput,
|
|
253
|
+
}),
|
|
254
|
+
});
|
|
255
|
+
const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
throw new Error(payload.error || options.sendErrorMessage);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (payload.session) {
|
|
262
|
+
setSession(payload.session);
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
setErrorMessage(error instanceof Error ? error.message : options.sendErrorMessage);
|
|
266
|
+
} finally {
|
|
267
|
+
setIsSending(false);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[options.basePath, options.sendErrorMessage, session],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Stops the active terminal session.
|
|
275
|
+
*/
|
|
276
|
+
const stopSession = useCallback(async (): Promise<void> => {
|
|
277
|
+
if (!session) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
setIsStopping(true);
|
|
283
|
+
setErrorMessage(null);
|
|
284
|
+
|
|
285
|
+
const response = await fetch(options.basePath, {
|
|
286
|
+
method: 'DELETE',
|
|
287
|
+
headers: {
|
|
288
|
+
'Content-Type': 'application/json',
|
|
289
|
+
},
|
|
290
|
+
body: JSON.stringify({
|
|
291
|
+
sessionId: session.id,
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
const payload = (await response.json()) as AdminTerminalSessionResponse<TSession>;
|
|
295
|
+
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
throw new Error(payload.error || options.stopErrorMessage);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (payload.session) {
|
|
301
|
+
setSession(payload.session);
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
setErrorMessage(error instanceof Error ? error.message : options.stopErrorMessage);
|
|
305
|
+
} finally {
|
|
306
|
+
setIsStopping(false);
|
|
307
|
+
}
|
|
308
|
+
}, [options.basePath, options.stopErrorMessage, session]);
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Clears any terminal status messages shown above the panel.
|
|
312
|
+
*/
|
|
313
|
+
const clearMessages = useCallback((): void => {
|
|
314
|
+
setErrorMessage(null);
|
|
315
|
+
setSuccessMessage(null);
|
|
316
|
+
}, []);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
session,
|
|
320
|
+
input,
|
|
321
|
+
setInput,
|
|
322
|
+
isLoadingSession,
|
|
323
|
+
isStarting,
|
|
324
|
+
isSending,
|
|
325
|
+
isStopping,
|
|
326
|
+
errorMessage,
|
|
327
|
+
successMessage,
|
|
328
|
+
setErrorMessage,
|
|
329
|
+
setSuccessMessage,
|
|
330
|
+
clearMessages,
|
|
331
|
+
loadSession,
|
|
332
|
+
startSession,
|
|
333
|
+
sendInput,
|
|
334
|
+
stopSession,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
@@ -220,6 +220,10 @@ export function buildHeaderSystemMenuItems({
|
|
|
220
220
|
label: translate('header.codeRunners'),
|
|
221
221
|
href: '/admin/code-runners',
|
|
222
222
|
} as SubMenuItem,
|
|
223
|
+
{
|
|
224
|
+
label: translate('header.cliAccess'),
|
|
225
|
+
href: '/admin/cli-access',
|
|
226
|
+
} as SubMenuItem,
|
|
223
227
|
]
|
|
224
228
|
: []),
|
|
225
229
|
];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { $isRunningInNode } from '@promptbook-local/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { resolvePostgresConnectionString } from './resolvePostgresConnectionString';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SQL tagged-template executor used by server routes and utilities.
|
|
@@ -30,6 +31,13 @@ export type ClientSqlExecutor = ClientSql & {
|
|
|
30
31
|
readonly raw: ClientSqlRaw;
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Shared PostgreSQL pool reused across all requests in the server process.
|
|
36
|
+
*
|
|
37
|
+
* @private internal singleton of Agents Server database layer
|
|
38
|
+
*/
|
|
39
|
+
let clientPool: Pool | undefined;
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* Provides SQL tagged-template client for server-side PostgreSQL access.
|
|
35
43
|
*
|
|
@@ -40,7 +48,12 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
|
|
|
40
48
|
throw new Error('Function `$provideClientSql` can only be used in Node.js runtime.');
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
if (!clientPool) {
|
|
52
|
+
clientPool = new Pool({
|
|
53
|
+
connectionString: resolvePostgresConnectionString(),
|
|
54
|
+
ssl: { rejectUnauthorized: false },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
44
57
|
|
|
45
58
|
const executeTemplate = async <TRow = Array<Record<string, unknown>>>(
|
|
46
59
|
templateStrings: TemplateStringsArray,
|
|
@@ -55,7 +68,7 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
|
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
const text = textChunks.join('');
|
|
58
|
-
const result = await clientPool
|
|
71
|
+
const result = await clientPool!.query(text, [...templateValues]);
|
|
59
72
|
return result.rows as TRow;
|
|
60
73
|
};
|
|
61
74
|
|
|
@@ -63,7 +76,7 @@ export async function $provideClientSql(): Promise<ClientSqlExecutor> {
|
|
|
63
76
|
text: string,
|
|
64
77
|
values: ReadonlyArray<unknown> = [],
|
|
65
78
|
): Promise<TRow> => {
|
|
66
|
-
const result = await clientPool
|
|
79
|
+
const result = await clientPool!.query(text, [...values]);
|
|
67
80
|
return result.rows as TRow;
|
|
68
81
|
};
|
|
69
82
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StudioBFFSqlLintDetails, StudioBFFSqlLintResult } from '@prisma/studio-core/data/bff';
|
|
2
|
-
import
|
|
3
|
-
import { $providePostgresPool } from './$providePostgresPool';
|
|
2
|
+
import { Pool, type PoolClient } from 'pg';
|
|
4
3
|
import { isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
|
|
4
|
+
import { resolvePostgresConnectionString } from './resolvePostgresConnectionString';
|
|
5
5
|
import {
|
|
6
6
|
$provideAgentsServerSqliteDatabase,
|
|
7
7
|
type AgentsServerSqliteDatabase,
|
|
@@ -31,6 +31,11 @@ export type DatabaseAdminExecutor = {
|
|
|
31
31
|
readonly lintSql: (details: StudioBFFSqlLintDetails) => Promise<StudioBFFSqlLintResult>;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Shared PostgreSQL pool for Embedded Prisma Studio requests.
|
|
36
|
+
*/
|
|
37
|
+
let databaseAdminPostgresPool: Pool | null = null;
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
40
|
* Provides a raw SQL executor for the configured Agents Server database backend.
|
|
36
41
|
*
|
|
@@ -48,7 +53,7 @@ export function $provideDatabaseAdminExecutor(): DatabaseAdminExecutor {
|
|
|
48
53
|
* @returns PostgreSQL-backed database admin executor.
|
|
49
54
|
*/
|
|
50
55
|
function $providePostgresDatabaseAdminExecutor(): DatabaseAdminExecutor {
|
|
51
|
-
const pool = $
|
|
56
|
+
const pool = $provideDatabaseAdminPostgresPool();
|
|
52
57
|
|
|
53
58
|
return {
|
|
54
59
|
execute: (query) => executePostgresDatabaseAdminQuery(pool, query),
|
|
@@ -77,6 +82,22 @@ function $provideSqliteDatabaseAdminExecutor(): DatabaseAdminExecutor {
|
|
|
77
82
|
};
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Provides the shared PostgreSQL connection pool for raw database admin access.
|
|
87
|
+
*
|
|
88
|
+
* @returns Shared PostgreSQL pool.
|
|
89
|
+
*/
|
|
90
|
+
function $provideDatabaseAdminPostgresPool(): Pool {
|
|
91
|
+
if (!databaseAdminPostgresPool) {
|
|
92
|
+
databaseAdminPostgresPool = new Pool({
|
|
93
|
+
connectionString: resolvePostgresConnectionString(),
|
|
94
|
+
ssl: { rejectUnauthorized: false },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return databaseAdminPostgresPool;
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
/**
|
|
81
102
|
* Executes one PostgreSQL query for Embedded Prisma Studio.
|
|
82
103
|
*
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
1
|
import { $isRunningInNode } from '@promptbook-local/utils';
|
|
3
2
|
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
4
|
-
import {
|
|
3
|
+
import { isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
|
|
5
4
|
import { $provideLocalSqliteSupabase } from './sqlite/$provideLocalSqliteSupabase';
|
|
6
5
|
import { AgentsServerDatabase } from './schema';
|
|
7
6
|
|
|
8
|
-
const requirePostgresSupabase = createRequire(__filename);
|
|
9
|
-
|
|
10
7
|
/**
|
|
11
8
|
* Internal cache for `$provideSupabaseForServer`
|
|
12
9
|
*
|
|
@@ -35,13 +32,6 @@ export function $provideSupabaseForServer(): SupabaseClient<AgentsServerDatabase
|
|
|
35
32
|
return $provideLocalSqliteSupabase() as SupabaseClient<AgentsServerDatabase>;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
if (isAgentsServerPostgresMode()) {
|
|
39
|
-
const { $provideLocalPostgresSupabase } = requirePostgresSupabase(
|
|
40
|
-
'./postgres/$provideLocalPostgresSupabase',
|
|
41
|
-
) as typeof import('./postgres/$provideLocalPostgresSupabase');
|
|
42
|
-
return $provideLocalPostgresSupabase() as SupabaseClient<AgentsServerDatabase>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
35
|
if (!supabase) {
|
|
46
36
|
// Create a single supabase client for interacting with your database
|
|
47
37
|
supabase = createClient<AgentsServerDatabase>(
|
|
@@ -11,7 +11,7 @@ export const AGENTS_SERVER_SQLITE_PATH_ENV_NAME = 'PTBK_AGENTS_SERVER_SQLITE_PAT
|
|
|
11
11
|
/**
|
|
12
12
|
* Supported Agents Server database backends.
|
|
13
13
|
*/
|
|
14
|
-
export type AgentsServerDatabaseMode = 'supabase' | 'sqlite'
|
|
14
|
+
export type AgentsServerDatabaseMode = 'supabase' | 'sqlite';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Resolves the configured Agents Server database backend.
|
|
@@ -23,10 +23,6 @@ export function resolveAgentsServerDatabaseMode(): AgentsServerDatabaseMode {
|
|
|
23
23
|
return 'sqlite';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
if (rawMode === 'postgres' || rawMode === 'postgresql') {
|
|
27
|
-
return 'postgres';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
return 'supabase';
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -36,18 +32,3 @@ export function resolveAgentsServerDatabaseMode(): AgentsServerDatabaseMode {
|
|
|
36
32
|
export function isAgentsServerSqliteMode(): boolean {
|
|
37
33
|
return resolveAgentsServerDatabaseMode() === 'sqlite';
|
|
38
34
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Returns whether the Agents Server is using the standalone PostgreSQL backend.
|
|
42
|
-
*/
|
|
43
|
-
export function isAgentsServerPostgresMode(): boolean {
|
|
44
|
-
return resolveAgentsServerDatabaseMode() === 'postgres';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Returns whether the Agents Server is using a standalone local database backend.
|
|
49
|
-
*/
|
|
50
|
-
export function isAgentsServerStandaloneMode(): boolean {
|
|
51
|
-
const databaseMode = resolveAgentsServerDatabaseMode();
|
|
52
|
-
return databaseMode === 'sqlite' || databaseMode === 'postgres';
|
|
53
|
-
}
|
|
@@ -140,6 +140,7 @@ header.update: Aktualizace
|
|
|
140
140
|
header.database: Databáze
|
|
141
141
|
header.logs: Logy
|
|
142
142
|
header.codeRunners: Code runnery
|
|
143
|
+
header.cliAccess: CLI přístup
|
|
143
144
|
header.models: Modely
|
|
144
145
|
header.openApiDocumentation: Dokumentace OpenAPI
|
|
145
146
|
header.apiTokens: API tokeny
|
|
@@ -142,6 +142,7 @@ header.update: Update
|
|
|
142
142
|
header.database: Database
|
|
143
143
|
header.logs: Logs
|
|
144
144
|
header.codeRunners: Code runners
|
|
145
|
+
header.cliAccess: CLI Access
|
|
145
146
|
header.models: Models
|
|
146
147
|
header.openApiDocumentation: OpenAPI Documentation
|
|
147
148
|
header.apiTokens: API Tokens
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NEXT_PUBLIC_SITE_URL, SUPABASE_TABLE_PREFIX } from '@/config';
|
|
2
2
|
import { headers } from 'next/headers';
|
|
3
3
|
import { cache } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { isAgentsServerSqliteMode } from '../database/agentsServerDatabaseMode';
|
|
5
5
|
import { resolveInternalServerOrigin } from '../utils/resolveInternalServerOrigin';
|
|
6
6
|
import { createServerPublicUrl, listRegisteredServersUsingServiceRole } from '../utils/serverRegistry';
|
|
7
7
|
import { resolveServerSelection } from '../utils/serverSelection';
|
|
@@ -37,7 +37,7 @@ const getCachedProvidedServer = cache(async (): Promise<ProvidedServer> => {
|
|
|
37
37
|
const requestHost = headersList.get('host');
|
|
38
38
|
const xPromptbookServer = headersList.get('x-promptbook-server');
|
|
39
39
|
|
|
40
|
-
if (
|
|
40
|
+
if (isAgentsServerSqliteMode()) {
|
|
41
41
|
if (isLocalDevelopmentHost(requestHost)) {
|
|
42
42
|
return {
|
|
43
43
|
id: null,
|
|
@@ -410,7 +410,7 @@ export class BrowserConnectionProvider {
|
|
|
410
410
|
await mkdir(userDataDir, { recursive: true });
|
|
411
411
|
|
|
412
412
|
const launchOptions: NonNullable<Parameters<typeof chromium.launchPersistentContext>[1]> = {
|
|
413
|
-
headless:
|
|
413
|
+
headless: true,
|
|
414
414
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
415
415
|
};
|
|
416
416
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ChatMessage } from '../../../../../src/book-components/Chat/types/ChatMessage';
|
|
2
|
+
import type { ChatParticipant } from '../../../../../src/book-components/Chat/types/ChatParticipant';
|
|
3
|
+
import { downloadBlob, parseFilenameFromContentDisposition } from '../download/browserFileDownload';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Payload sent to the Agents Server PDF export endpoint.
|
|
7
|
+
*
|
|
8
|
+
* @private internal type for `downloadChatPdfFromServer`
|
|
9
|
+
*/
|
|
10
|
+
type DownloadChatPdfFromServerOptions = {
|
|
11
|
+
readonly title: string;
|
|
12
|
+
readonly messages: ReadonlyArray<ChatMessage>;
|
|
13
|
+
readonly participants: ReadonlyArray<ChatParticipant>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Requests a server-rendered chat PDF and triggers the browser download.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Chat export payload.
|
|
20
|
+
*/
|
|
21
|
+
export async function downloadChatPdfFromServer(options: DownloadChatPdfFromServerOptions): Promise<void> {
|
|
22
|
+
const response = await fetch('/api/chat/export/pdf', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
title: options.title,
|
|
29
|
+
messages: options.messages,
|
|
30
|
+
participants: options.participants.map(serializeChatParticipantForExport),
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const payload = (await response.json().catch(() => null)) as { error?: string } | null;
|
|
36
|
+
throw new Error(payload?.error || 'Failed to export chat as PDF.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pdfBlob = await response.blob();
|
|
40
|
+
const filename =
|
|
41
|
+
parseFilenameFromContentDisposition(response.headers.get('Content-Disposition')) || 'chat-export.pdf';
|
|
42
|
+
|
|
43
|
+
downloadBlob(pdfBlob, filename);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Normalizes participant data so the payload stays JSON-safe when custom color helpers are used.
|
|
48
|
+
*
|
|
49
|
+
* @param participant - One chat participant.
|
|
50
|
+
* @returns Serializable participant payload.
|
|
51
|
+
*
|
|
52
|
+
* @private helper for `downloadChatPdfFromServer`
|
|
53
|
+
*/
|
|
54
|
+
function serializeChatParticipantForExport(participant: ChatParticipant): ChatParticipant {
|
|
55
|
+
return {
|
|
56
|
+
...participant,
|
|
57
|
+
color: participant.color ? String(participant.color) : undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser viewport used while rendering standalone HTML exports to PDF.
|
|
5
|
+
*/
|
|
6
|
+
const CHAT_EXPORT_PDF_VIEWPORT = {
|
|
7
|
+
width: 1280,
|
|
8
|
+
height: 1600,
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Prints standalone HTML into a PDF using the shared server-side Chromium instance.
|
|
13
|
+
*
|
|
14
|
+
* @param html - Fully rendered standalone HTML document.
|
|
15
|
+
* @returns PDF bytes ready for an HTTP response.
|
|
16
|
+
*/
|
|
17
|
+
export async function renderHtmlToPdfOnServer(html: string): Promise<Buffer> {
|
|
18
|
+
const browserContext = await $provideBrowserForServer();
|
|
19
|
+
const page = await browserContext.newPage();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await page.setViewportSize(CHAT_EXPORT_PDF_VIEWPORT);
|
|
23
|
+
await page.emulateMedia({ media: 'print' });
|
|
24
|
+
await page.setContent(html, { waitUntil: 'load' });
|
|
25
|
+
await page.evaluate(async () => {
|
|
26
|
+
await document.fonts?.ready;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return await page.pdf({
|
|
30
|
+
format: 'Letter',
|
|
31
|
+
printBackground: true,
|
|
32
|
+
preferCSSPageSize: true,
|
|
33
|
+
});
|
|
34
|
+
} finally {
|
|
35
|
+
await page.close();
|
|
36
|
+
}
|
|
37
|
+
}
|