@kapeta/local-cluster-service 0.62.1 → 0.63.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/storm/PageGenerator.d.ts +4 -2
- package/dist/cjs/src/storm/PageGenerator.js +17 -5
- package/dist/cjs/src/storm/UIServer.d.ts +1 -0
- package/dist/cjs/src/storm/UIServer.js +12 -2
- package/dist/cjs/src/storm/events.d.ts +10 -1
- package/dist/cjs/src/storm/page-utils.d.ts +1 -0
- package/dist/cjs/src/storm/page-utils.js +8 -1
- package/dist/cjs/src/storm/routes.js +22 -21
- package/dist/esm/src/storm/PageGenerator.d.ts +4 -2
- package/dist/esm/src/storm/PageGenerator.js +17 -5
- package/dist/esm/src/storm/UIServer.d.ts +1 -0
- package/dist/esm/src/storm/UIServer.js +12 -2
- package/dist/esm/src/storm/events.d.ts +10 -1
- package/dist/esm/src/storm/page-utils.d.ts +1 -0
- package/dist/esm/src/storm/page-utils.js +8 -1
- package/dist/esm/src/storm/routes.js +22 -21
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +33 -6
- package/src/storm/UIServer.ts +16 -2
- package/src/storm/events.ts +19 -2
- package/src/storm/page-utils.ts +7 -0
- package/src/storm/routes.ts +26 -25
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.63.0](https://github.com/kapetacom/local-cluster-service/compare/v0.62.2...v0.63.0) (2024-08-15)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Add reset endpoint for resetting localStorage in UI server ([#218](https://github.com/kapetacom/local-cluster-service/issues/218)) ([1dd1edc](https://github.com/kapetacom/local-cluster-service/commit/1dd1edcba982f40b853569fd408e6ed561495686))
|
7
|
+
|
8
|
+
## [0.62.2](https://github.com/kapetacom/local-cluster-service/compare/v0.62.1...v0.62.2) (2024-08-14)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Single page adjustments ([#217](https://github.com/kapetacom/local-cluster-service/issues/217)) ([8d0f7e6](https://github.com/kapetacom/local-cluster-service/commit/8d0f7e6b9a3fdd971559a4c9b06b78db38145b4d))
|
14
|
+
|
1
15
|
## [0.62.1](https://github.com/kapetacom/local-cluster-service/compare/v0.62.0...v0.62.1) (2024-08-14)
|
2
16
|
|
3
17
|
|
@@ -4,18 +4,20 @@
|
|
4
4
|
*/
|
5
5
|
/// <reference types="node" />
|
6
6
|
import { UIPagePrompt } from './stormClient';
|
7
|
-
import { ReferenceClassification, StormEvent, StormEventPage } from './events';
|
7
|
+
import { ReferenceClassification, StormEvent, StormEventPage, UIShell } from './events';
|
8
8
|
import { EventEmitter } from 'node:events';
|
9
9
|
export declare class PageQueue extends EventEmitter {
|
10
10
|
private readonly queue;
|
11
11
|
private readonly systemId;
|
12
12
|
private readonly references;
|
13
|
+
private uiShells;
|
13
14
|
constructor(systemId: string, concurrency?: number);
|
14
15
|
on(event: 'event', listener: (data: StormEvent) => void): this;
|
15
16
|
on(event: 'page', listener: (data: StormEventPage) => void): this;
|
16
17
|
emit(type: 'event', event: StormEvent): boolean;
|
17
18
|
emit(type: 'page', event: StormEventPage): boolean;
|
18
|
-
|
19
|
+
addUiShell(uiShell: UIShell): void;
|
20
|
+
addPrompt(initialPrompt: Omit<UIPagePrompt, 'shell_page'>, conversationId?: string, overwrite?: boolean): Promise<void>;
|
19
21
|
private addPageGenerator;
|
20
22
|
cancel(): void;
|
21
23
|
wait(): Promise<void>;
|
@@ -12,10 +12,12 @@ const node_uuid_1 = __importDefault(require("node-uuid"));
|
|
12
12
|
const stormClient_1 = require("./stormClient");
|
13
13
|
const node_events_1 = require("node:events");
|
14
14
|
const PromiseQueue_1 = require("./PromiseQueue");
|
15
|
+
const page_utils_1 = require("./page-utils");
|
15
16
|
class PageQueue extends node_events_1.EventEmitter {
|
16
17
|
queue;
|
17
18
|
systemId;
|
18
19
|
references = new Map();
|
20
|
+
uiShells = [];
|
19
21
|
constructor(systemId, concurrency = 5) {
|
20
22
|
super();
|
21
23
|
this.systemId = systemId;
|
@@ -27,14 +29,24 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
27
29
|
emit(eventName, ...args) {
|
28
30
|
return super.emit(eventName, ...args);
|
29
31
|
}
|
30
|
-
|
31
|
-
|
32
|
+
addUiShell(uiShell) {
|
33
|
+
this.uiShells.push(uiShell);
|
34
|
+
}
|
35
|
+
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false) {
|
36
|
+
if (!overwrite && this.references.has(initialPrompt.path)) {
|
32
37
|
console.log('Ignoring duplicate prompt', initialPrompt.path);
|
33
38
|
return Promise.resolve();
|
34
39
|
}
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
if (!overwrite && (0, page_utils_1.hasPageOnDisk)(this.systemId, initialPrompt.method, initialPrompt.path)) {
|
41
|
+
console.log('Ignoring prompt with existing page', initialPrompt.path);
|
42
|
+
return Promise.resolve();
|
43
|
+
}
|
44
|
+
const prompt = {
|
45
|
+
...initialPrompt,
|
46
|
+
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
47
|
+
};
|
48
|
+
const generator = new PageGenerator(prompt, conversationId);
|
49
|
+
this.references.set(prompt.path, generator);
|
38
50
|
return this.addPageGenerator(generator);
|
39
51
|
}
|
40
52
|
async addPageGenerator(generator) {
|
@@ -22,6 +22,13 @@ class UIServer {
|
|
22
22
|
}
|
23
23
|
async start() {
|
24
24
|
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.get('/_reset', (req, res) => {
|
26
|
+
res.send(`
|
27
|
+
<script>
|
28
|
+
window.localStorage.clear();
|
29
|
+
window.sessionStorage.clear();
|
30
|
+
</script>`);
|
31
|
+
});
|
25
32
|
this.express.all('/*', async (req, res) => {
|
26
33
|
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
34
|
});
|
@@ -40,8 +47,11 @@ class UIServer {
|
|
40
47
|
}
|
41
48
|
}
|
42
49
|
resolveUrl(screenData) {
|
43
|
-
|
44
|
-
|
50
|
+
return this.resolveUrlFromPath(screenData.payload.path);
|
51
|
+
}
|
52
|
+
resolveUrlFromPath(path) {
|
53
|
+
const resolvedPath = path.startsWith('/') ? path : `/${path}`;
|
54
|
+
return `http://localhost:${this.port}${resolvedPath}`;
|
45
55
|
}
|
46
56
|
}
|
47
57
|
exports.UIServer = UIServer;
|
@@ -375,5 +375,14 @@ export interface StormEventReferenceClassification {
|
|
375
375
|
created: number;
|
376
376
|
payload: ReferenceClassification;
|
377
377
|
}
|
378
|
-
export
|
378
|
+
export interface StormEventUIStarted {
|
379
|
+
type: 'UI_SERVER_STARTED';
|
380
|
+
reason: string;
|
381
|
+
created: number;
|
382
|
+
payload: {
|
383
|
+
conversationId: string;
|
384
|
+
resetUrl: string;
|
385
|
+
};
|
386
|
+
}
|
387
|
+
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;
|
379
388
|
export {};
|
@@ -9,6 +9,7 @@ export declare const SystemIdHeader = "System-Id";
|
|
9
9
|
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
10
10
|
path: string;
|
11
11
|
}>;
|
12
|
+
export declare function hasPageOnDisk(systemId: string, method: string, path: string): boolean;
|
12
13
|
export declare function resolveReadPath(systemId: string, path: string, method: string): string | null;
|
13
14
|
export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
|
14
15
|
export declare function readPageFromDisk(systemId: string, path: string, method: string, res: Response): void;
|
@@ -3,7 +3,7 @@ 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.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
6
|
+
exports.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.hasPageOnDisk = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
7
7
|
const node_os_1 = __importDefault(require("node:os"));
|
8
8
|
const path_1 = __importDefault(require("path"));
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
@@ -26,6 +26,13 @@ async function writePageToDisk(systemId, event) {
|
|
26
26
|
};
|
27
27
|
}
|
28
28
|
exports.writePageToDisk = writePageToDisk;
|
29
|
+
function hasPageOnDisk(systemId, method, path) {
|
30
|
+
const baseDir = getBaseDir(systemId);
|
31
|
+
const filePath = getFilePath(method);
|
32
|
+
const fullPath = path_1.default.join(baseDir, normalizePath(path), filePath);
|
33
|
+
return fs_extra_1.default.existsSync(fullPath);
|
34
|
+
}
|
35
|
+
exports.hasPageOnDisk = hasPageOnDisk;
|
29
36
|
function getBaseDir(systemId) {
|
30
37
|
return path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId);
|
31
38
|
}
|
@@ -71,19 +71,13 @@ router.post('/ui/screen', async (req, res) => {
|
|
71
71
|
onRequestAborted(req, res, () => {
|
72
72
|
queue.cancel();
|
73
73
|
});
|
74
|
-
await queue.addPrompt(aiRequest);
|
75
74
|
const promises = [];
|
76
75
|
queue.on('page', (data) => {
|
77
|
-
|
78
|
-
|
79
|
-
console.log('Processing page event', data);
|
80
|
-
if (systemId) {
|
81
|
-
promises.push(sendPageEvent(systemId, data, res));
|
82
|
-
}
|
83
|
-
break;
|
76
|
+
if (systemId) {
|
77
|
+
promises.push(sendPageEvent(systemId, data, res));
|
84
78
|
}
|
85
|
-
sendEvent(res, data);
|
86
79
|
});
|
80
|
+
await queue.addPrompt(aiRequest, conversationId, true);
|
87
81
|
await queue.wait();
|
88
82
|
await Promise.allSettled(promises);
|
89
83
|
sendDone(res);
|
@@ -233,7 +227,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
233
227
|
onRequestAborted(req, res, () => {
|
234
228
|
shellsStream.abort();
|
235
229
|
});
|
236
|
-
const
|
230
|
+
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
237
231
|
shellsStream.on('data', (data) => {
|
238
232
|
console.log('Processing shell event', data);
|
239
233
|
sendEvent(res, data);
|
@@ -243,7 +237,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
243
237
|
if (shellsStream.isAborted()) {
|
244
238
|
return;
|
245
239
|
}
|
246
|
-
|
240
|
+
queue.addUiShell(data.payload);
|
247
241
|
});
|
248
242
|
shellsStream.on('error', (error) => {
|
249
243
|
console.error('Error on shellsStream', error);
|
@@ -253,8 +247,16 @@ router.post('/:handle/ui', async (req, res) => {
|
|
253
247
|
await waitForStormStream(shellsStream);
|
254
248
|
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
255
249
|
await UI_SERVERS[outerConversationId].start();
|
250
|
+
sendEvent(res, {
|
251
|
+
type: 'UI_SERVER_STARTED',
|
252
|
+
reason: '',
|
253
|
+
payload: {
|
254
|
+
conversationId: outerConversationId,
|
255
|
+
resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
|
256
|
+
},
|
257
|
+
created: Date.now(),
|
258
|
+
});
|
256
259
|
// Get the pages (5 at a time)
|
257
|
-
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
258
260
|
const pagePromises = [];
|
259
261
|
onRequestAborted(req, res, () => {
|
260
262
|
queue.cancel();
|
@@ -276,7 +278,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
276
278
|
title: screen.title,
|
277
279
|
filename: screen.filename,
|
278
280
|
storage_prefix: outerConversationId + '_',
|
279
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
280
281
|
}));
|
281
282
|
}
|
282
283
|
await queue.wait();
|
@@ -536,7 +537,7 @@ function streamStormPartialResponse(result, res) {
|
|
536
537
|
switch (data.type) {
|
537
538
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
538
539
|
case 'ERROR_INTERNAL':
|
539
|
-
console.log(
|
540
|
+
console.log('Error internal', data);
|
540
541
|
return;
|
541
542
|
}
|
542
543
|
sendEvent(res, data);
|
@@ -555,13 +556,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
555
556
|
onAborted();
|
556
557
|
});
|
557
558
|
}
|
558
|
-
function sendPageEvent(mainConversationId, data, res) {
|
559
|
-
|
560
|
-
.
|
559
|
+
async function sendPageEvent(mainConversationId, data, res) {
|
560
|
+
try {
|
561
|
+
await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
|
562
|
+
}
|
563
|
+
catch (err) {
|
561
564
|
console.error('Failed to write page to disk', err);
|
562
|
-
}
|
563
|
-
|
564
|
-
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
565
|
-
});
|
565
|
+
}
|
566
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
566
567
|
}
|
567
568
|
exports.default = router;
|
@@ -4,18 +4,20 @@
|
|
4
4
|
*/
|
5
5
|
/// <reference types="node" />
|
6
6
|
import { UIPagePrompt } from './stormClient';
|
7
|
-
import { ReferenceClassification, StormEvent, StormEventPage } from './events';
|
7
|
+
import { ReferenceClassification, StormEvent, StormEventPage, UIShell } from './events';
|
8
8
|
import { EventEmitter } from 'node:events';
|
9
9
|
export declare class PageQueue extends EventEmitter {
|
10
10
|
private readonly queue;
|
11
11
|
private readonly systemId;
|
12
12
|
private readonly references;
|
13
|
+
private uiShells;
|
13
14
|
constructor(systemId: string, concurrency?: number);
|
14
15
|
on(event: 'event', listener: (data: StormEvent) => void): this;
|
15
16
|
on(event: 'page', listener: (data: StormEventPage) => void): this;
|
16
17
|
emit(type: 'event', event: StormEvent): boolean;
|
17
18
|
emit(type: 'page', event: StormEventPage): boolean;
|
18
|
-
|
19
|
+
addUiShell(uiShell: UIShell): void;
|
20
|
+
addPrompt(initialPrompt: Omit<UIPagePrompt, 'shell_page'>, conversationId?: string, overwrite?: boolean): Promise<void>;
|
19
21
|
private addPageGenerator;
|
20
22
|
cancel(): void;
|
21
23
|
wait(): Promise<void>;
|
@@ -12,10 +12,12 @@ const node_uuid_1 = __importDefault(require("node-uuid"));
|
|
12
12
|
const stormClient_1 = require("./stormClient");
|
13
13
|
const node_events_1 = require("node:events");
|
14
14
|
const PromiseQueue_1 = require("./PromiseQueue");
|
15
|
+
const page_utils_1 = require("./page-utils");
|
15
16
|
class PageQueue extends node_events_1.EventEmitter {
|
16
17
|
queue;
|
17
18
|
systemId;
|
18
19
|
references = new Map();
|
20
|
+
uiShells = [];
|
19
21
|
constructor(systemId, concurrency = 5) {
|
20
22
|
super();
|
21
23
|
this.systemId = systemId;
|
@@ -27,14 +29,24 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
27
29
|
emit(eventName, ...args) {
|
28
30
|
return super.emit(eventName, ...args);
|
29
31
|
}
|
30
|
-
|
31
|
-
|
32
|
+
addUiShell(uiShell) {
|
33
|
+
this.uiShells.push(uiShell);
|
34
|
+
}
|
35
|
+
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false) {
|
36
|
+
if (!overwrite && this.references.has(initialPrompt.path)) {
|
32
37
|
console.log('Ignoring duplicate prompt', initialPrompt.path);
|
33
38
|
return Promise.resolve();
|
34
39
|
}
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
if (!overwrite && (0, page_utils_1.hasPageOnDisk)(this.systemId, initialPrompt.method, initialPrompt.path)) {
|
41
|
+
console.log('Ignoring prompt with existing page', initialPrompt.path);
|
42
|
+
return Promise.resolve();
|
43
|
+
}
|
44
|
+
const prompt = {
|
45
|
+
...initialPrompt,
|
46
|
+
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
47
|
+
};
|
48
|
+
const generator = new PageGenerator(prompt, conversationId);
|
49
|
+
this.references.set(prompt.path, generator);
|
38
50
|
return this.addPageGenerator(generator);
|
39
51
|
}
|
40
52
|
async addPageGenerator(generator) {
|
@@ -22,6 +22,13 @@ class UIServer {
|
|
22
22
|
}
|
23
23
|
async start() {
|
24
24
|
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.get('/_reset', (req, res) => {
|
26
|
+
res.send(`
|
27
|
+
<script>
|
28
|
+
window.localStorage.clear();
|
29
|
+
window.sessionStorage.clear();
|
30
|
+
</script>`);
|
31
|
+
});
|
25
32
|
this.express.all('/*', async (req, res) => {
|
26
33
|
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
34
|
});
|
@@ -40,8 +47,11 @@ class UIServer {
|
|
40
47
|
}
|
41
48
|
}
|
42
49
|
resolveUrl(screenData) {
|
43
|
-
|
44
|
-
|
50
|
+
return this.resolveUrlFromPath(screenData.payload.path);
|
51
|
+
}
|
52
|
+
resolveUrlFromPath(path) {
|
53
|
+
const resolvedPath = path.startsWith('/') ? path : `/${path}`;
|
54
|
+
return `http://localhost:${this.port}${resolvedPath}`;
|
45
55
|
}
|
46
56
|
}
|
47
57
|
exports.UIServer = UIServer;
|
@@ -375,5 +375,14 @@ export interface StormEventReferenceClassification {
|
|
375
375
|
created: number;
|
376
376
|
payload: ReferenceClassification;
|
377
377
|
}
|
378
|
-
export
|
378
|
+
export interface StormEventUIStarted {
|
379
|
+
type: 'UI_SERVER_STARTED';
|
380
|
+
reason: string;
|
381
|
+
created: number;
|
382
|
+
payload: {
|
383
|
+
conversationId: string;
|
384
|
+
resetUrl: string;
|
385
|
+
};
|
386
|
+
}
|
387
|
+
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;
|
379
388
|
export {};
|
@@ -9,6 +9,7 @@ export declare const SystemIdHeader = "System-Id";
|
|
9
9
|
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
10
10
|
path: string;
|
11
11
|
}>;
|
12
|
+
export declare function hasPageOnDisk(systemId: string, method: string, path: string): boolean;
|
12
13
|
export declare function resolveReadPath(systemId: string, path: string, method: string): string | null;
|
13
14
|
export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
|
14
15
|
export declare function readPageFromDisk(systemId: string, path: string, method: string, res: Response): void;
|
@@ -3,7 +3,7 @@ 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.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
6
|
+
exports.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.hasPageOnDisk = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
7
7
|
const node_os_1 = __importDefault(require("node:os"));
|
8
8
|
const path_1 = __importDefault(require("path"));
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
@@ -26,6 +26,13 @@ async function writePageToDisk(systemId, event) {
|
|
26
26
|
};
|
27
27
|
}
|
28
28
|
exports.writePageToDisk = writePageToDisk;
|
29
|
+
function hasPageOnDisk(systemId, method, path) {
|
30
|
+
const baseDir = getBaseDir(systemId);
|
31
|
+
const filePath = getFilePath(method);
|
32
|
+
const fullPath = path_1.default.join(baseDir, normalizePath(path), filePath);
|
33
|
+
return fs_extra_1.default.existsSync(fullPath);
|
34
|
+
}
|
35
|
+
exports.hasPageOnDisk = hasPageOnDisk;
|
29
36
|
function getBaseDir(systemId) {
|
30
37
|
return path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId);
|
31
38
|
}
|
@@ -71,19 +71,13 @@ router.post('/ui/screen', async (req, res) => {
|
|
71
71
|
onRequestAborted(req, res, () => {
|
72
72
|
queue.cancel();
|
73
73
|
});
|
74
|
-
await queue.addPrompt(aiRequest);
|
75
74
|
const promises = [];
|
76
75
|
queue.on('page', (data) => {
|
77
|
-
|
78
|
-
|
79
|
-
console.log('Processing page event', data);
|
80
|
-
if (systemId) {
|
81
|
-
promises.push(sendPageEvent(systemId, data, res));
|
82
|
-
}
|
83
|
-
break;
|
76
|
+
if (systemId) {
|
77
|
+
promises.push(sendPageEvent(systemId, data, res));
|
84
78
|
}
|
85
|
-
sendEvent(res, data);
|
86
79
|
});
|
80
|
+
await queue.addPrompt(aiRequest, conversationId, true);
|
87
81
|
await queue.wait();
|
88
82
|
await Promise.allSettled(promises);
|
89
83
|
sendDone(res);
|
@@ -233,7 +227,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
233
227
|
onRequestAborted(req, res, () => {
|
234
228
|
shellsStream.abort();
|
235
229
|
});
|
236
|
-
const
|
230
|
+
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
237
231
|
shellsStream.on('data', (data) => {
|
238
232
|
console.log('Processing shell event', data);
|
239
233
|
sendEvent(res, data);
|
@@ -243,7 +237,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
243
237
|
if (shellsStream.isAborted()) {
|
244
238
|
return;
|
245
239
|
}
|
246
|
-
|
240
|
+
queue.addUiShell(data.payload);
|
247
241
|
});
|
248
242
|
shellsStream.on('error', (error) => {
|
249
243
|
console.error('Error on shellsStream', error);
|
@@ -253,8 +247,16 @@ router.post('/:handle/ui', async (req, res) => {
|
|
253
247
|
await waitForStormStream(shellsStream);
|
254
248
|
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
255
249
|
await UI_SERVERS[outerConversationId].start();
|
250
|
+
sendEvent(res, {
|
251
|
+
type: 'UI_SERVER_STARTED',
|
252
|
+
reason: '',
|
253
|
+
payload: {
|
254
|
+
conversationId: outerConversationId,
|
255
|
+
resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
|
256
|
+
},
|
257
|
+
created: Date.now(),
|
258
|
+
});
|
256
259
|
// Get the pages (5 at a time)
|
257
|
-
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
258
260
|
const pagePromises = [];
|
259
261
|
onRequestAborted(req, res, () => {
|
260
262
|
queue.cancel();
|
@@ -276,7 +278,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
276
278
|
title: screen.title,
|
277
279
|
filename: screen.filename,
|
278
280
|
storage_prefix: outerConversationId + '_',
|
279
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
280
281
|
}));
|
281
282
|
}
|
282
283
|
await queue.wait();
|
@@ -536,7 +537,7 @@ function streamStormPartialResponse(result, res) {
|
|
536
537
|
switch (data.type) {
|
537
538
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
538
539
|
case 'ERROR_INTERNAL':
|
539
|
-
console.log(
|
540
|
+
console.log('Error internal', data);
|
540
541
|
return;
|
541
542
|
}
|
542
543
|
sendEvent(res, data);
|
@@ -555,13 +556,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
555
556
|
onAborted();
|
556
557
|
});
|
557
558
|
}
|
558
|
-
function sendPageEvent(mainConversationId, data, res) {
|
559
|
-
|
560
|
-
.
|
559
|
+
async function sendPageEvent(mainConversationId, data, res) {
|
560
|
+
try {
|
561
|
+
await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
|
562
|
+
}
|
563
|
+
catch (err) {
|
561
564
|
console.error('Failed to write page to disk', err);
|
562
|
-
}
|
563
|
-
|
564
|
-
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
565
|
-
});
|
565
|
+
}
|
566
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
566
567
|
}
|
567
568
|
exports.default = router;
|
package/package.json
CHANGED
@@ -5,14 +5,22 @@
|
|
5
5
|
|
6
6
|
import uuid from 'node-uuid';
|
7
7
|
import { stormClient, UIPagePrompt } from './stormClient';
|
8
|
-
import {
|
8
|
+
import {
|
9
|
+
ReferenceClassification,
|
10
|
+
StormEvent,
|
11
|
+
StormEventPage,
|
12
|
+
StormEventReferenceClassification,
|
13
|
+
UIShell,
|
14
|
+
} from './events';
|
9
15
|
import { EventEmitter } from 'node:events';
|
10
16
|
import { PromiseQueue } from './PromiseQueue';
|
17
|
+
import { hasPageOnDisk } from './page-utils';
|
11
18
|
|
12
19
|
export class PageQueue extends EventEmitter {
|
13
20
|
private readonly queue: PromiseQueue;
|
14
21
|
private readonly systemId: string;
|
15
22
|
private readonly references: Map<string, PageGenerator> = new Map();
|
23
|
+
private uiShells: UIShell[] = [];
|
16
24
|
|
17
25
|
constructor(systemId: string, concurrency: number = 5) {
|
18
26
|
super();
|
@@ -33,14 +41,33 @@ export class PageQueue extends EventEmitter {
|
|
33
41
|
return super.emit(eventName, ...args);
|
34
42
|
}
|
35
43
|
|
36
|
-
public
|
37
|
-
|
44
|
+
public addUiShell(uiShell: UIShell) {
|
45
|
+
this.uiShells.push(uiShell);
|
46
|
+
}
|
47
|
+
|
48
|
+
public addPrompt(
|
49
|
+
initialPrompt: Omit<UIPagePrompt, 'shell_page'>,
|
50
|
+
conversationId: string = uuid.v4(),
|
51
|
+
overwrite: boolean = false
|
52
|
+
) {
|
53
|
+
if (!overwrite && this.references.has(initialPrompt.path)) {
|
38
54
|
console.log('Ignoring duplicate prompt', initialPrompt.path);
|
39
55
|
return Promise.resolve();
|
40
56
|
}
|
41
|
-
|
42
|
-
|
43
|
-
|
57
|
+
|
58
|
+
if (!overwrite && hasPageOnDisk(this.systemId, initialPrompt.method, initialPrompt.path)) {
|
59
|
+
console.log('Ignoring prompt with existing page', initialPrompt.path);
|
60
|
+
return Promise.resolve();
|
61
|
+
}
|
62
|
+
|
63
|
+
const prompt: UIPagePrompt = {
|
64
|
+
...initialPrompt,
|
65
|
+
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
66
|
+
};
|
67
|
+
|
68
|
+
const generator = new PageGenerator(prompt, conversationId);
|
69
|
+
this.references.set(prompt.path, generator);
|
70
|
+
|
44
71
|
return this.addPageGenerator(generator);
|
45
72
|
}
|
46
73
|
|
package/src/storm/UIServer.ts
CHANGED
@@ -23,6 +23,16 @@ export class UIServer {
|
|
23
23
|
public async start() {
|
24
24
|
this.port = await clusterService.getNextAvailablePort(this.port);
|
25
25
|
|
26
|
+
this.express.get('/_reset', (req: Request, res: Response) => {
|
27
|
+
res.send(
|
28
|
+
`
|
29
|
+
<script>
|
30
|
+
window.localStorage.clear();
|
31
|
+
window.sessionStorage.clear();
|
32
|
+
</script>`
|
33
|
+
);
|
34
|
+
});
|
35
|
+
|
26
36
|
this.express.all('/*', async (req: Request, res: Response) => {
|
27
37
|
readPageFromDisk(this.systemId, req.params[0], req.method, res);
|
28
38
|
});
|
@@ -44,7 +54,11 @@ export class UIServer {
|
|
44
54
|
}
|
45
55
|
|
46
56
|
resolveUrl(screenData: StormEventPage) {
|
47
|
-
|
48
|
-
|
57
|
+
return this.resolveUrlFromPath(screenData.payload.path);
|
58
|
+
}
|
59
|
+
|
60
|
+
resolveUrlFromPath(path: string) {
|
61
|
+
const resolvedPath = path.startsWith('/') ? path : `/${path}`;
|
62
|
+
return `http://localhost:${this.port}${resolvedPath}`;
|
49
63
|
}
|
50
64
|
}
|
package/src/storm/events.ts
CHANGED
@@ -264,7 +264,13 @@ export interface StormEventFileChunk extends StormEventFileBase {
|
|
264
264
|
}
|
265
265
|
|
266
266
|
export interface StormEventApiBase {
|
267
|
-
type:
|
267
|
+
type:
|
268
|
+
| 'API_STREAM_CHUNK'
|
269
|
+
| 'API_STREAM_DONE'
|
270
|
+
| 'API_STREAM_FAILED'
|
271
|
+
| 'API_STREAM_STATE'
|
272
|
+
| 'API_STREAM_START'
|
273
|
+
| 'API_STREAM_CHUNK_RESET';
|
268
274
|
payload: StormEventFileBasePayload;
|
269
275
|
}
|
270
276
|
|
@@ -445,6 +451,16 @@ export interface StormEventReferenceClassification {
|
|
445
451
|
payload: ReferenceClassification;
|
446
452
|
}
|
447
453
|
|
454
|
+
export interface StormEventUIStarted {
|
455
|
+
type: 'UI_SERVER_STARTED';
|
456
|
+
reason: string;
|
457
|
+
created: number;
|
458
|
+
payload: {
|
459
|
+
conversationId: string;
|
460
|
+
resetUrl: string;
|
461
|
+
};
|
462
|
+
}
|
463
|
+
|
448
464
|
export type StormEvent =
|
449
465
|
| StormEventCreateBlock
|
450
466
|
| StormEventCreateConnection
|
@@ -477,4 +493,5 @@ export type StormEvent =
|
|
477
493
|
| StormEventPromptImprove
|
478
494
|
| StormEventLandingPage
|
479
495
|
| StormEventReferenceClassification
|
480
|
-
| StormEventApiBase
|
496
|
+
| StormEventApiBase
|
497
|
+
| StormEventUIStarted;
|
package/src/storm/page-utils.ts
CHANGED
@@ -34,6 +34,13 @@ export async function writePageToDisk(systemId: string, event: StormEventPage) {
|
|
34
34
|
};
|
35
35
|
}
|
36
36
|
|
37
|
+
export function hasPageOnDisk(systemId: string, method: string, path: string) {
|
38
|
+
const baseDir = getBaseDir(systemId);
|
39
|
+
const filePath = getFilePath(method);
|
40
|
+
const fullPath = Path.join(baseDir, normalizePath(path), filePath);
|
41
|
+
return FS.existsSync(fullPath);
|
42
|
+
}
|
43
|
+
|
37
44
|
function getBaseDir(systemId: string) {
|
38
45
|
return Path.join(os.tmpdir(), 'ai-systems', systemId);
|
39
46
|
}
|
package/src/storm/routes.ts
CHANGED
@@ -106,22 +106,16 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
106
106
|
queue.cancel();
|
107
107
|
});
|
108
108
|
|
109
|
-
await queue.addPrompt(aiRequest);
|
110
|
-
|
111
109
|
const promises: Promise<void>[] = [];
|
112
|
-
queue.on('page', (data) => {
|
113
|
-
switch (data.type) {
|
114
|
-
case 'PAGE':
|
115
|
-
console.log('Processing page event', data);
|
116
110
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
break;
|
111
|
+
queue.on('page', (data) => {
|
112
|
+
if (systemId) {
|
113
|
+
promises.push(sendPageEvent(systemId, data, res));
|
121
114
|
}
|
122
|
-
sendEvent(res, data);
|
123
115
|
});
|
124
116
|
|
117
|
+
await queue.addPrompt(aiRequest, conversationId, true);
|
118
|
+
|
125
119
|
await queue.wait();
|
126
120
|
await Promise.allSettled(promises);
|
127
121
|
|
@@ -304,8 +298,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
304
298
|
shellsStream.abort();
|
305
299
|
});
|
306
300
|
|
307
|
-
const
|
308
|
-
|
301
|
+
const queue = new PageQueue(outerConversationId, 5);
|
309
302
|
shellsStream.on('data', (data: StormEvent) => {
|
310
303
|
console.log('Processing shell event', data);
|
311
304
|
sendEvent(res, data);
|
@@ -318,7 +311,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
318
311
|
return;
|
319
312
|
}
|
320
313
|
|
321
|
-
|
314
|
+
queue.addUiShell(data.payload);
|
322
315
|
});
|
323
316
|
|
324
317
|
shellsStream.on('error', (error) => {
|
@@ -332,8 +325,18 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
332
325
|
UI_SERVERS[outerConversationId] = new UIServer(outerConversationId);
|
333
326
|
await UI_SERVERS[outerConversationId].start();
|
334
327
|
|
328
|
+
sendEvent(res, {
|
329
|
+
type: 'UI_SERVER_STARTED',
|
330
|
+
reason: '',
|
331
|
+
payload: {
|
332
|
+
conversationId: outerConversationId,
|
333
|
+
resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
|
334
|
+
},
|
335
|
+
created: Date.now(),
|
336
|
+
});
|
337
|
+
|
335
338
|
// Get the pages (5 at a time)
|
336
|
-
|
339
|
+
|
337
340
|
const pagePromises: Promise<void>[] = [];
|
338
341
|
onRequestAborted(req, res, () => {
|
339
342
|
queue.cancel();
|
@@ -359,7 +362,6 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
359
362
|
title: screen.title,
|
360
363
|
filename: screen.filename,
|
361
364
|
storage_prefix: outerConversationId + '_',
|
362
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
363
365
|
})
|
364
366
|
);
|
365
367
|
}
|
@@ -668,7 +670,7 @@ function streamStormPartialResponse(result: StormStream, res: Response) {
|
|
668
670
|
switch (data.type) {
|
669
671
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
670
672
|
case 'ERROR_INTERNAL':
|
671
|
-
console.log(
|
673
|
+
console.log('Error internal', data);
|
672
674
|
return;
|
673
675
|
}
|
674
676
|
sendEvent(res, data);
|
@@ -691,14 +693,13 @@ function onRequestAborted(req: KapetaBodyRequest, res: Response, onAborted: () =
|
|
691
693
|
});
|
692
694
|
}
|
693
695
|
|
694
|
-
function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
});
|
696
|
+
async function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
697
|
+
try {
|
698
|
+
await writePageToDisk(mainConversationId, data);
|
699
|
+
} catch (err) {
|
700
|
+
console.error('Failed to write page to disk', err);
|
701
|
+
}
|
702
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
702
703
|
}
|
703
704
|
|
704
705
|
export default router;
|