@kapeta/local-cluster-service 0.74.0 → 0.74.2
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 +15 -0
- package/dist/cjs/src/storm/PageGenerator.js +12 -5
- package/dist/cjs/src/storm/codegen.js +11 -7
- package/dist/cjs/src/storm/events.d.ts +2 -0
- package/dist/cjs/src/storm/routes.js +31 -14
- package/dist/cjs/src/storm/stormClient.d.ts +4 -4
- package/dist/cjs/src/storm/stormClient.js +13 -3
- package/dist/cjs/src/storm/stream.d.ts +2 -0
- package/dist/cjs/src/stormService.d.ts +2 -2
- package/dist/cjs/src/stormService.js +8 -6
- package/dist/esm/src/storm/PageGenerator.js +12 -5
- package/dist/esm/src/storm/codegen.js +11 -7
- package/dist/esm/src/storm/events.d.ts +2 -0
- package/dist/esm/src/storm/routes.js +31 -14
- package/dist/esm/src/storm/stormClient.d.ts +4 -4
- package/dist/esm/src/storm/stormClient.js +13 -3
- package/dist/esm/src/storm/stream.d.ts +2 -0
- package/dist/esm/src/stormService.d.ts +2 -2
- package/dist/esm/src/stormService.js +8 -6
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +18 -8
- package/src/storm/codegen.ts +5 -2
- package/src/storm/events.ts +2 -0
- package/src/storm/routes.ts +42 -22
- package/src/storm/stormClient.ts +12 -7
- package/src/storm/stream.ts +2 -0
- package/src/stormService.ts +9 -7
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.74.2](https://github.com/kapetacom/local-cluster-service/compare/v0.74.1...v0.74.2) (2024-09-27)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* add more bail checkpoints for aborted request ([1f4823c](https://github.com/kapetacom/local-cluster-service/commit/1f4823c648d21f3ea8c8f8f2ead156d1e43c7252))
|
7
|
+
* define conversationIds for pages as early as possible ([7b020a5](https://github.com/kapetacom/local-cluster-service/commit/7b020a52ddeba60329989dafda62abc8d3565039))
|
8
|
+
|
9
|
+
## [0.74.1](https://github.com/kapetacom/local-cluster-service/compare/v0.74.0...v0.74.1) (2024-09-27)
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* adding system id to all calls ([b3e978f](https://github.com/kapetacom/local-cluster-service/commit/b3e978ffb74fe0ab1a46006412e52c63fe49eff8))
|
15
|
+
|
1
16
|
# [0.74.0](https://github.com/kapetacom/local-cluster-service/compare/v0.73.0...v0.74.0) (2024-09-26)
|
2
17
|
|
3
18
|
|
@@ -37,6 +37,7 @@ const node_events_1 = require("node:events");
|
|
37
37
|
const p_queue_1 = __importDefault(require("p-queue"));
|
38
38
|
const page_utils_1 = require("./page-utils");
|
39
39
|
const mimetypes = __importStar(require("mime-types"));
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
40
41
|
class PageQueue extends node_events_1.EventEmitter {
|
41
42
|
queue;
|
42
43
|
eventQueue;
|
@@ -166,6 +167,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
166
167
|
}
|
167
168
|
this.pages.set(normalizedPath, reference.description);
|
168
169
|
initialPrompts.push({
|
170
|
+
conversationId: (0, node_crypto_1.randomUUID)(),
|
171
|
+
id: (0, node_crypto_1.randomUUID)(),
|
169
172
|
name: reference.name,
|
170
173
|
title: reference.title,
|
171
174
|
path: normalizedPath,
|
@@ -193,20 +196,21 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
193
196
|
reason: 'reference',
|
194
197
|
created: Date.now(),
|
195
198
|
payload: {
|
199
|
+
id: prompt.id,
|
200
|
+
conversationId: prompt.conversationId,
|
196
201
|
name: prompt.name,
|
197
202
|
title: prompt.title,
|
198
203
|
filename: prompt.filename,
|
199
204
|
method: 'GET',
|
200
205
|
path: prompt.path,
|
201
206
|
prompt: prompt.description,
|
202
|
-
conversationId: '',
|
203
207
|
content: '',
|
204
208
|
description: prompt.description,
|
205
209
|
},
|
206
210
|
});
|
207
211
|
}
|
208
212
|
// Trigger but don't wait for the "bonus" pages
|
209
|
-
this.addPrompt(prompt).catch((err) => {
|
213
|
+
this.addPrompt(prompt, prompt.conversationId).catch((err) => {
|
210
214
|
console.error('Failed to generate page reference', prompt.name, err);
|
211
215
|
this.emit('error', err);
|
212
216
|
});
|
@@ -240,8 +244,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
240
244
|
console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
|
241
245
|
return;
|
242
246
|
}
|
247
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
243
248
|
this.images.set(prompt.url, prompt.description);
|
244
|
-
const result = await
|
249
|
+
const result = await client.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
|
245
250
|
let imageEvent = null;
|
246
251
|
result.on('data', (event) => {
|
247
252
|
if (event.type === 'IMAGE') {
|
@@ -256,7 +261,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
256
261
|
this.emit('image', imageEvent, prompt);
|
257
262
|
}
|
258
263
|
async generate(prompt, conversationId) {
|
259
|
-
const
|
264
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
265
|
+
const screenStream = await client.createUIPage(prompt, conversationId);
|
260
266
|
let pageEvent = null;
|
261
267
|
screenStream.on('data', (event) => {
|
262
268
|
if (event.type === 'PAGE') {
|
@@ -273,7 +279,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
273
279
|
await this.processPageEventWithReferences(pageEvent);
|
274
280
|
}
|
275
281
|
async resolveReferences(content) {
|
276
|
-
const
|
282
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
283
|
+
const referenceStream = await client.classifyUIReferences(content);
|
277
284
|
const references = [];
|
278
285
|
referenceStream.on('data', (referenceData) => {
|
279
286
|
if (referenceData.type !== 'REF_CLASSIFICATION') {
|
@@ -311,9 +311,10 @@ class StormCodegen {
|
|
311
311
|
permissions: '0644',
|
312
312
|
});
|
313
313
|
const uiEvents = [];
|
314
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
314
315
|
// generate screens
|
315
316
|
if (uiTemplates.length) {
|
316
|
-
const screenStream = await
|
317
|
+
const screenStream = await stormClient.listScreens({
|
317
318
|
events: filteredEvents,
|
318
319
|
templates: uiTemplates,
|
319
320
|
context: relevantFiles,
|
@@ -346,7 +347,7 @@ class StormCodegen {
|
|
346
347
|
context: relevantFiles.concat([getScreenEventsFile()]),
|
347
348
|
prompt: this.userPrompt,
|
348
349
|
};
|
349
|
-
const uiStream = await
|
350
|
+
const uiStream = await stormClient.createUIImplementation(payload);
|
350
351
|
uiStream.on('data', (evt) => {
|
351
352
|
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
352
353
|
if (uiFile != undefined) {
|
@@ -374,13 +375,13 @@ class StormCodegen {
|
|
374
375
|
});
|
375
376
|
allFiles.push(...screenFilesConverted);
|
376
377
|
let webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
377
|
-
webRouters = await this.processTemplates(blockUri, block.aiName,
|
378
|
+
webRouters = await this.processTemplates(blockUri, block.aiName, stormClient.generateCode.bind(stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
|
378
379
|
// Gather the context files for implementation. These will be all be passed to the AI
|
379
380
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
380
381
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
381
382
|
let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
382
383
|
if (serviceFiles.length > 0) {
|
383
|
-
serviceFiles = await this.processTemplates(blockUri, block.aiName,
|
384
|
+
serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient.createServiceImplementation.bind(stormClient), serviceFiles, contextFiles);
|
384
385
|
}
|
385
386
|
if (this.isAborted()) {
|
386
387
|
return;
|
@@ -510,7 +511,8 @@ class StormCodegen {
|
|
510
511
|
}
|
511
512
|
}
|
512
513
|
async classifyErrors(errors, basePath) {
|
513
|
-
const
|
514
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
515
|
+
const errorStream = await stormClient.createErrorClassification(errors, []);
|
514
516
|
const fixes = new Map();
|
515
517
|
this.out.on('aborted', () => {
|
516
518
|
errorStream.abort();
|
@@ -547,7 +549,8 @@ class StormCodegen {
|
|
547
549
|
error: error,
|
548
550
|
projectFiles: allFiles.map((f) => f.filename),
|
549
551
|
};
|
550
|
-
const
|
552
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
553
|
+
const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
|
551
554
|
detailsStream.on('data', (evt) => {
|
552
555
|
if (evt.type === 'ERROR_DETAILS') {
|
553
556
|
resolve(evt.payload.files);
|
@@ -611,7 +614,8 @@ class StormCodegen {
|
|
611
614
|
async codeFix(blockUri, blockName, fix, history) {
|
612
615
|
return new Promise(async (resolve, reject) => {
|
613
616
|
try {
|
614
|
-
const
|
617
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
618
|
+
const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
|
615
619
|
let resolved = false;
|
616
620
|
fixStream.on('data', (evt) => {
|
617
621
|
if (this.handleFileEvents(blockUri, blockName, evt)) {
|
@@ -287,6 +287,7 @@ export interface StormEventPhases {
|
|
287
287
|
};
|
288
288
|
}
|
289
289
|
export interface Page {
|
290
|
+
id: string;
|
290
291
|
name: string;
|
291
292
|
filename: string;
|
292
293
|
title: string;
|
@@ -329,6 +330,7 @@ export interface UserJourneyScreen {
|
|
329
330
|
path: string;
|
330
331
|
method: string;
|
331
332
|
nextScreens: string[];
|
333
|
+
conversationId?: string;
|
332
334
|
}
|
333
335
|
export interface UserJourney {
|
334
336
|
title: string;
|
@@ -117,8 +117,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
117
117
|
res.set(stormClient_1.ConversationIdHeader, systemId);
|
118
118
|
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
119
119
|
const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
|
120
|
-
const
|
120
|
+
const client = new stormClient_1.StormClient(systemId);
|
121
|
+
const pagesWithImplementation = await client.replaceMockWithAPICall({
|
121
122
|
pages: pagesFromDisk,
|
123
|
+
systemId: systemId,
|
122
124
|
});
|
123
125
|
await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
|
124
126
|
// find the page from result1 and write the content to the file
|
@@ -131,7 +133,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
131
133
|
const pageContents = pagesWithImplementation.map((page) => {
|
132
134
|
return page.content;
|
133
135
|
});
|
134
|
-
const prompt = await
|
136
|
+
const prompt = await client.generatePrompt(pageContents);
|
135
137
|
sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
|
136
138
|
req.query.systemId = systemId;
|
137
139
|
const promptRequest = {
|
@@ -149,10 +151,12 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
|
|
149
151
|
//res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
150
152
|
//res.set(ConversationIdHeader, systemId);
|
151
153
|
//sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
154
|
+
const client = new stormClient_1.StormClient(systemId);
|
152
155
|
try {
|
153
156
|
const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
|
154
|
-
const pagesWithImplementation = await
|
157
|
+
const pagesWithImplementation = await client.replaceMockWithAPICall({
|
155
158
|
pages: pagesFromDisk,
|
159
|
+
systemId: systemId,
|
156
160
|
});
|
157
161
|
//sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
158
162
|
//sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
|
@@ -165,7 +169,7 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
|
|
165
169
|
}
|
166
170
|
return page;
|
167
171
|
});
|
168
|
-
const systemUrl = await
|
172
|
+
const systemUrl = await client.createSimpleBackend(handle, systemId, { pages: allFiles });
|
169
173
|
//sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
|
170
174
|
//sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
|
171
175
|
//sendDone(res);
|
@@ -250,7 +254,8 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
250
254
|
try {
|
251
255
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
252
256
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
253
|
-
const
|
257
|
+
const client = new stormClient_1.StormClient(conversationId); //todo is this correct we are using the landing page getConversationId down below as well
|
258
|
+
const landingPagesStream = await client.createUILandingPages(aiRequest, conversationId);
|
254
259
|
onRequestAborted(req, res, () => {
|
255
260
|
landingPagesStream.abort();
|
256
261
|
});
|
@@ -339,8 +344,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
339
344
|
try {
|
340
345
|
const outerConversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || (0, crypto_1.randomUUID)();
|
341
346
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
347
|
+
const stormClient = new stormClient_1.StormClient(outerConversationId);
|
342
348
|
// Get user journeys
|
343
|
-
const userJourneysStream = await
|
349
|
+
const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest, outerConversationId);
|
344
350
|
onRequestAborted(req, res, () => {
|
345
351
|
userJourneysStream.abort();
|
346
352
|
});
|
@@ -351,11 +357,11 @@ router.post('/:handle/ui', async (req, res) => {
|
|
351
357
|
let systemPrompt = aiRequest.prompt;
|
352
358
|
userJourneysStream.on('data', (data) => {
|
353
359
|
try {
|
354
|
-
sendEvent(res, data);
|
355
360
|
if (data.type === 'PROMPT_IMPROVE') {
|
356
361
|
systemPrompt = data.payload.prompt;
|
357
362
|
}
|
358
363
|
if (data.type !== 'USER_JOURNEY') {
|
364
|
+
sendEvent(res, data);
|
359
365
|
return;
|
360
366
|
}
|
361
367
|
if (userJourneysStream.isAborted()) {
|
@@ -363,9 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
|
|
363
369
|
}
|
364
370
|
data.payload.screens.forEach((screen) => {
|
365
371
|
if (!uniqueUserJourneyScreens[screen.name]) {
|
372
|
+
screen.conversationId = (0, crypto_1.randomUUID)();
|
366
373
|
uniqueUserJourneyScreens[screen.name] = screen;
|
367
374
|
}
|
368
375
|
});
|
376
|
+
sendEvent(res, data);
|
369
377
|
}
|
370
378
|
catch (e) {
|
371
379
|
console.error('Failed to process event', e);
|
@@ -378,7 +386,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
378
386
|
});
|
379
387
|
let theme = '';
|
380
388
|
try {
|
381
|
-
const themeStream = await
|
389
|
+
const themeStream = await stormClient.createTheme(aiRequest, outerConversationId);
|
382
390
|
onRequestAborted(req, res, () => {
|
383
391
|
themeStream.abort();
|
384
392
|
});
|
@@ -416,8 +424,11 @@ router.post('/:handle/ui', async (req, res) => {
|
|
416
424
|
});
|
417
425
|
}
|
418
426
|
await waitForStormStream(userJourneysStream);
|
427
|
+
if (req.socket.closed) {
|
428
|
+
return;
|
429
|
+
}
|
419
430
|
// Get the UI shells
|
420
|
-
const shellsStream = await
|
431
|
+
const shellsStream = await stormClient.createUIShells({
|
421
432
|
theme: theme || undefined,
|
422
433
|
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
423
434
|
name: screen.name,
|
@@ -450,6 +461,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
450
461
|
sendError(error, res);
|
451
462
|
});
|
452
463
|
await waitForStormStream(shellsStream);
|
464
|
+
if (req.socket.closed) {
|
465
|
+
return;
|
466
|
+
}
|
453
467
|
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
454
468
|
await UI_SERVERS[outerConversationId].start();
|
455
469
|
sendEvent(res, {
|
@@ -464,7 +478,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
464
478
|
onRequestAborted(req, res, () => {
|
465
479
|
queue.cancel();
|
466
480
|
});
|
467
|
-
queue.on('page', (
|
481
|
+
queue.on('page', (pageEvent) => sendPageEvent(outerConversationId, pageEvent, res));
|
468
482
|
queue.on('event', (event) => {
|
469
483
|
if (event.type === 'FILE_CHUNK') {
|
470
484
|
return;
|
@@ -487,7 +501,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
487
501
|
filename: screen.filename,
|
488
502
|
storage_prefix: outerConversationId + '_',
|
489
503
|
theme,
|
490
|
-
})
|
504
|
+
}, screen.conversationId)
|
491
505
|
.catch((e) => {
|
492
506
|
console.error('Failed to generate page for screen %s', screen.name, e);
|
493
507
|
sendError(e, res);
|
@@ -574,7 +588,8 @@ router.post('/ui/vote', async (req, res) => {
|
|
574
588
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
575
589
|
const { topic, vote, mainConversationId } = aiRequest;
|
576
590
|
try {
|
577
|
-
|
591
|
+
const stormClient = new stormClient_1.StormClient(mainConversationId);
|
592
|
+
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
578
593
|
}
|
579
594
|
catch (e) {
|
580
595
|
res.status(500).send({ error: e.message });
|
@@ -585,7 +600,8 @@ router.post('/ui/get-vote', async (req, res) => {
|
|
585
600
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
586
601
|
const { topic, mainConversationId } = aiRequest;
|
587
602
|
try {
|
588
|
-
const
|
603
|
+
const stormClient = new stormClient_1.StormClient(mainConversationId);
|
604
|
+
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
589
605
|
res.send({ vote });
|
590
606
|
}
|
591
607
|
catch (e) {
|
@@ -609,7 +625,8 @@ async function handleAll(req, res) {
|
|
609
625
|
const eventParser = new event_parser_1.StormEventParser(stormOptions);
|
610
626
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
611
627
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
612
|
-
const
|
628
|
+
const stormClient = new stormClient_1.StormClient(systemId);
|
629
|
+
const metaStream = await stormClient.createMetadata(aiRequest, conversationId);
|
613
630
|
onRequestAborted(req, res, () => {
|
614
631
|
metaStream.abort();
|
615
632
|
});
|
@@ -4,6 +4,7 @@ import { ConversationItem, CreateSimpleBackendRequest, HTMLPage, ImplementAPICli
|
|
4
4
|
import { Page, StormEventPageUrl } from './events';
|
5
5
|
export declare const STORM_ID = "storm";
|
6
6
|
export declare const ConversationIdHeader = "Conversation-Id";
|
7
|
+
export declare const SystemIdHeader = "System-Id";
|
7
8
|
export interface UIShellsPrompt {
|
8
9
|
theme?: string;
|
9
10
|
pages: {
|
@@ -55,9 +56,10 @@ export interface BasePromptRequest {
|
|
55
56
|
prompt: string;
|
56
57
|
skipImprovement?: boolean;
|
57
58
|
}
|
58
|
-
declare class StormClient {
|
59
|
+
export declare class StormClient {
|
59
60
|
private readonly _baseUrl;
|
60
|
-
|
61
|
+
private readonly _systemId;
|
62
|
+
constructor(systemId?: string);
|
61
63
|
private createOptions;
|
62
64
|
private send;
|
63
65
|
createMetadata(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
@@ -88,5 +90,3 @@ declare class StormClient {
|
|
88
90
|
downloadSystem(handle: string, conversationId: string): Promise<Buffer>;
|
89
91
|
uploadSystem(handle: string, conversationId: string, buffer: Buffer): Promise<Response>;
|
90
92
|
}
|
91
|
-
export declare const stormClient: StormClient;
|
92
|
-
export {};
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.
|
6
|
+
exports.StormClient = exports.SystemIdHeader = exports.ConversationIdHeader = exports.STORM_ID = void 0;
|
7
7
|
/**
|
8
8
|
* Copyright 2023 Kapeta Inc.
|
9
9
|
* SPDX-License-Identifier: BUSL-1.1
|
@@ -17,10 +17,13 @@ const fetch_retry_1 = __importDefault(require("fetch-retry"));
|
|
17
17
|
const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5, retryDelay: 10 });
|
18
18
|
exports.STORM_ID = 'storm';
|
19
19
|
exports.ConversationIdHeader = 'Conversation-Id';
|
20
|
+
exports.SystemIdHeader = 'System-Id';
|
20
21
|
class StormClient {
|
21
22
|
_baseUrl;
|
22
|
-
|
23
|
+
_systemId;
|
24
|
+
constructor(systemId) {
|
23
25
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
26
|
+
this._systemId = systemId || "";
|
24
27
|
}
|
25
28
|
async createOptions(path, method, body) {
|
26
29
|
const url = `${this._baseUrl}${path}`;
|
@@ -35,6 +38,9 @@ class StormClient {
|
|
35
38
|
if (body.conversationId) {
|
36
39
|
headers[exports.ConversationIdHeader] = body.conversationId;
|
37
40
|
}
|
41
|
+
if (this._systemId) {
|
42
|
+
headers[exports.SystemIdHeader] = this._systemId;
|
43
|
+
}
|
38
44
|
return {
|
39
45
|
url,
|
40
46
|
method: method,
|
@@ -103,6 +109,7 @@ class StormClient {
|
|
103
109
|
createUIShells(prompt, conversationId) {
|
104
110
|
return this.send('/v2/ui/shells', {
|
105
111
|
prompt: JSON.stringify(prompt),
|
112
|
+
conversationId: conversationId,
|
106
113
|
});
|
107
114
|
}
|
108
115
|
createUILandingPages(prompt, conversationId) {
|
@@ -138,6 +145,9 @@ class StormClient {
|
|
138
145
|
const response = await fetch(u, {
|
139
146
|
method: 'POST',
|
140
147
|
body: JSON.stringify(prompt.pages),
|
148
|
+
headers: {
|
149
|
+
'systemId': prompt.systemId,
|
150
|
+
},
|
141
151
|
});
|
142
152
|
return (await response.json());
|
143
153
|
}
|
@@ -252,4 +262,4 @@ class StormClient {
|
|
252
262
|
return response;
|
253
263
|
}
|
254
264
|
}
|
255
|
-
exports.
|
265
|
+
exports.StormClient = StormClient;
|
@@ -35,6 +35,7 @@ export interface ConversationItem {
|
|
35
35
|
}
|
36
36
|
export interface StormContextRequest<T = string> {
|
37
37
|
conversationId?: string;
|
38
|
+
systemId?: string;
|
38
39
|
history?: ConversationItem[];
|
39
40
|
prompt: T;
|
40
41
|
}
|
@@ -78,6 +79,7 @@ export declare enum HTMLPageEncoding {
|
|
78
79
|
}
|
79
80
|
export interface ImplementAPIClients {
|
80
81
|
pages: HTMLPage[];
|
82
|
+
systemId: string;
|
81
83
|
}
|
82
84
|
export interface HTMLPage {
|
83
85
|
fileName: string;
|
@@ -13,8 +13,8 @@ export declare class StormService {
|
|
13
13
|
saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
|
14
14
|
appendConversation(conversationId: string, events: StormEvent[]): Promise<void>;
|
15
15
|
deleteConversation(conversationId: string): Promise<void>;
|
16
|
-
uploadConversation(handle: string,
|
17
|
-
installProjectById(handle: string,
|
16
|
+
uploadConversation(handle: string, systemId: string): Promise<void>;
|
17
|
+
installProjectById(handle: string, systemId: string): Promise<void>;
|
18
18
|
}
|
19
19
|
declare const _default: StormService;
|
20
20
|
export default _default;
|
@@ -119,8 +119,8 @@ class StormService {
|
|
119
119
|
await promises_1.default.rm(conversationDir, { recursive: true, force: true });
|
120
120
|
}
|
121
121
|
}
|
122
|
-
async uploadConversation(handle,
|
123
|
-
const tarballFile = this.getConversationTarball(
|
122
|
+
async uploadConversation(handle, systemId) {
|
123
|
+
const tarballFile = this.getConversationTarball(systemId);
|
124
124
|
const destDir = path_1.default.dirname(tarballFile);
|
125
125
|
const tarballName = path_1.default.basename(tarballFile);
|
126
126
|
await tar.create({
|
@@ -129,12 +129,14 @@ class StormService {
|
|
129
129
|
gzip: true,
|
130
130
|
filter: (entry) => !entry.includes(tarballName),
|
131
131
|
}, ['.']);
|
132
|
-
|
132
|
+
const stormClient = new stormClient_1.StormClient(systemId);
|
133
|
+
await stormClient.uploadSystem(handle, systemId, await promises_1.default.readFile(tarballFile));
|
133
134
|
}
|
134
|
-
async installProjectById(handle,
|
135
|
-
const tarballFile = this.getConversationTarball(
|
135
|
+
async installProjectById(handle, systemId) {
|
136
|
+
const tarballFile = this.getConversationTarball(systemId);
|
136
137
|
const destDir = path_1.default.dirname(tarballFile);
|
137
|
-
const
|
138
|
+
const stormClient = new stormClient_1.StormClient(systemId);
|
139
|
+
const buffer = await stormClient.downloadSystem(handle, systemId);
|
138
140
|
await promises_1.default.mkdir(destDir, { recursive: true });
|
139
141
|
await promises_1.default.writeFile(tarballFile, buffer);
|
140
142
|
await tar.extract({
|
@@ -37,6 +37,7 @@ const node_events_1 = require("node:events");
|
|
37
37
|
const p_queue_1 = __importDefault(require("p-queue"));
|
38
38
|
const page_utils_1 = require("./page-utils");
|
39
39
|
const mimetypes = __importStar(require("mime-types"));
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
40
41
|
class PageQueue extends node_events_1.EventEmitter {
|
41
42
|
queue;
|
42
43
|
eventQueue;
|
@@ -166,6 +167,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
166
167
|
}
|
167
168
|
this.pages.set(normalizedPath, reference.description);
|
168
169
|
initialPrompts.push({
|
170
|
+
conversationId: (0, node_crypto_1.randomUUID)(),
|
171
|
+
id: (0, node_crypto_1.randomUUID)(),
|
169
172
|
name: reference.name,
|
170
173
|
title: reference.title,
|
171
174
|
path: normalizedPath,
|
@@ -193,20 +196,21 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
193
196
|
reason: 'reference',
|
194
197
|
created: Date.now(),
|
195
198
|
payload: {
|
199
|
+
id: prompt.id,
|
200
|
+
conversationId: prompt.conversationId,
|
196
201
|
name: prompt.name,
|
197
202
|
title: prompt.title,
|
198
203
|
filename: prompt.filename,
|
199
204
|
method: 'GET',
|
200
205
|
path: prompt.path,
|
201
206
|
prompt: prompt.description,
|
202
|
-
conversationId: '',
|
203
207
|
content: '',
|
204
208
|
description: prompt.description,
|
205
209
|
},
|
206
210
|
});
|
207
211
|
}
|
208
212
|
// Trigger but don't wait for the "bonus" pages
|
209
|
-
this.addPrompt(prompt).catch((err) => {
|
213
|
+
this.addPrompt(prompt, prompt.conversationId).catch((err) => {
|
210
214
|
console.error('Failed to generate page reference', prompt.name, err);
|
211
215
|
this.emit('error', err);
|
212
216
|
});
|
@@ -240,8 +244,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
240
244
|
console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
|
241
245
|
return;
|
242
246
|
}
|
247
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
243
248
|
this.images.set(prompt.url, prompt.description);
|
244
|
-
const result = await
|
249
|
+
const result = await client.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
|
245
250
|
let imageEvent = null;
|
246
251
|
result.on('data', (event) => {
|
247
252
|
if (event.type === 'IMAGE') {
|
@@ -256,7 +261,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
256
261
|
this.emit('image', imageEvent, prompt);
|
257
262
|
}
|
258
263
|
async generate(prompt, conversationId) {
|
259
|
-
const
|
264
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
265
|
+
const screenStream = await client.createUIPage(prompt, conversationId);
|
260
266
|
let pageEvent = null;
|
261
267
|
screenStream.on('data', (event) => {
|
262
268
|
if (event.type === 'PAGE') {
|
@@ -273,7 +279,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
273
279
|
await this.processPageEventWithReferences(pageEvent);
|
274
280
|
}
|
275
281
|
async resolveReferences(content) {
|
276
|
-
const
|
282
|
+
const client = new stormClient_1.StormClient(this.systemId);
|
283
|
+
const referenceStream = await client.classifyUIReferences(content);
|
277
284
|
const references = [];
|
278
285
|
referenceStream.on('data', (referenceData) => {
|
279
286
|
if (referenceData.type !== 'REF_CLASSIFICATION') {
|
@@ -311,9 +311,10 @@ class StormCodegen {
|
|
311
311
|
permissions: '0644',
|
312
312
|
});
|
313
313
|
const uiEvents = [];
|
314
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
314
315
|
// generate screens
|
315
316
|
if (uiTemplates.length) {
|
316
|
-
const screenStream = await
|
317
|
+
const screenStream = await stormClient.listScreens({
|
317
318
|
events: filteredEvents,
|
318
319
|
templates: uiTemplates,
|
319
320
|
context: relevantFiles,
|
@@ -346,7 +347,7 @@ class StormCodegen {
|
|
346
347
|
context: relevantFiles.concat([getScreenEventsFile()]),
|
347
348
|
prompt: this.userPrompt,
|
348
349
|
};
|
349
|
-
const uiStream = await
|
350
|
+
const uiStream = await stormClient.createUIImplementation(payload);
|
350
351
|
uiStream.on('data', (evt) => {
|
351
352
|
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
352
353
|
if (uiFile != undefined) {
|
@@ -374,13 +375,13 @@ class StormCodegen {
|
|
374
375
|
});
|
375
376
|
allFiles.push(...screenFilesConverted);
|
376
377
|
let webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
377
|
-
webRouters = await this.processTemplates(blockUri, block.aiName,
|
378
|
+
webRouters = await this.processTemplates(blockUri, block.aiName, stormClient.generateCode.bind(stormClient), webRouters, screenFilesConverted.concat([getScreenEventsFile()]));
|
378
379
|
// Gather the context files for implementation. These will be all be passed to the AI
|
379
380
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
380
381
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
381
382
|
let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
382
383
|
if (serviceFiles.length > 0) {
|
383
|
-
serviceFiles = await this.processTemplates(blockUri, block.aiName,
|
384
|
+
serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient.createServiceImplementation.bind(stormClient), serviceFiles, contextFiles);
|
384
385
|
}
|
385
386
|
if (this.isAborted()) {
|
386
387
|
return;
|
@@ -510,7 +511,8 @@ class StormCodegen {
|
|
510
511
|
}
|
511
512
|
}
|
512
513
|
async classifyErrors(errors, basePath) {
|
513
|
-
const
|
514
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
515
|
+
const errorStream = await stormClient.createErrorClassification(errors, []);
|
514
516
|
const fixes = new Map();
|
515
517
|
this.out.on('aborted', () => {
|
516
518
|
errorStream.abort();
|
@@ -547,7 +549,8 @@ class StormCodegen {
|
|
547
549
|
error: error,
|
548
550
|
projectFiles: allFiles.map((f) => f.filename),
|
549
551
|
};
|
550
|
-
const
|
552
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
553
|
+
const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
|
551
554
|
detailsStream.on('data', (evt) => {
|
552
555
|
if (evt.type === 'ERROR_DETAILS') {
|
553
556
|
resolve(evt.payload.files);
|
@@ -611,7 +614,8 @@ class StormCodegen {
|
|
611
614
|
async codeFix(blockUri, blockName, fix, history) {
|
612
615
|
return new Promise(async (resolve, reject) => {
|
613
616
|
try {
|
614
|
-
const
|
617
|
+
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
618
|
+
const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
|
615
619
|
let resolved = false;
|
616
620
|
fixStream.on('data', (evt) => {
|
617
621
|
if (this.handleFileEvents(blockUri, blockName, evt)) {
|
@@ -287,6 +287,7 @@ export interface StormEventPhases {
|
|
287
287
|
};
|
288
288
|
}
|
289
289
|
export interface Page {
|
290
|
+
id: string;
|
290
291
|
name: string;
|
291
292
|
filename: string;
|
292
293
|
title: string;
|
@@ -329,6 +330,7 @@ export interface UserJourneyScreen {
|
|
329
330
|
path: string;
|
330
331
|
method: string;
|
331
332
|
nextScreens: string[];
|
333
|
+
conversationId?: string;
|
332
334
|
}
|
333
335
|
export interface UserJourney {
|
334
336
|
title: string;
|