@kapeta/local-cluster-service 0.58.6 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/clusterService.d.ts +1 -2
- package/dist/cjs/src/clusterService.js +14 -5
- package/dist/cjs/src/storm/UIServer.d.ts +11 -0
- package/dist/cjs/src/storm/UIServer.js +47 -0
- package/dist/cjs/src/storm/events.d.ts +20 -1
- package/dist/cjs/src/storm/page-utils.d.ts +12 -0
- package/dist/cjs/src/storm/page-utils.js +40 -0
- package/dist/cjs/src/storm/routes.js +111 -8
- package/dist/cjs/src/storm/stormClient.d.ts +9 -4
- package/dist/esm/src/clusterService.d.ts +1 -2
- package/dist/esm/src/clusterService.js +14 -5
- package/dist/esm/src/storm/UIServer.d.ts +11 -0
- package/dist/esm/src/storm/UIServer.js +47 -0
- package/dist/esm/src/storm/events.d.ts +20 -1
- package/dist/esm/src/storm/page-utils.d.ts +12 -0
- package/dist/esm/src/storm/page-utils.js +40 -0
- package/dist/esm/src/storm/routes.js +111 -8
- package/dist/esm/src/storm/stormClient.d.ts +9 -4
- package/package.json +1 -1
- package/src/clusterService.ts +14 -5
- package/src/storm/UIServer.ts +50 -0
- package/src/storm/events.ts +23 -1
- package/src/storm/page-utils.ts +53 -0
- package/src/storm/routes.ts +127 -13
- package/src/storm/stormClient.ts +10 -4
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.60.0](https://github.com/kapetacom/local-cluster-service/compare/v0.59.0...v0.60.0) (2024-08-05)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Host ui pages on a temp server so we can have it on its own server ([#208](https://github.com/kapetacom/local-cluster-service/issues/208)) ([31ba4b8](https://github.com/kapetacom/local-cluster-service/commit/31ba4b8142c4618cc347037004f8fed87c69c6b7))
|
7
|
+
|
8
|
+
# [0.59.0](https://github.com/kapetacom/local-cluster-service/compare/v0.58.6...v0.59.0) (2024-08-01)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Convert PAGE events to PAGE_URL ([#207](https://github.com/kapetacom/local-cluster-service/issues/207)) ([9fddeec](https://github.com/kapetacom/local-cluster-service/commit/9fddeec4a055a02796d80987ae5b9ca39d7dd9ee))
|
14
|
+
|
1
15
|
## [0.58.6](https://github.com/kapetacom/local-cluster-service/compare/v0.58.5...v0.58.6) (2024-08-01)
|
2
16
|
|
3
17
|
|
@@ -14,9 +14,8 @@ declare class ClusterService {
|
|
14
14
|
_findClusterServicePort(): Promise<void>;
|
15
15
|
/**
|
16
16
|
* Gets next available port
|
17
|
-
* @return {Promise<number>}
|
18
17
|
*/
|
19
|
-
getNextAvailablePort(): Promise<number>;
|
18
|
+
getNextAvailablePort(startPort?: number): Promise<number>;
|
20
19
|
_checkIfPortIsUsed(port: number, host?: string): Promise<unknown>;
|
21
20
|
/**
|
22
21
|
* The port of this local cluster service itself
|
@@ -48,14 +48,23 @@ class ClusterService {
|
|
48
48
|
}
|
49
49
|
/**
|
50
50
|
* Gets next available port
|
51
|
-
* @return {Promise<number>}
|
52
51
|
*/
|
53
|
-
async getNextAvailablePort() {
|
52
|
+
async getNextAvailablePort(startPort = -1) {
|
53
|
+
let receivedStartPort = startPort > 0;
|
54
|
+
if (!receivedStartPort) {
|
55
|
+
startPort = this._currentPort;
|
56
|
+
}
|
54
57
|
while (true) {
|
55
|
-
while (this._reservedPorts.indexOf(
|
56
|
-
|
58
|
+
while (this._reservedPorts.indexOf(startPort) > -1) {
|
59
|
+
startPort++;
|
60
|
+
if (!receivedStartPort) {
|
61
|
+
this._currentPort = startPort;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
const nextPort = startPort++;
|
65
|
+
if (!receivedStartPort) {
|
66
|
+
this._currentPort = startPort;
|
57
67
|
}
|
58
|
-
const nextPort = this._currentPort++;
|
59
68
|
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
60
69
|
if (!isUsed) {
|
61
70
|
return nextPort;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { StormEventPage } from './events';
|
2
|
+
export declare class UIServer {
|
3
|
+
private readonly express;
|
4
|
+
private readonly systemId;
|
5
|
+
private port;
|
6
|
+
private server;
|
7
|
+
constructor(systemId: string);
|
8
|
+
start(): Promise<void>;
|
9
|
+
close(): void;
|
10
|
+
resolveUrl(screenData: StormEventPage): string;
|
11
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.UIServer = void 0;
|
7
|
+
/**
|
8
|
+
* Copyright 2023 Kapeta Inc.
|
9
|
+
* SPDX-License-Identifier: BUSL-1.1
|
10
|
+
*/
|
11
|
+
const express_1 = __importDefault(require("express"));
|
12
|
+
const page_utils_1 = require("./page-utils");
|
13
|
+
const clusterService_1 = require("../clusterService");
|
14
|
+
class UIServer {
|
15
|
+
express;
|
16
|
+
systemId;
|
17
|
+
port = 50000;
|
18
|
+
server;
|
19
|
+
constructor(systemId) {
|
20
|
+
this.systemId = systemId;
|
21
|
+
this.express = (0, express_1.default)();
|
22
|
+
}
|
23
|
+
async start() {
|
24
|
+
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.all('/*', async (req, res) => {
|
26
|
+
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
|
+
});
|
28
|
+
return new Promise((resolve) => {
|
29
|
+
this.server = this.express.listen(this.port, () => {
|
30
|
+
console.log(`UI Server started on port ${this.port}`);
|
31
|
+
resolve();
|
32
|
+
});
|
33
|
+
});
|
34
|
+
}
|
35
|
+
close() {
|
36
|
+
if (this.server) {
|
37
|
+
console.log('UI Server closed on port: %s', this.port);
|
38
|
+
this.server.close();
|
39
|
+
this.server = undefined;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
resolveUrl(screenData) {
|
43
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
44
|
+
return `http://localhost:${this.port}${path}`;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
exports.UIServer = UIServer;
|
@@ -272,12 +272,14 @@ export interface StormEventPhases {
|
|
272
272
|
}
|
273
273
|
export interface Page {
|
274
274
|
name: string;
|
275
|
+
filename: string;
|
275
276
|
title: string;
|
276
277
|
description: string;
|
277
278
|
content: string;
|
278
279
|
path: string;
|
279
280
|
method: string;
|
280
281
|
conversationId: string;
|
282
|
+
prompt: string;
|
281
283
|
}
|
282
284
|
export interface StormEventPage {
|
283
285
|
type: 'PAGE';
|
@@ -285,6 +287,23 @@ export interface StormEventPage {
|
|
285
287
|
created: number;
|
286
288
|
payload: Page;
|
287
289
|
}
|
290
|
+
export interface StormEventPageUrl {
|
291
|
+
type: 'PAGE_URL';
|
292
|
+
reason: string;
|
293
|
+
created: number;
|
294
|
+
payload: {
|
295
|
+
id: string;
|
296
|
+
name: string;
|
297
|
+
filename: string;
|
298
|
+
title: string;
|
299
|
+
description: string;
|
300
|
+
path: string;
|
301
|
+
url: string;
|
302
|
+
method: string;
|
303
|
+
conversationId: string;
|
304
|
+
prompt: string;
|
305
|
+
};
|
306
|
+
}
|
288
307
|
export interface UserJourneyScreen {
|
289
308
|
name: string;
|
290
309
|
title: string;
|
@@ -304,5 +323,5 @@ export interface StormEventUserJourney {
|
|
304
323
|
created: number;
|
305
324
|
payload: UserJourney;
|
306
325
|
}
|
307
|
-
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 | StormEventPage;
|
326
|
+
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 | StormEventPage | StormEventPageUrl;
|
308
327
|
export {};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import { StormEventPage } from './events';
|
6
|
+
import { Response } from 'express';
|
7
|
+
export declare const SystemIdHeader = "System-Id";
|
8
|
+
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
9
|
+
path: string;
|
10
|
+
}>;
|
11
|
+
export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
|
12
|
+
export declare function readPageFromDisk(systemId: string, path: string, method: string, res: Response): void;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
8
|
+
const path_1 = __importDefault(require("path"));
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
10
|
+
exports.SystemIdHeader = 'System-Id';
|
11
|
+
async function writePageToDisk(systemId, event) {
|
12
|
+
const path = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, event.payload.path, event.payload.method.toLowerCase(), 'index.html');
|
13
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
|
14
|
+
await fs_extra_1.default.writeFile(path, event.payload.content);
|
15
|
+
console.log(`Page written to disk: ${event.payload.title} > ${path}`);
|
16
|
+
return {
|
17
|
+
path,
|
18
|
+
};
|
19
|
+
}
|
20
|
+
exports.writePageToDisk = writePageToDisk;
|
21
|
+
function readPageFromDiskAsString(systemId, path, method) {
|
22
|
+
const filePath = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
23
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
24
|
+
return null;
|
25
|
+
}
|
26
|
+
return fs_extra_1.default.readFileSync(filePath, 'utf8');
|
27
|
+
}
|
28
|
+
exports.readPageFromDiskAsString = readPageFromDiskAsString;
|
29
|
+
function readPageFromDisk(systemId, path, method, res) {
|
30
|
+
const filePath = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
31
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
32
|
+
res.status(404).send('Page not found');
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
res.type(filePath.split('.').pop());
|
36
|
+
const content = fs_extra_1.default.readFileSync(filePath, 'utf8');
|
37
|
+
res.write(content);
|
38
|
+
res.end();
|
39
|
+
}
|
40
|
+
exports.readPageFromDisk = readPageFromDisk;
|
@@ -20,14 +20,48 @@ const codegen_1 = require("./codegen");
|
|
20
20
|
const assetManager_1 = require("../assetManager");
|
21
21
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
22
22
|
const PromiseQueue_1 = require("./PromiseQueue");
|
23
|
+
const page_utils_1 = require("./page-utils");
|
24
|
+
const UIServer_1 = require("./UIServer");
|
25
|
+
const UI_SERVERS = {};
|
23
26
|
const router = (0, express_promise_router_1.default)();
|
24
27
|
router.use('/', cors_1.corsHandler);
|
25
28
|
router.use('/', stringBody_1.stringBody);
|
26
|
-
|
27
|
-
|
29
|
+
function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
30
|
+
if (screenData.type === 'PAGE') {
|
31
|
+
const server = UI_SERVERS[mainConversationId];
|
32
|
+
if (!server) {
|
33
|
+
console.warn('No server found for conversation', mainConversationId);
|
34
|
+
}
|
35
|
+
screenData.payload.conversationId = innerConversationId;
|
36
|
+
return {
|
37
|
+
type: 'PAGE_URL',
|
38
|
+
reason: screenData.reason,
|
39
|
+
created: screenData.created,
|
40
|
+
payload: {
|
41
|
+
id: node_uuid_1.default.v4(),
|
42
|
+
name: screenData.payload.name,
|
43
|
+
title: screenData.payload.title,
|
44
|
+
filename: screenData.payload.filename,
|
45
|
+
description: screenData.payload.description,
|
46
|
+
prompt: screenData.payload.prompt,
|
47
|
+
path: screenData.payload.path,
|
48
|
+
url: server ? server.resolveUrl(screenData) : '',
|
49
|
+
method: screenData.payload.method,
|
50
|
+
conversationId: innerConversationId,
|
51
|
+
},
|
52
|
+
};
|
53
|
+
}
|
54
|
+
return screenData;
|
55
|
+
}
|
56
|
+
router.all('/ui/:systemId/serve/:method/*', async (req, res) => {
|
57
|
+
(0, page_utils_1.readPageFromDisk)(req.params.systemId, req.params[0], req.params.method, res);
|
58
|
+
});
|
59
|
+
router.post('/ui/screen', async (req, res) => {
|
28
60
|
try {
|
29
61
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
62
|
+
const systemId = req.headers[page_utils_1.SystemIdHeader.toLowerCase()];
|
30
63
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
64
|
+
aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
|
31
65
|
const screenStream = await stormClient_1.stormClient.createUIPage(aiRequest, conversationId);
|
32
66
|
onRequestAborted(req, res, () => {
|
33
67
|
screenStream.abort();
|
@@ -35,11 +69,21 @@ router.post('/:handle/ui/screen', async (req, res) => {
|
|
35
69
|
res.set('Content-Type', 'application/x-ndjson');
|
36
70
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
37
71
|
res.set(stormClient_1.ConversationIdHeader, screenStream.getConversationId());
|
72
|
+
const promises = [];
|
38
73
|
screenStream.on('data', (data) => {
|
39
|
-
|
74
|
+
switch (data.type) {
|
75
|
+
case 'PAGE':
|
76
|
+
console.log('Processing page event', data);
|
77
|
+
data.payload.conversationId = screenStream.getConversationId();
|
78
|
+
if (systemId) {
|
79
|
+
promises.push(sendPageEvent(systemId, data, res));
|
80
|
+
}
|
81
|
+
break;
|
82
|
+
}
|
40
83
|
sendEvent(res, data);
|
41
84
|
});
|
42
85
|
await waitForStormStream(screenStream);
|
86
|
+
await Promise.allSettled(promises);
|
43
87
|
sendDone(res);
|
44
88
|
}
|
45
89
|
catch (err) {
|
@@ -49,6 +93,18 @@ router.post('/:handle/ui/screen', async (req, res) => {
|
|
49
93
|
}
|
50
94
|
}
|
51
95
|
});
|
96
|
+
router.delete('/:handle/ui', async (req, res) => {
|
97
|
+
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
98
|
+
if (!conversationId) {
|
99
|
+
res.status(400).send('Missing conversation id');
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
const server = UI_SERVERS[conversationId];
|
103
|
+
if (server) {
|
104
|
+
server.close();
|
105
|
+
delete UI_SERVERS[conversationId];
|
106
|
+
}
|
107
|
+
});
|
52
108
|
router.post('/:handle/ui', async (req, res) => {
|
53
109
|
const handle = req.params.handle;
|
54
110
|
try {
|
@@ -62,10 +118,13 @@ router.post('/:handle/ui', async (req, res) => {
|
|
62
118
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
63
119
|
res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
|
64
120
|
const promises = {};
|
65
|
-
const queue = new PromiseQueue_1.PromiseQueue(
|
121
|
+
const queue = new PromiseQueue_1.PromiseQueue(5);
|
66
122
|
onRequestAborted(req, res, () => {
|
67
123
|
queue.cancel();
|
68
124
|
});
|
125
|
+
const systemId = userJourneysStream.getConversationId();
|
126
|
+
UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
|
127
|
+
await UI_SERVERS[systemId].start();
|
69
128
|
userJourneysStream.on('data', async (data) => {
|
70
129
|
try {
|
71
130
|
console.log('Processing user journey event', data);
|
@@ -91,15 +150,20 @@ router.post('/:handle/ui', async (req, res) => {
|
|
91
150
|
name: screen.name,
|
92
151
|
title: screen.title,
|
93
152
|
filename: screen.filename,
|
153
|
+
storage_prefix: userJourneysStream.getConversationId() + '_',
|
94
154
|
}, innerConversationId);
|
155
|
+
const promises = [];
|
95
156
|
screenStream.on('data', (screenData) => {
|
96
157
|
if (screenData.type === 'PAGE') {
|
97
158
|
screenData.payload.conversationId = innerConversationId;
|
159
|
+
promises.push(sendPageEvent(userJourneysStream.getConversationId(), screenData, res));
|
160
|
+
}
|
161
|
+
else {
|
162
|
+
sendEvent(res, screenData);
|
98
163
|
}
|
99
|
-
sendEvent(res, screenData);
|
100
164
|
});
|
101
165
|
screenStream.on('end', () => {
|
102
|
-
|
166
|
+
Promise.allSettled(promises).finally(resolve);
|
103
167
|
});
|
104
168
|
}
|
105
169
|
catch (e) {
|
@@ -131,16 +195,45 @@ router.post('/ui/edit', async (req, res) => {
|
|
131
195
|
try {
|
132
196
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
133
197
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
134
|
-
const
|
198
|
+
const pages = aiRequest.prompt.pages
|
199
|
+
.map((page) => {
|
200
|
+
const content = (0, page_utils_1.readPageFromDiskAsString)(conversationId, page.path, page.method);
|
201
|
+
if (!content) {
|
202
|
+
console.warn('Page not found', page);
|
203
|
+
return undefined;
|
204
|
+
}
|
205
|
+
return {
|
206
|
+
filename: page.filename,
|
207
|
+
path: page.path,
|
208
|
+
method: page.method,
|
209
|
+
title: page.title,
|
210
|
+
conversationId: page.conversationId,
|
211
|
+
prompt: page.prompt,
|
212
|
+
name: page.name,
|
213
|
+
description: page.description,
|
214
|
+
content,
|
215
|
+
};
|
216
|
+
})
|
217
|
+
.filter((page) => !!page);
|
218
|
+
const editStream = await stormClient_1.stormClient.editPages({
|
219
|
+
...aiRequest.prompt,
|
220
|
+
pages,
|
221
|
+
}, conversationId);
|
135
222
|
onRequestAborted(req, res, () => {
|
136
223
|
editStream.abort();
|
137
224
|
});
|
138
225
|
res.set('Content-Type', 'application/x-ndjson');
|
139
226
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
140
227
|
res.set(stormClient_1.ConversationIdHeader, editStream.getConversationId());
|
228
|
+
const promises = [];
|
141
229
|
editStream.on('data', (data) => {
|
142
230
|
try {
|
143
|
-
|
231
|
+
if (data.type === 'PAGE') {
|
232
|
+
promises.push(sendPageEvent(editStream.getConversationId(), data, res));
|
233
|
+
}
|
234
|
+
else {
|
235
|
+
sendEvent(res, data);
|
236
|
+
}
|
144
237
|
}
|
145
238
|
catch (e) {
|
146
239
|
console.error('Failed to process event', e);
|
@@ -150,6 +243,7 @@ router.post('/ui/edit', async (req, res) => {
|
|
150
243
|
if (editStream.isAborted()) {
|
151
244
|
return;
|
152
245
|
}
|
246
|
+
await Promise.all(promises);
|
153
247
|
sendDone(res);
|
154
248
|
}
|
155
249
|
catch (err) {
|
@@ -351,4 +445,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
351
445
|
onAborted();
|
352
446
|
});
|
353
447
|
}
|
448
|
+
function sendPageEvent(mainConversationId, data, res) {
|
449
|
+
return (0, page_utils_1.writePageToDisk)(mainConversationId, data)
|
450
|
+
.catch((err) => {
|
451
|
+
console.error('Failed to write page to disk', err);
|
452
|
+
})
|
453
|
+
.then(() => {
|
454
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
455
|
+
});
|
456
|
+
}
|
354
457
|
exports.default = router;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
|
2
|
+
import { Page, StormEventPageUrl } from './events';
|
2
3
|
export declare const STORM_ID = "storm";
|
3
4
|
export declare const ConversationIdHeader = "Conversation-Id";
|
4
5
|
export interface UIPagePrompt {
|
@@ -9,14 +10,18 @@ export interface UIPagePrompt {
|
|
9
10
|
path: string;
|
10
11
|
method: string;
|
11
12
|
description: string;
|
13
|
+
storage_prefix: string;
|
12
14
|
}
|
13
15
|
export interface UIPageEditPrompt {
|
14
16
|
planDescription: string;
|
15
17
|
blockDescription: string;
|
16
|
-
pages:
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
pages: Page[];
|
19
|
+
prompt: string;
|
20
|
+
}
|
21
|
+
export interface UIPageEditRequest {
|
22
|
+
planDescription: string;
|
23
|
+
blockDescription: string;
|
24
|
+
pages: StormEventPageUrl['payload'][];
|
20
25
|
prompt: string;
|
21
26
|
}
|
22
27
|
declare class StormClient {
|
@@ -14,9 +14,8 @@ declare class ClusterService {
|
|
14
14
|
_findClusterServicePort(): Promise<void>;
|
15
15
|
/**
|
16
16
|
* Gets next available port
|
17
|
-
* @return {Promise<number>}
|
18
17
|
*/
|
19
|
-
getNextAvailablePort(): Promise<number>;
|
18
|
+
getNextAvailablePort(startPort?: number): Promise<number>;
|
20
19
|
_checkIfPortIsUsed(port: number, host?: string): Promise<unknown>;
|
21
20
|
/**
|
22
21
|
* The port of this local cluster service itself
|
@@ -48,14 +48,23 @@ class ClusterService {
|
|
48
48
|
}
|
49
49
|
/**
|
50
50
|
* Gets next available port
|
51
|
-
* @return {Promise<number>}
|
52
51
|
*/
|
53
|
-
async getNextAvailablePort() {
|
52
|
+
async getNextAvailablePort(startPort = -1) {
|
53
|
+
let receivedStartPort = startPort > 0;
|
54
|
+
if (!receivedStartPort) {
|
55
|
+
startPort = this._currentPort;
|
56
|
+
}
|
54
57
|
while (true) {
|
55
|
-
while (this._reservedPorts.indexOf(
|
56
|
-
|
58
|
+
while (this._reservedPorts.indexOf(startPort) > -1) {
|
59
|
+
startPort++;
|
60
|
+
if (!receivedStartPort) {
|
61
|
+
this._currentPort = startPort;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
const nextPort = startPort++;
|
65
|
+
if (!receivedStartPort) {
|
66
|
+
this._currentPort = startPort;
|
57
67
|
}
|
58
|
-
const nextPort = this._currentPort++;
|
59
68
|
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
60
69
|
if (!isUsed) {
|
61
70
|
return nextPort;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { StormEventPage } from './events';
|
2
|
+
export declare class UIServer {
|
3
|
+
private readonly express;
|
4
|
+
private readonly systemId;
|
5
|
+
private port;
|
6
|
+
private server;
|
7
|
+
constructor(systemId: string);
|
8
|
+
start(): Promise<void>;
|
9
|
+
close(): void;
|
10
|
+
resolveUrl(screenData: StormEventPage): string;
|
11
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.UIServer = void 0;
|
7
|
+
/**
|
8
|
+
* Copyright 2023 Kapeta Inc.
|
9
|
+
* SPDX-License-Identifier: BUSL-1.1
|
10
|
+
*/
|
11
|
+
const express_1 = __importDefault(require("express"));
|
12
|
+
const page_utils_1 = require("./page-utils");
|
13
|
+
const clusterService_1 = require("../clusterService");
|
14
|
+
class UIServer {
|
15
|
+
express;
|
16
|
+
systemId;
|
17
|
+
port = 50000;
|
18
|
+
server;
|
19
|
+
constructor(systemId) {
|
20
|
+
this.systemId = systemId;
|
21
|
+
this.express = (0, express_1.default)();
|
22
|
+
}
|
23
|
+
async start() {
|
24
|
+
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.all('/*', async (req, res) => {
|
26
|
+
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
|
+
});
|
28
|
+
return new Promise((resolve) => {
|
29
|
+
this.server = this.express.listen(this.port, () => {
|
30
|
+
console.log(`UI Server started on port ${this.port}`);
|
31
|
+
resolve();
|
32
|
+
});
|
33
|
+
});
|
34
|
+
}
|
35
|
+
close() {
|
36
|
+
if (this.server) {
|
37
|
+
console.log('UI Server closed on port: %s', this.port);
|
38
|
+
this.server.close();
|
39
|
+
this.server = undefined;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
resolveUrl(screenData) {
|
43
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
44
|
+
return `http://localhost:${this.port}${path}`;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
exports.UIServer = UIServer;
|
@@ -272,12 +272,14 @@ export interface StormEventPhases {
|
|
272
272
|
}
|
273
273
|
export interface Page {
|
274
274
|
name: string;
|
275
|
+
filename: string;
|
275
276
|
title: string;
|
276
277
|
description: string;
|
277
278
|
content: string;
|
278
279
|
path: string;
|
279
280
|
method: string;
|
280
281
|
conversationId: string;
|
282
|
+
prompt: string;
|
281
283
|
}
|
282
284
|
export interface StormEventPage {
|
283
285
|
type: 'PAGE';
|
@@ -285,6 +287,23 @@ export interface StormEventPage {
|
|
285
287
|
created: number;
|
286
288
|
payload: Page;
|
287
289
|
}
|
290
|
+
export interface StormEventPageUrl {
|
291
|
+
type: 'PAGE_URL';
|
292
|
+
reason: string;
|
293
|
+
created: number;
|
294
|
+
payload: {
|
295
|
+
id: string;
|
296
|
+
name: string;
|
297
|
+
filename: string;
|
298
|
+
title: string;
|
299
|
+
description: string;
|
300
|
+
path: string;
|
301
|
+
url: string;
|
302
|
+
method: string;
|
303
|
+
conversationId: string;
|
304
|
+
prompt: string;
|
305
|
+
};
|
306
|
+
}
|
288
307
|
export interface UserJourneyScreen {
|
289
308
|
name: string;
|
290
309
|
title: string;
|
@@ -304,5 +323,5 @@ export interface StormEventUserJourney {
|
|
304
323
|
created: number;
|
305
324
|
payload: UserJourney;
|
306
325
|
}
|
307
|
-
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 | StormEventPage;
|
326
|
+
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 | StormEventPage | StormEventPageUrl;
|
308
327
|
export {};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import { StormEventPage } from './events';
|
6
|
+
import { Response } from 'express';
|
7
|
+
export declare const SystemIdHeader = "System-Id";
|
8
|
+
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
9
|
+
path: string;
|
10
|
+
}>;
|
11
|
+
export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
|
12
|
+
export declare function readPageFromDisk(systemId: string, path: string, method: string, res: Response): void;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
8
|
+
const path_1 = __importDefault(require("path"));
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
10
|
+
exports.SystemIdHeader = 'System-Id';
|
11
|
+
async function writePageToDisk(systemId, event) {
|
12
|
+
const path = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, event.payload.path, event.payload.method.toLowerCase(), 'index.html');
|
13
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
|
14
|
+
await fs_extra_1.default.writeFile(path, event.payload.content);
|
15
|
+
console.log(`Page written to disk: ${event.payload.title} > ${path}`);
|
16
|
+
return {
|
17
|
+
path,
|
18
|
+
};
|
19
|
+
}
|
20
|
+
exports.writePageToDisk = writePageToDisk;
|
21
|
+
function readPageFromDiskAsString(systemId, path, method) {
|
22
|
+
const filePath = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
23
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
24
|
+
return null;
|
25
|
+
}
|
26
|
+
return fs_extra_1.default.readFileSync(filePath, 'utf8');
|
27
|
+
}
|
28
|
+
exports.readPageFromDiskAsString = readPageFromDiskAsString;
|
29
|
+
function readPageFromDisk(systemId, path, method, res) {
|
30
|
+
const filePath = path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
31
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
32
|
+
res.status(404).send('Page not found');
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
res.type(filePath.split('.').pop());
|
36
|
+
const content = fs_extra_1.default.readFileSync(filePath, 'utf8');
|
37
|
+
res.write(content);
|
38
|
+
res.end();
|
39
|
+
}
|
40
|
+
exports.readPageFromDisk = readPageFromDisk;
|