@kapeta/local-cluster-service 0.70.6 → 0.70.8

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,18 @@
1
+ ## [0.70.8](https://github.com/kapetacom/local-cluster-service/compare/v0.70.7...v0.70.8) (2024-09-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * use language-target-html if pre-existing systemId is specified ([0a38e0c](https://github.com/kapetacom/local-cluster-service/commit/0a38e0cb384a0954269c1b00ee1ef6660f979e42))
7
+
8
+ ## [0.70.7](https://github.com/kapetacom/local-cluster-service/compare/v0.70.6...v0.70.7) (2024-09-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add missing error handlers to pagegen ([56a3ab5](https://github.com/kapetacom/local-cluster-service/commit/56a3ab576f011ed29ce8074c53e025b347fb4e8a))
14
+ * skip unknown mimetypes in image gen ([3076367](https://github.com/kapetacom/local-cluster-service/commit/307636771c46a99f555c4c3e944c61fcffa16adc))
15
+
1
16
  ## [0.70.6](https://github.com/kapetacom/local-cluster-service/compare/v0.70.5...v0.70.6) (2024-09-11)
2
17
 
3
18
 
@@ -3,6 +3,29 @@
3
3
  * Copyright 2023 Kapeta Inc.
4
4
  * SPDX-License-Identifier: BUSL-1.1
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
6
29
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
31
  };
@@ -13,6 +36,7 @@ const stormClient_1 = require("./stormClient");
13
36
  const node_events_1 = require("node:events");
14
37
  const p_queue_1 = __importDefault(require("p-queue"));
15
38
  const page_utils_1 = require("./page-utils");
39
+ const mimetypes = __importStar(require("mime-types"));
16
40
  class PageQueue extends node_events_1.EventEmitter {
17
41
  queue;
18
42
  eventQueue;
@@ -208,6 +232,14 @@ class PageQueue extends node_events_1.EventEmitter {
208
232
  //console.log('Ignoring duplicate image prompt', prompt);
209
233
  return;
210
234
  }
235
+ // Add safeguard to avoid generating images for nonsense URLs
236
+ // Sometimes we get entries for Base URLs that will then cause issues on the filesystem
237
+ // Example: https://www.kapeta.com/images/
238
+ const mimeType = mimetypes.lookup(prompt.url);
239
+ if (!mimeType || !mimeType.startsWith('image/')) {
240
+ console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
241
+ return;
242
+ }
211
243
  this.images.set(prompt.url, prompt.description);
212
244
  const prefix = this.getPrefix();
213
245
  const result = await stormClient_1.stormClient.createImage(prefix + `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
@@ -12,7 +12,8 @@ export declare class StormCodegen {
12
12
  private readonly events;
13
13
  private tmpDir;
14
14
  private readonly conversationId;
15
- constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
15
+ private readonly uiSystemId?;
16
+ constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[], uiSystemId?: string);
16
17
  setTmpDir(tmpDir: string): void;
17
18
  process(): Promise<void>;
18
19
  isAborted(): boolean;
@@ -47,6 +47,7 @@ const yaml_1 = __importDefault(require("yaml"));
47
47
  const predefined_1 = require("./predefined");
48
48
  const archetype_1 = require("./archetype");
49
49
  const lodash_1 = __importDefault(require("lodash"));
50
+ const page_utils_1 = require("./page-utils");
50
51
  const SIMULATED_DELAY = 1000;
51
52
  const ENABLE_SIMULATED_DELAY = false;
52
53
  class SimulatedFileDelay {
@@ -100,12 +101,14 @@ class StormCodegen {
100
101
  events;
101
102
  tmpDir;
102
103
  conversationId;
103
- constructor(conversationId, userPrompt, blocks, events) {
104
+ uiSystemId;
105
+ constructor(conversationId, userPrompt, blocks, events, uiSystemId) {
104
106
  this.userPrompt = userPrompt;
105
107
  this.blocks = blocks;
106
108
  this.events = events;
107
109
  this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
108
110
  this.conversationId = conversationId;
111
+ this.uiSystemId = uiSystemId;
109
112
  }
110
113
  setTmpDir(tmpDir) {
111
114
  this.tmpDir = tmpDir;
@@ -794,6 +797,9 @@ class StormCodegen {
794
797
  const basePath = this.getBasePath(yamlContent.metadata.name);
795
798
  const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
796
799
  codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
800
+ if (this.uiSystemId) {
801
+ codeGenerator.withOption('AIStaticFiles', (0, page_utils_1.getSystemBaseDir)(this.uiSystemId));
802
+ }
797
803
  const generatedResult = await codeGenerator.generate();
798
804
  new codegen_1.CodeWriter(basePath).write(generatedResult);
799
805
  return generatedResult;
@@ -26,6 +26,7 @@ export interface StormOptions {
26
26
  serviceLanguage: string;
27
27
  frontendKind: string;
28
28
  frontendLanguage: string;
29
+ htmlLanguage: string;
29
30
  exchangeKind: string;
30
31
  queueKind: string;
31
32
  publisherKind: string;
@@ -39,7 +40,8 @@ export interface StormOptions {
39
40
  desktopKind: string;
40
41
  desktopLanguage: string;
41
42
  gatewayKind: string;
42
- [key: string]: string;
43
+ systemId?: string;
44
+ [key: string]: string | undefined;
43
45
  }
44
46
  export declare function createPhaseStartEvent(type: StormEventPhaseType): StormEventPhases;
45
47
  export declare function createPhaseEndEvent(type: StormEventPhaseType): StormEventPhases;
@@ -67,6 +67,7 @@ async function resolveOptions() {
67
67
  const javaLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
68
68
  const reactLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
69
69
  const nodejsLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
70
+ const htmlLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-html');
70
71
  const blockTypePubsub = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-pubsub');
71
72
  const resourceTypePubsubSubscriber = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-pubsub-subscriber');
72
73
  const resourceTypePubsubSubscription = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-pubsub-subscription');
@@ -83,6 +84,7 @@ async function resolveOptions() {
83
84
  !postgresResource ||
84
85
  !javaLanguage ||
85
86
  !reactLanguage ||
87
+ !htmlLanguage ||
86
88
  !webPageResource ||
87
89
  !restApiResource ||
88
90
  !restClientResource ||
@@ -105,6 +107,7 @@ async function resolveOptions() {
105
107
  serviceLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${javaLanguage.definition.metadata.name}:${javaLanguage.version}`),
106
108
  frontendKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
107
109
  frontendLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${reactLanguage.definition.metadata.name}:${reactLanguage.version}`),
110
+ htmlLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${htmlLanguage.definition.metadata.name}:${htmlLanguage.version}`),
108
111
  cliKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeCli.definition.metadata.name}:${blockTypeCli.version}`),
109
112
  cliLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${nodejsLanguage.definition.metadata.name}:${nodejsLanguage.version}`),
