@kapeta/local-cluster-service 0.71.3 → 0.71.5

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.5](https://github.com/kapetacom/local-cluster-service/compare/v0.71.4...v0.71.5) (2024-09-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * adds create-system-simple route ([fea005a](https://github.com/kapetacom/local-cluster-service/commit/fea005a183b45a2caf6811ae6d0d7019386a21ef))
7
+ * review comments ([17f8192](https://github.com/kapetacom/local-cluster-service/commit/17f819237efff8bb57dfdb9b2f53431d5dc44021))
8
+ * send html + images etc. to endpoint ([bdb5e81](https://github.com/kapetacom/local-cluster-service/commit/bdb5e818d66fff6983f126d872fdf228e97bc5d1))
9
+
10
+ ## [0.71.4](https://github.com/kapetacom/local-cluster-service/compare/v0.71.3...v0.71.4) (2024-09-19)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * Making implementing APIs from faster ([33f4f1c](https://github.com/kapetacom/local-cluster-service/commit/33f4f1c071eb6c248e4912bfceffc820496c6edd))
16
+
1
17
  ## [0.71.3](https://github.com/kapetacom/local-cluster-service/compare/v0.71.2...v0.71.3) (2024-09-18)
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");
@@ -76,17 +77,22 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
76
77
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
77
78
  res.set(stormClient_1.ConversationIdHeader, systemId);
78
79
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
79
- await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
80
- const result = await stormClient_1.stormClient.implementAPIClients({
81
- content: content,
82
- fileName: fileName,
83
- });
84
- return result;
80
+ const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
81
+ const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
82
+ pages: pagesFromDisk,
83
+ });
84
+ await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
85
+ // find the page from result1 and write the content to the file
86
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
87
+ return page ? page.content : content;
85
88
  });
86
89
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
87
90
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
88
- const pages = (0, utils_1.readPages)(destDir);
89
- const prompt = await stormClient_1.stormClient.generatePrompt(pages);
91
+ // get the content of the pages
92
+ const pageContents = pagesWithImplementation.map((page) => {
93
+ return page.content;
94
+ });
95
+ const prompt = await stormClient_1.stormClient.generatePrompt(pageContents);
90
96
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
91
97
  req.query.systemId = systemId;
92
98
  const promptRequest = {
@@ -96,6 +102,44 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
96
102
  req.stringBody = JSON.stringify(promptRequest);
97
103
  await handleAll(req, res);
98
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', stormClient_1.ConversationIdHeader);
111
+ res.set(stormClient_1.ConversationIdHeader, systemId);
112
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.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, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
119
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.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, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM));
132
+ sendDone(res);
133
+ }
134
+ catch (err) {
135
+ sendError(err, res);
136
+ }
137
+ finally {
138
+ if (!res.closed) {
139
+ res.end();
140
+ }
141
+ }
142
+ });
99
143
  router.delete('/ui/serve/:systemId', async (req, res) => {
100
144
  const systemId = req.params.systemId;
101
145
  if (!systemId) {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { ConversationItem, ImplementAPIClientsRequest, 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
- implementAPIClients(prompt: ImplementAPIClientsRequest): Promise<string>;
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>;
@@ -133,17 +133,13 @@ class StormClient {
133
133
  const response = await fetch(options.url, options);
134
134
  return response.json();
135
135
  }
136
- async implementAPIClients(prompt) {
137
- const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
136
+ async replaceMockWithAPICall(prompt) {
137
+ const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
138
138
  const response = await fetch(u, {
139
139
  method: 'POST',
140
- body: JSON.stringify({
141
- fileName: prompt.fileName,
142
- content: prompt.content,
143
- }),
140
+ body: JSON.stringify(prompt.pages),
144
141
  });
145
- const data = await response.text();
146
- return data;
142
+ return await response.json();
147
143
  }
148
144
  async generatePrompt(pages) {
149
145
  const u = `${this._baseUrl}/v2/ui/prompt`;
@@ -155,6 +151,19 @@ class StormClient {
155
151
  });
156
152
  return await response.text();
157
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
+ }
158
167
  classifyUIReferences(prompt, conversationId) {
159
168
  return this.send('/v2/ui/references', {
160
169
  prompt: prompt,
@@ -72,7 +72,18 @@ export interface StormUIListPrompt {
72
72
  blockName: string;
73
73
  prompt: string;
74
74
  }
75
- export interface ImplementAPIClientsRequest {
75
+ export declare enum HTMLPageEncoding {
76
+ TEXT = "TEXT",
77
+ BINARY = "BINARY"
78
+ }
79
+ export interface ImplementAPIClients {
80
+ pages: HTMLPage[];
81
+ }
82
+ export interface HTMLPage {
76
83
  fileName: string;
77
84
  content: string;
85
+ encoding: HTMLPageEncoding;
86
+ }
87
+ export interface CreateSimpleBackendRequest {
88
+ pages: HTMLPage[];
78
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,5 +1,6 @@
1
- export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => Promise<string>): Promise<void>;
2
- export declare function readPages(directoryPath: string): string[];
1
+ import { HTMLPage } from './stream';
2
+ export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => string): Promise<void>;
3
+ export declare function readFilesAndContent(directoryPath: string, htmlExclusive?: boolean): HTMLPage[];
3
4
  export declare function createFuture<T = void>(): {
4
5
  promise: Promise<T>;
5
6
  resolve: (value: T | PromiseLike<T>) => void;
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createFuture = exports.readPages = exports.copyDirectory = void 0;
6
+ exports.createFuture = exports.readFilesAndContent = exports.copyDirectory = void 0;
7
7
  /**
8
8
  * Copyright 2023 Kapeta Inc.
9
9
  * SPDX-License-Identifier: BUSL-1.1
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 });
@@ -22,33 +23,42 @@ async function copyDirectory(src, dest, modifyHtml) {
22
23
  else if (entry.isFile()) {
23
24
  let content = await fs_extra_1.default.promises.readFile(srcPath, 'utf-8');
24
25
  if (path_1.default.extname(srcPath) === '.html') {
25
- content = await modifyHtml(srcPath, content);
26
+ content = modifyHtml(srcPath, content);
26
27
  }
27
28
  await fs_extra_1.default.promises.writeFile(destPath, content, 'utf-8');
28
29
  }
29
30
  }
30
31
  }
31
32
  exports.copyDirectory = copyDirectory;
32
- function readPages(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
- const filePath = path_1.default.join(currentPath, file);
38
- const stats = fs_extra_1.default.statSync(filePath);
38
+ const fileName = path_1.default.join(currentPath, file);
39
+ const relativeFilename = path_1.default.relative(directoryPath, fileName);
40
+ const stats = fs_extra_1.default.statSync(fileName);
39
41
  if (stats.isDirectory()) {
40
- traverseDirectory(filePath);
42
+ traverseDirectory(fileName);
41
43
  }
42
- else if (stats.isFile() && path_1.default.extname(filePath) === '.html') {
43
- const content = fs_extra_1.default.readFileSync(filePath, 'utf8');
44
- htmlFiles.push(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
  }
48
58
  traverseDirectory(directoryPath);
49
59
  return htmlFiles;
50
60
  }
51
- exports.readPages = readPages;
61
+ exports.readFilesAndContent = readFilesAndContent;
52
62
  function createFuture() {
53
63
  let resolve = () => { };
54
64
  let reject = () => { };
@@ -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");
@@ -76,17 +77,22 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
76
77
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
77
78
  res.set(stormClient_1.ConversationIdHeader, systemId);
78
79
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
79
- await (0, utils_1.copyDirectory)(srcDir, destDir, async (fileName, content) => {
80
- const result = await stormClient_1.stormClient.implementAPIClients({
81
- content: content,
82
- fileName: fileName,
83
- });
84
- return result;
80
+ const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
81
+ const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
82
+ pages: pagesFromDisk,
83
+ });
84
+ await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
85
+ // find the page from result1 and write the content to the file
86
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
87
+ return page ? page.content : content;
85
88
  });
86
89
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
87
90
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
88
- const pages = (0, utils_1.readPages)(destDir);
89
- const prompt = await stormClient_1.stormClient.generatePrompt(pages);
91
+ // get the content of the pages
92
+ const pageContents = pagesWithImplementation.map((page) => {
93
+ return page.content;
94
+ });
95
+ const prompt = await stormClient_1.stormClient.generatePrompt(pageContents);
90
96
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
91
97
  req.query.systemId = systemId;
92
98
  const promptRequest = {
@@ -96,6 +102,44 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
96
102
  req.stringBody = JSON.stringify(promptRequest);
97
103
  await handleAll(req, res);
98
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', stormClient_1.ConversationIdHeader);
111
+ res.set(stormClient_1.ConversationIdHeader, systemId);
112
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.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, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
119
+ sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.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, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM));
132
+ sendDone(res);
133
+ }
134
+ catch (err) {
135
+ sendError(err, res);
136
+ }
137
+ finally {
138
+ if (!res.closed) {
139
+ res.end();
140
+ }
141
+ }
142
+ });
99
143
  router.delete('/ui/serve/:systemId', async (req, res) => {
100
144
  const systemId = req.params.systemId;
101
145
  if (!systemId) {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { ConversationItem, ImplementAPIClientsRequest, 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
- implementAPIClients(prompt: ImplementAPIClientsRequest): Promise<string>;
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>;
@@ -133,17 +133,13 @@ class StormClient {
133
133
  const response = await fetch(options.url, options);
134
134
  return response.json();
135
135
  }
136
- async implementAPIClients(prompt) {
137
- const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
136
+ async replaceMockWithAPICall(prompt) {
137
+ const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
138
138
  const response = await fetch(u, {
139
139
  method: 'POST',
140
- body: JSON.stringify({
141
- fileName: prompt.fileName,
142
- content: prompt.content,
143
- }),
140
+ body: JSON.stringify(prompt.pages),
144
141
  });
145
- const data = await response.text();
146
- return data;
142
+ return await response.json();
147
143
  }
148
144
  async generatePrompt(pages) {
149
145
  const u = `${this._baseUrl}/v2/ui/prompt`;
@@ -155,6 +151,19 @@ class StormClient {
155
151
  });
156
152
  return await response.text();
157
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
+ }
158
167
  classifyUIReferences(prompt, conversationId) {
159
168
  return this.send('/v2/ui/references', {
160
169
  prompt: prompt,
@@ -72,7 +72,18 @@ export interface StormUIListPrompt {
72
72
  blockName: string;
73
73
  prompt: string;
74
74
  }
75
- export interface ImplementAPIClientsRequest {
75
+ export declare enum HTMLPageEncoding {
76
+ TEXT = "TEXT",
77
+ BINARY = "BINARY"
78
+ }
79
+ export interface ImplementAPIClients {
80
+ pages: HTMLPage[];
81
+ }
82
+ export interface HTMLPage {
76
83
  fileName: string;
77
84
  content: string;
85
+ encoding: HTMLPageEncoding;
86
+ }
87
+ export interface CreateSimpleBackendRequest {
88
+ pages: HTMLPage[];
78
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,5 +1,6 @@
1
- export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => Promise<string>): Promise<void>;
2
- export declare function readPages(directoryPath: string): string[];
1
+ import { HTMLPage } from './stream';
2
+ export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => string): Promise<void>;
3
+ export declare function readFilesAndContent(directoryPath: string, htmlExclusive?: boolean): HTMLPage[];
3
4
  export declare function createFuture<T = void>(): {
4
5
  promise: Promise<T>;
5
6
  resolve: (value: T | PromiseLike<T>) => void;
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createFuture = exports.readPages = exports.copyDirectory = void 0;
6
+ exports.createFuture = exports.readFilesAndContent = exports.copyDirectory = void 0;
7
7
  /**
8
8
  * Copyright 2023 Kapeta Inc.
9
9
  * SPDX-License-Identifier: BUSL-1.1
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 });
@@ -22,33 +23,42 @@ async function copyDirectory(src, dest, modifyHtml) {
22
23
  else if (entry.isFile()) {
23
24
  let content = await fs_extra_1.default.promises.readFile(srcPath, 'utf-8');
24
25
  if (path_1.default.extname(srcPath) === '.html') {
25
- content = await modifyHtml(srcPath, content);
26
+ content = modifyHtml(srcPath, content);
26
27
  }
27
28
  await fs_extra_1.default.promises.writeFile(destPath, content, 'utf-8');
28
29
  }
29
30
  }
30
31
  }
31
32
  exports.copyDirectory = copyDirectory;
32
- function readPages(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
- const filePath = path_1.default.join(currentPath, file);
38
- const stats = fs_extra_1.default.statSync(filePath);
38
+ const fileName = path_1.default.join(currentPath, file);
39
+ const relativeFilename = path_1.default.relative(directoryPath, fileName);
40
+ const stats = fs_extra_1.default.statSync(fileName);
39
41
  if (stats.isDirectory()) {
40
- traverseDirectory(filePath);
42
+ traverseDirectory(fileName);
41
43
  }
42
- else if (stats.isFile() && path_1.default.extname(filePath) === '.html') {
43
- const content = fs_extra_1.default.readFileSync(filePath, 'utf8');
44
- htmlFiles.push(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
  }
48
58
  traverseDirectory(directoryPath);
49
59
  return htmlFiles;
50
60
  }
51
- exports.readPages = readPages;
61
+ exports.readFilesAndContent = readFilesAndContent;
52
62
  function createFuture() {
53
63
  let resolve = () => { };
54
64
  let reject = () => { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.71.3",
3
+ "version": "0.71.5",
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,7 @@ 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 {HTMLPageEncoding, StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream} from './stream';
15
15
 
16
16
  import {
17
17
  ConversationIdHeader,
@@ -22,7 +22,7 @@ import {
22
22
  UIPageVoteRequest,
23
23
  UIPageGetVoteRequest,
24
24
  } from './stormClient';
25
- import { Page, StormEvent, StormEventPage, StormEventPhaseType, StormImage, UserJourneyScreen } from './events';
25
+ import { StormEvent, StormEventPage, StormEventPhaseType, UserJourneyScreen } from './events';
26
26
 
27
27
  import {
28
28
  createPhaseEndEvent,
@@ -37,16 +37,14 @@ import uuid from 'node-uuid';
37
37
  import {
38
38
  getSystemBaseDir,
39
39
  getSystemBaseImplDir,
40
- readPageFromDisk,
41
- resolveReadPath,
42
40
  SystemIdHeader,
43
41
  writeAssetToDisk,
44
42
  writePageToDisk,
45
43
  } from './page-utils';
46
44
  import { UIServer } from './UIServer';
47
45
  import { randomUUID } from 'crypto';
48
- import { ImagePrompt, PageQueue } from './PageGenerator';
49
- import { copyDirectory, createFuture, readPages } from './utils';
46
+ import { PageQueue } from './PageGenerator';
47
+ import { copyDirectory, createFuture, readFilesAndContent } from './utils';
50
48
 
51
49
  const UI_SERVERS: { [key: string]: UIServer } = {};
52
50
  const router = Router();
@@ -111,20 +109,28 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
111
109
 
112
110
  sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
113
111
 
114
- await copyDirectory(srcDir, destDir, async (fileName, content) => {
115
- const result = await stormClient.implementAPIClients({
116
- content: content,
117
- fileName: fileName,
118
- });
119
- return result;
112
+ const pagesFromDisk = readFilesAndContent(srcDir);
113
+ const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
114
+ pages: pagesFromDisk,
115
+ },
116
+ );
117
+ await copyDirectory(srcDir, destDir, (fileName, content) => {
118
+ // find the page from result1 and write the content to the file
119
+ const page = pagesWithImplementation.find((p) => p.fileName === fileName);
120
+ return page ? page.content : content;
120
121
  });
121
122
 
123
+
122
124
  sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
123
125
 
124
126
  sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
125
127
 
126
- const pages = readPages(destDir);
127
- const prompt = await stormClient.generatePrompt(pages);
128
+ // get the content of the pages
129
+ const pageContents = pagesWithImplementation.map((page) => {
130
+ return page.content
131
+ })
132
+
133
+ const prompt = await stormClient.generatePrompt(pageContents);
128
134
 
129
135
  sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
130
136
 
@@ -134,10 +140,56 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
134
140
  skipImprovement: true,
135
141
  };
136
142
  req.stringBody = JSON.stringify(promptRequest);
137
-
138
143
  await handleAll(req, res);
139
144
  });
140
145
 
146
+ router.post('/ui/create-system-simple/:handle/:systemId', async (req: KapetaBodyRequest, res: Response) => {
147
+ const handle = req.params.handle as string;
148
+ const systemId = req.params.systemId as string;
149
+ const srcDir = getSystemBaseDir(systemId);
150
+
151
+ res.set('Content-Type', 'application/x-ndjson');
152
+ res.set('Access-Control-Expose-Headers', ConversationIdHeader);
153
+ res.set(ConversationIdHeader, systemId);
154
+
155
+ sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
156
+
157
+ try {
158
+ const pagesFromDisk = readFilesAndContent(srcDir);
159
+ const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
160
+ pages: pagesFromDisk,
161
+ },
162
+ );
163
+
164
+ sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
165
+
166
+ sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
167
+
168
+ const allFiles = readFilesAndContent(srcDir, false).map((page) => {
169
+ if (page.encoding == HTMLPageEncoding.TEXT) {
170
+ const matchingFile = pagesWithImplementation.find((pageWithImpl) => pageWithImpl.fileName === page.fileName);
171
+ if (matchingFile) {
172
+ return matchingFile;
173
+ }
174
+ }
175
+ return page;
176
+ });
177
+
178
+ const systemUrl = await stormClient.createSimpleBackend(handle, systemId, {pages: allFiles});
179
+ sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
180
+
181
+ sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
182
+
183
+ sendDone(res);
184
+ } catch (err: any) {
185
+ sendError(err, res);
186
+ } finally {
187
+ if (!res.closed) {
188
+ res.end();
189
+ }
190
+ }
191
+ });
192
+
141
193
  router.delete('/ui/serve/:systemId', async (req: KapetaBodyRequest, res: Response) => {
142
194
  const systemId = req.params.systemId as string | undefined;
143
195
  if (!systemId) {
@@ -8,7 +8,8 @@ import readLine from 'node:readline/promises';
8
8
  import { Readable } from 'node:stream';
9
9
  import {
10
10
  ConversationItem,
11
- ImplementAPIClientsRequest,
11
+ CreateSimpleBackendRequest, HTMLPage,
12
+ ImplementAPIClients,
12
13
  StormContextRequest,
13
14
  StormFileImplementationPrompt,
14
15
  StormStream,
@@ -238,17 +239,13 @@ class StormClient {
238
239
  return response.json() as Promise<{ vote: -1 | 0 | 1 }>;
239
240
  }
240
241
 
241
- public async implementAPIClients(prompt: ImplementAPIClientsRequest) {
242
- const u = `${this._baseUrl}/v2/ui/implement-api-clients`;
242
+ public async replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]> {
243
+ const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
243
244
  const response = await fetch(u, {
244
245
  method: 'POST',
245
- body: JSON.stringify({
246
- fileName: prompt.fileName,
247
- content: prompt.content,
248
- }),
246
+ body: JSON.stringify(prompt.pages),
249
247
  });
250
- const data = await response.text();
251
- return data;
248
+ return await response.json() as HTMLPage[];
252
249
  }
253
250
 
254
251
  public async generatePrompt(pages: string[]): Promise<string> {
@@ -262,6 +259,20 @@ class StormClient {
262
259
  return await response.text();
263
260
  }
264
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
+
265
276
  public classifyUIReferences(prompt: string, conversationId?: string) {
266
277
  return this.send('/v2/ui/references', {
267
278
  prompt: prompt,
@@ -143,7 +143,21 @@ export interface StormUIListPrompt {
143
143
  prompt: string;
144
144
  }
145
145
 
146
- export interface ImplementAPIClientsRequest {
146
+ export enum HTMLPageEncoding {
147
+ TEXT = 'TEXT',
148
+ BINARY = 'BINARY',
149
+ }
150
+
151
+ export interface ImplementAPIClients {
152
+ pages: HTMLPage[];
153
+ }
154
+
155
+ export interface HTMLPage {
147
156
  fileName: string;
148
- content: string
157
+ content: string;
158
+ encoding: HTMLPageEncoding;
159
+ }
160
+
161
+ export interface CreateSimpleBackendRequest {
162
+ pages: HTMLPage[];
149
163
  }
@@ -4,11 +4,12 @@
4
4
  */
5
5
  import FS from 'fs-extra';
6
6
  import Path from 'path';
7
+ import { HTMLPage, HTMLPageEncoding } from './stream';
7
8
 
8
9
  export async function copyDirectory(
9
10
  src: string,
10
11
  dest: string,
11
- modifyHtml: (fileName: string, content: string) => Promise<string>
12
+ modifyHtml: (fileName: string, content: string) => string
12
13
  ): Promise<void> {
13
14
  await FS.promises.mkdir(dest, { recursive: true });
14
15
  const entries = await FS.promises.readdir(src, { withFileTypes: true });
@@ -23,7 +24,7 @@ export async function copyDirectory(
23
24
  let content = await FS.promises.readFile(srcPath, 'utf-8');
24
25
 
25
26
  if (Path.extname(srcPath) === '.html') {
26
- content = await modifyHtml(srcPath, content);
27
+ content = modifyHtml(srcPath, content);
27
28
  }
28
29
 
29
30
  await FS.promises.writeFile(destPath, content, 'utf-8');
@@ -31,21 +32,27 @@ export async function copyDirectory(
31
32
  }
32
33
  }
33
34
 
34
- export function readPages(directoryPath: string): string[] {
35
- const htmlFiles: string[] = [];
35
+ export function readFilesAndContent(directoryPath: string, htmlExclusive: boolean = true): HTMLPage[] {
36
+ const htmlFiles:HTMLPage[] = [];
36
37
 
37
38
  function traverseDirectory(currentPath: string) {
38
39
  const files = FS.readdirSync(currentPath);
39
-
40
40
  for (const file of files) {
41
- const filePath = Path.join(currentPath, file);
42
- const stats = FS.statSync(filePath);
43
-
41
+ const fileName = Path.join(currentPath, file);
42
+ const relativeFilename = Path.relative(directoryPath, fileName);
43
+ const stats = FS.statSync(fileName);
44
44
  if (stats.isDirectory()) {
45
- traverseDirectory(filePath);
46
- } else if (stats.isFile() && Path.extname(filePath) === '.html') {
47
- const content = FS.readFileSync(filePath, 'utf8');
48
- htmlFiles.push(content);
45
+ traverseDirectory(fileName);
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
+ }
49
56
  }
50
57
  }
51
58
  }