@kapeta/local-cluster-service 0.62.0 → 0.62.2
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/event-parser.js +11 -0
- package/dist/cjs/src/storm/events.d.ts +5 -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 +14 -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/event-parser.js +11 -0
- package/dist/esm/src/storm/events.d.ts +5 -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 +14 -21
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +33 -6
- package/src/storm/event-parser.ts +12 -0
- package/src/storm/events.ts +7 -1
- package/src/storm/page-utils.ts +7 -0
- package/src/storm/routes.ts +17 -25
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.62.2](https://github.com/kapetacom/local-cluster-service/compare/v0.62.1...v0.62.2) (2024-08-14)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Single page adjustments ([#217](https://github.com/kapetacom/local-cluster-service/issues/217)) ([8d0f7e6](https://github.com/kapetacom/local-cluster-service/commit/8d0f7e6b9a3fdd971559a4c9b06b78db38145b4d))
|
7
|
+
|
8
|
+
## [0.62.1](https://github.com/kapetacom/local-cluster-service/compare/v0.62.0...v0.62.1) (2024-08-14)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* add openapi spec streaming for a sense of progress ([95eff45](https://github.com/kapetacom/local-cluster-service/commit/95eff4504fd83a2440f4343f608383299fa50326))
|
14
|
+
|
1
15
|
# [0.62.0](https://github.com/kapetacom/local-cluster-service/compare/v0.61.2...v0.62.0) (2024-08-13)
|
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) {
|
@@ -236,6 +236,17 @@ class StormEventParser {
|
|
236
236
|
this.blocks[evt.payload.blockName].models = [];
|
237
237
|
}
|
238
238
|
break;
|
239
|
+
case 'API_STREAM_CHUNK':
|
240
|
+
case 'API_STREAM_CHUNK_RESET':
|
241
|
+
case 'API_STREAM_DONE':
|
242
|
+
case 'API_STREAM_FAILED':
|
243
|
+
case 'API_STREAM_STATE':
|
244
|
+
case 'API_STREAM_START':
|
245
|
+
if ('blockName' in evt.payload) {
|
246
|
+
evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
|
247
|
+
evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
|
248
|
+
}
|
249
|
+
break;
|
239
250
|
}
|
240
251
|
return await this.toResult(handle);
|
241
252
|
}
|
@@ -218,6 +218,10 @@ export interface StormEventFileChunk extends StormEventFileBase {
|
|
218
218
|
lineNumber: number;
|
219
219
|
};
|
220
220
|
}
|
221
|
+
export interface StormEventApiBase {
|
222
|
+
type: 'API_STREAM_CHUNK' | 'API_STREAM_DONE' | 'API_STREAM_FAILED' | 'API_STREAM_STATE' | 'API_STREAM_START' | 'API_STREAM_CHUNK_RESET';
|
223
|
+
payload: StormEventFileBasePayload;
|
224
|
+
}
|
221
225
|
export interface StormEventBlockReady {
|
222
226
|
type: 'BLOCK_READY';
|
223
227
|
reason: string;
|
@@ -371,5 +375,5 @@ export interface StormEventReferenceClassification {
|
|
371
375
|
created: number;
|
372
376
|
payload: ReferenceClassification;
|
373
377
|
}
|
374
|
-
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;
|
378
|
+
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;
|
375
379
|
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);
|
@@ -254,7 +248,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
254
248
|
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
255
249
|
await UI_SERVERS[outerConversationId].start();
|
256
250
|
// Get the pages (5 at a time)
|
257
|
-
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
258
251
|
const pagePromises = [];
|
259
252
|
onRequestAborted(req, res, () => {
|
260
253
|
queue.cancel();
|
@@ -276,7 +269,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
276
269
|
title: screen.title,
|
277
270
|
filename: screen.filename,
|
278
271
|
storage_prefix: outerConversationId + '_',
|
279
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
280
272
|
}));
|
281
273
|
}
|
282
274
|
await queue.wait();
|
@@ -377,6 +369,7 @@ router.post('/:handle/all', async (req, res) => {
|
|
377
369
|
try {
|
378
370
|
const result = await eventParser.processEvent(handle, data);
|
379
371
|
switch (data.type) {
|
372
|
+
case 'API_STREAM_START':
|
380
373
|
case 'CREATE_API':
|
381
374
|
case 'CREATE_MODEL':
|
382
375
|
case 'CREATE_TYPE':
|
@@ -535,7 +528,7 @@ function streamStormPartialResponse(result, res) {
|
|
535
528
|
switch (data.type) {
|
536
529
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
537
530
|
case 'ERROR_INTERNAL':
|
538
|
-
console.log(
|
531
|
+
console.log('Error internal', data);
|
539
532
|
return;
|
540
533
|
}
|
541
534
|
sendEvent(res, data);
|
@@ -554,13 +547,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
554
547
|
onAborted();
|
555
548
|
});
|
556
549
|
}
|
557
|
-
function sendPageEvent(mainConversationId, data, res) {
|
558
|
-
|
559
|
-
.
|
550
|
+
async function sendPageEvent(mainConversationId, data, res) {
|
551
|
+
try {
|
552
|
+
await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
|
553
|
+
}
|
554
|
+
catch (err) {
|
560
555
|
console.error('Failed to write page to disk', err);
|
561
|
-
}
|
562
|
-
|
563
|
-
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
564
|
-
});
|
556
|
+
}
|
557
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
565
558
|
}
|
566
559
|
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) {
|
@@ -236,6 +236,17 @@ class StormEventParser {
|
|
236
236
|
this.blocks[evt.payload.blockName].models = [];
|
237
237
|
}
|
238
238
|
break;
|
239
|
+
case 'API_STREAM_CHUNK':
|
240
|
+
case 'API_STREAM_CHUNK_RESET':
|
241
|
+
case 'API_STREAM_DONE':
|
242
|
+
case 'API_STREAM_FAILED':
|
243
|
+
case 'API_STREAM_STATE':
|
244
|
+
case 'API_STREAM_START':
|
245
|
+
if ('blockName' in evt.payload) {
|
246
|
+
evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
|
247
|
+
evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
|
248
|
+
}
|
249
|
+
break;
|
239
250
|
}
|
240
251
|
return await this.toResult(handle);
|
241
252
|
}
|
@@ -218,6 +218,10 @@ export interface StormEventFileChunk extends StormEventFileBase {
|
|
218
218
|
lineNumber: number;
|
219
219
|
};
|
220
220
|
}
|
221
|
+
export interface StormEventApiBase {
|
222
|
+
type: 'API_STREAM_CHUNK' | 'API_STREAM_DONE' | 'API_STREAM_FAILED' | 'API_STREAM_STATE' | 'API_STREAM_START' | 'API_STREAM_CHUNK_RESET';
|
223
|
+
payload: StormEventFileBasePayload;
|
224
|
+
}
|
221
225
|
export interface StormEventBlockReady {
|
222
226
|
type: 'BLOCK_READY';
|
223
227
|
reason: string;
|
@@ -371,5 +375,5 @@ export interface StormEventReferenceClassification {
|
|
371
375
|
created: number;
|
372
376
|
payload: ReferenceClassification;
|
373
377
|
}
|
374
|
-
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;
|
378
|
+
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;
|
375
379
|
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);
|
@@ -254,7 +248,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
254
248
|
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
255
249
|
await UI_SERVERS[outerConversationId].start();
|
256
250
|
// Get the pages (5 at a time)
|
257
|
-
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
258
251
|
const pagePromises = [];
|
259
252
|
onRequestAborted(req, res, () => {
|
260
253
|
queue.cancel();
|
@@ -276,7 +269,6 @@ router.post('/:handle/ui', async (req, res) => {
|
|
276
269
|
title: screen.title,
|
277
270
|
filename: screen.filename,
|
278
271
|
storage_prefix: outerConversationId + '_',
|
279
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
280
272
|
}));
|
281
273
|
}
|
282
274
|
await queue.wait();
|
@@ -377,6 +369,7 @@ router.post('/:handle/all', async (req, res) => {
|
|
377
369
|
try {
|
378
370
|
const result = await eventParser.processEvent(handle, data);
|
379
371
|
switch (data.type) {
|
372
|
+
case 'API_STREAM_START':
|
380
373
|
case 'CREATE_API':
|
381
374
|
case 'CREATE_MODEL':
|
382
375
|
case 'CREATE_TYPE':
|
@@ -535,7 +528,7 @@ function streamStormPartialResponse(result, res) {
|
|
535
528
|
switch (data.type) {
|
536
529
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
537
530
|
case 'ERROR_INTERNAL':
|
538
|
-
console.log(
|
531
|
+
console.log('Error internal', data);
|
539
532
|
return;
|
540
533
|
}
|
541
534
|
sendEvent(res, data);
|
@@ -554,13 +547,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
554
547
|
onAborted();
|
555
548
|
});
|
556
549
|
}
|
557
|
-
function sendPageEvent(mainConversationId, data, res) {
|
558
|
-
|
559
|
-
.
|
550
|
+
async function sendPageEvent(mainConversationId, data, res) {
|
551
|
+
try {
|
552
|
+
await (0, page_utils_1.writePageToDisk)(mainConversationId, data);
|
553
|
+
}
|
554
|
+
catch (err) {
|
560
555
|
console.error('Failed to write page to disk', err);
|
561
|
-
}
|
562
|
-
|
563
|
-
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
564
|
-
});
|
556
|
+
}
|
557
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
565
558
|
}
|
566
559
|
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
|
|
@@ -357,6 +357,18 @@ export class StormEventParser {
|
|
357
357
|
this.blocks[evt.payload.blockName].models = [];
|
358
358
|
}
|
359
359
|
break;
|
360
|
+
|
361
|
+
case 'API_STREAM_CHUNK':
|
362
|
+
case 'API_STREAM_CHUNK_RESET':
|
363
|
+
case 'API_STREAM_DONE':
|
364
|
+
case 'API_STREAM_FAILED':
|
365
|
+
case 'API_STREAM_STATE':
|
366
|
+
case 'API_STREAM_START':
|
367
|
+
if ('blockName' in evt.payload) {
|
368
|
+
evt.payload.blockRef = StormEventParser.toRef(handle, evt.payload.blockName).toNormalizedString();
|
369
|
+
evt.payload.instanceId = StormEventParser.toInstanceIdFromRef(evt.payload.blockRef);
|
370
|
+
}
|
371
|
+
break;
|
360
372
|
}
|
361
373
|
|
362
374
|
return await this.toResult(handle);
|
package/src/storm/events.ts
CHANGED
@@ -263,6 +263,11 @@ export interface StormEventFileChunk extends StormEventFileBase {
|
|
263
263
|
};
|
264
264
|
}
|
265
265
|
|
266
|
+
export interface StormEventApiBase {
|
267
|
+
type: 'API_STREAM_CHUNK' | 'API_STREAM_DONE' | 'API_STREAM_FAILED' | 'API_STREAM_STATE' | 'API_STREAM_START' | 'API_STREAM_CHUNK_RESET';
|
268
|
+
payload: StormEventFileBasePayload;
|
269
|
+
}
|
270
|
+
|
266
271
|
export interface StormEventBlockReady {
|
267
272
|
type: 'BLOCK_READY';
|
268
273
|
reason: string;
|
@@ -471,4 +476,5 @@ export type StormEvent =
|
|
471
476
|
| StormEventPageUrl
|
472
477
|
| StormEventPromptImprove
|
473
478
|
| StormEventLandingPage
|
474
|
-
| StormEventReferenceClassification
|
479
|
+
| StormEventReferenceClassification
|
480
|
+
| StormEventApiBase;
|
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) => {
|
@@ -333,7 +326,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
333
326
|
await UI_SERVERS[outerConversationId].start();
|
334
327
|
|
335
328
|
// Get the pages (5 at a time)
|
336
|
-
|
329
|
+
|
337
330
|
const pagePromises: Promise<void>[] = [];
|
338
331
|
onRequestAborted(req, res, () => {
|
339
332
|
queue.cancel();
|
@@ -359,7 +352,6 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
359
352
|
title: screen.title,
|
360
353
|
filename: screen.filename,
|
361
354
|
storage_prefix: outerConversationId + '_',
|
362
|
-
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
363
355
|
})
|
364
356
|
);
|
365
357
|
}
|
@@ -483,6 +475,7 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
483
475
|
const result = await eventParser.processEvent(handle, data);
|
484
476
|
|
485
477
|
switch (data.type) {
|
478
|
+
case 'API_STREAM_START':
|
486
479
|
case 'CREATE_API':
|
487
480
|
case 'CREATE_MODEL':
|
488
481
|
case 'CREATE_TYPE':
|
@@ -667,7 +660,7 @@ function streamStormPartialResponse(result: StormStream, res: Response) {
|
|
667
660
|
switch (data.type) {
|
668
661
|
// todo: temporarily (for demo purposes) disable error messages when codegen fails
|
669
662
|
case 'ERROR_INTERNAL':
|
670
|
-
console.log(
|
663
|
+
console.log('Error internal', data);
|
671
664
|
return;
|
672
665
|
}
|
673
666
|
sendEvent(res, data);
|
@@ -690,14 +683,13 @@ function onRequestAborted(req: KapetaBodyRequest, res: Response, onAborted: () =
|
|
690
683
|
});
|
691
684
|
}
|
692
685
|
|
693
|
-
function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
});
|
686
|
+
async function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
687
|
+
try {
|
688
|
+
await writePageToDisk(mainConversationId, data);
|
689
|
+
} catch (err) {
|
690
|
+
console.error('Failed to write page to disk', err);
|
691
|
+
}
|
692
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
701
693
|
}
|
702
694
|
|
703
695
|
export default router;
|