@kapeta/local-cluster-service 0.73.0 → 0.74.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,10 @@
1
+ # [0.74.0](https://github.com/kapetacom/local-cluster-service/compare/v0.73.0...v0.74.0) (2024-09-26)
2
+
3
+
4
+ ### Features
5
+
6
+ * add URL extraction to conversations endpoint ([#259](https://github.com/kapetacom/local-cluster-service/issues/259)) ([b4a38cd](https://github.com/kapetacom/local-cluster-service/commit/b4a38cdd97d94e3c69091f87bd10487c000ba631))
7
+
1
8
  # [0.73.0](https://github.com/kapetacom/local-cluster-service/compare/v0.72.0...v0.73.0) (2024-09-25)
2
9
 
3
10
 
@@ -404,5 +404,13 @@ export interface StormEventSystemReady {
404
404
  systemUrl: string;
405
405
  };
406
406
  }
407
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady;
407
+ export interface StormEventModelResponse {
408
+ type: 'MODEL_RESPONSE';
409
+ reason: string;
410
+ created: number;
411
+ payload: {
412
+ text: string;
413
+ };
414
+ }
415
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady | StormEventModelResponse;
408
416
  export {};
@@ -7,6 +7,7 @@ export declare class StormService {
7
7
  id: string;
8
8
  description: string;
9
9
  title: string;
10
+ url?: string | undefined;
10
11
  }[]>;
11
12
  getConversation(conversationId: string): Promise<string>;
12
13
  saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
@@ -54,22 +54,43 @@ class StormService {
54
54
  // Returns list of UUIDs - probably want to make it more useful than that
55
55
  const conversations = [];
56
56
  for (const file of eventFiles) {
57
- const nldContents = await promises_1.default.readFile(file, 'utf8');
58
- const events = nldContents.split('\n').map((e) => JSON.parse(e));
59
- // find the shell and get the title tag
60
- const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event;
61
- const html = shellEvent?.payload.content;
62
- const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
63
- const id = events.find((e) => e.type === 'AI')?.systemId;
64
- const initialPrompt = events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event?.payload?.prompt || events[0].text;
65
- if (!id) {
66
- continue;
57
+ try {
58
+ const nldContents = await promises_1.default.readFile(file, 'utf8');
59
+ const events = nldContents.split('\n').map((e) => JSON.parse(e));
60
+ // find the shell and get the title tag
61
+ const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event;
62
+ const html = shellEvent?.payload.content;
63
+ const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
64
+ const id = events.find((e) => e.type === 'AI')?.systemId;
65
+ const initialPrompt = events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event?.payload?.prompt || events[0].text;
66
+ if (!id) {
67
+ continue;
68
+ }
69
+ let url = undefined;
70
+ // Find the last model response event that has a URL in the payload (in case it changed over time)
71
+ for (const evt of [...events].reverse()) {
72
+ const event = evt.event;
73
+ if (evt.type === 'AI' && event.type === 'MODEL_RESPONSE') {
74
+ // Look for a URL in the model response markdown
75
+ const regex = /\[(.*?)\]\((.*?)\)/g;
76
+ const match = regex.exec(event.payload.text);
77
+ const [, _linkText, linkUrl] = match || [];
78
+ if (linkUrl?.startsWith('http')) {
79
+ url = linkUrl;
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ conversations.push({
85
+ id,
86
+ description: initialPrompt,
87
+ title: title || 'New system',
88
+ url,
89
+ });
90
+ }
91
+ catch (e) {
92
+ console.error('Failed to load conversation at %s', file, e);
67
93
  }
68
- conversations.push({
69
- id,
70
- description: initialPrompt,
71
- title: title || 'New system',
72
- });
73
94
  }
74
95
  return conversations;
75
96
  }
@@ -404,5 +404,13 @@ export interface StormEventSystemReady {
404
404
  systemUrl: string;
405
405
  };
406
406
  }
407
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady;
407
+ export interface StormEventModelResponse {
408
+ type: 'MODEL_RESPONSE';
409
+ reason: string;
410
+ created: number;
411
+ payload: {
412
+ text: string;
413
+ };
414
+ }
415
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted | StormImage | StormEventSystemReady | StormEventModelResponse;
408
416
  export {};
@@ -7,6 +7,7 @@ export declare class StormService {
7
7
  id: string;
8
8
  description: string;
9
9
  title: string;
10
+ url?: string | undefined;
10
11
  }[]>;
11
12
  getConversation(conversationId: string): Promise<string>;
12
13
  saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
@@ -54,22 +54,43 @@ class StormService {
54
54
  // Returns list of UUIDs - probably want to make it more useful than that
55
55
  const conversations = [];
56
56
  for (const file of eventFiles) {
57
- const nldContents = await promises_1.default.readFile(file, 'utf8');
58
- const events = nldContents.split('\n').map((e) => JSON.parse(e));
59
- // find the shell and get the title tag
60
- const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event;
61
- const html = shellEvent?.payload.content;
62
- const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
63
- const id = events.find((e) => e.type === 'AI')?.systemId;
64
- const initialPrompt = events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event?.payload?.prompt || events[0].text;
65
- if (!id) {
66
- continue;
57
+ try {
58
+ const nldContents = await promises_1.default.readFile(file, 'utf8');
59
+ const events = nldContents.split('\n').map((e) => JSON.parse(e));
60
+ // find the shell and get the title tag
61
+ const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event;
62
+ const html = shellEvent?.payload.content;
63
+ const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
64
+ const id = events.find((e) => e.type === 'AI')?.systemId;
65
+ const initialPrompt = events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event?.payload?.prompt || events[0].text;
66
+ if (!id) {
67
+ continue;
68
+ }
69
+ let url = undefined;
70
+ // Find the last model response event that has a URL in the payload (in case it changed over time)
71
+ for (const evt of [...events].reverse()) {
72
+ const event = evt.event;
73
+ if (evt.type === 'AI' && event.type === 'MODEL_RESPONSE') {
74
+ // Look for a URL in the model response markdown
75
+ const regex = /\[(.*?)\]\((.*?)\)/g;
76
+ const match = regex.exec(event.payload.text);
77
+ const [, _linkText, linkUrl] = match || [];
78
+ if (linkUrl?.startsWith('http')) {
79
+ url = linkUrl;
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ conversations.push({
85
+ id,
86
+ description: initialPrompt,
87
+ title: title || 'New system',
88
+ url,
89
+ });
90
+ }
91
+ catch (e) {
92
+ console.error('Failed to load conversation at %s', file, e);
67
93
  }
68
- conversations.push({
69
- id,
70
- description: initialPrompt,
71
- title: title || 'New system',
72
- });
73
94
  }
74
95
  return conversations;
75
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.73.0",
3
+ "version": "0.74.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -480,7 +480,16 @@ export interface StormEventSystemReady {
480
480
  created: number;
481
481
  payload: {
482
482
  systemUrl: string;
483
- }
483
+ };
484
+ }
485
+
486
+ export interface StormEventModelResponse {
487
+ type: 'MODEL_RESPONSE';
488
+ reason: string;
489
+ created: number;
490
+ payload: {
491
+ text: string;
492
+ };
484
493
  }
485
494
 
486
495
  export type StormEvent =
@@ -518,4 +527,5 @@ export type StormEvent =
518
527
  | StormEventApiBase
519
528
  | StormEventUIStarted
520
529
  | StormImage
521
- | StormEventSystemReady;
530
+ | StormEventSystemReady
531
+ | StormEventModelResponse;
@@ -165,6 +165,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req: KapetaBodyRequest
165
165
  const pagesWithImplementation = await stormClient.replaceMockWithAPICall({
166
166
  pages: pagesFromDisk,
167
167
  });
168
+
168
169
  await copyDirectory(srcDir, destDir, (fileName, content) => {
169
170
  // find the page from result1 and write the content to the file
170
171
  const page = pagesWithImplementation.find((p) => p.fileName === fileName);
@@ -3,7 +3,7 @@ import { glob } from 'glob';
3
3
  import { filesystemManager } from './filesystemManager';
4
4
  import path from 'path';
5
5
  import { existsSync } from 'fs';
6
- import { StormEvent, StormEventPromptImprove, StormEventUIShell } from './storm/events';
6
+ import { StormEvent, StormEventModelResponse, StormEventPromptImprove, StormEventUIShell } from './storm/events';
7
7
  import * as tar from 'tar';
8
8
  import { stormClient } from './storm/stormClient';
9
9
 
@@ -28,39 +28,61 @@ export class StormService {
28
28
  absolute: true,
29
29
  });
30
30
  // Returns list of UUIDs - probably want to make it more useful than that
31
- const conversations: { id: string; description: string; title: string }[] = [];
31
+ const conversations: { id: string; description: string; title: string; url?: string }[] = [];
32
32
  for (const file of eventFiles) {
33
- const nldContents = await fs.readFile(file as string, 'utf8');
34
- const events = nldContents.split('\n').map((e) => JSON.parse(e)) as {
35
- // | { type: 'USER'; event: any } // IS stupid!
36
- type: 'AI';
37
- systemId: string;
38
- event: StormEvent;
39
- }[];
40
- // find the shell and get the title tag
41
- const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event as
42
- | StormEventUIShell
43
- | undefined;
44
- const html = shellEvent?.payload.content;
45
- const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
46
- const id = events.find((e) => e.type === 'AI')?.systemId;
47
-
48
- const initialPrompt =
49
- (
50
- events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event as
51
- | StormEventPromptImprove
52
- | undefined
53
- )?.payload?.prompt || (events[0] as any).text;
54
-
55
- if (!id) {
56
- continue;
33
+ try {
34
+ const nldContents = await fs.readFile(file as string, 'utf8');
35
+ const events = nldContents.split('\n').map((e) => JSON.parse(e)) as {
36
+ // | { type: 'USER'; event: any } // IS stupid!
37
+ type: 'AI';
38
+ systemId: string;
39
+ event: StormEvent;
40
+ reason: string;
41
+ }[];
42
+ // find the shell and get the title tag
43
+ const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event as
44
+ | StormEventUIShell
45
+ | undefined;
46
+ const html = shellEvent?.payload.content;
47
+ const title = html?.match(/<title>(.*?)<\/title>/)?.[1];
48
+ const id = events.find((e) => e.type === 'AI')?.systemId;
49
+
50
+ const initialPrompt =
51
+ (
52
+ events.find((e) => e.type === 'AI' && e.event.type === 'PROMPT_IMPROVE')?.event as
53
+ | StormEventPromptImprove
54
+ | undefined
55
+ )?.payload?.prompt || (events[0] as any).text;
56
+
57
+ if (!id) {
58
+ continue;
59
+ }
60
+
61
+ let url = undefined;
62
+ // Find the last model response event that has a URL in the payload (in case it changed over time)
63
+ for (const evt of [...events].reverse()) {
64
+ const event = evt.event;
65
+ if (evt.type === 'AI' && event.type === 'MODEL_RESPONSE') {
66
+ // Look for a URL in the model response markdown
67
+ const regex = /\[(.*?)\]\((.*?)\)/g;
68
+ const match = regex.exec(event.payload.text);
69
+ const [, _linkText, linkUrl] = match || [];
70
+ if (linkUrl?.startsWith('http')) {
71
+ url = linkUrl;
72
+ break;
73
+ }
74
+ }
75
+ }
76
+
77
+ conversations.push({
78
+ id,
79
+ description: initialPrompt,
80
+ title: title || 'New system',
81
+ url,
82
+ });
83
+ } catch (e) {
84
+ console.error('Failed to load conversation at %s', file, e);
57
85
  }
58
-
59
- conversations.push({
60
- id,
61
- description: initialPrompt,
62
- title: title || 'New system',
63
- });
64
86
  }
65
87
 
66
88
  return conversations;