@promptbook/cli 0.113.0-0 → 0.113.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +7 -2
  2. package/apps/agents-server/src/app/api/page-preview/check/route.ts +5 -20
  3. package/apps/agents-server/src/app/api/page-preview/interact/route.ts +179 -0
  4. package/apps/agents-server/src/app/api/page-preview/live/route.ts +67 -0
  5. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +13 -21
  6. package/apps/agents-server/src/constants/chatVisualMode.ts +1 -1
  7. package/apps/agents-server/src/constants/themeMode.ts +1 -1
  8. package/apps/agents-server/src/database/migrations/2026-05-2600-default-theme.sql +2 -2
  9. package/apps/agents-server/src/utils/chatTasksAdmin.ts +3 -1
  10. package/apps/agents-server/src/utils/getAdminChatTasksResponse/mapVpsSelfUpdateJobToAdminChatTask.ts +84 -0
  11. package/apps/agents-server/src/utils/getAdminChatTasksResponse.ts +195 -5
  12. package/apps/agents-server/src/utils/pagePreview/livePagePreviewSessions.ts +365 -0
  13. package/apps/agents-server/src/utils/pagePreview/resolvePagePreviewRequestUrl.ts +74 -0
  14. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +12 -0
  15. package/esm/index.es.js +1 -1
  16. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  17. package/esm/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  18. package/esm/src/version.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/book-components/Chat/Chat/Chat.module.css +14 -5
  21. package/src/book-components/Chat/Chat/CitationIframePreview.tsx +197 -22
  22. package/src/book-components/Chat/utils/livePagePreviewConstants.ts +17 -0
  23. package/src/other/templates/getTemplatesPipelineCollection.ts +764 -750
  24. package/src/version.ts +2 -2
  25. package/src/versions.txt +1 -0
  26. package/umd/index.umd.js +1 -1
  27. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  28. package/umd/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  29. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Width of the browser viewport used for live knowledge previews.
