@kapeta/local-cluster-service 0.74.0 → 0.74.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,8 +117,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
117
117
  res.set(stormClient_1.ConversationIdHeader, systemId);
118
118
  sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
119
119
  const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
120
- const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
120
+ const client = new stormClient_1.StormClient(systemId);
121
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
121
122
  pages: pagesFromDisk,
123
+ systemId: systemId,
122
124
  });
123
125
  await (0, utils_1.copyDirectory)(srcDir, destDir, (fileName, content) => {
124
126
  // find the page from result1 and write the content to the file
@@ -131,7 +133,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
131
133
  const pageContents = pagesWithImplementation.map((page) => {
132
134
  return page.content;
133
135
  });
134
- const prompt = await stormClient_1.stormClient.generatePrompt(pageContents);
136
+ const prompt = await client.generatePrompt(pageContents);
135
137
  sendEvent(res, (0, event_parser_1.createPhaseEndEvent)(events_1.StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
136
138
  req.query.systemId = systemId;
137
139
  const promptRequest = {
@@ -149,10 +151,12 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
149
151
  //res.set('Access-Control-Expose-Headers', ConversationIdHeader);
150
152
  //res.set(ConversationIdHeader, systemId);
151
153
  //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
154
+ const client = new stormClient_1.StormClient(systemId);
152
155
  try {
153
156
  const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
154
- const pagesWithImplementation = await stormClient_1.stormClient.replaceMockWithAPICall({
157
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
155
158
  pages: pagesFromDisk,
159
+ systemId: systemId,
156
160
  });
157
161
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
158
162
  //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.COMPOSE_SYSTEM));
@@ -165,7 +169,7 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
165
169
  }
166
170
  return page;
167
171
  });
168
- const systemUrl = await stormClient_1.stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
172
+ const systemUrl = await client.createSimpleBackend(handle, systemId, { pages: allFiles });
169
173
  //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
170
174
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
171
175
  //sendDone(res);
@@ -250,7 +254,8 @@ router.post('/:handle/ui/iterative', async (req, res) => {
250
254
  try {
251
255
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
252
256
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
253
- const landingPagesStream = await stormClient_1.stormClient.createUILandingPages(aiRequest, conversationId);
257
+ const client = new stormClient_1.StormClient(conversationId); //todo is this correct we are using the landing page getConversationId down below as well
258
+ const landingPagesStream = await client.createUILandingPages(aiRequest, conversationId);
254
259
  onRequestAborted(req, res, () => {
255
260
  landingPagesStream.abort();
256
261
  });
@@ -339,8 +344,9 @@ router.post('/:handle/ui', async (req, res) => {
339
344
  try {
340
345
  const outerConversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || (0, crypto_1.randomUUID)();
341
346
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
347
+ const stormClient = new stormClient_1.StormClient(outerConversationId);
342
348
  // Get user journeys
343
- const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest, outerConversationId);
349
+ const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest, outerConversationId);
344
350
  onRequestAborted(req, res, () => {
345
351
  userJourneysStream.abort();
346
352
  });
@@ -351,11 +357,11 @@ router.post('/:handle/ui', async (req, res) => {
351
357
  let systemPrompt = aiRequest.prompt;
352
358
  userJourneysStream.on('data', (data) => {
353
359
  try {
354
- sendEvent(res, data);
355
360
  if (data.type === 'PROMPT_IMPROVE') {
356
361
  systemPrompt = data.payload.prompt;
357
362
  }
358
363
  if (data.type !== 'USER_JOURNEY') {
364
+ sendEvent(res, data);
359
365
  return;
360
366
  }
361
367
  if (userJourneysStream.isAborted()) {
@@ -363,9 +369,11 @@ router.post('/:handle/ui', async (req, res) => {
363
369
  }
364
370
  data.payload.screens.forEach((screen) => {
365
371
  if (!uniqueUserJourneyScreens[screen.name]) {
372
+ screen.conversationId = (0, crypto_1.randomUUID)();
366
373
  uniqueUserJourneyScreens[screen.name] = screen;
367
374
  }
368
375
  });
376
+ sendEvent(res, data);
369
377
  }
370
378
  catch (e) {
371
379
  console.error('Failed to process event', e);
@@ -378,7 +386,7 @@ router.post('/:handle/ui', async (req, res) => {
378
386
  });
379
387
  let theme = '';
380
388
  try {
381
- const themeStream = await stormClient_1.stormClient.createTheme(aiRequest, outerConversationId);
389
+ const themeStream = await stormClient.createTheme(aiRequest, outerConversationId);
382
390
  onRequestAborted(req, res, () => {
383
391
  themeStream.abort();
384
392
  });
@@ -416,8 +424,11 @@ router.post('/:handle/ui', async (req, res) => {
416
424
  });
417
425
  }
418
426
  await waitForStormStream(userJourneysStream);
427
+ if (req.socket.closed) {
428
+ return;
429
+ }
419
430
  // Get the UI shells
420
- const shellsStream = await stormClient_1.stormClient.createUIShells({
431
+ const shellsStream = await stormClient.createUIShells({
421
432
  theme: theme || undefined,
422
433
  pages: Object.values(uniqueUserJourneyScreens).map((screen) => ({
423
434
  name: screen.name,
@@ -450,6 +461,9 @@ router.post('/:handle/ui', async (req, res) => {
450
461
  sendError(error, res);
451
462
  });
452
463
  await waitForStormStream(shellsStream);
464
+ if (req.socket.closed) {
465
+ return;
466
+ }
453
467
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
454
468
  await UI_SERVERS[outerConversationId].start();
455
469
  sendEvent(res, {
@@ -464,7 +478,7 @@ router.post('/:handle/ui', async (req, res) => {
464
478
  onRequestAborted(req, res, () => {
465
479
  queue.cancel();
466
480
  });
467
- queue.on('page', (screenData) => sendPageEvent(outerConversationId, screenData, res));
481
+ queue.on('page', (pageEvent) => sendPageEvent(outerConversationId, pageEvent, res));
468
482
  queue.on('event', (event) => {
469
483
  if (event.type === 'FILE_CHUNK') {
470
484
  return;
@@ -487,7 +501,7 @@ router.post('/:handle/ui', async (req, res) => {
487
501
  filename: screen.filename,
488
502
  storage_prefix: outerConversationId + '_',
489
503
  theme,
490
- })
504
+ }, screen.conversationId)
491
505
  .catch((e) => {
492
506
  console.error('Failed to generate page for screen %s', screen.name, e);
493
507
  sendError(e, res);
@@ -574,7 +588,8 @@ router.post('/ui/vote', async (req, res) => {
574
588
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
575
589
  const { topic, vote, mainConversationId } = aiRequest;
576
590
  try {
577
- await stormClient_1.stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
591
+ const stormClient = new stormClient_1.StormClient(mainConversationId);
592
+ await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
578
593
  }
579
594
  catch (e) {
580
595
  res.status(500).send({ error: e.message });
@@ -585,7 +600,8 @@ router.post('/ui/get-vote', async (req, res) => {
585
600
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
586
601
  const { topic, mainConversationId } = aiRequest;
587
602
  try {
588
- const vote = await stormClient_1.stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
603
+ const stormClient = new stormClient_1.StormClient(mainConversationId);
604
+ const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
589
605
  res.send({ vote });
590
606
  }
591
607
  catch (e) {
@@ -609,7 +625,8 @@ async function handleAll(req, res) {
609
625
  const eventParser = new event_parser_1.StormEventParser(stormOptions);
610
626
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
611
627
  const aiRequest = JSON.parse(req.stringBody ?? '{}');
612
- const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest, conversationId);
628
+ const stormClient = new stormClient_1.StormClient(systemId);
629
+ const metaStream = await stormClient.createMetadata(aiRequest, conversationId);
613
630
  onRequestAborted(req, res, () => {
614
631
  metaStream.abort();
615
632
  });
@@ -4,6 +4,7 @@ import { ConversationItem, CreateSimpleBackendRequest, HTMLPage, ImplementAPICli
4
4
  import { Page, StormEventPageUrl } from './events';
5
5
  export declare const STORM_ID = "storm";
6
6
  export declare const ConversationIdHeader = "Conversation-Id";
7
+ export declare const SystemIdHeader = "System-Id";
7
8
  export interface UIShellsPrompt {
8
9
  theme?: string;
9
10
  pages: {
@@ -55,9 +56,10 @@ export interface BasePromptRequest {
55
56
  prompt: string;
56
57
  skipImprovement?: boolean;
57
58
  }
58
- declare class StormClient {
59
+ export declare class StormClient {
59
60
  private readonly _baseUrl;
60
- constructor();
61
+ private readonly _systemId;
62
+ constructor(systemId?: string);
61
63
  private createOptions;
62
64
  private send;
63
65
  createMetadata(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
@@ -88,5 +90,3 @@ declare class StormClient {
88
90
  downloadSystem(handle: string, conversationId: string): Promise<Buffer>;
89
91
  uploadSystem(handle: string, conversationId: string, buffer: Buffer): Promise<Response>;
90
92
  }
91
- export declare const stormClient: StormClient;
92
- export {};
@@ -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.stormClient = exports.ConversationIdHeader = exports.STORM_ID = void 0;
6
+ exports.StormClient = exports.SystemIdHeader = exports.ConversationIdHeader = exports.STORM_ID = void 0;
7
7
  /**
8
8
  * Copyright 2023 Kapeta Inc.
9
9
  * SPDX-License-Identifier: BUSL-1.1
@@ -17,10 +17,13 @@ const fetch_retry_1 = __importDefault(require("fetch-retry"));
17
17
  const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5, retryDelay: 10 });
18
18
  exports.STORM_ID = 'storm';
19
19
  exports.ConversationIdHeader = 'Conversation-Id';
20
+ exports.SystemIdHeader = 'System-Id';
20
21
  class StormClient {
21
22
  _baseUrl;
22
- constructor() {
23
+ _systemId;
24
+ constructor(systemId) {
23
25
  this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
26
+ this._systemId = systemId || "";
24
27
  }
25
28
  async createOptions(path, method, body) {
26
29
  const url = `${this._baseUrl}${path}`;
@@ -35,6 +38,9 @@ class StormClient {
35
38
  if (body.conversationId) {
36
39
  headers[exports.ConversationIdHeader] = body.conversationId;
37
40
  }
41
+ if (this._systemId) {
42
+ headers[exports.SystemIdHeader] = this._systemId;
43
+ }
38
44
  return {
39
45
  url,
40
46
  method: method,
@@ -103,6 +109,7 @@ class StormClient {
103
109
  createUIShells(prompt, conversationId) {
104
110
  return this.send('/v2/ui/shells', {
105
111
  prompt: JSON.stringify(prompt),
112
+ conversationId: conversationId,
106
113
  });
107
114
  }
108
115
  createUILandingPages(prompt, conversationId) {
@@ -138,6 +145,9 @@ class StormClient {
138
145
  const response = await fetch(u, {
139
146
  method: 'POST',
140
147
  body: JSON.stringify(prompt.pages),
148
+ headers: {
149
+ 'systemId': prompt.systemId,
150
+ },
141
151
  });
142
152
  return (await response.json());
143
153
  }
@@ -252,4 +262,4 @@ class StormClient {
252
262
  return response;
253
263
  }
254
264
  }
255
- exports.stormClient = new StormClient();
265
+ exports.StormClient = StormClient;
@@ -35,6 +35,7 @@ export interface ConversationItem {
35
35
  }
36
36
  export interface StormContextRequest<T = string> {
37
37
  conversationId?: string;
38
+ systemId?: string;
38
39
  history?: ConversationItem[];
39
40
  prompt: T;
40
41
  }
@@ -78,6 +79,7 @@ export declare enum HTMLPageEncoding {
78
79
  }
79
80
  export interface ImplementAPIClients {
80
81
  pages: HTMLPage[];
82
+ systemId: string;
81
83
  }
82
84
  export interface HTMLPage {
83
85
  fileName: string;
@@ -13,8 +13,8 @@ export declare class StormService {
13
13
  saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
14
14
  appendConversation(conversationId: string, events: StormEvent[]): Promise<void>;
15
15
  deleteConversation(conversationId: string): Promise<void>;
16
- uploadConversation(handle: string, conversationId: string): Promise<void>;
17
- installProjectById(handle: string, conversationId: string): Promise<void>;
16
+ uploadConversation(handle: string, systemId: string): Promise<void>;
17
+ installProjectById(handle: string, systemId: string): Promise<void>;
18
18
  }
19
19
  declare const _default: StormService;
20
20
  export default _default;
@@ -119,8 +119,8 @@ class StormService {
119
119
  await promises_1.default.rm(conversationDir, { recursive: true, force: true });
120
120
  }
121
121
  }
122
- async uploadConversation(handle, conversationId) {
123
- const tarballFile = this.getConversationTarball(conversationId);
122
+ async uploadConversation(handle, systemId) {
123
+ const tarballFile = this.getConversationTarball(systemId);
124
124
  const destDir = path_1.default.dirname(tarballFile);
125
125
  const tarballName = path_1.default.basename(tarballFile);
126
126
  await tar.create({
@@ -129,12 +129,14 @@ class StormService {
129
129
  gzip: true,
130
130
  filter: (entry) => !entry.includes(tarballName),
131
131
  }, ['.']);
132
- await stormClient_1.stormClient.uploadSystem(handle, conversationId, await promises_1.default.readFile(tarballFile));
132
+ const stormClient = new stormClient_1.StormClient(systemId);
133
+ await stormClient.uploadSystem(handle, systemId, await promises_1.default.readFile(tarballFile));
133
134
  }
134
- async installProjectById(handle, conversationId) {
135
- const tarballFile = this.getConversationTarball(conversationId);
135
+ async installProjectById(handle, systemId) {
136
+ const tarballFile = this.getConversationTarball(systemId);
136
137
  const destDir = path_1.default.dirname(tarballFile);
137
- const buffer = await stormClient_1.stormClient.downloadSystem(handle, conversationId);
138
+ const stormClient = new stormClient_1.StormClient(systemId);
139
+ const buffer = await stormClient.downloadSystem(handle, systemId);
138
140
  await promises_1.default.mkdir(destDir, { recursive: true });
139
141
  await promises_1.default.writeFile(tarballFile, buffer);
140
142
  await tar.extract({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.74.0",
3
+ "version": "0.74.2",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -4,13 +4,14 @@
4
4
  */
5
5
 
6
6
  import uuid from 'node-uuid';
7
- import { stormClient, UIPagePrompt } from './stormClient';
7
+ import { StormClient, UIPagePrompt } from './stormClient';
8
8
  import { ReferenceClassification, StormEvent, StormEventPage, StormImage, UIShell } from './events';
9
9
  import { EventEmitter } from 'node:events';
10
10
  import PQueue from 'p-queue';
11
11
 
12
12
  import { hasPageOnDisk, normalizePath, writeImageToDisk } from './page-utils';
13
13
  import * as mimetypes from 'mime-types';
14
+ import { randomUUID } from 'node:crypto';
14
15
 
15
16
  export interface ImagePrompt {
16
17
  name: string;
@@ -22,7 +23,10 @@ export interface ImagePrompt {
22
23
  content: string;
23
24
  }
24
25
 
25
- type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & { shellType?: 'public' | 'admin' | 'user' };
26
+ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
27
+ shellType?: 'public' | 'admin' | 'user';
28
+ };
29
+ type PagePrompt = InitialPrompt & { conversationId: string; id: string };
26
30
 
27
31
  export class PageQueue extends EventEmitter {
28
32
  private readonly queue: PQueue;
@@ -149,7 +153,7 @@ export class PageQueue extends EventEmitter {
149
153
  new RegExp(`^${path.replaceAll('/*', '/[^/]+')}$`).test(url)
150
154
  );
151
155
  };
152
- const initialPrompts: InitialPrompt[] = [];
156
+ const initialPrompts: PagePrompt[] = [];
153
157
  const resourcePromises = references.map(async (reference) => {
154
158
  if (
155
159
  reference.url.startsWith('#') ||
@@ -185,6 +189,8 @@ export class PageQueue extends EventEmitter {
185
189
  this.pages.set(normalizedPath, reference.description);
186
190
 
187
191
  initialPrompts.push({
192
+ conversationId: randomUUID(),
193
+ id: randomUUID(),
188
194
  name: reference.name,
189
195
  title: reference.title,
190
196
  path: normalizedPath,
@@ -215,20 +221,21 @@ export class PageQueue extends EventEmitter {
215
221
  reason: 'reference',
216
222
  created: Date.now(),
217
223
  payload: {
224
+ id: prompt.id,
225
+ conversationId: prompt.conversationId,
218
226
  name: prompt.name,
219
227
  title: prompt.title,
220
228
  filename: prompt.filename,
221
229
  method: 'GET',
222
230
  path: prompt.path,
223
231
  prompt: prompt.description,
224
- conversationId: '',
225
232
  content: '',
226
233
  description: prompt.description,
227
234
  },
228
235
  });
229
236
  }
230
237
  // Trigger but don't wait for the "bonus" pages
231
- this.addPrompt(prompt).catch((err) => {
238
+ this.addPrompt(prompt, prompt.conversationId).catch((err) => {
232
239
  console.error('Failed to generate page reference', prompt.name, err);
233
240
  this.emit('error', err);
234
241
  });
@@ -265,8 +272,9 @@ export class PageQueue extends EventEmitter {
265
272
  return;
266
273
  }
267
274
 
275
+ const client = new StormClient(this.systemId);
268
276
  this.images.set(prompt.url, prompt.description);
269
- const result = await stormClient.createImage(
277
+ const result = await client.createImage(
270
278
  `Create an image for the url "${prompt.url}" with this description: ${prompt.description}`.trim()
271
279
  );
272
280
 
@@ -287,7 +295,8 @@ export class PageQueue extends EventEmitter {
287
295
  }
288
296
 
289
297
  public async generate(prompt: UIPagePrompt, conversationId: string) {
290
- const screenStream = await stormClient.createUIPage(prompt, conversationId);
298
+ const client = new StormClient(this.systemId);
299
+ const screenStream = await client.createUIPage(prompt, conversationId);
291
300
  let pageEvent: StormEventPage | null = null;
292
301
  screenStream.on('data', (event: StormEvent) => {
293
302
  if (event.type === 'PAGE') {
@@ -306,7 +315,8 @@ export class PageQueue extends EventEmitter {
306
315
  }
307
316
 
308
317
  private async resolveReferences(content: string): Promise<ReferenceClassification[]> {
309
- const referenceStream = await stormClient.classifyUIReferences(content);
318
+ const client = new StormClient(this.systemId);
319
+ const referenceStream = await client.classifyUIReferences(content);
310
320
 
311
321
  const references: ReferenceClassification[] = [];
312
322
 
@@ -17,7 +17,7 @@ import {
17
17
 
18
18
  import { BlockDefinition } from '@kapeta/schemas';
19
19
  import { codeGeneratorManager } from '../codeGeneratorManager';
20
- import { STORM_ID, stormClient } from './stormClient';
20
+ import { STORM_ID, StormClient } from './stormClient';
21
21
  import {
22
22
  StormEvent,
23
23
  StormEventBlockStatusType,
@@ -371,6 +371,7 @@ export class StormCodegen {
371
371
  });
372
372
  const uiEvents = [];
373
373
 
374
+ const stormClient = new StormClient(this.uiSystemId);
374
375
  // generate screens
375
376
  if (uiTemplates.length) {
376
377
  const screenStream = await stormClient.listScreens({
@@ -663,6 +664,7 @@ export class StormCodegen {
663
664
  }
664
665
 
665
666
  private async classifyErrors(errors: string, basePath: string): Promise<Map<string, ErrorClassification[]>> {
667
+ const stormClient = new StormClient(this.uiSystemId);
666
668
  const errorStream = await stormClient.createErrorClassification(errors, []);
667
669
  const fixes = new Map<string, ErrorClassification[]>();
668
670
 
@@ -711,7 +713,7 @@ export class StormCodegen {
711
713
  error: error,
712
714
  projectFiles: allFiles.map((f) => f.filename),
713
715
  };
714
-
716
+ const stormClient = new StormClient(this.uiSystemId);
715
717
  const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
716
718
  detailsStream.on('data', (evt) => {
717
719
  if (evt.type === 'ERROR_DETAILS') {
@@ -799,6 +801,7 @@ export class StormCodegen {
799
801
  ): Promise<StormEventErrorDetailsFile> {
800
802
  return new Promise<StormEventErrorDetailsFile>(async (resolve, reject) => {
801
803
  try {
804
+ const stormClient = new StormClient(this.uiSystemId);
802
805
  const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
803
806
  let resolved = false;
804
807
  fixStream.on('data', (evt) => {
@@ -347,6 +347,7 @@ export interface StormEventPhases {
347
347
  }
348
348
 
349
349
  export interface Page {
350
+ id: string;
350
351
  name: string;
351
352
  filename: string;
352
353
  title: string;
@@ -394,6 +395,7 @@ export interface UserJourneyScreen {
394
395
  path: string;
395
396
  method: string;
396
397
  nextScreens: string[];
398
+ conversationId?: string;
397
399
  }
398
400
 
399
401
  export interface UserJourney {
@@ -21,12 +21,12 @@ import {
21
21
 
22
22
  import {
23
23
  ConversationIdHeader,
24
- stormClient,
25
24
  UIPagePrompt,
26
25
  UIPageEditRequest,
27
26
  BasePromptRequest,
28
27
  UIPageVoteRequest,
29
28
  UIPageGetVoteRequest,
29
+ StormClient,
30
30
  } from './stormClient';
31
31
  import { StormEvent, StormEventPage, StormEventPhaseType, UserJourneyScreen } from './events';
32
32
 
@@ -162,8 +162,10 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
162
162
  sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
163
163
 
164
164
  const pagesFromDisk = readFilesAndContent(srcDir);
165
- const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
165
+ const client = new StormClient(systemId)
166
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
166
167
  pages: pagesFromDisk,
168
+ systemId: systemId,
167
169
  });
168
170
 
169
171
  await copyDirectory(srcDir, destDir, (fileName, content) => {
@@ -181,7 +183,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
181
183
  return page.content;
182
184
  });
183
185
 
184
- const prompt = await stormClient.generatePrompt(pageContents);
186
+ const prompt = await client.generatePrompt(pageContents);
185
187
 
186
188
  sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM_PROMPT));
187
189
 
@@ -204,11 +206,12 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req: KapetaBody
204
206
  //res.set(ConversationIdHeader, systemId);
205
207
 
206
208
  //sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
207
-
209
+ const client = new StormClient(systemId);
208
210
  try {
209
211
  const pagesFromDisk = readFilesAndContent(srcDir);
210
- const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
212
+ const pagesWithImplementation = await client.replaceMockWithAPICall({
211
213
  pages: pagesFromDisk,
214
+ systemId: systemId,
212
215
  });
213
216
 
214
217
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.IMPLEMENT_APIS));
@@ -227,7 +230,7 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req: KapetaBody
227
230
  return page;
228
231
  });
229
232
 
230
- const systemUrl = await stormClient.createSimpleBackend(handle, systemId, { pages: allFiles });
233
+ const systemUrl = await client.createSimpleBackend(handle, systemId, { pages: allFiles });
231
234
  //sendEvent(res, {type: 'SYSTEM_READY', created: Math.floor(Date.now() / 1000), reason: 'System Ready', payload: { systemUrl: systemUrl }});
232
235
 
233
236
  //sendEvent(res, createPhaseEndEvent(StormEventPhaseType.COMPOSE_SYSTEM));
@@ -328,8 +331,8 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
328
331
  const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
329
332
 
330
333
  const aiRequest: BasePromptRequest = JSON.parse(req.stringBody ?? '{}');
331
-
332
- const landingPagesStream = await stormClient.createUILandingPages(aiRequest, conversationId);
334
+ const client = new StormClient(conversationId); //todo is this correct we are using the landing page getConversationId down below as well
335
+ const landingPagesStream = await client.createUILandingPages(aiRequest, conversationId);
333
336
 
334
337
  onRequestAborted(req, res, () => {
335
338
  landingPagesStream.abort();
@@ -437,7 +440,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
437
440
  (req.headers[ConversationIdHeader.toLowerCase()] as string | undefined) || randomUUID();
438
441
 
439
442
  const aiRequest: BasePromptRequest = JSON.parse(req.stringBody ?? '{}');
440
-
443
+ const stormClient = new StormClient(outerConversationId);
441
444
  // Get user journeys
442
445
  const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest, outerConversationId);
443
446
 
@@ -455,11 +458,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
455
458
 
456
459
  userJourneysStream.on('data', (data: StormEvent) => {
457
460
  try {
458
- sendEvent(res, data);
459
461
  if (data.type === 'PROMPT_IMPROVE') {
460
462
  systemPrompt = data.payload.prompt;
461
463
  }
462
464
  if (data.type !== 'USER_JOURNEY') {
465
+ sendEvent(res, data);
463
466
  return;
464
467
  }
465
468
 
@@ -469,9 +472,12 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
469
472
 
470
473
  data.payload.screens.forEach((screen) => {
471
474
  if (!uniqueUserJourneyScreens[screen.name]) {
475
+ screen.conversationId = randomUUID();
472
476
  uniqueUserJourneyScreens[screen.name] = screen;
473
477
  }
474
478
  });
479
+
480
+ sendEvent(res, data);
475
481
  } catch (e) {
476
482
  console.error('Failed to process event', e);
477
483
  }
@@ -525,6 +531,10 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
525
531
 
526
532
  await waitForStormStream(userJourneysStream);
527
533
 
534
+ if (req.socket.closed) {
535
+ return;
536
+ }
537
+
528
538
  // Get the UI shells
529
539
  const shellsStream = await stormClient.createUIShells(
530
540
  {
@@ -571,6 +581,10 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
571
581
 
572
582
  await waitForStormStream(shellsStream);
573
583
 
584
+ if (req.socket.closed) {
585
+ return;
586
+ }
587
+
574
588
  UI_SERVERS[outerConversationId] = new UIServer(outerConversationId);
575
589
  await UI_SERVERS[outerConversationId].start();
576
590
 
@@ -588,7 +602,7 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
588
602
  queue.cancel();
589
603
  });
590
604
 
591
- queue.on('page', (screenData: StormEventPage) => sendPageEvent(outerConversationId, screenData, res));
605
+ queue.on('page', (pageEvent: StormEventPage) => sendPageEvent(outerConversationId, pageEvent, res));
592
606
 
593
607
  queue.on('event', (event: StormEvent) => {
594
608
  if (event.type === 'FILE_CHUNK') {
@@ -604,17 +618,20 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
604
618
 
605
619
  for (const screen of Object.values(uniqueUserJourneyScreens)) {
606
620
  queue
607
- .addPrompt({
608
- prompt: screen.requirements,
609
- method: screen.method,
610
- path: screen.path,
611
- description: screen.requirements,
612
- name: screen.name,
613
- title: screen.title,
614
- filename: screen.filename,
615
- storage_prefix: outerConversationId + '_',
616
- theme,
617
- })
621
+ .addPrompt(
622
+ {
623
+ prompt: screen.requirements,
624
+ method: screen.method,
625
+ path: screen.path,
626
+ description: screen.requirements,
627
+ name: screen.name,
628
+ title: screen.title,
629
+ filename: screen.filename,
630
+ storage_prefix: outerConversationId + '_',
631
+ theme,
632
+ },
633
+ screen.conversationId
634
+ )
618
635
  .catch((e) => {
619
636
  console.error('Failed to generate page for screen %s', screen.name, e);
620
637
  sendError(e as any, res);
@@ -719,6 +736,7 @@ router.post('/ui/vote', async (req: KapetaBodyRequest, res: Response) => {
719
736
  const aiRequest: UIPageVoteRequest = JSON.parse(req.stringBody ?? '{}');
720
737
  const { topic, vote, mainConversationId } = aiRequest;
721
738
  try {
739
+ const stormClient = new StormClient(mainConversationId);
722
740
  await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
723
741
  } catch (e: any) {
724
742
  res.status(500).send({ error: e.message });
@@ -730,6 +748,7 @@ router.post('/ui/get-vote', async (req: KapetaBodyRequest, res: Response) => {
730
748
  const aiRequest: UIPageGetVoteRequest = JSON.parse(req.stringBody ?? '{}');
731
749
  const { topic, mainConversationId } = aiRequest;
732
750
  try {
751
+ const stormClient = new StormClient(mainConversationId);
733
752
  const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
734
753
  res.send({ vote });
735
754
  } catch (e: any) {
@@ -761,6 +780,7 @@ async function handleAll(req: KapetaBodyRequest, res: Response) {
761
780
  const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
762
781
 
763
782
  const aiRequest: BasePromptRequest = JSON.parse(req.stringBody ?? '{}');
783
+ const stormClient = new StormClient(systemId);
764
784
  const metaStream = await stormClient.createMetadata(aiRequest, conversationId);
765
785
 
766
786
  onRequestAborted(req, res, () => {