@kapeta/local-cluster-service 0.70.3 → 0.70.4

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.
@@ -7,11 +7,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.PageGenerator = exports.PageQueue = void 0;
10
+ exports.PageQueue = void 0;
11
11
  const node_uuid_1 = __importDefault(require("node-uuid"));
12
12
  const stormClient_1 = require("./stormClient");
13
13
  const node_events_1 = require("node:events");
14
- const PromiseQueue_1 = require("./PromiseQueue");
14
+ const p_queue_1 = __importDefault(require("p-queue"));
15
15
  const page_utils_1 = require("./page-utils");
16
16
  class PageQueue extends node_events_1.EventEmitter {
17
17
  queue;
@@ -27,8 +27,8 @@ class PageQueue extends node_events_1.EventEmitter {
27
27
  super();
28
28
  this.systemId = systemId;
29
29
  this.systemPrompt = systemPrompt;
30
- this.queue = new PromiseQueue_1.PromiseQueue(concurrency);
31
- this.eventQueue = new PromiseQueue_1.PromiseQueue(Number.MAX_VALUE);
30
+ this.queue = new p_queue_1.default({ concurrency });
31
+ this.eventQueue = new p_queue_1.default({ concurrency: Number.MAX_VALUE });
32
32
  }
33
33
  on(event, listener) {
34
34
  return super.on(event, (...args) => {
@@ -67,10 +67,9 @@ class PageQueue extends node_events_1.EventEmitter {
67
67
  prompt: this.wrapPagePrompt(initialPrompt.path, initialPrompt.prompt),
68
68
  theme: this.theme,
69
69
  };
70
- const generator = new PageGenerator(prompt, conversationId);
71
- this.references.set(prompt.path, generator);
70
+ this.references.set(prompt.path, true);
72
71
  this.pages.set(prompt.path, prompt.description);
73
- return this.addPageGenerator(generator);
72
+ return this.queue.add(() => this.generate(prompt, conversationId));
74
73
  }
75
74
  getPrefix() {
76
75
  let promptPrefix = '';
@@ -99,102 +98,99 @@ class PageQueue extends node_events_1.EventEmitter {
99
98
  }
100
99
  return promptPrefix + prompt + promptPostfix;
101
100
  }
102
- async addPageGenerator(generator) {
103
- generator.on('event', (event) => this.emit('event', event));
104
- generator.on('page_refs', async ({ event, references }) => {
105
- try {
106
- const matchesExistingPages = (url) => {
107
- return [...this.pages.keys()].some((path) => new RegExp(`^${path.replaceAll('/*', '/[^/]+')}$`).test(url.replace(/\?.*$/gi, '')));
108
- };
109
- const initialPrompts = [];
110
- const resourcePromises = references.map(async (reference) => {
111
- if (reference.url.startsWith('#') ||
112
- reference.url.startsWith('javascript:') ||
113
- reference.url.startsWith('http://') ||
114
- reference.url.startsWith('https://')) {
115
- return;
116
- }
117
- switch (reference.type) {
118
- case 'image':
119
- await this.addImagePrompt({
120
- ...reference,
121
- content: event.payload.content,
122
- });
123
- break;
124
- case 'css':
125
- case 'javascript':
126
- //console.log('Ignoring reference', reference);
127
- break;
128
- case 'html': {
129
- const normalizedPath = (0, page_utils_1.normalizePath)(reference.url);
130
- if (matchesExistingPages(normalizedPath)) {
131
- break;
132
- }
133
- this.pages.set(normalizedPath, reference.description);
134
- initialPrompts.push({
135
- name: reference.name,
136
- title: reference.title,
137
- path: normalizedPath,
138
- method: 'GET',
139
- storage_prefix: this.systemId + '_',
140
- prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
141
- `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
142
- description: reference.description,
143
- // Only used for matching
144
- filename: reference.name + '.ref.html',
145
- theme: this.theme,
146
- });
101
+ async processPageEventWithReferences(event) {
102
+ try {
103
+ console.log('Processing page event', event.payload.path);
104
+ const references = await this.resolveReferences(event.payload.content);
105
+ const matchesExistingPages = (url) => {
106
+ return [...this.pages.keys()].some((path) => new RegExp(`^${path.replaceAll('/*', '/[^/]+')}$`).test(url));
107
+ };
108
+ const initialPrompts = [];
109
+ const resourcePromises = references.map(async (reference) => {
110
+ if (reference.url.startsWith('#') ||
111
+ reference.url.startsWith('javascript:') ||
112
+ reference.url.startsWith('http://') ||
113
+ reference.url.startsWith('https://') ||
114
+ reference.url.startsWith('data:') ||
115
+ reference.url.startsWith('blob:') ||
116
+ reference.url.startsWith('mailto:')) {
117
+ return;
118
+ }
119
+ switch (reference.type) {
120
+ case 'image':
121
+ await this.addImagePrompt({
122
+ ...reference,
123
+ content: event.payload.content,
124
+ });
125
+ break;
126
+ case 'css':
127
+ case 'javascript':
128
+ //console.log('Ignoring reference', reference);
129
+ break;
130
+ case 'html': {
131
+ const normalizedPath = (0, page_utils_1.normalizePath)(reference.url);
132
+ if (matchesExistingPages(normalizedPath)) {
147
133
  break;
148
134
  }
149
- }
150
- });
151
- // Wait for resources to be generated
152
- await Promise.allSettled(resourcePromises);
153
- this.emit('page', event);
154
- // Emit any new pages after the current page to increase responsiveness
155
- const newPages = initialPrompts.map((prompt) => {
156
- if (!this.hasPrompt(prompt.path)) {
157
- this.emit('page', {
158
- type: 'PAGE',
159
- reason: 'reference',
160
- created: Date.now(),
161
- payload: {
162
- name: prompt.name,
163
- title: prompt.title,
164
- filename: prompt.filename,
165
- method: 'GET',
166
- path: prompt.path,
167
- prompt: prompt.description,
168
- conversationId: '',
169
- content: '',
170
- description: prompt.description,
171
- },
135
+ this.pages.set(normalizedPath, reference.description);
136
+ initialPrompts.push({
137
+ name: reference.name,
138
+ title: reference.title,
139
+ path: normalizedPath,
140
+ method: 'GET',
141
+ storage_prefix: this.systemId + '_',
142
+ prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
143
+ `The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
144
+ description: reference.description,
145
+ // Only used for matching
146
+ filename: reference.name + '.ref.html',
147
+ theme: this.theme,
172
148
  });
149
+ break;
173
150
  }
174
- return this.addPrompt(prompt);
175
- });
176
- await Promise.allSettled(newPages);
177
- }
178
- catch (e) {
179
- console.error('Failed to process event', e);
180
- throw e;
181
- }
182
- });
183
- return this.queue.add(() => generator.generate());
151
+ }
152
+ });
153
+ // Wait for resources to be generated
154
+ await Promise.allSettled(resourcePromises);
155
+ this.emit('page', event);
156
+ // Emit any new pages after the current page to increase responsiveness
157
+ void initialPrompts.map((prompt) => {
158
+ if (!this.hasPrompt(prompt.path)) {
159
+ this.emit('page', {
160
+ type: 'PAGE',
161
+ reason: 'reference',
162
+ created: Date.now(),
163
+ payload: {
164
+ name: prompt.name,
165
+ title: prompt.title,
166
+ filename: prompt.filename,
167
+ method: 'GET',
168
+ path: prompt.path,
169
+ prompt: prompt.description,
170
+ conversationId: '',
171
+ content: '',
172
+ description: prompt.description,
173
+ },
174
+ });
175
+ }
176
+ return this.addPrompt(prompt);
177
+ });
178
+ }
179
+ catch (e) {
180
+ console.error('Failed to process event', e);
181
+ throw e;
182
+ }
184
183
  }