3
+ *
4
+ * @private shared constant of Agents Server knowledge previews
5
+ */
6
+ export declare const LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH = 1280;
7
+ /**
8
+ * Height of the browser viewport used for live knowledge previews.
9
+ *
10
+ * @private shared constant of Agents Server knowledge previews
11
+ */
12
+ export declare const LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT = 800;
@@ -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.112.0`).
18
+ * It follows semantic versioning (e.g., `0.113.0-0`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.113.0-0",
3
+ "version": "0.113.0-1",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -3322,23 +3322,32 @@
3322
3322
  font-size: 0.9em;
3323
3323
  }
3324
3324
 
3325
- .citationScreenshotFallback {
3325
+ .citationBrowserPreviewFallback {
3326
3326
  width: 100%;
3327
3327
  position: relative;
3328
3328
  display: flex;
3329
3329
  flex-direction: column;
3330
3330
  }
3331
3331
 
3332
- .citationScreenshotImage {
3332
+ .citationBrowserPreviewImage {
3333
3333
  width: 100%;
3334
+ aspect-ratio: 16 / 10;
3334
3335
  max-height: 60vh;
3335
3336
  min-height: 400px;
3336
- object-fit: cover;
3337
+ object-fit: contain;
3337
3338
  object-position: top;
3338
3339
  display: block;
3340
+ background: #fff;
3341
+ cursor: crosshair;
3342
+ outline: none;
3343
+ touch-action: none;
3344
+ }
3345
+
3346
+ .citationBrowserPreviewImage:focus-visible {
3347
+ box-shadow: inset 0 0 0 3px rgba(0, 112, 243, 0.35);
3339
3348
  }
3340
3349
 
3341
- .citationScreenshotLink {
3350
+ .citationBrowserPreviewLink {
3342
3351
  display: block;
3343
3352
  text-align: center;
3344
3353
  padding: 10px 16px;
@@ -3351,7 +3360,7 @@
3351
3360
  transition: background 0.15s;
3352
3361
  }
3353
3362
 
3354
- .citationScreenshotLink:hover {
3363
+ .citationBrowserPreviewLink:hover {
3355
3364
  background: #e0e0e0;
3356
3365
  text-decoration: underline;
3357
3366
  }
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useState } from 'react';
3
+ import { useCallback, useEffect, useId, useMemo, useState } from 'react';
4
+ import type { KeyboardEvent, MouseEvent, WheelEvent } from 'react';
5
+ import { LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT, LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH } from '../utils/livePagePreviewConstants';
4
6
  import styles from './Chat.module.css';
5
7
 
6
8
  /**
@@ -18,12 +20,48 @@ export type CitationIframePreviewProps = {
18
20
  *
19
21
  * @private type of `<CitationIframePreview/>`
20
22
  */
21
- type EmbedStatus = 'loading' | 'embed' | 'screenshot';
23
+ type EmbedStatus = 'loading' | 'embed' | 'browser';
24
+
25
+ /**
26
+ * Browser-preview interaction sent to the Agents Server preview route.
27
+ *
28
+ * @private type of `<CitationIframePreview/>`
29
+ */
30
+ type LivePagePreviewInteraction =
31
+ | {
32
+ readonly type: 'click';
33
+ readonly x: number;
34
+ readonly y: number;
35
+ }
36
+ | {
37
+ readonly type: 'wheel';
38
+ readonly deltaX: number;
39
+ readonly deltaY: number;
40
+ }
41
+ | {
42
+ readonly type: 'keyDown';
43
+ readonly key: string;
44
+ };
45
+
46
+ /**
47
+ * Pointer position translated into the live-preview browser viewport.
48
+ *
49
+ * @private type of `<CitationIframePreview/>`
50
+ */
51
+ type LivePagePreviewPointerPosition = {
52
+ readonly x: number;
53
+ readonly y: number;
54
+ };
55
+
56
+ /**
57
+ * Keyboard keys that should not be forwarded as standalone browser actions.
58
+ */
59
+ const LIVE_PAGE_PREVIEW_IGNORED_KEYS = new Set(['Alt', 'CapsLock', 'Control', 'Meta', 'Shift']);
22
60
 
23
61
  /**
24
62
  * Renders a citation URL preview as an iframe when the target page allows embedding,
25
- * or falls back to a server-side screenshot with an "Open in new tab" link when it
26
- * does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
63
+ * or falls back to a live server-side browser preview with an "Open in new tab" link
64
+ * when it does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
27
65
  *
28
66
  * Embedding capability is determined by `GET /api/page-preview/check?url=<url>`.
29
67
  * If that endpoint is unavailable the component falls back to the iframe directly.
@@ -34,24 +72,24 @@ export function CitationIframePreview({ src, title }: CitationIframePreviewProps
34
72
  const [status, setStatus] = useState<EmbedStatus>('loading');
35
73
 
36
74
  useEffect(() => {
37
- let cancelled = false;
75
+ let isCancelled = false;
38
76
 
39
77
  fetch(`/api/page-preview/check?url=${encodeURIComponent(src)}`)
40
78
  .then((response) => response.json())
41
79
  .then((data: { canEmbed: boolean }) => {
42
- if (!cancelled) {
43
- setStatus(data.canEmbed ? 'embed' : 'screenshot');
80
+ if (!isCancelled) {
81
+ setStatus(data.canEmbed ? 'embed' : 'browser');
44
82
  }
45
83
  })
46
84
  .catch(() => {
47
85
  // API not available — fall back to iframe (e.g. outside agents-server)
48
- if (!cancelled) {
86
+ if (!isCancelled) {
49
87
  setStatus('embed');
50
88
  }
51
89
  });
52
90
 
53
91
  return () => {
54
- cancelled = true;
92
+ isCancelled = true;
55
93
  };
56
94
  }, [src]);
57
95
 
@@ -59,20 +97,157 @@ export function CitationIframePreview({ src, title }: CitationIframePreviewProps
59
97
  return <div className={styles.citationIframeLoading}>Loading preview…</div>;
60
98
  }
61
99
 
62
- if (status === 'screenshot') {
63
- return (
64
- <div className={styles.citationScreenshotFallback}>
65
- <img
66
- src={`/api/page-preview/screenshot?url=${encodeURIComponent(src)}`}
67
- alt={`Screenshot of ${title}`}
68
- className={styles.citationScreenshotImage}
69
- />
70
- <a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationScreenshotLink}>
71
- Open in new tab ↗
72
- </a>
73
- </div>
74
- );
100
+ if (status === 'browser') {
101
+ return <LiveBrowserPreview src={src} title={title} />;
75
102
  }
76
103
 
77
104
  return <iframe src={src} className={styles.citationIframe} title={title} />;
78
105
  }
106
+
107
+ /**
108
+ * Renders and controls a streamed browser preview for non-embeddable citation URLs.
109
+ *
110
+ * @private component of `<CitationIframePreview/>`
111
+ */
112
+ function LiveBrowserPreview({ src, title }: CitationIframePreviewProps) {
113
+ const reactSessionId = useId();
114
+ const sessionId = useMemo(() => createLivePagePreviewSessionId(reactSessionId), [reactSessionId]);
115
+ const streamSrc = useMemo(
116
+ () => `/api/page-preview/live?url=${encodeURIComponent(src)}&sessionId=${encodeURIComponent(sessionId)}`,
117
+ [sessionId, src],
118
+ );
119
+
120
+ const handleClick = useCallback(
121
+ (event: MouseEvent<HTMLImageElement>) => {
122
+ event.currentTarget.focus();
123
+ const position = resolveLivePagePreviewPointerPosition(event);
124
+ if (!position) {
125
+ return;
126
+ }
127
+
128
+ void postLivePagePreviewInteraction(sessionId, {
129
+ type: 'click',
130
+ x: position.x,
131
+ y: position.y,
132
+ });
133
+ },
134
+ [sessionId],
135
+ );
136
+
137
+ const handleWheel = useCallback(
138
+ (event: WheelEvent<HTMLImageElement>) => {
139
+ event.preventDefault();
140
+ void postLivePagePreviewInteraction(sessionId, {
141
+ type: 'wheel',
142
+ deltaX: event.deltaX,
143
+ deltaY: event.deltaY,
144
+ });
145
+ },
146
+ [sessionId],
147
+ );
148
+
149
+ const handleKeyDown = useCallback(
150
+ (event: KeyboardEvent<HTMLImageElement>) => {
151
+ if (event.altKey || event.ctrlKey || event.metaKey) {
152
+ return;
153
+ }
154
+
155
+ const key = normalizeLivePagePreviewKey(event.key);
156
+ if (!key) {
157
+ return;
158
+ }
159
+
160
+ event.preventDefault();
161
+ void postLivePagePreviewInteraction(sessionId, {
162
+ type: 'keyDown',
163
+ key,
164
+ });
165
+ },
166
+ [sessionId],
167
+ );
168
+
169
+ return (
170
+ <div className={styles.citationBrowserPreviewFallback}>
171
+ <img
172
+ src={streamSrc}
173
+ alt={`Live browser preview of ${title}`}
174
+ className={styles.citationBrowserPreviewImage}
175
+ onClick={handleClick}
176
+ onWheel={handleWheel}
177
+ onKeyDown={handleKeyDown}
178
+ tabIndex={0}
179
+ />
180
+ <a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationBrowserPreviewLink}>
181
+ Open in new tab ↗
182
+ </a>
183
+ </div>
184
+ );
185
+ }
186
+
187
+ /**
188
+ * Creates one browser-preview session id from React's hydration-stable id.
189
+ *
190
+ * @param reactSessionId - React-generated id for the component instance.
191
+ * @returns Session id shared by the stream and interaction endpoints.
192
+ */
193
+ function createLivePagePreviewSessionId(reactSessionId: string): string {
194
+ return `preview-${reactSessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
195
+ }
196
+
197
+ /**
198
+ * Converts one pointer event over the stream image to browser viewport coordinates.
199
+ *
200
+ * @param event - Pointer event from the stream image.
201
+ * @returns Browser viewport position or null when the image has no layout box.
202
+ */
203
+ function resolveLivePagePreviewPointerPosition(
204
+ event: MouseEvent<HTMLImageElement>,
205
+ ): LivePagePreviewPointerPosition | null {
206
+ const rect = event.currentTarget.getBoundingClientRect();
207
+ if (rect.width <= 0 || rect.height <= 0) {
208
+ return null;
209
+ }
210
+
211
+ return {
212
+ x: ((event.clientX - rect.left) / rect.width) * LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH,
213
+ y: ((event.clientY - rect.top) / rect.height) * LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Normalizes one browser keyboard key before forwarding it to Playwright.
219
+ *
220
+ * @param key - Browser KeyboardEvent key.
221
+ * @returns Playwright-compatible key name or null when the key should be ignored.
222
+ */
223
+ function normalizeLivePagePreviewKey(key: string): string | null {
224
+ if (!key || LIVE_PAGE_PREVIEW_IGNORED_KEYS.has(key)) {
225
+ return null;
226
+ }
227
+
228
+ if (key === ' ') {
229
+ return 'Space';
230
+ }
231
+
232
+ return key;
233
+ }
234
+
235
+ /**
236
+ * Sends one live-preview interaction to the Agents Server.
237
+ *
238
+ * @param sessionId - Active live-preview session id.
239
+ * @param interaction - Interaction to apply to the browser page.
240
+ */
241
+ async function postLivePagePreviewInteraction(
242
+ sessionId: string,
243
+ interaction: LivePagePreviewInteraction,
244
+ ): Promise<void> {
245
+ await fetch('/api/page-preview/interact', {
246
+ method: 'POST',
247
+ headers: { 'Content-Type': 'application/json' },
248
+ body: JSON.stringify({
249
+ sessionId,
250
+ ...interaction,
251
+ }),
252
+ }).catch(() => undefined);
253
+ }
@@ -0,0 +1,17 @@
1
+ /* eslint-disable no-magic-numbers */
2
+
3
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
4
+
5
+ /**
6
+ * Width of the browser viewport used for live knowledge previews.
7
+ *
8
+ * @private shared constant of Agents Server knowledge previews
9
+ */
10
+ export const LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH = 1280;
11
+
12
+ /**
13
+ * Height of the browser viewport used for live knowledge previews.
14
+ *
15
+ * @private shared constant of Agents Server knowledge previews
16
+ */
17
+ export const LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT = 800;