@kapeta/local-cluster-service 0.45.0 → 0.46.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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.46.0](https://github.com/kapetacom/local-cluster-service/compare/v0.45.0...v0.46.0) (2024-05-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * 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))
7
+
1
8
  # [0.45.0](https://github.com/kapetacom/local-cluster-service/compare/v0.44.0...v0.45.0) (2024-05-24)
2
9
 
3
10
 
@@ -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,13 +2,13 @@
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
13
  export interface ParsedResult {
14
14
  plan: Plan;
@@ -106,7 +106,6 @@ class StormEventParser {
106
106
  this.connections = [];
107
107
  }
108
108
  addEvent(evt) {
109
- console.log('Processing storm event', evt);
110
109
  this.events.push(evt);
111
110
  switch (evt.type) {
112
111
  case 'CREATE_PLAN_PROPERTIES':
@@ -119,7 +118,6 @@ class StormEventParser {
119
118
  apis: [],
120
119
  models: [],
121
120
  types: [],
122
- screens: [],
123
121
  };
124
122
  break;
125
123
  case 'PLAN_RETRY':
@@ -141,18 +139,9 @@ class StormEventParser {
141
139
  case 'CREATE_CONNECTION':
142
140
  this.connections.push(evt.payload);
143
141
  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
142
  default:
153
143
  case 'SCREEN_CANDIDATE':
154
144
  case 'FILE':
155
- console.warn('Unhandled event: %s', evt.type, evt);
156
145
  break;
157
146
  }
158
147
  }
@@ -190,15 +179,6 @@ class StormEventParser {
190
179
  },
191
180
  };
192
181
  });
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
182
  // Copy API methods from API provider to CLIENT consumer
203
183
  this.connections
204
184
  .filter((connection) => connection.fromResourceType === 'API' && connection.toResourceType === 'CLIENT')
@@ -207,29 +187,28 @@ class StormEventParser {
207
187
  const clientConsumerRef = this.toRef(handle, apiConnection.toComponent);
208
188
  const apiProviderBlock = blockDefinitions[apiProviderRef.toNormalizedString()];
209
189
  if (!apiProviderBlock) {
210
- console.warn('API provider not found: %s', apiConnection.fromComponent);
190
+ console.warn('API provider not found: %s', apiConnection.fromComponent, apiConnection);
211
191
  return;
212
192
  }
213
193
  const clientConsumerBlock = blockDefinitions[clientConsumerRef.toNormalizedString()];
214
194
  if (!clientConsumerBlock) {
215
- console.warn('Client consumer not found: %s', apiConnection.toComponent);
195
+ console.warn('Client consumer not found: %s', apiConnection.toComponent, apiConnection);
216
196
  return;
217
197
  }
218
198
  const apiResource = apiProviderBlock.content.spec.providers?.find((p) => p.kind === this.options.apiKind && p.metadata.name === apiConnection.fromResource);
219
199
  if (!apiResource) {
220
- console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString());
200
+ console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString(), apiConnection);
221
201
  return;
222
202
  }
223
203
  const clientResource = clientConsumerBlock.content.spec.consumers?.find((clientResource) => {
224
204
  if (clientResource.kind !== this.options.clientKind) {
225
- return;
226
- }
227
- if (clientResource.metadata.name !== apiConnection.toResource) {
228
- return;
205
+ console.warn('Client resource kind mismatch: %s', clientResource.kind, this.options.clientKind);
206
+ return false;
229
207
  }
208
+ return clientResource.metadata.name === apiConnection.toResource;
230
209
  });
231
210
  if (!clientResource) {
232
- console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString());
211
+ console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString(), apiConnection);
233
212
  return;
234
213
  }
235
214
  clientResource.spec.methods = apiResource.spec.methods;
