@kapeta/local-cluster-service 0.70.11 → 0.71.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,17 @@
1
+ # [0.71.0](https://github.com/kapetacom/local-cluster-service/compare/v0.70.12...v0.71.0) (2024-09-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add two new phases ([d2805a8](https://github.com/kapetacom/local-cluster-service/commit/d2805a89d7e21b6f30674ae852ddc356954e7d79))
7
+
8
+ ## [0.70.12](https://github.com/kapetacom/local-cluster-service/compare/v0.70.11...v0.70.12) (2024-09-13)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * wait for image resources on disk [CORE-3467] ([#249](https://github.com/kapetacom/local-cluster-service/issues/249)) ([934d9f4](https://github.com/kapetacom/local-cluster-service/commit/934d9f400ae917559a4ede08d3f3e5644b3b5053))
14
+
1
15
  ## [0.70.11](https://github.com/kapetacom/local-cluster-service/compare/v0.70.10...v0.70.11) (2024-09-12)
2
16
 
3
17
 
@@ -242,13 +242,18 @@ class PageQueue extends node_events_1.EventEmitter {
242
242
  }
243
243
  this.images.set(prompt.url, prompt.description);
244
244
  const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
245
- //console.log('Adding image prompt', prompt);
245
+ let imageEvent = null;
246
246
  result.on('data', (event) => {
247
247
  if (event.type === 'IMAGE') {
248
- this.emit('image', event, prompt);
248
+ imageEvent = event;
249
249
  }
250
250
  });
251
251
  await result.waitForDone();
252
+ if (!imageEvent) {
253
+ throw new Error('No image was generated');
254
+ }
255
+ await (0, page_utils_1.writeImageToDisk)(this.systemId, imageEvent, prompt);
256
+ this.emit('image', imageEvent, prompt);
252
257
  }
253
258
  async generate(prompt, conversationId) {
254
259
  const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
@@ -271,6 +271,8 @@ export interface StormEventDefinitionChange {
271
271
  payload: StormDefinitions;
272
272
  }
273
273
  export declare enum StormEventPhaseType {
274
+ IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
275
+ COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
274
276
  META = "META",
275
277
  DEFINITIONS = "DEFINITIONS",
276
278
  IMPLEMENTATION = "IMPLEMENTATION",
@@ -11,6 +11,8 @@ var StormEventBlockStatusType;
11
11
  })(StormEventBlockStatusType || (exports.StormEventBlockStatusType = StormEventBlockStatusType = {}));
12
12
  var StormEventPhaseType;
13
13
  (function (StormEventPhaseType) {
14
+ StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
15
+ StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
14
16
  StormEventPhaseType["META"] = "META";
15
17
  StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
16
18
  StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
@@ -72,6 +72,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
72
72
  const systemId = req.params.systemId;
73
73
  const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
74
74
  const destDir = (0, page_utils_1.getSystemBaseImplDir)(systemId);
75
+ res.set('Content-Type', 'application/x-ndjson');
76
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
77
+ res.set(stormClient_1.ConversationIdHeader, systemId);
78
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
75
79
  await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
76
80
  const result = await stormClient_1.stormClient.implementAPIClients({
77
81
  content: content,
@@ -79,8 +83,11 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
79
83
  });
80
84
  return result;
81
85
  });
86
+ sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
87
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
82
88
  const pages = (0, utils_1.readPages)(destDir);
83
89
  const prompt = await stormClient_1.stormClient.generatePrompt(pages);
90
+ sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
84
91
  req.query.systemId = systemId;
