@kapeta/local-cluster-service 0.74.2 → 0.76.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/middleware/stringBody.js +2 -1
- package/dist/cjs/src/storm/PageGenerator.d.ts +2 -1
- package/dist/cjs/src/storm/PageGenerator.js +5 -3
- package/dist/cjs/src/storm/codegen.d.ts +0 -10
- package/dist/cjs/src/storm/codegen.js +1 -202
- package/dist/cjs/src/storm/routes.js +28 -11
- package/dist/cjs/src/storm/stormClient.d.ts +3 -1
- package/dist/cjs/src/storm/stormClient.js +9 -2
- package/dist/cjs/src/stormService.d.ts +7 -0
- package/dist/cjs/src/stormService.js +25 -4
- package/dist/esm/src/middleware/stringBody.js +2 -1
- package/dist/esm/src/storm/PageGenerator.d.ts +2 -1
- package/dist/esm/src/storm/PageGenerator.js +5 -3
- package/dist/esm/src/storm/codegen.d.ts +0 -10
- package/dist/esm/src/storm/codegen.js +1 -202
- package/dist/esm/src/storm/routes.js +28 -11
- package/dist/esm/src/storm/stormClient.d.ts +3 -1
- package/dist/esm/src/storm/stormClient.js +9 -2
- package/dist/esm/src/stormService.d.ts +7 -0
- package/dist/esm/src/stormService.js +25 -4
- package/package.json +1 -1
- package/src/middleware/stringBody.ts +2 -1
- package/src/storm/PageGenerator.ts +5 -3
- package/src/storm/codegen.ts +2 -297
- package/src/storm/routes.ts +29 -11
- package/src/storm/stormClient.ts +8 -1
- package/src/stormService.ts +38 -6
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.76.0](https://github.com/kapetacom/local-cluster-service/compare/v0.75.0...v0.76.0) (2024-09-30)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* adding handle to request to the ai-service ([f91a4b0](https://github.com/kapetacom/local-cluster-service/commit/f91a4b07b49497375f49256244619223d91876c1))
|
7
|
+
|
8
|
+
# [0.75.0](https://github.com/kapetacom/local-cluster-service/compare/v0.74.2...v0.75.0) (2024-09-30)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* add thumbnail support and timestamps to AI systems [CORE-3532] ([#262](https://github.com/kapetacom/local-cluster-service/issues/262)) ([31a3cad](https://github.com/kapetacom/local-cluster-service/commit/31a3cadba0d5034d3497b98616468e12ce3f586b))
|
14
|
+
|
1
15
|
## [0.74.2](https://github.com/kapetacom/local-cluster-service/compare/v0.74.1...v0.74.2) (2024-09-27)
|
2
16
|
|
3
17
|
|
@@ -11,7 +11,8 @@ function stringBody(req, res, next) {
|
|
11
11
|
req.on('data', (chunk) => {
|
12
12
|
body.push(chunk);
|
13
13
|
}).on('end', () => {
|
14
|
-
req.
|
14
|
+
req.body = Buffer.concat(body);
|
15
|
+
req.stringBody = req.body.toString();
|
15
16
|
next();
|
16
17
|
});
|
17
18
|
}
|
@@ -21,6 +21,7 @@ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
|
|
21
21
|
export declare class PageQueue extends EventEmitter {
|
22
22
|
private readonly queue;
|
23
23
|
private readonly eventQueue;
|
24
|
+
private readonly handle;
|
24
25
|
private readonly systemId;
|
25
26
|
private readonly systemPrompt;
|
26
27
|
private readonly references;
|
@@ -28,7 +29,7 @@ export declare class PageQueue extends EventEmitter {
|
|
28
29
|
private readonly images;
|
29
30
|
private uiShells;
|
30
31
|
private theme;
|
31
|
-
constructor(systemId: string, systemPrompt: string, concurrency?: number);
|
32
|
+
constructor(handle: string, systemId: string, systemPrompt: string, concurrency?: number);
|
32
33
|
on(event: 'error', listener: (error: unknown) => void): this;
|
33
34
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
34
35
|
on(event: 'page', listener: (data: StormEventPage) => void | Promise<void>): this;
|
@@ -41,6 +41,7 @@ const node_crypto_1 = require("node:crypto");
|
|
41
41
|
class PageQueue extends node_events_1.EventEmitter {
|
42
42
|
queue;
|
43
43
|
eventQueue;
|
44
|
+
handle;
|
44
45
|
systemId;
|
45
46
|
systemPrompt;
|
46
47
|
references = new Map();
|
@@ -48,8 +49,9 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
48
49
|
images = new Map();
|
49
50
|
uiShells = [];
|
50
51
|
theme = '';
|
51
|
-
constructor(systemId, systemPrompt, concurrency = 5) {
|
52
|
+
constructor(handle, systemId, systemPrompt, concurrency = 5) {
|
52
53
|
super();
|
54
|
+
this.handle = handle;
|
53
55
|
this.systemId = systemId;
|
54
56
|
this.systemPrompt = systemPrompt;
|
55
57
|
this.queue = new p_queue_1.default({ concurrency });
|
@@ -261,7 +263,7 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
261
263
|
this.emit('image', imageEvent, prompt);
|
262
264
|
}
|
263
265
|
async generate(prompt, conversationId) {
|
264
|
-
const client = new stormClient_1.StormClient(this.systemId);
|
266
|
+
const client = new stormClient_1.StormClient("", this.systemId);
|
265
267
|
const screenStream = await client.createUIPage(prompt, conversationId);
|
266
268
|
let pageEvent = null;
|
267
269
|
screenStream.on('data', (event) => {
|
@@ -279,7 +281,7 @@ class PageQueue extends node_events_1.EventEmitter {
|
|
279
281
|
await this.processPageEventWithReferences(pageEvent);
|
280
282
|
}
|
281
283
|
async resolveReferences(content) {
|
282
|
-
const client = new stormClient_1.StormClient(this.systemId);
|
284
|
+
const client = new stormClient_1.StormClient("", this.systemId);
|
283
285
|
const referenceStream = await client.classifyUIReferences(content);
|
284
286
|
const references = [];
|
285
287
|
referenceStream.on('data', (referenceData) => {
|
@@ -30,17 +30,7 @@ export declare class StormCodegen {
|
|
30
30
|
private processBlockCode;
|
31
31
|
private emitBlockStatusDone;
|
32
32
|
private emitBlockStatus;
|
33
|
-
private verifyAndFixCode;
|
34
|
-
private tryToFixFile;
|
35
|
-
private classifyErrors;
|
36
|
-
private getErrorDetailsForFile;
|
37
|
-
private createFixRequestForFile;
|
38
|
-
private getErrorLine;
|
39
33
|
removePrefix(prefix: string, str: string): string;
|
40
|
-
/**
|
41
|
-
* Sends the code to the AI for a fix
|
42
|
-
*/
|
43
|
-
private codeFix;
|
44
34
|
/**
|
45
35
|
* Emits the text-based files to the stream
|
46
36
|
*/
|
@@ -42,7 +42,6 @@ const promises_1 = require("fs/promises");
|
|
42
42
|
const path_1 = __importDefault(require("path"));
|
43
43
|
const path_2 = __importStar(require("path"));
|
44
44
|
const node_os_1 = __importDefault(require("node:os"));
|
45
|
-
const fs_1 = require("fs");
|
46
45
|
const yaml_1 = __importDefault(require("yaml"));
|
47
46
|
const predefined_1 = require("./predefined");
|
48
47
|
const archetype_1 = require("./archetype");
|
@@ -311,7 +310,7 @@ class StormCodegen {
|
|
311
310
|
permissions: '0644',
|
312
311
|
});
|
313
312
|
const uiEvents = [];
|
314
|
-
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
313
|
+
const stormClient = new stormClient_1.StormClient(blockUri.handle, this.uiSystemId);
|
315
314
|
// generate screens
|
316
315
|
if (uiTemplates.length) {
|
317
316
|
const screenStream = await stormClient.listScreens({
|
@@ -439,212 +438,12 @@ class StormCodegen {
|
|
439
438
|
},
|
440
439
|
});
|
441
440
|
}
|
442
|
-
async verifyAndFixCode(blockUri, blockName, codeGenerator, basePath, filesToBeFixed, allFiles) {
|
443
|
-
let attempts = 0;
|
444
|
-
let validCode = false;
|
445
|
-
for (let i = 0; i <= 3; i++) {
|
446
|
-
attempts++;
|
447
|
-
try {
|
448
|
-
console.log(`Validating the code in ${basePath} attempt #${attempts}`);
|
449
|
-
const result = await codeGenerator.validateForTarget(basePath);
|
450
|
-
if (result && result.valid) {
|
451
|
-
validCode = true;
|
452
|
-
break;
|
453
|
-
}
|
454
|
-
if (result && !result.valid) {
|
455
|
-
console.debug('Validation error:', result);
|
456
|
-
this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.PLANNING_FIX);
|
457
|
-
const errors = await this.classifyErrors(result.error, basePath);
|
458
|
-
if (errors.size > 0) {
|
459
|
-
this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIXING);
|
460
|
-
const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
|
461
|
-
if (filesToBeFixed.some((file) => file.filename === filename)) {
|
462
|
-
return this.tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator);
|
463
|
-
}
|
464
|
-
});
|
465
|
-
await Promise.all(promises);
|
466
|
-
}
|
467
|
-
this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIX_DONE);
|
468
|
-
}
|
469
|
-
}
|
470
|
-
catch (e) {
|
471
|
-
console.error('Error:', e);
|
472
|
-
}
|
473
|
-
}
|
474
|
-
if (validCode) {
|
475
|
-
console.log(`Validation successful after ${attempts} attempts`);
|
476
|
-
}
|
477
|
-
else {
|
478
|
-
console.error(`Validation failed for ${basePath} after ${attempts} attempts`);
|
479
|
-
}
|
480
|
-
}
|
481
|
-
async tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator) {
|
482
|
-
console.log(`Processing ${filename}`);
|
483
|
-
const language = await codeGenerator.language();
|
484
|
-
const relevantFiles = allFiles.filter((file) => file.type != codegen_1.AIFileTypes.IGNORE);
|
485
|
-
for (let attempts = 1; attempts <= 5; attempts++) {
|
486
|
-
if (fileErrors.length == 0) {
|
487
|
-
console.log(`No more errors for ${filename}`);
|
488
|
-
return;
|
489
|
-
}
|
490
|
-
console.log(`Errors in ${filename} - requesting error details`);
|
491
|
-
const filesForContext = await this.getErrorDetailsForFile(basePath, filename, fileErrors[0], relevantFiles, language);
|
492
|
-
console.log(`Get error details for ${filename} requesting code fixes`);
|
493
|
-
const fix = this.createFixRequestForFile(basePath, filename, fileErrors[0], filesForContext, relevantFiles, language);
|
494
|
-
const codeFixFile = await this.codeFix(blockUri, blockName, fix);
|
495
|
-
console.log(`Got fixed code for ${filename}`);
|
496
|
-
const filePath = codeFixFile.filename.indexOf(basePath) > -1
|
497
|
-
? codeFixFile.filename
|
498
|
-
: (0, path_2.join)(basePath, codeFixFile.filename);
|
499
|
-
const existing = (0, fs_1.readFileSync)(filePath);
|
500
|
-
if (existing.toString().replace(/(\r\n|\r|\n)+$/, '') == codeFixFile.content.replace(/(\r\n|\r|\n)+$/, '')) {
|
501
|
-
console.log(`${filename} not changed by gemini`);
|
502
|
-
continue;
|
503
|
-
}
|
504
|
-
(0, fs_1.writeFileSync)(filePath, codeFixFile.content);
|
505
|
-
const result = await codeGenerator.validateForTarget(basePath);
|
506
|
-
if (result && result.valid) {
|
507
|
-
return;
|
508
|
-
}
|
509
|
-
const errors = await this.classifyErrors(result.error, basePath);
|
510
|
-
fileErrors = errors.get(filename) ?? [];
|
511
|
-
}
|
512
|
-
}
|
513
|
-
async classifyErrors(errors, basePath) {
|
514
|
-
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
515
|
-
const errorStream = await stormClient.createErrorClassification(errors, []);
|
516
|
-
const fixes = new Map();
|
517
|
-
this.out.on('aborted', () => {
|
518
|
-
errorStream.abort();
|
519
|
-
});
|
520
|
-
errorStream.on('data', (evt) => {
|
521
|
-
if (evt.type === 'ERROR_CLASSIFIER') {
|
522
|
-
const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
|
523
|
-
const fix = {
|
524
|
-
error: evt.payload.error,
|
525
|
-
lineNumber: evt.payload.lineNumber,
|
526
|
-
column: evt.payload.column,
|
527
|
-
};
|
528
|
-
let existingFixes = fixes.get(eventFileName);
|
529
|
-
if (existingFixes) {
|
530
|
-
existingFixes.push(fix);
|
531
|
-
}
|
532
|
-
else {
|
533
|
-
fixes.set(eventFileName, [fix]);
|
534
|
-
}
|
535
|
-
}
|
536
|
-
});
|
537
|
-
await errorStream.waitForDone();
|
538
|
-
return fixes;
|
539
|
-
}
|
540
|
-
async getErrorDetailsForFile(basePath, filename, error, allFiles, language) {
|
541
|
-
const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename); // to compensate when compiler returns absolute path
|
542
|
-
return new Promise(async (resolve, reject) => {
|
543
|
-
const request = {
|
544
|
-
language: language,
|
545
|
-
sourceFile: {
|
546
|
-
filename: filename,
|
547
|
-
content: (0, fs_1.readFileSync)(filePath, 'utf8'),
|
548
|
-
},
|
549
|
-
error: error,
|
550
|
-
projectFiles: allFiles.map((f) => f.filename),
|
551
|
-
};
|
552
|
-
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
553
|
-
const detailsStream = await stormClient.createErrorDetails(JSON.stringify(request), []);
|
554
|
-
detailsStream.on('data', (evt) => {
|
555
|
-
if (evt.type === 'ERROR_DETAILS') {
|
556
|
-
resolve(evt.payload.files);
|
557
|
-
}
|
558
|
-
reject(new Error('Error details: Unexpected event [' + evt.type + ']'));
|
559
|
-
});
|
560
|
-
this.out.on('aborted', () => {
|
561
|
-
detailsStream.abort();
|
562
|
-
reject(new Error('aborted'));
|
563
|
-
});
|
564
|
-
detailsStream.on('error', (err) => {
|
565
|
-
reject(err);
|
566
|
-
});
|
567
|
-
await detailsStream.waitForDone();
|
568
|
-
});
|
569
|
-
}
|
570
|
-
createFixRequestForFile(basePath, filename, error, filesForContext, allFiles, language) {
|
571
|
-
const files = new Set(filesForContext);
|
572
|
-
files.add(filename);
|
573
|
-
const requestedFiles = Array.from(files).flatMap((file) => {
|
574
|
-
if ((0, fs_1.existsSync)(file)) {
|
575
|
-
return file;
|
576
|
-
}
|
577
|
-
// file does not exist - look for similar
|
578
|
-
const candidateName = file.split('/').pop();
|
579
|
-
return allFiles.filter((file) => file.filename.split('/').pop() === candidateName).map((f) => f.filename);
|
580
|
-
});
|
581
|
-
const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename);
|
582
|
-
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
583
|
-
const affectedLine = this.getErrorLine(error, content);
|
584
|
-
const fixRequest = {
|
585
|
-
language: language,
|
586
|
-
filename: filename,
|
587
|
-
error: error.error,
|
588
|
-
affectedLine: affectedLine,
|
589
|
-
projectFiles: requestedFiles.map((filename) => {
|
590
|
-
const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename);
|
591
|
-
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
592
|
-
return { filename: filename, content: content };
|
593
|
-
}),
|
594
|
-
};
|
595
|
-
return JSON.stringify(fixRequest);
|
596
|
-
}
|
597
|
-
getErrorLine(errorDetails, sourceCode) {
|
598
|
-
const lines = sourceCode.split('\n');
|
599
|
-
const errorLine = lines[errorDetails.lineNumber - 1];
|
600
|
-
if (!errorLine) {
|
601
|
-
return 'Error: Line number out of range.';
|
602
|
-
}
|
603
|
-
return errorLine;
|
604
|
-
}
|
605
441
|
removePrefix(prefix, str) {
|
606
442
|
if (str.startsWith(prefix)) {
|
607
443
|
return str.slice(prefix.length);
|
608
444
|
}
|
609
445
|
return str;
|
610
446
|
}
|
611
|
-
/**
|
612
|
-
* Sends the code to the AI for a fix
|
613
|
-
*/
|
614
|
-
async codeFix(blockUri, blockName, fix, history) {
|
615
|
-
return new Promise(async (resolve, reject) => {
|
616
|
-
try {
|
617
|
-
const stormClient = new stormClient_1.StormClient(this.uiSystemId);
|
618
|
-
const fixStream = await stormClient.createCodeFix(fix, history, this.conversationId);
|
619
|
-
let resolved = false;
|
620
|
-
fixStream.on('data', (evt) => {
|
621
|
-
if (this.handleFileEvents(blockUri, blockName, evt)) {
|
622
|
-
return;
|
623
|
-
}
|
624
|
-
this.handleFileDoneOutput(blockUri, blockName, evt);
|
625
|
-
if (evt.type === 'CODE_FIX') {
|
626
|
-
resolved = true;
|
627
|
-
resolve(evt.payload);
|
628
|
-
}
|
629
|
-
});
|
630
|
-
this.out.on('aborted', () => {
|
631
|
-
fixStream.abort();
|
632
|
-
reject(new Error('aborted'));
|
633
|
-
});
|
634
|
-
fixStream.on('error', (err) => {
|
635
|
-
reject(err);
|
636
|
-
});
|
637
|
-
fixStream.on('end', () => {
|
638
|
-
if (!resolved) {
|
639
|
-
reject(new Error('Code fix never returned a valid event'));
|
640
|
-
}
|
641
|
-
});
|
642
|
-
}
|
643
|
-
catch (e) {
|
644
|
-
reject(e);
|
645
|
-
}
|
646
|
-
});
|
647
|
-
}
|
648
447
|
/**
|
649
448
|
* Emits the text-based files to the stream
|
650
449
|
*/
|
@@ -110,6 +110,7 @@ router.post('/ui/conversations/:systemId/append', async (req, res) => {
|
|
110
110
|
});
|
111
111
|
router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
112
112
|
const systemId = req.params.systemId;
|
113
|
+
const handle = req.params.handle;
|
113
114
|
const srcDir = (0, page_utils_1.getSystemBaseDir)(systemId);
|
114
115
|
const destDir = (0, page_utils_1.getSystemBaseImplDir)(systemId);
|
115
116
|
res.set('Content-Type', 'application/x-ndjson');
|
@@ -117,7 +118,7 @@ router.post('/ui/create-system/:handle/:systemId', async (req, res) => {
|
|
117
118
|
res.set(stormClient_1.ConversationIdHeader, systemId);
|
118
119
|
sendEvent(res, (0, event_parser_1.createPhaseStartEvent)(events_1.StormEventPhaseType.IMPLEMENT_APIS));
|
119
120
|
const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
|
120
|
-
const client = new stormClient_1.StormClient(systemId);
|
121
|
+
const client = new stormClient_1.StormClient(handle, systemId);
|
121
122
|
const pagesWithImplementation = await client.replaceMockWithAPICall({
|
122
123
|
pages: pagesFromDisk,
|
123
124
|
systemId: systemId,
|
@@ -151,7 +152,7 @@ router.post('/ui/create-system-simple/:handle/:systemId', async (req, res) => {
|
|
151
152
|
//res.set('Access-Control-Expose-Headers', ConversationIdHeader);
|
152
153
|
//res.set(ConversationIdHeader, systemId);
|
153
154
|
//sendEvent(res, createPhaseStartEvent(StormEventPhaseType.IMPLEMENT_APIS));
|
154
|
-
const client = new stormClient_1.StormClient(systemId);
|
155
|
+
const client = new stormClient_1.StormClient(handle, systemId);
|
155
156
|
try {
|
156
157
|
const pagesFromDisk = (0, utils_1.readFilesAndContent)(srcDir);
|
157
158
|
const pagesWithImplementation = await client.replaceMockWithAPICall({
|
@@ -196,6 +197,22 @@ router.post('/ui/systems/:handle/:systemId/upload', async (req, res) => {
|
|
196
197
|
await stormService_1.default.uploadConversation(handle, systemId);
|
197
198
|
res.send({ ok: true });
|
198
199
|
});
|
200
|
+
router.put('/ui/systems/:handle/:systemId/thumbnail', async (req, res) => {
|
201
|
+
const systemId = req.params.systemId;
|
202
|
+
await stormService_1.default.saveThumbnail(systemId, req.body);
|
203
|
+
res.send({ ok: true });
|
204
|
+
});
|
205
|
+
router.get('/ui/systems/:handle/:systemId/thumbnail.png', async (req, res) => {
|
206
|
+
const systemId = req.params.systemId;
|
207
|
+
const thumbnail = await stormService_1.default.getThumbnail(systemId);
|
208
|
+
if (thumbnail) {
|
209
|
+
res.set('Content-Type', 'image/png');
|
210
|
+
res.send(thumbnail);
|
211
|
+
}
|
212
|
+
else {
|
213
|
+
res.status(404).send({ error: 'No thumbnail found' });
|
214
|
+
}
|
215
|
+
});
|
199
216
|
router.delete('/ui/serve/:systemId', async (req, res) => {
|
200
217
|
const systemId = req.params.systemId;
|
201
218
|
if (!systemId) {
|
@@ -219,7 +236,7 @@ router.post('/ui/screen', async (req, res) => {
|
|
219
236
|
res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
|
220
237
|
res.set(stormClient_1.ConversationIdHeader, conversationId);
|
221
238
|
const parentConversationId = systemId ?? '';
|
222
|
-
const queue = new PageGenerator_1.PageQueue(parentConversationId, '', 5);
|
239
|
+
const queue = new PageGenerator_1.PageQueue("", parentConversationId, '', 5);
|
223
240
|
onRequestAborted(req, res, () => {
|
224
241
|
queue.cancel();
|
225
242
|
});
|
@@ -254,7 +271,7 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
254
271
|
try {
|
255
272
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
256
273
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
257
|
-
const client = new stormClient_1.StormClient(conversationId); //todo is this correct we are using the landing page getConversationId down below as well
|
274
|
+
const client = new stormClient_1.StormClient(handle, conversationId); //todo is this correct we are using the landing page getConversationId down below as well
|
258
275
|
const landingPagesStream = await client.createUILandingPages(aiRequest, conversationId);
|
259
276
|
onRequestAborted(req, res, () => {
|
260
277
|
landingPagesStream.abort();
|
@@ -309,7 +326,7 @@ router.post('/:handle/ui/iterative', async (req, res) => {
|
|
309
326
|
waitForStormStream(landingPagesStream).then(() => {
|
310
327
|
systemPrompt.resolve(aiRequest.prompt);
|
311
328
|
});
|
312
|
-
const pageQueue = new PageGenerator_1.PageQueue(systemId, await systemPrompt.promise, 5);
|
329
|
+
const pageQueue = new PageGenerator_1.PageQueue(handle, systemId, await systemPrompt.promise, 5);
|
313
330
|
onRequestAborted(req, res, () => {
|
314
331
|
pageQueue.cancel();
|
315
332
|
});
|
@@ -344,7 +361,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
344
361
|
try {
|
345
362
|
const outerConversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()] || (0, crypto_1.randomUUID)();
|
346
363
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
347
|
-
const stormClient = new stormClient_1.StormClient(outerConversationId);
|
364
|
+
const stormClient = new stormClient_1.StormClient(handle, outerConversationId);
|
348
365
|
// Get user journeys
|
349
366
|
const userJourneysStream = await stormClient.createUIUserJourneys(aiRequest, outerConversationId);
|
350
367
|
onRequestAborted(req, res, () => {
|
@@ -442,7 +459,7 @@ router.post('/:handle/ui', async (req, res) => {
|
|
442
459
|
onRequestAborted(req, res, () => {
|
443
460
|
shellsStream.abort();
|
444
461
|
});
|
445
|
-
const queue = new PageGenerator_1.PageQueue(outerConversationId, systemPrompt, 5);
|
462
|
+
const queue = new PageGenerator_1.PageQueue(handle, outerConversationId, systemPrompt, 5);
|
446
463
|
queue.setUiTheme(theme);
|
447
464
|
shellsStream.on('data', (data) => {
|
448
465
|
//console.log('Processing shell event', data);
|
@@ -528,7 +545,7 @@ router.post('/ui/edit', async (req, res) => {
|
|
528
545
|
req.headers[stormClient_1.ConversationIdHeader.toLowerCase()]);
|
529
546
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
530
547
|
const storagePrefix = systemId ? systemId + '_' : 'mock_';
|
531
|
-
const queue = new PageGenerator_1.PageQueue(systemId, '', 5);
|
548
|
+
const queue = new PageGenerator_1.PageQueue("", systemId, '', 5);
|
532
549
|
onRequestAborted(req, res, () => {
|
533
550
|
queue.cancel();
|
534
551
|
});
|
@@ -588,7 +605,7 @@ router.post('/ui/vote', async (req, res) => {
|
|
588
605
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
589
606
|
const { topic, vote, mainConversationId } = aiRequest;
|
590
607
|
try {
|
591
|
-
const stormClient = new stormClient_1.StormClient(mainConversationId);
|
608
|
+
const stormClient = new stormClient_1.StormClient("", mainConversationId);
|
592
609
|
await stormClient.voteUIPage(topic, conversationId, vote, mainConversationId);
|
593
610
|
}
|
594
611
|
catch (e) {
|
@@ -600,7 +617,7 @@ router.post('/ui/get-vote', async (req, res) => {
|
|
600
617
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
601
618
|
const { topic, mainConversationId } = aiRequest;
|
602
619
|
try {
|
603
|
-
const stormClient = new stormClient_1.StormClient(mainConversationId);
|
620
|
+
const stormClient = new stormClient_1.StormClient("", mainConversationId);
|
604
621
|
const vote = await stormClient.getVoteUIPage(topic, conversationId, mainConversationId);
|
605
622
|
res.send({ vote });
|
606
623
|
}
|
@@ -625,7 +642,7 @@ async function handleAll(req, res) {
|
|
625
642
|
const eventParser = new event_parser_1.StormEventParser(stormOptions);
|
626
643
|
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
627
644
|
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
628
|
-
const stormClient = new stormClient_1.StormClient(systemId);
|
645
|
+
const stormClient = new stormClient_1.StormClient(handle, systemId);
|
629
646
|
const metaStream = await stormClient.createMetadata(aiRequest, conversationId);
|
630
647
|
onRequestAborted(req, res, () => {
|
631
648
|
metaStream.abort();
|
@@ -5,6 +5,7 @@ import { Page, StormEventPageUrl } from './events';
|
|
5
5
|
export declare const STORM_ID = "storm";
|
6
6
|
export declare const ConversationIdHeader = "Conversation-Id";
|
7
7
|
export declare const SystemIdHeader = "System-Id";
|
8
|
+
export declare const HandleHeader = "Handle";
|
8
9
|
export interface UIShellsPrompt {
|
9
10
|
theme?: string;
|
10
11
|
pages: {
|
@@ -59,7 +60,8 @@ export interface BasePromptRequest {
|
|
59
60
|
export declare class StormClient {
|
60
61
|
private readonly _baseUrl;
|
61
62
|
private readonly _systemId;
|
62
|
-
|
63
|
+
private readonly _handle;
|
64
|
+
constructor(handle: string, systemId?: string);
|
63
65
|
private createOptions;
|
64
66
|
private send;
|
65
67
|
createMetadata(prompt: BasePromptRequest, conversationId?: string): Promise<StormStream>;
|
@@ -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.StormClient = exports.SystemIdHeader = exports.ConversationIdHeader = exports.STORM_ID = void 0;
|
6
|
+
exports.StormClient = exports.HandleHeader = exports.SystemIdHeader = exports.ConversationIdHeader = exports.STORM_ID = void 0;
|
7
7
|
/**
|
8
8
|
* Copyright 2023 Kapeta Inc.
|
9
9
|
* SPDX-License-Identifier: BUSL-1.1
|
@@ -18,12 +18,15 @@ const fetchWithRetries = (0, fetch_retry_1.default)(global.fetch, { retries: 5,
|
|
18
18
|
exports.STORM_ID = 'storm';
|
19
19
|
exports.ConversationIdHeader = 'Conversation-Id';
|
20
20
|
exports.SystemIdHeader = 'System-Id';
|
21
|
+
exports.HandleHeader = 'Handle';
|
21
22
|
class StormClient {
|
22
23
|
_baseUrl;
|
23
24
|
_systemId;
|
24
|
-
|
25
|
+
_handle;
|
26
|
+
constructor(handle, systemId) {
|
25
27
|
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
26
28
|
this._systemId = systemId || "";
|
29
|
+
this._handle = handle;
|
27
30
|
}
|
28
31
|
async createOptions(path, method, body) {
|
29
32
|
const url = `${this._baseUrl}${path}`;
|
@@ -41,6 +44,9 @@ class StormClient {
|
|
41
44
|
if (this._systemId) {
|
42
45
|
headers[exports.SystemIdHeader] = this._systemId;
|
43
46
|
}
|
47
|
+
if (this._handle) {
|
48
|
+
headers[exports.HandleHeader] = this._handle;
|
49
|
+
}
|
44
50
|
return {
|
45
51
|
url,
|
46
52
|
method: method,
|
@@ -147,6 +153,7 @@ class StormClient {
|
|
147
153
|
body: JSON.stringify(prompt.pages),
|
148
154
|
headers: {
|
149
155
|
'systemId': prompt.systemId,
|
156
|
+
'conversationId': prompt.systemId,
|
150
157
|
},
|
151
158
|
});
|
152
159
|
return (await response.json());
|
@@ -1,13 +1,18 @@
|
|
1
|
+
/// <reference types="node" />
|
1
2
|
import { StormEvent } from './storm/events';
|
2
3
|
export declare class StormService {
|
3
4
|
private getConversationFile;
|
4
5
|
private getConversationTarball;
|
6
|
+
private getThumbnailFile;
|
5
7
|
listRemoteConversations(): Promise<never[]>;
|
6
8
|
listLocalConversations(): Promise<{
|
7
9
|
id: string;
|
8
10
|
description: string;
|
9
11
|
title: string;
|
10
12
|
url?: string | undefined;
|
13
|
+
lastModified?: number | undefined;
|
14
|
+
createdAt?: number | undefined;
|
15
|
+
thumbnail?: string | undefined;
|
11
16
|
}[]>;
|
12
17
|
getConversation(conversationId: string): Promise<string>;
|
13
18
|
saveConversation(conversationId: string, events: StormEvent[]): Promise<void>;
|
@@ -15,6 +20,8 @@ export declare class StormService {
|
|
15
20
|
deleteConversation(conversationId: string): Promise<void>;
|
16
21
|
uploadConversation(handle: string, systemId: string): Promise<void>;
|
17
22
|
installProjectById(handle: string, systemId: string): Promise<void>;
|
23
|
+
saveThumbnail(systemId: string, thumbnail: Buffer): Promise<void>;
|
24
|
+
getThumbnail(systemId: string): Promise<Buffer | null>;
|
18
25
|
}
|
19
26
|
declare const _default: StormService;
|
20
27
|
export default _default;
|
@@ -41,6 +41,9 @@ class StormService {
|
|
41
41
|
getConversationTarball(conversationId) {
|
42
42
|
return path_1.default.join(filesystemManager_1.filesystemManager.getProjectRootFolder(), 'ai-systems', conversationId, 'system.tar.gz');
|
43
43
|
}
|
44
|
+
getThumbnailFile(conversationId) {
|
45
|
+
return path_1.default.join(filesystemManager_1.filesystemManager.getProjectRootFolder(), 'ai-systems', conversationId, 'thumbnail.png');
|
46
|
+
}
|
44
47
|
async listRemoteConversations() {
|
45
48
|
// i.e. conversations from org / user on registry
|
46
49
|
return [];
|
@@ -49,13 +52,16 @@ class StormService {
|
|
49
52
|
const systemsFolder = path_1.default.join(filesystemManager_1.filesystemManager.getProjectRootFolder(), 'ai-systems');
|
50
53
|
const eventFiles = await (0, glob_1.glob)('*/events.ndjson', {
|
51
54
|
cwd: systemsFolder,
|
52
|
-
|
55
|
+
stat: true,
|
56
|
+
withFileTypes: true,
|
53
57
|
});
|
54
58
|
// Returns list of UUIDs - probably want to make it more useful than that
|
55
59
|
const conversations = [];
|
60
|
+
// Sort by modification time, newest first
|
61
|
+
eventFiles.sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
|
56
62
|
for (const file of eventFiles) {
|
57
63
|
try {
|
58
|
-
const nldContents = await promises_1.default.readFile(file, 'utf8');
|
64
|
+
const nldContents = await promises_1.default.readFile(file.fullpath(), 'utf8');
|
59
65
|
const events = nldContents.split('\n').map((e) => JSON.parse(e));
|
60
66
|
// find the shell and get the title tag
|
61
67
|
const shellEvent = events.find((e) => e.type === 'AI' && e.event.type === 'UI_SHELL')?.event;
|
@@ -86,6 +92,9 @@ class StormService {
|
|
86
92
|
description: initialPrompt,
|
87
93
|
title: title || 'New system',
|
88
94
|
url,
|
95
|
+
lastModified: file.mtimeMs,
|
96
|
+
createdAt: file.birthtimeMs,
|
97
|
+
thumbnail: (0, fs_1.existsSync)(this.getThumbnailFile(id)) ? `thumbnail.png?v=${file.mtimeMs}` : undefined,
|
89
98
|
});
|
90
99
|
}
|
91
100
|
catch (e) {
|
@@ -129,13 +138,13 @@ class StormService {
|
|
129
138
|
gzip: true,
|
130
139
|
filter: (entry) => !entry.includes(tarballName),
|
131
140
|
}, ['.']);
|
132
|
-
const stormClient = new stormClient_1.StormClient(systemId);
|
141
|
+
const stormClient = new stormClient_1.StormClient(handle, systemId);
|
133
142
|
await stormClient.uploadSystem(handle, systemId, await promises_1.default.readFile(tarballFile));
|
134
143
|
}
|
135
144
|
async installProjectById(handle, systemId) {
|
136
145
|
const tarballFile = this.getConversationTarball(systemId);
|
137
146
|
const destDir = path_1.default.dirname(tarballFile);
|
138
|
-
const stormClient = new stormClient_1.StormClient(systemId);
|
147
|
+
const stormClient = new stormClient_1.StormClient(handle, systemId);
|
139
148
|
const buffer = await stormClient.downloadSystem(handle, systemId);
|
140
149
|
await promises_1.default.mkdir(destDir, { recursive: true });
|
141
150
|
await promises_1.default.writeFile(tarballFile, buffer);
|
@@ -145,6 +154,18 @@ class StormService {
|
|
145
154
|
});
|
146
155
|
await promises_1.default.unlink(tarballFile);
|
147
156
|
}
|
157
|
+
async saveThumbnail(systemId, thumbnail) {
|
158
|
+
const thumbnailFile = this.getThumbnailFile(systemId);
|
159
|
+
await promises_1.default.mkdir(path_1.default.dirname(thumbnailFile), { recursive: true });
|
160
|
+
await promises_1.default.writeFile(thumbnailFile, thumbnail);
|
161
|
+
}
|
162
|
+
async getThumbnail(systemId) {
|
163
|
+
const thumbnailFile = this.getThumbnailFile(systemId);
|
164
|
+
if ((0, fs_1.existsSync)(thumbnailFile)) {
|
165
|
+
return promises_1.default.readFile(thumbnailFile);
|
166
|
+
}
|
167
|
+
return null;
|
168
|
+
}
|
148
169
|
}
|
149
170
|
exports.StormService = StormService;
|
150
171
|
exports.default = new StormService();
|
@@ -11,7 +11,8 @@ function stringBody(req, res, next) {
|
|
11
11
|
req.on('data', (chunk) => {
|
12
12
|
body.push(chunk);
|
13
13
|
}).on('end', () => {
|
14
|
-
req.
|
14
|
+
req.body = Buffer.concat(body);
|
15
|
+
req.stringBody = req.body.toString();
|
15
16
|
next();
|
16
17
|
});
|
17
18
|
}
|
@@ -21,6 +21,7 @@ type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & {
|
|
21
21
|
export declare class PageQueue extends EventEmitter {
|
22
22
|
private readonly queue;
|
23
23
|
private readonly eventQueue;
|
24
|
+
private readonly handle;
|
24
25
|
private readonly systemId;
|
25
26
|
private readonly systemPrompt;
|
26
27
|
private readonly references;
|
@@ -28,7 +29,7 @@ export declare class PageQueue extends EventEmitter {
|
|
28
29
|
private readonly images;
|
29
30
|
private uiShells;
|
30
31
|
private theme;
|
31
|
-
constructor(systemId: string, systemPrompt: string, concurrency?: number);
|
32
|
+
constructor(handle: string, systemId: string, systemPrompt: string, concurrency?: number);
|
32
33
|
on(event: 'error', listener: (error: unknown) => void): this;
|
33
34
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
34
35
|
on(event: 'page', listener: (data: StormEventPage) => void | Promise<void>): this;
|