@kapeta/local-cluster-service 0.53.5 → 0.54.0
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 +7 -0
- package/dist/cjs/src/storm/codegen.js +68 -22
- package/dist/cjs/src/storm/events.d.ts +1 -0
- package/dist/cjs/src/storm/stormClient.d.ts +2 -1
- package/dist/cjs/src/storm/stormClient.js +6 -0
- package/dist/cjs/src/storm/stream.d.ts +8 -0
- package/dist/esm/src/storm/codegen.js +68 -22
- package/dist/esm/src/storm/events.d.ts +1 -0
- package/dist/esm/src/storm/stormClient.d.ts +2 -1
- package/dist/esm/src/storm/stormClient.js +6 -0
- package/dist/esm/src/storm/stream.d.ts +8 -0
- package/package.json +2 -2
- package/src/storm/codegen.ts +101 -39
- package/src/storm/events.ts +1 -0
- package/src/storm/stormClient.ts +9 -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,10 @@
|
|
1
|
+
# [0.54.0](https://github.com/kapetacom/local-cluster-service/compare/v0.53.5...v0.54.0) (2024-06-17)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* 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))
|
7
|
+
|
1
8
|
## [0.53.5](https://github.com/kapetacom/local-cluster-service/compare/v0.53.4...v0.53.5) (2024-06-17)
|
2
9
|
|
3
10
|
|
@@ -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,8 +238,11 @@ 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);
|
245
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
244
246
|
const screenFiles = [];
|
245
247
|
let filteredEvents = [];
|
246
248
|
for (const event of this.events) {
|
@@ -249,14 +251,55 @@ class StormCodegen {
|
|
249
251
|
filteredEvents = [];
|
250
252
|
}
|
251
253
|
}
|
252
|
-
|
253
|
-
|
254
|
+
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,
|
262
|
+
});
|
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 = {
|
254
282
|
events: filteredEvents,
|
255
|
-
templates: uiTemplates,
|
256
|
-
context: relevantFiles,
|
257
283
|
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
|
+
]),
|
258
300
|
prompt: this.userPrompt,
|
259
|
-
}
|
301
|
+
};
|
302
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
260
303
|
uiStream.on('data', (evt) => {
|
261
304
|
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
262
305
|
if (uiFile != undefined) {
|
@@ -267,21 +310,34 @@ class StormCodegen {
|
|
267
310
|
uiStream.abort();
|
268
311
|
});
|
269
312
|
await uiStream.waitForDone();
|
270
|
-
}
|
313
|
+
}));
|
271
314
|
if (this.isAborted()) {
|
272
315
|
return;
|
273
316
|
}
|
317
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
318
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
319
|
+
return {
|
320
|
+
filename: screenFile.payload.filename,
|
321
|
+
content: screenFile.payload.content,
|
322
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
323
|
+
permissions: '0644',
|
324
|
+
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
325
|
+
};
|
326
|
+
});
|
274
327
|
// 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));
|
328
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
276
329
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
277
330
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
278
331
|
if (serviceFiles.length > 0) {
|
279
332
|
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
280
333
|
}
|
281
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
282
334
|
if (this.isAborted()) {
|
283
335
|
return;
|
284
336
|
}
|
337
|
+
for (const screenFile of screenFilesConverted) {
|
338
|
+
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
339
|
+
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
340
|
+
}
|
285
341
|
for (const serviceFile of serviceFiles) {
|
286
342
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
287
343
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -296,16 +352,6 @@ class StormCodegen {
|
|
296
352
|
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
297
353
|
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
298
354
|
}
|
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
355
|
const blockRef = block.uri;
|
310
356
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
311
357
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -466,7 +512,7 @@ class StormCodegen {
|
|
466
512
|
const files = new Set(filesForContext);
|
467
513
|
files.add(filename);
|
468
514
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
469
|
-
if (
|
515
|
+
if ((0, fs_1.existsSync)(file)) {
|
470
516
|
return file;
|
471
517
|
}
|
472
518
|
// file does not exist - look for similar
|
@@ -551,7 +597,7 @@ class StormCodegen {
|
|
551
597
|
// They will need to be implemented by the AI
|
552
598
|
return;
|
553
599
|
}
|
554
|
-
if (
|
600
|
+
if ([codegen_1.AIFileTypes.WEB_ROUTER, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
555
601
|
// Don't send the web screen files to the stream yet
|
556
602
|
// They will need to be implemented by the AI
|
557
603
|
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,6 +7,7 @@ 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>;
|
@@ -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,
|
@@ -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,8 +238,11 @@ 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);
|
245
|
+
const webRouters = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_ROUTER);
|
244
246
|
const screenFiles = [];
|
245
247
|
let filteredEvents = [];
|
246
248
|
for (const event of this.events) {
|
@@ -249,14 +251,55 @@ class StormCodegen {
|
|
249
251
|
filteredEvents = [];
|
250
252
|
}
|
251
253
|
}
|
252
|
-
|
253
|
-
|
254
|
+
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,
|
262
|
+
});
|
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 = {
|
254
282
|
events: filteredEvents,
|
255
|
-
templates: uiTemplates,
|
256
|
-
context: relevantFiles,
|
257
283
|
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
|
+
]),
|
258
300
|
prompt: this.userPrompt,
|
259
|
-
}
|
301
|
+
};
|
302
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation(payload);
|
260
303
|
uiStream.on('data', (evt) => {
|
261
304
|
const uiFile = this.handleUiOutput(blockUri, block.aiName, evt);
|
262
305
|
if (uiFile != undefined) {
|
@@ -267,21 +310,34 @@ class StormCodegen {
|
|
267
310
|
uiStream.abort();
|
268
311
|
});
|
269
312
|
await uiStream.waitForDone();
|
270
|
-
}
|
313
|
+
}));
|
271
314
|
if (this.isAborted()) {
|
272
315
|
return;
|
273
316
|
}
|
317
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
318
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
319
|
+
return {
|
320
|
+
filename: screenFile.payload.filename,
|
321
|
+
content: screenFile.payload.content,
|
322
|
+
mode: codegen_1.MODE_CREATE_ONLY,
|
323
|
+
permissions: '0644',
|
324
|
+
type: codegen_1.AIFileTypes.WEB_SCREEN,
|
325
|
+
};
|
326
|
+
});
|
274
327
|
// 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));
|
328
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
276
329
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
277
330
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
278
331
|
if (serviceFiles.length > 0) {
|
279
332
|
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
280
333
|
}
|
281
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
282
334
|
if (this.isAborted()) {
|
283
335
|
return;
|
284
336
|
}
|
337
|
+
for (const screenFile of screenFilesConverted) {
|
338
|
+
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
339
|
+
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
340
|
+
}
|
285
341
|
for (const serviceFile of serviceFiles) {
|
286
342
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
287
343
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
@@ -296,16 +352,6 @@ class StormCodegen {
|
|
296
352
|
const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
|
297
353
|
await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
|
298
354
|
}
|
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
355
|
const blockRef = block.uri;
|
310
356
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
311
357
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
@@ -466,7 +512,7 @@ class StormCodegen {
|
|
466
512
|
const files = new Set(filesForContext);
|
467
513
|
files.add(filename);
|
468
514
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
469
|
-
if (
|
515
|
+
if ((0, fs_1.existsSync)(file)) {
|
470
516
|
return file;
|
471
517
|
}
|
472
518
|
// file does not exist - look for similar
|
@@ -551,7 +597,7 @@ class StormCodegen {
|
|
551
597
|
// They will need to be implemented by the AI
|
552
598
|
return;
|
553
599
|
}
|
554
|
-
if (
|
600
|
+
if ([codegen_1.AIFileTypes.WEB_ROUTER, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
555
601
|
// Don't send the web screen files to the stream yet
|
556
602
|
// They will need to be implemented by the AI
|
557
603
|
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,6 +7,7 @@ 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>;
|
@@ -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,
|
@@ -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.0",
|
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
@@ -13,6 +13,7 @@ import {
|
|
13
13
|
GeneratedResult,
|
14
14
|
MODE_CREATE_ONLY,
|
15
15
|
} from '@kapeta/codegen';
|
16
|
+
|
16
17
|
import { BlockDefinition } from '@kapeta/schemas';
|
17
18
|
import { codeGeneratorManager } from '../codeGeneratorManager';
|
18
19
|
import { STORM_ID, stormClient } from './stormClient';
|
@@ -23,6 +24,7 @@ import {
|
|
23
24
|
StormEventFileChunk,
|
24
25
|
StormEventFileDone,
|
25
26
|
StormEventFileLogical,
|
27
|
+
StormEventScreen,
|
26
28
|
} from './events';
|
27
29
|
import { BlockDefinitionInfo, StormEventParser } from './event-parser';
|
28
30
|
import { ConversationItem, StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
|
@@ -31,11 +33,14 @@ import { writeFile } from 'fs/promises';
|
|
31
33
|
import path from 'path';
|
32
34
|
import Path, { join } from 'path';
|
33
35
|
import os from 'node:os';
|
34
|
-
import { readFileSync, writeFileSync } from 'fs';
|
36
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
35
37
|
import YAML from 'yaml';
|
36
|
-
import
|
38
|
+
import assert from 'assert';
|
37
39
|
|
38
|
-
type ImplementationGenerator =
|
40
|
+
type ImplementationGenerator<T = StormFileImplementationPrompt> = (
|
41
|
+
prompt: T,
|
42
|
+
conversationId?: string
|
43
|
+
) => Promise<StormStream>;
|
39
44
|
|
40
45
|
interface ErrorClassification {
|
41
46
|
error: string;
|
@@ -266,9 +271,13 @@ export class StormCodegen {
|
|
266
271
|
const blockUri = parseKapetaUri(block.uri);
|
267
272
|
|
268
273
|
const relevantFiles: StormFileInfo[] = allFiles.filter(
|
269
|
-
(file) =>
|
274
|
+
(file) =>
|
275
|
+
file.type !== AIFileTypes.IGNORE &&
|
276
|
+
file.type !== AIFileTypes.WEB_SCREEN &&
|
277
|
+
file.type !== AIFileTypes.WEB_ROUTER
|
270
278
|
);
|
271
|
-
const uiTemplates
|
279
|
+
const uiTemplates = allFiles.filter((file) => file.type === AIFileTypes.WEB_SCREEN);
|
280
|
+
const webRouters = allFiles.filter((file) => file.type === AIFileTypes.WEB_ROUTER);
|
272
281
|
const screenFiles: StormEventFileDone[] = [];
|
273
282
|
let filteredEvents = [] as StormEvent[];
|
274
283
|
for (const event of this.events) {
|
@@ -277,36 +286,96 @@ export class StormCodegen {
|
|
277
286
|
filteredEvents = [];
|
278
287
|
}
|
279
288
|
}
|
280
|
-
if (uiTemplates.length > 0) {
|
281
|
-
const uiStream = await stormClient.createUIImplementation({
|
282
|
-
events: filteredEvents,
|
283
|
-
templates: uiTemplates,
|
284
|
-
context: relevantFiles,
|
285
|
-
blockName: block.aiName,
|
286
|
-
prompt: this.userPrompt,
|
287
|
-
});
|
288
289
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
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
|
+
});
|
295
305
|
|
296
|
-
|
297
|
-
|
298
|
-
|
306
|
+
this.out.on('aborted', () => {
|
307
|
+
screenStream.abort();
|
308
|
+
});
|
299
309
|
|
300
|
-
|
301
|
-
|
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
|
+
});
|
352
|
+
|
353
|
+
this.out.on('aborted', () => {
|
354
|
+
uiStream.abort();
|
355
|
+
});
|
356
|
+
|
357
|
+
await uiStream.waitForDone();
|
358
|
+
})
|
359
|
+
);
|
302
360
|
|
303
361
|
if (this.isAborted()) {
|
304
362
|
return;
|
305
363
|
}
|
364
|
+
const basePath = this.getBasePath(block.content.metadata.name);
|
365
|
+
|
366
|
+
const screenFilesConverted = screenFiles.map((screenFile) => {
|
367
|
+
return {
|
368
|
+
filename: screenFile.payload.filename,
|
369
|
+
content: screenFile.payload.content,
|
370
|
+
mode: MODE_CREATE_ONLY,
|
371
|
+
permissions: '0644',
|
372
|
+
type: AIFileTypes.WEB_SCREEN,
|
373
|
+
};
|
374
|
+
});
|
306
375
|
|
307
376
|
// Gather the context files for implementation. These will be all be passed to the AI
|
308
377
|
const contextFiles: StormFileInfo[] = relevantFiles.filter(
|
309
|
-
(file) => ![AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN].includes(file.type)
|
378
|
+
(file) => ![AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN, AIFileTypes.WEB_ROUTER].includes(file.type)
|
310
379
|
);
|
311
380
|
|
312
381
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
@@ -321,12 +390,15 @@ export class StormCodegen {
|
|
321
390
|
);
|
322
391
|
}
|
323
392
|
|
324
|
-
const basePath = this.getBasePath(block.content.metadata.name);
|
325
|
-
|
326
393
|
if (this.isAborted()) {
|
327
394
|
return;
|
328
395
|
}
|
329
396
|
|
397
|
+
for (const screenFile of screenFilesConverted) {
|
398
|
+
const filePath = join(basePath, screenFile.filename);
|
399
|
+
await writeFile(filePath, screenFile.content);
|
400
|
+
}
|
401
|
+
|
330
402
|
for (const serviceFile of serviceFiles) {
|
331
403
|
const filePath = join(basePath, serviceFile.filename);
|
332
404
|
await writeFile(filePath, serviceFile.content);
|
@@ -345,16 +417,6 @@ export class StormCodegen {
|
|
345
417
|
await writeFile(filePath, screenFile.payload.content);
|
346
418
|
}
|
347
419
|
|
348
|
-
const screenFilesConverted = screenFiles.map((screenFile) => {
|
349
|
-
return {
|
350
|
-
filename: screenFile.payload.filename,
|
351
|
-
content: screenFile.payload.content,
|
352
|
-
mode: MODE_CREATE_ONLY,
|
353
|
-
permissions: '0644',
|
354
|
-
type: AIFileTypes.WEB_SCREEN,
|
355
|
-
};
|
356
|
-
});
|
357
|
-
allFiles.push(...screenFilesConverted);
|
358
420
|
const blockRef = block.uri;
|
359
421
|
|
360
422
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
|
@@ -594,7 +656,7 @@ export class StormCodegen {
|
|
594
656
|
files.add(filename);
|
595
657
|
|
596
658
|
const requestedFiles = Array.from(files).flatMap((file) => {
|
597
|
-
if (
|
659
|
+
if (existsSync(file)) {
|
598
660
|
return file;
|
599
661
|
}
|
600
662
|
|
@@ -696,7 +758,7 @@ export class StormCodegen {
|
|
696
758
|
return;
|
697
759
|
}
|
698
760
|
|
699
|
-
if (
|
761
|
+
if ([AIFileTypes.WEB_ROUTER, AIFileTypes.WEB_SCREEN].includes(file.type)) {
|
700
762
|
// Don't send the web screen files to the stream yet
|
701
763
|
// They will need to be implemented by the AI
|
702
764
|
return;
|
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
|
});
|
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[];
|