@kapeta/local-cluster-service 0.67.1 → 0.67.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.67.3](https://github.com/kapetacom/local-cluster-service/compare/v0.67.2...v0.67.3) (2024-08-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Several fixes for prompting ([#229](https://github.com/kapetacom/local-cluster-service/issues/229)) ([9dfa649](https://github.com/kapetacom/local-cluster-service/commit/9dfa6497dd3cc74e6ce460eda8b6a3616d1d493d))
7
+
8
+ ## [0.67.2](https://github.com/kapetacom/local-cluster-service/compare/v0.67.1...v0.67.2) (2024-08-28)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Add stack trace to error event ([ad52d4d](https://github.com/kapetacom/local-cluster-service/commit/ad52d4d22b20c0f4e34f83e0a9bc167599b02a08))
14
+ * Don't check file existence when path is empty ([8d9aa1a](https://github.com/kapetacom/local-cluster-service/commit/8d9aa1a180c38f53a0ae6cd122fedc20c8a78307))
15
+
1
16
  ## [0.67.1](https://github.com/kapetacom/local-cluster-service/compare/v0.67.0...v0.67.1) (2024-08-28)
2
17
 
3
18
 
@@ -16,13 +16,19 @@ export interface ImagePrompt {
16
16
  url: string;
17
17
  content: string;
18
18
  }
19
+ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
20
+ shellType?: 'public' | 'admin' | 'user';
21
+ };
19
22
  export declare class PageQueue extends EventEmitter {
20
23
  private readonly queue;
21
24
  private readonly systemId;
25
+ private readonly systemPrompt;
22
26
  private readonly references;
27
+ private readonly pages;
28
+ private readonly images;
23
29
  private uiShells;
24
30
  private theme;
25
- constructor(systemId: string, concurrency?: number);
31
+ constructor(systemId: string, systemPrompt: string, concurrency?: number);
26
32
  on(event: 'event', listener: (data: StormEvent) => void): this;
27
33
  on(event: 'page', listener: (data: StormEventPage) => void): this;
28
34
  on(event: 'image', listener: (data: StormImage, source: ImagePrompt, future: FuturePromise<void>) => void): this;
@@ -31,7 +37,10 @@ export declare class PageQueue extends EventEmitter {
31
37
  emit(type: 'image', event: StormImage, source: ImagePrompt, future: FuturePromise<void>): boolean;
32
38
  addUiShell(uiShell: UIShell): void;
33
39
  setUiTheme(theme: string): void;
34
- addPrompt(initialPrompt: Omit<UIPagePrompt, 'shell_page'>, conversationId?: string, overwrite?: boolean): Promise<void>;
40
+ private hasPrompt;
41
+ addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean): Promise<void>;
42
+ private getPrefix;
43
+ private wrapPagePrompt;
35
44
  private addPageGenerator;
36
45
  cancel(): void;
37
46
  wait(): Promise<void>;
@@ -39,18 +48,21 @@ export declare class PageQueue extends EventEmitter {
39
48
  }
40
49
  export declare class PageGenerator extends EventEmitter {
41
50
  private readonly conversationId;
42
- private prompt;
51
+ readonly prompt: UIPagePrompt;
43
52
  constructor(prompt: UIPagePrompt, conversationId?: string);
44
53
  on(event: 'event', listener: (data: StormEvent) => void): this;
45
54
  on(event: 'page_refs', listener: (data: {
46
55
  event: StormEventPage;
47
56
  references: ReferenceClassification[];
57
+ future: FuturePromise<void>;
48
58
  }) => void): this;
49
59
  emit(type: 'event', event: StormEvent): boolean;
50
60
  emit(type: 'page_refs', event: {
51
61
  event: StormEventPage;
52
62
  references: ReferenceClassification[];
63
+ future: FuturePromise<void>;
53
64
  }): boolean;
54
65
  generate(): Promise<void>;
55
66
  private resolveReferences;
56
67
  }
68
+ export {};
@@ -16,12 +16,16 @@ const page_utils_1 = require("./page-utils");
16
16
  class PageQueue extends node_events_1.EventEmitter {
17
17
  queue;
18
18
  systemId;
19
+ systemPrompt;
19
20
  references = new Map();
21
+ pages = new Map();
22
+ images = new Map();
20
23
  uiShells = [];
21
24
  theme = '';
22
- constructor(systemId, concurrency = 5) {
25
+ constructor(systemId, systemPrompt, concurrency = 5) {
23
26
  super();
24
27
  this.systemId = systemId;
28
+ this.systemPrompt = systemPrompt;
25
29
  this.queue = new PromiseQueue_1.PromiseQueue(concurrency);
26
30
  }
27
31
  on(event, listener) {
@@ -36,68 +40,128 @@ class PageQueue extends node_events_1.EventEmitter {
36
40
  setUiTheme(theme) {
37
41
  this.theme = theme;
38
42
  }
39
- addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false) {
40
- if (!overwrite && this.references.has(initialPrompt.path)) {
43
+ hasPrompt(path) {
44
+ if (this.references.has(path)) {
41
45
  //console.log('Ignoring duplicate prompt', initialPrompt.path);
42
- return Promise.resolve();
46
+ return true;
43
47
  }
44
- if (!overwrite && (0, page_utils_1.hasPageOnDisk)(this.systemId, initialPrompt.method, initialPrompt.path)) {
48
+ if ((0, page_utils_1.hasPageOnDisk)(this.systemId, 'GET', path)) {
45
49
  //console.log('Ignoring prompt with existing page', initialPrompt.path);
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false) {
55
+ if (!overwrite && this.hasPrompt(initialPrompt.path)) {
56
+ //console.log('Ignoring duplicate prompt', initialPrompt.path);
46
57
  return Promise.resolve();
47
58
  }
48
59
  const prompt = {
49
60
  ...initialPrompt,
50
61
  shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
62
+ prompt: this.wrapPagePrompt(initialPrompt.path, initialPrompt.prompt),
51
63
  theme: this.theme,
52
64
  };
53
65
  const generator = new PageGenerator(prompt, conversationId);
54
66
  this.references.set(prompt.path, generator);
67
+ this.pages.set(prompt.path, prompt.description);
55
68
  return this.addPageGenerator(generator);
56
69
  }
57
- async addPageGenerator(generator) {
58
- generator.on('event', (event) => this.emit('event', event));
59
- generator.on('page_refs', async ({ event, references }) => {
60
- const promises = references.map(async (reference) => {
61
- if (reference.url.startsWith('#') ||
62
- reference.url.startsWith('javascript:') ||
63
- reference.url.startsWith('http://') ||
64
- reference.url.startsWith('https://')) {
70
+ getPrefix() {
71
+ let promptPrefix = '';
72
+ if (this.systemPrompt) {
73
+ promptPrefix = `For a system with this description: ${this.systemPrompt}\n`;
74
+ }
75
+ return promptPrefix;
76
+ }
77
+ wrapPagePrompt(pagePath, prompt) {
78
+ let promptPrefix = this.getPrefix();
79
+ let promptPostfix = '';
80
+ if (this.pages.size > 0) {
81
+ promptPostfix = `\nThe following pages are already implemented:\n`;
82
+ this.pages.forEach((description, path) => {
83
+ if (pagePath === path) {
65
84
  return;
66
85
  }
67
- switch (reference.type) {
68
- case 'image':
69
- await this.addImagePrompt({
70
- ...reference,
71
- content: event.payload.content,
72
- });
73
- break;
74
- case 'css':
75
- case 'javascript':
76
- //console.log('Ignoring reference', reference);
77
- break;
78
- case 'html':
79
- //console.log('Adding page generator for', reference);
80
- const paths = Array.from(this.references.keys());
81
- this.addPrompt({
82
- name: reference.name,
83
- title: reference.title,
84
- path: reference.url,
85
- method: 'GET',
86
- storage_prefix: this.systemId + '_',
87
- prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
88
- `The page was referenced from this page: \`\`\`html\n${event.payload.content}\n\`\`\`\n` +
89
- (paths.length > 0
90
- ? `\nThese paths are already implemented:\n- ${paths.join('\n - ')}\n\n`
91
- : ''),
92
- description: reference.description,
93
- filename: '',
94
- theme: this.theme,
95
- });
96
- break;
97
- }
86
+ promptPostfix += `- PAGE: '${path}' -> ${description}.\n`;
87
+ });
88
+ }
89
+ if (this.images.size > 0) {
90
+ promptPostfix += `\nThe following images already exist:\n`;
91
+ this.images.forEach((description, path) => {
92
+ promptPostfix += `- IMAGE: '${path}' -> ${description}.\n`;
98
93
  });
99
- await Promise.allSettled(promises);
100
- this.emit('page', event);
94
+ }
95
+ return promptPrefix + prompt + promptPostfix;
96
+ }
97
+ async addPageGenerator(generator) {
98
+ generator.on('event', (event) => this.emit('event', event));
99
+ generator.on('page_refs', async ({ event, references, future }) => {
100
+ try {
101
+ const initialPrompts = [];
102
+ let promises = references.map(async (reference) => {
103
+ if (reference.url.startsWith('#') ||
104
+ reference.url.startsWith('javascript:') ||
105
+ reference.url.startsWith('http://') ||
106
+ reference.url.startsWith('https://')) {
107
+ return;
108
+ }
109
+ switch (reference.type) {
110
+ case 'image':
111
+ await this.addImagePrompt({
112
+ ...reference,
113
+ content: event.payload.content,
114
+ });
115
+ break;
116
+ case 'css':
117
+ case 'javascript':
118
+ //console.log('Ignoring reference', reference);
119
+ break;
120
+ case 'html':
121
+ //console.log('Adding page generator for', reference);
122
+ this.pages.set(reference.url, reference.description);
123
+ initialPrompts.push({
124
+ name: reference.name,
125
+ title: reference.title,
126
+ path: reference.url,
127
+ method: 'GET',
128
+ storage_prefix: this.systemId + '_',
129
+ prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
130
+ `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
131
+ description: reference.description,
132
+ filename: '',
133
+ theme: this.theme,
134
+ });
135
+ break;
136
+ }
137
+ });
138
+ await Promise.allSettled(promises);
139
+ initialPrompts.forEach((prompt) => {
140
+ if (!this.hasPrompt(prompt.path)) {
141
+ this.emit('page', {
142
+ type: 'PAGE',
143
+ reason: 'reference',
144
+ created: Date.now(),
145
+ payload: {
146
+ name: prompt.name,
147
+ title: prompt.title,
148
+ filename: '',
149
+ method: 'GET',
150
+ path: prompt.path,
151
+ prompt: prompt.description,
152
+ conversationId: '',
153
+ content: '',
154
+ description: prompt.description,
155
+ },
156
+ });
157
+ }
158
+ this.addPrompt(prompt);
159
+ });
160
+ this.emit('page', event);
161
+ }
162
+ finally {
163
+ future.resolve();
164
+ }
101
165
  });
102
166
  return this.queue.add(() => generator.generate());
103
167
  }
@@ -108,7 +172,14 @@ class PageQueue extends node_events_1.EventEmitter {
108
172
  return this.queue.wait();
109
173
  }
110
174
  async addImagePrompt(prompt) {
111
- const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
175
+ if (this.images.has(prompt.url)) {
176
+ //console.log('Ignoring duplicate image prompt', prompt);
177
+ return;
178
+ }
179
+ this.images.set(prompt.url, prompt.description);
180
+ const prefix = this.getPrefix();
181
+ const result = await stormClient_1.stormClient.createImage(prefix + `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
182
+ //console.log('Adding image prompt', prompt);
112
183
  const futures = [];
113
184
  result.on('data', async (event) => {
114
185
  if (event.type === 'IMAGE') {
@@ -144,27 +215,29 @@ class PageGenerator extends node_events_1.EventEmitter {
144
215
  }
145
216
  async generate() {
146
217
  return new Promise(async (resolve) => {
147
- const screenStream = await stormClient_1.stormClient.createUIPage(this.prompt, this.conversationId);
148
218
  const promises = [];
219
+ const screenStream = await stormClient_1.stormClient.createUIPage(this.prompt, this.conversationId);
149
220
  screenStream.on('data', (event) => {
150
221
  if (event.type === 'PAGE') {
151
222
  event.payload.conversationId = this.conversationId;
152
223
  promises.push((async () => {
153
224
  const references = await this.resolveReferences(event.payload.content);
154
225
  //console.log('Resolved references for page', references, event.payload);
226
+ const future = (0, PromiseQueue_1.createFuture)();
155
227
  this.emit('page_refs', {
156
228
  event,
157
229
  references,
230
+ future,
158
231
  });
232
+ await future.promise;
159
233
  })());
160
234
  return;
161
235
  }
162
236
  this.emit('event', event);
163
237
  });
164
- screenStream.on('end', () => {
165
- Promise.allSettled(promises).finally(resolve);
166
- });
167
238
  await screenStream.waitForDone();
239
+ await Promise.all(promises);
240
+ resolve();
168
241
  });
169
242
  }
170
243
  async resolveReferences(content) {
@@ -111,6 +111,7 @@ export interface StormEventError {
111
111
  created: number;
112
112
  payload: {
113
113
  error: string;
114
+ stack?: string;
114
115
  };
115
116
  }
116
117
  export interface StormEventErrorClassifier {
@@ -53,6 +53,9 @@ async function writeImageToDisk(systemId, event, prompt) {
53
53
  }
54
54
  exports.writeImageToDisk = writeImageToDisk;
55
55
  function hasPageOnDisk(systemId, method, path) {
56
+ if (!systemId || !method || !path) {
57
+ return false;
58
+ }
56
59
  const baseDir = getSystemBaseDir(systemId);
57
60
  const filePath = getFilePath(method);
58
61
  const fullPath = path_1.default.join(baseDir, normalizePath(path), filePath);
@@ -23,6 +23,7 @@ const page_utils_1 = require("./page-utils");
23
23
  const UIServer_1 = require("./UIServer");
24
24
  const crypto_1 = require("crypto");
25
25
  const PageGenerator_1 = require("./PageGenerator");
26
+ const PromiseQueue_1 = require("./PromiseQueue");
26
27
  const UI_SERVERS = {};
27
28
  const router = (0, express_promise_router_1.default)();
28
29
  router.use('/', cors_1.corsHandler);
@@ -47,7 +48,7 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
47
48
  description: screenData.payload.description,
48
49
  prompt: screenData.payload.prompt,
49
50
  path: screenData.payload.path,
50
- url: server ? server.resolveUrl(screenData) : '',
51
+ url: server && screenData.payload.content ? server.resolveUrl(screenData) : '',
51
52
  method: screenData.payload.method,
52
53
  conversationId: innerConversationId,
53
54
  },
@@ -68,7 +69,7 @@ router.post('/ui/screen', async (req, res) => {
68
69
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
69
70
  res.set(stormClient_1.ConversationIdHeader, conversationId);
70
71
  const parentConversationId = systemId ?? '';
71
- const queue = new PageGenerator_1.PageQueue(parentConversationId, 5);
72
+ const queue = new PageGenerator_1.PageQueue(parentConversationId, '', 5);
72
73
  onRequestAborted(req, res, () => {
73
74
  queue.cancel();
74
75
  });
@@ -100,6 +101,8 @@ router.post('/ui/screen', async (req, res) => {
100
101
  }
101
102
  catch (err) {
102
103
  sendError(err, res);
104
+ }
105
+ finally {
103
106
  if (!res.closed) {
104
107
  res.end();
105
108
  }
@@ -133,10 +136,16 @@ router.post('/:handle/ui/iterative', async (req, res) => {
133
136
  const promises = {};
134
137
  const pageEventPromises = [];
135
138
  const systemId = landingPagesStream.getConversationId();
136
- const pageQueue = new PageGenerator_1.PageQueue(systemId, 5);
139
+ const systemPrompt = (0, PromiseQueue_1.createFuture)();
140
+ if (aiRequest.skipImprovement) {
141
+ systemPrompt.resolve(aiRequest.prompt);
142
+ }
137
143
  landingPagesStream.on('data', async (data) => {
138
144
  try {
139
145
  sendEvent(res, data);
146
+ if (data.type === 'PROMPT_IMPROVE') {
147
+ systemPrompt.resolve(data.payload.prompt);
148
+ }
140
149
  if (data.type !== 'LANDING_PAGE') {
141
150
  return;
142
151
  }
@@ -168,6 +177,12 @@ router.post('/:handle/ui/iterative', async (req, res) => {
168
177
  });
169
178
  UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
170
179
  await UI_SERVERS[systemId].start();
180
+ waitForStormStream(landingPagesStream).then(() => {
181
+ if (!systemPrompt.isResolved()) {
182
+ systemPrompt.resolve(aiRequest.prompt);
183
+ }
184
+ });
185
+ const pageQueue = new PageGenerator_1.PageQueue(systemId, await systemPrompt.promise, 5);
171
186
  onRequestAborted(req, res, () => {
172
187
  pageQueue.cancel();
173
188
  });
@@ -218,9 +233,13 @@ router.post('/:handle/ui', async (req, res) => {
218
233
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
219
234
  res.set(stormClient_1.ConversationIdHeader, outerConversationId);
220
235
  const uniqueUserJourneyScreens = {};
236
+ let systemPrompt = aiRequest.prompt;
221
237
  userJourneysStream.on('data', (data) => {
222
238
  try {
223
239
  sendEvent(res, data);
240
+ if (data.type === 'PROMPT_IMPROVE') {
241
+ systemPrompt = data.payload.prompt;
242
+ }
224
243
  if (data.type !== 'USER_JOURNEY') {
225
244
  return;
226
245
  }
@@ -249,7 +268,6 @@ router.post('/:handle/ui', async (req, res) => {
249
268
  themeStream.abort();
250
269
  });
251
270
  themeStream.on('data', (evt) => {
252
- sendEvent(res, evt);
253
271
  if (evt.type === 'FILE_DONE') {
254
272
  theme = evt.payload.content;
255
273
  (0, page_utils_1.writeAssetToDisk)(outerConversationId, evt).catch((err) => {
@@ -298,7 +316,7 @@ router.post('/:handle/ui', async (req, res) => {
298
316
  onRequestAborted(req, res, () => {
299
317
  shellsStream.abort();
300
318
  });
301
- const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
319
+ const queue = new PageGenerator_1.PageQueue(outerConversationId, systemPrompt, 5);
302
320
  queue.setUiTheme(theme);
303
321
  shellsStream.on('data', (data) => {
304
322
  //console.log('Processing shell event', data);
@@ -328,7 +346,6 @@ router.post('/:handle/ui', async (req, res) => {
328
346
  },
329
347
  created: Date.now(),
330
348
  });
331
- // Get the pages (5 at a time)
332
349
  const pagePromises = [];
333
350
  onRequestAborted(req, res, () => {
334
351
  queue.cancel();
@@ -365,16 +382,18 @@ router.post('/:handle/ui', async (req, res) => {
365
382
  theme,
366
383
  }));
367
384
  }
368
- await queue.wait();
369
- await Promise.allSettled(pagePromises);
370
- await Promise.allSettled(pageEventPromises);
371
385
  if (userJourneysStream.isAborted()) {
372
386
  return;
373
387
  }
388
+ await queue.wait();
389
+ await Promise.allSettled(pagePromises);
390
+ await Promise.allSettled(pageEventPromises);
374
391
  sendDone(res);
375
392
  }
376
393
  catch (err) {
377
394
  sendError(err, res);
395
+ }
396
+ finally {
378
397
  if (!res.closed) {
379
398
  res.end();
380
399
  }
@@ -386,7 +405,7 @@ router.post('/ui/edit', async (req, res) => {
386
405
  req.headers[stormClient_1.ConversationIdHeader.toLowerCase()]);
387
406
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
388
407
  const storagePrefix = systemId ? systemId + '_' : 'mock_';
389
- const queue = new PageGenerator_1.PageQueue(storagePrefix, 5);
408
+ const queue = new PageGenerator_1.PageQueue(systemId, '', 5);
390
409
  onRequestAborted(req, res, () => {
391
410
  queue.cancel();
392
411
  });
@@ -401,7 +420,13 @@ router.post('/ui/edit', async (req, res) => {
401
420
  sendEvent(res, data);
402
421
  }
403
422
  });
404
- await Promise.allSettled(aiRequest.prompt.pages.map((page) => {
423
+ const pages = aiRequest.prompt.pages.filter((page) => page.conversationId);
424
+ if (pages.length === 0) {
425
+ console.log('No pages to update', aiRequest.prompt.pages);
426
+ sendDone(res);
427
+ return;
428
+ }
429
+ await Promise.allSettled(pages.map((page) => {
405
430
  if (page.conversationId) {
406
431
  return queue.addPrompt({
407
432
  title: page.title,
@@ -421,6 +446,8 @@ router.post('/ui/edit', async (req, res) => {
421
446
  }
422
447
  catch (err) {
423
448
  sendError(err, res);
449
+ }
450
+ finally {
424
451
  if (!res.closed) {
425
452
  res.end();
426
453
  }
@@ -430,13 +457,24 @@ router.post('/ui/vote', async (req, res) => {
430
457
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || '';
431
458
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
432
459
  const { topic, vote, mainConversationId } = aiRequest;
433
- return stormClient_1.stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
460
+ try {
461
+ await stormClient_1.stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
462
+ }
463
+ catch (e) {
464
+ res.status(500).send({ error: e.message });
465
+ }
434
466
  });
435
467
  router.post('/ui/get-vote', async (req, res) => {
436
468
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || '';
437
469
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
438
470
  const { topic, mainConversationId } = aiRequest;
439
- return stormClient_1.stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
471
+ try {
472
+ const vote = await stormClient_1.stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
473
+ res.send({ vote });
474
+ }
475
+ catch (e) {
476
+ res.status(500).send({ error: e.message });
477
+ }
440
478
  });
441
479
  router.post('/:handle/all', async (req, res) => {
442
480
  const handle = req.params.handle;
@@ -556,7 +594,6 @@ router.post('/block/create', async (req, res) => {
556
594
  });
557
595
  router.post('/block/codegen', async (req, res) => {
558
596
  const body = JSON.parse(req.stringBody ?? '{}');
559
- console.log('Codegen request', body);
560
597
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
561
598
  try {
562
599
  const stormCodegen = new codegen_1.StormCodegen(conversationId ?? '', body.prompt, [body.block], body.events || []);
@@ -596,17 +633,21 @@ function sendError(err, res) {
596
633
  if (res.closed) {
597
634
  return;
598
635
  }
636
+ const errorPayload = {
637
+ error: err.message,
638
+ stack: err.stack,
639
+ };
599
640
  console.error('Failed to send prompt', err);
600
641
  if (res.headersSent) {
601
642
  sendEvent(res, {
602
643
  type: 'ERROR_INTERNAL',
603
644
  created: Date.now(),
604
- payload: { error: err.message },
645
+ payload: errorPayload,
605
646
  reason: 'Failed while sending prompt',
606
647
  });
607
648
  }
608
649
  else {
609
- res.status(400).send({ error: err.message });
650
+ res.status(400).send(errorPayload);
610
651
  }
611
652
  }
612
653
  function waitForStormStream(result) {
@@ -638,11 +679,13 @@ function onRequestAborted(req, res, onAborted) {
638
679
  });
639
680
  }
640
681
  async function sendPageEvent(mainConversationId, data, res) {
641
- try {
642
- await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
643
- }
644
- catch (err) {
645
- console.error('Failed to write page to disk', err);
682
+ if (data.payload.content) {
683
+ try {
684
+ await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
685
+ }
686
+ catch (err) {
687
+ console.error('Failed to write page to disk', err);
688
+ }
646
689
  }
647
690
  sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
648
691
  }
@@ -16,13 +16,19 @@ export interface ImagePrompt {
16
16
  url: string;
17
17
  content: string;
18
18
  }
19
+ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
20
+ shellType?: 'public' | 'admin' | 'user';
21
+ };
19
22
  export declare class PageQueue extends EventEmitter {
20
23
  private readonly queue;
21
24
  private readonly systemId;
25
+ private readonly systemPrompt;
22
26
  private readonly references;
27
+ private readonly pages;
28
+ private readonly images;
23
29
  private uiShells;
24
30
  private theme;
25
- constructor(systemId: string, concurrency?: number);
31
+ constructor(systemId: string, systemPrompt: string, concurrency?: number);
26
32
  on(event: 'event', listener: (data: StormEvent) => void): this;
27
33
  on(event: 'page', listener: (data: StormEventPage) => void): this;
28
34
  on(event: 'image', listener: (data: StormImage, source: ImagePrompt, future: FuturePromise<void>) => void): this;
@@ -31,7 +37,10 @@ export declare class PageQueue extends EventEmitter {
31
37
  emit(type: 'image', event: StormImage, source: ImagePrompt, future: FuturePromise<void>): boolean;
32
38
  addUiShell(uiShell: UIShell): void;
33
39
  setUiTheme(theme: string): void;
34
- addPrompt(initialPrompt: Omit<UIPagePrompt, 'shell_page'>, conversationId?: string, overwrite?: boolean): Promise<void>;
40
+ private hasPrompt;
41
+ addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean): Promise<void>;
42
+ private getPrefix;
43
+ private wrapPagePrompt;
35
44
  private addPageGenerator;
36
45
  cancel(): void;
37
46
  wait(): Promise<void>;
@@ -39,18 +48,21 @@ export declare class PageQueue extends EventEmitter {
39
48
  }
40
49
  export declare class PageGenerator extends EventEmitter {
41
50
  private readonly conversationId;
42
- private prompt;
51
+ readonly prompt: UIPagePrompt;
43
52
  constructor(prompt: UIPagePrompt, conversationId?: string);
44
53
  on(event: 'event', listener: (data: StormEvent) => void): this;
45
54
  on(event: 'page_refs', listener: (data: {
46
55
  event: StormEventPage;
47
56
  references: ReferenceClassification[];
57
+ future: FuturePromise<void>;
48
58
  }) => void): this;
49
59
  emit(type: 'event', event: StormEvent): boolean;
50
60
  emit(type: 'page_refs', event: {
51
61
  event: StormEventPage;
52
62
  references: ReferenceClassification[];
63
+ future: FuturePromise<void>;
53
64
  }): boolean;
54
65
  generate(): Promise<void>;
55
66
  private resolveReferences;
56
67
  }
68
+ export {};