185
184
  cancel() {
186
- this.queue.cancel();
187
- this.eventQueue.cancel();
185
+ this.queue.clear();
186
+ this.eventQueue.clear();
188
187
  }
189
188
  async wait() {
190
- while (!this.eventQueue.empty || !this.queue.empty) {
191
- await this.eventQueue.wait();
192
- await this.queue.wait();
189
+ while (this.eventQueue.size || this.eventQueue.pending || this.queue.size || this.queue.pending) {
190
+ await this.eventQueue.onIdle();
191
+ await this.queue.onIdle();
193
192
  }
194
193
  }
195
- get length() {
196
- return this.queue.length + this.eventQueue.length;
197
- }
198
194
  async addImagePrompt(prompt) {
199
195
  if (this.images.has(prompt.url)) {
200
196
  //console.log('Ignoring duplicate image prompt', prompt);
@@ -211,47 +207,19 @@ class PageQueue extends node_events_1.EventEmitter {
211
207
  });
212
208
  await result.waitForDone();
213
209
  }
214
- }
215
- exports.PageQueue = PageQueue;
216
- class PageGenerator extends node_events_1.EventEmitter {
217
- eventQueue;
218
- conversationId;
219
- prompt;
220
- constructor(prompt, conversationId = node_uuid_1.default.v4()) {
221
- super();
222
- this.conversationId = conversationId;
223
- this.prompt = prompt;
224
- this.eventQueue = new PromiseQueue_1.PromiseQueue(Number.MAX_VALUE);
225
- }
226
- on(event, listener) {
227
- return super.on(event, (...args) => {
228
- void this.eventQueue.add(async () => listener(...args));
229
- });
230
- }
231
- emit(eventName, ...args) {
232
- return super.emit(eventName, ...args);
233
- }
234
- async generate() {
210
+ async generate(prompt, conversationId) {
235
211
  const promises = [];
236
- const screenStream = await stormClient_1.stormClient.createUIPage(this.prompt, this.conversationId);
212
+ const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
237
213
  screenStream.on('data', (event) => {
238
214
  if (event.type === 'PAGE') {
239
- event.payload.conversationId = this.conversationId;
240
- promises.push((async () => {
241
- const references = await this.resolveReferences(event.payload.content);
242
- //console.log('Resolved references for page', references, event.payload);
243
- this.emit('page_refs', {
244
- event,
245
- references,
246
- });
247
- })());
215
+ event.payload.conversationId = conversationId;
216
+ promises.push(this.processPageEventWithReferences(event));
248
217
  return;
249
218
  }
250
219
  this.emit('event', event);
251
220
  });
252
221
  await screenStream.waitForDone();
253
222
  await Promise.all(promises);
254
- await this.eventQueue.wait();
255
223
  }
256
224
  async resolveReferences(content) {
257
225
  const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
@@ -267,4 +235,4 @@ class PageGenerator extends node_events_1.EventEmitter {
267
235
  return references;
268
236
  }
269
237
  }
270
- exports.PageGenerator = PageGenerator;
238
+ exports.PageQueue = PageQueue;
@@ -63,7 +63,8 @@ function getSystemBaseDir(systemId) {
63
63
  }
64
64
  exports.getSystemBaseDir = getSystemBaseDir;
65
65
  function getFilePath(method) {
66
- return path_1.default.join(method.toLowerCase(), 'index.html');
66
+ // For HEAD requests, we assume we're serving looking for a GET resource
67
+ return path_1.default.join(method === 'HEAD' ? 'get' : method.toLowerCase(), 'index.html');
67
68
  }
68
69
  function resolveReadPath(systemId, path, method) {
69
70
  const baseDir = getSystemBaseDir(systemId);
@@ -84,7 +85,7 @@ function resolveReadPath(systemId, path, method) {
84
85
  if (fs_extra_1.default.existsSync(fullPath)) {
85
86
  return fullPath;
86
87
  }
87
- const parts = path.split(/\*/g);
88
+ const parts = path.split('/');
88
89
  let currentPath = '';
89
90
  for (let part of parts) {
90
91
  const thisPath = path_1.default.join(currentPath, part);
@@ -185,7 +186,7 @@ function getFallbackHtml(path, method) {
185
186
  <script>
186
187
  const checkInterval = 3000;
187
188
  function checkPageReady() {
188
- fetch('${path}', { method: 'HEAD' })
189
+ fetch('/${path}', { method: 'HEAD' })
189
190
  .then(response => {
190
191
  if (response.status === 200) {
191
192
  // The page is ready, reload to fetch it
@@ -24,7 +24,6 @@ const page_utils_1 = require("./page-utils");
24
24
  const UIServer_1 = require("./UIServer");
25
25
  const crypto_1 = require("crypto");
26
26
  const PageGenerator_1 = require("./PageGenerator");
27
- const PromiseQueue_1 = require("./PromiseQueue");
28
27
  const utils_1 = require("./utils");
29
28
  const UI_SERVERS = {};
30
29
  const router = (0, express_promise_router_1.default)();
@@ -154,7 +153,7 @@ router.post('/:handle/ui/iterative', async (req, res) => {
154
153
  const promises = {};
155
154
  const pageEventPromises = [];
156
155
  const systemId = landingPagesStream.getConversationId();
157
- const systemPrompt = (0, PromiseQueue_1.createFuture)();
156
+ const systemPrompt = (0, utils_1.createFuture)();
158
157
  if (aiRequest.skipImprovement) {
159
158
  systemPrompt.resolve(aiRequest.prompt);
160
159
  }
@@ -196,9 +195,7 @@ router.post('/:handle/ui/iterative', async (req, res) => {
196
195
  UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
197
196
  await UI_SERVERS[systemId].start();
198
197
  waitForStormStream(landingPagesStream).then(() => {
199
- if (!systemPrompt.isResolved()) {
200
- systemPrompt.resolve(aiRequest.prompt);
201
- }
198
+ systemPrompt.resolve(aiRequest.prompt);
202
199
  });
203
200
  const pageQueue = new PageGenerator_1.PageQueue(systemId, await systemPrompt.promise, 5);
204
201
  onRequestAborted(req, res, () => {
@@ -1 +1,6 @@
1
1
  export declare function copyDirectory(src: string, dest: string, modifyHtml: (fileName: string, content: string) => Promise<string>): Promise<void>;
2
+ export declare function createFuture<T = void>(): {
3
+ promise: Promise<T>;
4
+ resolve: (value: T | PromiseLike<T>) => void;
5
+ reject: (reason?: any) => void;
6
+ };
@@ -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.copyDirectory = void 0;
6
+ exports.createFuture = exports.copyDirectory = void 0;
7
7
  /**
8
8
  * Copyright 2023 Kapeta Inc.
9
9
  * SPDX-License-Identifier: BUSL-1.1
@@ -29,3 +29,17 @@ async function copyDirectory(src, dest, modifyHtml) {
29
29
  }
30
30
  }
31
31
  exports.copyDirectory = copyDirectory;
32
+ function createFuture() {
33
+ let resolve = () => { };
34
+ let reject = () => { };
35
+ const promise = new Promise((res, rej) => {
36
+ resolve = res;
37
+ reject = rej;
38
+ });
39
+ return {
40
+ promise,
41
+ resolve,
42
+ reject,
43
+ };
44
+ }
45
+ exports.createFuture = createFuture;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.70.3",
3
+ "version": "0.70.4",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -79,6 +79,7 @@
79
79
  "node-cache": "^5.1.2",
80
80
  "node-fetch": "^3.3.2",
81
81
  "node-uuid": "^1.4.8",
82
+ "p-queue": "^6.6.2",
82
83
  "parse-data-uri": "^0.2.0",
83
84
  "qs": "^6.11.2",
84
85
  "request": "2.88.2",