@kapeta/local-cluster-service 0.61.1 → 0.62.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.62.0](https://github.com/kapetacom/local-cluster-service/compare/v0.61.2...v0.62.0) (2024-08-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add iterative reference resolving ([#215](https://github.com/kapetacom/local-cluster-service/issues/215)) ([8bf5e23](https://github.com/kapetacom/local-cluster-service/commit/8bf5e23ce36031ca10df6c407a8a3e73a3e1ac46))
7
+
8
+ ## [0.61.2](https://github.com/kapetacom/local-cluster-service/compare/v0.61.1...v0.61.2) (2024-08-13)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * silence codegen internal errors for demo purposes ([3892ed3](https://github.com/kapetacom/local-cluster-service/commit/3892ed3817a73c5ac4564dcad05f50ffd86d221f))
14
+
1
15
  ## [0.61.1](https://github.com/kapetacom/local-cluster-service/compare/v0.61.0...v0.61.1) (2024-08-09)
2
16
 
3
17
 
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ /// <reference types="node" />
6
+ import { UIPagePrompt } from './stormClient';
7
+ import { ReferenceClassification, StormEvent, StormEventPage } from './events';
8
+ import { EventEmitter } from 'node:events';
9
+ export declare class PageQueue extends EventEmitter {
10
+ private readonly queue;
11
+ private readonly systemId;
12
+ private readonly references;
13
+ constructor(systemId: string, concurrency?: number);
14
+ on(event: 'event', listener: (data: StormEvent) => void): this;
15
+ on(event: 'page', listener: (data: StormEventPage) => void): this;
16
+ emit(type: 'event', event: StormEvent): boolean;
17
+ emit(type: 'page', event: StormEventPage): boolean;
18
+ addPrompt(initialPrompt: UIPagePrompt): Promise<void>;
19
+ private addPageGenerator;
20
+ cancel(): void;
21
+ wait(): Promise<void>;
22
+ }
23
+ export declare class PageGenerator extends EventEmitter {
24
+ private readonly conversationId;
25
+ private prompt;
26
+ constructor(prompt: UIPagePrompt, conversationId?: string);
27
+ on(event: 'event', listener: (data: StormEvent) => void): this;
28
+ on(event: 'page_refs', listener: (data: {
29
+ event: StormEventPage;
30
+ references: ReferenceClassification[];
31
+ }) => void): this;
32
+ emit(type: 'event', event: StormEvent): boolean;
33
+ emit(type: 'page_refs', event: {
34
+ event: StormEventPage;
35
+ references: ReferenceClassification[];
36
+ }): boolean;
37
+ generate(): Promise<void>;
38
+ private resolveReferences;
39
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2023 Kapeta Inc.
4
+ * SPDX-License-Identifier: BUSL-1.1
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.PageGenerator = exports.PageQueue = void 0;
11
+ const node_uuid_1 = __importDefault(require("node-uuid"));
12
+ const stormClient_1 = require("./stormClient");
13
+ const node_events_1 = require("node:events");
14
+ const PromiseQueue_1 = require("./PromiseQueue");
15
+ class PageQueue extends node_events_1.EventEmitter {
16
+ queue;
17
+ systemId;
18
+ references = new Map();
19
+ constructor(systemId, concurrency = 5) {
20
+ super();
21
+ this.systemId = systemId;
22
+ this.queue = new PromiseQueue_1.PromiseQueue(concurrency);
23
+ }
24
+ on(event, listener) {
25
+ return super.on(event, listener);
26
+ }
27
+ emit(eventName, ...args) {
28
+ return super.emit(eventName, ...args);
29
+ }
30
+ addPrompt(initialPrompt) {
31
+ if (this.references.has(initialPrompt.path)) {
32
+ console.log('Ignoring duplicate prompt', initialPrompt.path);
33
+ return Promise.resolve();
34
+ }
35
+ console.log('processing prompt', initialPrompt.path);
36
+ const generator = new PageGenerator(initialPrompt);
37
+ this.references.set(initialPrompt.path, generator);
38
+ return this.addPageGenerator(generator);
39
+ }
40
+ async addPageGenerator(generator) {
41
+ generator.on('event', (event) => this.emit('event', event));
42
+ generator.on('page_refs', ({ event, references }) => {
43
+ this.emit('page', event);
44
+ references.forEach((reference) => {
45
+ if (reference.url.startsWith('#') ||
46
+ reference.url.startsWith('javascript:') ||
47
+ reference.url.startsWith('http://') ||
48
+ reference.url.startsWith('https://')) {
49
+ return;
50
+ }
51
+ switch (reference.type) {
52
+ case 'image':
53
+ console.log('Ignoring image reference', reference);
54
+ break;
55
+ case 'css':
56
+ case 'javascript':
57
+ //console.log('Ignoring reference', reference);
58
+ break;
59
+ case 'html':
60
+ console.log('Adding page generator for', reference);
61
+ const paths = Array.from(this.references.keys());
62
+ this.addPrompt({
63
+ name: reference.name,
64
+ title: reference.title,
65
+ path: reference.url,
66
+ method: 'GET',
67
+ storage_prefix: this.systemId + '_',
68
+ prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
69
+ `The page was referenced from this page: \`\`\`html\n${event.payload.content}\n\`\`\`\n` +
70
+ (paths.length > 0
71
+ ? `\nThese paths are already implemented:\n- ${paths.join('\n - ')}\n\n`
72
+ : ''),
73
+ description: reference.description,
74
+ filename: '',
75
+ });
76
+ break;
77
+ }
78
+ });
79
+ });
80
+ return this.queue.add(() => generator.generate());
81
+ }
82
+ cancel() {
83
+ this.queue.cancel();
84
+ }
85
+ wait() {
86
+ return this.queue.wait();
87
+ }
88
+ }
89
+ exports.PageQueue = PageQueue;
90
+ class PageGenerator extends node_events_1.EventEmitter {
91
+ conversationId;
92
+ prompt;
93
+ constructor(prompt, conversationId = node_uuid_1.default.v4()) {
94
+ super();
95
+ this.conversationId = conversationId;
96
+ this.prompt = prompt;
97
+ }
98
+ on(event, listener) {
99
+ return super.on(event, listener);
100
+ }
101
+ emit(eventName, ...args) {
102
+ return super.emit(eventName, ...args);
103
+ }
104
+ async generate() {
105
+ return new Promise(async (resolve) => {
106
+ const screenStream = await stormClient_1.stormClient.createUIPage(this.prompt, this.conversationId);
107
+ const promises = [];
108
+ screenStream.on('data', (event) => {
109
+ if (event.type === 'PAGE') {
110
+ event.payload.conversationId = this.conversationId;
111
+ promises.push((async () => {
112
+ const references = await this.resolveReferences(event.payload.content);
113
+ //console.log('Resolved references for page', references, event.payload);
114
+ this.emit('page_refs', {
115
+ event,
116
+ references,
117
+ });
118
+ })());
119
+ return;
120
+ }
121
+ this.emit('event', event);
122
+ });
123
+ screenStream.on('end', () => {
124
+ Promise.allSettled(promises).finally(resolve);
125
+ });
126
+ await screenStream.waitForDone();
127
+ });
128
+ }
129
+ async resolveReferences(content) {
130
+ const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
131
+ const references = [];
132
+ referenceStream.on('data', (referenceData) => {
133
+ if (referenceData.type !== 'REF_CLASSIFICATION') {
134
+ return;
135
+ }
136
+ //console.log('Processing reference classification', referenceData);
137
+ references.push(referenceData.payload);
138
+ });
139
+ await referenceStream.waitForDone();
140
+ return references;
141
+ }
142
+ }
143
+ exports.PageGenerator = PageGenerator;
@@ -342,5 +342,34 @@ export interface StormEventPromptImprove {
342
342
  prompt: string;
343
343
  };
344
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;
345
+ export interface LandingPage {
346
+ name: string;
347
+ title: string;
348
+ filename: string;
349
+ create_prompt: string;
350
+ path: string;
351
+ archetype: string;
352
+ requires_authentication: boolean;
353
+ }
354
+ export interface StormEventLandingPage {
355
+ type: 'LANDING_PAGE';
356
+ reason: string;
357
+ created: number;
358
+ payload: LandingPage;
359
+ }
360
+ export interface ReferenceClassification {
361
+ name: string;
362
+ title: string;
363
+ url: string;
364
+ description: string;
365
+ type: 'image' | 'css' | 'javascript' | 'html';
366
+ source: 'local' | 'cdn' | 'example';
367
+ }
368
+ export interface StormEventReferenceClassification {
369
+ type: 'REF_CLASSIFICATION';
370
+ reason: string;
371
+ created: number;
372
+ payload: ReferenceClassification;
373
+ }
374
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification;
346
375
  export {};
@@ -4,9 +4,19 @@
4
4
  */
5
5
  import { StormEventPage } from './events';
6
6
  import { Response } from 'express';
7
+ import { ConversationItem } from './stream';
7
8
  export declare const SystemIdHeader = "System-Id";
8
9
  export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
9
10
  path: string;
10
11
  }>;
12
+ export declare function resolveReadPath(systemId: string, path: string, method: string): string | null;
11
13
  export declare function readPageFromDiskAsString(systemId: string, path: string, method: string): string | null;
12
14
  export declare function readPageFromDisk(systemId: string, path: string, method: string, res: Response): void;
15
+ export interface Conversation {
16
+ messages: ConversationItem[];
17
+ variantId: string;
18
+ type: 'page';
19
+ filename: string;
20
+ }
21
+ export declare function readConversationFromFile(filename: string): Conversation[];
22
+ export declare function writeConversationToFile(filename: string, conversations: Conversation[]): void;
@@ -3,13 +3,21 @@ 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.readPageFromDisk = exports.readPageFromDiskAsString = exports.writePageToDisk = exports.SystemIdHeader = void 0;
6
+ exports.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = 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"));
10
10
  exports.SystemIdHeader = 'System-Id';
11
+ function normalizePath(path) {
12
+ return path
13
+ .replace(/\?.*$/gi, '')
14
+ .replace(/:[a-z][a-z_]*\b/gi, '*')
15
+ .replace(/\{[a-z]+}/gi, '*');
16
+ }
11
17
  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');
18
+ const baseDir = getBaseDir(systemId);
19
+ const filePath = getFilePath(event.payload.method);
20
+ const path = path_1.default.join(baseDir, normalizePath(event.payload.path), filePath);
13
21
  await fs_extra_1.default.ensureDir(path_1.default.dirname(path));
14
22
  await fs_extra_1.default.writeFile(path, event.payload.content);
15
23
  console.log(`Page written to disk: ${event.payload.title} > ${path}`);
@@ -18,17 +26,53 @@ async function writePageToDisk(systemId, event) {
18
26
  };
19
27
  }
