@kapeta/local-cluster-service 0.53.5 → 0.54.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/.eslintrc.cjs +1 -0
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.js +91 -28
- package/dist/cjs/src/storm/events.d.ts +1 -0
- package/dist/cjs/src/storm/stormClient.d.ts +3 -1
- package/dist/cjs/src/storm/stormClient.js +12 -0
- package/dist/cjs/src/storm/stream.d.ts +8 -0
- package/dist/esm/src/storm/codegen.js +91 -28
- package/dist/esm/src/storm/events.d.ts +1 -0
- package/dist/esm/src/storm/stormClient.d.ts +3 -1
- package/dist/esm/src/storm/stormClient.js +12 -0
- package/dist/esm/src/storm/stream.d.ts +8 -0
- package/package.json +2 -2
- package/src/storm/codegen.ts +125 -36
- package/src/storm/events.ts +1 -0
- package/src/storm/stormClient.ts +16 -1
- package/src/storm/stream.ts +9 -0
package/.eslintrc.cjs
CHANGED
@@ -6,6 +6,7 @@ module.exports = {
|
|
6
6
|
rules: {
|
7
7
|
'@typescript-eslint/no-explicit-any': 'off',
|
8
8
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
9
|
+
'@typescript-eslint/no-use-before-define': 'off',
|
9
10
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
10
11
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
11
12
|
'@typescript-eslint/no-unsafe-return': 'off',
|
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.54.1](https://github.com/kapetacom/local-cluster-service/compare/v0.54.0...v0.54.1) (2024-06-19)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* 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))
|
7
|
+
|
8
|
+
# [0.54.0](https://github.com/kapetacom/local-cluster-service/compare/v0.53.5...v0.54.0) (2024-06-17)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* add support for split UI routes + screen context in AI service ([#178](https://github.com/kapetacom/local-cluster-service/issues/178)) ([4330a72](https://github.com/kapetacom/local-cluster-service/commit/4330a72eddb5fe82f9e7a9c2af412d47091cf9b0))
|
14
|
+
|
1
15
|
## [0.53.5](https://github.com/kapetacom/local-cluster-service/compare/v0.53.4...v0.53.5) (2024-06-17)
|
2
16
|
|
3
17
|
|
@@ -44,7 +44,6 @@ const path_2 = __importStar(require("path"));
|
|
44
44
|
const node_os_1 = __importDefault(require("node:os"));
|
45
45
|
const fs_1 = require("fs");
|
46
46
|
const yaml_1 = __importDefault(require("yaml"));
|
47
|
-
const fs = __importStar(require("node:fs"));
|
48
47
|
const SIMULATED_DELAY = 1000;
|
49
48
|
const ENABLE_SIMULATED_DELAY = false;
|
50
49
|
class SimulatedFileDelay {
|
@@ -239,7 +238,9 @@ class StormCodegen {
|
|
239
238
|
return;
|
240
239
|
}
|
241
240
|
const blockUri = (0, nodejs_utils_1.parseKapetaUri)(block.uri);
|
242
|
-
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
241
|
+
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
242
|
+
file.type !== codegen_1.AIFileTypes.WEB_SCREEN &&
|
243
|
+
file.type !== codegen_1.AIFileTypes.WEB_ROUTER);
|
243
244
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
244
245
|
const screenFiles = [];
|
245
246
|
let filteredEvents = [];
|
@@ -249,39 +250,110 @@ class StormCodegen {
|
|
249
250
|
filteredEvents = [];
|
250
251
|
}
|
251
252
|
}
|
252
|
-
|
253
|
-
|
253
|
+
const screenEvents = [];
|
254
|
+
const getScreenEventsFile = () => ({
|
255
|
+
content: JSON.stringify(screenEvents),
|
256
|
+
filename: '<screens>.json',
|
257
|
+
type: codegen_1.AIFileTypes.CONFIG,
|
258
|
+
mode: codegen_1.MODE_WRITE_NEVER,
|
259
|
+
permissions: '0644',
|
260
|
+
});
|
261
|
+
const uiEvents = [];
|
262
|
+
// generate screens
|
263
|
+
if (uiTemplates.length) {
|
264
|
+
const screenStream = await stormClient_1.stormClient.listScreens({
|
254
265
|
events: filteredEvents,
|
255
266
|
templates: uiTemplates,
|
256
267
|
context: relevantFiles,
|
257
268
|
blockName: block.aiName,
|
258
269
|
prompt: this.userPrompt,
|
259
270
|
});
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
screenFiles.push(uiFile);
|
271
|
+
screenStream.on('data', (evt) => {
|
272
|
+
if (evt.type === 'SCREEN') {
|
273
|
+
screenEvents.push(evt);
|
264
274
|
}
|
275
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
265
276
|
});
|
266
277
|
this.out.on('aborted', () => {
|
267
|
-
|
278
|
+
screenStream.abort();
|
268
279
|
});
|
269
|
-
await
|
280
|
+
await screenStream.waitForDone();
|
281
|
+
// screenfiles
|
282
|
+
const screenTemplates = screenEvents
|
283
|
+
.map((screenEvent) => ({
|
284
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
285
|
+
filename: screenEvent.payload.filename,
|
286
|
+
}))
|
287
|
+
.filter((tpl) => !!tpl.content);
|
288
|
+
await Promise.all(screenTemplates.map(async (template) => {
|
289
|
+
const payload = {
|
290
|
+
events: filteredEvents,
|
291
|
+
blockName: block.aiName,
|
292
|
+
filename: template.filename,
|
293
|
+
template: template,
|
294
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
295
|
+
prompt: this.userPrompt,
|
296
|
+
};
|
297
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
298
|
+
uiStream.on('data', (evt) => {
|
299
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
300
|
+
if (uiFile != undefined) {
|
301
|
+
screenFiles.push(uiFile);
|
302
|
+
}
|
303
|
+
uiEvents.push(evt);
|
304
|
+
});
|
305
|
+
this.out.on('aborted', () => {
|
306
|
+
uiStream.abort();
|
307
|
+
});
|
308
|
+
await uiStream.waitForDone();
|
309
|
+
}));
|
270
310
|
}
|
271
311
|
if (this.isAborted()) {
|
272
312
|
return;
|
273
313
|
}
|
314
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
315
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
316
|
+
return {
|
317
|
+
filename: screenFile.payload.filename,
|
318
|
+
content: screenFile.payload.content,
|
319
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
320
|
+
permissions: '0644',
|
321
|
+
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
322
|
+
};
|
323
|
+
});
|
324
|
+
allFiles.push(...screenFilesConverted);
|
325
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
326
|
+
await Promise.all(webRouters.map(async (webRouter) => {
|
327
|
+
const payload = {
|
328
|
+
filename: webRouter.filename,
|
329
|
+
template: webRouter,
|
330
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
331
|
+
prompt: this.userPrompt,
|
332
|
+
};
|
333
|
+
const stream = await stormClient_1.stormClient.generateCode(payload);
|
334
|
+
stream.on('data', (evt) => {
|
335
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
336
|
+
});
|
337
|
+
this.out.on('aborted', () => {
|
338
|
+
stream.abort();
|
339
|
+
});
|
340
|
+
await stream.waitForDone();
|
341
|
+
}));
|
274
342
|
// Gather the context files for implementation. These will be all be passed to the AI
|
275
|
-
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
343
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
276
344
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
277
345
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
278
346
|
if (serviceFiles.length > 0) {
|
279
347
|
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
280
348
|
}
|
281
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
282
349
|
if (this.isAborted()) {
|
283
350
|
return;
|
284
351
|
}
|
352
|
+
for (const screenFile of screenFilesConverted) {
|
353
|
+
// this.emitFile(blockUri, block.aiName, screenFile.filename, screenFile.content);
|
354
|
+
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
355
|
+
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
356
|
+
}
|
285
357
|
for (const serviceFile of serviceFiles) {
|
286
358
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
287
359
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -290,22 +362,13 @@ class StormCodegen {
|
|
290
362
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
291
363
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
292
364
|
}
|
365
|
+
// Write again after modifications
|
366
|
+
for (const webRouterFile of webRouters) {
|
367
|
+
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
368
|
+
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
369
|
+
}
|
293
370
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
294
371
|
await (0, promises_1.writeFile)(kapetaYmlPath, yaml_1.default.stringify(block.content));
|
295
|
-
for (const screenFile of screenFiles) {
|
296
|
-
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
297
|
-
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
298
|
-
}
|
299
|
-
const screenFilesConverted = screenFiles.map((screenFile) => {
|
300
|
-
return {
|
301
|
-
filename: screenFile.payload.filename,
|
302
|
-
content: screenFile.payload.content,
|
303
|
-
mode: codegen_1.MODE_CREATE_ONLY,
|
304
|
-
permissions: '0644',
|
305
|
-
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
306
|
-
};
|
307
|
-
});
|
308
|
-
allFiles.push(...screenFilesConverted);
|
309
372
|
const blockRef = block.uri;
|
310
373
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
311
374
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -466,7 +529,7 @@ class StormCodegen {
|
|
466
529
|
const files = new Set(filesForContext);
|
467
530
|
files.add(filename);
|
468
531
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
469
|
-
if (
|
532
|
+
if ((0, fs_1.existsSync)(file)) {
|
470
533
|
return file;
|
471
534
|
}
|
472
535
|
// file does not exist - look for similar
|
@@ -551,7 +614,7 @@ class StormCodegen {
|
|
551
614
|
// They will need to be implemented by the AI
|
552
615
|
return;
|
553
616
|
}
|
554
|
-
if (
|
617
|
+
if ([codegen_1.AIFileTypes.WEB_ROUTER, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
555
618
|
// Don't send the web screen files to the stream yet
|
556
619
|
// They will need to be implemented by the AI
|
557
620
|
return;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
|
1
|
+
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
2
|
export declare const STORM_ID = "storm";
|
3
3
|
export declare const ConversationIdHeader = "Conversation-Id";
|
4
4
|
declare class StormClient {
|
@@ -7,11 +7,13 @@ declare class StormClient {
|
|
7
7
|
private createOptions;
|
8
8
|
private send;
|
9
9
|
createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
|
10
|
+
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
10
11
|
createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
11
12
|
createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
12
13
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
13
14
|
createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
14
15
|
createErrorDetails(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
|
+
generateCode(prompt: StormFileImplementationPrompt, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
15
17
|
}
|
16
18
|
export declare const stormClient: StormClient;
|
17
19
|
export {};
|
@@ -80,6 +80,12 @@ class StormClient {
|
|
80
80
|
conversationId,
|
81
81
|
});
|
82
82
|
}
|
83
|
+
listScreens(prompt, conversationId) {
|
84
|
+
return this.send('/v2/ui/list', {
|
85
|
+
prompt,
|
86
|
+
conversationId,
|
87
|
+
});
|
88
|
+
}
|
83
89
|
createUIImplementation(prompt, conversationId) {
|
84
90
|
return this.send('/v2/ui/merge', {
|
85
91
|
prompt,
|
@@ -110,5 +116,11 @@ class StormClient {
|
|
110
116
|
prompt,
|
111
117
|
});
|
112
118
|
}
|
119
|
+
generateCode(prompt, history, conversationId) {
|
120
|
+
return this.send('/v2/code/generate', {
|
121
|
+
conversationId: conversationId,
|
122
|
+
prompt,
|
123
|
+
});
|
124
|
+
}
|
113
125
|
}
|
114
126
|
exports.stormClient = new StormClient();
|
@@ -50,6 +50,14 @@ export interface StormFileImplementationPrompt {
|
|
50
50
|
prompt: string;
|
51
51
|
}
|
52
52
|
export interface StormUIImplementationPrompt {
|
53
|
+
events: StormEvent[];
|
54
|
+
template: StormFileInfo;
|
55
|
+
filename: string;
|
56
|
+
context: StormFileInfo[];
|
57
|
+
blockName: string;
|
58
|
+
prompt: string;
|
59
|
+
}
|
60
|
+
export interface StormUIListPrompt {
|
53
61
|
events: StormEvent[];
|
54
62
|
templates: StormFileInfo[];
|
55
63
|
context: StormFileInfo[];
|
@@ -44,7 +44,6 @@ const path_2 = __importStar(require("path"));
|
|
44
44
|
const node_os_1 = __importDefault(require("node:os"));
|
45
45
|
const fs_1 = require("fs");
|
46
46
|
const yaml_1 = __importDefault(require("yaml"));
|
47
|
-
const fs = __importStar(require("node:fs"));
|
48
47
|
const SIMULATED_DELAY = 1000;
|
49
48
|
const ENABLE_SIMULATED_DELAY = false;
|
50
49
|
class SimulatedFileDelay {
|
@@ -239,7 +238,9 @@ class StormCodegen {
|
|
239
238
|
return;
|
240
239
|
}
|
241
240
|
const blockUri = (0, nodejs_utils_1.parseKapetaUri)(block.uri);
|
242
|
-
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
241
|
+
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE &&
|
242
|
+
file.type !== codegen_1.AIFileTypes.WEB_SCREEN &&
|
243
|
+
file.type !== codegen_1.AIFileTypes.WEB_ROUTER);
|
243
244
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
244
245
|
const screenFiles = [];
|
245
246
|
let filteredEvents = [];
|
@@ -249,39 +250,110 @@ class StormCodegen {
|
|
249
250
|
filteredEvents = [];
|
250
251
|
}
|
251
252
|
}
|
252
|
-
|
253
|
-
|
253
|
+
const screenEvents = [];
|
254
|
+
const getScreenEventsFile = () => ({
|
255
|
+
content: JSON.stringify(screenEvents),
|
256
|
+
filename: '<screens>.json',
|
257
|
+
type: codegen_1.AIFileTypes.CONFIG,
|
258
|
+
mode: codegen_1.MODE_WRITE_NEVER,
|
259
|
+
permissions: '0644',
|
260
|
+
});
|
261
|
+
const uiEvents = [];
|
262
|
+
// generate screens
|
263
|
+
if (uiTemplates.length) {
|
264
|
+
const screenStream = await stormClient_1.stormClient.listScreens({
|
254
265
|
events: filteredEvents,
|
255
266
|
templates: uiTemplates,
|
256
267
|
context: relevantFiles,
|
257
268
|
blockName: block.aiName,
|
258
269
|
prompt: this.userPrompt,
|
259
270
|
});
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
screenFiles.push(uiFile);
|
271
|
+
screenStream.on('data', (evt) => {
|
272
|
+
if (evt.type === 'SCREEN') {
|
273
|
+
screenEvents.push(evt);
|
264
274
|
}
|
275
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
265
276
|
});
|
266
277
|
this.out.on('aborted', () => {
|
267
|
-
|
278
|
+
screenStream.abort();
|
268
279
|
});
|
269
|
-
await
|
280
|
+
await screenStream.waitForDone();
|
281
|
+
// screenfiles
|
282
|
+
const screenTemplates = screenEvents
|
283
|
+
.map((screenEvent) => ({
|
284
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
285
|
+
filename: screenEvent.payload.filename,
|
286
|
+
}))
|
287
|
+
.filter((tpl) => !!tpl.content);
|
288
|
+
await Promise.all(screenTemplates.map(async (template) => {
|
289
|
+
const payload = {
|
290
|
+
events: filteredEvents,
|
291
|
+
blockName: block.aiName,
|
292
|
+
filename: template.filename,
|
293
|
+
template: template,
|
294
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
295
|
+
prompt: this.userPrompt,
|
296
|
+
};
|
297
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
298
|
+
uiStream.on('data', (evt) => {
|
299
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
300
|
+
if (uiFile != undefined) {
|
301
|
+
screenFiles.push(uiFile);
|
302
|
+
}
|
303
|
+
uiEvents.push(evt);
|
304
|
+
});
|
305
|
+
this.out.on('aborted', () => {
|
306
|
+
uiStream.abort();
|
307
|
+
});
|
308
|
+
await uiStream.waitForDone();
|
309
|
+
}));
|
270
310
|
}
|
271
311
|
if (this.isAborted()) {
|
272
312
|
return;
|
273
313
|
}
|
314
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
315
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
316
|
+
return {
|
317
|
+
filename: screenFile.payload.filename,
|
318
|
+
content: screenFile.payload.content,
|
319
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
320
|
+
permissions: '0644',
|
321
|
+
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
322
|
+
};
|
323
|
+
});
|
324
|
+
allFiles.push(...screenFilesConverted);
|
325
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
326
|
+
await Promise.all(webRouters.map(async (webRouter) => {
|
327
|
+
const payload = {
|
328
|
+
filename: webRouter.filename,
|
329
|
+
template: webRouter,
|
330
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
331
|
+
prompt: this.userPrompt,
|
332
|
+
};
|
333
|
+
const stream = await stormClient_1.stormClient.generateCode(payload);
|
334
|
+
stream.on('data', (evt) => {
|
335
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
336
|
+
});
|
337
|
+
this.out.on('aborted', () => {
|
338
|
+
stream.abort();
|
339
|
+
});
|
340
|
+
await stream.waitForDone();
|
341
|
+
}));
|
274
342
|
// Gather the context files for implementation. These will be all be passed to the AI
|
275
|
-
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
343
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
276
344
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
277
345
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
278
346
|
if (serviceFiles.length > 0) {
|
279
347
|
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
280
348
|
}
|
281
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
282
349
|
if (this.isAborted()) {
|
283
350
|
return;
|
284
351
|
}
|
352
|
+
for (const screenFile of screenFilesConverted) {
|
353
|
+
// this.emitFile(blockUri, block.aiName, screenFile.filename, screenFile.content);
|
354
|
+
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
355
|
+
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
356
|
+
}
|
285
357
|
for (const serviceFile of serviceFiles) {
|
286
358
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
287
359
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -290,22 +362,13 @@ class StormCodegen {
|
|
290
362
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
291
363
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
292
364
|
}
|
365
|
+
// Write again after modifications
|
366
|
+
for (const webRouterFile of webRouters) {
|
367
|
+
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
368
|
+
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
369
|
+
}
|
293
370
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
294
371
|
await (0, promises_1.writeFile)(kapetaYmlPath, yaml_1.default.stringify(block.content));
|
295
|
-
for (const screenFile of screenFiles) {
|
296
|
-
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
297
|
-
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
298
|
-
}
|
299
|
-
const screenFilesConverted = screenFiles.map((screenFile) => {
|
300
|
-
return {
|
301
|
-
filename: screenFile.payload.filename,
|
302
|
-
content: screenFile.payload.content,
|
303
|
-
mode: codegen_1.MODE_CREATE_ONLY,
|
304
|
-
permissions: '0644',
|
305
|
-
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
306
|
-
};
|
307
|
-
});
|
308
|
-
allFiles.push(...screenFilesConverted);
|
309
372
|
const blockRef = block.uri;
|
310
373
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
311
374
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -466,7 +529,7 @@ class StormCodegen {
|
|
466
529
|
const files = new Set(filesForContext);
|
467
530
|
files.add(filename);
|
468
531
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
469
|
-
if (
|
532
|
+
if ((0, fs_1.existsSync)(file)) {
|
470
533
|
return file;
|
471
534
|
}
|
472
535
|
// file does not exist - look for similar
|
@@ -551,7 +614,7 @@ class StormCodegen {
|
|
551
614
|
// They will need to be implemented by the AI
|
552
615
|
return;
|
553
616
|
}
|
554
|
-
if (
|
617
|
+
if ([codegen_1.AIFileTypes.WEB_ROUTER, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
555
618
|
// Don't send the web screen files to the stream yet
|
556
619
|
// They will need to be implemented by the AI
|
557
620
|
return;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
|
1
|
+
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
2
|
export declare const STORM_ID = "storm";
|
3
3
|
export declare const ConversationIdHeader = "Conversation-Id";
|
4
4
|
declare class StormClient {
|
@@ -7,11 +7,13 @@ declare class StormClient {
|
|
7
7
|
private createOptions;
|
8
8
|
private send;
|
9
9
|
createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
|
10
|
+
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
10
11
|
createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
11
12
|
createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
12
13
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
13
14
|
createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
14
15
|
createErrorDetails(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
16
|
+
generateCode(prompt: StormFileImplementationPrompt, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
15
17
|
}
|
16
18
|
export declare const stormClient: StormClient;
|
17
19
|
export {};
|
@@ -80,6 +80,12 @@ class StormClient {
|
|
80
80
|
conversationId,
|
81
81
|
});
|
82
82
|
}
|
83
|
+
listScreens(prompt, conversationId) {
|
84
|
+
return this.send('/v2/ui/list', {
|
85
|
+
prompt,
|
86
|
+
conversationId,
|
87
|
+
});
|
88
|
+
}
|
83
89
|
createUIImplementation(prompt, conversationId) {
|
84
90
|
return this.send('/v2/ui/merge', {
|
85
91
|
prompt,
|
@@ -110,5 +116,11 @@ class StormClient {
|
|
110
116
|
prompt,
|
111
117
|
});
|
112
118
|
}
|
119
|
+
generateCode(prompt, history, conversationId) {
|
120
|
+
return this.send('/v2/code/generate', {
|
121
|
+
conversationId: conversationId,
|
122
|
+
prompt,
|
123
|
+
});
|
124
|
+
}
|
113
125
|
}
|
114
126
|
exports.stormClient = new StormClient();
|
@@ -50,6 +50,14 @@ export interface StormFileImplementationPrompt {
|
|
50
50
|
prompt: string;
|
51
51
|
}
|
52
52
|
export interface StormUIImplementationPrompt {
|
53
|
+
events: StormEvent[];
|
54
|
+
template: StormFileInfo;
|
55
|
+
filename: string;
|
56
|
+
context: StormFileInfo[];
|
57
|
+
blockName: string;
|
58
|
+
prompt: string;
|
59
|
+
}
|
60
|
+
export interface StormUIListPrompt {
|
53
61
|
events: StormEvent[];
|
54
62
|
templates: StormFileInfo[];
|
55
63
|
context: StormFileInfo[];
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.54.1",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -50,7 +50,7 @@
|
|
50
50
|
},
|
51
51
|
"homepage": "https://github.com/kapetacom/local-cluster-service#readme",
|
52
52
|
"dependencies": {
|
53
|
-
"@kapeta/codegen": "^1.6.
|
53
|
+
"@kapeta/codegen": "^1.6.1",
|
54
54
|
"@kapeta/config-mapper": "^1.2.2",
|
55
55
|
"@kapeta/kaplang-core": "^1.17.2",
|
56
56
|
"@kapeta/local-cluster-config": "^0.4.2",
|
package/src/storm/codegen.ts
CHANGED
@@ -12,7 +12,9 @@ import {
|
|
12
12
|
GeneratedFile,
|
13
13
|
GeneratedResult,
|
14
14
|
MODE_CREATE_ONLY,
|
15
|
+
MODE_WRITE_NEVER,
|
15
16
|
} from '@kapeta/codegen';
|
17
|
+
|
16
18
|
import { BlockDefinition } from '@kapeta/schemas';
|
17
19
|
import { codeGeneratorManager } from '../codeGeneratorManager';
|
18
20
|
import { STORM_ID, stormClient } from './stormClient';
|
@@ -23,6 +25,7 @@ import {
|
|
23
25
|
StormEventFileChunk,
|
24
26
|
StormEventFileDone,
|
25
27
|
StormEventFileLogical,
|
28
|
+
StormEventScreen,
|
26
29
|
} from './events';
|
27
30
|
import { BlockDefinitionInfo, StormEventParser } from './event-parser';
|
28
31
|
import { ConversationItem, StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
|
@@ -31,11 +34,14 @@ import { writeFile } from 'fs/promises';
|
|
31
34
|
import path from 'path';
|
32
35
|
import Path, { join } from 'path';
|
33
36
|
import os from 'node:os';
|
34
|
-
import { readFileSync, writeFileSync } from 'fs';
|
37
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
35
38
|
import YAML from 'yaml';
|
36
|
-
import
|
39
|
+
import assert from 'assert';
|
37
40
|
|
38
|
-
type ImplementationGenerator =
|
41
|
+
type ImplementationGenerator<T = StormFileImplementationPrompt> = (
|
42
|
+
prompt: T,
|
43
|
+
conversationId?: string
|
44
|
+
) => Promise<StormStream>;
|
39
45
|
|
40
46
|
interface ErrorClassification {
|
41
47
|
error: string;
|
@@ -266,9 +272,12 @@ export class StormCodegen {
|
|
266
272
|
const blockUri = parseKapetaUri(block.uri);
|
267
273
|
|
268
274
|
const relevantFiles: StormFileInfo[] = allFiles.filter(
|
269
|
-
(file) =>
|
275
|
+
(file) =>
|
276
|
+
file.type !== AIFileTypes.IGNORE &&
|
277
|
+
file.type !== AIFileTypes.WEB_SCREEN &&
|
278
|
+
file.type !== AIFileTypes.WEB_ROUTER
|
270
279
|
);
|
271
|
-
const uiTemplates
|
280
|
+
const uiTemplates = allFiles.filter((file) => file.type === AIFileTypes.WEB_SCREEN);
|
272
281
|
const screenFiles: StormEventFileDone[] = [];
|
273
282
|
let filteredEvents = [] as StormEvent[];
|
274
283
|
for (const event of this.events) {
|
@@ -277,36 +286,120 @@ export class StormCodegen {
|
|
277
286
|
filteredEvents = [];
|
278
287
|
}
|
279
288
|
}
|
280
|
-
|
281
|
-
|
289
|
+
|
290
|
+
const screenEvents: StormEventScreen[] = [];
|
291
|
+
const getScreenEventsFile = () => ({
|
292
|
+
content: JSON.stringify(screenEvents),
|
293
|
+
filename: '<screens>.json',
|
294
|
+
type: AIFileTypes.CONFIG,
|
295
|
+
mode: MODE_WRITE_NEVER,
|
296
|
+
permissions: '0644',
|
297
|
+
});
|
298
|
+
const uiEvents = [];
|
299
|
+
|
300
|
+
// generate screens
|
301
|
+
if (uiTemplates.length) {
|
302
|
+
const screenStream = await stormClient.listScreens({
|
282
303
|
events: filteredEvents,
|
283
304
|
templates: uiTemplates,
|
284
305
|
context: relevantFiles,
|
285
306
|
blockName: block.aiName,
|
286
307
|
prompt: this.userPrompt,
|
287
308
|
});
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
if (uiFile != undefined) {
|
292
|
-
screenFiles.push(uiFile);
|
309
|
+
screenStream.on('data', (evt) => {
|
310
|
+
if (evt.type === 'SCREEN') {
|
311
|
+
screenEvents.push(evt);
|
293
312
|
}
|
313
|
+
this.handleUiOutput(blockUri, block.aiName, evt);
|
294
314
|
});
|
295
315
|
|
296
316
|
this.out.on('aborted', () => {
|
297
|
-
|
317
|
+
screenStream.abort();
|
298
318
|
});
|
299
319
|
|
300
|
-
await
|
320
|
+
await screenStream.waitForDone();
|
321
|
+
|
322
|
+
// screenfiles
|
323
|
+
const screenTemplates = screenEvents
|
324
|
+
.map((screenEvent) => ({
|
325
|
+
...uiTemplates.find((template) => template.filename.endsWith(screenEvent.payload.template)),
|
326
|
+
filename: screenEvent.payload.filename,
|
327
|
+
}))
|
328
|
+
.filter((tpl): tpl is StormFileInfo => !!tpl.content);
|
329
|
+
|
330
|
+
await Promise.all(
|
331
|
+
screenTemplates.map(async (template) => {
|
332
|
+
const payload = {
|
333
|
+
events: filteredEvents,
|
334
|
+
blockName: block.aiName,
|
335
|
+
filename: template.filename,
|
336
|
+
template: template,
|
337
|
+
context: relevantFiles.concat([getScreenEventsFile()]),
|
338
|
+
prompt: this.userPrompt,
|
339
|
+
};
|
340
|
+
|
341
|
+
const uiStream = await stormClient.createUIImplementation(payload);
|
342
|
+
|
343
|
+
uiStream.on('data', (evt) => {
|
344
|
+
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
345
|
+
if (uiFile != undefined) {
|
346
|
+
screenFiles.push(uiFile);
|
347
|
+
}
|
348
|
+
uiEvents.push(evt);
|
349
|
+
});
|
350
|
+
|
351
|
+
this.out.on('aborted', () => {
|
352
|
+
uiStream.abort();
|
353
|
+
});
|
354
|
+
|
355
|
+
await uiStream.waitForDone();
|
356
|
+
})
|
357
|
+
);
|
301
358
|
}
|
302
359
|
|
303
360
|
if (this.isAborted()) {
|
304
361
|
return;
|
305
362
|
}
|
363
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
364
|
+
|
365
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
366
|
+
return {
|
367
|
+
filename: screenFile.payload.filename,
|
368
|
+
content: screenFile.payload.content,
|
369
|
+
mode: MODE_CREATE_ONLY,
|
370
|
+
permissions: '0644',
|
371
|
+
type: AIFileTypes.WEB_SCREEN,
|
372
|
+
};
|
373
|
+
});
|
374
|
+
allFiles.push(...screenFilesConverted);
|
375
|
+
|
376
|
+
const webRouters = allFiles.filter((file) => file.type === AIFileTypes.WEB_ROUTER);
|
377
|
+
await Promise.all(
|
378
|
+
webRouters.map(async (webRouter) => {
|
379
|
+
const payload = {
|
380
|
+
filename: webRouter.filename,
|
381
|
+
template: webRouter,
|
382
|
+
context: screenFilesConverted.concat([getScreenEventsFile()]),
|
383
|
+
prompt: this.userPrompt,
|
384
|
+
};
|
385
|
+
|
386
|
+
const stream = await stormClient.generateCode(payload);
|
387
|
+
|
388
|
+
stream.on('data', (evt) => {
|
389
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, webRouter, evt);
|
390
|
+
});
|
391
|
+
|
392
|
+
this.out.on('aborted', () => {
|
393
|
+
stream.abort();
|
394
|
+
});
|
395
|
+
|
396
|
+
await stream.waitForDone();
|
397
|
+
})
|
398
|
+
);
|
306
399
|
|
307
400
|
// Gather the context files for implementation. These will be all be passed to the AI
|
308
401
|
const contextFiles: StormFileInfo[] = relevantFiles.filter(
|
309
|
-
(file) => ![AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN].includes(file.type)
|
402
|
+
(file) => ![AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN, AIFileTypes.WEB_ROUTER].includes(file.type)
|
310
403
|
);
|
311
404
|
|
312
405
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
@@ -321,12 +414,16 @@ export class StormCodegen {
|
|
321
414
|
);
|
322
415
|
}
|
323
416
|
|
324
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
325
|
-
|
326
417
|
if (this.isAborted()) {
|
327
418
|
return;
|
328
419
|
}
|
329
420
|
|
421
|
+
for (const screenFile of screenFilesConverted) {
|
422
|
+
// this.emitFile(blockUri, block.aiName, screenFile.filename, screenFile.content);
|
423
|
+
const filePath = join(basePath, screenFile.filename);
|
424
|
+
await writeFile(filePath, screenFile.content);
|
425
|
+
}
|
426
|
+
|
330
427
|
for (const serviceFile of serviceFiles) {
|
331
428
|
const filePath = join(basePath, serviceFile.filename);
|
332
429
|
await writeFile(filePath, serviceFile.content);
|
@@ -337,30 +434,21 @@ export class StormCodegen {
|
|
337
434
|
await writeFile(filePath, serviceFile.content);
|
338
435
|
}
|
339
436
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
const filePath = join(basePath, screenFile.payload.filename);
|
345
|
-
await writeFile(filePath, screenFile.payload.content);
|
437
|
+
// Write again after modifications
|
438
|
+
for (const webRouterFile of webRouters) {
|
439
|
+
const filePath = join(basePath, webRouterFile.filename);
|
440
|
+
await writeFile(filePath, webRouterFile.content);
|
346
441
|
}
|
347
442
|
|
348
|
-
const
|
349
|
-
|
350
|
-
|
351
|
-
content: screenFile.payload.content,
|
352
|
-
mode: MODE_CREATE_ONLY,
|
353
|
-
permissions: '0644',
|
354
|
-
type: AIFileTypes.WEB_SCREEN,
|
355
|
-
};
|
356
|
-
});
|
357
|
-
allFiles.push(...screenFilesConverted);
|
443
|
+
const kapetaYmlPath = join(basePath, 'kapeta.yml');
|
444
|
+
await writeFile(kapetaYmlPath, YAML.stringify(block.content));
|
445
|
+
|
358
446
|
const blockRef = block.uri;
|
359
447
|
|
360
448
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
|
361
449
|
|
362
450
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
363
|
-
const codeGenerator = new BlockCodeGenerator(block.content
|
451
|
+
const codeGenerator = new BlockCodeGenerator(block.content);
|
364
452
|
|
365
453
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.BUILDING);
|
366
454
|
await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
@@ -594,7 +682,7 @@ export class StormCodegen {
|
|
594
682
|
files.add(filename);
|
595
683
|
|
596
684
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
597
|
-
if (
|
685
|
+
if (existsSync(file)) {
|
598
686
|
return file;
|
599
687
|
}
|
600
688
|
|
@@ -696,7 +784,7 @@ export class StormCodegen {
|
|
696
784
|
return;
|
697
785
|
}
|
698
786
|
|
699
|
-
if (
|
787
|
+
if ([AIFileTypes.WEB_ROUTER, AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
700
788
|
// Don't send the web screen files to the stream yet
|
701
789
|
// They will need to be implemented by the AI
|
702
790
|
return;
|
@@ -819,6 +907,7 @@ export class StormCodegen {
|
|
819
907
|
type,
|
820
908
|
};
|
821
909
|
});
|
910
|
+
|
822
911
|
return allFiles;
|
823
912
|
}
|
824
913
|
|
package/src/storm/events.ts
CHANGED
package/src/storm/stormClient.ts
CHANGED
@@ -12,6 +12,7 @@ import {
|
|
12
12
|
StormFileImplementationPrompt,
|
13
13
|
StormStream,
|
14
14
|
StormUIImplementationPrompt,
|
15
|
+
StormUIListPrompt,
|
15
16
|
} from './stream';
|
16
17
|
import { getRawAsset } from 'node:sea';
|
17
18
|
|
@@ -112,8 +113,15 @@ class StormClient {
|
|
112
113
|
});
|
113
114
|
}
|
114
115
|
|
116
|
+
public listScreens(prompt: StormUIListPrompt, conversationId?: string) {
|
117
|
+
return this.send('/v2/ui/list', {
|
118
|
+
prompt,
|
119
|
+
conversationId,
|
120
|
+
});
|
121
|
+
}
|
122
|
+
|
115
123
|
public createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string) {
|
116
|
-
return this.send
|
124
|
+
return this.send('/v2/ui/merge', {
|
117
125
|
prompt,
|
118
126
|
conversationId,
|
119
127
|
});
|
@@ -146,6 +154,13 @@ class StormClient {
|
|
146
154
|
prompt,
|
147
155
|
});
|
148
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
|
+
}
|
149
164
|
}
|
150
165
|
|
151
166
|
export const stormClient = new StormClient();
|
package/src/storm/stream.ts
CHANGED
@@ -117,6 +117,15 @@ export interface StormFileImplementationPrompt {
|
|
117
117
|
}
|
118
118
|
|
119
119
|
export interface StormUIImplementationPrompt {
|
120
|
+
events: StormEvent[];
|
121
|
+
template: StormFileInfo;
|
122
|
+
filename: string;
|
123
|
+
context: StormFileInfo[];
|
124
|
+
blockName: string;
|
125
|
+
prompt: string;
|
126
|
+
}
|
127
|
+
|
128
|
+
export interface StormUIListPrompt {
|
120
129
|
events: StormEvent[];
|
121
130
|
templates: StormFileInfo[];
|
122
131
|
context: StormFileInfo[];
|