@kapeta/local-cluster-service 0.76.4 → 0.77.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 +14 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +5 -3
- package/dist/cjs/src/storm/PageGenerator.js +27 -30
- package/dist/cjs/src/storm/routes.js +8 -1
- package/dist/cjs/src/storm/stormClient.d.ts +16 -0
- package/dist/cjs/src/storm/stormClient.js +15 -3
- package/dist/esm/src/storm/PageGenerator.d.ts +5 -3
- package/dist/esm/src/storm/PageGenerator.js +27 -30
- package/dist/esm/src/storm/routes.js +8 -1
- package/dist/esm/src/storm/stormClient.d.ts +16 -0
- package/dist/esm/src/storm/stormClient.js +15 -3
- package/package.json +1 -1
- package/src/storm/PageGenerator.ts +33 -34
- package/src/storm/routes.ts +8 -1
- package/src/storm/stormClient.ts +35 -3
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.77.0](https://github.com/kapetacom/local-cluster-service/compare/v0.76.5...v0.77.0) (2024-10-04)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Enable page agent to distinguish beween global and local edits ([1e24653](https://github.com/kapetacom/local-cluster-service/commit/1e246536272f9a141acf2233eb96fdec2a30c709))
|
7
|
+
|
8
|
+
## [0.76.5](https://github.com/kapetacom/local-cluster-service/compare/v0.76.4...v0.76.5) (2024-10-03)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Poor mans security ([#268](https://github.com/kapetacom/local-cluster-service/issues/268)) ([dcc85ec](https://github.com/kapetacom/local-cluster-service/commit/dcc85ecdf9971c3033eaf43e65a9b262de269821))
|
14
|
+
|
1
15
|
## [0.76.4](https://github.com/kapetacom/local-cluster-service/compare/v0.76.3...v0.76.4) (2024-10-01)
|
2
16
|
|
3
17
|
|
@@ -41,9 +41,11 @@ export declare class PageQueue extends EventEmitter {
|
|
41
41
|
addUiShell(uiShell: UIShell): void;
|
42
42
|
setUiTheme(theme: string): void;
|
43
43
|
private hasPrompt;
|
44
|
-
addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean): Promise<void>;
|
45
|
-
|
46
|
-
|
44
|
+
addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean, globalEdit?: boolean): Promise<void>;
|
45
|
+
/**
|
46
|
+
* Get the existing pages
|
47
|
+
*/
|
48
|
+
private getExistingPages;
|
47
49
|
private processPageEventWithReferences;
|
48
50
|
cancel(): void;
|
49
51
|
wait(): Promise<void>;
|
@@ -87,7 +87,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
87
87
|
}
|
88
88
|
return false;
|
89
89
|
}
|
90
|
-
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false
|
90
|
+
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false,
|
91
|
+
// Set globalEdit to true when the same prompt is being sent to multiple pages
|
92
|
+
globalEdit = false) {
|
91
93
|
if (!overwrite && this.hasPrompt(initialPrompt.path)) {
|
92
94
|
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
93
95
|
return Promise.resolve();
|
@@ -96,39 +98,26 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
96
98
|
const prompt = {
|
97
99
|
...initialPrompt,
|
98
100
|
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
99
|
-
prompt:
|
101
|
+
prompt: initialPrompt.prompt,
|
100
102
|
theme: this.theme,
|
103
|
+
global_edit: globalEdit,
|
104
|
+
system_prompt: this.systemPrompt,
|
105
|
+
existing_pages: this.getExistingPages(initialPrompt.path),
|
106
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({ path, description })),
|
101
107
|
};
|
102
108
|
this.references.set(prompt.path, true);
|
103
109
|
this.pages.set(prompt.path, prompt.description);
|
104
110
|
return this.queue.add(() => this.generate(prompt, conversationId));
|
105
111
|
}
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
let promptPostfix = '';
|
116
|
-
if (this.pages.size > 0) {
|
117
|
-
promptPostfix = `\nThe following pages are already implemented:\n`;
|
118
|
-
this.pages.forEach((description, path) => {
|
119
|
-
if (pagePath === path) {
|
120
|
-
return;
|
121
|
-
}
|
122
|
-
promptPostfix += `- PAGE: '${path}' -> ${description}.\n`;
|
123
|
-
});
|
124
|
-
}
|
125
|
-
if (this.images.size > 0) {
|
126
|
-
promptPostfix += `\nThe following images already exist:\n`;
|
127
|
-
this.images.forEach((description, path) => {
|
128
|
-
promptPostfix += `- IMAGE: '${path}' -> ${description}.\n`;
|
129
|
-
});
|
130
|
-
}
|
131
|
-
return promptPrefix + prompt + promptPostfix;
|
112
|
+
/**
|
113
|
+
* Get the existing pages
|
114
|
+
*/
|
115
|
+
getExistingPages(excludePath) {
|
116
|
+
return (Object.entries(this.pages)
|
117
|
+
// Possibly exclude one page. This is useful when we don't want to include the page
|
118
|
+
// we're currently editing.
|
119
|
+
.filter(([path]) => (excludePath ? path !== excludePath : true))
|
120
|
+
.map(([path, description]) => ({ path, description })));
|
132
121
|
}
|
133
122
|
async processPageEventWithReferences(event) {
|
134
123
|
try {
|
@@ -176,9 +165,17 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
176
165
|
path: normalizedPath,
|
177
166
|
method: 'GET',
|
178
167
|
storage_prefix: this.systemId + '_',
|
179
|
-
prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}
|
180
|
-
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
168
|
+
prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}`,
|
181
169
|
description: reference.description,
|
170
|
+
referenced_from: {
|
171
|
+
path: event.payload.path,
|
172
|
+
content: event.payload.content,
|
173
|
+
},
|
174
|
+
existing_pages: this.getExistingPages(),
|
175
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({
|
176
|
+
path,
|
177
|
+
description,
|
178
|
+
})),
|
182
179
|
// Only used for matching
|
183
180
|
filename: reference.name + '.ref.html',
|
184
181
|
theme: this.theme,
|
@@ -226,6 +226,9 @@ router.delete('/ui/serve/:systemId', async (req, res) => {
|
|
226
226
|
}
|
227
227
|
res.status(200).json({ status: 'ok' });
|
228
228
|
});
|
229
|
+
/**
|
230
|
+
* Edit a single page
|
231
|
+
*/
|
229
232
|
router.post('/:handle/ui/screen', async (req, res) => {
|
230
233
|
try {
|
231
234
|
const handle = req.params.handle;
|
@@ -540,6 +543,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
540
543
|
}
|
541
544
|
}
|
542
545
|
});
|
546
|
+
/**
|
547
|
+
* Edit all pages
|
548
|
+
*/
|
543
549
|
router.post('/:handle/ui/edit', async (req, res) => {
|
544
550
|
try {
|
545
551
|
const handle = req.params.handle;
|
@@ -586,7 +592,8 @@ router.post('/:handle/ui/edit', async (req, res) => {
|
|
586
592
|
filename: page.filename,
|
587
593
|
prompt: aiRequest.prompt.prompt.prompt,
|
588
594
|
storage_prefix: storagePrefix,
|
589
|
-
}, page.conversationId, true
|
595
|
+
}, page.conversationId, true, true // this is a global edit
|
596
|
+
);
|
590
597
|
}
|
591
598
|
}));
|
592
599
|
await queue.wait();
|
@@ -28,6 +28,20 @@ export interface UIPagePrompt {
|
|
28
28
|
storage_prefix: string;
|
29
29
|
shell_page?: string;
|
30
30
|
theme?: string;
|
31
|
+
global_edit?: boolean;
|
32
|
+
system_prompt?: string;
|
33
|
+
existing_pages?: {
|
34
|
+
path: string;
|
35
|
+
description: string;
|
36
|
+
}[];
|
37
|
+
existing_images?: {
|
38
|
+
path: string;
|
39
|
+
description: string;
|
40
|
+
}[];
|
41
|
+
referenced_from?: {
|
42
|
+
path: string;
|
43
|
+
content: string;
|
44
|
+
};
|
31
45
|
}
|
32
46
|
export interface UIPageSamplePrompt extends UIPagePrompt {
|
33
47
|
variantId: string;
|
@@ -61,6 +75,7 @@ export declare class StormClient {
|
|
61
75
|
private readonly _baseUrl;
|
62
76
|
private readonly _systemId;
|
63
77
|
private readonly _handle;
|
78
|
+
private readonly _sharedSecret;
|
64
79
|
constructor(handle: string, systemId?: string);
|
65
80
|
private createOptions;
|
66
81
|
private send;
|
@@ -91,4 +106,5 @@ export declare class StormClient {
|
|
91
106
|
deleteUIPageConversation(conversationId: string): Promise<string>;
|
92
107
|
downloadSystem(handle: string, conversationId: string): Promise<Buffer>;
|
93
108
|
uploadSystem(handle: string, conversationId: string, buffer: Buffer): Promise<Response>;
|
109
|
+
private getSharedSecretHeader;
|
94
110
|
}
|
@@ -23,14 +23,17 @@ class StormClient {
|
|
23
23
|
_baseUrl;
|
24
24
|
_systemId;
|
25
25
|
_handle;
|
26
|
+
_sharedSecret;
|
26
27
|
constructor(handle, systemId) {
|
27
28
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
28
29
|
this._systemId = systemId || '';
|
29
30
|
this._handle = handle;
|
31
|
+
this._sharedSecret = process.env.SHARED_SECRET || '@keep-this-super-secret!';
|
30
32
|
}
|
31
33
|
async createOptions(path, method, body) {
|
32
34
|
const url = `${this._baseUrl}${path}`;
|
33
35
|
const headers = {
|
36
|
+
...this.getSharedSecretHeader(),
|
34
37
|
'Content-Type': 'application/json',
|
35
38
|
};
|
36
39
|
const api = new nodejs_api_client_1.KapetaAPI();
|
@@ -149,7 +152,7 @@ class StormClient {
|
|
149
152
|
async replaceMockWithAPICall(prompt) {
|
150
153
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
151
154
|
try {
|
152
|
-
const headers =
|
155
|
+
const headers = this.getSharedSecretHeader();
|
153
156
|
headers[exports.HandleHeader] = this._handle;
|
154
157
|
headers[exports.ConversationIdHeader] = this._systemId;
|
155
158
|
headers[exports.SystemIdHeader] = this._systemId;
|
@@ -176,12 +179,13 @@ class StormClient {
|
|
176
179
|
body: JSON.stringify({
|
177
180
|
pages: pages,
|
178
181
|
}),
|
182
|
+
headers: this.getSharedSecretHeader(),
|
179
183
|
});
|
180
184
|
return await response.text();
|
181
185
|
}
|
182
186
|
async createSimpleBackend(handle, systemId, input) {
|
183
187
|
const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
|
184
|
-
const headers =
|
188
|
+
const headers = this.getSharedSecretHeader();
|
185
189
|
headers[exports.HandleHeader] = this._handle;
|
186
190
|
headers[exports.ConversationIdHeader] = this._systemId;
|
187
191
|
headers[exports.SystemIdHeader] = this._systemId;
|
@@ -267,7 +271,9 @@ class StormClient {
|
|
267
271
|
}
|
268
272
|
async downloadSystem(handle, conversationId) {
|
269
273
|
const u = `${this._baseUrl}/v2/systems/${handle}/${conversationId}/download`;
|
270
|
-
const response = await fetch(u
|
274
|
+
const response = await fetch(u, {
|
275
|
+
headers: this.getSharedSecretHeader(),
|
276
|
+
});
|
271
277
|
if (!response.ok) {
|
272
278
|
throw new Error(`Failed to download system: ${response.status}`);
|
273
279
|
}
|
@@ -279,10 +285,16 @@ class StormClient {
|
|
279
285
|
method: 'PUT',
|
280
286
|
body: buffer,
|
281
287
|
headers: {
|
288
|
+
...this.getSharedSecretHeader(),
|
282
289
|
'content-type': 'application/zip',
|
283
290
|
},
|
284
291
|
});
|
285
292
|
return response;
|
286
293
|
}
|
294
|
+
getSharedSecretHeader() {
|
295
|
+
return {
|
296
|
+
SharedSecret: this._sharedSecret,
|
297
|
+
};
|
298
|
+
}
|
287
299
|
}
|
288
300
|
exports.StormClient = StormClient;
|
@@ -41,9 +41,11 @@ export declare class PageQueue extends EventEmitter {
|
|
41
41
|
addUiShell(uiShell: UIShell): void;
|
42
42
|
setUiTheme(theme: string): void;
|
43
43
|
private hasPrompt;
|
44
|
-
addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean): Promise<void>;
|
45
|
-
|
46
|
-
|
44
|
+
addPrompt(initialPrompt: InitialPrompt, conversationId?: string, overwrite?: boolean, globalEdit?: boolean): Promise<void>;
|
45
|
+
/**
|
46
|
+
* Get the existing pages
|
47
|
+
*/
|
48
|
+
private getExistingPages;
|
47
49
|
private processPageEventWithReferences;
|
48
50
|
cancel(): void;
|
49
51
|
wait(): Promise<void>;
|
@@ -87,7 +87,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
87
87
|
}
|
88
88
|
return false;
|
89
89
|
}
|
90
|
-
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false
|
90
|
+
addPrompt(initialPrompt, conversationId = node_uuid_1.default.v4(), overwrite = false,
|
91
|
+
// Set globalEdit to true when the same prompt is being sent to multiple pages
|
92
|
+
globalEdit = false) {
|
91
93
|
if (!overwrite && this.hasPrompt(initialPrompt.path)) {
|
92
94
|
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
93
95
|
return Promise.resolve();
|
@@ -96,39 +98,26 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
96
98
|
const prompt = {
|
97
99
|
...initialPrompt,
|
98
100
|
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
99
|
-
prompt:
|
101
|
+
prompt: initialPrompt.prompt,
|
100
102
|
theme: this.theme,
|
103
|
+
global_edit: globalEdit,
|
104
|
+
system_prompt: this.systemPrompt,
|
105
|
+
existing_pages: this.getExistingPages(initialPrompt.path),
|
106
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({ path, description })),
|
101
107
|
};
|
102
108
|
this.references.set(prompt.path, true);
|
103
109
|
this.pages.set(prompt.path, prompt.description);
|
104
110
|
return this.queue.add(() => this.generate(prompt, conversationId));
|
105
111
|
}
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
let promptPostfix = '';
|
116
|
-
if (this.pages.size > 0) {
|
117
|
-
promptPostfix = `\nThe following pages are already implemented:\n`;
|
118
|
-
this.pages.forEach((description, path) => {
|
119
|
-
if (pagePath === path) {
|
120
|
-
return;
|
121
|
-
}
|
122
|
-
promptPostfix += `- PAGE: '${path}' -> ${description}.\n`;
|
123
|
-
});
|
124
|
-
}
|
125
|
-
if (this.images.size > 0) {
|
126
|
-
promptPostfix += `\nThe following images already exist:\n`;
|
127
|
-
this.images.forEach((description, path) => {
|
128
|
-
promptPostfix += `- IMAGE: '${path}' -> ${description}.\n`;
|
129
|
-
});
|
130
|
-
}
|
131
|
-
return promptPrefix + prompt + promptPostfix;
|
112
|
+
/**
|
113
|
+
* Get the existing pages
|
114
|
+
*/
|
115
|
+
getExistingPages(excludePath) {
|
116
|
+
return (Object.entries(this.pages)
|
117
|
+
// Possibly exclude one page. This is useful when we don't want to include the page
|
118
|
+
// we're currently editing.
|
119
|
+
.filter(([path]) => (excludePath ? path !== excludePath : true))
|
120
|
+
.map(([path, description]) => ({ path, description })));
|
132
121
|
}
|
133
122
|
async processPageEventWithReferences(event) {
|
134
123
|
try {
|
@@ -176,9 +165,17 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
176
165
|
path: normalizedPath,
|
177
166
|
method: 'GET',
|
178
167
|
storage_prefix: this.systemId + '_',
|
179
|
-
prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}
|
180
|
-
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
168
|
+
prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}`,
|
181
169
|
description: reference.description,
|
170
|
+
referenced_from: {
|
171
|
+
path: event.payload.path,
|
172
|
+
content: event.payload.content,
|
173
|
+
},
|
174
|
+
existing_pages: this.getExistingPages(),
|
175
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({
|
176
|
+
path,
|
177
|
+
description,
|
178
|
+
})),
|
182
179
|
// Only used for matching
|
183
180
|
filename: reference.name + '.ref.html',
|
184
181
|
theme: this.theme,
|
@@ -226,6 +226,9 @@ router.delete('/ui/serve/:systemId', async (req, res) => {
|
|
226
226
|
}
|
227
227
|
res.status(200).json({ status: 'ok' });
|
228
228
|
});
|
229
|
+
/**
|
230
|
+
* Edit a single page
|
231
|
+
*/
|
229
232
|
router.post('/:handle/ui/screen', async (req, res) => {
|
230
233
|
try {
|
231
234
|
const handle = req.params.handle;
|
@@ -540,6 +543,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
540
543
|
}
|
541
544
|
}
|
542
545
|
});
|
546
|
+
/**
|
547
|
+
* Edit all pages
|
548
|
+
*/
|
543
549
|
router.post('/:handle/ui/edit', async (req, res) => {
|
544
550
|
try {
|
545
551
|
const handle = req.params.handle;
|
@@ -586,7 +592,8 @@ router.post('/:handle/ui/edit', async (req, res) => {
|
|
586
592
|
filename: page.filename,
|
587
593
|
prompt: aiRequest.prompt.prompt.prompt,
|
588
594
|
storage_prefix: storagePrefix,
|
589
|
-
}, page.conversationId, true
|
595
|
+
}, page.conversationId, true, true // this is a global edit
|
596
|
+
);
|
590
597
|
}
|
591
598
|
}));
|
592
599
|
await queue.wait();
|
@@ -28,6 +28,20 @@ export interface UIPagePrompt {
|
|
28
28
|
storage_prefix: string;
|
29
29
|
shell_page?: string;
|
30
30
|
theme?: string;
|
31
|
+
global_edit?: boolean;
|
32
|
+
system_prompt?: string;
|
33
|
+
existing_pages?: {
|
34
|
+
path: string;
|
35
|
+
description: string;
|
36
|
+
}[];
|
37
|
+
existing_images?: {
|
38
|
+
path: string;
|
39
|
+
description: string;
|
40
|
+
}[];
|
41
|
+
referenced_from?: {
|
42
|
+
path: string;
|
43
|
+
content: string;
|
44
|
+
};
|
31
45
|
}
|
32
46
|
export interface UIPageSamplePrompt extends UIPagePrompt {
|
33
47
|
variantId: string;
|
@@ -61,6 +75,7 @@ export declare class StormClient {
|
|
61
75
|
private readonly _baseUrl;
|
62
76
|
private readonly _systemId;
|
63
77
|
private readonly _handle;
|
78
|
+
private readonly _sharedSecret;
|
64
79
|
constructor(handle: string, systemId?: string);
|
65
80
|
private createOptions;
|
66
81
|
private send;
|
@@ -91,4 +106,5 @@ export declare class StormClient {
|
|
91
106
|
deleteUIPageConversation(conversationId: string): Promise<string>;
|
92
107
|
downloadSystem(handle: string, conversationId: string): Promise<Buffer>;
|
93
108
|
uploadSystem(handle: string, conversationId: string, buffer: Buffer): Promise<Response>;
|
109
|
+
private getSharedSecretHeader;
|
94
110
|
}
|
@@ -23,14 +23,17 @@ class StormClient {
|
|
23
23
|
_baseUrl;
|
24
24
|
_systemId;
|
25
25
|
_handle;
|
26
|
+
_sharedSecret;
|
26
27
|
constructor(handle, systemId) {
|
27
28
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
28
29
|
this._systemId = systemId || '';
|
29
30
|
this._handle = handle;
|
31
|
+
this._sharedSecret = process.env.SHARED_SECRET || '@keep-this-super-secret!';
|
30
32
|
}
|
31
33
|
async createOptions(path, method, body) {
|
32
34
|
const url = `${this._baseUrl}${path}`;
|
33
35
|
const headers = {
|
36
|
+
...this.getSharedSecretHeader(),
|
34
37
|
'Content-Type': 'application/json',
|
35
38
|
};
|
36
39
|
const api = new nodejs_api_client_1.KapetaAPI();
|
@@ -149,7 +152,7 @@ class StormClient {
|
|
149
152
|
async replaceMockWithAPICall(prompt) {
|
150
153
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
151
154
|
try {
|
152
|
-
const headers =
|
155
|
+
const headers = this.getSharedSecretHeader();
|
153
156
|
headers[exports.HandleHeader] = this._handle;
|
154
157
|
headers[exports.ConversationIdHeader] = this._systemId;
|
155
158
|
headers[exports.SystemIdHeader] = this._systemId;
|
@@ -176,12 +179,13 @@ class StormClient {
|
|
176
179
|
body: JSON.stringify({
|
177
180
|
pages: pages,
|
178
181
|
}),
|
182
|
+
headers: this.getSharedSecretHeader(),
|
179
183
|
});
|
180
184
|
return await response.text();
|
181
185
|
}
|
182
186
|
async createSimpleBackend(handle, systemId, input) {
|
183
187
|
const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
|
184
|
-
const headers =
|
188
|
+
const headers = this.getSharedSecretHeader();
|
185
189
|
headers[exports.HandleHeader] = this._handle;
|
186
190
|
headers[exports.ConversationIdHeader] = this._systemId;
|
187
191
|
headers[exports.SystemIdHeader] = this._systemId;
|
@@ -267,7 +271,9 @@ class StormClient {
|
|
267
271
|
}
|
268
272
|
async downloadSystem(handle, conversationId) {
|
269
273
|
const u = `${this._baseUrl}/v2/systems/${handle}/${conversationId}/download`;
|
270
|
-
const response = await fetch(u
|
274
|
+
const response = await fetch(u, {
|
275
|
+
headers: this.getSharedSecretHeader(),
|
276
|
+
});
|
271
277
|
if (!response.ok) {
|
272
278
|
throw new Error(`Failed to download system: ${response.status}`);
|
273
279
|
}
|
@@ -279,10 +285,16 @@ class StormClient {
|
|
279
285
|
method: 'PUT',
|
280
286
|
body: buffer,
|
281
287
|
headers: {
|
288
|
+
...this.getSharedSecretHeader(),
|
282
289
|
'content-type': 'application/zip',
|
283
290
|
},
|
284
291
|
});
|
285
292
|
return response;
|
286
293
|
}
|
294
|
+
getSharedSecretHeader() {
|
295
|
+
return {
|
296
|
+
SharedSecret: this._sharedSecret,
|
297
|
+
};
|
298
|
+
}
|
287
299
|
}
|
288
300
|
exports.StormClient = StormClient;
|
package/package.json
CHANGED
@@ -95,7 +95,13 @@ export class PageQueue extends EventEmitter {
|
|
95
95
|
return false;
|
96
96
|
}
|
97
97
|
|
98
|
-
public addPrompt(
|
98
|
+
public addPrompt(
|
99
|
+
initialPrompt: InitialPrompt,
|
100
|
+
conversationId: string = uuid.v4(),
|
101
|
+
overwrite: boolean = false,
|
102
|
+
// Set globalEdit to true when the same prompt is being sent to multiple pages
|
103
|
+
globalEdit: boolean = false
|
104
|
+
) {
|
99
105
|
if (!overwrite && this.hasPrompt(initialPrompt.path)) {
|
100
106
|
//console.log('Ignoring duplicate prompt', initialPrompt.path);
|
101
107
|
return Promise.resolve();
|
@@ -105,8 +111,12 @@ export class PageQueue extends EventEmitter {
|
|
105
111
|
const prompt: UIPagePrompt = {
|
106
112
|
...initialPrompt,
|
107
113
|
shell_page: this.uiShells.find((shell) => shell.screens.includes(initialPrompt.name))?.content,
|
108
|
-
prompt:
|
114
|
+
prompt: initialPrompt.prompt,
|
109
115
|
theme: this.theme,
|
116
|
+
global_edit: globalEdit,
|
117
|
+
system_prompt: this.systemPrompt,
|
118
|
+
existing_pages: this.getExistingPages(initialPrompt.path),
|
119
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({ path, description })),
|
110
120
|
};
|
111
121
|
|
112
122
|
this.references.set(prompt.path, true);
|
@@ -114,36 +124,18 @@ export class PageQueue extends EventEmitter {
|
|
114
124
|
|
115
125
|
return this.queue.add<void>(() => this.generate(prompt, conversationId));
|
116
126
|
}
|
117
|
-
private getPrefix(): string {
|
118
|
-
let promptPrefix = '';
|
119
|
-
if (this.systemPrompt) {
|
120
|
-
promptPrefix = `For a system with this description: ${this.systemPrompt}\n`;
|
121
|
-
}
|
122
|
-
return promptPrefix;
|
123
|
-
}
|
124
|
-
|
125
|
-
private wrapPagePrompt(pagePath: string, prompt: string): string {
|
126
|
-
const promptPrefix = this.getPrefix();
|
127
|
-
let promptPostfix = '';
|
128
|
-
|
129
|
-
if (this.pages.size > 0) {
|
130
|
-
promptPostfix = `\nThe following pages are already implemented:\n`;
|
131
|
-
this.pages.forEach((description, path) => {
|
132
|
-
if (pagePath === path) {
|
133
|
-
return;
|
134
|
-
}
|
135
|
-
promptPostfix += `- PAGE: '${path}' -> ${description}.\n`;
|
136
|
-
});
|
137
|
-
}
|
138
|
-
|
139
|
-
if (this.images.size > 0) {
|
140
|
-
promptPostfix += `\nThe following images already exist:\n`;
|
141
|
-
this.images.forEach((description, path) => {
|
142
|
-
promptPostfix += `- IMAGE: '${path}' -> ${description}.\n`;
|
143
|
-
});
|
144
|
-
}
|
145
127
|
|
146
|
-
|
128
|
+
/**
|
129
|
+
* Get the existing pages
|
130
|
+
*/
|
131
|
+
private getExistingPages(excludePath?: string) {
|
132
|
+
return (
|
133
|
+
Object.entries(this.pages)
|
134
|
+
// Possibly exclude one page. This is useful when we don't want to include the page
|
135
|
+
// we're currently editing.
|
136
|
+
.filter(([path]) => (excludePath ? path !== excludePath : true))
|
137
|
+
.map(([path, description]) => ({ path, description }))
|
138
|
+
);
|
147
139
|
}
|
148
140
|
|
149
141
|
private async processPageEventWithReferences(event: StormEventPage) {
|
@@ -198,10 +190,17 @@ export class PageQueue extends EventEmitter {
|
|
198
190
|
path: normalizedPath,
|
199
191
|
method: 'GET',
|
200
192
|
storage_prefix: this.systemId + '_',
|
201
|
-
prompt:
|
202
|
-
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
203
|
-
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
193
|
+
prompt: `Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}`,
|
204
194
|
description: reference.description,
|
195
|
+
referenced_from: {
|
196
|
+
path: event.payload.path,
|
197
|
+
content: event.payload.content,
|
198
|
+
},
|
199
|
+
existing_pages: this.getExistingPages(),
|
200
|
+
existing_images: Object.entries(this.images).map(([path, description]) => ({
|
201
|
+
path,
|
202
|
+
description,
|
203
|
+
})),
|
205
204
|
// Only used for matching
|
206
205
|
filename: reference.name + '.ref.html',
|
207
206
|
theme: this.theme,
|
package/src/storm/routes.ts
CHANGED
@@ -293,6 +293,9 @@ router.delete('/ui/serve/:systemId', async (req: KapetaBodyRequest, res: Respons
|
|
293
293
|
res.status(200).json({ status: 'ok' });
|
294
294
|
});
|
295
295
|
|
296
|
+
/**
|
297
|
+
* Edit a single page
|
298
|
+
*/
|
296
299
|
router.post('/:handle/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
297
300
|
try {
|
298
301
|
const handle = req.params.handle as string;
|
@@ -673,6 +676,9 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
673
676
|
}
|
674
677
|
});
|
675
678
|
|
679
|
+
/**
|
680
|
+
* Edit all pages
|
681
|
+
*/
|
676
682
|
router.post('/:handle/ui/edit', async (req: KapetaBodyRequest, res: Response) => {
|
677
683
|
try {
|
678
684
|
const handle = req.params.handle as string;
|
@@ -732,7 +738,8 @@ router.post('/:handle/ui/edit', async (req: KapetaBodyRequest, res: Response) =>
|
|
732
738
|
storage_prefix: storagePrefix,
|
733
739
|
},
|
734
740
|
page.conversationId,
|
735
|
-
true
|
741
|
+
true,
|
742
|
+
true // this is a global edit
|
736
743
|
);
|
737
744
|
}
|
738
745
|
})
|
package/src/storm/stormClient.ts
CHANGED
@@ -51,6 +51,25 @@ export interface UIPagePrompt {
|
|
51
51
|
shell_page?: string;
|
52
52
|
// contents of theme.md
|
53
53
|
theme?: string;
|
54
|
+
// whether this prompt is for a global edit
|
55
|
+
global_edit?: boolean;
|
56
|
+
// The prompt that was used to start the conversation (user prompt or improved prompt)
|
57
|
+
system_prompt?: string;
|
58
|
+
// The pages we have already created
|
59
|
+
existing_pages?: {
|
60
|
+
path: string;
|
61
|
+
description: string;
|
62
|
+
}[];
|
63
|
+
// The images we have already created
|
64
|
+
existing_images?: {
|
65
|
+
path: string;
|
66
|
+
description: string;
|
67
|
+
}[];
|
68
|
+
// The page that referenced this page
|
69
|
+
referenced_from?: {
|
70
|
+
path: string;
|
71
|
+
content: string;
|
72
|
+
};
|
54
73
|
}
|
55
74
|
|
56
75
|
export interface UIPageSamplePrompt extends UIPagePrompt {
|
@@ -91,10 +110,12 @@ export class StormClient {
|
|
91
110
|
private readonly _baseUrl: string;
|
92
111
|
private readonly _systemId: string;
|
93
112
|
private readonly _handle: string;
|
113
|
+
private readonly _sharedSecret: string;
|
94
114
|
constructor(handle: string, systemId?: string) {
|
95
115
|
this._baseUrl = getRemoteUrl('ai-service', 'https://ai.kapeta.com');
|
96
116
|
this._systemId = systemId || '';
|
97
117
|
this._handle = handle;
|
118
|
+
this._sharedSecret = process.env.SHARED_SECRET || '@keep-this-super-secret!';
|
98
119
|
}
|
99
120
|
|
100
121
|
private async createOptions(
|
@@ -104,6 +125,7 @@ export class StormClient {
|
|
104
125
|
): Promise<RequestInit & { url: string }> {
|
105
126
|
const url = `${this._baseUrl}${path}`;
|
106
127
|
const headers: { [k: string]: string } = {
|
128
|
+
...this.getSharedSecretHeader(),
|
107
129
|
'Content-Type': 'application/json',
|
108
130
|
};
|
109
131
|
const api = new KapetaAPI();
|
@@ -253,7 +275,7 @@ export class StormClient {
|
|
253
275
|
public async replaceMockWithAPICall(prompt: ImplementAPIClients): Promise<HTMLPage[]> {
|
254
276
|
const u = `${this._baseUrl}/v2/ui/implement-api-clients-all`;
|
255
277
|
try {
|
256
|
-
const headers: { [key: string]: any } =
|
278
|
+
const headers: { [key: string]: any } = this.getSharedSecretHeader();
|
257
279
|
headers[HandleHeader] = this._handle;
|
258
280
|
headers[ConversationIdHeader] = this._systemId;
|
259
281
|
headers[SystemIdHeader] = this._systemId;
|
@@ -283,6 +305,7 @@ export class StormClient {
|
|
283
305
|
body: JSON.stringify({
|
284
306
|
pages: pages,
|
285
307
|
}),
|
308
|
+
headers: this.getSharedSecretHeader(),
|
286
309
|
});
|
287
310
|
return await response.text();
|
288
311
|
}
|
@@ -290,7 +313,7 @@ export class StormClient {
|
|
290
313
|
public async createSimpleBackend(handle: string, systemId: string, input: CreateSimpleBackendRequest) {
|
291
314
|
const u = `${this._baseUrl}/v2/create-simple-backend/${handle}/${systemId}`;
|
292
315
|
|
293
|
-
const headers: { [key: string]: any } =
|
316
|
+
const headers: { [key: string]: any } = this.getSharedSecretHeader();
|
294
317
|
headers[HandleHeader] = this._handle;
|
295
318
|
headers[ConversationIdHeader] = this._systemId;
|
296
319
|
headers[SystemIdHeader] = this._systemId;
|
@@ -391,7 +414,9 @@ export class StormClient {
|
|
391
414
|
|
392
415
|
async downloadSystem(handle: string, conversationId: string) {
|
393
416
|
const u = `${this._baseUrl}/v2/systems/${handle}/${conversationId}/download`;
|
394
|
-
const response = await fetch(u
|
417
|
+
const response = await fetch(u, {
|
418
|
+
headers: this.getSharedSecretHeader(),
|
419
|
+
});
|
395
420
|
if (!response.ok) {
|
396
421
|
throw new Error(`Failed to download system: ${response.status}`);
|
397
422
|
}
|
@@ -404,10 +429,17 @@ export class StormClient {
|
|
404
429
|
method: 'PUT',
|
405
430
|
body: buffer,
|
406
431
|
headers: {
|
432
|
+
...this.getSharedSecretHeader(),
|
407
433
|
'content-type': 'application/zip',
|
408
434
|
},
|
409
435
|
});
|
410
436
|
|
411
437
|
return response;
|
412
438
|
}
|
439
|
+
|
440
|
+
private getSharedSecretHeader() {
|
441
|
+
return {
|
442
|
+
SharedSecret: this._sharedSecret,
|
443
|
+
};
|
444
|
+
}
|
413
445
|
}
|