@kapeta/local-cluster-service 0.58.6 → 0.59.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 +7 -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 +91 -8
- package/dist/cjs/src/storm/stormClient.d.ts +9 -4
- 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 +91 -8
- package/dist/esm/src/storm/stormClient.d.ts +9 -4
- package/package.json +1 -1
- package/src/storm/events.ts +23 -1
- package/src/storm/page-utils.ts +53 -0
- package/src/storm/routes.ts +103 -13
- package/src/storm/stormClient.ts +10 -4
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.59.0](https://github.com/kapetacom/local-cluster-service/compare/v0.58.6...v0.59.0) (2024-08-01)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* 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))
|
7
|
+
|
1
8
|
## [0.58.6](https://github.com/kapetacom/local-cluster-service/compare/v0.58.5...v0.58.6) (2024-08-01)
|
2
9
|
|
3
10
|
|
@@ -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,43 @@ 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");
|
23
24
|
const router = (0, express_promise_router_1.default)();
|
24
25
|
router.use('/', cors_1.corsHandler);
|
25
26
|
router.use('/', stringBody_1.stringBody);
|
26
|
-
|
27
|
-
|
27
|
+
function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
28
|
+
if (screenData.type === 'PAGE') {
|
29
|
+
screenData.payload.conversationId = innerConversationId;
|
30
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
31
|
+
return {
|
32
|
+
type: 'PAGE_URL',
|
33
|
+
reason: screenData.reason,
|
34
|
+
created: screenData.created,
|
35
|
+
payload: {
|
36
|
+
id: node_uuid_1.default.v4(),
|
37
|
+
name: screenData.payload.name,
|
38
|
+
title: screenData.payload.title,
|
39
|
+
filename: screenData.payload.filename,
|
40
|
+
description: screenData.payload.description,
|
41
|
+
prompt: screenData.payload.prompt,
|
42
|
+
path: screenData.payload.path,
|
43
|
+
url: `/storm/ui/${mainConversationId}/serve/${screenData.payload.method}${path}`,
|
44
|
+
method: screenData.payload.method,
|
45
|
+
conversationId: innerConversationId,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
}
|
49
|
+
return screenData;
|
50
|
+
}
|
51
|
+
router.all('/ui/:systemId/serve/:method/*', async (req, res) => {
|
52
|
+
(0, page_utils_1.readPageFromDisk)(req.params.systemId, req.params[0], req.params.method, res);
|
53
|
+
});
|
54
|
+
router.post('/ui/screen', async (req, res) => {
|
28
55
|
try {
|
29
56
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
57
|
+
const systemId = req.headers[page_utils_1.SystemIdHeader.toLowerCase()];
|
30
58
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
59
|
+
aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
|
31
60
|
const screenStream = await stormClient_1.stormClient.createUIPage(aiRequest, conversationId);
|
32
61
|
onRequestAborted(req, res, () => {
|
33
62
|
screenStream.abort();
|
@@ -35,11 +64,21 @@ router.post('/:handle/ui/screen', async (req, res) => {
|
|
35
64
|
res.set('Content-Type', 'application/x-ndjson');
|
36
65
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
37
66
|
res.set(stormClient_1.ConversationIdHeader, screenStream.getConversationId());
|
67
|
+
const promises = [];
|
38
68
|
screenStream.on('data', (data) => {
|
39
|
-
|
69
|
+
switch (data.type) {
|
70
|
+
case 'PAGE':
|
71
|
+
console.log('Processing page event', data);
|
72
|
+
data.payload.conversationId = screenStream.getConversationId();
|
73
|
+
if (systemId) {
|
74
|
+
promises.push(sendPageEvent(systemId, data, res));
|
75
|
+
}
|
76
|
+
break;
|
77
|
+
}
|
40
78
|
sendEvent(res, data);
|
41
79
|
});
|
42
80
|
await waitForStormStream(screenStream);
|
81
|
+
await Promise.allSettled(promises);
|
43
82
|
sendDone(res);
|
44
83
|
}
|
45
84
|
catch (err) {
|
@@ -62,7 +101,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
62
101
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
63
102
|
res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
|
64
103
|
const promises = {};
|
65
|
-
const queue = new PromiseQueue_1.PromiseQueue(
|
104
|
+
const queue = new PromiseQueue_1.PromiseQueue(5);
|
66
105
|
onRequestAborted(req, res, () => {
|
67
106
|
queue.cancel();
|
68
107
|
});
|
@@ -91,15 +130,20 @@ router.post('/:handle/ui', async (req, res) => {
|
|
91
130
|
name: screen.name,
|
92
131
|
title: screen.title,
|
93
132
|
filename: screen.filename,
|
133
|
+
storage_prefix: userJourneysStream.getConversationId() + '_',
|
94
134
|
}, innerConversationId);
|
135
|
+
const promises = [];
|
95
136
|
screenStream.on('data', (screenData) => {
|
96
137
|
if (screenData.type === 'PAGE') {
|
97
138
|
screenData.payload.conversationId = innerConversationId;
|
139
|
+
promises.push(sendPageEvent(userJourneysStream.getConversationId(), screenData, res));
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
sendEvent(res, screenData);
|
98
143
|
}
|
99
|
-
sendEvent(res, screenData);
|
100
144
|
});
|
101
145
|
screenStream.on('end', () => {
|
102
|
-
|
146
|
+
Promise.allSettled(promises).finally(resolve);
|
103
147
|
});
|
104
148
|
}
|
105
149
|
catch (e) {
|
@@ -131,16 +175,45 @@ router.post('/ui/edit', async (req, res) => {
|
|
131
175
|
try {
|
132
176
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
133
177
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
134
|
-
const
|
178
|
+
const pages = aiRequest.prompt.pages
|
179
|
+
.map((page) => {
|
180
|
+
const content = (0, page_utils_1.readPageFromDiskAsString)(conversationId, page.path, page.method);
|
181
|
+
if (!content) {
|
182
|
+
console.warn('Page not found', page);
|
183
|
+
return undefined;
|
184
|
+
}
|
185
|
+
return {
|
186
|
+
filename: page.filename,
|
187
|
+
path: page.path,
|
188
|
+
method: page.method,
|
189
|
+
title: page.title,
|
190
|
+
conversationId: page.conversationId,
|
191
|
+
prompt: page.prompt,
|
192
|
+
name: page.name,
|
193
|
+
description: page.description,
|
194
|
+
content,
|
195
|
+
};
|
196
|
+
})
|
197
|
+
.filter((page) => !!page);
|
198
|
+
const editStream = await stormClient_1.stormClient.editPages({
|
199
|
+
...aiRequest.prompt,
|
200
|
+
pages,
|
201
|
+
}, conversationId);
|
135
202
|
onRequestAborted(req, res, () => {
|
136
203
|
editStream.abort();
|
137
204
|
});
|
138
205
|
res.set('Content-Type', 'application/x-ndjson');
|
139
206
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
140
207
|
res.set(stormClient_1.ConversationIdHeader, editStream.getConversationId());
|
208
|
+
const promises = [];
|
141
209
|
editStream.on('data', (data) => {
|
142
210
|
try {
|
143
|
-
|
211
|
+
if (data.type === 'PAGE') {
|
212
|
+
promises.push(sendPageEvent(editStream.getConversationId(), data, res));
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
sendEvent(res, data);
|
216
|
+
}
|
144
217
|
}
|
145
218
|
catch (e) {
|
146
219
|
console.error('Failed to process event', e);
|
@@ -150,6 +223,7 @@ router.post('/ui/edit', async (req, res) => {
|
|
150
223
|
if (editStream.isAborted()) {
|
151
224
|
return;
|
152
225
|
}
|
226
|
+
await Promise.all(promises);
|
153
227
|
sendDone(res);
|
154
228
|
}
|
155
229
|
catch (err) {
|
@@ -351,4 +425,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
351
425
|
onAborted();
|
352
426
|
});
|
353
427
|
}
|
428
|
+
function sendPageEvent(mainConversationId, data, res) {
|
429
|
+
return (0, page_utils_1.writePageToDisk)(mainConversationId, data)
|
430
|
+
.catch((err) => {
|
431
|
+
console.error('Failed to write page to disk', err);
|
432
|
+
})
|
433
|
+
.then(() => {
|
434
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
435
|
+
});
|
436
|
+
}
|
354
437
|
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 {
|
@@ -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,43 @@ 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");
|
23
24
|
const router = (0, express_promise_router_1.default)();
|
24
25
|
router.use('/', cors_1.corsHandler);
|
25
26
|
router.use('/', stringBody_1.stringBody);
|
26
|
-
|
27
|
-
|
27
|
+
function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
28
|
+
if (screenData.type === 'PAGE') {
|
29
|
+
screenData.payload.conversationId = innerConversationId;
|
30
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
31
|
+
return {
|
32
|
+
type: 'PAGE_URL',
|
33
|
+
reason: screenData.reason,
|
34
|
+
created: screenData.created,
|
35
|
+
payload: {
|
36
|
+
id: node_uuid_1.default.v4(),
|
37
|
+
name: screenData.payload.name,
|
38
|
+
title: screenData.payload.title,
|
39
|
+
filename: screenData.payload.filename,
|
40
|
+
description: screenData.payload.description,
|
41
|
+
prompt: screenData.payload.prompt,
|
42
|
+
path: screenData.payload.path,
|
43
|
+
url: `/storm/ui/${mainConversationId}/serve/${screenData.payload.method}${path}`,
|
44
|
+
method: screenData.payload.method,
|
45
|
+
conversationId: innerConversationId,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
}
|
49
|
+
return screenData;
|
50
|
+
}
|
51
|
+
router.all('/ui/:systemId/serve/:method/*', async (req, res) => {
|
52
|
+
(0, page_utils_1.readPageFromDisk)(req.params.systemId, req.params[0], req.params.method, res);
|
53
|
+
});
|
54
|
+
router.post('/ui/screen', async (req, res) => {
|
28
55
|
try {
|
29
56
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
57
|
+
const systemId = req.headers[page_utils_1.SystemIdHeader.toLowerCase()];
|
30
58
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
59
|
+
aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
|
31
60
|
const screenStream = await stormClient_1.stormClient.createUIPage(aiRequest, conversationId);
|
32
61
|
onRequestAborted(req, res, () => {
|
33
62
|
screenStream.abort();
|
@@ -35,11 +64,21 @@ router.post('/:handle/ui/screen', async (req, res) => {
|
|
35
64
|
res.set('Content-Type', 'application/x-ndjson');
|
36
65
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
37
66
|
res.set(stormClient_1.ConversationIdHeader, screenStream.getConversationId());
|
67
|
+
const promises = [];
|
38
68
|
screenStream.on('data', (data) => {
|
39
|
-
|
69
|
+
switch (data.type) {
|
70
|
+
case 'PAGE':
|
71
|
+
console.log('Processing page event', data);
|
72
|
+
data.payload.conversationId = screenStream.getConversationId();
|
73
|
+
if (systemId) {
|
74
|
+
promises.push(sendPageEvent(systemId, data, res));
|
75
|
+
}
|
76
|
+
break;
|
77
|
+
}
|
40
78
|
sendEvent(res, data);
|
41
79
|
});
|
42
80
|
await waitForStormStream(screenStream);
|
81
|
+
await Promise.allSettled(promises);
|
43
82
|
sendDone(res);
|
44
83
|
}
|
45
84
|
catch (err) {
|
@@ -62,7 +101,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
62
101
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
63
102
|
res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
|
64
103
|
const promises = {};
|
65
|
-
const queue = new PromiseQueue_1.PromiseQueue(
|
104
|
+
const queue = new PromiseQueue_1.PromiseQueue(5);
|
66
105
|
onRequestAborted(req, res, () => {
|
67
106
|
queue.cancel();
|
68
107
|
});
|
@@ -91,15 +130,20 @@ router.post('/:handle/ui', async (req, res) => {
|
|
91
130
|
name: screen.name,
|
92
131
|
title: screen.title,
|
93
132
|
filename: screen.filename,
|
133
|
+
storage_prefix: userJourneysStream.getConversationId() + '_',
|
94
134
|
}, innerConversationId);
|
135
|
+
const promises = [];
|
95
136
|
screenStream.on('data', (screenData) => {
|
96
137
|
if (screenData.type === 'PAGE') {
|
97
138
|
screenData.payload.conversationId = innerConversationId;
|
139
|
+
promises.push(sendPageEvent(userJourneysStream.getConversationId(), screenData, res));
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
sendEvent(res, screenData);
|
98
143
|
}
|
99
|
-
sendEvent(res, screenData);
|
100
144
|
});
|
101
145
|
screenStream.on('end', () => {
|
102
|
-
|
146
|
+
Promise.allSettled(promises).finally(resolve);
|
103
147
|
});
|
104
148
|
}
|
105
149
|
catch (e) {
|
@@ -131,16 +175,45 @@ router.post('/ui/edit', async (req, res) => {
|
|
131
175
|
try {
|
132
176
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
133
177
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
134
|
-
const
|
178
|
+
const pages = aiRequest.prompt.pages
|
179
|
+
.map((page) => {
|
180
|
+
const content = (0, page_utils_1.readPageFromDiskAsString)(conversationId, page.path, page.method);
|
181
|
+
if (!content) {
|
182
|
+
console.warn('Page not found', page);
|
183
|
+
return undefined;
|
184
|
+
}
|
185
|
+
return {
|
186
|
+
filename: page.filename,
|
187
|
+
path: page.path,
|
188
|
+
method: page.method,
|
189
|
+
title: page.title,
|
190
|
+
conversationId: page.conversationId,
|
191
|
+
prompt: page.prompt,
|
192
|
+
name: page.name,
|
193
|
+
description: page.description,
|
194
|
+
content,
|
195
|
+
};
|
196
|
+
})
|
197
|
+
.filter((page) => !!page);
|
198
|
+
const editStream = await stormClient_1.stormClient.editPages({
|
199
|
+
...aiRequest.prompt,
|
200
|
+
pages,
|
201
|
+
}, conversationId);
|
135
202
|
onRequestAborted(req, res, () => {
|
136
203
|
editStream.abort();
|
137
204
|
});
|
138
205
|
res.set('Content-Type', 'application/x-ndjson');
|
139
206
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
140
207
|
res.set(stormClient_1.ConversationIdHeader, editStream.getConversationId());
|
208
|
+
const promises = [];
|
141
209
|
editStream.on('data', (data) => {
|
142
210
|
try {
|
143
|
-
|
211
|
+
if (data.type === 'PAGE') {
|
212
|
+
promises.push(sendPageEvent(editStream.getConversationId(), data, res));
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
sendEvent(res, data);
|
216
|
+
}
|
144
217
|
}
|
145
218
|
catch (e) {
|
146
219
|
console.error('Failed to process event', e);
|
@@ -150,6 +223,7 @@ router.post('/ui/edit', async (req, res) => {
|
|
150
223
|
if (editStream.isAborted()) {
|
151
224
|
return;
|
152
225
|
}
|
226
|
+
await Promise.all(promises);
|
153
227
|
sendDone(res);
|
154
228
|
}
|
155
229
|
catch (err) {
|
@@ -351,4 +425,13 @@ function onRequestAborted(req, res, onAborted) {
|
|
351
425
|
onAborted();
|
352
426
|
});
|
353
427
|
}
|
428
|
+
function sendPageEvent(mainConversationId, data, res) {
|
429
|
+
return (0, page_utils_1.writePageToDisk)(mainConversationId, data)
|
430
|
+
.catch((err) => {
|
431
|
+
console.error('Failed to write page to disk', err);
|
432
|
+
})
|
433
|
+
.then(() => {
|
434
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
435
|
+
});
|
436
|
+
}
|
354
437
|
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 {
|
package/package.json
CHANGED
package/src/storm/events.ts
CHANGED
@@ -324,12 +324,14 @@ export interface StormEventPhases {
|
|
324
324
|
|
325
325
|
export interface Page {
|
326
326
|
name: string;
|
327
|
+
filename: string;
|
327
328
|
title: string;
|
328
329
|
description: string;
|
329
330
|
content: string;
|
330
331
|
path: string;
|
331
332
|
method: string;
|
332
333
|
conversationId: string;
|
334
|
+
prompt: string;
|
333
335
|
}
|
334
336
|
|
335
337
|
// Event for creating a page
|
@@ -340,6 +342,25 @@ export interface StormEventPage {
|
|
340
342
|
payload: Page;
|
341
343
|
}
|
342
344
|
|
345
|
+
// Event for creating a page
|
346
|
+
export interface StormEventPageUrl {
|
347
|
+
type: 'PAGE_URL';
|
348
|
+
reason: string;
|
349
|
+
created: number;
|
350
|
+
payload: {
|
351
|
+
id: string;
|
352
|
+
name: string;
|
353
|
+
filename: string;
|
354
|
+
title: string;
|
355
|
+
description: string;
|
356
|
+
path: string;
|
357
|
+
url: string;
|
358
|
+
method: string;
|
359
|
+
conversationId: string;
|
360
|
+
prompt: string;
|
361
|
+
};
|
362
|
+
}
|
363
|
+
|
343
364
|
export interface UserJourneyScreen {
|
344
365
|
name: string;
|
345
366
|
title: string;
|
@@ -389,4 +410,5 @@ export type StormEvent =
|
|
389
410
|
| StormEventBlockStatus
|
390
411
|
| StormEventCreateDSLRetry
|
391
412
|
| StormEventUserJourney
|
392
|
-
| StormEventPage
|
413
|
+
| StormEventPage
|
414
|
+
| StormEventPageUrl;
|
@@ -0,0 +1,53 @@
|
|
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
|
+
import os from 'node:os';
|
8
|
+
import Path from 'path';
|
9
|
+
import FS from 'fs-extra';
|
10
|
+
|
11
|
+
export const SystemIdHeader = 'System-Id';
|
12
|
+
|
13
|
+
export async function writePageToDisk(systemId: string, event: StormEventPage) {
|
14
|
+
const path = Path.join(
|
15
|
+
os.tmpdir(),
|
16
|
+
'ai-systems',
|
17
|
+
systemId,
|
18
|
+
event.payload.path,
|
19
|
+
event.payload.method.toLowerCase(),
|
20
|
+
'index.html'
|
21
|
+
);
|
22
|
+
await FS.ensureDir(Path.dirname(path));
|
23
|
+
await FS.writeFile(path, event.payload.content);
|
24
|
+
|
25
|
+
console.log(`Page written to disk: ${event.payload.title} > ${path}`);
|
26
|
+
|
27
|
+
return {
|
28
|
+
path,
|
29
|
+
};
|
30
|
+
}
|
31
|
+
|
32
|
+
export function readPageFromDiskAsString(systemId: string, path: string, method: string) {
|
33
|
+
const filePath = Path.join(os.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
34
|
+
if (!FS.existsSync(filePath)) {
|
35
|
+
return null;
|
36
|
+
}
|
37
|
+
|
38
|
+
return FS.readFileSync(filePath, 'utf8');
|
39
|
+
}
|
40
|
+
|
41
|
+
export function readPageFromDisk(systemId: string, path: string, method: string, res: Response) {
|
42
|
+
const filePath = Path.join(os.tmpdir(), 'ai-systems', systemId, path, method.toLowerCase(), 'index.html');
|
43
|
+
if (!FS.existsSync(filePath)) {
|
44
|
+
res.status(404).send('Page not found');
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
|
48
|
+
res.type(filePath.split('.').pop() as string);
|
49
|
+
|
50
|
+
const content = FS.readFileSync(filePath, 'utf8');
|
51
|
+
res.write(content);
|
52
|
+
res.end();
|
53
|
+
}
|
package/src/storm/routes.ts
CHANGED
@@ -12,8 +12,8 @@ import { corsHandler } from '../middleware/cors';
|
|
12
12
|
import { stringBody } from '../middleware/stringBody';
|
13
13
|
import { KapetaBodyRequest } from '../types';
|
14
14
|
import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
|
15
|
-
import { ConversationIdHeader, stormClient, UIPagePrompt, UIPageEditPrompt } from './stormClient';
|
16
|
-
import { StormEvent, StormEventPhaseType } from './events';
|
15
|
+
import { ConversationIdHeader, stormClient, UIPagePrompt, UIPageEditPrompt, UIPageEditRequest } from './stormClient';
|
16
|
+
import { Page, StormEvent, StormEventPage, StormEventPhaseType } from './events';
|
17
17
|
import {
|
18
18
|
createPhaseEndEvent,
|
19
19
|
createPhaseStartEvent,
|
@@ -25,18 +25,50 @@ import { StormCodegen } from './codegen';
|
|
25
25
|
import { assetManager } from '../assetManager';
|
26
26
|
import uuid from 'node-uuid';
|
27
27
|
import { PromiseQueue } from './PromiseQueue';
|
28
|
+
import { readPageFromDisk, readPageFromDiskAsString, SystemIdHeader, writePageToDisk } from './page-utils';
|
28
29
|
|
29
30
|
const router = Router();
|
30
31
|
|
31
32
|
router.use('/', corsHandler);
|
32
33
|
router.use('/', stringBody);
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
function convertPageEvent(screenData: StormEvent, innerConversationId: string, mainConversationId: string): StormEvent {
|
36
|
+
if (screenData.type === 'PAGE') {
|
37
|
+
screenData.payload.conversationId = innerConversationId;
|
38
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
39
|
+
return {
|
40
|
+
type: 'PAGE_URL',
|
41
|
+
reason: screenData.reason,
|
42
|
+
created: screenData.created,
|
43
|
+
payload: {
|
44
|
+
id: uuid.v4(),
|
45
|
+
name: screenData.payload.name,
|
46
|
+
title: screenData.payload.title,
|
47
|
+
filename: screenData.payload.filename,
|
48
|
+
description: screenData.payload.description,
|
49
|
+
prompt: screenData.payload.prompt,
|
50
|
+
path: screenData.payload.path,
|
51
|
+
url: `/storm/ui/${mainConversationId}/serve/${screenData.payload.method}${path}`,
|
52
|
+
method: screenData.payload.method,
|
53
|
+
conversationId: innerConversationId,
|
54
|
+
},
|
55
|
+
};
|
56
|
+
}
|
57
|
+
|
58
|
+
return screenData;
|
59
|
+
}
|
60
|
+
|
61
|
+
router.all('/ui/:systemId/serve/:method/*', async (req: KapetaBodyRequest, res: Response) => {
|
62
|
+
readPageFromDisk(req.params.systemId, req.params[0], req.params.method, res);
|
63
|
+
});
|
64
|
+
|
65
|
+
router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
36
66
|
try {
|
37
67
|
const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
|
68
|
+
const systemId = req.headers[SystemIdHeader.toLowerCase()] as string | undefined;
|
38
69
|
|
39
70
|
const aiRequest: UIPagePrompt = JSON.parse(req.stringBody ?? '{}');
|
71
|
+
aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
|
40
72
|
|
41
73
|
const screenStream = await stormClient.createUIPage(aiRequest, conversationId);
|
42
74
|
|
@@ -48,12 +80,22 @@ router.post('/:handle/ui/screen', async (req: KapetaBodyRequest, res: Response)
|
|
48
80
|
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
49
81
|
res.set(ConversationIdHeader, screenStream.getConversationId());
|
50
82
|
|
83
|
+
const promises: Promise<void>[] = [];
|
51
84
|
screenStream.on('data', (data: StormEvent) => {
|
52
|
-
|
85
|
+
switch (data.type) {
|
86
|
+
case 'PAGE':
|
87
|
+
console.log('Processing page event', data);
|
88
|
+
data.payload.conversationId = screenStream.getConversationId();
|
89
|
+
if (systemId) {
|
90
|
+
promises.push(sendPageEvent(systemId, data, res));
|
91
|
+
}
|
92
|
+
break;
|
93
|
+
}
|
53
94
|
sendEvent(res, data);
|
54
95
|
});
|
55
96
|
|
56
97
|
await waitForStormStream(screenStream);
|
98
|
+
await Promise.allSettled(promises);
|
57
99
|
|
58
100
|
sendDone(res);
|
59
101
|
} catch (err: any) {
|
@@ -83,7 +125,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
83
125
|
|
84
126
|
const promises: { [key: string]: Promise<void> } = {};
|
85
127
|
|
86
|
-
const queue = new PromiseQueue(
|
128
|
+
const queue = new PromiseQueue(5);
|
87
129
|
onRequestAborted(req, res, () => {
|
88
130
|
queue.cancel();
|
89
131
|
});
|
@@ -118,18 +160,23 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
118
160
|
name: screen.name,
|
119
161
|
title: screen.title,
|
120
162
|
filename: screen.filename,
|
163
|
+
storage_prefix: userJourneysStream.getConversationId() + '_',
|
121
164
|
},
|
122
165
|
innerConversationId
|
123
166
|
);
|
167
|
+
const promises: Promise<void>[] = [];
|
124
168
|
screenStream.on('data', (screenData: StormEvent) => {
|
125
169
|
if (screenData.type === 'PAGE') {
|
126
170
|
screenData.payload.conversationId = innerConversationId;
|
171
|
+
promises.push(
|
172
|
+
sendPageEvent(userJourneysStream.getConversationId(), screenData, res)
|
173
|
+
);
|
174
|
+
} else {
|
175
|
+
sendEvent(res, screenData);
|
127
176
|
}
|
128
|
-
|
129
|
-
sendEvent(res, screenData);
|
130
177
|
});
|
131
178
|
screenStream.on('end', () => {
|
132
|
-
|
179
|
+
Promise.allSettled(promises).finally(resolve);
|
133
180
|
});
|
134
181
|
} catch (e: any) {
|
135
182
|
console.error('Failed to process screen', e);
|
@@ -163,9 +210,37 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
163
210
|
try {
|
164
211
|
const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
|
165
212
|
|
166
|
-
const aiRequest: StormContextRequest<
|
213
|
+
const aiRequest: StormContextRequest<UIPageEditRequest> = JSON.parse(req.stringBody ?? '{}');
|
214
|
+
|
215
|
+
const pages = aiRequest.prompt.pages
|
216
|
+
.map((page) => {
|
217
|
+
const content = readPageFromDiskAsString(conversationId!, page.path, page.method);
|
218
|
+
if (!content) {
|
219
|
+
console.warn('Page not found', page);
|
220
|
+
return undefined;
|
221
|
+
}
|
167
222
|
|
168
|
-
|
223
|
+
return {
|
224
|
+
filename: page.filename,
|
225
|
+
path: page.path,
|
226
|
+
method: page.method,
|
227
|
+
title: page.title,
|
228
|
+
conversationId: page.conversationId,
|
229
|
+
prompt: page.prompt,
|
230
|
+
name: page.name,
|
231
|
+
description: page.description,
|
232
|
+
content,
|
233
|
+
} satisfies Page;
|
234
|
+
})
|
235
|
+
.filter((page: Page | undefined) => !!page) as UIPageEditPrompt['pages'];
|
236
|
+
|
237
|
+
const editStream = await stormClient.editPages(
|
238
|
+
{
|
239
|
+
...aiRequest.prompt,
|
240
|
+
pages,
|
241
|
+
},
|
242
|
+
conversationId
|
243
|
+
);
|
169
244
|
|
170
245
|
onRequestAborted(req, res, () => {
|
171
246
|
editStream.abort();
|
@@ -175,19 +250,24 @@ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
|
175
250
|
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
176
251
|
res.set(ConversationIdHeader, editStream.getConversationId());
|
177
252
|
|
253
|
+
const promises: Promise<void>[] = [];
|
178
254
|
editStream.on('data', (data: StormEvent) => {
|
179
255
|
try {
|
180
|
-
|
256
|
+
if (data.type === 'PAGE') {
|
257
|
+
promises.push(sendPageEvent(editStream.getConversationId(), data, res));
|
258
|
+
} else {
|
259
|
+
sendEvent(res, data);
|
260
|
+
}
|
181
261
|
} catch (e) {
|
182
262
|
console.error('Failed to process event', e);
|
183
263
|
}
|
184
264
|
});
|
185
265
|
|
186
266
|
await waitForStormStream(editStream);
|
187
|
-
|
188
267
|
if (editStream.isAborted()) {
|
189
268
|
return;
|
190
269
|
}
|
270
|
+
await Promise.all(promises);
|
191
271
|
|
192
272
|
sendDone(res);
|
193
273
|
} catch (err: any) {
|
@@ -428,4 +508,14 @@ function onRequestAborted(req: KapetaBodyRequest, res: Response, onAborted: () =
|
|
428
508
|
});
|
429
509
|
}
|
430
510
|
|
511
|
+
function sendPageEvent(mainConversationId: string, data: StormEventPage, res: Response) {
|
512
|
+
return writePageToDisk(mainConversationId, data)
|
513
|
+
.catch((err) => {
|
514
|
+
console.error('Failed to write page to disk', err);
|
515
|
+
})
|
516
|
+
.then(() => {
|
517
|
+
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
518
|
+
});
|
519
|
+
}
|
520
|
+
|
431
521
|
export default router;
|
package/src/storm/stormClient.ts
CHANGED
@@ -15,6 +15,7 @@ import {
|
|
15
15
|
StormUIListPrompt,
|
16
16
|
} from './stream';
|
17
17
|
import { getRawAsset } from 'node:sea';
|
18
|
+
import { Page, StormEventPageUrl } from './events';
|
18
19
|
|
19
20
|
export const STORM_ID = 'storm';
|
20
21
|
|
@@ -28,15 +29,20 @@ export interface UIPagePrompt {
|
|
28
29
|
path: string;
|
29
30
|
method: string;
|
30
31
|
description: string;
|
32
|
+
storage_prefix: string;
|
31
33
|
}
|
32
34
|
|
33
35
|
export interface UIPageEditPrompt {
|
34
36
|
planDescription: string;
|
35
37
|
blockDescription: string;
|
36
|
-
pages:
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
pages: Page[];
|
39
|
+
prompt: string;
|
40
|
+
}
|
41
|
+
|
42
|
+
export interface UIPageEditRequest {
|
43
|
+
planDescription: string;
|
44
|
+
blockDescription: string;
|
45
|
+
pages: StormEventPageUrl['payload'][];
|
40
46
|
prompt: string;
|
41
47
|
}
|
42
48
|
|