@@ -284,6 +263,7 @@ class StormEventParser {
284
263
  const blockRef = this.toRef(handle, blockInfo.name);
285
264
  const blockDefinitionInfo = {
286
265
  uri: blockRef,
266
+ aiName: blockInfo.name,
287
267
  content: {
288
268
  kind: this.toBlockKind(blockInfo.type),
289
269
  metadata: {
@@ -305,7 +285,6 @@ class StormEventParser {
305
285
  consumers: [],
306
286
  },
307
287
  },
308
- screens: blockInfo.screens,
309
288
  };
310
289
  const blockSpec = blockDefinitionInfo.content.spec;
311
290
  let apiResource = undefined;
@@ -18,7 +18,6 @@ export interface StormBlockInfoFilled extends StormBlockInfo {
18
18
  apis: string[];
19
19
  types: string[];
20
20
  models: string[];
21
- screens: ScreenTemplate[];
22
21
  }
23
22
  export interface StormEventCreateBlock {
24
23
  type: 'CREATE_BLOCK';
@@ -21,7 +21,6 @@ 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');
@@ -41,8 +40,7 @@ router.post('/:handle/all', async (req, res) => {
41
40
  return;
42
41
  }
43
42
  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);
43
+ const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
46
44
  const codegenStream = streamStormPartialResponse(stormCodegen.getStream(), res);
47
45
  await stormCodegen.process();
48
46
  await codegenStream;
@@ -52,26 +50,6 @@ router.post('/:handle/all', async (req, res) => {
52
50
  sendError(err, res);
53
51
  }
54
52
  });
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);
74
- }
75
53
  function sendDone(res) {
76
54
  res.write(JSON.stringify({
77
55
  type: 'DONE',
@@ -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
+ }
@@ -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,13 +2,13 @@
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
13
  export interface ParsedResult {
14
14
  plan: Plan;
@@ -106,7 +106,6 @@ class StormEventParser {
106
106
  this.connections = [];
107
107
  }
108
108
  addEvent(evt) {
109
- console.log('Processing storm event', evt);
110
109
  this.events.push(evt);
111
110
  switch (evt.type) {
112
111
  case 'CREATE_PLAN_PROPERTIES':
@@ -119,7 +118,6 @@ class StormEventParser {
119
118
  apis: [],
120
119
  models: [],
121
120
  types: [],
122
- screens: [],
123
121
  };
124
122
  break;
125
123
  case 'PLAN_RETRY':
@@ -141,18 +139,9 @@ class StormEventParser {
141
139
  case 'CREATE_CONNECTION':
142
140
  this.connections.push(evt.payload);
143
141
  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
142
  default:
153
143
  case 'SCREEN_CANDIDATE':
154
144
  case 'FILE':
155
- console.warn('Unhandled event: %s', evt.type, evt);
156
145
  break;
157
146
  }
158
147
  }
@@ -190,15 +179,6 @@ class StormEventParser {
190
179
  },
191
180
  };
192
181
  });
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
182
  // Copy API methods from API provider to CLIENT consumer
203
183
  this.connections
204
184
  .filter((connection) => connection.fromResourceType === 'API' && connection.toResourceType === 'CLIENT')
@@ -207,29 +187,28 @@ class StormEventParser {
207
187
  const clientConsumerRef = this.toRef(handle, apiConnection.toComponent);
208
188
  const apiProviderBlock = blockDefinitions[apiProviderRef.toNormalizedString()];
209
189
  if (!apiProviderBlock) {
210
- console.warn('API provider not found: %s', apiConnection.fromComponent);
190
+ console.warn('API provider not found: %s', apiConnection.fromComponent, apiConnection);
211
191
  return;
212
192
  }
213
193
  const clientConsumerBlock = blockDefinitions[clientConsumerRef.toNormalizedString()];
214
194
  if (!clientConsumerBlock) {
215
- console.warn('Client consumer not found: %s', apiConnection.toComponent);
195
+ console.warn('Client consumer not found: %s', apiConnection.toComponent, apiConnection);
216
196
  return;
217
197
  }
218
198
  const apiResource = apiProviderBlock.content.spec.providers?.find((p) => p.kind === this.options.apiKind && p.metadata.name === apiConnection.fromResource);
219
199
  if (!apiResource) {
220
- console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString());
200
+ console.warn('API resource not found: %s on %s', apiConnection.fromResource, apiProviderRef.toNormalizedString(), apiConnection);
221
201
  return;
222
202
  }
223
203
  const clientResource = clientConsumerBlock.content.spec.consumers?.find((clientResource) => {
224
204
  if (clientResource.kind !== this.options.clientKind) {
225
- return;
226
- }
227
- if (clientResource.metadata.name !== apiConnection.toResource) {
228
- return;
205
+ console.warn('Client resource kind mismatch: %s', clientResource.kind, this.options.clientKind);
206
+ return false;
229
207
  }
208
+ return clientResource.metadata.name === apiConnection.toResource;
230
209
  });
231
210
  if (!clientResource) {
232
- console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString());
211
+ console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString(), apiConnection);
233
212
  return;
234
213
  }