20
28
  exports.writePageToDisk = writePageToDisk;
29
+ function getBaseDir(systemId) {
30
+ return path_1.default.join(node_os_1.default.tmpdir(), 'ai-systems', systemId);
31
+ }
32
+ function getFilePath(method) {
33
+ return path_1.default.join(method.toLowerCase(), 'index.html');
34
+ }
35
+ function resolveReadPath(systemId, path, method) {
36
+ const baseDir = getBaseDir(systemId);
37
+ path = normalizePath(path);
38
+ const filePath = getFilePath(method);
39
+ const fullPath = path_1.default.join(baseDir, path, filePath);
40
+ if (fs_extra_1.default.existsSync(fullPath)) {
41
+ return fullPath;
42
+ }
43
+ const parts = path.split(/\*/g);
44
+ let currentPath = '';
45
+ for (let part in parts) {
46
+ const thisPath = path_1.default.join(currentPath, part);
47
+ const starPath = path_1.default.join(currentPath, '*');
48
+ const thisPathDir = path_1.default.join(baseDir, thisPath);
49
+ const starPathDir = path_1.default.join(baseDir, starPath);
50
+ if (fs_extra_1.default.existsSync(thisPathDir)) {
51
+ currentPath = thisPath;
52
+ continue;
53
+ }
54
+ if (fs_extra_1.default.existsSync(starPathDir)) {
55
+ currentPath = starPath;
56
+ continue;
57
+ }
58
+ console.log('Path not found', thisPathDir, starPathDir);
59
+ // Path not found
60
+ return null;
61
+ }
62
+ return path_1.default.join(baseDir, currentPath, filePath);
63
+ }
64
+ exports.resolveReadPath = resolveReadPath;
21
65
  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)) {
66
+ const filePath = resolveReadPath(systemId, path, method);
67
+ if (!filePath || !fs_extra_1.default.existsSync(filePath)) {
24
68
  return null;
25
69
  }
26
70
  return fs_extra_1.default.readFileSync(filePath, 'utf8');
27
71
  }
