@kapeta/local-cluster-service 0.67.2 → 0.67.4
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 +14 -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 +59 -20
- 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 +59 -20
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +148 -63
- package/src/storm/routes.ts +59 -30
- package/src/storm/stormClient.ts +1 -1
@@ -20,16 +20,22 @@ export interface ImagePrompt {
|
|
20
20
|
content: string;
|
21
21
|
}
|
22
22
|
|
23
|
+
type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & { shellType?: 'public' | 'admin' | 'user' };
|
24
|
+
|
23
25
|
export class PageQueue extends EventEmitter {
|
24
26
|
private readonly queue: PromiseQueue;
|
25
27
|
private readonly systemId: string;
|
28
|
+
private readonly systemPrompt: string;
|
26
29
|
private readonly references: Map<string, PageGenerator> = new Map();
|
30
|
+
private readonly pages: Map<string, string> = new Map();
|
31
|
+
private readonly images: Map<string, string> = new Map();
|
27
32
|
private uiShells: UIShell[] = [];
|
28
33
|
private theme = '';
|
29
34
|
|
30
|
-
constructor(systemId: string, concurrency: number = 5) {
|
35
|
+
constructor(systemId: string, systemPrompt: string, concurrency: number = 5) {
|
31
36
|
super();
|
32
37
|
this.systemId = systemId;
|
38
|
+
this.systemPrompt = systemPrompt;
|
33
39
|
this.queue = new PromiseQueue(concurrency);
|
34
40
|
}
|
35
41
|
|
@@ -56,82 +62,145 @@ export class PageQueue extends EventEmitter {
|
|
56
62
|
this.theme = theme;
|
57
63
|
}
|
58
64
|
|
59
|
-
|
60
|
-
|
61
|
-
conversationId: string = uuid.v4(),
|
62
|
-
overwrite: boolean = false
|
63
|
-
) {
|
64
|
-
if (!overwrite && this.references.has(initialPrompt.path)) {
|
65
|
+
private hasPrompt(path: string) {
|
66
|
+
if (this.references.has(path)) {
|
65
67
|
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
66
|
-
return
|
68
|
+
return true;
|
67
69
|
}
|
68
70
|
|
69
|
-
if (
|
71
|
+
if (hasPageOnDisk(this.systemId, 'GET', path)) {
|
70
72
|
//console.log('Ignoring prompt with existing page', initialPrompt.path);
|
73
|
+
return true;
|
74
|
+
}
|
75
|
+
|
76
|
+
return false;
|
77
|
+
}
|
78
|
+
|
79
|
+
public addPrompt(initialPrompt: InitialPrompt, conversationId: string = uuid.v4(), overwrite: boolean = false) {
|
80
|
+
if (!overwrite && this.hasPrompt(initialPrompt.path)) {
|
81
|
+
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
71
82
|
return Promise.resolve();
|
72
83
|
}
|
73
84
|
|
74
85
|
const prompt: UIPagePrompt = {
|
75
86
|
...initialPrompt,
|
76
87
|
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
88
|
+
prompt: this.wrapPagePrompt(initialPrompt.path, initialPrompt.prompt),
|
77
89
|
theme: this.theme,
|
78
90
|
};
|
79
91
|
|
80
92
|
const generator = new PageGenerator(prompt, conversationId);
|
81
93
|
this.references.set(prompt.path, generator);
|
94
|
+
this.pages.set(prompt.path, prompt.description);
|
82
95
|
|
83
96
|
return this.addPageGenerator(generator);
|
84
97
|
}
|
98
|
+
private getPrefix(): string {
|
99
|
+
let promptPrefix = '';
|
100
|
+
if (this.systemPrompt) {
|
101
|
+
promptPrefix = `For a system with this description: ${this.systemPrompt}\n`;
|
102
|
+
}
|
103
|
+
return promptPrefix;
|
104
|
+
}
|
85
105
|
|
86
|
-
private
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
reference.url.startsWith('https://')
|
95
|
-
) {
|
106
|
+
private wrapPagePrompt(pagePath: string, prompt: string): string {
|
107
|
+
let promptPrefix = this.getPrefix();
|
108
|
+
let promptPostfix = '';
|
109
|
+
|
110
|
+
if (this.pages.size > 0) {
|
111
|
+
promptPostfix = `\nThe following pages are already implemented:\n`;
|
112
|
+
this.pages.forEach((description, path) => {
|
113
|
+
if (pagePath === path) {
|
96
114
|
return;
|
97
115
|
}
|
116
|
+
promptPostfix += `- PAGE: '${path}' -> ${description}.\n`;
|
117
|
+
});
|
118
|
+
}
|
98
119
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
content: event.payload.content,
|
104
|
-
});
|
105
|
-
break;
|
106
|
-
case 'css':
|
107
|
-
case 'javascript':
|
108
|
-
//console.log('Ignoring reference', reference);
|
109
|
-
break;
|
110
|
-
case 'html':
|
111
|
-
//console.log('Adding page generator for', reference);
|
112
|
-
const paths = Array.from(this.references.keys());
|
113
|
-
this.addPrompt({
|
114
|
-
name: reference.name,
|
115
|
-
title: reference.title,
|
116
|
-
path: reference.url,
|
117
|
-
method: 'GET',
|
118
|
-
storage_prefix: this.systemId + '_',
|
119
|
-
prompt:
|
120
|
-
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
121
|
-
`The page was referenced from this page: \`\`\`html\n${event.payload.content}\n\`\`\`\n` +
|
122
|
-
(paths.length > 0
|
123
|
-
? `\nThese paths are already implemented:\n- ${paths.join('\n - ')}\n\n`
|
124
|
-
: ''),
|
125
|
-
description: reference.description,
|
126
|
-
filename: '',
|
127
|
-
theme: this.theme,
|
128
|
-
});
|
129
|
-
break;
|
130
|
-
}
|
120
|
+
if (this.images.size > 0) {
|
121
|
+
promptPostfix += `\nThe following images already exist:\n`;
|
122
|
+
this.images.forEach((description, path) => {
|
123
|
+
promptPostfix += `- IMAGE: '${path}' -> ${description}.\n`;
|
131
124
|
});
|
125
|
+
}
|
126
|
+
|
127
|
+
return promptPrefix + prompt + promptPostfix;
|
128
|
+
}
|
132
129
|
|
133
|
-
|
134
|
-
|
130
|
+
private async addPageGenerator(generator: PageGenerator) {
|
131
|
+
generator.on('event', (event: StormEvent) => this.emit('event', event));
|
132
|
+
generator.on('page_refs', async ({ event, references, future }) => {
|
133
|
+
try {
|
134
|
+
const initialPrompts: InitialPrompt[] = [];
|
135
|
+
let promises = references.map(async (reference) => {
|
136
|
+
if (
|
137
|
+
reference.url.startsWith('#') ||
|
138
|
+
reference.url.startsWith('javascript:') ||
|
139
|
+
reference.url.startsWith('http://') ||
|
140
|
+
reference.url.startsWith('https://')
|
141
|
+
) {
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
145
|
+
switch (reference.type) {
|
146
|
+
case 'image':
|
147
|
+
await this.addImagePrompt({
|
148
|
+
...reference,
|
149
|
+
content: event.payload.content,
|
150
|
+
});
|
151
|
+
break;
|
152
|
+
case 'css':
|
153
|
+
case 'javascript':
|
154
|
+
//console.log('Ignoring reference', reference);
|
155
|
+
break;
|
156
|
+
case 'html':
|
157
|
+
//console.log('Adding page generator for', reference);
|
158
|
+
this.pages.set(reference.url, reference.description);
|
159
|
+
|
160
|
+
initialPrompts.push({
|
161
|
+
name: reference.name,
|
162
|
+
title: reference.title,
|
163
|
+
path: reference.url,
|
164
|
+
method: 'GET',
|
165
|
+
storage_prefix: this.systemId + '_',
|
166
|
+
prompt:
|
167
|
+
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
168
|
+
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
169
|
+
description: reference.description,
|
170
|
+
filename: '',
|
171
|
+
theme: this.theme,
|
172
|
+
});
|
173
|
+
break;
|
174
|
+
}
|
175
|
+
});
|
176
|
+
|
177
|
+
await Promise.allSettled(promises);
|
178
|
+
initialPrompts.forEach((prompt) => {
|
179
|
+
if (!this.hasPrompt(prompt.path)) {
|
180
|
+
this.emit('page', {
|
181
|
+
type: 'PAGE',
|
182
|
+
reason: 'reference',
|
183
|
+
created: Date.now(),
|
184
|
+
payload: {
|
185
|
+
name: prompt.name,
|
186
|
+
title: prompt.title,
|
187
|
+
filename: '',
|
188
|
+
method: 'GET',
|
189
|
+
path: prompt.path,
|
190
|
+
prompt: prompt.description,
|
191
|
+
conversationId: '',
|
192
|
+
content: '',
|
193
|
+
description: prompt.description,
|
194
|
+
},
|
195
|
+
});
|
196
|
+
}
|
197
|
+
this.addPrompt(prompt);
|
198
|
+
});
|
199
|
+
|
200
|
+
this.emit('page', event);
|
201
|
+
} finally {
|
202
|
+
future.resolve();
|
203
|
+
}
|
135
204
|
});
|
136
205
|
return this.queue.add(() => generator.generate());
|
137
206
|
}
|
@@ -145,10 +214,18 @@ export class PageQueue extends EventEmitter {
|
|
145
214
|
}
|
146
215
|
|
147
216
|
private async addImagePrompt(prompt: ImagePrompt) {
|
217
|
+
if (this.images.has(prompt.url)) {
|
218
|
+
//console.log('Ignoring duplicate image prompt', prompt);
|
219
|
+
return;
|
220
|
+
}
|
221
|
+
this.images.set(prompt.url, prompt.description);
|
222
|
+
const prefix = this.getPrefix();
|
148
223
|
const result = await stormClient.createImage(
|
149
|
-
`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim()
|
224
|
+
prefix + `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim()
|
150
225
|
);
|
151
226
|
|
227
|
+
//console.log('Adding image prompt', prompt);
|
228
|
+
|
152
229
|
const futures: FuturePromise<void>[] = [];
|
153
230
|
|
154
231
|
result.on('data', async (event: StormEvent) => {
|
@@ -172,7 +249,7 @@ export class PageQueue extends EventEmitter {
|
|
172
249
|
|
173
250
|
export class PageGenerator extends EventEmitter {
|
174
251
|
private readonly conversationId: string;
|
175
|
-
|
252
|
+
public readonly prompt: UIPagePrompt;
|
176
253
|
|
177
254
|
constructor(prompt: UIPagePrompt, conversationId: string = uuid.v4()) {
|
178
255
|
super();
|
@@ -183,7 +260,11 @@ export class PageGenerator extends EventEmitter {
|
|
183
260
|
on(event: 'event', listener: (data: StormEvent) => void): this;
|
184
261
|
on(
|
185
262
|
event: 'page_refs',
|
186
|
-
listener: (data: {
|
263
|
+
listener: (data: {
|
264
|
+
event: StormEventPage;
|
265
|
+
references: ReferenceClassification[];
|
266
|
+
future: FuturePromise<void>;
|
267
|
+
}) => void
|
187
268
|
): this;
|
188
269
|
|
189
270
|
on(event: string, listener: (...args: any[]) => void): this {
|
@@ -191,16 +272,18 @@ export class PageGenerator extends EventEmitter {
|
|
191
272
|
}
|
192
273
|
|
193
274
|
emit(type: 'event', event: StormEvent): boolean;
|
194
|
-
emit(
|
275
|
+
emit(
|
276
|
+
type: 'page_refs',
|
277
|
+
event: { event: StormEventPage; references: ReferenceClassification[]; future: FuturePromise<void> }
|
278
|
+
): boolean;
|
195
279
|
emit(eventName: string | symbol, ...args: any[]): boolean {
|
196
280
|
return super.emit(eventName, ...args);
|
197
281
|
}
|
198
282
|
|
199
283
|
public async generate() {
|
200
284
|
return new Promise<void>(async (resolve) => {
|
201
|
-
const screenStream = await stormClient.createUIPage(this.prompt, this.conversationId);
|
202
|
-
|
203
285
|
const promises: Promise<void>[] = [];
|
286
|
+
const screenStream = await stormClient.createUIPage(this.prompt, this.conversationId);
|
204
287
|
|
205
288
|
screenStream.on('data', (event: StormEvent) => {
|
206
289
|
if (event.type === 'PAGE') {
|
@@ -210,10 +293,14 @@ export class PageGenerator extends EventEmitter {
|
|
210
293
|
(async () => {
|
211
294
|
const references = await this.resolveReferences(event.payload.content);
|
212
295
|
//console.log('Resolved references for page', references, event.payload);
|
296
|
+
const future = createFuture();
|
213
297
|
this.emit('page_refs', {
|
214
298
|
event,
|
215
299
|
references,
|
300
|
+
future,
|
216
301
|
});
|
302
|
+
|
303
|
+
await future.promise;
|
217
304
|
})()
|
218
305
|
);
|
219
306
|
return;
|
@@ -222,11 +309,9 @@ export class PageGenerator extends EventEmitter {
|
|
222
309
|
this.emit('event', event);
|
223
310
|
});
|
224
311
|
|
225
|
-
screenStream.on('end', () => {
|
226
|
-
Promise.allSettled(promises).finally(resolve);
|
227
|
-
});
|
228
|
-
|
229
312
|
await screenStream.waitForDone();
|
313
|
+
await Promise.all(promises);
|
314
|
+
resolve();
|
230
315
|
});
|
231
316
|
}
|
232
317
|
|
package/src/storm/routes.ts
CHANGED
@@ -17,7 +17,6 @@ import {
|
|
17
17
|
ConversationIdHeader,
|
18
18
|
stormClient,
|
19
19
|
UIPagePrompt,
|
20
|
-
UIPageEditPrompt,
|
21
20
|
UIPageEditRequest,
|
22
21
|
BasePromptRequest,
|
23
22
|
UIPageVoteRequest,
|
@@ -35,17 +34,11 @@ import {
|
|
35
34
|
import { StormCodegen } from './codegen';
|
36
35
|
import { assetManager } from '../assetManager';
|
37
36
|
import uuid from 'node-uuid';
|
38
|
-
import {
|
39
|
-
readPageFromDisk,
|
40
|
-
readPageFromDiskAsString,
|
41
|
-
SystemIdHeader,
|
42
|
-
writeAssetToDisk,
|
43
|
-
writeImageToDisk,
|
44
|
-
writePageToDisk,
|
45
|
-
} from './page-utils';
|
37
|
+
import { readPageFromDisk, SystemIdHeader, writeAssetToDisk, writeImageToDisk, writePageToDisk } from './page-utils';
|
46
38
|
import { UIServer } from './UIServer';
|
47
39
|
import { randomUUID } from 'crypto';
|
48
40
|
import { ImagePrompt, PageQueue } from './PageGenerator';
|
41
|
+
import { createFuture } from './PromiseQueue';
|
49
42
|
|
50
43
|
const UI_SERVERS: { [key: string]: UIServer } = {};
|
51
44
|
const router = Router();
|
@@ -74,7 +67,7 @@ function convertPageEvent(screenData: StormEvent, innerConversationId: string, m
|
|
74
67
|
description: screenData.payload.description,
|
75
68
|
prompt: screenData.payload.prompt,
|
76
69
|
path: screenData.payload.path,
|
77
|
-
url: server ? server.resolveUrl(screenData) : '',
|
70
|
+
url: server && screenData.payload.content ? server.resolveUrl(screenData) : '',
|
78
71
|
method: screenData.payload.method,
|
79
72
|
conversationId: innerConversationId,
|
80
73
|
},
|
@@ -102,7 +95,7 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
102
95
|
|
103
96
|
const parentConversationId = systemId ?? '';
|
104
97
|
|
105
|
-
const queue = new PageQueue(parentConversationId, 5);
|
98
|
+
const queue = new PageQueue(parentConversationId, '', 5);
|
106
99
|
onRequestAborted(req, res, () => {
|
107
100
|
queue.cancel();
|
108
101
|
});
|
@@ -138,6 +131,7 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
138
131
|
sendDone(res);
|
139
132
|
} catch (err: any) {
|
140
133
|
sendError(err, res);
|
134
|
+
} finally {
|
141
135
|
if (!res.closed) {
|
142
136
|
res.end();
|
143
137
|
}
|
@@ -180,11 +174,17 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
|
|
180
174
|
const promises: { [key: string]: Promise<void> } = {};
|
181
175
|
const pageEventPromises: Promise<void>[] = [];
|
182
176
|
const systemId = landingPagesStream.getConversationId();
|
183
|
-
const
|
177
|
+
const systemPrompt = createFuture<string>();
|
178
|
+
if (aiRequest.skipImprovement) {
|
179
|
+
systemPrompt.resolve(aiRequest.prompt);
|
180
|
+
}
|
184
181
|
|
185
182
|
landingPagesStream.on('data', async (data: StormEvent) => {
|
186
183
|
try {
|
187
184
|
sendEvent(res, data);
|
185
|
+
if (data.type === 'PROMPT_IMPROVE') {
|
186
|
+
systemPrompt.resolve(data.payload.prompt);
|
187
|
+
}
|
188
188
|
if (data.type !== 'LANDING_PAGE') {
|
189
189
|
return;
|
190
190
|
}
|
@@ -218,7 +218,13 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
|
|
218
218
|
|
219
219
|
UI_SERVERS[systemId] = new UIServer(systemId);
|
220
220
|
await UI_SERVERS[systemId].start();
|
221
|
+
waitForStormStream(landingPagesStream).then(() => {
|
222
|
+
if (!systemPrompt.isResolved()) {
|
223
|
+
systemPrompt.resolve(aiRequest.prompt);
|
224
|
+
}
|
225
|
+
});
|
221
226
|
|
227
|
+
const pageQueue = new PageQueue(systemId, await systemPrompt.promise, 5);
|
222
228
|
onRequestAborted(req, res, () => {
|
223
229
|
pageQueue.cancel();
|
224
230
|
});
|
@@ -281,9 +287,14 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
281
287
|
|
282
288
|
const uniqueUserJourneyScreens: Record<string, UserJourneyScreen> = {};
|
283
289
|
|
290
|
+
let systemPrompt = aiRequest.prompt;
|
291
|
+
|
284
292
|
userJourneysStream.on('data', (data: StormEvent) => {
|
285
293
|
try {
|
286
294
|
sendEvent(res, data);
|
295
|
+
if (data.type === 'PROMPT_IMPROVE') {
|
296
|
+
systemPrompt = data.payload.prompt;
|
297
|
+
}
|
287
298
|
if (data.type !== 'USER_JOURNEY') {
|
288
299
|
return;
|
289
300
|
}
|
@@ -316,7 +327,6 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
316
327
|
});
|
317
328
|
|
318
329
|
themeStream.on('data', (evt) => {
|
319
|
-
sendEvent(res, evt);
|
320
330
|
if (evt.type === 'FILE_DONE') {
|
321
331
|
theme = evt.payload.content;
|
322
332
|
writeAssetToDisk(outerConversationId, evt).catch((err) => {
|
@@ -354,7 +364,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
354
364
|
// Get the UI shells
|
355
365
|
const shellsStream = await stormClient.createUIShells(
|
356
366
|
{
|
357
|
-
theme: theme
|
367
|
+
theme: theme || undefined,
|
358
368
|
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
359
369
|
name: screen.name,
|
360
370
|
title: screen.title,
|
@@ -371,7 +381,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
371
381
|
shellsStream.abort();
|
372
382
|
});
|
373
383
|
|
374
|
-
const queue = new PageQueue(outerConversationId, 5);
|
384
|
+
const queue = new PageQueue(outerConversationId, systemPrompt, 5);
|
375
385
|
queue.setUiTheme(theme);
|
376
386
|
|
377
387
|
shellsStream.on('data', (data: StormEvent) => {
|
@@ -410,8 +420,6 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
410
420
|
created: Date.now(),
|
411
421
|
});
|
412
422
|
|
413
|
-
// Get the pages (5 at a time)
|
414
|
-
|
415
423
|
const pagePromises: Promise<void>[] = [];
|
416
424
|
onRequestAborted(req, res, () => {
|
417
425
|
queue.cancel();
|
@@ -454,17 +462,18 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
454
462
|
);
|
455
463
|
}
|
456
464
|
|
457
|
-
await queue.wait();
|
458
|
-
await Promise.allSettled(pagePromises);
|
459
|
-
await Promise.allSettled(pageEventPromises);
|
460
|
-
|
461
465
|
if (userJourneysStream.isAborted()) {
|
462
466
|
return;
|
463
467
|
}
|
464
468
|
|
469
|
+
await queue.wait();
|
470
|
+
await Promise.allSettled(pagePromises);
|
471
|
+
await Promise.allSettled(pageEventPromises);
|
472
|
+
|
465
473
|
sendDone(res);
|
466
474
|
} catch (err) {
|
467
475
|
sendError(err as Error, res);
|
476
|
+
} finally {
|
468
477
|
if (!res.closed) {
|
469
478
|
res.end();
|
470
479
|
}
|
@@ -478,7 +487,8 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
478
487
|
|
479
488
|
const aiRequest: StormContextRequest<UIPageEditRequest> = JSON.parse(req.stringBody ?? '{}');
|
480
489
|
const storagePrefix = systemId ? systemId + '_' : 'mock_';
|
481
|
-
|
490
|
+
|
491
|
+
const queue = new PageQueue(systemId!, '', 5);
|
482
492
|
|
483
493
|
onRequestAborted(req, res, () => {
|
484
494
|
queue.cancel();
|
@@ -498,8 +508,15 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
498
508
|
}
|
499
509
|
});
|
500
510
|
|
511
|
+
const pages = aiRequest.prompt.pages.filter((page) => page.conversationId);
|
512
|
+
if (pages.length === 0) {
|
513
|
+
console.log('No pages to update', aiRequest.prompt.pages);
|
514
|
+
sendDone(res);
|
515
|
+
return;
|
516
|
+
}
|
517
|
+
|
501
518
|
await Promise.allSettled(
|
502
|
-
|
519
|
+
pages.map((page) => {
|
503
520
|
if (page.conversationId) {
|
504
521
|
return queue.addPrompt(
|
505
522
|
{
|
@@ -525,6 +542,7 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
525
542
|
sendDone(res);
|
526
543
|
} catch (err: any) {
|
527
544
|
sendError(err as Error, res);
|
545
|
+
} finally {
|
528
546
|
if (!res.closed) {
|
529
547
|
res.end();
|
530
548
|
}
|
@@ -535,14 +553,23 @@ router.post('/ui/vote', async (req: KapetaBodyRequest, res: Response) => {
|
|
535
553
|
const conversationId = (req.headers[ConversationIdHeader.toLowerCase()] as string | undefined) || '';
|
536
554
|
const aiRequest: UIPageVoteRequest = JSON.parse(req.stringBody ?? '{}');
|
537
555
|
const { topic, vote, mainConversationId } = aiRequest;
|
538
|
-
|
556
|
+
try {
|
557
|
+
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
558
|
+
} catch (e: any) {
|
559
|
+
res.status(500).send({ error: e.message });
|
560
|
+
}
|
539
561
|
});
|
540
562
|
|
541
563
|
router.post('/ui/get-vote', async (req: KapetaBodyRequest, res: Response) => {
|
542
564
|
const conversationId = (req.headers[ConversationIdHeader.toLowerCase()] as string | undefined) || '';
|
543
565
|
const aiRequest: UIPageGetVoteRequest = JSON.parse(req.stringBody ?? '{}');
|
544
566
|
const { topic, mainConversationId } = aiRequest;
|
545
|
-
|
567
|
+
try {
|
568
|
+
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
569
|
+
res.send({ vote });
|
570
|
+
} catch (e: any) {
|
571
|
+
res.status(500).send({ error: e.message });
|
572
|
+
}
|
546
573
|
});
|
547
574
|
|
548
575
|
router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
@@ -689,7 +716,6 @@ router.post('/block/create', async (req: KapetaBodyRequest, res: Response) => {
|
|
689
716
|
|
690
717
|
router.post('/block/codegen', async (req: KapetaBodyRequest, res: Response) => {
|
691
718
|
const body: StormCodegenRequest = JSON.parse(req.stringBody ?? '{}');
|
692
|
-
console.log('Codegen request', body);
|
693
719
|
const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
|
694
720
|
try {
|
695
721
|
const stormCodegen = new StormCodegen(conversationId ?? '', body.prompt, [body.block], body.events || []);
|
@@ -791,11 +817,14 @@ function onRequestAborted(req: KapetaBodyRequest, res: Response, onAborted: () =
|
|
791
817
|
}
|
792
818
|
|
793
819
|
async function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
820
|
+
if (data.payload.content) {
|
821
|
+
try {
|
822
|
+
await writePageToDisk(mainConversationId, data);
|
823
|
+
} catch (err) {
|
824
|
+
console.error('Failed to write page to disk', err);
|
825
|
+
}
|
798
826
|
}
|
827
|
+
|
799
828
|
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
800
829
|
}
|
801
830
|
|