@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.
Files changed (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/src/storm/codegen.d.ts +5 -1
  3. package/dist/cjs/src/storm/codegen.js +68 -18
  4. package/dist/cjs/src/storm/event-parser.d.ts +5 -5
  5. package/dist/cjs/src/storm/event-parser.js +50 -33
  6. package/dist/cjs/src/storm/events.d.ts +8 -2
  7. package/dist/cjs/src/storm/events.js +0 -4
  8. package/dist/cjs/src/storm/routes.js +20 -29
  9. package/dist/cjs/src/storm/stormClient.d.ts +2 -2
  10. package/dist/cjs/src/storm/stormClient.js +0 -7
  11. package/dist/cjs/src/storm/stream.d.ts +7 -0
  12. package/dist/cjs/test/storm/event-parser.test.d.ts +5 -0
  13. package/dist/cjs/test/storm/event-parser.test.js +169 -0
  14. package/dist/esm/src/storm/codegen.d.ts +5 -1
  15. package/dist/esm/src/storm/codegen.js +68 -18
  16. package/dist/esm/src/storm/event-parser.d.ts +5 -5
  17. package/dist/esm/src/storm/event-parser.js +50 -33
  18. package/dist/esm/src/storm/events.d.ts +8 -2
  19. package/dist/esm/src/storm/events.js +0 -4
  20. package/dist/esm/src/storm/routes.js +20 -29
  21. package/dist/esm/src/storm/stormClient.d.ts +2 -2
  22. package/dist/esm/src/storm/stormClient.js +0 -7
  23. package/dist/esm/src/storm/stream.d.ts +7 -0
  24. package/dist/esm/test/storm/event-parser.test.d.ts +5 -0
  25. package/dist/esm/test/storm/event-parser.test.js +169 -0
  26. package/package.json +3 -1
  27. package/src/storm/codegen.ts +83 -27
  28. package/src/storm/event-parser.ts +68 -47
  29. package/src/storm/events.ts +10 -2
  30. package/src/storm/routes.ts +42 -59
  31. package/src/storm/stormClient.ts +3 -11
  32. package/src/storm/stream.ts +8 -0
  33. 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
- constructor(userPrompt: string, blocks: BlockDefinitionInfo[]);
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
- constructor(userPrompt, blocks) {
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
- handleFileOutput(blockUri, template, data) {
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
- break;
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, block.screens);
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 implementFiles = [codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN];
53
- // Gather the context files. These will be all be passed to the AI
54
- const contextFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && !implementFiles.includes(file.type));
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
- await this.processTemplates(block.uri, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
58
- //const uiTemplates: StormFileInfo[] = allFiles.filter((file) => file.type === 'ui');
59
- //await this.processTemplates(stormClient.createUIImplementation, uiTemplates, contextFiles);
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
- stream.on('data', (evt) => this.handleFileOutput(blockUri, templateFile, evt));
105
- return stream.waitForDone();
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
- return Promise.all(promises);
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, screens) {
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 { ScreenTemplate, StormEvent } from './events';
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
- screens: ScreenTemplate[];
11
+ aiName: string;
12
12
  }
13
- export interface ParsedResult {
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): void;
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): ParsedResult;
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: 200,
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
- return;
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.providers.push(dbResource);
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 type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone;
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;
@@ -1,6 +1,2 @@
1
1
  "use strict";
2
- /**
3
- * Copyright 2023 Kapeta Inc.
4
- * SPDX-License-Identifier: BUSL-1.1
5
- */
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.write({
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
- console.log('RESULT\n', JSON.stringify(result, null, 2));
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
- router.post('/metadata', async (req, res) => {
56
- const aiRequest = JSON.parse(req.stringBody ?? '{}');
57
- const result = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, aiRequest.history);
58
- await streamStormResponse(result, res);
59
- });
60
- router.post('/services/implement', async (req, res) => {
61
- const aiRequest = JSON.parse(req.stringBody ?? '{}');
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.write(JSON.stringify({
64
+ sendEvent(res, {
77
65
  type: 'DONE',
78
66
  created: Date.now(),
79
- }) + '\n');
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.write(JSON.stringify({
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
- }) + '\n');
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.write(JSON.stringify(data) + '\n');
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: StormFileImplementationPrompt, history?: ConversationItem[]): Promise<StormStream>;
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
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export {};