@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 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
- // generate screens
256
- const screenStream = await stormClient_1.stormClient.listScreens({
257
- events: filteredEvents,
258
- templates: uiTemplates,
259
- context: relevantFiles,
260
- blockName: block.aiName,
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
- screenStream.on('data', (evt) => {
264
- if (evt.type === 'SCREEN') {
265
- screenEvents.push(evt);
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
- const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
303
- uiStream.on('data', (evt) => {
304
- const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
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
- uiStream.abort();
288
+ screenStream.abort();
311
289
  });
312
- await uiStream.waitForDone();
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
- // generate screens
256
- const screenStream = await stormClient_1.stormClient.listScreens({
257
- events: filteredEvents,
258
- templates: uiTemplates,
259
- context: relevantFiles,
260
- blockName: block.aiName,
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
- screenStream.on('data', (evt) => {
264
- if (evt.type === 'SCREEN') {
265
- screenEvents.push(evt);
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
- const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
303
- uiStream.on('data', (evt) => {
304
- const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
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
- uiStream.abort();
288
+ screenStream.abort();
311
289
  });
312
- await uiStream.waitForDone();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -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 screenEvents: StormEventScreen[] = [];
291
- // generate screens
292
- const screenStream = await stormClient.listScreens({
293
- events: filteredEvents,
294
- templates: uiTemplates,
295
- context: relevantFiles,
296
- blockName: block.aiName,
297
- prompt: this.userPrompt,
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
- this.out.on('aborted', () => {
307
- screenStream.abort();
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
- await screenStream.waitForDone();
311
-
312
- // screenfiles
313
- const screenTemplates = screenEvents
314
- .map((screenEvent) => ({
315
- ...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
316
- filename: screenEvent.payload.filename,
317
- }))
318
- .filter((tpl): tpl is StormFileInfo => !!tpl.content);
319
-
320
- await Promise.all(
321
- screenTemplates.concat(webRouters).map(async (template) => {
322
- const payload = {
323
- events: filteredEvents,
324
- blockName: block.aiName,
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
- this.out.on('aborted', () => {
354
- uiStream.abort();
355
- });
330
+ this.out.on('aborted', () => {
331
+ screenStream.abort();
332
+ });
356
333
 
357
- await uiStream.waitForDone();
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
- const kapetaYmlPath = join(basePath, 'kapeta.yml');
413
- await writeFile(kapetaYmlPath, YAML.stringify(block.content as BlockDefinition));
414
-
415
- for (const screenFile of screenFiles) {
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 as BlockDefinition);
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
 
@@ -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();