85
92
  const promptRequest = {
86
93
  prompt: prompt,
@@ -118,18 +125,6 @@ router.post('/ui/screen', async (req, res) => {
118
125
  });
119
126
  const promises = [];
120
127
  queue.on('page', (data) => (systemId ? sendPageEvent(systemId, data, res) : undefined));
121
- queue.on('image', async (screenData, prompt) => {
122
- if (!systemId) {
123
- return;
124
- }
125
- try {
126
- await handleImageEvent(systemId, screenData, prompt);
127
- }
128
- catch (e) {
129
- console.error('Failed to handle image event', e);
130
- throw e;
131
- }
132
- });
133
128
  queue.on('error', (err) => {
134
129
  console.error('Failed to process page', err);
135
130
  sendError(err, res);
@@ -212,15 +207,6 @@ router.post('/:handle/ui/iterative', async (req, res) => {
212
207
  pageQueue.cancel();
213
208
  });
214
209
  pageQueue.on('page', (screenData) => sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
215
- pageQueue.on('image', async (screenData, prompt) => {
216
- try {
217
- await handleImageEvent(landingPagesStream.getConversationId(), screenData, prompt);
218
- }
219
- catch (e) {
220
- console.error('Failed to handle image event', e);
221
- throw e;
222
- }
223
- });
224
210
  pageQueue.on('event', (screenData) => {
225
211
  sendEvent(res, screenData);
226
212
  });
@@ -374,15 +360,6 @@ router.post('/:handle/ui', async (req, res) => {
374
360
  queue.cancel();
375
361
  });
376
362
  queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
377
- queue.on('image', async (screenData, prompt) => {
378
- try {
379
- await handleImageEvent(outerConversationId, screenData, prompt);
380
- }
381
- catch (e) {
382
- console.error('Failed to handle image event', e);
383
- throw e;
384
- }
385
- });
386
363
  queue.on('event', (screenData) => {
387
364
  sendEvent(res, screenData);
388
365
  });
@@ -520,9 +497,13 @@ async function handleAll(req, res) {
520
497
  onRequestAborted(req, res, () => {
521
498
  metaStream.abort();
522
499
  });
523
- res.set('Content-Type', 'application/x-ndjson');
524
- res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
525
- res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
500
+ // We check if the headers have been sent, because we might have already sent some data
501
+ // before this function is called
502
+ if (!res.headersSent) {
503
+ res.set('Content-Type', 'application/x-ndjson');
504
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
505
+ res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
506
+ }
526
507
  let currentPhase = events_1.StormEventPhaseType.META;
527
508
  // Helper to avoid sending the plan multiple times in a row
528
509
  const sendUpdatedPlan = lodash_1.default.debounce(sendDefinitions, 50, { maxWait: 200 });
@@ -722,12 +703,4 @@ async function sendPageEvent(mainConversationId, data, res) {
722
703
  }
723
704
  sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
724
705
  }
725
- async function handleImageEvent(mainConversationId, data, prompt) {
726
- try {
727
- await (0, page_utils_1.writeImageToDisk)(mainConversationId, data, prompt);
728
- }
729
- catch (err) {
730
- console.error('Failed to write image to disk', err);
731
- }
732
- }
733
706
  exports.default = router;
@@ -242,13 +242,18 @@ class PageQueue extends node_events_1.EventEmitter {
242
242
  }
243
243
  this.images.set(prompt.url, prompt.description);
244
244
  const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
245
- //console.log('Adding image prompt', prompt);
245
+ let imageEvent = null;
246
246
  result.on('data', (event) => {
247
247
  if (event.type === 'IMAGE') {
248
- this.emit('image', event, prompt);
248
+ imageEvent = event;
249
249
  }
250
250
  });
251
251
  await result.waitForDone();
252
+ if (!imageEvent) {
253
+ throw new Error('No image was generated');
254
+ }
255
+ await (0, page_utils_1.writeImageToDisk)(this.systemId, imageEvent, prompt);
256
+ this.emit('image', imageEvent, prompt);
252
257
  }
253
258
  async generate(prompt, conversationId) {
254
259
  const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
@@ -271,6 +271,8 @@ export interface StormEventDefinitionChange {
271
271
  payload: StormDefinitions;
272
272
  }
273
273
  export declare enum StormEventPhaseType {
274
+ IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
275
+ COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
274
276
  META = "META",
275
277
  DEFINITIONS = "DEFINITIONS",
276
278
  IMPLEMENTATION = "IMPLEMENTATION",
@@ -11,6 +11,8 @@ var StormEventBlockStatusType;
11
11
  })(StormEventBlockStatusType || (exports.StormEventBlockStatusType = StormEventBlockStatusType = {}));
12
12
  var StormEventPhaseType;
13
13
  (function (StormEventPhaseType) {
14
+ StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
15
+ StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
14
16
  StormEventPhaseType["META"] = "META";
15
17
  StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
16
18
  StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
@@ -72,6 +72,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
72
72
  const systemId = req.params.systemId;
73
73
  const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
74
74
  const destDir = (0, page_utils_1.getSystemBaseImplDir)(systemId);
75
+ res.set('Content-Type', 'application/x-ndjson');
76
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
77
+ res.set(stormClient_1.ConversationIdHeader, systemId);
78
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
75
79
  await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
76
80
  const result = await stormClient_1.stormClient.implementAPIClients({
77
81
  content: content,
@@ -79,8 +83,11 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
79
83
  });
80
84
  return result;
81
85
  });
86
+ sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
87
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
82
88
  const pages = (0, utils_1.readPages)(destDir);
83
89
  const prompt = await stormClient_1.stormClient.generatePrompt(pages);
90
+ sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
84
91
  req.query.systemId = systemId;
85
92
  const promptRequest = {
86
93
  prompt: prompt,
@@ -118,18 +125,6 @@ router.post('/ui/screen', async (req, res) => {
118
125
  });
119
126
  const promises = [];
120
127
  queue.on('page', (data) => (systemId ? sendPageEvent(systemId, data, res) : undefined));
121
- queue.on('image', async (screenData, prompt) => {
122
- if (!systemId) {
123
- return;
124
- }
125
- try {
126
- await handleImageEvent(systemId, screenData, prompt);
127
- }
128
- catch (e) {
129
- console.error('Failed to handle image event', e);
130
- throw e;
131
- }
132
- });
133
128
  queue.on('error', (err) => {
134
129
  console.error('Failed to process page', err);
135
130
  sendError(err, res);
@@ -212,15 +207,6 @@ router.post('/:handle/ui/iterative', async (req, res) => {
212
207
  pageQueue.cancel();
213
208
  });
214
209
  pageQueue.on('page', (screenData) => sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
215
- pageQueue.on('image', async (screenData, prompt) => {
216
- try {
217
- await handleImageEvent(landingPagesStream.getConversationId(), screenData, prompt);
218
- }
219
- catch (e) {
220
- console.error('Failed to handle image event', e);
221
- throw e;
222
- }
223
- });
224
210
  pageQueue.on('event', (screenData) => {
225
211
  sendEvent(res, screenData);
226
212
  });
@@ -374,15 +360,6 @@ router.post('/:handle/ui', async (req, res) => {
374
360
  queue.cancel();
375
361
  });
376
362
  queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
377
- queue.on('image', async (screenData, prompt) => {
378
- try {
379
- await handleImageEvent(outerConversationId, screenData, prompt);
380
- }
381
- catch (e) {
382
- console.error('Failed to handle image event', e);
383
- throw e;
384
- }
385
- });
386
363
  queue.on('event', (screenData) => {
387
364
  sendEvent(res, screenData);
388
365
  });
@@ -520,9 +497,13 @@ async function handleAll(req, res) {
520
497
  onRequestAborted(req, res, () => {
521
498
  metaStream.abort();
522
499
  });
523
- res.set('Content-Type', 'application/x-ndjson');
524
- res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
525
- res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
500
+ // We check if the headers have been sent, because we might have already sent some data
501
+ // before this function is called
502
+ if (!res.headersSent) {
503
+ res.set('Content-Type', 'application/x-ndjson');
504
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
505
+ res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
506
+ }
526
507
  let currentPhase = events_1.StormEventPhaseType.META;
527
508
  // Helper to avoid sending the plan multiple times in a row
528
509
  const sendUpdatedPlan = lodash_1.default.debounce(sendDefinitions, 50, { maxWait: 200 });
@@ -722,12 +703,4 @@ async function sendPageEvent(mainConversationId, data, res) {
722
703
  }
723
704
  sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
724
705
  }
725
- async function handleImageEvent(mainConversationId, data, prompt) {
726
- try {
727
- await (0, page_utils_1.writeImageToDisk)(mainConversationId, data, prompt);
728
- }
729
- catch (err) {
730
- console.error('Failed to write image to disk', err);
731
- }
732
- }
733
706
  exports.default = router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.70.11",
3
+ "version": "0.71.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -9,7 +9,7 @@ import { ReferenceClassification, StormEvent, StormEventPage, StormImage, UIShel
9
9
  import { EventEmitter } from 'node:events';
10
10
  import PQueue from 'p-queue';
11
11
 
12
- import { hasPageOnDisk, normalizePath } from './page-utils';
12
+ import { hasPageOnDisk, normalizePath, writeImageToDisk } from './page-utils';
13
13
  import * as mimetypes from 'mime-types';
14
14
 
15
15
  export interface ImagePrompt {
@@ -270,14 +270,20 @@ export class PageQueue extends EventEmitter {
270
270
  `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim()
271
271
  );
272
272
 
273
- //console.log('Adding image prompt', prompt);
273
+ let imageEvent: StormImage | null = null;
274
274
  result.on('data', (event: StormEvent) => {
275
275
  if (event.type === 'IMAGE') {
276
- this.emit('image', event, prompt);
276
+ imageEvent = event;
277
277
  }
278
278
  });
279
279
 
280
280
  await result.waitForDone();
281
+ if (!imageEvent) {
282
+ throw new Error('No image was generated');
283
+ }
284
+
285
+ await writeImageToDisk(this.systemId, imageEvent, prompt);
286
+ this.emit('image', imageEvent, prompt);
281
287
  }
282
288
 
283
289
  public async generate(prompt: UIPagePrompt, conversationId: string) {
@@ -329,6 +329,8 @@ export interface StormEventDefinitionChange {
329
329
  }
330
330
 
331
331
  export enum StormEventPhaseType {
332
+ IMPLEMENT_APIS = 'IMPLEMENT_APIS', // Implement APIs in the html pages
333
+ COMPOSE_SYSTEM_PROMPT = 'COMPOSE_SYSTEM_PROMPT', // Compose system prompt for bottom-up approach
332
334
  META = 'META',
333
335
  DEFINITIONS = 'DEFINITIONS',
334
336
  IMPLEMENTATION = 'IMPLEMENTATION',
@@ -41,7 +41,6 @@ import {
41
41
  resolveReadPath,
42
42
  SystemIdHeader,
43
43
  writeAssetToDisk,
44
- writeImageToDisk,
45
44
  writePageToDisk,
46
45
  } from './page-utils';
47
46
  import { UIServer } from './UIServer';
@@ -106,6 +105,12 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
106
105
  const srcDir = getSystemBaseDir(systemId);
107
106
  const destDir = getSystemBaseImplDir(systemId);
108
107
 
108
+ res.set('Content-Type', 'application/x-ndjson');
109
+ res.set('Access-Control-Expose-Headers', ConversationIdHeader);
110
+ res.set(ConversationIdHeader, systemId);
111
+
112
+ sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
113
+
109
114
  await copyDirectory(srcDir, destDir, async (fileName, content) => {
110
115
  const result = await stormClient.implementAPIClients({
111
116
  content: content,
@@ -114,9 +119,15 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
114
119
  return result;
115
120
  });
116
121
 
122
+ sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
123
+
124
+ sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
125
+
117
126
  const pages = readPages(destDir);
118
127
  const prompt = await stormClient.generatePrompt(pages);
119
128
 
129
+ sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
130
+
120
131
  req.query.systemId = systemId;
121
132
  const promptRequest: BasePromptRequest = {
122
133
  prompt: prompt,
@@ -165,18 +176,6 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
165
176
 
166
177
  queue.on('page', (data) => (systemId ? sendPageEvent(systemId, data, res) : undefined));
167
178
 
168
- queue.on('image', async (screenData, prompt) => {
169
- if (!systemId) {
170
- return;
171
- }
172
- try {
173
- await handleImageEvent(systemId, screenData, prompt);
174
- } catch (e) {
175
- console.error('Failed to handle image event', e);
176
- throw e;
177
- }
178
- });
179
-
180
179
  queue.on('error', (err) => {
181
180
  console.error('Failed to process page', err);
182
181
  sendError(err as any, res);
@@ -275,15 +274,6 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
275
274
  sendPageEvent(landingPagesStream.getConversationId(), screenData, res)
276
275
  );
277
276
 
278
- pageQueue.on('image', async (screenData, prompt) => {
279
- try {
280
- await handleImageEvent(landingPagesStream.getConversationId(), screenData, prompt);
281
- } catch (e) {
282
- console.error('Failed to handle image event', e);
283
- throw e;
284
- }
285
- });
286
-
287
277
  pageQueue.on('event', (screenData: StormEvent) => {
288
278
  sendEvent(res, screenData);
289
279
  });
@@ -470,15 +460,6 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
470
460
 
471
461
  queue.on('page', (screenData: StormEventPage) => sendPageEvent(outerConversationId, screenData, res));
472
462
 
473
- queue.on('image', async (screenData, prompt) => {
474
- try {
475
- await handleImageEvent(outerConversationId, screenData, prompt);
476
- } catch (e) {
477
- console.error('Failed to handle image event', e);
478
- throw e;
479
- }
480
- });
481
-
482
463
  queue.on('event', (screenData: StormEvent) => {
483
464
  sendEvent(res, screenData);
484
465
  });
@@ -643,9 +624,13 @@ async function handleAll(req: KapetaBodyRequest, res: Response) {
643
624
  metaStream.abort();
644
625
  });
645
626
 
646
- res.set('Content-Type', 'application/x-ndjson');
647
- res.set('Access-Control-Expose-Headers', ConversationIdHeader);
648
- res.set(ConversationIdHeader, metaStream.getConversationId());
627
+ // We check if the headers have been sent, because we might have already sent some data
628
+ // before this function is called
629
+ if (!res.headersSent) {
630
+ res.set('Content-Type', 'application/x-ndjson');
631
+ res.set('Access-Control-Expose-Headers', ConversationIdHeader);
632
+ res.set(ConversationIdHeader, metaStream.getConversationId());
633
+ }
649
634
 
650
635
  let currentPhase = StormEventPhaseType.META;
651
636
 
@@ -883,12 +868,4 @@ async function sendPageEvent(mainConversationId: string, data: StormEventPage, r
883
868
  sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
884
869
  }
885
870
 
886
- async function handleImageEvent(mainConversationId: string, data: StormImage, prompt: ImagePrompt) {
887
- try {
888
- await writeImageToDisk(mainConversationId, data, prompt);
889
- } catch (err) {
890
- console.error('Failed to write image to disk', err);
891
- }
892
- }
893
-
894
871
  export default router;