235
214
  clientResource.spec.methods = apiResource.spec.methods;
@@ -284,6 +263,7 @@ class StormEventParser {
284
263
  const blockRef = this.toRef(handle, blockInfo.name);
285
264
  const blockDefinitionInfo = {
286
265
  uri: blockRef,
266
+ aiName: blockInfo.name,
287
267
  content: {
288
268
  kind: this.toBlockKind(blockInfo.type),
289
269
  metadata: {
@@ -305,7 +285,6 @@ class StormEventParser {
305
285
  consumers: [],
306
286
  },
307
287
  },
308
- screens: blockInfo.screens,
309
288
  };
310
289
  const blockSpec = blockDefinitionInfo.content.spec;
311
290
  let apiResource = undefined;
@@ -18,7 +18,6 @@ export interface StormBlockInfoFilled extends StormBlockInfo {
18
18
  apis: string[];
19
19
  types: string[];
20
20
  models: string[];
21
- screens: ScreenTemplate[];
22
21
  }
23
22
  export interface StormEventCreateBlock {
24
23
  type: 'CREATE_BLOCK';
@@ -21,7 +21,6 @@ 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');
@@ -41,8 +40,7 @@ router.post('/:handle/all', async (req, res) => {
41
40
  return;
42
41
  }
43
42
  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);
43
+ const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
46
44
  const codegenStream = streamStormPartialResponse(stormCodegen.getStream(), res);
47
45
  await stormCodegen.process();
48
46
  await codegenStream;
@@ -52,26 +50,6 @@ router.post('/:handle/all', async (req, res) => {
52
50
  sendError(err, res);
53
51
  }
54
52
  });
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);
74
- }
75
53
  function sendDone(res) {
76
54
  res.write(JSON.stringify({
77
55
  type: 'DONE',
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -8,7 +8,7 @@ import { AIFileTypes, BlockCodeGenerator, GeneratedFile, GeneratedResult } from
8
8
  import { BlockDefinition } from '@kapeta/schemas';
9
9
  import { codeGeneratorManager } from '../codeGeneratorManager';
10
10
  import { STORM_ID, stormClient } from './stormClient';
11
- import { ScreenTemplate, StormEvent, StormEventFile } from './events';
11
+ import { ScreenTemplate, StormEvent, StormEventFile, StormEventScreen } from './events';
12
12
  import { BlockDefinitionInfo } from './event-parser';
13
13
  import { ConversationItem, StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
14
14
  import { KapetaURI } from '@kapeta/nodejs-utils';
@@ -22,15 +22,15 @@ export class StormCodegen {
22
22
  private readonly userPrompt: string;
23
23
  private readonly blocks: BlockDefinitionInfo[];
24
24
  private readonly out = new StormStream();
25
+ private readonly events: StormEvent[];
25
26
 
26
- constructor(userPrompt: string, blocks: BlockDefinitionInfo[]) {
27
+ constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
27
28
  this.userPrompt = userPrompt;
28
29
  this.blocks = blocks;
30
+ this.events = events;
29
31
  }
30
32
 
31
33
  public async process() {
32
- console.log('Processing blocks', this.blocks.length);
33
-
34
34
  for (const block of this.blocks) {
35
35
  await this.processBlockCode(block);
36
36
  }
@@ -42,13 +42,44 @@ export class StormCodegen {
42
42
  return this.out;
43
43
  }
44
44
 
45
- private handleFileOutput(blockUri: KapetaURI, template: StormFileInfo, data: StormEvent) {
45
+ private handleTemplateFileOutput(blockUri: KapetaURI, template: StormFileInfo, data: StormEvent) {
46
46
  switch (data.type) {
47
47
  case 'FILE':
48
48
  template.filename = data.payload.filename;
49
49
  template.content = data.payload.content;
50
+ return this.handleFileOutput(blockUri, data);
51
+ }
52
+ }
53
+
54
+ private handleUiOutput(blockUri: KapetaURI, data: StormEvent) {
55
+ switch (data.type) {
56
+ case 'SCREEN':
57
+ this.out.emit('data', {
58
+ type: 'SCREEN',
59
+ reason: data.reason,
60
+ created: Date.now(),
61
+ payload: {
62
+ ...data.payload,
63
+ blockName: blockUri.toNormalizedString(),
64
+ },
65
+ });
66
+ case 'FILE':
67
+ return this.handleFileOutput(blockUri, data);
68
+ }
69
+ }
70
+
71
+ private handleFileOutput(blockUri: KapetaURI, data: StormEvent) {
72
+ switch (data.type) {
73
+ case 'FILE':
50
74
  this.emitFile(blockUri, data.payload.filename, data.payload.content, data.reason);
51
- break;
75
+ return {
76
+ type: 'FILE',
77
+ created: Date.now(),
78
+ payload: {
79
+ filename: data.payload.filename,
80
+ content: data.payload.content,
81
+ },
82
+ } as StormEventFile;
52
83
  }
53
84
  }
54
85
 
@@ -57,9 +88,8 @@ export class StormCodegen {
57
88
  */
58
89
  private async processBlockCode(block: BlockDefinitionInfo) {
59
90
  // Generate the code for the block using the standard codegen templates
60
- const generatedResult = await this.generateBlock(block.content, block.screens);
91
+ const generatedResult = await this.generateBlock(block.content);
61
92
  if (!generatedResult) {
62
- console.warn('No generated result for block', block.uri);
63
93
  return;
64
94
  }
65
95
 
@@ -68,24 +98,41 @@ export class StormCodegen {
68
98
  // Send all the non-ai files to the stream
69
99
  this.emitFiles(block.uri, allFiles);
70
100
 
71
- const implementFiles = [AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN];
101
+ const relevantFiles: StormFileInfo[] = allFiles.filter(
102
+ (file) => file.type !== AIFileTypes.IGNORE && file.type !== AIFileTypes.WEB_SCREEN
103
+ );
104
+ const uiTemplates: StormFileInfo[] = allFiles.filter((file) => file.type === AIFileTypes.WEB_SCREEN);
105
+ if (uiTemplates.length > 0) {
106
+ const uiStream = await stormClient.createUIImplementation({
107
+ events: this.events,
108
+ templates: uiTemplates,
109
+ context: relevantFiles,
110
+ blockName: block.aiName,
111
+ prompt: this.userPrompt,
112
+ });
113
+
114
+ uiStream.on('data', (evt) => {
115
+ this.handleUiOutput(block.uri, evt);
116
+ });
117
+
118
+ await uiStream.waitForDone();
119
+ }
72
120
 
73
- // Gather the context files. These will be all be passed to the AI
74
- const contextFiles: StormFileInfo[] = allFiles.filter(
75
- (file) => file.type !== AIFileTypes.IGNORE && !implementFiles.includes(file.type)
121
+ // Gather the context files for implementation. These will be all be passed to the AI
122
+ const contextFiles: StormFileInfo[] = relevantFiles.filter(
123
+ (file) => ![AIFileTypes.SERVICE, AIFileTypes.WEB_SCREEN].includes(file.type)
76
124
  );
77
125
 
78
126
  // Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
79
127
  const serviceFiles: StormFileInfo[] = allFiles.filter((file) => file.type === AIFileTypes.SERVICE);
80
- await this.processTemplates(
81
- block.uri,
82
- stormClient.createServiceImplementation.bind(stormClient),
83
- serviceFiles,
84
- contextFiles
85
- );
86
-
87
- //const uiTemplates: StormFileInfo[] = allFiles.filter((file) => file.type === 'ui');
88
- //await this.processTemplates(stormClient.createUIImplementation, uiTemplates, contextFiles);
128
+ if (serviceFiles.length > 0) {
129
+ await this.processTemplates(
130
+ block.uri,
131
+ stormClient.createServiceImplementation.bind(stormClient),
132
+ serviceFiles,
133
+ contextFiles
134
+ );
135
+ }
89
136
  }
90
137
 
91
138
  /**
@@ -129,7 +176,7 @@ export class StormCodegen {
129
176
  /**
130
177
  * Sends the template to the AI and processes the response
131
178
  */
132
- private processTemplates(
179
+ private async processTemplates(
133
180
  blockUri: KapetaURI,
134
181
  generator: ImplementationGenerator,
135
182
  templates: StormFileInfo[],
@@ -142,12 +189,22 @@ export class StormCodegen {
142
189
  prompt: this.userPrompt,
143
190
  });
144
191
 
145
- stream.on('data', (evt) => this.handleFileOutput(blockUri, templateFile, evt));
192
+ const files: StormEventFile[] = [];
146
193
 
147
- return stream.waitForDone();
194
+ stream.on('data', (evt) => {
195
+ const file = this.handleTemplateFileOutput(blockUri, templateFile, evt);
196
+ if (file) {
197
+ files.push(file);
198
+ }
199
+ });
200
+
201
+ await stream.waitForDone();
202
+ return files;
148
203
  });
149
204
 
150
- return Promise.all(promises);
205
+ const fileChunks = await Promise.all(promises);
206
+
207
+ return fileChunks.flat();
151
208
  }
152
209
 
153
210
  /**
@@ -191,7 +248,7 @@ export class StormCodegen {
191
248
  /**
192
249
  * Generates the code using codegen for a given block.
193
250
  */
194
- private async generateBlock(yamlContent: Definition, screens: ScreenTemplate[] | undefined) {
251
+ private async generateBlock(yamlContent: Definition) {
195
252
  if (!yamlContent.spec.target?.kind) {
196
253
  //Not all block types have targets
197
254
  return;
@@ -203,7 +260,6 @@ export class StormCodegen {
203
260
 
204
261
  const codeGenerator = new BlockCodeGenerator(yamlContent as BlockDefinition);
205
262
  codeGenerator.withOption('AIContext', STORM_ID);
206
- codeGenerator.withOption('AIScreens', screens ?? []);
207
263
 
208
264
  return codeGenerator.generate();
209
265
  }
@@ -28,7 +28,7 @@ import { definitionsManager } from '../definitionsManager';
28
28
  export interface BlockDefinitionInfo {
29
29
  uri: KapetaURI;
30
30
  content: BlockDefinition;
31
- screens: ScreenTemplate[];
31
+ aiName: string;
32
32
  }
33
33
 
34
34
  export interface ParsedResult {
@@ -197,7 +197,6 @@ export class StormEventParser {
197
197
  }
198
198
 
199
199
  public addEvent(evt: StormEvent): void {
200
- console.log('Processing storm event', evt);
201
200
  this.events.push(evt);
202
201
  switch (evt.type) {
203
202
  case 'CREATE_PLAN_PROPERTIES':
@@ -210,7 +209,6 @@ export class StormEventParser {
210
209
  apis: [],
211
210
  models: [],
212
211
  types: [],
213
- screens: [],
214
212
  };
215
213
  break;
216
214
  case 'PLAN_RETRY':
@@ -232,19 +230,10 @@ export class StormEventParser {
232
230
  case 'CREATE_CONNECTION':
233
231
  this.connections.push(evt.payload);
234
232
  break;
235
- case 'SCREEN':
236
- this.blocks[evt.payload.blockName].screens.push({
237
- name: evt.payload.name,
238
- description: evt.payload.description,
239
- url: evt.payload.url,
240
- template: evt.payload.template,
241
- });
242
- break;
243
233
 
244
234
  default:
245
235
  case 'SCREEN_CANDIDATE':
246
236
  case 'FILE':
247
- console.warn('Unhandled event: %s', evt.type, evt);
248
237
  break;
249
238
  }
250
239
  }
@@ -288,17 +277,6 @@ export class StormEventParser {
288
277
  } satisfies BlockInstance;
289
278
  });
290
279
 
291
- Object.values(this.blocks).forEach((blockInfo) => {
292
- const blockRef = this.toRef(handle, blockInfo.name);
293
- const block = blockDefinitions[blockRef.toNormalizedString()];
294
- if (!block) {
295
- console.warn('Block not found: %s', blockInfo.name);
296
- return;
297
- }
298
-
299
- screens[blockRef.fullName] = blockInfo.screens;
300
- });
301
-
302
280
  // Copy API methods from API provider to CLIENT consumer
303
281
  this.connections
304
282
  .filter((connection) => connection.fromResourceType === 'API' && connection.toResourceType === 'CLIENT')
@@ -307,12 +285,12 @@ export class StormEventParser {
307
285
  const clientConsumerRef = this.toRef(handle, apiConnection.toComponent);
308
286
  const apiProviderBlock = blockDefinitions[apiProviderRef.toNormalizedString()];
309
287
  if (!apiProviderBlock) {
310
- console.warn('API provider not found: %s', apiConnection.fromComponent);
288
+ console.warn('API provider not found: %s', apiConnection.fromComponent, apiConnection);
311
289
  return;
312
290
  }
313
291
  const clientConsumerBlock = blockDefinitions[clientConsumerRef.toNormalizedString()];
314
292
  if (!clientConsumerBlock) {
315
- console.warn('Client consumer not found: %s', apiConnection.toComponent);
293
+ console.warn('Client consumer not found: %s', apiConnection.toComponent, apiConnection);
316
294
  return;
317
295
  }
318
296
 
@@ -324,25 +302,27 @@ export class StormEventParser {
324
302
  console.warn(
325
303
  'API resource not found: %s on %s',
326
304
  apiConnection.fromResource,
327
- apiProviderRef.toNormalizedString()
305
+ apiProviderRef.toNormalizedString(),
306
+ apiConnection
328
307
  );
329
308
  return;
330
309
  }
331
310
 
332
311
  const clientResource = clientConsumerBlock.content.spec.consumers?.find((clientResource) => {
333
312
  if (clientResource.kind !== this.options.clientKind) {
334
- return;
335
- }
336
- if (clientResource.metadata.name !== apiConnection.toResource) {
337
- return;
313
+ console.warn('Client resource kind mismatch: %s', clientResource.kind, this.options.clientKind);
314
+ return false;
338
315
  }
316
+
317
+ return clientResource.metadata.name === apiConnection.toResource;
339
318
  });
340
319
 
341
320
  if (!clientResource) {
342
321
  console.warn(
343
322
  'Client resource not found: %s on %s',
344
323
  apiConnection.toResource,
345
- clientConsumerRef.toNormalizedString()
324
+ clientConsumerRef.toNormalizedString(),
325
+ apiConnection
346
326
  );
347
327
  return;
348
328
  }
@@ -407,6 +387,7 @@ export class StormEventParser {
407
387
 
408
388
  const blockDefinitionInfo: BlockDefinitionInfo = {
409
389
  uri: blockRef,
390
+ aiName: blockInfo.name,
410
391
  content: {
411
392
  kind: this.toBlockKind(blockInfo.type),
412
393
  metadata: {
@@ -428,7 +409,6 @@ export class StormEventParser {
428
409
  consumers: [],
429
410
  },
430
411
  },
431
- screens: blockInfo.screens,
432
412
  };
433
413
 
434
414
  const blockSpec = blockDefinitionInfo.content.spec;
@@ -35,7 +35,6 @@ export interface StormBlockInfoFilled extends StormBlockInfo {
35
35
  apis: string[];
36
36
  types: string[];
37
37
  models: string[];
38
- screens: ScreenTemplate[];
39
38
  }
40
39
 
41
40
  export interface StormEventCreateBlock {
@@ -27,7 +27,6 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
27
27
 
28
28
  const eventParser = new StormEventParser(stormOptions);
29
29
 
30
- console.log('Got prompt', req.stringBody);
31
30
  const aiRequest: StormContextRequest = JSON.parse(req.stringBody ?? '{}');
32
31
  const metaStream = await stormClient.createMetadata(aiRequest.prompt, aiRequest.history);
33
32
 
@@ -52,9 +51,7 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
52
51
  }
53
52
  const result = eventParser.toResult(handle);
54
53
 
55
- console.log('RESULT\n', JSON.stringify(result, null, 2));
56
-
57
- const stormCodegen = new StormCodegen(aiRequest.prompt, result.blocks);
54
+ const stormCodegen = new StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
58
55
 
59
56
  const codegenStream = streamStormPartialResponse(stormCodegen.getStream(), res);
60
57
 
@@ -68,35 +65,6 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
68
65
  }
69
66
  });
70
67
 
71
- router.post('/metadata', async (req: KapetaBodyRequest, res: Response) => {
72
- const aiRequest: StormContextRequest = JSON.parse(req.stringBody ?? '{}');
73
- const result = await stormClient.createMetadata(aiRequest.prompt, aiRequest.history);
74
-
75
- await streamStormResponse(result, res);
76
- });
77
-
78
- router.post('/services/implement', async (req: KapetaBodyRequest, res: Response) => {
79
- const aiRequest: StormContextRequest<StormFileImplementationPrompt> = JSON.parse(req.stringBody ?? '{}');
80
- const result = await stormClient.createServiceImplementation(aiRequest.prompt, aiRequest.history);
81
-
82
- await streamStormResponse(result, res);
83
- });
84
-
85
- router.post('/ui/implement', async (req: KapetaBodyRequest, res: Response) => {
86
- const aiRequest: StormContextRequest<StormFileImplementationPrompt> = JSON.parse(req.stringBody ?? '{}');
87
- const result = await stormClient.createUIImplementation(aiRequest.prompt, aiRequest.history);
88
-
89
- await streamStormResponse(result, res);
90
- });
91
-
92
- async function streamStormResponse(result: StormStream, res: Response) {
93
- res.set('Content-Type', 'application/x-ndjson');
94
-
95
- await streamStormPartialResponse(result, res);
96
-
97
- sendDone(res);
98
- }
99
-
100
68
  function sendDone(res: Response) {
101
69
  res.write(
102
70
  JSON.stringify({
@@ -12,6 +12,7 @@ import {
12
12
  StormFileImplementationPrompt,
13
13
  StormFileInfo,
14
14
  StormStream,
15
+ StormUIImplementationPrompt,
15
16
  } from './stream';
16
17
 
17
18
  export const STORM_ID = 'storm';
@@ -73,14 +74,6 @@ class StormClient {
73
74
  out.emit('error', error);
74
75
  });
75
76
 
76
- jsonLStream.on('pause', () => {
77
- console.log('paused');
78
- });
79
-
80
- jsonLStream.on('resume', () => {
81
- console.log('resumed');
82
- });
83
-
84
77
  jsonLStream.on('close', () => {
85
78
  out.end();
86
79
  });
@@ -95,15 +88,14 @@ class StormClient {
95
88
  });
96
89
  }
97
90
 
98
- public createUIImplementation(prompt: StormFileImplementationPrompt, history?: ConversationItem[]) {
99
- return this.send<StormFileImplementationPrompt>('/v2/ui/merge', {
91
+ public createUIImplementation(prompt: StormUIImplementationPrompt, history?: ConversationItem[]) {
92
+ return this.send<StormUIImplementationPrompt>('/v2/ui/merge', {
100
93
  history: history ?? [],
101
94
  prompt,
102
95
  });
103
96
  }
104
97
 
105
98
  public createServiceImplementation(prompt: StormFileImplementationPrompt, history?: ConversationItem[]) {
106
- console.log('SENDING SERVICE PROMPT', JSON.stringify(prompt, null, 2));
107
99
  return this.send<StormFileImplementationPrompt>('/v2/services/merge', {
108
100
  history: history ?? [],
109
101
  prompt,
@@ -86,3 +86,11 @@ export interface StormFileImplementationPrompt {
86
86
  template: StormFileInfo;
87
87
  prompt: string;
88
88
  }
89
+
90
+ export interface StormUIImplementationPrompt {
91
+ events: StormEvent[];
92
+ templates: StormFileInfo[];
93
+ context: StormFileInfo[];
94
+ blockName: string;
95
+ prompt: string;
96
+ }