@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.
- package/CHANGELOG.md +10 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +3 -20
- package/dist/cjs/src/storm/PageGenerator.js +94 -126
- package/dist/cjs/src/storm/page-utils.js +4 -3
- package/dist/cjs/src/storm/routes.js +2 -5
- package/dist/cjs/src/storm/utils.d.ts +5 -0
- package/dist/cjs/src/storm/utils.js +15 -1
- package/dist/esm/src/storm/PageGenerator.d.ts +3 -20
- package/dist/esm/src/storm/PageGenerator.js +94 -126
- package/dist/esm/src/storm/page-utils.js +4 -3
- package/dist/esm/src/storm/routes.js +2 -5
- package/dist/esm/src/storm/utils.d.ts +5 -0
- package/dist/esm/src/storm/utils.js +15 -1
- package/package.json +2 -1
- package/src/storm/PageGenerator.ts +104 -149
- package/src/storm/page-utils.ts +4 -3
- package/src/storm/routes.ts +2 -5
- package/src/storm/utils.ts +19 -1
- package/dist/cjs/src/storm/PromiseQueue.d.ts +0 -25
- package/dist/cjs/src/storm/PromiseQueue.js +0 -97
- package/dist/esm/src/storm/PromiseQueue.d.ts +0 -25
- package/dist/esm/src/storm/PromiseQueue.js +0 -97
- package/src/storm/PromiseQueue.ts +0 -129
@@ -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.
|
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
|
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
|
31
|
-
this.eventQueue = new
|
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
|
-
|
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.
|
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
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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.
|
187
|
-
this.eventQueue.
|
185
|
+
this.queue.clear();
|
186
|
+
this.eventQueue.clear();
|
188
187
|
}
|
189
188
|
async wait() {
|
190
|
-
while (
|
191
|
-
await this.eventQueue.
|
192
|
-
await this.queue.
|
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(
|
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 =
|
240
|
-
promises.push((
|
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.
|
238
|
+
exports.PageQueue = PageQueue;
|
@@ -63,7 +63,8 @@ function getSystemBaseDir(systemId) {
|
|
63
63
|
}
|
64
64
|
exports.getSystemBaseDir = getSystemBaseDir;
|
65
65
|
function getFilePath(method) {
|
66
|
-
|
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(
|
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('
|
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,
|
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
|
-
|
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
|
+
"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",
|