@kapeta/local-cluster-service 0.65.0 → 0.67.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 +16 -1
- package/dist/cjs/src/storm/PageGenerator.js +36 -7
- package/dist/cjs/src/storm/PromiseQueue.d.ts +7 -0
- package/dist/cjs/src/storm/PromiseQueue.js +27 -1
- package/dist/cjs/src/storm/UIServer.js +3 -0
- package/dist/cjs/src/storm/events.d.ts +9 -1
- package/dist/cjs/src/storm/page-utils.d.ts +9 -1
- package/dist/cjs/src/storm/page-utils.js +45 -7
- package/dist/cjs/src/storm/routes.js +97 -5
- package/dist/cjs/src/storm/stormClient.d.ts +4 -0
- package/dist/cjs/src/storm/stormClient.js +12 -0
- package/dist/esm/src/storm/PageGenerator.d.ts +16 -1
- package/dist/esm/src/storm/PageGenerator.js +36 -7
- package/dist/esm/src/storm/PromiseQueue.d.ts +7 -0
- package/dist/esm/src/storm/PromiseQueue.js +27 -1
- package/dist/esm/src/storm/UIServer.js +3 -0
- package/dist/esm/src/storm/events.d.ts +9 -1
- package/dist/esm/src/storm/page-utils.d.ts +9 -1
- package/dist/esm/src/storm/page-utils.js +45 -7
- package/dist/esm/src/storm/routes.js +97 -5
- package/dist/esm/src/storm/stormClient.d.ts +4 -0
- package/dist/esm/src/storm/stormClient.js +12 -0
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +58 -15
- package/src/storm/PromiseQueue.ts +34 -0
- package/src/storm/UIServer.ts +5 -1
- package/src/storm/events.ts +11 -1
- package/src/storm/page-utils.ts +53 -8
- package/src/storm/routes.ts +110 -8
- package/src/storm/stormClient.ts +17 -0
@@ -18,6 +18,7 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
18
18
|
systemId;
|
19
19
|
references = new Map();
|
20
20
|
uiShells = [];
|
21
|
+
theme = '';
|
21
22
|
constructor(systemId, concurrency = 5) {
|
22
23
|
super();
|
23
24
|
this.systemId = systemId;
|
@@ -32,18 +33,22 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
32
33
|
addUiShell(uiShell) {
|
33
34
|
this.uiShells.push(uiShell);
|
34
35
|
}
|
36
|
+
setUiTheme(theme) {
|
37
|
+
this.theme = theme;
|
38
|
+
}
|
35
39
|
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false) {
|
36
40
|
if (!overwrite && this.references.has(initialPrompt.path)) {
|
37
|
-
console.log('Ignoring duplicate prompt', initialPrompt.path);
|
41
|
+
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
38
42
|
return Promise.resolve();
|
39
43
|
}
|
40
44
|
if (!overwrite && (0, page_utils_1.hasPageOnDisk)(this.systemId, initialPrompt.method, initialPrompt.path)) {
|
41
|
-
console.log('Ignoring prompt with existing page', initialPrompt.path);
|
45
|
+
//console.log('Ignoring prompt with existing page', initialPrompt.path);
|
42
46
|
return Promise.resolve();
|
43
47
|
}
|
44
48
|
const prompt = {
|
45
49
|
...initialPrompt,
|
46
50
|
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
51
|
+
theme: this.theme,
|
47
52
|
};
|
48
53
|
const generator = new PageGenerator(prompt, conversationId);
|
49
54
|
this.references.set(prompt.path, generator);
|
@@ -51,9 +56,8 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
51
56
|
}
|
52
57
|
async addPageGenerator(generator) {
|
53
58
|
generator.on('event', (event) => this.emit('event', event));
|
54
|
-
generator.on('page_refs', ({ event, references }) => {
|
55
|
-
|
56
|
-
references.forEach((reference) => {
|
59
|
+
generator.on('page_refs', async ({ event, references }) => {
|
60
|
+
const promises = references.map(async (reference) => {
|
57
61
|
if (reference.url.startsWith('#') ||
|
58
62
|
reference.url.startsWith('javascript:') ||
|
59
63
|
reference.url.startsWith('http://') ||
|
@@ -62,14 +66,17 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
62
66
|
}
|
63
67
|
switch (reference.type) {
|
64
68
|
case 'image':
|
65
|
-
|
69
|
+
await this.addImagePrompt({
|
70
|
+
...reference,
|
71
|
+
content: event.payload.content,
|
72
|
+
});
|
66
73
|
break;
|
67
74
|
case 'css':
|
68
75
|
case 'javascript':
|
69
76
|
//console.log('Ignoring reference', reference);
|
70
77
|
break;
|
71
78
|
case 'html':
|
72
|
-
console.log('Adding page generator for', reference);
|
79
|
+
//console.log('Adding page generator for', reference);
|
73
80
|
const paths = Array.from(this.references.keys());
|
74
81
|
this.addPrompt({
|
75
82
|
name: reference.name,
|
@@ -84,10 +91,13 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
84
91
|
: ''),
|
85
92
|
description: reference.description,
|
86
93
|
filename: '',
|
94
|
+
theme: this.theme,
|
87
95
|
});
|
88
96
|
break;
|
89
97
|
}
|
90
98
|
});
|
99
|
+
await Promise.allSettled(promises);
|
100
|
+
this.emit('page', event);
|
91
101
|
});
|
92
102
|
return this.queue.add(() => generator.generate());
|
93
103
|
}
|
@@ -97,6 +107,25 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
97
107
|
wait() {
|
98
108
|
return this.queue.wait();
|
99
109
|
}
|
110
|
+
async addImagePrompt(prompt) {
|
111
|
+
const result = await stormClient_1.stormClient.createImage(`Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim());
|
112
|
+
const futures = [];
|
113
|
+
result.on('data', async (event) => {
|
114
|
+
if (event.type === 'IMAGE') {
|
115
|
+
const future = (0, PromiseQueue_1.createFuture)();
|
116
|
+
futures.push(future);
|
117
|
+
this.emit('image', event, prompt, future);
|
118
|
+
setTimeout(() => {
|
119
|
+
if (!future.isResolved()) {
|
120
|
+
console.log('Image prompt timed out', prompt);
|
121
|
+
future.reject(new Error('Image prompt timed out'));
|
122
|
+
}
|
123
|
+
}, 30000);
|
124
|
+
}
|
125
|
+
});
|
126
|
+
await result.waitForDone();
|
127
|
+
await Promise.allSettled(futures.map((f) => f.promise));
|
128
|
+
}
|
100
129
|
}
|
101
130
|
exports.PageQueue = PageQueue;
|
102
131
|
class PageGenerator extends node_events_1.EventEmitter {
|
@@ -3,6 +3,13 @@
|
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
5
|
export type Future<T> = () => Promise<T>;
|
6
|
+
export type FuturePromise<T> = {
|
7
|
+
promise: Promise<T>;
|
8
|
+
resolve: (value: T) => void;
|
9
|
+
reject: (reason: any) => void;
|
10
|
+
isResolved: () => boolean;
|
11
|
+
};
|
12
|
+
export declare function createFuture<T = void>(): FuturePromise<T>;
|
6
13
|
export declare class PromiseQueue {
|
7
14
|
private readonly queue;
|
8
15
|
private readonly active;
|
@@ -1,6 +1,32 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.PromiseQueue = void 0;
|
3
|
+
exports.PromiseQueue = exports.createFuture = void 0;
|
4
|
+
function createFuture() {
|
5
|
+
let resolved = false;
|
6
|
+
let resolve = () => {
|
7
|
+
resolved = true;
|
8
|
+
};
|
9
|
+
let reject = () => {
|
10
|
+
resolved = true;
|
11
|
+
};
|
12
|
+
const promise = new Promise((res, rej) => {
|
13
|
+
resolve = (value) => {
|
14
|
+
resolved = true;
|
15
|
+
res(value);
|
16
|
+
};
|
17
|
+
reject = (reason) => {
|
18
|
+
resolved = true;
|
19
|
+
rej(reason);
|
20
|
+
};
|
21
|
+
});
|
22
|
+
return {
|
23
|
+
promise,
|
24
|
+
resolve,
|
25
|
+
reject,
|
26
|
+
isResolved: () => resolved,
|
27
|
+
};
|
28
|
+
}
|
29
|
+
exports.createFuture = createFuture;
|
4
30
|
class PromiseQueue {
|
5
31
|
queue = [];
|
6
32
|
active = [];
|
@@ -12,6 +12,7 @@ const express_1 = __importDefault(require("express"));
|
|
12
12
|
const page_utils_1 = require("./page-utils");
|
13
13
|
const clusterService_1 = require("../clusterService");
|
14
14
|
const http_1 = require("http");
|
15
|
+
const path_1 = require("path");
|
15
16
|
class UIServer {
|
16
17
|
systemId;
|
17
18
|
port = 50000;
|
@@ -28,6 +29,8 @@ class UIServer {
|
|
28
29
|
window.sessionStorage.clear();
|
29
30
|
</script>`);
|
30
31
|
});
|
32
|
+
// Make it possible to serve static assets
|
33
|
+
app.use(express_1.default.static((0, path_1.join)((0, page_utils_1.getSystemBaseDir)(this.systemId), 'public'), { fallthrough: true }));
|
31
34
|
app.all('/*', (req, res) => {
|
32
35
|
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
33
36
|
});
|
@@ -255,6 +255,14 @@ export interface StormEventDone {
|
|
255
255
|
type: 'DONE';
|
256
256
|
created: number;
|
257
257
|
}
|
258
|
+
export interface StormImage {
|
259
|
+
type: 'IMAGE';
|
260
|
+
reason: string;
|
261
|
+
created: number;
|
262
|
+
payload: {
|
263
|
+
href: string;
|
264
|
+
};
|
265
|
+
}
|
258
266
|
export interface StormEventDefinitionChange {
|
259
267
|
type: 'DEFINITION_CHANGE';
|
260
268
|
reason: string;
|
@@ -384,5 +392,5 @@ export interface StormEventUIStarted {
|
|
384
392
|
resetUrl: string;
|
385
393
|
};
|
386
394
|
}
|
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;
|
395
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage;
|
388
396
|
export {};
|
@@ -2,14 +2,22 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import { StormEventPage } from './events';
|
5
|
+
import { StormEventFileDone, StormEventPage, StormImage } from './events';
|
6
6
|
import { Response } from 'express';
|
7
7
|
import { ConversationItem } from './stream';
|
8
|
+
import { ImagePrompt } from './PageGenerator';
|
8
9
|
export declare const SystemIdHeader = "System-Id";
|
9
10
|
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
10
11
|
path: string;
|
11
12
|
}>;
|
13
|
+
export declare function writeAssetToDisk(systemId: string, event: StormEventFileDone): Promise<{
|
14
|
+
path: string;
|
15
|
+
}>;
|
16
|
+
export declare function writeImageToDisk(systemId: string, event: StormImage, prompt: ImagePrompt): Promise<{
|
17
|
+
path: string;
|
18
|
+
}>;
|
12
19
|
export declare function hasPageOnDisk(systemId: string, method: string, path: string): boolean;
|
20
|
+
export declare function getSystemBaseDir(systemId: string): string;
|
13
21
|
export declare function resolveReadPath(systemId: string, path: string, method: string): string | null;
|
14
22
|
export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
|
15
23
|
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.hasPageOnDisk = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
6
|
+
exports.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.getSystemBaseDir = exports.hasPageOnDisk = exports.writeImageToDisk = exports.writeAssetToDisk = 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"));
|
@@ -15,7 +15,7 @@ function normalizePath(path) {
|
|
15
15
|
.replace(/\{[a-z]+}/gi, '*');
|
16
16
|
}
|
17
17
|
async function writePageToDisk(systemId, event) {
|
18
|
-
const baseDir =
|
18
|
+
const baseDir = getSystemBaseDir(systemId);
|
19
19
|
const filePath = getFilePath(event.payload.method);
|
20
20
|
const path = path_1.default.join(baseDir, normalizePath(event.payload.path), filePath);
|
21
21
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
|
@@ -26,24 +26,62 @@ async function writePageToDisk(systemId, event) {
|
|
26
26
|
};
|
27
27
|
}
|
28
28
|
exports.writePageToDisk = writePageToDisk;
|
29
|
+
async function writeAssetToDisk(systemId, event) {
|
30
|
+
const baseDir = getSystemBaseDir(systemId);
|
31
|
+
const path = path_1.default.join(baseDir, 'public', event.payload.filename);
|
32
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
|
33
|
+
await fs_extra_1.default.writeFile(path, event.payload.content);
|
34
|
+
return {
|
35
|
+
path,
|
36
|
+
};
|
37
|
+
}
|
38
|
+
exports.writeAssetToDisk = writeAssetToDisk;
|
39
|
+
async function writeImageToDisk(systemId, event, prompt) {
|
40
|
+
const baseDir = getSystemBaseDir(systemId);
|
41
|
+
const path = path_1.default.join(baseDir, normalizePath(prompt.url));
|
42
|
+
const response = await fetch(event.payload.href);
|
43
|
+
if (!response.ok || !response.body) {
|
44
|
+
throw new Error(`Failed to fetch image: ${event.payload.href}`);
|
45
|
+
}
|
46
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
|
47
|
+
const buffer = await response.arrayBuffer();
|
48
|
+
await fs_extra_1.default.writeFile(path, Buffer.from(buffer));
|
49
|
+
console.log(`Image written to disk: ${event.payload.href} > ${path}`);
|
50
|
+
return {
|
51
|
+
path,
|
52
|
+
};
|
53
|
+
}
|
54
|
+
exports.writeImageToDisk = writeImageToDisk;
|
29
55
|
function hasPageOnDisk(systemId, method, path) {
|
30
|
-
const baseDir =
|
56
|
+
const baseDir = getSystemBaseDir(systemId);
|
31
57
|
const filePath = getFilePath(method);
|
32
58
|
const fullPath = path_1.default.join(baseDir, normalizePath(path), filePath);
|
33
59
|
return fs_extra_1.default.existsSync(fullPath);
|
34
60
|
}
|
35
61
|
exports.hasPageOnDisk = hasPageOnDisk;
|
36
|
-
function
|
62
|
+
function getSystemBaseDir(systemId) {
|
37
63
|
return path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId);
|
38
64
|
}
|
65
|
+
exports.getSystemBaseDir = getSystemBaseDir;
|
39
66
|
function getFilePath(method) {
|
40
67
|
return path_1.default.join(method.toLowerCase(), 'index.html');
|
41
68
|
}
|
42
69
|
function resolveReadPath(systemId, path, method) {
|
43
|
-
const baseDir =
|
70
|
+
const baseDir = getSystemBaseDir(systemId);
|
44
71
|
path = normalizePath(path);
|
72
|
+
let fullPath = path_1.default.join(baseDir, path);
|
73
|
+
//First check if there is a file at the exact path
|
74
|
+
try {
|
75
|
+
const stat = fs_extra_1.default.statSync(fullPath);
|
76
|
+
if (stat && stat.isFile()) {
|
77
|
+
return fullPath;
|
78
|
+
}
|
79
|
+
}
|
80
|
+
catch (e) {
|
81
|
+
// Ignore
|
82
|
+
}
|
45
83
|
const filePath = getFilePath(method);
|
46
|
-
|
84
|
+
fullPath = path_1.default.join(baseDir, path, filePath);
|
47
85
|
if (fs_extra_1.default.existsSync(fullPath)) {
|
48
86
|
return fullPath;
|
49
87
|
}
|
@@ -84,7 +122,7 @@ function readPageFromDisk(systemId, path, method, res) {
|
|
84
122
|
return;
|
85
123
|
}
|
86
124
|
res.type(filePath.split('.').pop());
|
87
|
-
const content = fs_extra_1.default.readFileSync(filePath
|
125
|
+
const content = fs_extra_1.default.readFileSync(filePath);
|
88
126
|
res.write(content);
|
89
127
|
res.end();
|
90
128
|
}
|
@@ -21,6 +21,7 @@ const assetManager_1 = require("../assetManager");
|
|
21
21
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
22
22
|
const page_utils_1 = require("./page-utils");
|
23
23
|
const UIServer_1 = require("./UIServer");
|
24
|
+
const crypto_1 = require("crypto");
|
24
25
|
const PageGenerator_1 = require("./PageGenerator");
|
25
26
|
const UI_SERVERS = {};
|
26
27
|
const router = (0, express_promise_router_1.default)();
|
@@ -77,6 +78,21 @@ router.post('/ui/screen', async (req, res) => {
|
|
77
78
|
promises.push(sendPageEvent(systemId, data, res));
|
78
79
|
}
|
79
80
|
});
|
81
|
+
queue.on('image', async (screenData, prompt, future) => {
|
82
|
+
if (!systemId) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
try {
|
86
|
+
const promise = handleImageEvent(systemId, screenData, prompt);
|
87
|
+
promises.push(promise);
|
88
|
+
await promise;
|
89
|
+
future.resolve();
|
90
|
+
}
|
91
|
+
catch (e) {
|
92
|
+
console.error('Failed to handle image event', e);
|
93
|
+
future.reject(e);
|
94
|
+
}
|
95
|
+
});
|
80
96
|
await queue.addPrompt(aiRequest, conversationId, true);
|
81
97
|
await queue.wait();
|
82
98
|
await Promise.allSettled(promises);
|
@@ -142,6 +158,8 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
142
158
|
title: landingPage.title,
|
143
159
|
filename: landingPage.filename,
|
144
160
|
storage_prefix: systemId + '_',
|
161
|
+
// TODO: Add themes to this request type
|
162
|
+
theme: '',
|
145
163
|
});
|
146
164
|
}
|
147
165
|
catch (e) {
|
@@ -156,6 +174,18 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
156
174
|
pageQueue.on('page', (screenData) => {
|
157
175
|
pageEventPromises.push(sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
|
158
176
|
});
|
177
|
+
pageQueue.on('image', async (screenData, prompt, future) => {
|
178
|
+
try {
|
179
|
+
const promise = handleImageEvent(landingPagesStream.getConversationId(), screenData, prompt);
|
180
|
+
pageEventPromises.push(promise);
|
181
|
+
await promise;
|
182
|
+
future.resolve();
|
183
|
+
}
|
184
|
+
catch (e) {
|
185
|
+
console.error('Failed to handle image event', e);
|
186
|
+
future.reject(e);
|
187
|
+
}
|
188
|
+
});
|
159
189
|
pageQueue.on('event', (screenData) => {
|
160
190
|
sendEvent(res, screenData);
|
161
191
|
});
|
@@ -177,11 +207,10 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
177
207
|
router.post('/:handle/ui', async (req, res) => {
|
178
208
|
const handle = req.params.handle;
|
179
209
|
try {
|
180
|
-
const
|
210
|
+
const outerConversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || (0, crypto_1.randomUUID)();
|
181
211
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
182
212
|
// Get user journeys
|
183
|
-
const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest,
|
184
|
-
const outerConversationId = userJourneysStream.getConversationId();
|
213
|
+
const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest, outerConversationId);
|
185
214
|
onRequestAborted(req, res, () => {
|
186
215
|
userJourneysStream.abort();
|
187
216
|
});
|
@@ -213,9 +242,50 @@ router.post('/:handle/ui', async (req, res) => {
|
|
213
242
|
userJourneysStream.abort();
|
214
243
|
sendError(error, res);
|
215
244
|
});
|
245
|
+
let theme = '';
|
246
|
+
try {
|
247
|
+
const themeStream = await stormClient_1.stormClient.createTheme(aiRequest, outerConversationId);
|
248
|
+
onRequestAborted(req, res, () => {
|
249
|
+
themeStream.abort();
|
250
|
+
});
|
251
|
+
themeStream.on('data', (evt) => {
|
252
|
+
sendEvent(res, evt);
|
253
|
+
if (evt.type === 'FILE_DONE') {
|
254
|
+
theme = evt.payload.content;
|
255
|
+
(0, page_utils_1.writeAssetToDisk)(outerConversationId, evt).catch((err) => {
|
256
|
+
sendEvent(res, {
|
257
|
+
type: 'ERROR_INTERNAL',
|
258
|
+
created: new Date().getTime(),
|
259
|
+
payload: { error: err.message },
|
260
|
+
reason: 'Failed to save theme',
|
261
|
+
});
|
262
|
+
});
|
263
|
+
}
|
264
|
+
});
|
265
|
+
themeStream.on('error', (error) => {
|
266
|
+
console.error(error);
|
267
|
+
sendEvent(res, {
|
268
|
+
type: 'ERROR_INTERNAL',
|
269
|
+
created: new Date().getTime(),
|
270
|
+
payload: { error: error.message },
|
271
|
+
reason: 'Failed to create theme',
|
272
|
+
});
|
273
|
+
});
|
274
|
+
await waitForStormStream(themeStream);
|
275
|
+
}
|
276
|
+
catch (e) {
|
277
|
+
console.error('Failed to generate theme', e);
|
278
|
+
sendEvent(res, {
|
279
|
+
type: 'ERROR_INTERNAL',
|
280
|
+
created: new Date().getTime(),
|
281
|
+
payload: { error: e.message },
|
282
|
+
reason: 'Failed to create theme',
|
283
|
+
});
|
284
|
+
}
|
216
285
|
await waitForStormStream(userJourneysStream);
|
217
286
|
// Get the UI shells
|
218
287
|
const shellsStream = await stormClient_1.stormClient.createUIShells({
|
288
|
+
theme: theme ? `// filename: theme.css\n${theme}` : undefined,
|
219
289
|
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
220
290
|
name: screen.name,
|
221
291
|
title: screen.title,
|
@@ -224,13 +294,14 @@ router.post('/:handle/ui', async (req, res) => {
|
|
224
294
|
method: screen.method,
|
225
295
|
requirements: screen.requirements,
|
226
296
|
})),
|
227
|
-
},
|
297
|
+
}, outerConversationId);
|
228
298
|
onRequestAborted(req, res, () => {
|
229
299
|
shellsStream.abort();
|
230
300
|
});
|
231
301
|
const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
|
302
|
+
queue.setUiTheme(theme);
|
232
303
|
shellsStream.on('data', (data) => {
|
233
|
-
console.log('Processing shell event', data);
|
304
|
+
//console.log('Processing shell event', data);
|
234
305
|
sendEvent(res, data);
|
235
306
|
if (data.type !== 'UI_SHELL') {
|
236
307
|
return;
|
@@ -266,6 +337,18 @@ router.post('/:handle/ui', async (req, res) => {
|
|
266
337
|
queue.on('page', (screenData) => {
|
267
338
|
pageEventPromises.push(sendPageEvent(outerConversationId, screenData, res));
|
268
339
|
});
|
340
|
+
queue.on('image', async (screenData, prompt, future) => {
|
341
|
+
try {
|
342
|
+
const promise = handleImageEvent(outerConversationId, screenData, prompt);
|
343
|
+
pageEventPromises.push(promise);
|
344
|
+
await promise;
|
345
|
+
future.resolve();
|
346
|
+
}
|
347
|
+
catch (e) {
|
348
|
+
console.error('Failed to handle image event', e);
|
349
|
+
future.reject(e);
|
350
|
+
}
|
351
|
+
});
|
269
352
|
queue.on('event', (screenData) => {
|
270
353
|
sendEvent(res, screenData);
|
271
354
|
});
|
@@ -279,6 +362,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
279
362
|
title: screen.title,
|
280
363
|
filename: screen.filename,
|
281
364
|
storage_prefix: outerConversationId + '_',
|
365
|
+
theme,
|
282
366
|
}));
|
283
367
|
}
|
284
368
|
await queue.wait();
|
@@ -580,4 +664,12 @@ async function sendPageEvent(mainConversationId, data, res) {
|
|
580
664
|
}
|
581
665
|
sendEvent(res, convertPageEvent(data, data.payload.conversationId, mainConversationId));
|
582
666
|
}
|
667
|
+
async function handleImageEvent(mainConversationId, data, prompt) {
|
668
|
+
try {
|
669
|
+
await (0, page_utils_1.writeImageToDisk)(mainConversationId, data, prompt);
|
670
|
+
}
|
671
|
+
catch (err) {
|
672
|
+
console.error('Failed to write image to disk', err);
|
673
|
+
}
|
674
|
+
}
|
583
675
|
exports.default = router;
|
@@ -4,6 +4,7 @@ import { Page, StormEventPageUrl } from './events';
|
|
4
4
|
export declare const STORM_ID = "storm";
|
5
5
|
export declare const ConversationIdHeader = "Conversation-Id";
|
6
6
|
export interface UIShellsPrompt {
|
7
|
+
theme?: string;
|
7
8
|
pages: {
|
8
9
|
name: string;
|
9
10
|
title: string;
|
@@ -23,6 +24,7 @@ export interface UIPagePrompt {
|
|
23
24
|
description: string;
|
24
25
|
storage_prefix: string;
|
25
26
|
shell_page?: string;
|
27
|
+
theme: string;
|
26
28
|
}
|
27
29
|
export interface UIPageSamplePrompt extends UIPagePrompt {
|
28
30
|
variantId: string;
|
@@ -60,6 +62,7 @@ declare class StormClient {
|
|
60
62
|
createMetadata(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
61
63
|
createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
|
62
64
|
createUIUserJourneys(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
65
|
+
createTheme(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
63
66
|
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
64
67
|
createUILandingPages(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
65
68
|
createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
|
@@ -70,6 +73,7 @@ declare class StormClient {
|
|
70
73
|
classifyUIReferences(prompt: string, conversationId?: string): Promise<StormStream>;
|
71
74
|
editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
|
72
75
|
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
76
|
+
createImage(prompt: string, conversationId?: string): Promise<StormStream>;
|
73
77
|
createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
74
78
|
createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
|
75
79
|
createErrorClassification(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
|
@@ -92,6 +92,12 @@ class StormClient {
|
|
92
92
|
conversationId,
|
93
93
|
});
|
94
94
|
}
|
95
|
+
createTheme(prompt, conversationId) {
|
96
|
+
return this.send('/v2/ui/theme', {
|
97
|
+
prompt: prompt,
|
98
|
+
conversationId,
|
99
|
+
});
|
100
|
+
}
|
95
101
|
createUIShells(prompt, conversationId) {
|
96
102
|
return this.send('/v2/ui/shells', {
|
97
103
|
prompt: JSON.stringify(prompt),
|
@@ -149,6 +155,12 @@ class StormClient {
|
|
149
155
|
conversationId,
|
150
156
|
});
|
151
157
|
}
|
158
|
+
createImage(prompt, conversationId) {
|
159
|
+
return this.send('/v2/ui/image', {
|
160
|
+
prompt,
|
161
|
+
conversationId,
|
162
|
+
});
|
163
|
+
}
|
152
164
|
createUIImplementation(prompt, conversationId) {
|
153
165
|
return this.send('/v2/ui/merge', {
|
154
166
|
prompt,
|