@kapeta/local-cluster-service 0.71.4 → 0.71.6

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,19 @@
1
+ ## [0.71.6](https://github.com/kapetacom/local-cluster-service/compare/v0.71.5...v0.71.6) (2024-09-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Change to simple json response for now ([#256](https://github.com/kapetacom/local-cluster-service/issues/256)) ([7d8ae48](https://github.com/kapetacom/local-cluster-service/commit/7d8ae4813b78f478a0f719195ea33dc2890f1d2a))
7
+
8
+ ## [0.71.5](https://github.com/kapetacom/local-cluster-service/compare/v0.71.4...v0.71.5) (2024-09-19)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * adds create-system-simple route ([fea005a](https://github.com/kapetacom/local-cluster-service/commit/fea005a183b45a2caf6811ae6d0d7019386a21ef))
14
+ * review comments ([17f8192](https://github.com/kapetacom/local-cluster-service/commit/17f819237efff8bb57dfdb9b2f53431d5dc44021))
15
+ * send html + images etc. to endpoint ([bdb5e81](https://github.com/kapetacom/local-cluster-service/commit/bdb5e818d66fff6983f126d872fdf228e97bc5d1))
16
+
1
17
  ## [0.71.4](https://github.com/kapetacom/local-cluster-service/compare/v0.71.3...v0.71.4) (2024-09-19)
2
18
 
3
19
 
@@ -273,6 +273,7 @@ export interface StormEventDefinitionChange {
273
273
  export declare enum StormEventPhaseType {
274
274
  IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
275
275
  COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
276
+ COMPOSE_SYSTEM = "COMPOSE_SYSTEM",
276
277
  META = "META",
277
278
  DEFINITIONS = "DEFINITIONS",
278
279
  IMPLEMENTATION = "IMPLEMENTATION",
@@ -395,5 +396,13 @@ export interface StormEventUIStarted {
395
396
  resetUrl: string;
396
397
  };
397
398
  }
398
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage;
399
+ export interface StormEventSystemReady {
400
+ type: 'SYSTEM_READY';
401
+ reason: string;
402
+ created: number;
403
+ payload: {
404
+ systemUrl: string;
405
+ };
406
+ }
407
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady;
399
408
  export {};
@@ -13,6 +13,7 @@ var StormEventPhaseType;
13
13
  (function (StormEventPhaseType) {
14
14
  StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
15
15
  StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
16
+ StormEventPhaseType["COMPOSE_SYSTEM"] = "COMPOSE_SYSTEM";
16
17
  StormEventPhaseType["META"] = "META";
17
18
  StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
18
19
  StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
@@ -13,6 +13,7 @@ const path_1 = __importDefault(require("path"));
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  const cors_1 = require("../middleware/cors");
15
15
  const stringBody_1 = require("../middleware/stringBody");
16
+ const stream_1 = require("./stream");
16
17
  const stormClient_1 = require("./stormClient");
17
18
  const events_1 = require("./events");
18
19
  const event_parser_1 = require("./event-parser");
@@ -82,7 +83,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
82
83
  });
83
84
  await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
84
85
  // find the page from result1 and write the content to the file
85
- const page = pagesWithImplementation.find((p) => p.filename === fileName);
86
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
86
87
  return page ? page.content : content;
87
88
  });
88
89
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
@@ -101,6 +102,45 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
101
102
  req.stringBody = JSON.stringify(promptRequest);
102
103
  await handleAll(req, res);
103
104
  });
105
+ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
106
+ const handle = req.params.handle;
107
+ const systemId = req.params.systemId;
108
+ const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
109
+ //res.set('Content-Type', 'application/x-ndjson');
110
+ //res.set('Access-Control-Expose-Headers', ConversationIdHeader);
111
+ //res.set(ConversationIdHeader, systemId);
112
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
113
+ try {
114
+ const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
115
+ const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
116
+ pages: pagesFromDisk,
117
+ });
118
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
119
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
120
+ const allFiles = (0, utils_1.readFilesAndContent)(srcDir, false).map((page) => {
121
+ if (page.encoding == stream_1.HTMLPageEncoding.TEXT) {
122
+ const matchingFile = pagesWithImplementation.find((pageWithImpl) => pageWithImpl.fileName === page.fileName);
123
+ if (matchingFile) {
124
+ return matchingFile;
125
+ }
126
+ }
127
+ return page;
128
+ });
129
+ const systemUrl = await stormClient_1.stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
130
+ //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
131
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
132
+ //sendDone(res);
133
+ res.json({ url: systemUrl });
134
+ }
135
+ catch (err) {
136
+ res.status(500).json({ error: err.message });
137
+ }
138
+ finally {
139
+ if (!res.closed) {
140
+ res.end();
141
+ }
142
+ }
143
+ });
104
144
  router.delete('/ui/serve/:systemId', async (req, res) => {
105
145
  const systemId = req.params.systemId;
106
146
  if (!systemId) {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { ConversationItem, ImplementAPIClients, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
2
+ import { ConversationItem, CreateSimpleBackendRequest, HTMLPage, ImplementAPIClients, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
3
3
  import { Page, StormEventPageUrl } from './events';
4
4
  export declare const STORM_ID = "storm";
5
5
  export declare const ConversationIdHeader = "Conversation-Id";
@@ -70,8 +70,9 @@ declare class StormClient {
70
70
  getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
71
71
  vote: -1 | 0 | 1;
72
72
  }>;
73
- replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<Page[]>;
73
+ replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]>;
74
74
  generatePrompt(pages: string[]): Promise<string>;
75
+ createSimpleBackend(handle: string, systemId: string, input: CreateSimpleBackendRequest): Promise<string>;
75
76
  classifyUIReferences(prompt: string, conversationId?: string): Promise<StormStream>;
76
77
  editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
77
78
  listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
@@ -139,8 +139,7 @@ class StormClient {
139
139
  method: 'POST',
140
140
  body: JSON.stringify(prompt.pages),
141
141
  });
142
- const data = await response.json();
143
- return data;
142
+ return await response.json();
144
143
  }
145
144
  async generatePrompt(pages) {
146
145
  const u = `${this._baseUrl}/v2/ui/prompt`;
@@ -152,6 +151,19 @@ class StormClient {
152
151
  });
153
152
  return await response.text();
154
153
  }
154
+ async createSimpleBackend(handle, systemId, input) {
155
+ const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
156
+ const response = await fetch(u, {
157
+ method: 'POST',
158
+ body: JSON.stringify({
159
+ pages: input.pages,
160
+ }),
161
+ });
162
+ if (!response.ok) {
163
+ throw new Error(`HTTP error! Status: ${response.status}`);
164
+ }
165
+ return await response.text();
166
+ }
155
167
  classifyUIReferences(prompt, conversationId) {
156
168
  return this.send('/v2/ui/references', {
157
169
  prompt: prompt,
@@ -72,10 +72,18 @@ export interface StormUIListPrompt {
72
72
  blockName: string;
73
73
  prompt: string;
74
74
  }
75
+ export declare enum HTMLPageEncoding {
76
+ TEXT = "TEXT",
77
+ BINARY = "BINARY"
78
+ }
75
79
  export interface ImplementAPIClients {
76
- pages: Page[];
80
+ pages: HTMLPage[];
77
81
  }
78
- export interface Page {
82
+ export interface HTMLPage {
79
83
  fileName: string;
80
84
  content: string;
85
+ encoding: HTMLPageEncoding;
86
+ }
87
+ export interface CreateSimpleBackendRequest {
88
+ pages: HTMLPage[];
81
89
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StormStream = void 0;
3
+ exports.HTMLPageEncoding = exports.StormStream = void 0;
4
4
  /**
5
5
  * Copyright 2023 Kapeta Inc.
6
6
  * SPDX-License-Identifier: BUSL-1.1
@@ -74,3 +74,8 @@ class StormStream extends node_events_1.EventEmitter {
74
74
  }
75
75
  }
76
76
  exports.StormStream = StormStream;
77
+ var HTMLPageEncoding;
78
+ (function (HTMLPageEncoding) {
79
+ HTMLPageEncoding["TEXT"] = "TEXT";
80
+ HTMLPageEncoding["BINARY"] = "BINARY";
81
+ })(HTMLPageEncoding || (exports.HTMLPageEncoding = HTMLPageEncoding = {}));
@@ -1,6 +1,6 @@
1
- import { Page } from './stream';
1
+ import { HTMLPage } from './stream';
2
2
  export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => string): Promise<void>;
3
- export declare function readFilesAndContent(directoryPath: string): Page[];
3
+ export declare function readFilesAndContent(directoryPath: string, htmlExclusive?: boolean): HTMLPage[];
4
4
  export declare function createFuture<T = void>(): {
5
5
  promise: Promise<T>;
6
6
  resolve: (value: T | PromiseLike<T>) => void;
@@ -10,6 +10,7 @@ exports.createFuture = exports.readFilesAndContent = exports.copyDirectory = voi
10
10
  */
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
12
  const path_1 = __importDefault(require("path"));
13
+ const stream_1 = require("./stream");
13
14
  async function copyDirectory(src, dest, modifyHtml) {
14
15
  await fs_extra_1.default.promises.mkdir(dest, { recursive: true });
15
16
  const entries = await fs_extra_1.default.promises.readdir(src, { withFileTypes: true });
@@ -29,19 +30,28 @@ async function copyDirectory(src, dest, modifyHtml) {
29
30
  }
30
31
  }
31
32
  exports.copyDirectory = copyDirectory;
32
- function readFilesAndContent(directoryPath) {
33
+ function readFilesAndContent(directoryPath, htmlExclusive = true) {
33
34
  const htmlFiles = [];
34
35
  function traverseDirectory(currentPath) {
35
36
  const files = fs_extra_1.default.readdirSync(currentPath);
36
37
  for (const file of files) {
37
38
  const fileName = path_1.default.join(currentPath, file);
39
+ const relativeFilename = path_1.default.relative(directoryPath, fileName);
38
40
  const stats = fs_extra_1.default.statSync(fileName);
39
41
  if (stats.isDirectory()) {
40
42
  traverseDirectory(fileName);
41
43
  }
42
- else if (stats.isFile() && path_1.default.extname(fileName) === '.html') {
43
- const content = fs_extra_1.default.readFileSync(fileName, 'utf8');
44
- htmlFiles.push({ fileName, content });
44
+ else if (stats.isFile()) {
45
+ if (path_1.default.extname(fileName) === '.html') {
46
+ const content = fs_extra_1.default.readFileSync(fileName, 'utf8');
47
+ htmlFiles.push({ fileName: relativeFilename, content: content, encoding: stream_1.HTMLPageEncoding.TEXT });
48
+ }
49
+ else {
50
+ if (!htmlExclusive) {
51
+ const content = fs_extra_1.default.readFileSync(fileName);
52
+ htmlFiles.push({ fileName: relativeFilename, content: content.toString('base64'), encoding: stream_1.HTMLPageEncoding.BINARY });
53
+ }
54
+ }
45
55
  }
46
56
  }
47
57
  }
@@ -273,6 +273,7 @@ export interface StormEventDefinitionChange {
273
273
  export declare enum StormEventPhaseType {
274
274
  IMPLEMENT_APIS = "IMPLEMENT_APIS",// Implement APIs in the html pages
275
275
  COMPOSE_SYSTEM_PROMPT = "COMPOSE_SYSTEM_PROMPT",// Compose system prompt for bottom-up approach
276
+ COMPOSE_SYSTEM = "COMPOSE_SYSTEM",
276
277
  META = "META",
277
278
  DEFINITIONS = "DEFINITIONS",
278
279
  IMPLEMENTATION = "IMPLEMENTATION",
@@ -395,5 +396,13 @@ export interface StormEventUIStarted {
395
396
  resetUrl: string;
396
397
  };
397
398
  }
398
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage;
399
+ export interface StormEventSystemReady {
400
+ type: 'SYSTEM_READY';
401
+ reason: string;
402
+ created: number;
403
+ payload: {
404
+ systemUrl: string;
405
+ };
406
+ }
407
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady;
399
408
  export {};
@@ -13,6 +13,7 @@ var StormEventPhaseType;
13
13
  (function (StormEventPhaseType) {
14
14
  StormEventPhaseType["IMPLEMENT_APIS"] = "IMPLEMENT_APIS";
15
15
  StormEventPhaseType["COMPOSE_SYSTEM_PROMPT"] = "COMPOSE_SYSTEM_PROMPT";
16
+ StormEventPhaseType["COMPOSE_SYSTEM"] = "COMPOSE_SYSTEM";
16
17
  StormEventPhaseType["META"] = "META";
17
18
  StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
18
19
  StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
@@ -13,6 +13,7 @@ const path_1 = __importDefault(require("path"));
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  const cors_1 = require("../middleware/cors");
15
15
  const stringBody_1 = require("../middleware/stringBody");
16
+ const stream_1 = require("./stream");
16
17
  const stormClient_1 = require("./stormClient");
17
18
  const events_1 = require("./events");
18
19
  const event_parser_1 = require("./event-parser");
@@ -82,7 +83,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
82
83
  });
83
84
  await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
84
85
  // find the page from result1 and write the content to the file
85
- const page = pagesWithImplementation.find((p) => p.filename === fileName);
86
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
86
87
  return page ? page.content : content;
87
88
  });
88
89
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
@@ -101,6 +102,45 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
101
102
  req.stringBody = JSON.stringify(promptRequest);
102
103
  await handleAll(req, res);
103
104
  });
105
+ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
106
+ const handle = req.params.handle;
107
+ const systemId = req.params.systemId;
108
+ const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
109
+ //res.set('Content-Type', 'application/x-ndjson');
110
+ //res.set('Access-Control-Expose-Headers', ConversationIdHeader);
111
+ //res.set(ConversationIdHeader, systemId);
112
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
113
+ try {
114
+ const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
115
+ const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
116
+ pages: pagesFromDisk,
117
+ });
118
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
119
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
120
+ const allFiles = (0, utils_1.readFilesAndContent)(srcDir, false).map((page) => {
121
+ if (page.encoding == stream_1.HTMLPageEncoding.TEXT) {
122
+ const matchingFile = pagesWithImplementation.find((pageWithImpl) => pageWithImpl.fileName === page.fileName);
123
+ if (matchingFile) {
124
+ return matchingFile;
125
+ }
126
+ }
127
+ return page;
128
+ });
129
+ const systemUrl = await stormClient_1.stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
130
+ //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
131
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
132
+ //sendDone(res);
133
+ res.json({ url: systemUrl });
134
+ }
135
+ catch (err) {
136
+ res.status(500).json({ error: err.message });
137
+ }
138
+ finally {
139
+ if (!res.closed) {
140
+ res.end();
141
+ }
142
+ }
143
+ });
104
144
  router.delete('/ui/serve/:systemId', async (req, res) => {
105
145
  const systemId = req.params.systemId;
106
146
  if (!systemId) {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { ConversationItem, ImplementAPIClients, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
2
+ import { ConversationItem, CreateSimpleBackendRequest, HTMLPage, ImplementAPIClients, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
3
3
  import { Page, StormEventPageUrl } from './events';
4
4
  export declare const STORM_ID = "storm";
5
5
  export declare const ConversationIdHeader = "Conversation-Id";
@@ -70,8 +70,9 @@ declare class StormClient {
70
70
  getVoteUIPage(topic: string, conversationId: string, mainConversationId?: string): Promise<{
71
71
  vote: -1 | 0 | 1;
72
72
  }>;
73
- replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<Page[]>;
73
+ replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]>;
74
74
  generatePrompt(pages: string[]): Promise<string>;
75
+ createSimpleBackend(handle: string, systemId: string, input: CreateSimpleBackendRequest): Promise<string>;
75
76
  classifyUIReferences(prompt: string, conversationId?: string): Promise<StormStream>;
76
77
  editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
77
78
  listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
@@ -139,8 +139,7 @@ class StormClient {
139
139
  method: 'POST',
140
140
  body: JSON.stringify(prompt.pages),
141
141
  });
142
- const data = await response.json();
143
- return data;
142
+ return await response.json();
144
143
  }
145
144
  async generatePrompt(pages) {
146
145
  const u = `${this._baseUrl}/v2/ui/prompt`;
@@ -152,6 +151,19 @@ class StormClient {
152
151
  });
153
152
  return await response.text();
154
153
  }
154
+ async createSimpleBackend(handle, systemId, input) {
155
+ const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
156
+ const response = await fetch(u, {
157
+ method: 'POST',
158
+ body: JSON.stringify({
159
+ pages: input.pages,
160
+ }),
161
+ });
162
+ if (!response.ok) {
163
+ throw new Error(`HTTP error! Status: ${response.status}`);
164
+ }
165
+ return await response.text();
166
+ }
155
167
  classifyUIReferences(prompt, conversationId) {
156
168
  return this.send('/v2/ui/references', {
157
169
  prompt: prompt,
@@ -72,10 +72,18 @@ export interface StormUIListPrompt {
72
72
  blockName: string;
73
73
  prompt: string;
74
74
  }
75
+ export declare enum HTMLPageEncoding {
76
+ TEXT = "TEXT",
77
+ BINARY = "BINARY"
78
+ }
75
79
  export interface ImplementAPIClients {
76
- pages: Page[];
80
+ pages: HTMLPage[];
77
81
  }
78
- export interface Page {
82
+ export interface HTMLPage {
79
83
  fileName: string;
80
84
  content: string;
85
+ encoding: HTMLPageEncoding;
86
+ }
87
+ export interface CreateSimpleBackendRequest {
88
+ pages: HTMLPage[];
81
89
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StormStream = void 0;
3
+ exports.HTMLPageEncoding = exports.StormStream = void 0;
4
4
  /**
5
5
  * Copyright 2023 Kapeta Inc.
6
6
  * SPDX-License-Identifier: BUSL-1.1
@@ -74,3 +74,8 @@ class StormStream extends node_events_1.EventEmitter {
74
74
  }
75
75
  }
76
76
  exports.StormStream = StormStream;
77
+ var HTMLPageEncoding;
78
+ (function (HTMLPageEncoding) {
79
+ HTMLPageEncoding["TEXT"] = "TEXT";
80
+ HTMLPageEncoding["BINARY"] = "BINARY";
81
+ })(HTMLPageEncoding || (exports.HTMLPageEncoding = HTMLPageEncoding = {}));
@@ -1,6 +1,6 @@
1
- import { Page } from './stream';
1
+ import { HTMLPage } from './stream';
2
2
  export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => string): Promise<void>;
3
- export declare function readFilesAndContent(directoryPath: string): Page[];
3
+ export declare function readFilesAndContent(directoryPath: string, htmlExclusive?: boolean): HTMLPage[];
4
4
  export declare function createFuture<T = void>(): {
5
5
  promise: Promise<T>;
6
6
  resolve: (value: T | PromiseLike<T>) => void;
@@ -10,6 +10,7 @@ exports.createFuture = exports.readFilesAndContent = exports.copyDirectory = voi
10
10
  */
11
11
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
12
  const path_1 = __importDefault(require("path"));
13
+ const stream_1 = require("./stream");
13
14
  async function copyDirectory(src, dest, modifyHtml) {
14
15
  await fs_extra_1.default.promises.mkdir(dest, { recursive: true });
15
16
  const entries = await fs_extra_1.default.promises.readdir(src, { withFileTypes: true });
@@ -29,19 +30,28 @@ async function copyDirectory(src, dest, modifyHtml) {
29
30
  }
30
31
  }
31
32
  exports.copyDirectory = copyDirectory;
32
- function readFilesAndContent(directoryPath) {
33
+ function readFilesAndContent(directoryPath, htmlExclusive = true) {
33
34
  const htmlFiles = [];
34
35
  function traverseDirectory(currentPath) {
35
36
  const files = fs_extra_1.default.readdirSync(currentPath);
36
37
  for (const file of files) {
37
38
  const fileName = path_1.default.join(currentPath, file);
39
+ const relativeFilename = path_1.default.relative(directoryPath, fileName);
38
40
  const stats = fs_extra_1.default.statSync(fileName);
39
41
  if (stats.isDirectory()) {
40
42
  traverseDirectory(fileName);
41
43
  }
42
- else if (stats.isFile() && path_1.default.extname(fileName) === '.html') {
43
- const content = fs_extra_1.default.readFileSync(fileName, 'utf8');
44
- htmlFiles.push({ fileName, content });
44
+ else if (stats.isFile()) {
45
+ if (path_1.default.extname(fileName) === '.html') {
46
+ const content = fs_extra_1.default.readFileSync(fileName, 'utf8');
47
+ htmlFiles.push({ fileName: relativeFilename, content: content, encoding: stream_1.HTMLPageEncoding.TEXT });
48
+ }
49
+ else {
50
+ if (!htmlExclusive) {
51
+ const content = fs_extra_1.default.readFileSync(fileName);
52
+ htmlFiles.push({ fileName: relativeFilename, content: content.toString('base64'), encoding: stream_1.HTMLPageEncoding.BINARY });
53
+ }
54
+ }
45
55
  }
46
56
  }
47
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.71.4",
3
+ "version": "0.71.6",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -331,6 +331,7 @@ export interface StormEventDefinitionChange {
331
331
  export enum StormEventPhaseType {
332
332
  IMPLEMENT_APIS = 'IMPLEMENT_APIS', // Implement APIs in the html pages
333
333
  COMPOSE_SYSTEM_PROMPT = 'COMPOSE_SYSTEM_PROMPT', // Compose system prompt for bottom-up approach
334
+ COMPOSE_SYSTEM = 'COMPOSE_SYSTEM',
334
335
  META = 'META',
335
336
  DEFINITIONS = 'DEFINITIONS',
336
337
  IMPLEMENTATION = 'IMPLEMENTATION',
@@ -473,6 +474,15 @@ export interface StormEventUIStarted {
473
474
  };
474
475
  }
475
476
 
477
+ export interface StormEventSystemReady {
478
+ type: 'SYSTEM_READY';
479
+ reason: string;
480
+ created: number;
481
+ payload: {
482
+ systemUrl: string;
483
+ }
484
+ }
485
+
476
486
  export type StormEvent =
477
487
  | StormEventCreateBlock
478
488
  | StormEventCreateConnection
@@ -507,4 +517,5 @@ export type StormEvent =
507
517
  | StormEventReferenceClassification
508
518
  | StormEventApiBase
509
519
  | StormEventUIStarted
510
- | StormImage;
520
+ | StormImage
521
+ | StormEventSystemReady;
@@ -11,7 +11,13 @@ import _ from 'lodash';
11
11
  import { corsHandler } from '../middleware/cors';
12
12
  import { stringBody } from '../middleware/stringBody';
13
13
  import { KapetaBodyRequest } from '../types';
14
- import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
14
+ import {
15
+ HTMLPageEncoding,
16
+ StormCodegenRequest,
17
+ StormContextRequest,
18
+ StormCreateBlockRequest,
19
+ StormStream,
20
+ } from './stream';
15
21
 
16
22
  import {
17
23
  ConversationIdHeader,
@@ -22,7 +28,7 @@ import {
22
28
  UIPageVoteRequest,
23
29
  UIPageGetVoteRequest,
24
30
  } from './stormClient';
25
- import { Page, StormEvent, StormEventPage, StormEventPhaseType, StormImage, UserJourneyScreen } from './events';
31
+ import { StormEvent, StormEventPage, StormEventPhaseType, UserJourneyScreen } from './events';
26
32
 
27
33
  import {
28
34
  createPhaseEndEvent,
@@ -43,7 +49,7 @@ import {
43
49
  } from './page-utils';
44
50
  import { UIServer } from './UIServer';
45
51
  import { randomUUID } from 'crypto';
46
- import { ImagePrompt, PageQueue } from './PageGenerator';
52
+ import { PageQueue } from './PageGenerator';
47
53
  import { copyDirectory, createFuture, readFilesAndContent } from './utils';
48
54
 
49
55
  const UI_SERVERS: { [key: string]: UIServer } = {};
@@ -109,26 +115,24 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
109
115
 
110
116
  sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
111
117
 
112
- const pagesFromDisk = readFilesAndContent(srcDir);
118
+ const pagesFromDisk = readFilesAndContent(srcDir);
113
119
  const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
114
- pages: pagesFromDisk,
115
- },
116
- );
120
+ pages: pagesFromDisk,
121
+ });
117
122
  await copyDirectory(srcDir, destDir, (fileName, content) => {
118
123
  // find the page from result1 and write the content to the file
119
- const page = pagesWithImplementation.find((p) => p.filename === fileName);
124
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
120
125
  return page ? page.content : content;
121
126
  });
122
127
 
123
-
124
128
  sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
125
129
 
126
130
  sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
127
131
 
128
132
  // get the content of the pages
129
133
  const pageContents = pagesWithImplementation.map((page) => {
130
- return page.content
131
- })
134
+ return page.content;
135
+ });
132
136
 
133
137
  const prompt = await stormClient.generatePrompt(pageContents);
134
138
 
@@ -143,6 +147,55 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
143
147
  await handleAll(req, res);
144
148
  });
145
149
 
150
+ router.post('/ui/create-system-simple/:handle/:systemId', async (req: KapetaBodyRequest, res: Response) => {
151
+ const handle = req.params.handle as string;
152
+ const systemId = req.params.systemId as string;
153
+ const srcDir = getSystemBaseDir(systemId);
154
+
155
+ //res.set('Content-Type', 'application/x-ndjson');
156
+ //res.set('Access-Control-Expose-Headers', ConversationIdHeader);
157
+ //res.set(ConversationIdHeader, systemId);
158
+
159
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
160
+
161
+ try {
162
+ const pagesFromDisk = readFilesAndContent(srcDir);
163
+ const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
164
+ pages: pagesFromDisk,
165
+ });
166
+
167
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
168
+
169
+ //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
170
+
171
+ const allFiles = readFilesAndContent(srcDir, false).map((page) => {
172
+ if (page.encoding == HTMLPageEncoding.TEXT) {
173
+ const matchingFile = pagesWithImplementation.find(
174
+ (pageWithImpl) => pageWithImpl.fileName === page.fileName
175
+ );
176
+ if (matchingFile) {
177
+ return matchingFile;
178
+ }
179
+ }
180
+ return page;
181
+ });
182
+
183
+ const systemUrl = await stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
184
+ //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
185
+
186
+ //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
187
+
188
+ //sendDone(res);
189
+ res.json({ url: systemUrl });
190
+ } catch (err: any) {
191
+ res.status(500).json({ error: err.message });
192
+ } finally {
193
+ if (!res.closed) {
194
+ res.end();
195
+ }
196
+ }
197
+ });
198
+
146
199
  router.delete('/ui/serve/:systemId', async (req: KapetaBodyRequest, res: Response) => {
147
200
  const systemId = req.params.systemId as string | undefined;
148
201
  if (!systemId) {
@@ -8,6 +8,7 @@ import readLine from 'node:readline/promises';
8
8
  import { Readable } from 'node:stream';
9
9
  import {
10
10
  ConversationItem,
11
+ CreateSimpleBackendRequest, HTMLPage,
11
12
  ImplementAPIClients,
12
13
  StormContextRequest,
13
14
  StormFileImplementationPrompt,
@@ -238,14 +239,13 @@ class StormClient {
238
239
  return response.json() as Promise<{ vote: -1 | 0 | 1 }>;
239
240
  }
240
241
 
241
- public async replaceMockWithAPICall(prompt: ImplementAPIClients) {
242
+ public async replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]> {
242
243
  const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
243
244
  const response = await fetch(u, {
244
245
  method: 'POST',
245
246
  body: JSON.stringify(prompt.pages),
246
247
  });
247
- const data = await response.json() as Page[];
248
- return data;
248
+ return await response.json() as HTMLPage[];
249
249
  }
250
250
 
251
251
  public async generatePrompt(pages: string[]): Promise<string> {
@@ -259,6 +259,20 @@ class StormClient {
259
259
  return await response.text();
260
260
  }
261
261
 
262
+ public async createSimpleBackend(handle: string, systemId: string, input: CreateSimpleBackendRequest) {
263
+ const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
264
+ const response = await fetch(u, {
265
+ method: 'POST',
266
+ body: JSON.stringify({
267
+ pages: input.pages,
268
+ }),
269
+ });
270
+ if (!response.ok) {
271
+ throw new Error(`HTTP error! Status: ${response.status}`);
272
+ }
273
+ return await response.text();
274
+ }
275
+
262
276
  public classifyUIReferences(prompt: string, conversationId?: string) {
263
277
  return this.send('/v2/ui/references', {
264
278
  prompt: prompt,
@@ -143,11 +143,21 @@ export interface StormUIListPrompt {
143
143
  prompt: string;
144
144
  }
145
145
 
146
+ export enum HTMLPageEncoding {
147
+ TEXT = 'TEXT',
148
+ BINARY = 'BINARY',
149
+ }
150
+
146
151
  export interface ImplementAPIClients {
147
- pages: Page[];
152
+ pages: HTMLPage[];
148
153
  }
149
154
 
150
- export interface Page {
155
+ export interface HTMLPage {
151
156
  fileName: string;
152
- content: string
157
+ content: string;
158
+ encoding: HTMLPageEncoding;
159
+ }
160
+
161
+ export interface CreateSimpleBackendRequest {
162
+ pages: HTMLPage[];
153
163
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import FS from 'fs-extra';
6
6
  import Path from 'path';
7
- import { Page } from './stream';
7
+ import { HTMLPage, HTMLPageEncoding } from './stream';
8
8
 
9
9
  export async function copyDirectory(
10
10
  src: string,
@@ -32,19 +32,27 @@ export async function copyDirectory(
32
32
  }
33
33
  }
34
34
 
35
- export function readFilesAndContent(directoryPath: string): Page[] {
36
- const htmlFiles:Page[] = [];
35
+ export function readFilesAndContent(directoryPath: string, htmlExclusive: boolean = true): HTMLPage[] {
36
+ const htmlFiles:HTMLPage[] = [];
37
37
 
38
38
  function traverseDirectory(currentPath: string) {
39
39
  const files = FS.readdirSync(currentPath);
40
40
  for (const file of files) {
41
41
  const fileName = Path.join(currentPath, file);
42
+ const relativeFilename = Path.relative(directoryPath, fileName);
42
43
  const stats = FS.statSync(fileName);
43
44
  if (stats.isDirectory()) {
44
45
  traverseDirectory(fileName);
45
- } else if (stats.isFile() && Path.extname(fileName) === '.html') {
46
- const content = FS.readFileSync(fileName, 'utf8');
47
- htmlFiles.push({fileName, content});
46
+ } else if (stats.isFile()) {
47
+ if (Path.extname(fileName) === '.html') {
48
+ const content = FS.readFileSync(fileName, 'utf8');
49
+ htmlFiles.push({fileName: relativeFilename, content: content, encoding: HTMLPageEncoding.TEXT });
50
+ } else {
51
+ if (!htmlExclusive) {
52
+ const content = FS.readFileSync(fileName);
53
+ htmlFiles.push({fileName: relativeFilename, content: content.toString('base64'), encoding: HTMLPageEncoding.BINARY })
54
+ }
55
+ }
48
56
  }
49
57
  }
50
58
  }