@kapeta/local-cluster-service 0.56.2 → 0.58.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.58.0](https://github.com/kapetacom/local-cluster-service/compare/v0.57.0...v0.58.0) (2024-07-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * add /ui/edit endpoint for Page edits ([#200](https://github.com/kapetacom/local-cluster-service/issues/200)) ([9ac81df](https://github.com/kapetacom/local-cluster-service/commit/9ac81dfaa200976a865a9d9387dc6d2ed58891b2))
7
+
8
+ # [0.57.0](https://github.com/kapetacom/local-cluster-service/compare/v0.56.2...v0.57.0) (2024-07-22)
9
+
10
+
11
+ ### Features
12
+
13
+ * Add support for pages and journeys ([743f6fd](https://github.com/kapetacom/local-cluster-service/commit/743f6fdb1851eb2c7a76e79bc68a74f529af9207))
14
+
1
15
  ## [0.56.2](https://github.com/kapetacom/local-cluster-service/compare/v0.56.1...v0.56.2) (2024-07-19)
2
16
 
3
17
 
@@ -266,4 +266,34 @@ export interface StormEventPhases {
266
266
  phaseType: StormEventPhaseType;
267
267
  };
268
268
  }
269
- 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;
269
+ export interface Page {
270
+ name: string;
271
+ description: string;
272
+ content: string;
273
+ path: string;
274
+ method: string;
275
+ }
276
+ export interface StormEventPage {
277
+ type: 'PAGE';
278
+ reason: string;
279
+ created: number;
280
+ payload: Page;
281
+ }
282
+ export interface UserJourneyScreen {
283
+ name: string;
284
+ filename: string;
285
+ requirements: string;
286
+ path: string;
287
+ method: string;
288
+ nextScreens: string[];
289
+ }
290
+ export interface UserJourney {
291
+ screens: UserJourneyScreen[];
292
+ }
293
+ export interface StormEventUserJourney {
294
+ type: 'USER_JOURNEY';
295
+ reason: string;
296
+ created: number;
297
+ payload: UserJourney;
298
+ }
299
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventPage;
@@ -21,6 +21,104 @@ const assetManager_1 = require("../assetManager");
21
21
  const router = (0, express_promise_router_1.default)();
22
22
  router.use('/', cors_1.corsHandler);
23
23
  router.use('/', stringBody_1.stringBody);
24
+ router.post('/:handle/ui', async (req, res) => {
25
+ const handle = req.params.handle;
26
+ try {
27
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
28
+ const aiRequest = JSON.parse(req.stringBody ?? '{}');
29
+ const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
30
+ onRequestAborted(req, res, () => {
31
+ userJourneysStream.abort();
32
+ });
33
+ res.set('Content-Type', 'application/x-ndjson');
34
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
35
+ res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
36
+ const promises = {};
37
+ userJourneysStream.on('data', async (data) => {
38
+ try {
39
+ console.log('Processing user journey event', data);
40
+ sendEvent(res, data);
41
+ if (data.type !== 'USER_JOURNEY') {
42
+ return;
43
+ }
44
+ data.payload.screens.forEach((screen) => {
45
+ if (screen.name in promises) {
46
+ return;
47
+ }
48
+ promises[screen.name] = new Promise(async (resolve, reject) => {
49
+ try {
50
+ const screenStream = await stormClient_1.stormClient.createUIPage({
51
+ prompt: screen.requirements,
52
+ method: screen.method,
53
+ path: screen.path,
54
+ description: screen.requirements,
55
+ name: screen.name,
56
+ filename: screen.filename,
57
+ }, conversationId);
58
+ screenStream.on('data', (screenData) => {
59
+ console.log('Processing screen event', screenData);
60
+ sendEvent(res, screenData);
61
+ });
62
+ screenStream.on('end', () => {
63
+ resolve();
64
+ });
65
+ }
66
+ catch (e) {
67
+ reject(e);
68
+ }
69
+ });
70
+ });
71
+ }
72
+ catch (e) {
73
+ console.error('Failed to process event', e);
74
+ }
75
+ });
76
+ await waitForStormStream(userJourneysStream);
77
+ if (userJourneysStream.isAborted()) {
78
+ return;
79
+ }
80
+ await Promise.all(Object.values(promises));
81
+ sendDone(res);
82
+ }
83
+ catch (err) {
84
+ sendError(err, res);
85
+ if (!res.closed) {
86
+ res.end();
87
+ }
88
+ }
89
+ });
90
+ router.post('/ui/edit', async (req, res) => {
91
+ try {
92
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
93
+ const aiRequest = JSON.parse(req.stringBody ?? '{}');
94
+ const editStream = await stormClient_1.stormClient.editPages(aiRequest.prompt, conversationId);
95
+ onRequestAborted(req, res, () => {
96
+ editStream.abort();
97
+ });
98
+ res.set('Content-Type', 'application/x-ndjson');
99
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
100
+ res.set(stormClient_1.ConversationIdHeader, editStream.getConversationId());
101
+ editStream.on('data', (data) => {
102
+ try {
103
+ sendEvent(res, data);
104
+ }
105
+ catch (e) {
106
+ console.error('Failed to process event', e);
107
+ }
108
+ });
109
+ await waitForStormStream(editStream);
110
+ if (editStream.isAborted()) {
111
+ return;
112
+ }
113
+ sendDone(res);
114
+ }
115
+ catch (err) {
116
+ sendError(err, res);
117
+ if (!res.closed) {
118
+ res.end();
119
+ }
120
+ }
121
+ });
24
122
  router.post('/:handle/all', async (req, res) => {
25
123
  const handle = req.params.handle;
26
124
  try {
@@ -1,12 +1,33 @@
1
1
  import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
2
2
  export declare const STORM_ID = "storm";
3
3
  export declare const ConversationIdHeader = "Conversation-Id";
4
+ interface UIPagePrompt {
5
+ name: string;
6
+ filename: string;
7
+ prompt: string;
8
+ path: string;
9
+ method: string;
10
+ description: string;
11
+ }
12
+ export interface UIPageEditPrompt {
13
+ planDescription: string;
14
+ blockDescription: string;
15
+ pages: {
16
+ filename: string;
17
+ content: string;
18
+ }[];
19
+ prompt: string;
20
+ }
4
21
  declare class StormClient {
5
22
  private readonly _baseUrl;
6
23
  constructor();
7
24
  private createOptions;
8
25
  private send;
9
26
  createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
27
+ createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
28
+ createUIUserJourneys(prompt: string, conversationId?: string): Promise<StormStream>;
29
+ createUIPage(prompt: UIPagePrompt, conversationId?: string): Promise<StormStream>;
30
+ editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
10
31
  listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
11
32
  createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
12
33
  createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
@@ -28,7 +28,7 @@ class StormClient {
28
28
  const api = new nodejs_api_client_1.KapetaAPI();
29
29
  if (api.hasToken()) {
30
30
  const token = await api.getAccessToken();
31
- headers['Authorization'] = `Bearer ${token}`;
31
+ //headers['Authorization'] = `Bearer ${token}`;
32
32
  }
33
33
  if (body.conversationId) {
34
34
  headers[exports.ConversationIdHeader] = body.conversationId;
@@ -80,6 +80,30 @@ class StormClient {
80
80
  conversationId,
81
81
  });
82
82
  }
83
+ createUIPages(prompt, conversationId) {
84
+ return this.send('/v2/ui/pages', {
85
+ prompt: prompt,
86
+ conversationId,
87
+ });
88
+ }
89
+ createUIUserJourneys(prompt, conversationId) {
90
+ return this.send('/v2/ui/user-journeys', {
91
+ prompt: prompt,
92
+ conversationId,
93
+ });
94
+ }
95
+ createUIPage(prompt, conversationId) {
96
+ return this.send('/v2/ui/page', {
97
+ prompt: prompt,
98
+ conversationId,
99
+ });
100
+ }
101
+ editPages(prompt, conversationId) {
102
+ return this.send('/v2/ui/edit', {
103
+ prompt: JSON.stringify(prompt),
104
+ conversationId,
105
+ });
106
+ }
83
107
  listScreens(prompt, conversationId) {
84
108
  return this.send('/v2/ui/list', {
85
109
  prompt,
@@ -266,4 +266,34 @@ export interface StormEventPhases {
266
266
  phaseType: StormEventPhaseType;
267
267
  };
268
268
  }
269
- 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;
269
+ export interface Page {
270
+ name: string;
271
+ description: string;
272
+ content: string;
273
+ path: string;
274
+ method: string;
275
+ }
276
+ export interface StormEventPage {
277
+ type: 'PAGE';
278
+ reason: string;
279
+ created: number;
280
+ payload: Page;
281
+ }
282
+ export interface UserJourneyScreen {
283
+ name: string;
284
+ filename: string;
285
+ requirements: string;
286
+ path: string;
287
+ method: string;
288
+ nextScreens: string[];
289
+ }
290
+ export interface UserJourney {
291
+ screens: UserJourneyScreen[];
292
+ }
293
+ export interface StormEventUserJourney {
294
+ type: 'USER_JOURNEY';
295
+ reason: string;
296
+ created: number;
297
+ payload: UserJourney;
298
+ }
299
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventPage;
@@ -21,6 +21,104 @@ const assetManager_1 = require("../assetManager");
21
21
  const router = (0, express_promise_router_1.default)();
22
22
  router.use('/', cors_1.corsHandler);
23
23
  router.use('/', stringBody_1.stringBody);
24
+ router.post('/:handle/ui', async (req, res) => {
25
+ const handle = req.params.handle;
26
+ try {
27
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
28
+ const aiRequest = JSON.parse(req.stringBody ?? '{}');
29
+ const userJourneysStream = await stormClient_1.stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
30
+ onRequestAborted(req, res, () => {
31
+ userJourneysStream.abort();
32
+ });
33
+ res.set('Content-Type', 'application/x-ndjson');
34
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
35
+ res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
36
+ const promises = {};
37
+ userJourneysStream.on('data', async (data) => {
38
+ try {
39
+ console.log('Processing user journey event', data);
40
+ sendEvent(res, data);
41
+ if (data.type !== 'USER_JOURNEY') {
42
+ return;
43
+ }
44
+ data.payload.screens.forEach((screen) => {
45
+ if (screen.name in promises) {
46
+ return;
47
+ }
48
+ promises[screen.name] = new Promise(async (resolve, reject) => {
49
+ try {
50
+ const screenStream = await stormClient_1.stormClient.createUIPage({
51
+ prompt: screen.requirements,
52
+ method: screen.method,
53
+ path: screen.path,
54
+ description: screen.requirements,
55
+ name: screen.name,
56
+ filename: screen.filename,
57
+ }, conversationId);
58
+ screenStream.on('data', (screenData) => {
59
+ console.log('Processing screen event', screenData);
60
+ sendEvent(res, screenData);
61
+ });
62
+ screenStream.on('end', () => {
63
+ resolve();
64
+ });
65
+ }
66
+ catch (e) {
67
+ reject(e);
68
+ }
69
+ });
70
+ });
71
+ }
72
+ catch (e) {
73
+ console.error('Failed to process event', e);
74
+ }
75
+ });
76
+ await waitForStormStream(userJourneysStream);
77
+ if (userJourneysStream.isAborted()) {
78
+ return;
79
+ }
80
+ await Promise.all(Object.values(promises));
81
+ sendDone(res);
82
+ }
83
+ catch (err) {
84
+ sendError(err, res);
85
+ if (!res.closed) {
86
+ res.end();
87
+ }
88
+ }
89
+ });
90
+ router.post('/ui/edit', async (req, res) => {
91
+ try {
92
+ const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
93
+ const aiRequest = JSON.parse(req.stringBody ?? '{}');
94
+ const editStream = await stormClient_1.stormClient.editPages(aiRequest.prompt, conversationId);
95
+ onRequestAborted(req, res, () => {
96
+ editStream.abort();
97
+ });
98
+ res.set('Content-Type', 'application/x-ndjson');
99
+ res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
100
+ res.set(stormClient_1.ConversationIdHeader, editStream.getConversationId());
101
+ editStream.on('data', (data) => {
102
+ try {
103
+ sendEvent(res, data);
104
+ }
105
+ catch (e) {
106
+ console.error('Failed to process event', e);
107
+ }
108
+ });
109
+ await waitForStormStream(editStream);
110
+ if (editStream.isAborted()) {
111
+ return;
112
+ }
113
+ sendDone(res);
114
+ }
115
+ catch (err) {
116
+ sendError(err, res);
117
+ if (!res.closed) {
118
+ res.end();
119
+ }
120
+ }
121
+ });
24
122
  router.post('/:handle/all', async (req, res) => {
25
123
  const handle = req.params.handle;
26
124
  try {
@@ -1,12 +1,33 @@
1
1
  import { ConversationItem, StormFileImplementationPrompt, StormStream, StormUIImplementationPrompt, StormUIListPrompt } from './stream';
2
2
  export declare const STORM_ID = "storm";
3
3
  export declare const ConversationIdHeader = "Conversation-Id";
4
+ interface UIPagePrompt {
5
+ name: string;
6
+ filename: string;
7
+ prompt: string;
8
+ path: string;
9
+ method: string;
10
+ description: string;
11
+ }
12
+ export interface UIPageEditPrompt {
13
+ planDescription: string;
14
+ blockDescription: string;
15
+ pages: {
16
+ filename: string;
17
+ content: string;
18
+ }[];
19
+ prompt: string;
20
+ }
4
21
  declare class StormClient {
5
22
  private readonly _baseUrl;
6
23
  constructor();
7
24
  private createOptions;
8
25
  private send;
9
26
  createMetadata(prompt: string, conversationId?: string): Promise<StormStream>;
27
+ createUIPages(prompt: string, conversationId?: string): Promise<StormStream>;
28
+ createUIUserJourneys(prompt: string, conversationId?: string): Promise<StormStream>;
29
+ createUIPage(prompt: UIPagePrompt, conversationId?: string): Promise<StormStream>;
30
+ editPages(prompt: UIPageEditPrompt, conversationId?: string): Promise<StormStream>;
10
31
  listScreens(prompt: StormUIListPrompt, conversationId?: string): Promise<StormStream>;
11
32
  createUIImplementation(prompt: StormUIImplementationPrompt, conversationId?: string): Promise<StormStream>;
12
33
  createServiceImplementation(prompt: StormFileImplementationPrompt, conversationId?: string): Promise<StormStream>;
@@ -28,7 +28,7 @@ class StormClient {
28
28
  const api = new nodejs_api_client_1.KapetaAPI();
29
29
  if (api.hasToken()) {
30
30
  const token = await api.getAccessToken();
31
- headers['Authorization'] = `Bearer ${token}`;
31
+ //headers['Authorization'] = `Bearer ${token}`;
32
32
  }
33
33
  if (body.conversationId) {
34
34
  headers[exports.ConversationIdHeader] = body.conversationId;
@@ -80,6 +80,30 @@ class StormClient {
80
80
  conversationId,
81
81
  });
82
82
  }
83
+ createUIPages(prompt, conversationId) {
84
+ return this.send('/v2/ui/pages', {
85
+ prompt: prompt,
86
+ conversationId,
87
+ });
88
+ }
89
+ createUIUserJourneys(prompt, conversationId) {
90
+ return this.send('/v2/ui/user-journeys', {
91
+ prompt: prompt,
92
+ conversationId,
93
+ });
94
+ }
95
+ createUIPage(prompt, conversationId) {
96
+ return this.send('/v2/ui/page', {
97
+ prompt: prompt,
98
+ conversationId,
99
+ });
100
+ }
101
+ editPages(prompt, conversationId) {
102
+ return this.send('/v2/ui/edit', {
103
+ prompt: JSON.stringify(prompt),
104
+ conversationId,
105
+ });
106
+ }
83
107
  listScreens(prompt, conversationId) {
84
108
  return this.send('/v2/ui/list', {
85
109
  prompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.56.2",
3
+ "version": "0.58.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -316,6 +316,43 @@ export interface StormEventPhases {
316
316
  };
317
317
  }
318
318
 
319
+ export interface Page {
320
+ name: string;
321
+ description: string;
322
+ content: string;
323
+ path: string;
324
+ method: string;
325
+ }
326
+
327
+ // Event for creating a page
328
+ export interface StormEventPage {
329
+ type: 'PAGE';
330
+ reason: string;
331
+ created: number;
332
+ payload: Page;
333
+ }
334
+
335
+ export interface UserJourneyScreen {
336
+ name: string;
337
+ filename: string;
338
+ requirements: string;
339
+ path: string;
340
+ method: string;
341
+ nextScreens: string[];
342
+ }
343
+
344
+ export interface UserJourney {
345
+ screens: UserJourneyScreen[];
346
+ }
347
+
348
+ // Event for defining a user journey
349
+ export interface StormEventUserJourney {
350
+ type: 'USER_JOURNEY';
351
+ reason: string;
352
+ created: number;
353
+ payload: UserJourney;
354
+ }
355
+
319
356
  export type StormEvent =
320
357
  | StormEventCreateBlock
321
358
  | StormEventCreateConnection
@@ -340,4 +377,6 @@ export type StormEvent =
340
377
  | StormEventBlockReady
341
378
  | StormEventPhases
342
379
  | StormEventBlockStatus
343
- | StormEventCreateDSLRetry;
380
+ | StormEventCreateDSLRetry
381
+ | StormEventUserJourney
382
+ | StormEventPage;
@@ -12,7 +12,7 @@ import { corsHandler } from '../middleware/cors';
12
12
  import { stringBody } from '../middleware/stringBody';
13
13
  import { KapetaBodyRequest } from '../types';
14
14
  import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
15
- import { ConversationIdHeader, stormClient } from './stormClient';
15
+ import { ConversationIdHeader, stormClient, UIPageEditPrompt } from './stormClient';
16
16
  import { StormEvent, StormEventPhaseType } from './events';
17
17
  import {
18
18
  createPhaseEndEvent,
@@ -29,6 +29,123 @@ const router = Router();
29
29
  router.use('/', corsHandler);
30
30
  router.use('/', stringBody);
31
31
 
32
+ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
33
+ const handle = req.params.handle as string;
34
+ try {
35
+ const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
36
+
37
+ const aiRequest: StormContextRequest = JSON.parse(req.stringBody ?? '{}');
38
+
39
+ const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest.prompt, conversationId);
40
+
41
+ onRequestAborted(req, res, () => {
42
+ userJourneysStream.abort();
43
+ });
44
+
45
+ res.set('Content-Type', 'application/x-ndjson');
46
+ res.set('Access-Control-Expose-Headers', ConversationIdHeader);
47
+ res.set(ConversationIdHeader, userJourneysStream.getConversationId());
48
+
49
+ const promises: { [key: string]: Promise<void> } = {};
50
+
51
+ userJourneysStream.on('data', async (data: StormEvent) => {
52
+ try {
53
+ console.log('Processing user journey event', data);
54
+ sendEvent(res, data);
55
+ if (data.type !== 'USER_JOURNEY') {
56
+ return;
57
+ }
58
+
59
+ data.payload.screens.forEach((screen) => {
60
+ if (screen.name in promises) {
61
+ return;
62
+ }
63
+ promises[screen.name] = new Promise(async (resolve, reject) => {
64
+ try {
65
+ const screenStream = await stormClient.createUIPage(
66
+ {
67
+ prompt: screen.requirements,
68
+ method: screen.method,
69
+ path: screen.path,
70
+ description: screen.requirements,
71
+ name: screen.name,
72
+ filename: screen.filename,
73
+ },
74
+ conversationId
75
+ );
76
+ screenStream.on('data', (screenData: StormEvent) => {
77
+ console.log('Processing screen event', screenData);
78
+ sendEvent(res, screenData);
79
+ });
80
+ screenStream.on('end', () => {
81
+ resolve();
82
+ });
83
+ } catch (e: any) {
84
+ reject(e);
85
+ }
86
+ });
87
+ });
88
+ } catch (e) {
89
+ console.error('Failed to process event', e);
90
+ }
91
+ });
92
+
93
+ await waitForStormStream(userJourneysStream);
94
+
95
+ if (userJourneysStream.isAborted()) {
96
+ return;
97
+ }
98
+
99
+ await Promise.all(Object.values(promises));
100
+
101
+ sendDone(res);
102
+ } catch (err: any) {
103
+ sendError(err, res);
104
+ if (!res.closed) {
105
+ res.end();
106
+ }
107
+ }
108
+ });
109
+
110
+ router.post('/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
111
+ try {
112
+ const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
113
+
114
+ const aiRequest: StormContextRequest<UIPageEditPrompt> = JSON.parse(req.stringBody ?? '{}');
115
+
116
+ const editStream = await stormClient.editPages(aiRequest.prompt, conversationId);
117
+
118
+ onRequestAborted(req, res, () => {
119
+ editStream.abort();
120
+ });
121
+
122
+ res.set('Content-Type', 'application/x-ndjson');
123
+ res.set('Access-Control-Expose-Headers', ConversationIdHeader);
124
+ res.set(ConversationIdHeader, editStream.getConversationId());
125
+
126
+ editStream.on('data', (data: StormEvent) => {
127
+ try {
128
+ sendEvent(res, data);
129
+ } catch (e) {
130
+ console.error('Failed to process event', e);
131
+ }
132
+ });
133
+
134
+ await waitForStormStream(editStream);
135
+
136
+ if (editStream.isAborted()) {
137
+ return;
138
+ }
139
+
140
+ sendDone(res);
141
+ } catch (err: any) {
142
+ sendError(err, res);
143
+ if (!res.closed) {
144
+ res.end();
145
+ }
146
+ }
147
+ });
148
+
32
149
  router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
33
150
  const handle = req.params.handle as string;
34
151
 
@@ -20,6 +20,25 @@ export const STORM_ID = 'storm';
20
20
 
21
21
  export const ConversationIdHeader = 'Conversation-Id';
22
22
 
23
+ interface UIPagePrompt {
24
+ name: string;
25
+ filename: string;
26
+ prompt: string;
27
+ path: string;
28
+ method: string;
29
+ description: string;
30
+ }
31
+
32
+ export interface UIPageEditPrompt {
33
+ planDescription: string;
34
+ blockDescription: string;
35
+ pages: {
36
+ filename: string;
37
+ content: string;
38
+ }[];
39
+ prompt: string;
40
+ }
41
+
23
42
  class StormClient {
24
43
  private readonly _baseUrl: string;
25
44
 
@@ -39,7 +58,7 @@ class StormClient {
39
58
  const api = new KapetaAPI();
40
59
  if (api.hasToken()) {
41
60
  const token = await api.getAccessToken();
42
- headers['Authorization'] = `Bearer ${token}`;
61
+ //headers['Authorization'] = `Bearer ${token}`;
43
62
  }
44
63
 
45
64
  if (body.conversationId) {
@@ -113,6 +132,34 @@ class StormClient {
113
132
  });
114
133
  }
115
134
 
135
+ public createUIPages(prompt: string, conversationId?: string) {
136
+ return this.send('/v2/ui/pages', {
137
+ prompt: prompt,
138
+ conversationId,
139
+ });
140
+ }
141
+
142
+ public createUIUserJourneys(prompt: string, conversationId?: string) {
143
+ return this.send('/v2/ui/user-journeys', {
144
+ prompt: prompt,
145
+ conversationId,
146
+ });
147
+ }
148
+
149
+ public createUIPage(prompt: UIPagePrompt, conversationId?: string) {
150
+ return this.send('/v2/ui/page', {
151
+ prompt: prompt,
152
+ conversationId,
153
+ });
154
+ }
155
+
156
+ public editPages(prompt: UIPageEditPrompt, conversationId?: string) {
157
+ return this.send('/v2/ui/edit', {
158
+ prompt: JSON.stringify(prompt),
159
+ conversationId,
160
+ });
161
+ }
162
+
116
163
  public listScreens(prompt: StormUIListPrompt, conversationId?: string) {
117
164
  return this.send('/v2/ui/list', {
118
165
  prompt,