@kapeta/local-cluster-service 0.45.0 → 0.47.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/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.d.ts +5 -1
- package/dist/cjs/src/storm/codegen.js +68 -18
- package/dist/cjs/src/storm/event-parser.d.ts +5 -5
- package/dist/cjs/src/storm/event-parser.js +50 -33
- package/dist/cjs/src/storm/events.d.ts +8 -2
- package/dist/cjs/src/storm/events.js +0 -4
- package/dist/cjs/src/storm/routes.js +20 -29
- package/dist/cjs/src/storm/stormClient.d.ts +2 -2
- package/dist/cjs/src/storm/stormClient.js +0 -7
- package/dist/cjs/src/storm/stream.d.ts +7 -0
- package/dist/cjs/test/storm/event-parser.test.d.ts +5 -0
- package/dist/cjs/test/storm/event-parser.test.js +169 -0
- package/dist/esm/src/storm/codegen.d.ts +5 -1
- package/dist/esm/src/storm/codegen.js +68 -18
- package/dist/esm/src/storm/event-parser.d.ts +5 -5
- package/dist/esm/src/storm/event-parser.js +50 -33
- package/dist/esm/src/storm/events.d.ts +8 -2
- package/dist/esm/src/storm/events.js +0 -4
- package/dist/esm/src/storm/routes.js +20 -29
- package/dist/esm/src/storm/stormClient.d.ts +2 -2
- package/dist/esm/src/storm/stormClient.js +0 -7
- package/dist/esm/src/storm/stream.d.ts +7 -0
- package/dist/esm/test/storm/event-parser.test.d.ts +5 -0
- package/dist/esm/test/storm/event-parser.test.js +169 -0
- package/package.json +3 -1
- package/src/storm/codegen.ts +83 -27
- package/src/storm/event-parser.ts +68 -47
- package/src/storm/events.ts +10 -2
- package/src/storm/routes.ts +42 -59
- package/src/storm/stormClient.ts +3 -11
- package/src/storm/stream.ts +8 -0
- package/test/storm/event-parser.test.ts +190 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.47.0](https://github.com/kapetacom/local-cluster-service/compare/v0.46.0...v0.47.0) (2024-05-29)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Added tests for event parsing and layouting of nodes ([#149](https://github.com/kapetacom/local-cluster-service/issues/149)) ([d36afa9](https://github.com/kapetacom/local-cluster-service/commit/d36afa905d0c66c4562b4eccf07c067efe6b3c9c))
|
7
|
+
|
8
|
+
# [0.46.0](https://github.com/kapetacom/local-cluster-service/compare/v0.45.0...v0.46.0) (2024-05-28)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Change how the UI AI work to get templates from codegen ([#148](https://github.com/kapetacom/local-cluster-service/issues/148)) ([f757ec8](https://github.com/kapetacom/local-cluster-service/commit/f757ec8e4fab0dbb58b96a1b9e25f3e734ddb10f))
|
14
|
+
|
1
15
|
# [0.45.0](https://github.com/kapetacom/local-cluster-service/compare/v0.44.0...v0.45.0) (2024-05-24)
|
2
16
|
|
3
17
|
|
@@ -2,15 +2,19 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
+
import { StormEvent } from './events';
|
5
6
|
import { BlockDefinitionInfo } from './event-parser';
|
6
7
|
import { StormStream } from './stream';
|
7
8
|
export declare class StormCodegen {
|
8
9
|
private readonly userPrompt;
|
9
10
|
private readonly blocks;
|
10
11
|
private readonly out;
|
11
|
-
|
12
|
+
private readonly events;
|
13
|
+
constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
|
12
14
|
process(): Promise<void>;
|
13
15
|
getStream(): StormStream;
|
16
|
+
private handleTemplateFileOutput;
|
17
|
+
private handleUiOutput;
|
14
18
|
private handleFileOutput;
|
15
19
|
/**
|
16
20
|
* Generates the code for a block and sends it to the AI
|
@@ -13,12 +13,13 @@ class StormCodegen {
|
|
13
13
|
userPrompt;
|
14
14
|
blocks;
|
15
15
|
out = new stream_1.StormStream();
|
16
|
-
|
16
|
+
events;
|
17
|
+
constructor(userPrompt, blocks, events) {
|
17
18
|
this.userPrompt = userPrompt;
|
18
19
|
this.blocks = blocks;
|
20
|
+
this.events = events;
|
19
21
|
}
|
20
22
|
async process() {
|
21
|
-
console.log('Processing blocks', this.blocks.length);
|
22
23
|
for (const block of this.blocks) {
|
23
24
|
await this.processBlockCode(block);
|
24
25
|
}
|
@@ -27,13 +28,42 @@ class StormCodegen {
|
|
27
28
|
getStream() {
|
28
29
|
return this.out;
|
29
30
|
}
|
30
|
-
|
31
|
+
handleTemplateFileOutput(blockUri, template, data) {
|
31
32
|
switch (data.type) {
|
32
33
|
case 'FILE':
|
33
34
|
template.filename = data.payload.filename;
|
34
35
|
template.content = data.payload.content;
|
36
|
+
return this.handleFileOutput(blockUri, data);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
handleUiOutput(blockUri, data) {
|
40
|
+
switch (data.type) {
|
41
|
+
case 'SCREEN':
|
42
|
+
this.out.emit('data', {
|
43
|
+
type: 'SCREEN',
|
44
|
+
reason: data.reason,
|
45
|
+
created: Date.now(),
|
46
|
+
payload: {
|
47
|
+
...data.payload,
|
48
|
+
blockName: blockUri.toNormalizedString(),
|
49
|
+
},
|
50
|
+
});
|
51
|
+
case 'FILE':
|
52
|
+
return this.handleFileOutput(blockUri, data);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
handleFileOutput(blockUri, data) {
|
56
|
+
switch (data.type) {
|
57
|
+
case 'FILE':
|
35
58
|
this.emitFile(blockUri, data.payload.filename, data.payload.content, data.reason);
|
36
|
-
|
59
|
+
return {
|
60
|
+
type: 'FILE',
|
61
|
+
created: Date.now(),
|
62
|
+
payload: {
|
63
|
+
filename: data.payload.filename,
|
64
|
+
content: data.payload.content,
|
65
|
+
},
|
66
|
+
};
|
37
67
|
}
|
38
68
|
}
|
39
69
|
/**
|
@@ -41,22 +71,35 @@ class StormCodegen {
|
|
41
71
|
*/
|
42
72
|
async processBlockCode(block) {
|
43
73
|
// Generate the code for the block using the standard codegen templates
|
44
|
-
const generatedResult = await this.generateBlock(block.content
|
74
|
+
const generatedResult = await this.generateBlock(block.content);
|
45
75
|
if (!generatedResult) {
|
46
|
-
console.warn('No generated result for block', block.uri);
|
47
76
|
return;
|
48
77
|
}
|
49
78
|
const allFiles = this.toStormFiles(generatedResult);
|
50
79
|
// Send all the non-ai files to the stream
|
51
80
|
this.emitFiles(block.uri, allFiles);
|
52
|
-
const
|
53
|
-
|
54
|
-
|
81
|
+
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
82
|
+
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
83
|
+
if (uiTemplates.length > 0) {
|
84
|
+
const uiStream = await stormClient_1.stormClient.createUIImplementation({
|
85
|
+
events: this.events,
|
86
|
+
templates: uiTemplates,
|
87
|
+
context: relevantFiles,
|
88
|
+
blockName: block.aiName,
|
89
|
+
prompt: this.userPrompt,
|
90
|
+
});
|
91
|
+
uiStream.on('data', (evt) => {
|
92
|
+
this.handleUiOutput(block.uri, evt);
|
93
|
+
});
|
94
|
+
await uiStream.waitForDone();
|
95
|
+
}
|
96
|
+
// Gather the context files for implementation. These will be all be passed to the AI
|
97
|
+
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
|
55
98
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
56
99
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
57
|
-
|
58
|
-
|
59
|
-
|
100
|
+
if (serviceFiles.length > 0) {
|
101
|
+
await this.processTemplates(block.uri, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
102
|
+
}
|
60
103
|
}
|
61
104
|
/**
|
62
105
|
* Emits the text-based files to the stream
|
@@ -94,17 +137,25 @@ class StormCodegen {
|
|
94
137
|
/**
|
95
138
|
* Sends the template to the AI and processes the response
|
96
139
|
*/
|
97
|
-
processTemplates(blockUri, generator, templates, contextFiles) {
|
140
|
+
async processTemplates(blockUri, generator, templates, contextFiles) {
|
98
141
|
const promises = templates.map(async (templateFile) => {
|
99
142
|
const stream = await generator({
|
100
143
|
context: contextFiles,
|
101
144
|
template: templateFile,
|
102
145
|
prompt: this.userPrompt,
|
103
146
|
});
|
104
|
-
|
105
|
-
|
147
|
+
const files = [];
|
148
|
+
stream.on('data', (evt) => {
|
149
|
+
const file = this.handleTemplateFileOutput(blockUri, templateFile, evt);
|
150
|
+
if (file) {
|
151
|
+
files.push(file);
|
152
|
+
}
|
153
|
+
});
|
154
|
+
await stream.waitForDone();
|
155
|
+
return files;
|
106
156
|
});
|
107
|
-
|
157
|
+
const fileChunks = await Promise.all(promises);
|
158
|
+
return fileChunks.flat();
|
108
159
|
}
|
109
160
|
/**
|
110
161
|
* Converts the generated files to a format that can be sent to the AI
|
@@ -143,7 +194,7 @@ class StormCodegen {
|
|
143
194
|
/**
|
144
195
|
* Generates the code using codegen for a given block.
|
145
196
|
*/
|
146
|
-
async generateBlock(yamlContent
|
197
|
+
async generateBlock(yamlContent) {
|
147
198
|
if (!yamlContent.spec.target?.kind) {
|
148
199
|
//Not all block types have targets
|
149
200
|
return;
|
@@ -153,7 +204,6 @@ class StormCodegen {
|
|
153
204
|
}
|
154
205
|
const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
|
155
206
|
codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
|
156
|
-
codeGenerator.withOption('AIScreens', screens ?? []);
|
157
207
|
return codeGenerator.generate();
|
158
208
|
}
|
159
209
|
}
|
@@ -2,15 +2,15 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {
|
5
|
+
import { StormEvent } from './events';
|
6
6
|
import { BlockDefinition, Plan } from '@kapeta/schemas';
|
7
7
|
import { KapetaURI } from '@kapeta/nodejs-utils';
|
8
8
|
export interface BlockDefinitionInfo {
|
9
9
|
uri: KapetaURI;
|
10
10
|
content: BlockDefinition;
|
11
|
-
|
11
|
+
aiName: string;
|
12
12
|
}
|
13
|
-
export interface
|
13
|
+
export interface StormDefinitions {
|
14
14
|
plan: Plan;
|
15
15
|
blocks: BlockDefinitionInfo[];
|
16
16
|
}
|
@@ -51,12 +51,12 @@ export declare class StormEventParser {
|
|
51
51
|
private options;
|
52
52
|
constructor(options: StormOptions);
|
53
53
|
private reset;
|
54
|
-
addEvent(evt: StormEvent):
|
54
|
+
addEvent(handle: string, evt: StormEvent): StormDefinitions;
|
55
55
|
getEvents(): StormEvent[];
|
56
56
|
isValid(): boolean;
|
57
57
|
getError(): string;
|
58
58
|
private applyLayoutToBlocks;
|
59
|
-
toResult(handle: string):
|
59
|
+
toResult(handle: string): StormDefinitions;
|
60
60
|
private toSafeName;
|
61
61
|
private toRef;
|
62
62
|
toBlockDefinitions(handle: string): {
|
@@ -12,6 +12,8 @@ const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
|
12
12
|
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
13
13
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
14
14
|
const definitionsManager_1 = require("../definitionsManager");
|
15
|
+
const ngraph_graph_1 = __importDefault(require("ngraph.graph"));
|
16
|
+
const ngraph_forcelayout_1 = __importDefault(require("ngraph.forcelayout"));
|
15
17
|
async function resolveOptions() {
|
16
18
|
// Predefined types for now - TODO: Allow user to select / change
|
17
19
|
const blockTypeService = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-service');
|
@@ -87,6 +89,7 @@ async function resolveOptions() {
|
|
87
89
|
};
|
88
90
|
}
|
89
91
|
exports.resolveOptions = resolveOptions;
|
92
|
+
const LAYOUT_MARGIN = 50;
|
90
93
|
class StormEventParser {
|
91
94
|
events = [];
|
92
95
|
planName = '';
|
@@ -105,8 +108,7 @@ class StormEventParser {
|
|
105
108
|
this.blocks = {};
|
106
109
|
this.connections = [];
|
107
110
|
}
|
108
|
-
addEvent(evt) {
|
109
|
-
console.log('Processing storm event', evt);
|
111
|
+
addEvent(handle, evt) {
|
110
112
|
this.events.push(evt);
|
111
113
|
switch (evt.type) {
|
112
114
|
case 'CREATE_PLAN_PROPERTIES':
|
@@ -119,7 +121,6 @@ class StormEventParser {
|
|
119
121
|
apis: [],
|
120
122
|
models: [],
|
121
123
|
types: [],
|
122
|
-
screens: [],
|
123
124
|
};
|
124
125
|
break;
|
125
126
|
case 'PLAN_RETRY':
|
@@ -141,20 +142,12 @@ class StormEventParser {
|
|
141
142
|
case 'CREATE_CONNECTION':
|
142
143
|
this.connections.push(evt.payload);
|
143
144
|
break;
|
144
|
-
case 'SCREEN':
|
145
|
-
this.blocks[evt.payload.blockName].screens.push({
|
146
|
-
name: evt.payload.name,
|
147
|
-
description: evt.payload.description,
|
148
|
-
url: evt.payload.url,
|
149
|
-
template: evt.payload.template,
|
150
|
-
});
|
151
|
-
break;
|
152
145
|
default:
|
153
146
|
case 'SCREEN_CANDIDATE':
|
154
147
|
case 'FILE':
|
155
|
-
console.warn('Unhandled event: %s', evt.type, evt);
|
156
148
|
break;
|
157
149
|
}
|
150
|
+
return this.toResult(handle);
|
158
151
|
}
|
159
152
|
getEvents() {
|
160
153
|
return this.events;
|
@@ -166,13 +159,47 @@ class StormEventParser {
|
|
166
159
|
return this.error;
|
167
160
|
}
|
168
161
|
applyLayoutToBlocks(result) {
|
162
|
+
const graph = (0, ngraph_graph_1.default)();
|
163
|
+
const blockInstances = {};
|
164
|
+
result.plan.spec.blocks.forEach((block, index) => {
|
165
|
+
graph.addNode(block.id, block);
|
166
|
+
blockInstances[block.id] = block;
|
167
|
+
});
|
168
|
+
result.plan.spec.connections.forEach((connection) => {
|
169
|
+
graph.addLink(connection.provider.blockId, connection.consumer.blockId);
|
170
|
+
});
|
171
|
+
const layout = (0, ngraph_forcelayout_1.default)(graph, {
|
172
|
+
springLength: 150,
|
173
|
+
debug: true,
|
174
|
+
dimensions: 2,
|
175
|
+
gravity: 2,
|
176
|
+
springCoefficient: 0.0008,
|
177
|
+
});
|
178
|
+
for (let i = 0; i < 100; ++i) {
|
179
|
+
layout.step();
|
180
|
+
}
|
181
|
+
// Layout might place things in negative space. We move everything to positive space
|
182
|
+
const graphBox = layout.getGraphRect();
|
183
|
+
let yAdjust = 0;
|
184
|
+
let xAdjust = 0;
|
185
|
+
if (graphBox.y1 < 0) {
|
186
|
+
yAdjust = -graphBox.y1;
|
187
|
+
}
|
188
|
+
if (graphBox.x1 < 0) {
|
189
|
+
xAdjust = -graphBox.x1;
|
190
|
+
}
|
191
|
+
graph.forEachNode((node) => {
|
192
|
+
const position = layout.getNodePosition(node.id);
|
193
|
+
blockInstances[node.id].dimensions.left = LAYOUT_MARGIN + Math.round(position.x + xAdjust);
|
194
|
+
blockInstances[node.id].dimensions.top = LAYOUT_MARGIN + Math.round(position.y + yAdjust);
|
195
|
+
});
|
196
|
+
layout.dispose();
|
169
197
|
return result;
|
170
198
|
}
|
171
199
|
toResult(handle) {
|
172
200
|
const planRef = this.toRef(handle, this.planName);
|
173
201
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
174
202
|
const refIdMap = {};
|
175
|
-
const screens = {};
|
176
203
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
177
204
|
const id = node_uuid_1.default.v4();
|
178
205
|
refIdMap[ref] = id;
|
@@ -185,20 +212,11 @@ class StormEventParser {
|
|
185
212
|
dimensions: {
|
186
213
|
left: 0,
|
187
214
|
top: 0,
|
188
|
-
width:
|
215
|
+
width: 150,
|
189
216
|
height: 200,
|
190
217
|
},
|
191
218
|
};
|
192
219
|
});
|
193
|
-
Object.values(this.blocks).forEach((blockInfo) => {
|
194
|
-
const blockRef = this.toRef(handle, blockInfo.name);
|
195
|
-
const block = blockDefinitions[blockRef.toNormalizedString()];
|
196
|
-
if (!block) {
|
197
|
-
console.warn('Block not found: %s', blockInfo.name);
|
198
|
-
return;
|
199
|
-
}
|
200
|
-
screens[blockRef.fullName] = blockInfo.screens;
|
201
|
-
});
|
202
220
|
// Copy API methods from API provider to CLIENT consumer
|
203
221
|
this.connections
|
204
222
|
.filter((connection) => connection.fromResourceType === 'API' && connection.toResourceType === 'CLIENT')
|
@@ -207,29 +225,28 @@ class StormEventParser {
|
|
207
225
|
const clientConsumerRef = this.toRef(handle, apiConnection.toComponent);
|
208
226
|
const apiProviderBlock = blockDefinitions[apiProviderRef.toNormalizedString()];
|
209
227
|
if (!apiProviderBlock) {
|
210
|
-
console.warn('API provider not found: %s', apiConnection.fromComponent);
|
228
|
+
console.warn('API provider not found: %s', apiConnection.fromComponent, apiConnection);
|
211
229
|
return;
|
212
230
|
}
|
213
231
|
const clientConsumerBlock = blockDefinitions[clientConsumerRef.toNormalizedString()];
|
214
232
|
if (!clientConsumerBlock) {
|
215
|
-
console.warn('Client consumer not found: %s', apiConnection.toComponent);
|
233
|
+
console.warn('Client consumer not found: %s', apiConnection.toComponent, apiConnection);
|
216
234
|
return;
|
217
235
|
}
|
218
236
|
const apiResource = apiProviderBlock.content.spec.providers?.find((p) => p.kind === this.options.apiKind && p.metadata.name === apiConnection.fromResource);
|
219
237
|
if (!apiResource) {
|
220
|
-
console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString());
|
238
|
+
console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString(), apiConnection);
|
221
239
|
return;
|
222
240
|
}
|
223
241
|
const clientResource = clientConsumerBlock.content.spec.consumers?.find((clientResource) => {
|
224
242
|
if (clientResource.kind !== this.options.clientKind) {
|
225
|
-
|
226
|
-
|
227
|
-
if (clientResource.metadata.name !== apiConnection.toResource) {
|
228
|
-
return;
|
243
|
+
console.warn('Client resource kind mismatch: %s', clientResource.kind, this.options.clientKind);
|
244
|
+
return false;
|
229
245
|
}
|
246
|
+
return clientResource.metadata.name === apiConnection.toResource;
|
230
247
|
});
|
231
248
|
if (!clientResource) {
|
232
|
-
console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString());
|
249
|
+
console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString(), apiConnection);
|
233
250
|
return;
|
234
251
|
}
|
235
252
|
clientResource.spec.methods = apiResource.spec.methods;
|
@@ -284,6 +301,7 @@ class StormEventParser {
|
|
284
301
|
const blockRef = this.toRef(handle, blockInfo.name);
|
285
302
|
const blockDefinitionInfo = {
|
286
303
|
uri: blockRef,
|
304
|
+
aiName: blockInfo.name,
|
287
305
|
content: {
|
288
306
|
kind: this.toBlockKind(blockInfo.type),
|
289
307
|
metadata: {
|
@@ -305,7 +323,6 @@ class StormEventParser {
|
|
305
323
|
consumers: [],
|
306
324
|
},
|
307
325
|
},
|
308
|
-
screens: blockInfo.screens,
|
309
326
|
};
|
310
327
|
const blockSpec = blockDefinitionInfo.content.spec;
|
311
328
|
let apiResource = undefined;
|
@@ -398,7 +415,7 @@ class StormEventParser {
|
|
398
415
|
},
|
399
416
|
},
|
400
417
|
};
|
401
|
-
blockSpec.
|
418
|
+
blockSpec.consumers.push(dbResource);
|
402
419
|
break;
|
403
420
|
case 'JWTCONSUMER':
|
404
421
|
case 'WEBFRAGMENT':
|
@@ -2,6 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
+
import { StormDefinitions } from "./event-parser";
|
5
6
|
export type StormResourceType = 'API' | 'DATABASE' | 'CLIENT' | 'JWTPROVIDER' | 'JWTCONSUMER' | 'WEBFRAGMENT' | 'WEBPAGE' | 'SMTPCLIENT' | 'EXTERNAL_API' | 'SUBSCRIBER' | 'PUBLISHER' | 'QUEUE' | 'EXCHANGE';
|
6
7
|
export type StormBlockType = 'BACKEND' | 'FRONTEND' | 'GATEWAY' | 'MQ' | 'CLI' | 'DESKTOP';
|
7
8
|
export interface StormBlockInfo {
|
@@ -18,7 +19,6 @@ export interface StormBlockInfoFilled extends StormBlockInfo {
|
|
18
19
|
apis: string[];
|
19
20
|
types: string[];
|
20
21
|
models: string[];
|
21
|
-
screens: ScreenTemplate[];
|
22
22
|
}
|
23
23
|
export interface StormEventCreateBlock {
|
24
24
|
type: 'CREATE_BLOCK';
|
@@ -124,4 +124,10 @@ export interface StormEventDone {
|
|
124
124
|
type: 'DONE';
|
125
125
|
created: number;
|
126
126
|
}
|
127
|
-
export
|
127
|
+
export interface StormEventDefinitionChange {
|
128
|
+
type: 'DEFINITION_CHANGE';
|
129
|
+
reason: string;
|
130
|
+
created: number;
|
131
|
+
payload: StormDefinitions;
|
132
|
+
}
|
133
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
@@ -21,17 +21,17 @@ router.post('/:handle/all', async (req, res) => {
|
|
21
21
|
try {
|
22
22
|
const stormOptions = await (0, event_parser_1.resolveOptions)();
|
23
23
|
const eventParser = new event_parser_1.StormEventParser(stormOptions);
|
24
|
-
console.log('Got prompt', req.stringBody);
|
25
24
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
26
25
|
const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, aiRequest.history);
|
27
26
|
res.set('Content-Type', 'application/x-ndjson');
|
28
27
|
metaStream.on('data', (data) => {
|
29
|
-
eventParser.addEvent(data);
|
28
|
+
const result = eventParser.addEvent(req.params.handle, data);
|
29
|
+
sendDefinitions(res, result);
|
30
30
|
});
|
31
31
|
await streamStormPartialResponse(metaStream, res);
|
32
32
|
if (!eventParser.isValid()) {
|
33
33
|
// We can't continue if the meta stream is invalid
|
34
|
-
res
|
34
|
+
sendEvent(res, {
|
35
35
|
type: 'ERROR_INTERNAL',
|
36
36
|
payload: { error: eventParser.getError() },
|
37
37
|
reason: 'Failed to generate system',
|
@@ -41,8 +41,8 @@ router.post('/:handle/all', async (req, res) => {
|
|
41
41
|
return;
|
42
42
|
}
|
43
43
|
const result = eventParser.toResult(handle);
|
44
|
-
|
45
|
-
const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks);
|
44
|
+
sendDefinitions(res, result);
|
45
|
+
const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
|
46
46
|
const codegenStream = streamStormPartialResponse(stormCodegen.getStream(), res);
|
47
47
|
await stormCodegen.process();
|
48
48
|
await codegenStream;
|
@@ -52,42 +52,30 @@ router.post('/:handle/all', async (req, res) => {
|
|
52
52
|
sendError(err, res);
|
53
53
|
}
|
54
54
|
});
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
const result = await stormClient_1.stormClient.createServiceImplementation(aiRequest.prompt, aiRequest.history);
|
63
|
-
await streamStormResponse(result, res);
|
64
|
-
});
|
65
|
-
router.post('/ui/implement', async (req, res) => {
|
66
|
-
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
67
|
-
const result = await stormClient_1.stormClient.createUIImplementation(aiRequest.prompt, aiRequest.history);
|
68
|
-
await streamStormResponse(result, res);
|
69
|
-
});
|
70
|
-
async function streamStormResponse(result, res) {
|
71
|
-
res.set('Content-Type', 'application/x-ndjson');
|
72
|
-
await streamStormPartialResponse(result, res);
|
73
|
-
sendDone(res);
|
55
|
+
function sendDefinitions(res, result) {
|
56
|
+
sendEvent(res, {
|
57
|
+
type: 'DEFINITION_CHANGE',
|
58
|
+
payload: result,
|
59
|
+
reason: 'Updates to definition',
|
60
|
+
created: Date.now(),
|
61
|
+
});
|
74
62
|
}
|
75
63
|
function sendDone(res) {
|
76
|
-
res
|
64
|
+
sendEvent(res, {
|
77
65
|
type: 'DONE',
|
78
66
|
created: Date.now(),
|
79
|
-
})
|
67
|
+
});
|
80
68
|
res.end();
|
81
69
|
}
|
82
70
|
function sendError(err, res) {
|
83
71
|
console.error('Failed to send prompt', err);
|
84
72
|
if (res.headersSent) {
|
85
|
-
res
|
73
|
+
sendEvent(res, {
|
86
74
|
type: 'ERROR_INTERNAL',
|
87
75
|
created: Date.now(),
|
88
76
|
payload: { error: err.message },
|
89
77
|
reason: 'Failed while sending prompt',
|
90
|
-
})
|
78
|
+
});
|
91
79
|
}
|
92
80
|
else {
|
93
81
|
res.status(400).send({ error: err.message });
|
@@ -96,7 +84,7 @@ function sendError(err, res) {
|
|
96
84
|
function streamStormPartialResponse(result, res) {
|
97
85
|
return new Promise((resolve, reject) => {
|
98
86
|
result.on('data', (data) => {
|
99
|
-
res
|
87
|
+
sendEvent(res, data);
|
100
88
|
});
|
101
89
|
result.on('error', (err) => {
|
102
90
|
reject(err);
|
@@ -106,4 +94,7 @@ function streamStormPartialResponse(result, res) {
|
|
106
94
|
});
|
107
95
|
});
|
108
96
|
}
|
97
|
+
function sendEvent(res, evt) {
|
98
|
+
res.write(JSON.stringify(evt) + '\n');
|
99
|
+
}
|
109
100
|
exports.default = router;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ConversationItem, StormFileImplementationPrompt, StormStream } from './stream';
|
1
|
+
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt } from './stream';
|
2
2
|
export declare const STORM_ID = "storm";
|
3
3
|
declare class StormClient {
|
4
4
|
private readonly _baseUrl;
|
@@ -6,7 +6,7 @@ declare class StormClient {
|
|
6
6
|
private createOptions;
|
7
7
|
private send;
|
8
8
|
createMetadata(prompt: string, history?: ConversationItem[]): Promise<StormStream>;
|
9
|
-
createUIImplementation(prompt:
|
9
|
+
createUIImplementation(prompt: StormUIImplementationPrompt, history?: ConversationItem[]): Promise<StormStream>;
|
10
10
|
createServiceImplementation(prompt: StormFileImplementationPrompt, history?: ConversationItem[]): Promise<StormStream>;
|
11
11
|
}
|
12
12
|
export declare const stormClient: StormClient;
|
@@ -53,12 +53,6 @@ class StormClient {
|
|
53
53
|
jsonLStream.on('error', (error) => {
|
54
54
|
out.emit('error', error);
|
55
55
|
});
|
56
|
-
jsonLStream.on('pause', () => {
|
57
|
-
console.log('paused');
|
58
|
-
});
|
59
|
-
jsonLStream.on('resume', () => {
|
60
|
-
console.log('resumed');
|
61
|
-
});
|
62
56
|
jsonLStream.on('close', () => {
|
63
57
|
out.end();
|
64
58
|
});
|
@@ -77,7 +71,6 @@ class StormClient {
|
|
77
71
|
});
|
78
72
|
}
|
79
73
|
createServiceImplementation(prompt, history) {
|
80
|
-
console.log('SENDING SERVICE PROMPT', JSON.stringify(prompt, null, 2));
|
81
74
|
return this.send('/v2/services/merge', {
|
82
75
|
history: history ?? [],
|
83
76
|
prompt,
|
@@ -36,3 +36,10 @@ export interface StormFileImplementationPrompt {
|
|
36
36
|
template: StormFileInfo;
|
37
37
|
prompt: string;
|
38
38
|
}
|
39
|
+
export interface StormUIImplementationPrompt {
|
40
|
+
events: StormEvent[];
|
41
|
+
templates: StormFileInfo[];
|
42
|
+
context: StormFileInfo[];
|
43
|
+
blockName: string;
|
44
|
+
prompt: string;
|
45
|
+
}
|