110
113
  desktopKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeDesktop.definition.metadata.name}:${blockTypeDesktop.version}`),
@@ -770,7 +773,7 @@ class StormEventParser {
770
773
  case 'CLI':
771
774
  return this.options.cliLanguage;
772
775
  case 'FRONTEND':
773
- return this.options.frontendLanguage;
776
+ return this.options.systemId ? this.options.htmlLanguage : this.options.frontendLanguage;
774
777
  case 'DESKTOP':
775
778
  return this.options.desktopLanguage;
776
779
  }
@@ -791,7 +794,7 @@ class StormEventParser {
791
794
  for (const prop in options) {
792
795
  if (options.hasOwnProperty(prop)) {
793
796
  const value = options[prop];
794
- if (value.indexOf(kind) > 0) {
797
+ if (typeof value === 'string' && value.indexOf(kind) > 0) {
795
798
  return value;
796
799
  }
797
800
  }
@@ -380,6 +380,10 @@ router.post('/:handle/ui', async (req, res) => {
380
380
  queue.on('event', (screenData) => {
381
381
  sendEvent(res, screenData);
382
382
  });
383
+ queue.on('error', (err) => {
384
+ console.error('Failed to process page', err);
385
+ sendError(err, res);
386
+ });
383
387
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
384
388
  queue
385
389
  .addPrompt({
@@ -435,6 +439,10 @@ router.post('/ui/edit', async (req, res) => {
435
439
  sendEvent(res, data);
436
440
  }
437
441
  });
442
+ queue.on('error', (err) => {
443
+ console.error('Failed to process page', err);
444
+ sendError(err, res);
445
+ });
438
446
  const pages = aiRequest.prompt.pages.filter((page) => page.conversationId);
439
447
  if (pages.length === 0) {
440
448
  console.log('No pages to update', aiRequest.prompt.pages);
@@ -493,8 +501,9 @@ router.post('/ui/get-vote', async (req, res) => {
493
501
  });
494
502
  router.post('/:handle/all', async (req, res) => {
495
503
  const handle = req.params.handle;
504
+ const systemId = req.query.systemId ?? undefined;
496
505
  try {
497
- const stormOptions = await (0, event_parser_1.resolveOptions)();
506
+ const stormOptions = { ...(await (0, event_parser_1.resolveOptions)()), systemId: systemId };
498
507
  const eventParser = new event_parser_1.StormEventParser(stormOptions);
499
508
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
500
509
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
@@ -563,7 +572,7 @@ router.post('/:handle/all', async (req, res) => {
563
572
  if (!req.query.skipCodegen) {
564
573
  try {
565
574
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
566
- const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
575
+ const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents(), systemId);
567
576
  onRequestAborted(req, res, () => {
568
577
  stormCodegen.abort();
569
578
  });
@@ -7,6 +7,7 @@ export declare const parserOptions: {
7
7
  serviceLanguage: string;
8
8
  frontendKind: string;
9
9
  frontendLanguage: string;
10
+ htmlLanguage: string;
10
11
  cliKind: string;
11
12
  cliLanguage: string;
12
13
  desktopKind: string;
@@ -19,6 +19,7 @@ exports.parserOptions = {
19
19
  serviceLanguage: 'kapeta/language-target-java-spring-boot:local',
20
20
  frontendKind: 'kapeta/block-type-frontend:local',
21
21
  frontendLanguage: 'kapeta/language-target-react-ts:local',
22
+ htmlLanguage: 'kapeta/language-target-html:local',
22
23
  cliKind: 'kapeta/block-type-cli:local',
23
24
  cliLanguage: 'kapeta/language-target-nodejs-ts:local',
24
25
  desktopKind: 'kapeta/block-type-desktop:local',
@@ -3,6 +3,29 @@
3
3
  * Copyright 2023 Kapeta Inc.
4
4
  * SPDX-License-Identifier: BUSL-1.1
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
6
29
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
31
  };
@@ -13,6 +36,7 @@ const stormClient_1 = require("./stormClient");
13
36
  const node_events_1 = require("node:events");
14
37
  const p_queue_1 = __importDefault(require("p-queue"));
15
38
  const page_utils_1 = require("./page-utils");
39
+ const mimetypes = __importStar(require("mime-types"));
16
40
  class PageQueue extends node_events_1.EventEmitter {
17
41
  queue;
18
42
  eventQueue;
@@ -208,6 +232,14 @@ class PageQueue extends node_events_1.EventEmitter {
208
232
  //console.log('Ignoring duplicate image prompt', prompt);
209
233
  return;
210
234
  }
235
+ // Add safeguard to avoid generating images for nonsense URLs
236
+ // Sometimes we get entries for Base URLs that will then cause issues on the filesystem
237
+ // Example: https://www.kapeta.com/images/
238
+ const mimeType = mimetypes.lookup(prompt.url);
239
+ if (!mimeType || !mimeType.startsWith('image/')) {
240
+ console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
241
+ return;
242
+ }
211
243
  this.images.set(prompt.url, prompt.description);
212
244
  const prefix = this.getPrefix();
213
245
  const result = await stormClient_1.stormClient.createImage(prefix + `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
