@kapeta/local-cluster-service 0.70.2 → 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 +18 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +3 -20
- package/dist/cjs/src/storm/PageGenerator.js +95 -126
- package/dist/cjs/src/storm/page-utils.d.ts +1 -0
- package/dist/cjs/src/storm/page-utils.js +6 -4
- 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 +95 -126
- package/dist/esm/src/storm/page-utils.d.ts +1 -0
- package/dist/esm/src/storm/page-utils.js +6 -4
- 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 +106 -148
- package/src/storm/page-utils.ts +5 -4
- 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,101 +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: reference.url,
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
title: prompt.title,
|
163
|
-
filename: prompt.filename,
|
164
|
-
method: 'GET',
|
165
|
-
path: prompt.path,
|
166
|
-
prompt: prompt.description,
|
167
|
-
conversationId: '',
|
168
|
-
content: '',
|
169
|
-
description: prompt.description,
|
170
|
-
},
|
134
|
+
}
|
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,
|
171
148
|
});
|
149
|
+
break;
|
172
150
|
}
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
+
}
|
183
183
|
}
|
184
184
|
cancel() {
|
185
|
-
this.queue.
|
186
|
-
this.eventQueue.
|
185
|
+
this.queue.clear();
|
186
|
+
this.eventQueue.clear();
|
187
187
|
}
|
188
188
|
async wait() {
|
189
|
-
while (
|
190
|
-
await this.eventQueue.
|
191
|
-
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();
|
192
192
|
}
|
193
193
|
}
|
194
|
-
get length() {
|
195
|
-
return this.queue.length + this.eventQueue.length;
|
196
|
-
}
|
197
194
|
async addImagePrompt(prompt) {
|
198
195
|
if (this.images.has(prompt.url)) {
|
199
196
|
//console.log('Ignoring duplicate image prompt', prompt);
|
@@ -210,47 +207,19 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
210
207
|
});
|
211
208
|
await result.waitForDone();
|
212
209
|
}
|
213
|
-
|
214
|
-
exports.PageQueue = PageQueue;
|
215
|
-
class PageGenerator extends node_events_1.EventEmitter {
|
216
|
-
eventQueue;
|
217
|
-
conversationId;
|
218
|
-
prompt;
|
219
|
-
constructor(prompt, conversationId = node_uuid_1.default.v4()) {
|
220
|
-
super();
|
221
|
-
this.conversationId = conversationId;
|
222
|
-
this.prompt = prompt;
|
223
|
-
this.eventQueue = new PromiseQueue_1.PromiseQueue(Number.MAX_VALUE);
|
224
|
-
}
|
225
|
-
on(event, listener) {
|
226
|
-
return super.on(event, (...args) => {
|
227
|
-
void this.eventQueue.add(async () => listener(...args));
|
228
|
-
});
|
229
|
-
}
|
230
|
-
emit(eventName, ...args) {
|
231
|
-
return super.emit(eventName, ...args);
|
232
|
-
}
|
233
|
-
async generate() {
|
210
|
+
async generate(prompt, conversationId) {
|
234
211
|
const promises = [];
|
235
|
-
const screenStream = await stormClient_1.stormClient.createUIPage(
|
212
|
+
const screenStream = await stormClient_1.stormClient.createUIPage(prompt, conversationId);
|
236
213
|
screenStream.on('data', (event) => {
|
237
214
|
if (event.type === 'PAGE') {
|
238
|
-
event.payload.conversationId =
|
239
|
-
promises.push((
|
240
|
-
const references = await this.resolveReferences(event.payload.content);
|
241
|
-
//console.log('Resolved references for page', references, event.payload);
|
242
|
-
this.emit('page_refs', {
|
243
|
-
event,
|
244
|
-
references,
|
245
|
-
});
|
246
|
-
})());
|
215
|
+
event.payload.conversationId = conversationId;
|
216
|
+
promises.push(this.processPageEventWithReferences(event));
|
247
217
|
return;
|
248
218
|
}
|
249
219
|
this.emit('event', event);
|
250
220
|
});
|
251
221
|
await screenStream.waitForDone();
|
252
222
|
await Promise.all(promises);
|
253
|
-
await this.eventQueue.wait();
|
254
223
|
}
|
255
224
|
async resolveReferences(content) {
|
256
225
|
const referenceStream = await stormClient_1.stormClient.classifyUIReferences(content);
|
@@ -266,4 +235,4 @@ class PageGenerator extends node_events_1.EventEmitter {
|
|
266
235
|
return references;
|
267
236
|
}
|
268
237
|
}
|
269
|
-
exports.
|
238
|
+
exports.PageQueue = PageQueue;
|
@@ -7,6 +7,7 @@ import { Response } from 'express';
|
|
7
7
|
import { ConversationItem } from './stream';
|
8
8
|
import { ImagePrompt } from './PageGenerator';
|
9
9
|
export declare const SystemIdHeader = "System-Id";
|
10
|
+
export declare function normalizePath(path: string): string;
|
10
11
|
export declare function writePageToDisk(systemId: string, event: StormEventPage): Promise<{
|
11
12
|
path: string;
|
12
13
|
}>;
|
@@ -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.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.getSystemBaseDir = exports.hasPageOnDisk = exports.writeImageToDisk = exports.writeAssetToDisk = exports.writePageToDisk = exports.SystemIdHeader = void 0;
|
6
|
+
exports.writeConversationToFile = exports.readConversationFromFile = exports.readPageFromDisk = exports.readPageFromDiskAsString = exports.resolveReadPath = exports.getSystemBaseDir = exports.hasPageOnDisk = exports.writeImageToDisk = exports.writeAssetToDisk = exports.writePageToDisk = exports.normalizePath = exports.SystemIdHeader = void 0;
|
7
7
|
const node_os_1 = __importDefault(require("node:os"));
|
8
8
|
const path_1 = __importDefault(require("path"));
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
@@ -14,6 +14,7 @@ function normalizePath(path) {
|
|
14
14
|
.replace(/:[a-z][a-z_]*\b/gi, '*')
|
15
15
|
.replace(/\{[a-z-.]+}/gi, '*');
|
16
16
|
}
|
17
|
+
exports.normalizePath = normalizePath;
|
17
18
|
async function writePageToDisk(systemId, event) {
|
18
19
|
const baseDir = getSystemBaseDir(systemId);
|
19
20
|
const filePath = getFilePath(event.payload.method);
|
@@ -62,7 +63,8 @@ function getSystemBaseDir(systemId) {
|
|
62
63
|
}
|
63
64
|
exports.getSystemBaseDir = getSystemBaseDir;
|
64
65
|
function getFilePath(method) {
|
65
|
-
|
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');
|
66
68
|
}
|
67
69
|
function resolveReadPath(systemId, path, method) {
|
68
70
|
const baseDir = getSystemBaseDir(systemId);
|
@@ -83,7 +85,7 @@ function resolveReadPath(systemId, path, method) {
|
|
83
85
|
if (fs_extra_1.default.existsSync(fullPath)) {
|
84
86
|
return fullPath;
|
85
87
|
}
|
86
|
-
const parts = path.split(
|
88
|
+
const parts = path.split('/');
|
87
89
|
let currentPath = '';
|
88
90
|
for (let part of parts) {
|
89
91
|
const thisPath = path_1.default.join(currentPath, part);
|
@@ -184,7 +186,7 @@ function getFallbackHtml(path, method) {
|
|
184
186
|
<script>
|
185
187
|
const checkInterval = 3000;
|
186
188
|
function checkPageReady() {
|
187
|
-
fetch('
|
189
|
+
fetch('/${path}', { method: 'HEAD' })
|
188
190
|
.then(response => {
|
189
191
|
if (response.status === 200) {
|
190
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",
|