@kapeta/local-cluster-service 0.67.2 → 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 +7 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +15 -3
- package/dist/cjs/src/storm/PageGenerator.js +124 -51
- package/dist/cjs/src/storm/routes.js +58 -19
- package/dist/esm/src/storm/PageGenerator.d.ts +15 -3
- package/dist/esm/src/storm/PageGenerator.js +124 -51
- package/dist/esm/src/storm/routes.js +58 -19
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +148 -63
- package/src/storm/routes.ts +58 -29
@@ -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
|
-
|
40
|
-
if (
|
43
|
+
hasPrompt(path) {
|
44
|
+
if (this.references.has(path)) {
|
41
45
|
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
42
|
-
return
|
46
|
+
return true;
|
43
47
|
}
|
44
|
-
if (
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
100
|
-
|
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
|
-
|
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) {
|
@@ -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
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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 || []);
|
@@ -642,11 +679,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
642
679
|
});
|
643
680
|
}
|
644
681
|
async function sendPageEvent(mainConversationId, data, res) {
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
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
|
+
}
|
650
689
|
}
|
651
690
|
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
652
691
|
}
|