@kapeta/local-cluster-service 0.69.0 → 0.70.1

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,18 @@
1
+ ## [0.70.1](https://github.com/kapetacom/local-cluster-service/compare/v0.70.0...v0.70.1) (2024-09-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * filenames and lazier generation for refs ([#235](https://github.com/kapetacom/local-cluster-service/issues/235)) ([02c5589](https://github.com/kapetacom/local-cluster-service/commit/02c55895c4ae3ed5632f4b53f33509ec18c87dcb))
7
+
8
+ # [0.70.0](https://github.com/kapetacom/local-cluster-service/compare/v0.69.0...v0.70.0) (2024-09-04)
9
+
10
+
11
+ ### Features
12
+
13
+ * add "serve" endpoint to resume a UI conversation server ([a2eadb2](https://github.com/kapetacom/local-cluster-service/commit/a2eadb25298d0d59aaf47202eba0fc911ba5e2f8))
14
+ * change endpoint to end conversation to same format as resume ([6161e5c](https://github.com/kapetacom/local-cluster-service/commit/6161e5ce2fda1261bb26abd6a608ea9e41d46099))
15
+
1
16
  # [0.69.0](https://github.com/kapetacom/local-cluster-service/compare/v0.68.0...v0.69.0) (2024-09-03)
2
17
 
3
18
 
@@ -103,6 +103,9 @@ class PageQueue extends node_events_1.EventEmitter {
103
103
  generator.on('event', (event) => this.emit('event', event));
104
104
  generator.on('page_refs', async ({ event, references }) => {
105
105
  try {
106
+ const matchesExistingPages = (url) => {
107
+ return [...this.pages.keys()].some((path) => new RegExp(path.replaceAll('/*', '/[^/]+')).test(url));
108
+ };
106
109
  const initialPrompts = [];
107
110
  const resourcePromises = references.map(async (reference) => {
108
111
  if (reference.url.startsWith('#') ||
@@ -124,6 +127,9 @@ class PageQueue extends node_events_1.EventEmitter {
124
127
  break;
125
128
  case 'html':
126
129
  //console.log('Adding page generator for', reference);
130
+ if (matchesExistingPages(reference.url)) {
131
+ break;
132
+ }
127
133
  this.pages.set(reference.url, reference.description);
128
134
  initialPrompts.push({
129
135
  name: reference.name,
@@ -134,7 +140,8 @@ class PageQueue extends node_events_1.EventEmitter {
134
140
  prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
135
141
  `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
136
142
  description: reference.description,
137
- filename: '',
143
+ // Only used for matching
144
+ filename: reference.name + '.ref.html',
138
145
  theme: this.theme,
139
146
  });
140
147
  break;
@@ -153,7 +160,7 @@ class PageQueue extends node_events_1.EventEmitter {
153
160
  payload: {
154
161
  name: prompt.name,
155
162
  title: prompt.title,
156
- filename: '',
163
+ filename: prompt.filename,
157
164
  method: 'GET',
158
165
  path: prompt.path,
159
166
  prompt: prompt.description,
@@ -1,9 +1,11 @@
1
1
  import { StormEventPage } from './events';
2
2
  export declare class UIServer {
3
3
  private readonly systemId;
4
- private port;
5
4
  private server;
5
+ private port;
6
6
  constructor(systemId: string);
7
+ isRunning(): boolean;
8
+ getUrl(): string;
7
9
  start(): Promise<void>;
8
10
  close(): void;
9
11
  resolveUrl(screenData: StormEventPage): string;
@@ -15,11 +15,17 @@ const http_1 = require("http");
15
15
  const path_1 = require("path");
16
16
  class UIServer {
17
17
  systemId;
18
- port = 50000;
19
18
  server;
19
+ port = 50000;
20
20
  constructor(systemId) {
21
21
  this.systemId = systemId;
22
22
  }
23
+ isRunning() {
24
+ return !!this.server;
25
+ }
26
+ getUrl() {
27
+ return `http://localhost:${this.port}`;
28
+ }
23
29
  async start() {
24
30
  const app = (0, express_1.default)();
25
31
  app.get('/_reset', (req, res) => {
@@ -50,7 +50,7 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
50
50
  description: screenData.payload.description,
51
51
  prompt: screenData.payload.prompt,
52
52
  path: screenData.payload.path,
53
- url: server && screenData.payload.content ? server.resolveUrl(screenData) : '',
53
+ url: screenData.payload.content ? screenData.payload.path : '',
54
54
  method: screenData.payload.method,
55
55
  conversationId: innerConversationId,
56
56
  },
@@ -58,8 +58,17 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
58
58
  }
59
59
  return screenData;
60
60
  }
61
- router.all('/ui/:systemId/serve/:method/*', async (req, res) => {
62
- (0, page_utils_1.readPageFromDisk)(req.params.systemId, req.params[0], req.params.method, res);
61
+ router.post('/ui/serve/:systemId', async (req, res) => {
62
+ const systemId = req.params.systemId;
63
+ if (!systemId) {
64
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
65
+ return;
66
+ }
67
+ const svr = (UI_SERVERS[systemId] = UI_SERVERS[systemId] || new UIServer_1.UIServer(systemId));
68
+ if (!svr.isRunning()) {
69
+ await UI_SERVERS[systemId].start();
70
+ }
71
+ res.status(200).send({ status: 'running', url: svr.getUrl() });
63
72
  });
64
73
  router.post('/ui/create-system/:systemId', async (req, res) => {
65
74
  const systemId = req.params.systemId;
@@ -75,6 +84,19 @@ router.post('/ui/create-system/:systemId', async (req, res) => {
75
84
  res.end();
76
85
  return;
77
86
  });
87
+ router.delete('/ui/serve/:systemId', async (req, res) => {
88
+ const systemId = req.params.systemId;
89
+ if (!systemId) {
90
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
91
+ return;
92
+ }
93
+ const server = UI_SERVERS[systemId];
94
+ if (server) {
95
+ server.close();
96
+ delete UI_SERVERS[systemId];
97
+ }
98
+ res.status(200).json({ status: 'ok' });
99
+ });
78
100
  router.post('/ui/screen', async (req, res) => {
79
101
  try {
80
102
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
@@ -117,19 +139,6 @@ router.post('/ui/screen', async (req, res) => {
117
139
  }
118
140
  }
119
141
  });
120
- router.delete('/:handle/ui', async (req, res) => {
121
- const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
122
- if (!conversationId) {
123
- res.status(400).send('Missing conversation id');
124
- return;
125
- }
126
- const server = UI_SERVERS[conversationId];
127
- if (server) {
128
- server.close();
129
- delete UI_SERVERS[conversationId];
130
- }
131
- res.status(200).json({ status: 'ok' });
132
- });
133
142
  router.post('/:handle/ui/iterative', async (req, res) => {
134
143
  const handle = req.params.handle;
135
144
  try {
@@ -103,6 +103,9 @@ class PageQueue extends node_events_1.EventEmitter {
103
103
  generator.on('event', (event) => this.emit('event', event));
104
104
  generator.on('page_refs', async ({ event, references }) => {
105
105
  try {
106
+ const matchesExistingPages = (url) => {
107
+ return [...this.pages.keys()].some((path) => new RegExp(path.replaceAll('/*', '/[^/]+')).test(url));
108
+ };
106
109
  const initialPrompts = [];
107
110
  const resourcePromises = references.map(async (reference) => {
108
111
  if (reference.url.startsWith('#') ||
@@ -124,6 +127,9 @@ class PageQueue extends node_events_1.EventEmitter {
124
127
  break;
125
128
  case 'html':
126
129
  //console.log('Adding page generator for', reference);
130
+ if (matchesExistingPages(reference.url)) {
131
+ break;
132
+ }
127
133
  this.pages.set(reference.url, reference.description);
128
134
  initialPrompts.push({
129
135
  name: reference.name,
@@ -134,7 +140,8 @@ class PageQueue extends node_events_1.EventEmitter {
134
140
  prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
135
141
  `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
136
142
  description: reference.description,
137
- filename: '',
143
+ // Only used for matching
144
+ filename: reference.name + '.ref.html',
138
145
  theme: this.theme,
139
146
  });
140
147
  break;
@@ -153,7 +160,7 @@ class PageQueue extends node_events_1.EventEmitter {
153
160
  payload: {
154
161
  name: prompt.name,
155
162
  title: prompt.title,
156
- filename: '',
163
+ filename: prompt.filename,
157
164
  method: 'GET',
158
165
  path: prompt.path,
159
166
  prompt: prompt.description,
@@ -1,9 +1,11 @@
1
1
  import { StormEventPage } from './events';
2
2
  export declare class UIServer {
3
3
  private readonly systemId;
4
- private port;
5
4
  private server;
5
+ private port;
6
6
  constructor(systemId: string);
7
+ isRunning(): boolean;
8
+ getUrl(): string;
7
9
  start(): Promise<void>;
8
10
  close(): void;
9
11
  resolveUrl(screenData: StormEventPage): string;
@@ -15,11 +15,17 @@ const http_1 = require("http");
15
15
  const path_1 = require("path");
16
16
  class UIServer {
17
17
  systemId;
18
- port = 50000;
19
18
  server;
19
+ port = 50000;
20
20
  constructor(systemId) {
21
21
  this.systemId = systemId;
22
22
  }
23
+ isRunning() {
24
+ return !!this.server;
25
+ }
26
+ getUrl() {
27
+ return `http://localhost:${this.port}`;
28
+ }
23
29
  async start() {
24
30
  const app = (0, express_1.default)();
25
31
  app.get('/_reset', (req, res) => {
@@ -50,7 +50,7 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
50
50
  description: screenData.payload.description,
51
51
  prompt: screenData.payload.prompt,
52
52
  path: screenData.payload.path,
53
- url: server && screenData.payload.content ? server.resolveUrl(screenData) : '',
53
+ url: screenData.payload.content ? screenData.payload.path : '',
54
54
  method: screenData.payload.method,
55
55
  conversationId: innerConversationId,
56
56
  },
@@ -58,8 +58,17 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
58
58
  }
59
59
  return screenData;
60
60
  }
61
- router.all('/ui/:systemId/serve/:method/*', async (req, res) => {
62
- (0, page_utils_1.readPageFromDisk)(req.params.systemId, req.params[0], req.params.method, res);
61
+ router.post('/ui/serve/:systemId', async (req, res) => {
62
+ const systemId = req.params.systemId;
63
+ if (!systemId) {
64
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
65
+ return;
66
+ }
67
+ const svr = (UI_SERVERS[systemId] = UI_SERVERS[systemId] || new UIServer_1.UIServer(systemId));
68
+ if (!svr.isRunning()) {
69
+ await UI_SERVERS[systemId].start();
70
+ }
71
+ res.status(200).send({ status: 'running', url: svr.getUrl() });
63
72
  });
64
73
  router.post('/ui/create-system/:systemId', async (req, res) => {
65
74
  const systemId = req.params.systemId;
@@ -75,6 +84,19 @@ router.post('/ui/create-system/:systemId', async (req, res) => {
75
84
  res.end();
76
85
  return;
77
86
  });
87
+ router.delete('/ui/serve/:systemId', async (req, res) => {
88
+ const systemId = req.params.systemId;
89
+ if (!systemId) {
90
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
91
+ return;
92
+ }
93
+ const server = UI_SERVERS[systemId];
94
+ if (server) {
95
+ server.close();
96
+ delete UI_SERVERS[systemId];
97
+ }
98
+ res.status(200).json({ status: 'ok' });
99
+ });
78
100
  router.post('/ui/screen', async (req, res) => {
79
101
  try {
80
102
  const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
@@ -117,19 +139,6 @@ router.post('/ui/screen', async (req, res) => {
117
139
  }
118
140
  }
119
141
  });
120
- router.delete('/:handle/ui', async (req, res) => {
121
- const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
122
- if (!conversationId) {
123
- res.status(400).send('Missing conversation id');
124
- return;
125
- }
126
- const server = UI_SERVERS[conversationId];
127
- if (server) {
128
- server.close();
129
- delete UI_SERVERS[conversationId];
130
- }
131
- res.status(200).json({ status: 'ok' });
132
- });
133
142
  router.post('/:handle/ui/iterative', async (req, res) => {
134
143
  const handle = req.params.handle;
135
144
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.69.0",
3
+ "version": "0.70.1",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -136,6 +136,9 @@ export class PageQueue extends EventEmitter {
136
136
  generator.on('event', (event: StormEvent) => this.emit('event', event));
137
137
  generator.on('page_refs', async ({ event, references }) => {
138
138
  try {
139
+ const matchesExistingPages = (url: string) => {
140
+ return [...this.pages.keys()].some((path) => new RegExp(path.replaceAll('/*', '/[^/]+')).test(url));
141
+ };
139
142
  const initialPrompts: InitialPrompt[] = [];
140
143
  const resourcePromises = references.map(async (reference) => {
141
144
  if (
@@ -160,6 +163,9 @@ export class PageQueue extends EventEmitter {
160
163
  break;
161
164
  case 'html':
162
165
  //console.log('Adding page generator for', reference);
166
+ if (matchesExistingPages(reference.url)) {
167
+ break;
168
+ }
163
169
  this.pages.set(reference.url, reference.description);
164
170
 
165
171
  initialPrompts.push({
@@ -172,7 +178,8 @@ export class PageQueue extends EventEmitter {
172
178
  `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
173
179
  `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
174
180
  description: reference.description,
175
- filename: '',
181
+ // Only used for matching
182
+ filename: reference.name + '.ref.html',
176
183
  theme: this.theme,
177
184
  });
178
185
  break;
@@ -193,7 +200,7 @@ export class PageQueue extends EventEmitter {
193
200
  payload: {
194
201
  name: prompt.name,
195
202
  title: prompt.title,
196
- filename: '',
203
+ filename: prompt.filename,
197
204
  method: 'GET',
198
205
  path: prompt.path,
199
206
  prompt: prompt.description,
@@ -12,13 +12,21 @@ import { join } from 'path';
12
12
  export class UIServer {
13
13
  private readonly systemId: string;
14
14
 
15
- private port: number = 50000;
16
15
  private server: Server | undefined;
16
+ private port: number = 50000;
17
17
 
18
18
  constructor(systemId: string) {
19
19
  this.systemId = systemId;
20
20
  }
21
21
 
22
+ public isRunning() {
23
+ return !!this.server;
24
+ }
25
+
26
+ public getUrl() {
27
+ return `http://localhost:${this.port}`;
28
+ }
29
+
22
30
  public async start() {
23
31
  const app = express();
24
32
  app.get('/_reset', (req: Request, res: Response) => {
@@ -35,7 +35,15 @@ import {
35
35
  import { StormCodegen } from './codegen';
36
36
  import { assetManager } from '../assetManager';
37
37
  import uuid from 'node-uuid';
38
- import { getSystemBaseDir, readPageFromDisk, resolveReadPath, SystemIdHeader, writeAssetToDisk, writeImageToDisk, writePageToDisk } from './page-utils';
38
+ import {
39
+ getSystemBaseDir,
40
+ readPageFromDisk,
41
+ resolveReadPath,
42
+ SystemIdHeader,
43
+ writeAssetToDisk,
44
+ writeImageToDisk,
45
+ writePageToDisk,
46
+ } from './page-utils';
39
47
  import { UIServer } from './UIServer';
40
48
  import { randomUUID } from 'crypto';
41
49
  import { ImagePrompt, PageQueue } from './PageGenerator';
@@ -69,7 +77,7 @@ function convertPageEvent(screenData: StormEvent, innerConversationId: string, m
69
77
  description: screenData.payload.description,
70
78
  prompt: screenData.payload.prompt,
71
79
  path: screenData.payload.path,
72
- url: server && screenData.payload.content ? server.resolveUrl(screenData) : '',
80
+ url: screenData.payload.content ? screenData.payload.path : '',
73
81
  method: screenData.payload.method,
74
82
  conversationId: innerConversationId,
75
83
  },
@@ -79,8 +87,19 @@ function convertPageEvent(screenData: StormEvent, innerConversationId: string, m
79
87
  return screenData;
80
88
  }
81
89
 
82
- router.all('/ui/:systemId/serve/:method/*', async (req: KapetaBodyRequest, res: Response) => {
83
- readPageFromDisk(req.params.systemId, req.params[0], req.params.method, res);
90
+ router.post('/ui/serve/:systemId', async (req: KapetaBodyRequest, res: Response) => {
91
+ const systemId = req.params.systemId as string | undefined;
92
+ if (!systemId) {
93
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
94
+ return;
95
+ }
96
+
97
+ const svr = (UI_SERVERS[systemId] = UI_SERVERS[systemId] || new UIServer(systemId));
98
+ if (!svr.isRunning()) {
99
+ await UI_SERVERS[systemId].start();
100
+ }
101
+
102
+ res.status(200).send({ status: 'running', url: svr.getUrl() });
84
103
  });
85
104
 
86
105
  router.post('/ui/create-system/:systemId', async (req: KapetaBodyRequest, res: Response) => {
@@ -100,7 +119,20 @@ router.post('/ui/create-system/:systemId', async (req: KapetaBodyRequest, res: R
100
119
  return;
101
120
  });
102
121
 
122
+ router.delete('/ui/serve/:systemId', async (req: KapetaBodyRequest, res: Response) => {
123
+ const systemId = req.params.systemId as string | undefined;
124
+ if (!systemId) {
125
+ res.status(404).send({ error: 'Missing "systemId" in URL' });
126
+ return;
127
+ }
103
128
 
129
+ const server = UI_SERVERS[systemId];
130
+ if (server) {
131
+ server.close();
132
+ delete UI_SERVERS[systemId];
133
+ }
134
+ res.status(200).json({ status: 'ok' });
135
+ });
104
136
 
105
137
  router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
106
138
  try {
@@ -152,21 +184,6 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
152
184
  }
153
185
  });
154
186
 
155
- router.delete('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
156
- const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
157
- if (!conversationId) {
158
- res.status(400).send('Missing conversation id');
159
- return;
160
- }
161
-
162
- const server = UI_SERVERS[conversationId];
163
- if (server) {
164
- server.close();
165
- delete UI_SERVERS[conversationId];
166
- }
167
- res.status(200).json({ status: 'ok' });
168
- });
169
-
170
187
  router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Response) => {
171
188
  const handle = req.params.handle as string;
172
189
  try {