28
72
  exports.readPageFromDiskAsString = readPageFromDiskAsString;
29
73
  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)) {
74
+ const filePath = resolveReadPath(systemId, path, method);
75
+ if (!filePath || !fs_extra_1.default.existsSync(filePath)) {
32
76
  res.status(404).send('Page not found');
33
77
  return;
34
78
  }
@@ -38,3 +82,18 @@ function readPageFromDisk(systemId, path, method, res) {
38
82
  res.end();
39
83
  }
40
84
  exports.readPageFromDisk = readPageFromDisk;
85
+ function readConversationFromFile(filename) {
86
+ if (!fs_extra_1.default.existsSync(filename)) {
87
+ return [];
88
+ }
89
+ const content = fs_extra_1.default.readFileSync(filename).toString();
90
+ if (!content.trim()) {
91
+ return [];
92
+ }
93
+ return content.split(/\n/g).map((line) => JSON.parse(line));
94
+ }
95
+ exports.readConversationFromFile = readConversationFromFile;
96
+ function writeConversationToFile(filename, conversations) {
97
+ fs_extra_1.default.writeFileSync(filename, conversations.map((conversation) => JSON.stringify(conversation)).join('\n'));
98
+ }
99
+ exports.writeConversationToFile = writeConversationToFile;
@@ -19,13 +19,14 @@ const event_parser_1 = require("./event-parser");
19
19
  const codegen_1 = require("./codegen");
20
20
  const assetManager_1 = require("../assetManager");
21
21
  const node_uuid_1 = __importDefault(require("node-uuid"));
22
- const PromiseQueue_1 = require("./PromiseQueue");
23
22
  const page_utils_1 = require("./page-utils");
24
23
  const UIServer_1 = require("./UIServer");
24
+ const PageGenerator_1 = require("./PageGenerator");
25
25
  const UI_SERVERS = {};
26
26
  const router = (0, express_promise_router_1.default)();
27
27
  router.use('/', cors_1.corsHandler);
28
28
  router.use('/', stringBody_1.stringBody);
29
+ const samplesBaseDir = path_1.default.join(__dirname, 'samples');
29
30
  function convertPageEvent(screenData, innerConversationId, mainConversationId) {
30
31
  if (screenData.type === 'PAGE') {
31
32
  const server = UI_SERVERS[mainConversationId];
@@ -62,19 +63,20 @@ router.post('/ui/screen', async (req, res) => {
62
63
  const systemId = req.headers[page_utils_1.SystemIdHeader.toLowerCase()];
63
64
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
64
65
  aiRequest.storage_prefix = systemId ? systemId + '_' : 'mock_';
65
- const screenStream = await stormClient_1.stormClient.createUIPage(aiRequest, conversationId);
66
- onRequestAborted(req, res, () => {
67
- screenStream.abort();
68
- });
69
66
  res.set('Content-Type', 'application/x-ndjson');
70
67
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
71
- res.set(stormClient_1.ConversationIdHeader, screenStream.getConversationId());
68
+ res.set(stormClient_1.ConversationIdHeader, conversationId);
69
+ const parentConversationId = systemId ?? '';
70
+ const queue = new PageGenerator_1.PageQueue(parentConversationId, 5);
71
+ onRequestAborted(req, res, () => {
72
+ queue.cancel();
73
+ });
74
+ await queue.addPrompt(aiRequest);
72
75
  const promises = [];
73
- screenStream.on('data', (data) => {
76
+ queue.on('page', (data) => {
74
77
  switch (data.type) {
75
78
  case 'PAGE':
76
79
  console.log('Processing page event', data);
77
- data.payload.conversationId = screenStream.getConversationId();
78
80
  if (systemId) {
79
81
  promises.push(sendPageEvent(systemId, data, res));
80
82
  }
@@ -82,7 +84,7 @@ router.post('/ui/screen', async (req, res) => {
82
84
  }
83
85
  sendEvent(res, data);
84
86
  });
85
- await waitForStormStream(screenStream);
87
+ await queue.wait();
86
88
  await Promise.allSettled(promises);
87
89
  sendDone(res);
88
90
  }
@@ -105,6 +107,78 @@ router.delete('/:handle/ui', async (req, res) => {
105
107
  delete UI_SERVERS[conversationId];
106
108
  }
107
109
  });
110
+ router.post('/:handle/ui/iterative', async (req, res) => {
111
+ const handle = req.params.handle;
112
+ try {
113
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
114
+ const aiRequest = JSON.parse(req.stringBody ?? '{}');
115
+ const landingPagesStream = await stormClient_1.stormClient.createUILandingPages(aiRequest.prompt, conversationId);
116
+ onRequestAborted(req, res, () => {
117
+ landingPagesStream.abort();
118
+ });
119
+ res.set('Content-Type', 'application/x-ndjson');
120
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
121
+ res.set(stormClient_1.ConversationIdHeader, landingPagesStream.getConversationId());
122
+ const promises = {};
123
+ const pageEventPromises = [];
124
+ const systemId = landingPagesStream.getConversationId();
125
+ const pageQueue = new PageGenerator_1.PageQueue(systemId, 5);
126
+ landingPagesStream.on('data', async (data) => {
127
+ try {
128
+ sendEvent(res, data);
129
+ if (data.type !== 'LANDING_PAGE') {
130
+ return;
131
+ }
132
+ if (landingPagesStream.isAborted()) {
133
+ return;
134
+ }
135
+ const landingPage = data.payload;
136
+ if (landingPage.name in promises) {
137
+ return;
138
+ }
139
+ // We add the landing pages to the prompt queue.
140
+ // These will then be analysed - creating further pages as needed
141
+ promises[landingPage.name] = pageQueue.addPrompt({
142
+ prompt: landingPage.create_prompt,
143
+ method: 'GET',
144
+ path: landingPage.path,
145
+ description: landingPage.create_prompt,
146
+ name: landingPage.name,
147
+ title: landingPage.title,
148
+ filename: landingPage.filename,
149
+ storage_prefix: systemId + '_',
150
+ });
151
+ }
152
+ catch (e) {
153
+ console.error('Failed to process event', e);
154
+ }
155
+ });
156
+ UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
157
+ await UI_SERVERS[systemId].start();
158
+ onRequestAborted(req, res, () => {
159
+ pageQueue.cancel();
160
+ });
161
+ pageQueue.on('page', (screenData) => {
162
+ pageEventPromises.push(sendPageEvent(landingPagesStream.getConversationId(), screenData, res));
163
+ });
164
+ pageQueue.on('event', (screenData) => {
165
+ sendEvent(res, screenData);
166
+ });
167
+ await waitForStormStream(landingPagesStream);
168
+ await pageQueue.wait();
169
+ await Promise.allSettled(pageEventPromises);
170
+ if (landingPagesStream.isAborted()) {
171
+ return;
172
+ }
173
+ sendDone(res);
174
+ }
175
+ catch (err) {
176
+ sendError(err, res);
177
+ if (!res.closed) {
178
+ res.end();
179
+ }
180
+ }
181
+ });
108
182
  router.post('/:handle/ui', async (req, res) => {
109
183
  const handle = req.params.handle;
110
184
  try {
@@ -180,60 +254,34 @@ router.post('/:handle/ui', async (req, res) => {
180
254
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
181
255
  await UI_SERVERS[outerConversationId].start();
182
256
  // Get the pages (5 at a time)
183
- const queue = new PromiseQueue_1.PromiseQueue(5);
257
+ const queue = new PageGenerator_1.PageQueue(outerConversationId, 5);
258
+ const pagePromises = [];
184
259
  onRequestAborted(req, res, () => {
185
260
  queue.cancel();
186
261
  });
262
+ const pageEventPromises = [];
263
+ queue.on('page', (screenData) => {
264
+ pageEventPromises.push(sendPageEvent(outerConversationId, screenData, res));
265
+ });
266
+ queue.on('event', (screenData) => {
267
+ sendEvent(res, screenData);
268
+ });
187
269
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
188
- void 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
- }
270
+ pagePromises.push(queue.addPrompt({
271
+ prompt: screen.requirements,
272
+ method: screen.method,
273
+ path: screen.path,
274
+ description: screen.requirements,
275
+ name: screen.name,
276
+ title: screen.title,
277
+ filename: screen.filename,
278
+ storage_prefix: outerConversationId + '_',
279
+ shell_page: uiShells.find((shell) => shell.screens.includes(screen.name))?.content,
234
280
  }));
235
281
  }
236
282
  await queue.wait();
283
+ await Promise.allSettled(pagePromises);
284
+ await Promise.allSettled(pageEventPromises);
237
285
  if (userJourneysStream.isAborted()) {
238
286
  return;
239
287
  }
@@ -484,6 +532,12 @@ function waitForStormStream(result) {
484
532
  function streamStormPartialResponse(result, res) {
485
533
  return new Promise((resolve) => {
486
534
  result.on('data', (data) => {
535
+ switch (data.type) {
536
+ // todo: temporarily (for demo purposes) disable error messages when codegen fails
537
+ case 'ERROR_INTERNAL':
538
+ console.log("Error internal", data);
539
+ return;
540
+ }
487
541
  sendEvent(res, data);
488
542
  });
489
543
  resolve(result.waitForDone());
@@ -23,6 +23,9 @@ export interface UIPagePrompt {
23
23
  storage_prefix: string;
24
24
  shell_page?: string;
25
25
  }
26
+ export interface UIPageSamplePrompt extends UIPagePrompt {
27
+ variantId: string;
28
+ }
26
29
  export interface UIPageEditPrompt {
27
30
  planDescription: string;
28
31
  blockDescription: string;
@@ -44,7 +47,9 @@ declare class StormClient {
44
47
  createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
45
48
  createUIUserJourneys(prompt: string, conversationId?: string): Promise<StormStream>;
46
49
  createUIShells(prompt: UIShellsPrompt, conversationId?: string): Promise<StormStream>;
47
- createUIPage(prompt: UIPagePrompt, conversationId?: string): Promise<StormStream>;
50
+ createUILandingPages(prompt: string, conversationId?: string): Promise<StormStream>;
51
+ createUIPage(prompt: UIPagePrompt, conversationId?: string, history?: ConversationItem[]): Promise<StormStream>;
52
+ classifyUIReferences(prompt: string, conversationId?: string): Promise<StormStream>;
48
53
  editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
49
54
  listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
50
55
  createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
@@ -53,6 +58,7 @@ declare class StormClient {
53
58
  createCodeFix(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
54
59
  createErrorDetails(prompt: string, history?: ConversationItem[], conversationId?: string): Promise<StormStream>;
55
60
  generateCode(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
61
+ deleteUIPageConversation(conversationId: string): Promise<string>;
56
62
  }
57
63
  export declare const stormClient: StormClient;
58
64
  export {};
@@ -95,13 +95,25 @@ class StormClient {
95
95
  createUIShells(prompt, conversationId) {
96
96
  return this.send('/v2/ui/shells', {
97
97
  prompt: JSON.stringify(prompt),
98
+ });
99
+ }
100
+ createUILandingPages(prompt, conversationId) {
101
+ return this.send('/v2/ui/landing-pages', {
102
+ prompt: prompt,
98
103
  conversationId,
99
104
  });
100
105
  }
101
- createUIPage(prompt, conversationId) {
106
+ createUIPage(prompt, conversationId, history) {
102
107
  return this.send('/v2/ui/page', {
103
108
  prompt: prompt,
104
109
  conversationId,
110
+ history,
111
+ });
112
+ }
113
+ classifyUIReferences(prompt, conversationId) {
114
+ return this.send('/v2/ui/references', {
115
+ prompt: prompt,
116
+ conversationId,
105
117
  });
106
118
  }
107
119
  editPages(prompt, conversationId) {
@@ -152,5 +164,16 @@ class StormClient {
152
164
  conversationId: conversationId,
153
165
  });
154
166
  }
167
+ async deleteUIPageConversation(conversationId) {
168
+ const options = await this.createOptions('/v2/ui/page', 'DELETE', {
169
+ prompt: '',
170
+ conversationId: conversationId,
171
+ });
172
+ const response = await fetch(options.url, {
173
+ method: options.method,
174
+ headers: options.headers,
175
+ });
176
+ return response.text();
177
+ }
155
178
  }
156
179
  exports.stormClient = new StormClient();
@@ -35,6 +35,7 @@ export interface ConversationItem {
35
35
  }
36
36
  export interface StormContextRequest<T = string> {
37
37
  conversationId?: string;
38
+ history?: ConversationItem[];
38
39
  prompt: T;
39
40
  }
40
41
  export interface StormCreateBlockRequest {