@kapeta/local-cluster-service 0.54.0 → 0.54.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 +14 -0
- package/dist/cjs/src/storm/codegen.js +82 -56
- package/dist/cjs/src/storm/stormClient.d.ts +1 -0
- package/dist/cjs/src/storm/stormClient.js +6 -0
- package/dist/esm/src/storm/codegen.js +82 -56
- package/dist/esm/src/storm/stormClient.d.ts +1 -0
- package/dist/esm/src/storm/stormClient.js +6 -0
- package/package.json +1 -1
- package/src/storm/codegen.ts +113 -73
- package/src/storm/stormClient.ts +7 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.54.2](https://github.com/kapetacom/local-cluster-service/compare/v0.54.1...v0.54.2) (2024-06-19)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* handle error events to/from screen AIs ([#181](https://github.com/kapetacom/local-cluster-service/issues/181)) ([6cfd9d4](https://github.com/kapetacom/local-cluster-service/commit/6cfd9d48d221ba3c3b16c8514a7d19d6e4ffd805))
|
7
|
+
|
8
|
+
## [0.54.1](https://github.com/kapetacom/local-cluster-service/compare/v0.54.0...v0.54.1) (2024-06-19)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* handle screen events by adding a meta-file to context + routes ([#180](https://github.com/kapetacom/local-cluster-service/issues/180)) ([69d67f8](https://github.com/kapetacom/local-cluster-service/commit/69d67f84fd7a373d49f5c20ff9057d8b2baa620a))
|
14
|
+
|
1
15
|
# [0.54.0](https://github.com/kapetacom/local-cluster-service/compare/v0.53.5...v0.54.0) (2024-06-17)
|
2
16
|
|
3
17
|
|
@@ -151,6 +151,9 @@ class StormCodegen {
|
|
151
151
|
break;
|
152
152
|
case 'FILE_DONE':
|
153
153
|
return this.handleFileDoneOutput(blockUri, blockName, data);
|
154
|
+
default:
|
155
|
+
console.warn('Unknown event type', data);
|
156
|
+
break;
|
154
157
|
}
|
155
158
|
}
|
156
159
|
handleFileEvents(blockUri, blockName, data) {
|
@@ -242,7 +245,6 @@ class StormCodegen {
|
|
242
245
|
file.type !== codegen_1.AIFileTypes.WEB_SCREEN &&
|
243
246
|
file.type !== codegen_1.AIFileTypes.WEB_ROUTER);
|
244
247
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
245
|
-
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
246
248
|
const screenFiles = [];
|
247
249
|
let filteredEvents = [];
|
248
250
|
for (const event of this.events) {
|
@@ -251,66 +253,71 @@ class StormCodegen {
|
|
251
253
|
filteredEvents = [];
|
252
254
|
}
|
253
255
|
}
|
256
|
+
const failedEvents = filteredEvents.filter((event) => event.type.endsWith('_FAILED'));
|
257
|
+
if (failedEvents.length > 0) {
|
258
|
+
console.warn('Codegen encountered failed plan events. Plan might be invalid', failedEvents);
|
259
|
+
}
|
260
|
+
// Skip failed events - they are not relevant to the materializer
|
261
|
+
// TODO: should the materializer be able to handle failed events?
|
262
|
+
filteredEvents = filteredEvents.filter((event) => !event.type.endsWith('_FAILED') && !event.type.endsWith('ERROR_INTERNAL'));
|
254
263
|
const screenEvents = [];
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
prompt: this.userPrompt,
|
264
|
+
const getScreenEventsFile = () => ({
|
265
|
+
content: JSON.stringify(screenEvents),
|
266
|
+
filename: '<screens>.json',
|
267
|
+
type: codegen_1.AIFileTypes.CONFIG,
|
268
|
+
mode: codegen_1.MODE_WRITE_NEVER,
|
269
|
+
permissions: '0644',
|
262
270
|
});
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
this.handleUiOutput(blockUri, block.aiName, evt);
|
268
|
-
});
|
269
|
-
this.out.on('aborted', () => {
|
270
|
-
screenStream.abort();
|
271
|
-
});
|
272
|
-
await screenStream.waitForDone();
|
273
|
-
// screenfiles
|
274
|
-
const screenTemplates = screenEvents
|
275
|
-
.map((screenEvent) => ({
|
276
|
-
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
277
|
-
filename: screenEvent.payload.filename,
|
278
|
-
}))
|
279
|
-
.filter((tpl) => !!tpl.content);
|
280
|
-
await Promise.all(screenTemplates.concat(webRouters).map(async (template) => {
|
281
|
-
const payload = {
|
271
|
+
const uiEvents = [];
|
272
|
+
// generate screens
|
273
|
+
if (uiTemplates.length) {
|
274
|
+
const screenStream = await stormClient_1.stormClient.listScreens({
|
282
275
|
events: filteredEvents,
|
276
|
+
templates: uiTemplates,
|
277
|
+
context: relevantFiles,
|
283
278
|
blockName: block.aiName,
|
284
|
-
filename: template.filename,
|
285
|
-
template: template,
|
286
|
-
context: relevantFiles.concat([
|
287
|
-
{
|
288
|
-
type: codegen_1.AIFileTypes.INSTRUCTIONS,
|
289
|
-
mode: codegen_1.MODE_CREATE_ONLY,
|
290
|
-
permissions: '0644',
|
291
|
-
filename: '<screens>.md',
|
292
|
-
content: `
|
293
|
-
# Generated screens
|
294
|
-
|
295
|
-
${JSON.stringify({ screenEvents })}
|
296
|
-
|
297
|
-
`,
|
298
|
-
},
|
299
|
-
]),
|
300
279
|
prompt: this.userPrompt,
|
301
|
-
};
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
if (uiFile != undefined) {
|
306
|
-
screenFiles.push(uiFile);
|
280
|
+
});
|
281
|
+
screenStream.on('data', (evt) => {
|
282
|
+
if (evt.type === 'SCREEN') {
|
283
|
+
screenEvents.push(evt);
|
307
284
|
}
|
285
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
308
286
|
});
|
309
287
|
this.out.on('aborted', () => {
|
310
|
-
|
288
|
+
screenStream.abort();
|
311
289
|
});
|
312
|
-
await
|
313
|
-
|
290
|
+
await screenStream.waitForDone();
|
291
|
+
// screenfiles
|
292
|
+
const screenTemplates = screenEvents
|
293
|
+
.map((screenEvent) => ({
|
294
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
295
|
+
filename: screenEvent.payload.filename,
|
296
|
+
}))
|
297
|
+
.filter((tpl) => !!tpl.content);
|
298
|
+
await Promise.all(screenTemplates.map(async (template) => {
|
299
|
+
const payload = {
|
300
|
+
events: filteredEvents,
|
301
|
+
blockName: block.aiName,
|
302
|
+
filename: template.filename,
|
303
|
+
template: template,
|
304
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
305
|
+
prompt: this.userPrompt,
|
306
|
+
};
|
307
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
308
|
+
uiStream.on('data', (evt) => {
|
309
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
310
|
+
if (uiFile != undefined) {
|
311
|
+
screenFiles.push(uiFile);
|
312
|
+
}
|
313
|
+
uiEvents.push(evt);
|
314
|
+
});
|
315
|
+
this.out.on('aborted', () => {
|
316
|
+
uiStream.abort();
|
317
|
+
});
|
318
|
+
await uiStream.waitForDone();
|
319
|
+
}));
|
320
|
+
}
|
314
321
|
if (this.isAborted()) {
|
315
322
|
return;
|
316
323
|
}
|
@@ -324,6 +331,24 @@ ${JSON.stringify({ screenEvents })}
|
|
324
331
|
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
325
332
|
};
|
326
333
|
});
|
334
|
+
allFiles.push(...screenFilesConverted);
|
335
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
336
|
+
await Promise.all(webRouters.map(async (webRouter) => {
|
337
|
+
const payload = {
|
338
|
+
filename: webRouter.filename,
|
339
|
+
template: webRouter,
|
340
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
341
|
+
prompt: this.userPrompt,
|
342
|
+
};
|
343
|
+
const stream = await stormClient_1.stormClient.generateCode(payload);
|
344
|
+
stream.on('data', (evt) => {
|
345
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
346
|
+
});
|
347
|
+
this.out.on('aborted', () => {
|
348
|
+
stream.abort();
|
349
|
+
});
|
350
|
+
await stream.waitForDone();
|
351
|
+
}));
|
327
352
|
// Gather the context files for implementation. These will be all be passed to the AI
|
328
353
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
329
354
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
@@ -346,12 +371,13 @@ ${JSON.stringify({ screenEvents })}
|
|
346
371
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
347
372
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
348
373
|
}
|
374
|
+
// Write again after modifications
|
375
|
+
for (const webRouterFile of webRouters) {
|
376
|
+
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
377
|
+
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
378
|
+
}
|
349
379
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
350
380
|
await (0, promises_1.writeFile)(kapetaYmlPath, yaml_1.default.stringify(block.content));
|
351
|
-
for (const screenFile of screenFiles) {
|
352
|
-
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
353
|
-
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
354
|
-
}
|
355
381
|
const blockRef = block.uri;
|
356
382
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
357
383
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -13,6 +13,7 @@ declare class StormClient {
|
|
13
13
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
14
14
|
createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
15
15
|
createErrorDetails(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
|
+
generateCode(prompt: StormFileImplementationPrompt, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
17
|
}
|
17
18
|
export declare const stormClient: StormClient;
|
18
19
|
export {};
|
@@ -116,5 +116,11 @@ class StormClient {
|
|
116
116
|
prompt,
|
117
117
|
});
|
118
118
|
}
|
119
|
+
generateCode(prompt, history, conversationId) {
|
120
|
+
return this.send('/v2/code/generate', {
|
121
|
+
conversationId: conversationId,
|
122
|
+
prompt,
|
123
|
+
});
|
124
|
+
}
|
119
125
|
}
|
120
126
|
exports.stormClient = new StormClient();
|
@@ -151,6 +151,9 @@ class StormCodegen {
|
|
151
151
|
break;
|
152
152
|
case 'FILE_DONE':
|
153
153
|
return this.handleFileDoneOutput(blockUri, blockName, data);
|
154
|
+
default:
|
155
|
+
console.warn('Unknown event type', data);
|
156
|
+
break;
|
154
157
|
}
|
155
158
|
}
|
156
159
|
handleFileEvents(blockUri, blockName, data) {
|
@@ -242,7 +245,6 @@ class StormCodegen {
|
|
242
245
|
file.type !== codegen_1.AIFileTypes.WEB_SCREEN &&
|
243
246
|
file.type !== codegen_1.AIFileTypes.WEB_ROUTER);
|
244
247
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
245
|
-
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
246
248
|
const screenFiles = [];
|
247
249
|
let filteredEvents = [];
|
248
250
|
for (const event of this.events) {
|
@@ -251,66 +253,71 @@ class StormCodegen {
|
|
251
253
|
filteredEvents = [];
|
252
254
|
}
|
253
255
|
}
|
256
|
+
const failedEvents = filteredEvents.filter((event) => event.type.endsWith('_FAILED'));
|
257
|
+
if (failedEvents.length > 0) {
|
258
|
+
console.warn('Codegen encountered failed plan events. Plan might be invalid', failedEvents);
|
259
|
+
}
|
260
|
+
// Skip failed events - they are not relevant to the materializer
|
261
|
+
// TODO: should the materializer be able to handle failed events?
|
262
|
+
filteredEvents = filteredEvents.filter((event) => !event.type.endsWith('_FAILED') && !event.type.endsWith('ERROR_INTERNAL'));
|
254
263
|
const screenEvents = [];
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
prompt: this.userPrompt,
|
264
|
+
const getScreenEventsFile = () => ({
|
265
|
+
content: JSON.stringify(screenEvents),
|
266
|
+
filename: '<screens>.json',
|
267
|
+
type: codegen_1.AIFileTypes.CONFIG,
|
268
|
+
mode: codegen_1.MODE_WRITE_NEVER,
|
269
|
+
permissions: '0644',
|
262
270
|
});
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
this.handleUiOutput(blockUri, block.aiName, evt);
|
268
|
-
});
|
269
|
-
this.out.on('aborted', () => {
|
270
|
-
screenStream.abort();
|
271
|
-
});
|
272
|
-
await screenStream.waitForDone();
|
273
|
-
// screenfiles
|
274
|
-
const screenTemplates = screenEvents
|
275
|
-
.map((screenEvent) => ({
|
276
|
-
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
277
|
-
filename: screenEvent.payload.filename,
|
278
|
-
}))
|
279
|
-
.filter((tpl) => !!tpl.content);
|
280
|
-
await Promise.all(screenTemplates.concat(webRouters).map(async (template) => {
|
281
|
-
const payload = {
|
271
|
+
const uiEvents = [];
|
272
|
+
// generate screens
|
273
|
+
if (uiTemplates.length) {
|
274
|
+
const screenStream = await stormClient_1.stormClient.listScreens({
|
282
275
|
events: filteredEvents,
|
276
|
+
templates: uiTemplates,
|
277
|
+
context: relevantFiles,
|
283
278
|
blockName: block.aiName,
|
284
|
-
filename: template.filename,
|
285
|
-
template: template,
|
286
|
-
context: relevantFiles.concat([
|
287
|
-
{
|
288
|
-
type: codegen_1.AIFileTypes.INSTRUCTIONS,
|
289
|
-
mode: codegen_1.MODE_CREATE_ONLY,
|
290
|
-
permissions: '0644',
|
291
|
-
filename: '<screens>.md',
|
292
|
-
content: `
|
293
|
-
# Generated screens
|
294
|
-
|
295
|
-
${JSON.stringify({ screenEvents })}
|
296
|
-
|
297
|
-
`,
|
298
|
-
},
|
299
|
-
]),
|
300
279
|
prompt: this.userPrompt,
|
301
|
-
};
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
if (uiFile != undefined) {
|
306
|
-
screenFiles.push(uiFile);
|
280
|
+
});
|
281
|
+
screenStream.on('data', (evt) => {
|
282
|
+
if (evt.type === 'SCREEN') {
|
283
|
+
screenEvents.push(evt);
|
307
284
|
}
|
285
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
308
286
|
});
|
309
287
|
this.out.on('aborted', () => {
|
310
|
-
|
288
|
+
screenStream.abort();
|
311
289
|
});
|
312
|
-
await
|
313
|
-
|
290
|
+
await screenStream.waitForDone();
|
291
|
+
// screenfiles
|
292
|
+
const screenTemplates = screenEvents
|
293
|
+
.map((screenEvent) => ({
|
294
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
295
|
+
filename: screenEvent.payload.filename,
|
296
|
+
}))
|
297
|
+
.filter((tpl) => !!tpl.content);
|
298
|
+
await Promise.all(screenTemplates.map(async (template) => {
|
299
|
+
const payload = {
|
300
|
+
events: filteredEvents,
|
301
|
+
blockName: block.aiName,
|
302
|
+
filename: template.filename,
|
303
|
+
template: template,
|
304
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
305
|
+
prompt: this.userPrompt,
|
306
|
+
};
|
307
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
308
|
+
uiStream.on('data', (evt) => {
|
309
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
310
|
+
if (uiFile != undefined) {
|
311
|
+
screenFiles.push(uiFile);
|
312
|
+
}
|
313
|
+
uiEvents.push(evt);
|
314
|
+
});
|
315
|
+
this.out.on('aborted', () => {
|
316
|
+
uiStream.abort();
|
317
|
+
});
|
318
|
+
await uiStream.waitForDone();
|
319
|
+
}));
|
320
|
+
}
|
314
321
|
if (this.isAborted()) {
|
315
322
|
return;
|
316
323
|
}
|
@@ -324,6 +331,24 @@ ${JSON.stringify({ screenEvents })}
|
|
324
331
|
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
325
332
|
};
|
326
333
|
});
|
334
|
+
allFiles.push(...screenFilesConverted);
|
335
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
336
|
+
await Promise.all(webRouters.map(async (webRouter) => {
|
337
|
+
const payload = {
|
338
|
+
filename: webRouter.filename,
|
339
|
+
template: webRouter,
|
340
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
341
|
+
prompt: this.userPrompt,
|
342
|
+
};
|
343
|
+
const stream = await stormClient_1.stormClient.generateCode(payload);
|
344
|
+
stream.on('data', (evt) => {
|
345
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
346
|
+
});
|
347
|
+
this.out.on('aborted', () => {
|
348
|
+
stream.abort();
|
349
|
+
});
|
350
|
+
await stream.waitForDone();
|
351
|
+
}));
|
327
352
|
// Gather the context files for implementation. These will be all be passed to the AI
|
328
353
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
329
354
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
@@ -346,12 +371,13 @@ ${JSON.stringify({ screenEvents })}
|
|
346
371
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
347
372
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
348
373
|
}
|
374
|
+
// Write again after modifications
|
375
|
+
for (const webRouterFile of webRouters) {
|
376
|
+
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
377
|
+
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
378
|
+
}
|
349
379
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
350
380
|
await (0, promises_1.writeFile)(kapetaYmlPath, yaml_1.default.stringify(block.content));
|
351
|
-
for (const screenFile of screenFiles) {
|
352
|
-
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
353
|
-
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
354
|
-
}
|
355
381
|
const blockRef = block.uri;
|
356
382
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
357
383
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -13,6 +13,7 @@ declare class StormClient {
|
|
13
13
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
14
14
|
createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
15
15
|
createErrorDetails(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
|
+
generateCode(prompt: StormFileImplementationPrompt, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
17
|
}
|
17
18
|
export declare const stormClient: StormClient;
|
18
19
|
export {};
|
@@ -116,5 +116,11 @@ class StormClient {
|
|
116
116
|
prompt,
|
117
117
|
});
|
118
118
|
}
|
119
|
+
generateCode(prompt, history, conversationId) {
|
120
|
+
return this.send('/v2/code/generate', {
|
121
|
+
conversationId: conversationId,
|
122
|
+
prompt,
|
123
|
+
});
|
124
|
+
}
|
119
125
|
}
|
120
126
|
exports.stormClient = new StormClient();
|
package/package.json
CHANGED
package/src/storm/codegen.ts
CHANGED
@@ -12,6 +12,7 @@ import {
|
|
12
12
|
GeneratedFile,
|
13
13
|
GeneratedResult,
|
14
14
|
MODE_CREATE_ONLY,
|
15
|
+
MODE_WRITE_NEVER,
|
15
16
|
} from '@kapeta/codegen';
|
16
17
|
|
17
18
|
import { BlockDefinition } from '@kapeta/schemas';
|
@@ -173,6 +174,9 @@ export class StormCodegen {
|
|
173
174
|
break;
|
174
175
|
case 'FILE_DONE':
|
175
176
|
return this.handleFileDoneOutput(blockUri, blockName, data);
|
177
|
+
default:
|
178
|
+
console.warn('Unknown event type', data);
|
179
|
+
break;
|
176
180
|
}
|
177
181
|
}
|
178
182
|
|
@@ -277,8 +281,8 @@ export class StormCodegen {
|
|
277
281
|
file.type !== AIFileTypes.WEB_ROUTER
|
278
282
|
);
|
279
283
|
const uiTemplates = allFiles.filter((file) => file.type === AIFileTypes.WEB_SCREEN);
|
280
|
-
const webRouters = allFiles.filter((file) => file.type === AIFileTypes.WEB_ROUTER);
|
281
284
|
const screenFiles: StormEventFileDone[] = [];
|
285
|
+
|
282
286
|
let filteredEvents = [] as StormEvent[];
|
283
287
|
for (const event of this.events) {
|
284
288
|
filteredEvents.push(event);
|
@@ -287,76 +291,85 @@ export class StormCodegen {
|
|
287
291
|
}
|
288
292
|
}
|
289
293
|
|
290
|
-
const
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
screenStream.on('data', (evt) => {
|
300
|
-
if (evt.type === 'SCREEN') {
|
301
|
-
screenEvents.push(evt);
|
302
|
-
}
|
303
|
-
this.handleUiOutput(blockUri, block.aiName, evt);
|
304
|
-
});
|
294
|
+
const failedEvents = filteredEvents.filter((event) => event.type.endsWith('_FAILED'));
|
295
|
+
if (failedEvents.length > 0) {
|
296
|
+
console.warn('Codegen encountered failed plan events. Plan might be invalid', failedEvents);
|
297
|
+
}
|
298
|
+
// Skip failed events - they are not relevant to the materializer
|
299
|
+
// TODO: should the materializer be able to handle failed events?
|
300
|
+
filteredEvents = filteredEvents.filter(
|
301
|
+
(event) => !event.type.endsWith('_FAILED') && !event.type.endsWith('ERROR_INTERNAL')
|
302
|
+
);
|
305
303
|
|
306
|
-
|
307
|
-
|
304
|
+
const screenEvents: StormEventScreen[] = [];
|
305
|
+
const getScreenEventsFile = () => ({
|
306
|
+
content: JSON.stringify(screenEvents),
|
307
|
+
filename: '<screens>.json',
|
308
|
+
type: AIFileTypes.CONFIG,
|
309
|
+
mode: MODE_WRITE_NEVER,
|
310
|
+
permissions: '0644',
|
308
311
|
});
|
312
|
+
const uiEvents = [];
|
309
313
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
filename: template.filename,
|
326
|
-
template: template,
|
327
|
-
context: relevantFiles.concat([
|
328
|
-
{
|
329
|
-
type: AIFileTypes.INSTRUCTIONS,
|
330
|
-
mode: MODE_CREATE_ONLY,
|
331
|
-
permissions: '0644',
|
332
|
-
filename: '<screens>.md',
|
333
|
-
content: `
|
334
|
-
# Generated screens
|
335
|
-
|
336
|
-
${JSON.stringify({ screenEvents })}
|
337
|
-
|
338
|
-
`,
|
339
|
-
},
|
340
|
-
]),
|
341
|
-
prompt: this.userPrompt,
|
342
|
-
};
|
343
|
-
|
344
|
-
const uiStream = await stormClient.createUIImplementation(payload);
|
345
|
-
|
346
|
-
uiStream.on('data', (evt) => {
|
347
|
-
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
348
|
-
if (uiFile != undefined) {
|
349
|
-
screenFiles.push(uiFile);
|
350
|
-
}
|
351
|
-
});
|
314
|
+
// generate screens
|
315
|
+
if (uiTemplates.length) {
|
316
|
+
const screenStream = await stormClient.listScreens({
|
317
|
+
events: filteredEvents,
|
318
|
+
templates: uiTemplates,
|
319
|
+
context: relevantFiles,
|
320
|
+
blockName: block.aiName,
|
321
|
+
prompt: this.userPrompt,
|
322
|
+
});
|
323
|
+
screenStream.on('data', (evt) => {
|
324
|
+
if (evt.type === 'SCREEN') {
|
325
|
+
screenEvents.push(evt);
|
326
|
+
}
|
327
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
328
|
+
});
|
352
329
|
|
353
|
-
|
354
|
-
|
355
|
-
|
330
|
+
this.out.on('aborted', () => {
|
331
|
+
screenStream.abort();
|
332
|
+
});
|
356
333
|
|
357
|
-
|
358
|
-
|
359
|
-
|
334
|
+
await screenStream.waitForDone();
|
335
|
+
|
336
|
+
// screenfiles
|
337
|
+
const screenTemplates = screenEvents
|
338
|
+
.map((screenEvent) => ({
|
339
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
340
|
+
filename: screenEvent.payload.filename,
|
341
|
+
}))
|
342
|
+
.filter((tpl): tpl is StormFileInfo => !!tpl.content);
|
343
|
+
|
344
|
+
await Promise.all(
|
345
|
+
screenTemplates.map(async (template) => {
|
346
|
+
const payload = {
|
347
|
+
events: filteredEvents,
|
348
|
+
blockName: block.aiName,
|
349
|
+
filename: template.filename,
|
350
|
+
template: template,
|
351
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
352
|
+
prompt: this.userPrompt,
|
353
|
+
};
|
354
|
+
|
355
|
+
const uiStream = await stormClient.createUIImplementation(payload);
|
356
|
+
|
357
|
+
uiStream.on('data', (evt) => {
|
358
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
359
|
+
if (uiFile != undefined) {
|
360
|
+
screenFiles.push(uiFile);
|
361
|
+
}
|
362
|
+
uiEvents.push(evt);
|
363
|
+
});
|
364
|
+
|
365
|
+
this.out.on('aborted', () => {
|
366
|
+
uiStream.abort();
|
367
|
+
});
|
368
|
+
|
369
|
+
await uiStream.waitForDone();
|
370
|
+
})
|
371
|
+
);
|
372
|
+
}
|
360
373
|
|
361
374
|
if (this.isAborted()) {
|
362
375
|
return;
|
@@ -372,6 +385,31 @@ ${JSON.stringify({ screenEvents })}
|
|
372
385
|
type: AIFileTypes.WEB_SCREEN,
|
373
386
|
};
|
374
387
|
});
|
388
|
+
allFiles.push(...screenFilesConverted);
|
389
|
+
|
390
|
+
const webRouters = allFiles.filter((file) => file.type === AIFileTypes.WEB_ROUTER);
|
391
|
+
await Promise.all(
|
392
|
+
webRouters.map(async (webRouter) => {
|
393
|
+
const payload = {
|
394
|
+
filename: webRouter.filename,
|
395
|
+
template: webRouter,
|
396
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
397
|
+
prompt: this.userPrompt,
|
398
|
+
};
|
399
|
+
|
400
|
+
const stream = await stormClient.generateCode(payload);
|
401
|
+
|
402
|
+
stream.on('data', (evt) => {
|
403
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
404
|
+
});
|
405
|
+
|
406
|
+
this.out.on('aborted', () => {
|
407
|
+
stream.abort();
|
408
|
+
});
|
409
|
+
|
410
|
+
await stream.waitForDone();
|
411
|
+
})
|
412
|
+
);
|
375
413
|
|
376
414
|
// Gather the context files for implementation. These will be all be passed to the AI
|
377
415
|
const contextFiles: StormFileInfo[] = relevantFiles.filter(
|
@@ -409,20 +447,21 @@ ${JSON.stringify({ screenEvents })}
|
|
409
447
|
await writeFile(filePath, serviceFile.content);
|
410
448
|
}
|
411
449
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
const filePath = join(basePath, screenFile.payload.filename);
|
417
|
-
await writeFile(filePath, screenFile.payload.content);
|
450
|
+
// Write again after modifications
|
451
|
+
for (const webRouterFile of webRouters) {
|
452
|
+
const filePath = join(basePath, webRouterFile.filename);
|
453
|
+
await writeFile(filePath, webRouterFile.content);
|
418
454
|
}
|
419
455
|
|
456
|
+
const kapetaYmlPath = join(basePath, 'kapeta.yml');
|
457
|
+
await writeFile(kapetaYmlPath, YAML.stringify(block.content));
|
458
|
+
|
420
459
|
const blockRef = block.uri;
|
421
460
|
|
422
461
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
|
423
462
|
|
424
463
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
425
|
-
const codeGenerator = new BlockCodeGenerator(block.content
|
464
|
+
const codeGenerator = new BlockCodeGenerator(block.content);
|
426
465
|
|
427
466
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.BUILDING);
|
428
467
|
await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
@@ -881,6 +920,7 @@ ${JSON.stringify({ screenEvents })}
|
|
881
920
|
type,
|
882
921
|
};
|
883
922
|
});
|
923
|
+
|
884
924
|
return allFiles;
|
885
925
|
}
|
886
926
|
|
package/src/storm/stormClient.ts
CHANGED
@@ -154,6 +154,13 @@ class StormClient {
|
|
154
154
|
prompt,
|
155
155
|
});
|
156
156
|
}
|
157
|
+
|
158
|
+
public generateCode(prompt: StormFileImplementationPrompt, history?: ConversationItem[], conversationId?: string) {
|
159
|
+
return this.send('/v2/code/generate', {
|
160
|
+
conversationId: conversationId,
|
161
|
+
prompt,
|
162
|
+
});
|
163
|
+
}
|
157
164
|
}
|
158
165
|
|
159
166
|
export const stormClient = new StormClient();
|