@kapeta/local-cluster-service 0.60.3 → 0.61.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 +12 -0
- package/dist/cjs/src/storm/events.d.ts +20 -1
- package/dist/cjs/src/storm/routes.js +100 -45
- package/dist/cjs/src/storm/stormClient.d.ts +12 -0
- package/dist/cjs/src/storm/stormClient.js +6 -0
- package/dist/esm/src/storm/events.d.ts +20 -1
- package/dist/esm/src/storm/routes.js +100 -45
- package/dist/esm/src/storm/stormClient.d.ts +12 -0
- package/dist/esm/src/storm/stormClient.js +6 -0
- package/package.json +1 -1
- package/src/storm/events.ts +25 -1
- package/src/storm/routes.ts +133 -57
- package/src/storm/stormClient.ts +19 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# [0.61.0](https://github.com/kapetacom/local-cluster-service/compare/v0.60.3...v0.61.0) (2024-08-09)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Move UI server start to later ([fb75cb1](https://github.com/kapetacom/local-cluster-service/commit/fb75cb1b44e8047b9f451a12219d3c8295d9394e))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Get UI shells and use them when creating pages ([de6dc6f](https://github.com/kapetacom/local-cluster-service/commit/de6dc6fcebeaad1522a9d280ee95cf163f73fb7f))
|
12
|
+
|
1
13
|
## [0.60.3](https://github.com/kapetacom/local-cluster-service/compare/v0.60.2...v0.60.3) (2024-08-07)
|
2
14
|
|
3
15
|
|
@@ -280,6 +280,7 @@ export interface Page {
|
|
280
280
|
method: string;
|
281
281
|
conversationId: string;
|
282
282
|
prompt: string;
|
283
|
+
shellPage?: string;
|
283
284
|
}
|
284
285
|
export interface StormEventPage {
|
285
286
|
type: 'PAGE';
|
@@ -323,5 +324,23 @@ export interface StormEventUserJourney {
|
|
323
324
|
created: number;
|
324
325
|
payload: UserJourney;
|
325
326
|
}
|
326
|
-
export
|
327
|
+
export interface UIShell {
|
328
|
+
name: string;
|
329
|
+
content: string;
|
330
|
+
screens: string[];
|
331
|
+
}
|
332
|
+
export interface StormEventUIShell {
|
333
|
+
type: 'UI_SHELL';
|
334
|
+
reason: string;
|
335
|
+
created: number;
|
336
|
+
payload: UIShell;
|
337
|
+
}
|
338
|
+
export interface StormEventPromptImprove {
|
339
|
+
type: 'PROMPT_IMPROVE';
|
340
|
+
reason: string;
|
341
|
+
payload: {
|
342
|
+
prompt: string;
|
343
|
+
};
|
344
|
+
}
|
345
|
+
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;
|
327
346
|
export {};
|
@@ -110,24 +110,18 @@ router.post('/:handle/ui', async (req, res) => {
|
|
110
110
|
try {
|
111
111
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
112
112
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
113
|
+
// Get user journeys
|
113
114
|
const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
|
115
|
+
const outerConversationId = userJourneysStream.getConversationId();
|
114
116
|
onRequestAborted(req, res, () => {
|
115
117
|
userJourneysStream.abort();
|
116
118
|
});
|
117
119
|
res.set('Content-Type', 'application/x-ndjson');
|
118
120
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
119
|
-
res.set(stormClient_1.ConversationIdHeader,
|
120
|
-
const
|
121
|
-
|
122
|
-
onRequestAborted(req, res, () => {
|
123
|
-
queue.cancel();
|
124
|
-
});
|
125
|
-
const systemId = userJourneysStream.getConversationId();
|
126
|
-
UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
|
127
|
-
await UI_SERVERS[systemId].start();
|
128
|
-
userJourneysStream.on('data', async (data) => {
|
121
|
+
res.set(stormClient_1.ConversationIdHeader, outerConversationId);
|
122
|
+
const uniqueUserJourneyScreens = {};
|
123
|
+
userJourneysStream.on('data', (data) => {
|
129
124
|
try {
|
130
|
-
console.log('Processing user journey event', data);
|
131
125
|
sendEvent(res, data);
|
132
126
|
if (data.type !== 'USER_JOURNEY') {
|
133
127
|
return;
|
@@ -136,48 +130,109 @@ router.post('/:handle/ui', async (req, res) => {
|
|
136
130
|
return;
|
137
131
|
}
|
138
132
|
data.payload.screens.forEach((screen) => {
|
139
|
-
if (screen.name
|
140
|
-
|
133
|
+
if (!uniqueUserJourneyScreens[screen.name]) {
|
134
|
+
uniqueUserJourneyScreens[screen.name] = screen;
|
141
135
|
}
|
142
|
-
promises[screen.name] = queue.add(() => new Promise(async (resolve, reject) => {
|
143
|
-
try {
|
144
|
-
const innerConversationId = node_uuid_1.default.v4();
|
145
|
-
const screenStream = await stormClient_1.stormClient.createUIPage({
|
146
|
-
prompt: screen.requirements,
|
147
|
-
method: screen.method,
|
148
|
-
path: screen.path,
|
149
|
-
description: screen.requirements,
|
150
|
-
name: screen.name,
|
151
|
-
title: screen.title,
|
152
|
-
filename: screen.filename,
|
153
|
-
storage_prefix: userJourneysStream.getConversationId() + '_',
|
154
|
-
}, innerConversationId);
|
155
|
-
const promises = [];
|
156
|
-
screenStream.on('data', (screenData) => {
|
157
|
-
if (screenData.type === 'PAGE') {
|
158
|
-
screenData.payload.conversationId = innerConversationId;
|
159
|
-
promises.push(sendPageEvent(userJourneysStream.getConversationId(), screenData, res));
|
160
|
-
}
|
161
|
-
else {
|
162
|
-
sendEvent(res, screenData);
|
163
|
-
}
|
164
|
-
});
|
165
|
-
screenStream.on('end', () => {
|
166
|
-
Promise.allSettled(promises).finally(resolve);
|
167
|
-
});
|
168
|
-
}
|
169
|
-
catch (e) {
|
170
|
-
console.error('Failed to process screen', e);
|
171
|
-
reject(e);
|
172
|
-
}
|
173
|
-
}));
|
174
136
|
});
|
175
137
|
}
|
176
138
|
catch (e) {
|
177
139
|
console.error('Failed to process event', e);
|
178
140
|
}
|
179
141
|
});
|
142
|
+
userJourneysStream.on('error', (error) => {
|
143
|
+
console.error('Error on userJourneysStream', error);
|
144
|
+
userJourneysStream.abort();
|
145
|
+
sendError(error, res);
|
146
|
+
});
|
180
147
|
await waitForStormStream(userJourneysStream);
|
148
|
+
// Get the UI shells
|
149
|
+
const shellsStream = await stormClient_1.stormClient.createUIShells({
|
150
|
+
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
151
|
+
name: screen.name,
|
152
|
+
title: screen.title,
|
153
|
+
filename: screen.filename,
|
154
|
+
path: screen.path,
|
155
|
+
method: screen.method,
|
156
|
+
requirements: screen.requirements,
|
157
|
+
})),
|
158
|
+
}, conversationId);
|
159
|
+
onRequestAborted(req, res, () => {
|
160
|
+
shellsStream.abort();
|
161
|
+
});
|
162
|
+
const uiShells = [];
|
163
|
+
shellsStream.on('data', (data) => {
|
164
|
+
console.log('Processing shell event', data);
|
165
|
+
sendEvent(res, data);
|
166
|
+
if (data.type !== 'UI_SHELL') {
|
167
|
+
return;
|
168
|
+
}
|
169
|
+
if (shellsStream.isAborted()) {
|
170
|
+
return;
|
171
|
+
}
|
172
|
+
uiShells.push(data.payload);
|
173
|
+
});
|
174
|
+
shellsStream.on('error', (error) => {
|
175
|
+
console.error('Error on shellsStream', error);
|
176
|
+
shellsStream.abort();
|
177
|
+
sendError(error, res);
|
178
|
+
});
|
179
|
+
await waitForStormStream(shellsStream);
|
180
|
+
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
181
|
+
await UI_SERVERS[outerConversationId].start();
|
182
|
+
// Get the pages (5 at a time)
|
183
|
+
const queue = new PromiseQueue_1.PromiseQueue(5);
|
184
|
+
onRequestAborted(req, res, () => {
|
185
|
+
queue.cancel();
|
186
|
+
});
|
187
|
+
for (const screen of Object.values(uniqueUserJourneyScreens)) {
|
188
|
+
await queue.add(() => new Promise(async (resolve, reject) => {
|
189
|
+
try {
|
190
|
+
const innerConversationId = node_uuid_1.default.v4();
|
191
|
+
const screenStream = await stormClient_1.stormClient.createUIPage({
|
192
|
+
prompt: screen.requirements,
|
193
|
+
method: screen.method,
|
194
|
+
path: screen.path,
|
195
|
+
description: screen.requirements,
|
196
|
+
name: screen.name,
|
197
|
+
title: screen.title,
|
198
|
+
filename: screen.filename,
|
199
|
+
storage_prefix: outerConversationId + '_',
|
200
|
+
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
201
|
+
}, innerConversationId);
|
202
|
+
const promiseList = [];
|
203
|
+
screenStream.on('data', (screenData) => {
|
204
|
+
if (screenData.type === 'PAGE') {
|
205
|
+
promiseList.push(sendPageEvent(outerConversationId, {
|
206
|
+
...screenData,
|
207
|
+
payload: {
|
208
|
+
...screenData.payload,
|
209
|
+
conversationId: innerConversationId,
|
210
|
+
},
|
211
|
+
}, res));
|
212
|
+
}
|
213
|
+
else {
|
214
|
+
sendEvent(res, screenData);
|
215
|
+
}
|
216
|
+
});
|
217
|
+
screenStream.on('end', async () => {
|
218
|
+
try {
|
219
|
+
await Promise.allSettled(promiseList).finally(() => resolve(true));
|
220
|
+
}
|
221
|
+
catch (error) {
|
222
|
+
console.error('Failed to process screen', error);
|
223
|
+
}
|
224
|
+
});
|
225
|
+
screenStream.on('error', (error) => {
|
226
|
+
console.error('Error on screenStream', error);
|
227
|
+
screenStream.abort();
|
228
|
+
});
|
229
|
+
}
|
230
|
+
catch (e) {
|
231
|
+
console.error('Failed to process screen', e);
|
232
|
+
reject(e);
|
233
|
+
}
|
234
|
+
}));
|
235
|
+
}
|
181
236
|
await queue.wait();
|
182
237
|
if (userJourneysStream.isAborted()) {
|
183
238
|
return;
|
@@ -2,6 +2,16 @@ import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIIm
|
|
2
2
|
import { Page, StormEventPageUrl } from './events';
|
3
3
|
export declare const STORM_ID = "storm";
|
4
4
|
export declare const ConversationIdHeader = "Conversation-Id";
|
5
|
+
export interface UIShellsPrompt {
|
6
|
+
pages: {
|
7
|
+
name: string;
|
8
|
+
title: string;
|
9
|
+
filename: string;
|
10
|
+
path: string;
|
11
|
+
method: string;
|
12
|
+
requirements: string;
|
13
|
+
}[];
|
14
|
+
}
|
5
15
|
export interface UIPagePrompt {
|
6
16
|
name: string;
|
7
17
|
title: string;
|
@@ -11,6 +21,7 @@ export interface UIPagePrompt {
|
|
11
21
|
method: string;
|
12
22
|
description: string;
|
13
23
|
storage_prefix: string;
|
24
|
+
shell_page?: string;
|
14
25
|
}
|
15
26
|
export interface UIPageEditPrompt {
|
16
27
|
planDescription: string;
|
@@ -32,6 +43,7 @@ declare class StormClient {
|
|
32
43
|
createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
|
33
44
|
createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
|
34
45
|
createUIUserJourneys(prompt: string, conversationId?: string): Promise<StormStream>;
|
46
|
+
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
35
47
|
createUIPage(prompt: UIPagePrompt, conversationId?: string): Promise<StormStream>;
|
36
48
|
editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
|
37
49
|
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
@@ -92,6 +92,12 @@ class StormClient {
|
|
92
92
|
conversationId,
|
93
93
|
});
|
94
94
|
}
|
95
|
+
createUIShells(prompt, conversationId) {
|
96
|
+
return this.send('/v2/ui/shells', {
|
97
|
+
prompt: JSON.stringify(prompt),
|
98
|
+
conversationId,
|
99
|
+
});
|
100
|
+
}
|
95
101
|
createUIPage(prompt, conversationId) {
|
96
102
|
return this.send('/v2/ui/page', {
|
97
103
|
prompt: prompt,
|
@@ -280,6 +280,7 @@ export interface Page {
|
|
280
280
|
method: string;
|
281
281
|
conversationId: string;
|
282
282
|
prompt: string;
|
283
|
+
shellPage?: string;
|
283
284
|
}
|
284
285
|
export interface StormEventPage {
|
285
286
|
type: 'PAGE';
|
@@ -323,5 +324,23 @@ export interface StormEventUserJourney {
|
|
323
324
|
created: number;
|
324
325
|
payload: UserJourney;
|
325
326
|
}
|
326
|
-
export
|
327
|
+
export interface UIShell {
|
328
|
+
name: string;
|
329
|
+
content: string;
|
330
|
+
screens: string[];
|
331
|
+
}
|
332
|
+
export interface StormEventUIShell {
|
333
|
+
type: 'UI_SHELL';
|
334
|
+
reason: string;
|
335
|
+
created: number;
|
336
|
+
payload: UIShell;
|
337
|
+
}
|
338
|
+
export interface StormEventPromptImprove {
|
339
|
+
type: 'PROMPT_IMPROVE';
|
340
|
+
reason: string;
|
341
|
+
payload: {
|
342
|
+
prompt: string;
|
343
|
+
};
|
344
|
+
}
|
345
|
+
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;
|
327
346
|
export {};
|
@@ -110,24 +110,18 @@ router.post('/:handle/ui', async (req, res) => {
|
|
110
110
|
try {
|
111
111
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
112
112
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
113
|
+
// Get user journeys
|
113
114
|
const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
|
115
|
+
const outerConversationId = userJourneysStream.getConversationId();
|
114
116
|
onRequestAborted(req, res, () => {
|
115
117
|
userJourneysStream.abort();
|
116
118
|
});
|
117
119
|
res.set('Content-Type', 'application/x-ndjson');
|
118
120
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
119
|
-
res.set(stormClient_1.ConversationIdHeader,
|
120
|
-
const
|
121
|
-
|
122
|
-
onRequestAborted(req, res, () => {
|
123
|
-
queue.cancel();
|
124
|
-
});
|
125
|
-
const systemId = userJourneysStream.getConversationId();
|
126
|
-
UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
|
127
|
-
await UI_SERVERS[systemId].start();
|
128
|
-
userJourneysStream.on('data', async (data) => {
|
121
|
+
res.set(stormClient_1.ConversationIdHeader, outerConversationId);
|
122
|
+
const uniqueUserJourneyScreens = {};
|
123
|
+
userJourneysStream.on('data', (data) => {
|
129
124
|
try {
|
130
|
-
console.log('Processing user journey event', data);
|
131
125
|
sendEvent(res, data);
|
132
126
|
if (data.type !== 'USER_JOURNEY') {
|
133
127
|
return;
|
@@ -136,48 +130,109 @@ router.post('/:handle/ui', async (req, res) => {
|
|
136
130
|
return;
|
137
131
|
}
|
138
132
|
data.payload.screens.forEach((screen) => {
|
139
|
-
if (screen.name
|
140
|
-
|
133
|
+
if (!uniqueUserJourneyScreens[screen.name]) {
|
134
|
+
uniqueUserJourneyScreens[screen.name] = screen;
|
141
135
|
}
|
142
|
-
promises[screen.name] = queue.add(() => new Promise(async (resolve, reject) => {
|
143
|
-
try {
|
144
|
-
const innerConversationId = node_uuid_1.default.v4();
|
145
|
-
const screenStream = await stormClient_1.stormClient.createUIPage({
|
146
|
-
prompt: screen.requirements,
|
147
|
-
method: screen.method,
|
148
|
-
path: screen.path,
|
149
|
-
description: screen.requirements,
|
150
|
-
name: screen.name,
|
151
|
-
title: screen.title,
|
152
|
-
filename: screen.filename,
|
153
|
-
storage_prefix: userJourneysStream.getConversationId() + '_',
|
154
|
-
}, innerConversationId);
|
155
|
-
const promises = [];
|
156
|
-
screenStream.on('data', (screenData) => {
|
157
|
-
if (screenData.type === 'PAGE') {
|
158
|
-
screenData.payload.conversationId = innerConversationId;
|
159
|
-
promises.push(sendPageEvent(userJourneysStream.getConversationId(), screenData, res));
|
160
|
-
}
|
161
|
-
else {
|
162
|
-
sendEvent(res, screenData);
|
163
|
-
}
|
164
|
-
});
|
165
|
-
screenStream.on('end', () => {
|
166
|
-
Promise.allSettled(promises).finally(resolve);
|
167
|
-
});
|
168
|
-
}
|
169
|
-
catch (e) {
|
170
|
-
console.error('Failed to process screen', e);
|
171
|
-
reject(e);
|
172
|
-
}
|
173
|
-
}));
|
174
136
|
});
|
175
137
|
}
|
176
138
|
catch (e) {
|
177
139
|
console.error('Failed to process event', e);
|
178
140
|
}
|
179
141
|
});
|
142
|
+
userJourneysStream.on('error', (error) => {
|
143
|
+
console.error('Error on userJourneysStream', error);
|
144
|
+
userJourneysStream.abort();
|
145
|
+
sendError(error, res);
|
146
|
+
});
|
180
147
|
await waitForStormStream(userJourneysStream);
|
148
|
+
// Get the UI shells
|
149
|
+
const shellsStream = await stormClient_1.stormClient.createUIShells({
|
150
|
+
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
151
|
+
name: screen.name,
|
152
|
+
title: screen.title,
|
153
|
+
filename: screen.filename,
|
154
|
+
path: screen.path,
|
155
|
+
method: screen.method,
|
156
|
+
requirements: screen.requirements,
|
157
|
+
})),
|
158
|
+
}, conversationId);
|
159
|
+
onRequestAborted(req, res, () => {
|
160
|
+
shellsStream.abort();
|
161
|
+
});
|
162
|
+
const uiShells = [];
|
163
|
+
shellsStream.on('data', (data) => {
|
164
|
+
console.log('Processing shell event', data);
|
165
|
+
sendEvent(res, data);
|
166
|
+
if (data.type !== 'UI_SHELL') {
|
167
|
+
return;
|
168
|
+
}
|
169
|
+
if (shellsStream.isAborted()) {
|
170
|
+
return;
|
171
|
+
}
|
172
|
+
uiShells.push(data.payload);
|
173
|
+
});
|
174
|
+
shellsStream.on('error', (error) => {
|
175
|
+
console.error('Error on shellsStream', error);
|
176
|
+
shellsStream.abort();
|
177
|
+
sendError(error, res);
|
178
|
+
});
|
179
|
+
await waitForStormStream(shellsStream);
|
180
|
+
UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
|
181
|
+
await UI_SERVERS[outerConversationId].start();
|
182
|
+
// Get the pages (5 at a time)
|
183
|
+
const queue = new PromiseQueue_1.PromiseQueue(5);
|
184
|
+
onRequestAborted(req, res, () => {
|
185
|
+
queue.cancel();
|
186
|
+
});
|
187
|
+
for (const screen of Object.values(uniqueUserJourneyScreens)) {
|
188
|
+
await queue.add(() => new Promise(async (resolve, reject) => {
|
189
|
+
try {
|
190
|
+
const innerConversationId = node_uuid_1.default.v4();
|
191
|
+
const screenStream = await stormClient_1.stormClient.createUIPage({
|
192
|
+
prompt: screen.requirements,
|
193
|
+
method: screen.method,
|
194
|
+
path: screen.path,
|
195
|
+
description: screen.requirements,
|
196
|
+
name: screen.name,
|
197
|
+
title: screen.title,
|
198
|
+
filename: screen.filename,
|
199
|
+
storage_prefix: outerConversationId + '_',
|
200
|
+
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
201
|
+
}, innerConversationId);
|
202
|
+
const promiseList = [];
|
203
|
+
screenStream.on('data', (screenData) => {
|
204
|
+
if (screenData.type === 'PAGE') {
|
205
|
+
promiseList.push(sendPageEvent(outerConversationId, {
|
206
|
+
...screenData,
|
207
|
+
payload: {
|
208
|
+
...screenData.payload,
|
209
|
+
conversationId: innerConversationId,
|
210
|
+
},
|
211
|
+
}, res));
|
212
|
+
}
|
213
|
+
else {
|
214
|
+
sendEvent(res, screenData);
|
215
|
+
}
|
216
|
+
});
|
217
|
+
screenStream.on('end', async () => {
|
218
|
+
try {
|
219
|
+
await Promise.allSettled(promiseList).finally(() => resolve(true));
|
220
|
+
}
|
221
|
+
catch (error) {
|
222
|
+
console.error('Failed to process screen', error);
|
223
|
+
}
|
224
|
+
});
|
225
|
+
screenStream.on('error', (error) => {
|
226
|
+
console.error('Error on screenStream', error);
|
227
|
+
screenStream.abort();
|
228
|
+
});
|
229
|
+
}
|
230
|
+
catch (e) {
|
231
|
+
console.error('Failed to process screen', e);
|
232
|
+
reject(e);
|
233
|
+
}
|
234
|
+
}));
|
235
|
+
}
|
181
236
|
await queue.wait();
|
182
237
|
if (userJourneysStream.isAborted()) {
|
183
238
|
return;
|
@@ -2,6 +2,16 @@ import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIIm
|
|
2
2
|
import { Page, StormEventPageUrl } from './events';
|
3
3
|
export declare const STORM_ID = "storm";
|
4
4
|
export declare const ConversationIdHeader = "Conversation-Id";
|
5
|
+
export interface UIShellsPrompt {
|
6
|
+
pages: {
|
7
|
+
name: string;
|
8
|
+
title: string;
|
9
|
+
filename: string;
|
10
|
+
path: string;
|
11
|
+
method: string;
|
12
|
+
requirements: string;
|
13
|
+
}[];
|
14
|
+
}
|
5
15
|
export interface UIPagePrompt {
|
6
16
|
name: string;
|
7
17
|
title: string;
|
@@ -11,6 +21,7 @@ export interface UIPagePrompt {
|
|
11
21
|
method: string;
|
12
22
|
description: string;
|
13
23
|
storage_prefix: string;
|
24
|
+
shell_page?: string;
|
14
25
|
}
|
15
26
|
export interface UIPageEditPrompt {
|
16
27
|
planDescription: string;
|
@@ -32,6 +43,7 @@ declare class StormClient {
|
|
32
43
|
createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
|
33
44
|
createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
|
34
45
|
createUIUserJourneys(prompt: string, conversationId?: string): Promise<StormStream>;
|
46
|
+
createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
|
35
47
|
createUIPage(prompt: UIPagePrompt, conversationId?: string): Promise<StormStream>;
|
36
48
|
editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
|
37
49
|
listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
|
@@ -92,6 +92,12 @@ class StormClient {
|
|
92
92
|
conversationId,
|
93
93
|
});
|
94
94
|
}
|
95
|
+
createUIShells(prompt, conversationId) {
|
96
|
+
return this.send('/v2/ui/shells', {
|
97
|
+
prompt: JSON.stringify(prompt),
|
98
|
+
conversationId,
|
99
|
+
});
|
100
|
+
}
|
95
101
|
createUIPage(prompt, conversationId) {
|
96
102
|
return this.send('/v2/ui/page', {
|
97
103
|
prompt: prompt,
|
package/package.json
CHANGED
package/src/storm/events.ts
CHANGED
@@ -332,6 +332,7 @@ export interface Page {
|
|
332
332
|
method: string;
|
333
333
|
conversationId: string;
|
334
334
|
prompt: string;
|
335
|
+
shellPage?: string;
|
335
336
|
}
|
336
337
|
|
337
338
|
// Event for creating a page
|
@@ -384,6 +385,27 @@ export interface StormEventUserJourney {
|
|
384
385
|
payload: UserJourney;
|
385
386
|
}
|
386
387
|
|
388
|
+
export interface UIShell {
|
389
|
+
name: string;
|
390
|
+
content: string;
|
391
|
+
screens: string[];
|
392
|
+
}
|
393
|
+
|
394
|
+
export interface StormEventUIShell {
|
395
|
+
type: 'UI_SHELL';
|
396
|
+
reason: string;
|
397
|
+
created: number;
|
398
|
+
payload: UIShell;
|
399
|
+
}
|
400
|
+
|
401
|
+
export interface StormEventPromptImprove {
|
402
|
+
type: 'PROMPT_IMPROVE';
|
403
|
+
reason: string;
|
404
|
+
payload: {
|
405
|
+
prompt: string;
|
406
|
+
};
|
407
|
+
}
|
408
|
+
|
387
409
|
export type StormEvent =
|
388
410
|
| StormEventCreateBlock
|
389
411
|
| StormEventCreateConnection
|
@@ -410,5 +432,7 @@ export type StormEvent =
|
|
410
432
|
| StormEventBlockStatus
|
411
433
|
| StormEventCreateDSLRetry
|
412
434
|
| StormEventUserJourney
|
435
|
+
| StormEventUIShell
|
413
436
|
| StormEventPage
|
414
|
-
| StormEventPageUrl
|
437
|
+
| StormEventPageUrl
|
438
|
+
| StormEventPromptImprove;
|
package/src/storm/routes.ts
CHANGED
@@ -13,7 +13,7 @@ import { stringBody } from '../middleware/stringBody';
|
|
13
13
|
import { KapetaBodyRequest } from '../types';
|
14
14
|
import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
|
15
15
|
import { ConversationIdHeader, stormClient, UIPagePrompt, UIPageEditPrompt, UIPageEditRequest } from './stormClient';
|
16
|
-
import { Page, StormEvent, StormEventPage, StormEventPhaseType } from './events';
|
16
|
+
import { Page, StormEvent, StormEventPage, StormEventPhaseType, UIShell, UserJourneyScreen } from './events';
|
17
17
|
import {
|
18
18
|
createPhaseEndEvent,
|
19
19
|
createPhaseStartEvent,
|
@@ -132,7 +132,9 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
132
132
|
|
133
133
|
const aiRequest: StormContextRequest = JSON.parse(req.stringBody ?? '{}');
|
134
134
|
|
135
|
+
// Get user journeys
|
135
136
|
const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
|
137
|
+
const outerConversationId = userJourneysStream.getConversationId();
|
136
138
|
|
137
139
|
onRequestAborted(req, res, () => {
|
138
140
|
userJourneysStream.abort();
|
@@ -140,23 +142,12 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
140
142
|
|
141
143
|
res.set('Content-Type', 'application/x-ndjson');
|
142
144
|
res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
143
|
-
res.set(ConversationIdHeader,
|
145
|
+
res.set(ConversationIdHeader, outerConversationId);
|
144
146
|
|
145
|
-
const
|
147
|
+
const uniqueUserJourneyScreens: Record<string, UserJourneyScreen> = {};
|
146
148
|
|
147
|
-
|
148
|
-
onRequestAborted(req, res, () => {
|
149
|
-
queue.cancel();
|
150
|
-
});
|
151
|
-
|
152
|
-
const systemId = userJourneysStream.getConversationId();
|
153
|
-
|
154
|
-
UI_SERVERS[systemId] = new UIServer(systemId);
|
155
|
-
await UI_SERVERS[systemId].start();
|
156
|
-
|
157
|
-
userJourneysStream.on('data', async (data: StormEvent) => {
|
149
|
+
userJourneysStream.on('data', (data: StormEvent) => {
|
158
150
|
try {
|
159
|
-
console.log('Processing user journey event', data);
|
160
151
|
sendEvent(res, data);
|
161
152
|
if (data.type !== 'USER_JOURNEY') {
|
162
153
|
return;
|
@@ -167,54 +158,139 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
167
158
|
}
|
168
159
|
|
169
160
|
data.payload.screens.forEach((screen) => {
|
170
|
-
if (screen.name
|
171
|
-
|
161
|
+
if (!uniqueUserJourneyScreens[screen.name]) {
|
162
|
+
uniqueUserJourneyScreens[screen.name] = screen;
|
172
163
|
}
|
173
|
-
promises[screen.name] = queue.add(
|
174
|
-
() =>
|
175
|
-
new Promise(async (resolve, reject) => {
|
176
|
-
try {
|
177
|
-
const innerConversationId = uuid.v4();
|
178
|
-
const screenStream = await stormClient.createUIPage(
|
179
|
-
{
|
180
|
-
prompt: screen.requirements,
|
181
|
-
method: screen.method,
|
182
|
-
path: screen.path,
|
183
|
-
description: screen.requirements,
|
184
|
-
name: screen.name,
|
185
|
-
title: screen.title,
|
186
|
-
filename: screen.filename,
|
187
|
-
storage_prefix: userJourneysStream.getConversationId() + '_',
|
188
|
-
},
|
189
|
-
innerConversationId
|
190
|
-
);
|
191
|
-
const promises: Promise<void>[] = [];
|
192
|
-
screenStream.on('data', (screenData: StormEvent) => {
|
193
|
-
if (screenData.type === 'PAGE') {
|
194
|
-
screenData.payload.conversationId = innerConversationId;
|
195
|
-
promises.push(
|
196
|
-
sendPageEvent(userJourneysStream.getConversationId(), screenData, res)
|
197
|
-
);
|
198
|
-
} else {
|
199
|
-
sendEvent(res, screenData);
|
200
|
-
}
|
201
|
-
});
|
202
|
-
screenStream.on('end', () => {
|
203
|
-
Promise.allSettled(promises).finally(resolve);
|
204
|
-
});
|
205
|
-
} catch (e: any) {
|
206
|
-
console.error('Failed to process screen', e);
|
207
|
-
reject(e);
|
208
|
-
}
|
209
|
-
})
|
210
|
-
);
|
211
164
|
});
|
212
165
|
} catch (e) {
|
213
166
|
console.error('Failed to process event', e);
|
214
167
|
}
|
215
168
|
});
|
216
169
|
|
170
|
+
userJourneysStream.on('error', (error) => {
|
171
|
+
console.error('Error on userJourneysStream', error);
|
172
|
+
userJourneysStream.abort();
|
173
|
+
sendError(error, res);
|
174
|
+
});
|
175
|
+
|
217
176
|
await waitForStormStream(userJourneysStream);
|
177
|
+
|
178
|
+
// Get the UI shells
|
179
|
+
const shellsStream = await stormClient.createUIShells(
|
180
|
+
{
|
181
|
+
pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
|
182
|
+
name: screen.name,
|
183
|
+
title: screen.title,
|
184
|
+
filename: screen.filename,
|
185
|
+
path: screen.path,
|
186
|
+
method: screen.method,
|
187
|
+
requirements: screen.requirements,
|
188
|
+
})),
|
189
|
+
},
|
190
|
+
conversationId
|
191
|
+
);
|
192
|
+
|
193
|
+
onRequestAborted(req, res, () => {
|
194
|
+
shellsStream.abort();
|
195
|
+
});
|
196
|
+
|
197
|
+
const uiShells: UIShell[] = [];
|
198
|
+
|
199
|
+
shellsStream.on('data', (data: StormEvent) => {
|
200
|
+
console.log('Processing shell event', data);
|
201
|
+
sendEvent(res, data);
|
202
|
+
|
203
|
+
if (data.type !== 'UI_SHELL') {
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
|
207
|
+
if (shellsStream.isAborted()) {
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
|
211
|
+
uiShells.push(data.payload);
|
212
|
+
});
|
213
|
+
|
214
|
+
shellsStream.on('error', (error) => {
|
215
|
+
console.error('Error on shellsStream', error);
|
216
|
+
shellsStream.abort();
|
217
|
+
sendError(error, res);
|
218
|
+
});
|
219
|
+
|
220
|
+
await waitForStormStream(shellsStream);
|
221
|
+
|
222
|
+
UI_SERVERS[outerConversationId] = new UIServer(outerConversationId);
|
223
|
+
await UI_SERVERS[outerConversationId].start();
|
224
|
+
|
225
|
+
// Get the pages (5 at a time)
|
226
|
+
const queue = new PromiseQueue(5);
|
227
|
+
|
228
|
+
onRequestAborted(req, res, () => {
|
229
|
+
queue.cancel();
|
230
|
+
});
|
231
|
+
|
232
|
+
for (const screen of Object.values(uniqueUserJourneyScreens)) {
|
233
|
+
await queue.add(
|
234
|
+
() =>
|
235
|
+
new Promise(async (resolve, reject) => {
|
236
|
+
try {
|
237
|
+
const innerConversationId = uuid.v4();
|
238
|
+
const screenStream = await stormClient.createUIPage(
|
239
|
+
{
|
240
|
+
prompt: screen.requirements,
|
241
|
+
method: screen.method,
|
242
|
+
path: screen.path,
|
243
|
+
description: screen.requirements,
|
244
|
+
name: screen.name,
|
245
|
+
title: screen.title,
|
246
|
+
filename: screen.filename,
|
247
|
+
storage_prefix: outerConversationId + '_',
|
248
|
+
shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
|
249
|
+
},
|
250
|
+
innerConversationId
|
251
|
+
);
|
252
|
+
|
253
|
+
const promiseList: Promise<void>[] = [];
|
254
|
+
screenStream.on('data', (screenData: StormEvent) => {
|
255
|
+
if (screenData.type === 'PAGE') {
|
256
|
+
promiseList.push(
|
257
|
+
sendPageEvent(
|
258
|
+
outerConversationId,
|
259
|
+
{
|
260
|
+
...screenData,
|
261
|
+
payload: {
|
262
|
+
...screenData.payload,
|
263
|
+
conversationId: innerConversationId,
|
264
|
+
},
|
265
|
+
},
|
266
|
+
res
|
267
|
+
)
|
268
|
+
);
|
269
|
+
} else {
|
270
|
+
sendEvent(res, screenData);
|
271
|
+
}
|
272
|
+
});
|
273
|
+
|
274
|
+
screenStream.on('end', async () => {
|
275
|
+
try {
|
276
|
+
await Promise.allSettled(promiseList).finally(() => resolve(true));
|
277
|
+
} catch (error) {
|
278
|
+
console.error('Failed to process screen', error);
|
279
|
+
}
|
280
|
+
});
|
281
|
+
|
282
|
+
screenStream.on('error', (error) => {
|
283
|
+
console.error('Error on screenStream', error);
|
284
|
+
screenStream.abort();
|
285
|
+
});
|
286
|
+
} catch (e) {
|
287
|
+
console.error('Failed to process screen', e);
|
288
|
+
reject(e);
|
289
|
+
}
|
290
|
+
})
|
291
|
+
);
|
292
|
+
}
|
293
|
+
|
218
294
|
await queue.wait();
|
219
295
|
|
220
296
|
if (userJourneysStream.isAborted()) {
|
@@ -222,8 +298,8 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
222
298
|
}
|
223
299
|
|
224
300
|
sendDone(res);
|
225
|
-
} catch (err
|
226
|
-
sendError(err, res);
|
301
|
+
} catch (err) {
|
302
|
+
sendError(err as Error, res);
|
227
303
|
if (!res.closed) {
|
228
304
|
res.end();
|
229
305
|
}
|
package/src/storm/stormClient.ts
CHANGED
@@ -21,6 +21,17 @@ export const STORM_ID = 'storm';
|
|
21
21
|
|
22
22
|
export const ConversationIdHeader = 'Conversation-Id';
|
23
23
|
|
24
|
+
export interface UIShellsPrompt {
|
25
|
+
pages: {
|
26
|
+
name: string;
|
27
|
+
title: string;
|
28
|
+
filename: string;
|
29
|
+
path: string;
|
30
|
+
method: string;
|
31
|
+
requirements: string;
|
32
|
+
}[];
|
33
|
+
}
|
34
|
+
|
24
35
|
export interface UIPagePrompt {
|
25
36
|
name: string;
|
26
37
|
title: string;
|
@@ -30,6 +41,7 @@ export interface UIPagePrompt {
|
|
30
41
|
method: string;
|
31
42
|
description: string;
|
32
43
|
storage_prefix: string;
|
44
|
+
shell_page?: string;
|
33
45
|
}
|
34
46
|
|
35
47
|
export interface UIPageEditPrompt {
|
@@ -153,6 +165,13 @@ class StormClient {
|
|
153
165
|
});
|
154
166
|
}
|
155
167
|
|
168
|
+
public createUIShells(prompt: UIShellsPrompt, conversationId?: string) {
|
169
|
+
return this.send('/v2/ui/shells', {
|
170
|
+
prompt: JSON.stringify(prompt),
|
171
|
+
conversationId,
|
172
|
+
});
|
173
|
+
}
|
174
|
+
|
156
175
|
public createUIPage(prompt: UIPagePrompt, conversationId?: string) {
|
157
176
|
return this.send('/v2/ui/page', {
|
158
177
|
prompt: prompt,
|