@@ -12,7 +12,8 @@ export declare class StormCodegen {
12
12
  private readonly events;
13
13
  private tmpDir;
14
14
  private readonly conversationId;
15
- constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
15
+ private readonly uiSystemId?;
16
+ constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[], uiSystemId?: string);
16
17
  setTmpDir(tmpDir: string): void;
17
18
  process(): Promise<void>;
18
19
  isAborted(): boolean;
@@ -47,6 +47,7 @@ const yaml_1 = __importDefault(require("yaml"));
47
47
  const predefined_1 = require("./predefined");
48
48
  const archetype_1 = require("./archetype");
49
49
  const lodash_1 = __importDefault(require("lodash"));
50
+ const page_utils_1 = require("./page-utils");
50
51
  const SIMULATED_DELAY = 1000;
51
52
  const ENABLE_SIMULATED_DELAY = false;
52
53
  class SimulatedFileDelay {
@@ -100,12 +101,14 @@ class StormCodegen {
100
101
  events;
101
102
  tmpDir;
102
103
  conversationId;
103
- constructor(conversationId, userPrompt, blocks, events) {
104
+ uiSystemId;
105
+ constructor(conversationId, userPrompt, blocks, events, uiSystemId) {
104
106
  this.userPrompt = userPrompt;
105
107
  this.blocks = blocks;
106
108
  this.events = events;
107
109
  this.tmpDir = path_2.default.join(node_os_1.default.tmpdir(), conversationId);
108
110
  this.conversationId = conversationId;
111
+ this.uiSystemId = uiSystemId;
109
112
  }
110
113
  setTmpDir(tmpDir) {
111
114
  this.tmpDir = tmpDir;
@@ -794,6 +797,9 @@ class StormCodegen {
794
797
  const basePath = this.getBasePath(yamlContent.metadata.name);
795
798
  const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
796
799
  codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
800
+ if (this.uiSystemId) {
801
+ codeGenerator.withOption('AIStaticFiles', (0, page_utils_1.getSystemBaseDir)(this.uiSystemId));
802
+ }
797
803
  const generatedResult = await codeGenerator.generate();
798
804
  new codegen_1.CodeWriter(basePath).write(generatedResult);
799
805
  return generatedResult;
@@ -26,6 +26,7 @@ export interface StormOptions {
26
26
  serviceLanguage: string;
27
27
  frontendKind: string;
28
28
  frontendLanguage: string;
29
+ htmlLanguage: string;
29
30
  exchangeKind: string;
30
31
  queueKind: string;
31
32
  publisherKind: string;
@@ -39,7 +40,8 @@ export interface StormOptions {
39
40
  desktopKind: string;
40
41
  desktopLanguage: string;
41
42
  gatewayKind: string;
42
- [key: string]: string;
43
+ systemId?: string;
44
+ [key: string]: string | undefined;
43
45
  }
44
46
  export declare function createPhaseStartEvent(type: StormEventPhaseType): StormEventPhases;
45
47
  export declare function createPhaseEndEvent(type: StormEventPhaseType): StormEventPhases;
@@ -67,6 +67,7 @@ async function resolveOptions() {
67
67
  const javaLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
68
68
  const reactLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
69
69
  const nodejsLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
70
+ const htmlLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-html');
70
71
  const blockTypePubsub = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-pubsub');
71
72
  const resourceTypePubsubSubscriber = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-pubsub-subscriber');
72
73
  const resourceTypePubsubSubscription = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-pubsub-subscription');
@@ -83,6 +84,7 @@ async function resolveOptions() {
83
84
  !postgresResource ||
84
85
  !javaLanguage ||
85
86
  !reactLanguage ||
87
+ !htmlLanguage ||
86
88
  !webPageResource ||
87
89
  !restApiResource ||
88
90
  !restClientResource ||
@@ -105,6 +107,7 @@ async function resolveOptions() {
105
107
  serviceLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${javaLanguage.definition.metadata.name}:${javaLanguage.version}`),
106
108
  frontendKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
107
109
  frontendLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${reactLanguage.definition.metadata.name}:${reactLanguage.version}`),
110
+ htmlLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${htmlLanguage.definition.metadata.name}:${htmlLanguage.version}`),
108
111
  cliKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeCli.definition.metadata.name}:${blockTypeCli.version}`),
109
112
  cliLanguage: (0, nodejs_utils_1.normalizeKapetaUri)(`${nodejsLanguage.definition.metadata.name}:${nodejsLanguage.version}`),
110
113
  desktopKind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeDesktop.definition.metadata.name}:${blockTypeDesktop.version}`),
@@ -770,7 +773,7 @@ class StormEventParser {
770
773
  case 'CLI':
771
774
  return this.options.cliLanguage;
772
775
  case 'FRONTEND':
773
- return this.options.frontendLanguage;
776
+ return this.options.systemId ? this.options.htmlLanguage : this.options.frontendLanguage;
774
777
  case 'DESKTOP':
775
778
  return this.options.desktopLanguage;
776
779
  }
@@ -791,7 +794,7 @@ class StormEventParser {
791
794
  for (const prop in options) {
792
795
  if (options.hasOwnProperty(prop)) {
793
796
  const value = options[prop];
794
- if (value.indexOf(kind) > 0) {
797
+ if (typeof value === 'string' && value.indexOf(kind) > 0) {
795
798
  return value;
796
799
  }
797
800
  }
@@ -380,6 +380,10 @@ router.post('/:handle/ui', async (req, res) => {
380
380
  queue.on('event', (screenData) => {
381
381
  sendEvent(res, screenData);
382
382
  });
383
+ queue.on('error', (err) => {
384
+ console.error('Failed to process page', err);
385
+ sendError(err, res);
386
+ });
383
387
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
384
388
  queue
385
389
  .addPrompt({
@@ -435,6 +439,10 @@ router.post('/ui/edit', async (req, res) => {
435
439
  sendEvent(res, data);
436
440
  }
437
441
  });
442
+ queue.on('error', (err) => {
443
+ console.error('Failed to process page', err);
444
+ sendError(err, res);
445
+ });
438
446
  const pages = aiRequest.prompt.pages.filter((page) => page.conversationId);
439
447
  if (pages.length === 0) {
440
448
  console.log('No pages to update', aiRequest.prompt.pages);
@@ -493,8 +501,9 @@ router.post('/ui/get-vote', async (req, res) => {
493
501
  });
494
502
  router.post('/:handle/all', async (req, res) => {
495
503
  const handle = req.params.handle;
504
+ const systemId = req.query.systemId ?? undefined;
496
505
  try {
497
- const stormOptions = await (0, event_parser_1.resolveOptions)();
506
+ const stormOptions = { ...(await (0, event_parser_1.resolveOptions)()), systemId: systemId };
498
507
  const eventParser = new event_parser_1.StormEventParser(stormOptions);
499
508
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
500
509
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
@@ -563,7 +572,7 @@ router.post('/:handle/all', async (req, res) => {
563
572
  if (!req.query.skipCodegen) {
564
573
  try {
565
574
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENTATION));
566
- const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents());
575
+ const stormCodegen = new codegen_1.StormCodegen(metaStream.getConversationId(), aiRequest.prompt, result.blocks, eventParser.getEvents(), systemId);
567
576
  onRequestAborted(req, res, () => {
568
577
  stormCodegen.abort();
569
578
  });
@@ -7,6 +7,7 @@ export declare const parserOptions: {
7
7
  serviceLanguage: string;
8
8
  frontendKind: string;
9
9
  frontendLanguage: string;
10
+ htmlLanguage: string;
10
11
  cliKind: string;
11
12
  cliLanguage: string;
12
13
  desktopKind: string;
@@ -19,6 +19,7 @@ exports.parserOptions = {
19
19
  serviceLanguage: 'kapeta/language-target-java-spring-boot:local',
20
20
  frontendKind: 'kapeta/block-type-frontend:local',
21
21
  frontendLanguage: 'kapeta/language-target-react-ts:local',
22
+ htmlLanguage: 'kapeta/language-target-html:local',
22
23
  cliKind: 'kapeta/block-type-cli:local',
23
24
  cliLanguage: 'kapeta/language-target-nodejs-ts:local',
24
25
  desktopKind: 'kapeta/block-type-desktop:local',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.70.6",
3
+ "version": "0.70.8",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -76,6 +76,7 @@
76
76
  "js-yaml": "^4.1.0",
77
77
  "lodash": "^4.17.15",
78
78
  "md5": "2.2.1",
79
+ "mime-types": "^2.1.35",
79
80
  "node-cache": "^5.1.2",
80
81
  "node-fetch": "^3.3.2",
81
82
  "node-uuid": "^1.4.8",
@@ -104,6 +105,7 @@
104
105
  "@types/js-yaml": "^4.0.9",
105
106
  "@types/lodash": "^4.14.195",
106
107
  "@types/md5": "^2.3.2",
108
+ "@types/mime-types": "^2.1.4",
107
109
  "@types/node": "^20.5.8",
108
110
  "@types/node-fetch": "^2.6.11",
109
111
  "@types/node-uuid": "^0.0.29",
@@ -10,6 +10,7 @@ import { EventEmitter } from 'node:events';
10
10
  import PQueue from 'p-queue';
11
11
 
12
12
  import { hasPageOnDisk, normalizePath } from './page-utils';
13
+ import * as mimetypes from 'mime-types';
13
14
 
14
15
  export interface ImagePrompt {
15
16
  name: string;
@@ -255,6 +256,15 @@ export class PageQueue extends EventEmitter {
255
256
  //console.log('Ignoring duplicate image prompt', prompt);
256
257
  return;
257
258
  }
259
+ // Add safeguard to avoid generating images for nonsense URLs
260
+ // Sometimes we get entries for Base URLs that will then cause issues on the filesystem
261
+ // Example: https://www.kapeta.com/images/
262
+ const mimeType = mimetypes.lookup(prompt.url) as string | false;
263
+ if (!mimeType || !mimeType.startsWith('image/')) {
264
+ console.warn('Skipping image reference of type %s for url %s', mimeType, prompt.url);
265
+ return;
266
+ }
267
+
258
268
  this.images.set(prompt.url, prompt.description);
259
269
  const prefix = this.getPrefix();
260
270
  const result = await stormClient.createImage(
@@ -39,6 +39,7 @@ import YAML from 'yaml';
39
39
  import { PREDEFINED_BLOCKS } from './predefined';
40
40
  import { Archetype } from './archetype';
41
41
  import _ from 'lodash';
42
+ import { getSystemBaseDir } from './page-utils';
42
43
 
43
44
  type ImplementationGenerator<T = StormFileImplementationPrompt> = (
44
45
  prompt: T,
@@ -108,13 +109,21 @@ export class StormCodegen {
108
109
  private readonly events: StormEvent[];
109
110
  private tmpDir: string;
110
111
  private readonly conversationId: string;
111
-
112
- constructor(conversationId: string, userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
112
+ private readonly uiSystemId?: string;
113
+
114
+ constructor(
115
+ conversationId: string,
116
+ userPrompt: string,
117
+ blocks: BlockDefinitionInfo[],
118
+ events: StormEvent[],
119
+ uiSystemId?: string
120
+ ) {
113
121
  this.userPrompt = userPrompt;
114
122
  this.blocks = blocks;
115
123
  this.events = events;
116
124
  this.tmpDir = Path.join(os.tmpdir(), conversationId);
117
125
  this.conversationId = conversationId;
126
+ this.uiSystemId = uiSystemId;
118
127
  }
119
128
 
120
129
  public setTmpDir(tmpDir: string) {
@@ -488,7 +497,6 @@ export class StormCodegen {
488
497
  await writeFile(filePath, webRouterFile.content);
489
498
  }
490
499
 
491
-
492
500
  const blockRef = block.uri;
493
501
 
494
502
  this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
@@ -1009,6 +1017,11 @@ export class StormCodegen {
1009
1017
 
1010
1018
  const codeGenerator = new BlockCodeGenerator(yamlContent as BlockDefinition);
1011
1019
  codeGenerator.withOption('AIContext', STORM_ID);
1020
+
1021
+ if (this.uiSystemId) {
1022
+ codeGenerator.withOption('AIStaticFiles', getSystemBaseDir(this.uiSystemId));
1023
+ }
1024
+
1012
1025
  const generatedResult = await codeGenerator.generate();
1013
1026
  new CodeWriter(basePath).write(generatedResult);
1014
1027
  return generatedResult;
@@ -67,6 +67,7 @@ export interface StormOptions {
67
67
  serviceLanguage: string;
68
68
  frontendKind: string;
69
69
  frontendLanguage: string;
70
+ htmlLanguage: string;
70
71
  exchangeKind: string;
71
72
  queueKind: string;
72
73
  publisherKind: string;
@@ -80,7 +81,10 @@ export interface StormOptions {
80
81
  desktopKind: string;
81
82
  desktopLanguage: string;
82
83
  gatewayKind: string;
83
- [key: string]: string;
84
+
85
+ systemId?: string;
86
+
87
+ [key: string]: string | undefined;
84
88
  }
85
89
 
86
90
  function prettifyKaplang(source: string) {
@@ -143,6 +147,7 @@ export async function resolveOptions(): Promise<StormOptions> {
143
147
  const javaLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
144
148
  const reactLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
145
149
  const nodejsLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
150
+ const htmlLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-html');
146
151
 
147
152
  const blockTypePubsub = await definitionsManager.getLatestDefinition('kapeta/block-type-pubsub');
148
153
  const resourceTypePubsubSubscriber = await definitionsManager.getLatestDefinition(
@@ -170,6 +175,7 @@ export async function resolveOptions(): Promise<StormOptions> {
170
175
  !postgresResource ||
171
176
  !javaLanguage ||
172
177
  !reactLanguage ||
178
+ !htmlLanguage ||
173
179
  !webPageResource ||
174
180
  !restApiResource ||
175
181
  !restClientResource ||
@@ -195,6 +201,7 @@ export async function resolveOptions(): Promise<StormOptions> {
195
201
 
196
202
  frontendKind: normalizeKapetaUri(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
197
203
  frontendLanguage: normalizeKapetaUri(`${reactLanguage.definition.metadata.name}:${reactLanguage.version}`),
204
+ htmlLanguage: normalizeKapetaUri(`${htmlLanguage.definition.metadata.name}:${htmlLanguage.version}`),
198
205
 
199
206
  cliKind: normalizeKapetaUri(`${blockTypeCli.definition.metadata.name}:${blockTypeCli.version}`),
200
207
  cliLanguage: normalizeKapetaUri(`${nodejsLanguage.definition.metadata.name}:${nodejsLanguage.version}`),
@@ -997,7 +1004,7 @@ export class StormEventParser {
997
1004
  case 'CLI':
998
1005
  return this.options.cliLanguage;
999
1006
  case 'FRONTEND':
1000
- return this.options.frontendLanguage;
1007
+ return this.options.systemId ? this.options.htmlLanguage : this.options.frontendLanguage;
1001
1008
  case 'DESKTOP':
1002
1009
  return this.options.desktopLanguage;
1003
1010
  }
@@ -1027,7 +1034,7 @@ export class StormEventParser {
1027
1034
  for (const prop in options) {
1028
1035
  if (options.hasOwnProperty(prop)) {
1029
1036
  const value = options[prop];
1030
- if (value.indexOf(kind) > 0) {
1037
+ if (typeof value === 'string' && value.indexOf(kind) > 0) {
1031
1038
  return value;
1032
1039
  }
1033
1040
  }
@@ -474,6 +474,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
474
474
  sendEvent(res, screenData);
475
475
  });
476
476
 
477
+ queue.on('error', (err) => {
478
+ console.error('Failed to process page', err);
479
+ sendError(err as any, res);
480
+ });
481
+
477
482
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
478
483
  queue
479
484
  .addPrompt({
@@ -538,6 +543,11 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
538
543
  }
539
544
  });
540
545
 
546
+ queue.on('error', (err) => {
547
+ console.error('Failed to process page', err);
548
+ sendError(err as any, res);
549
+ });
550
+
541
551
  const pages = aiRequest.prompt.pages.filter((page) => page.conversationId);
542
552
  if (pages.length === 0) {
543
553
  console.log('No pages to update', aiRequest.prompt.pages);
@@ -604,9 +614,10 @@ router.post('/ui/get-vote', async (req: KapetaBodyRequest, res: Response) => {
604
614
 
605
615
  router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
606
616
  const handle = req.params.handle as string;
617
+ const systemId = (req.query.systemId as string) ?? undefined;
607
618
 
608
619
  try {
609
- const stormOptions = await resolveOptions();
620
+ const stormOptions = { ...(await resolveOptions()), systemId: systemId };
610
621
 
611
622
  const eventParser = new StormEventParser(stormOptions);
612
623
 
@@ -693,7 +704,8 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
693
704
  metaStream.getConversationId(),
694
705
  aiRequest.prompt,
695
706
  result.blocks,
696
- eventParser.getEvents()
707
+ eventParser.getEvents(),
708
+ systemId
697
709
  );
698
710
 
699
711
  onRequestAborted(req, res, () => {
@@ -17,6 +17,7 @@ export const parserOptions = {
17
17
 
18
18
  frontendKind: 'kapeta/block-type-frontend:local',
19
19
  frontendLanguage: 'kapeta/language-target-react-ts:local',
20
+ htmlLanguage: 'kapeta/language-target-html:local',
20
21
 
21
22
  cliKind: 'kapeta/block-type-cli:local',
22
23
  cliLanguage: 'kapeta/language-target-nodejs-ts:local',