@kapeta/local-cluster-service 0.61.2 → 0.62.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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +39 -0
- package/dist/cjs/src/storm/PageGenerator.js +143 -0
- package/dist/cjs/src/storm/event-parser.js +11 -0
- package/dist/cjs/src/storm/events.d.ts +34 -1
- package/dist/cjs/src/storm/page-utils.d.ts +10 -0
- package/dist/cjs/src/storm/page-utils.js +65 -6
- package/dist/cjs/src/storm/routes.js +105 -56
- package/dist/cjs/src/storm/stormClient.d.ts +7 -1
- package/dist/cjs/src/storm/stormClient.js +24 -1
- package/dist/cjs/src/storm/stream.d.ts +1 -0
- package/dist/cjs/src/storm/stream.js +1 -0
- package/dist/esm/src/storm/PageGenerator.d.ts +39 -0
- package/dist/esm/src/storm/PageGenerator.js +143 -0
- package/dist/esm/src/storm/event-parser.js +11 -0
- package/dist/esm/src/storm/events.d.ts +34 -1
- package/dist/esm/src/storm/page-utils.d.ts +10 -0
- package/dist/esm/src/storm/page-utils.js +65 -6
- package/dist/esm/src/storm/routes.js +105 -56
- package/dist/esm/src/storm/stormClient.d.ts +7 -1
- package/dist/esm/src/storm/stormClient.js +24 -1
- package/dist/esm/src/storm/stream.d.ts +1 -0
- package/dist/esm/src/storm/stream.js +1 -0
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +181 -0
- package/src/storm/event-parser.ts +12 -0
- package/src/storm/events.ts +43 -1
- package/src/storm/page-utils.ts +88 -12
- package/src/storm/routes.ts +147 -70
- package/src/storm/stormClient.ts +33 -1
- package/src/storm/stream.ts +2 -0
package/src/storm/routes.ts
CHANGED
@@ -12,8 +12,17 @@ import { corsHandler } from '../middleware/cors';
|
|
12
12
|
import { stringBody } from '../middleware/stringBody';
|
13
13
|
import { KapetaBodyRequest } from '../types';
|
14
14
|
import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
|
15
|
-
|
15
|
+
|
16
|
+
import {
|
17
|
+
ConversationIdHeader,
|
18
|
+
stormClient,
|
19
|
+
UIPagePrompt,
|
20
|
+
UIPageEditPrompt,
|
21
|
+
UIPageEditRequest,
|
22
|
+
UIPageSamplePrompt,
|
23
|
+
} from './stormClient';
|
16
24
|
import { Page, StormEvent, StormEventPage, StormEventPhaseType, UIShell, UserJourneyScreen } from './events';
|
25
|
+
|
17
26
|
import {
|
18
27
|
createPhaseEndEvent,
|
19
28
|
createPhaseStartEvent,
|
@@ -25,8 +34,17 @@ import { StormCodegen } from './codegen';
|
|
25
34
|
import { assetManager } from '../assetManager';
|
26
35
|
import uuid from 'node-uuid';
|
27
36
|
import { PromiseQueue } from './PromiseQueue';
|
28
|
-
import {
|
37
|
+
import {
|
38
|
+
readConversationFromFile,
|
39
|
+
readPageFromDisk,
|
40
|
+
readPageFromDiskAsString,
|
41
|
+
SystemIdHeader,
|
42
|
+
writeConversationToFile,
|
43
|
+
writePageToDisk,
|
44
|
+
} from './page-utils';
|
29
45
|
import { UIServer } from './UIServer';
|
46
|
+
import { PageQueue } from './PageGenerator';
|
47
|
+
import FSExtra from 'fs-extra';
|
30
48
|
|
31
49
|
const UI_SERVERS: { [key: string]: UIServer } = {};
|
32
50
|
const router = Router();
|
@@ -34,6 +52,8 @@ const router = Router();
|
|
34
52
|
router.use('/', corsHandler);
|
35
53
|
router.use('/', stringBody);
|
36
54
|
|
55
|
+
const samplesBaseDir = Path.join(__dirname, 'samples');
|
56
|
+
|
37
57
|
function convertPageEvent(screenData: StormEvent, innerConversationId: string, mainConversationId: string): StormEvent {
|
38
58
|
if (screenData.type === 'PAGE') {
|
39
59
|
const server: UIServer | undefined = UI_SERVERS[mainConversationId];
|
@@ -75,22 +95,25 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
75
95
|
const aiRequest: UIPagePrompt = JSON.parse(req.stringBody ?? '{}');
|
76
96
|
aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
|
77
97
|
|
78
|
-
|
98
|
+
res.set('Content-Type', 'application/x-ndjson');
|
99
|
+
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
100
|
+
res.set(ConversationIdHeader, conversationId);
|
101
|
+
|
102
|
+
const parentConversationId = systemId ?? '';
|
79
103
|
|
104
|
+
const queue = new PageQueue(parentConversationId, 5);
|
80
105
|
onRequestAborted(req, res, () => {
|
81
|
-
|
106
|
+
queue.cancel();
|
82
107
|
});
|
83
108
|
|
84
|
-
|
85
|
-
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
86
|
-
res.set(ConversationIdHeader, screenStream.getConversationId());
|
109
|
+
await queue.addPrompt(aiRequest);
|
87
110
|
|
88
111
|
const promises: Promise<void>[] = [];
|
89
|
-
|
112
|
+
queue.on('page', (data) => {
|
90
113
|
switch (data.type) {
|
91
114
|
case 'PAGE':
|
92
115
|
console.log('Processing page event', data);
|
93
|
-
|
116
|
+
|
94
117
|
if (systemId) {
|
95
118
|
promises.push(sendPageEvent(systemId, data, res));
|
96
119
|
}
|
@@ -99,7 +122,7 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
99
122
|
sendEvent(res, data);
|
100
123
|
});
|
101
124
|
|
102
|
-
await
|
125
|
+
await queue.wait();
|
103
126
|
await Promise.allSettled(promises);
|
104
127
|
|
105
128
|
sendDone(res);
|
@@ -125,6 +148,93 @@ router.delete('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
125
148
|
}
|
126
149
|
});
|
127
150
|
|
151
|
+
router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Response) => {
|
152
|
+
const handle = req.params.handle as string;
|
153
|
+
try {
|
154
|
+
const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
|
155
|
+
|
156
|
+
const aiRequest: StormContextRequest = JSON.parse(req.stringBody ?? '{}');
|
157
|
+
|
158
|
+
const landingPagesStream = await stormClient.createUILandingPages(aiRequest.prompt, conversationId);
|
159
|
+
|
160
|
+
onRequestAborted(req, res, () => {
|
161
|
+
landingPagesStream.abort();
|
162
|
+
});
|
163
|
+
|
164
|
+
res.set('Content-Type', 'application/x-ndjson');
|
165
|
+
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
166
|
+
|
167
|
+
res.set(ConversationIdHeader, landingPagesStream.getConversationId());
|
168
|
+
|
169
|
+
const promises: { [key: string]: Promise<void> } = {};
|
170
|
+
const pageEventPromises: Promise<void>[] = [];
|
171
|
+
const systemId = landingPagesStream.getConversationId();
|
172
|
+
const pageQueue = new PageQueue(systemId, 5);
|
173
|
+
|
174
|
+
landingPagesStream.on('data', async (data: StormEvent) => {
|
175
|
+
try {
|
176
|
+
sendEvent(res, data);
|
177
|
+
if (data.type !== 'LANDING_PAGE') {
|
178
|
+
return;
|
179
|
+
}
|
180
|
+
|
181
|
+
if (landingPagesStream.isAborted()) {
|
182
|
+
return;
|
183
|
+
}
|
184
|
+
const landingPage = data.payload;
|
185
|
+
if (landingPage.name in promises) {
|
186
|
+
return;
|
187
|
+
}
|
188
|
+
|
189
|
+
// We add the landing pages to the prompt queue.
|
190
|
+
// These will then be analysed - creating further pages as needed
|
191
|
+
promises[landingPage.name] = pageQueue.addPrompt({
|
192
|
+
prompt: landingPage.create_prompt,
|
193
|
+
method: 'GET',
|
194
|
+
path: landingPage.path,
|
195
|
+
description: landingPage.create_prompt,
|
196
|
+
name: landingPage.name,
|
197
|
+
title: landingPage.title,
|
198
|
+
filename: landingPage.filename,
|
199
|
+
storage_prefix: systemId + '_',
|
200
|
+
});
|
201
|
+
} catch (e) {
|
202
|
+
console.error('Failed to process event', e);
|
203
|
+
}
|
204
|
+
});
|
205
|
+
|
206
|
+
UI_SERVERS[systemId] = new UIServer(systemId);
|
207
|
+
await UI_SERVERS[systemId].start();
|
208
|
+
|
209
|
+
onRequestAborted(req, res, () => {
|
210
|
+
pageQueue.cancel();
|
211
|
+
});
|
212
|
+
|
213
|
+
pageQueue.on('page', (screenData: StormEventPage) => {
|
214
|
+
pageEventPromises.push(sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
|
215
|
+
});
|
216
|
+
|
217
|
+
pageQueue.on('event', (screenData: StormEvent) => {
|
218
|
+
sendEvent(res, screenData);
|
219
|
+
});
|
220
|
+
|
221
|
+
await waitForStormStream(landingPagesStream);
|
222
|
+
await pageQueue.wait();
|
223
|
+
await Promise.allSettled(pageEventPromises);
|
224
|
+
|
225
|
+
if (landingPagesStream.isAborted()) {
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
|
229
|
+
sendDone(res);
|
230
|
+
} catch (err) {
|
231
|
+
sendError(err as Error, res);
|
232
|
+
if (!res.closed) {
|
233
|
+
res.end();
|
234
|
+
}
|
235
|
+
}
|
236
|
+
});
|
237
|
+
|
128
238
|
router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
129
239
|
const handle = req.params.handle as string;
|
130
240
|
try {
|
@@ -223,75 +333,40 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
223
333
|
await UI_SERVERS[outerConversationId].start();
|
224
334
|
|
225
335
|
// Get the pages (5 at a time)
|
226
|
-
const queue = new
|
227
|
-
|
336
|
+
const queue = new PageQueue(outerConversationId, 5);
|
337
|
+
const pagePromises: Promise<void>[] = [];
|
228
338
|
onRequestAborted(req, res, () => {
|
229
339
|
queue.cancel();
|
230
340
|
});
|
231
341
|
|
342
|
+
const pageEventPromises: Promise<void>[] = [];
|
343
|
+
queue.on('page', (screenData: StormEventPage) => {
|
344
|
+
pageEventPromises.push(sendPageEvent(outerConversationId, screenData, res));
|
345
|
+
});
|
346
|
+
|
347
|
+
queue.on('event', (screenData: StormEvent) => {
|
348
|
+
sendEvent(res, screenData);
|
349
|
+
});
|
350
|
+
|
232
351
|
for (const screen of Object.values(uniqueUserJourneyScreens)) {
|
233
|
-
|
234
|
-
(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
title: screen.title,
|
246
|
-
filename: screen.filename,
|
247
|
-
storage_prefix: outerConversationId + '_',
|
248
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
249
|
-
},
|
250
|
-
innerConversationId
|
251
|
-
);
|
252
|
-
|
253
|
-
const promiseList: Promise<void>[] = [];
|
254
|
-
screenStream.on('data', (screenData: StormEvent) => {
|
255
|
-
if (screenData.type === 'PAGE') {
|
256
|
-
promiseList.push(
|
257
|
-
sendPageEvent(
|
258
|
-
outerConversationId,
|
259
|
-
{
|
260
|
-
...screenData,
|
261
|
-
payload: {
|
262
|
-
...screenData.payload,
|
263
|
-
conversationId: innerConversationId,
|
264
|
-
},
|
265
|
-
},
|
266
|
-
res
|
267
|
-
)
|
268
|
-
);
|
269
|
-
} else {
|
270
|
-
sendEvent(res, screenData);
|
271
|
-
}
|
272
|
-
});
|
273
|
-
|
274
|
-
screenStream.on('end', async () => {
|
275
|
-
try {
|
276
|
-
await Promise.allSettled(promiseList).finally(() => resolve(true));
|
277
|
-
} catch (error) {
|
278
|
-
console.error('Failed to process screen', error);
|
279
|
-
}
|
280
|
-
});
|
281
|
-
|
282
|
-
screenStream.on('error', (error) => {
|
283
|
-
console.error('Error on screenStream', error);
|
284
|
-
screenStream.abort();
|
285
|
-
});
|
286
|
-
} catch (e) {
|
287
|
-
console.error('Failed to process screen', e);
|
288
|
-
reject(e);
|
289
|
-
}
|
290
|
-
})
|
352
|
+
pagePromises.push(
|
353
|
+
queue.addPrompt({
|
354
|
+
prompt: screen.requirements,
|
355
|
+
method: screen.method,
|
356
|
+
path: screen.path,
|
357
|
+
description: screen.requirements,
|
358
|
+
name: screen.name,
|
359
|
+
title: screen.title,
|
360
|
+
filename: screen.filename,
|
361
|
+
storage_prefix: outerConversationId + '_',
|
362
|
+
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
363
|
+
})
|
291
364
|
);
|
292
365
|
}
|
293
366
|
|
294
367
|
await queue.wait();
|
368
|
+
await Promise.allSettled(pagePromises);
|
369
|
+
await Promise.allSettled(pageEventPromises);
|
295
370
|
|
296
371
|
if (userJourneysStream.isAborted()) {
|
297
372
|
return;
|
@@ -408,6 +483,7 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
408
483
|
const result = await eventParser.processEvent(handle, data);
|
409
484
|
|
410
485
|
switch (data.type) {
|
486
|
+
case 'API_STREAM_START':
|
411
487
|
case 'CREATE_API':
|
412
488
|
case 'CREATE_MODEL':
|
413
489
|
case 'CREATE_TYPE':
|
@@ -581,6 +657,7 @@ function sendError(err: Error, res: Response) {
|
|
581
657
|
res.status(400).send({ error: err.message });
|
582
658
|
}
|
583
659
|
}
|
660
|
+
|
584
661
|
function waitForStormStream(result: StormStream) {
|
585
662
|
return result.waitForDone();
|
586
663
|
}
|
package/src/storm/stormClient.ts
CHANGED
@@ -44,6 +44,10 @@ export interface UIPagePrompt {
|
|
44
44
|
shell_page?: string;
|
45
45
|
}
|
46
46
|
|
47
|
+
export interface UIPageSamplePrompt extends UIPagePrompt {
|
48
|
+
variantId: string;
|
49
|
+
}
|
50
|
+
|
47
51
|
export interface UIPageEditPrompt {
|
48
52
|
planDescription: string;
|
49
53
|
blockDescription: string;
|
@@ -168,14 +172,28 @@ class StormClient {
|
|
168
172
|
public createUIShells(prompt: UIShellsPrompt, conversationId?: string) {
|
169
173
|
return this.send('/v2/ui/shells', {
|
170
174
|
prompt: JSON.stringify(prompt),
|
175
|
+
});
|
176
|
+
}
|
177
|
+
|
178
|
+
public createUILandingPages(prompt: string, conversationId?: string) {
|
179
|
+
return this.send('/v2/ui/landing-pages', {
|
180
|
+
prompt: prompt,
|
171
181
|
conversationId,
|
172
182
|
});
|
173
183
|
}
|
174
184
|
|
175
|
-
public createUIPage(prompt: UIPagePrompt, conversationId?: string) {
|
185
|
+
public createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]) {
|
176
186
|
return this.send('/v2/ui/page', {
|
177
187
|
prompt: prompt,
|
178
188
|
conversationId,
|
189
|
+
history,
|
190
|
+
});
|
191
|
+
}
|
192
|
+
|
193
|
+
public classifyUIReferences(prompt: string, conversationId?: string) {
|
194
|
+
return this.send('/v2/ui/references', {
|
195
|
+
prompt: prompt,
|
196
|
+
conversationId,
|
179
197
|
});
|
180
198
|
}
|
181
199
|
|
@@ -234,6 +252,20 @@ class StormClient {
|
|
234
252
|
conversationId: conversationId,
|
235
253
|
});
|
236
254
|
}
|
255
|
+
|
256
|
+
async deleteUIPageConversation(conversationId: string) {
|
257
|
+
const options = await this.createOptions('/v2/ui/page', 'DELETE', {
|
258
|
+
prompt: '',
|
259
|
+
conversationId: conversationId,
|
260
|
+
});
|
261
|
+
|
262
|
+
const response = await fetch(options.url, {
|
263
|
+
method: options.method,
|
264
|
+
headers: options.headers,
|
265
|
+
});
|
266
|
+
|
267
|
+
return response.text();
|
268
|
+
}
|
237
269
|
}
|
238
270
|
|
239
271
|
export const stormClient = new StormClient();
|
package/src/storm/stream.ts
CHANGED
@@ -37,6 +37,7 @@ export class StormStream extends EventEmitter {
|
|
37
37
|
this.emit('data', event);
|
38
38
|
} catch (e: any) {
|
39
39
|
this.emit('error', e);
|
40
|
+
console.warn('Failed to parse JSON line', e, line);
|
40
41
|
}
|
41
42
|
}
|
42
43
|
|
@@ -98,6 +99,7 @@ export interface ConversationItem {
|
|
98
99
|
|
99
100
|
export interface StormContextRequest<T = string> {
|
100
101
|
conversationId?: string;
|
102
|
+
history?: ConversationItem[];
|
101
103
|
prompt: T;
|
102
104
|
}
|
103
105
|
|