@superatomai/sdk-node 0.0.4 → 0.0.6
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/README.md +324 -57
- package/dist/index.d.mts +159 -11
- package/dist/index.d.ts +159 -11
- package/dist/index.js +1726 -472
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1712 -455
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -10,9 +10,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
10
10
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
11
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
12
|
});
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
};
|
|
13
16
|
var __commonJS = (cb, mod) => function __require2() {
|
|
14
17
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
18
|
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
16
23
|
var __copyProps = (to, from, except, desc) => {
|
|
17
24
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
25
|
for (let key of __getOwnPropNames(from))
|
|
@@ -102,7 +109,7 @@ var require_package = __commonJS({
|
|
|
102
109
|
var require_main = __commonJS({
|
|
103
110
|
"node_modules/dotenv/lib/main.js"(exports, module) {
|
|
104
111
|
"use strict";
|
|
105
|
-
var
|
|
112
|
+
var fs8 = __require("fs");
|
|
106
113
|
var path7 = __require("path");
|
|
107
114
|
var os4 = __require("os");
|
|
108
115
|
var crypto2 = __require("crypto");
|
|
@@ -244,7 +251,7 @@ var require_main = __commonJS({
|
|
|
244
251
|
if (options && options.path && options.path.length > 0) {
|
|
245
252
|
if (Array.isArray(options.path)) {
|
|
246
253
|
for (const filepath of options.path) {
|
|
247
|
-
if (
|
|
254
|
+
if (fs8.existsSync(filepath)) {
|
|
248
255
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
249
256
|
}
|
|
250
257
|
}
|
|
@@ -254,7 +261,7 @@ var require_main = __commonJS({
|
|
|
254
261
|
} else {
|
|
255
262
|
possibleVaultPath = path7.resolve(process.cwd(), ".env.vault");
|
|
256
263
|
}
|
|
257
|
-
if (
|
|
264
|
+
if (fs8.existsSync(possibleVaultPath)) {
|
|
258
265
|
return possibleVaultPath;
|
|
259
266
|
}
|
|
260
267
|
return null;
|
|
@@ -307,7 +314,7 @@ var require_main = __commonJS({
|
|
|
307
314
|
const parsedAll = {};
|
|
308
315
|
for (const path8 of optionPaths) {
|
|
309
316
|
try {
|
|
310
|
-
const parsed = DotenvModule.parse(
|
|
317
|
+
const parsed = DotenvModule.parse(fs8.readFileSync(path8, { encoding }));
|
|
311
318
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
312
319
|
} catch (e) {
|
|
313
320
|
if (debug) {
|
|
@@ -428,6 +435,128 @@ var require_main = __commonJS({
|
|
|
428
435
|
}
|
|
429
436
|
});
|
|
430
437
|
|
|
438
|
+
// src/userResponse/utils.ts
|
|
439
|
+
var utils_exports = {};
|
|
440
|
+
__export(utils_exports, {
|
|
441
|
+
convertTopToLimit: () => convertTopToLimit,
|
|
442
|
+
ensureQueryLimit: () => ensureQueryLimit,
|
|
443
|
+
fixScalarSubqueries: () => fixScalarSubqueries,
|
|
444
|
+
getJsonSizeInBytes: () => getJsonSizeInBytes,
|
|
445
|
+
validateMessageSize: () => validateMessageSize
|
|
446
|
+
});
|
|
447
|
+
function convertTopToLimit(query) {
|
|
448
|
+
if (!query || query.trim().length === 0) {
|
|
449
|
+
return query;
|
|
450
|
+
}
|
|
451
|
+
let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
|
|
452
|
+
if (modifiedQuery !== query) {
|
|
453
|
+
console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
|
|
454
|
+
}
|
|
455
|
+
return modifiedQuery;
|
|
456
|
+
}
|
|
457
|
+
function ensureQueryLimit(query, defaultLimit = 32, maxLimit = 32) {
|
|
458
|
+
if (!query || query.trim().length === 0) {
|
|
459
|
+
return query;
|
|
460
|
+
}
|
|
461
|
+
let trimmedQuery = query.trim();
|
|
462
|
+
const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
|
|
463
|
+
if (!isSelectQuery) {
|
|
464
|
+
return query;
|
|
465
|
+
}
|
|
466
|
+
trimmedQuery = convertTopToLimit(trimmedQuery);
|
|
467
|
+
const hadSemicolon = trimmedQuery.endsWith(";");
|
|
468
|
+
if (hadSemicolon) {
|
|
469
|
+
trimmedQuery = trimmedQuery.slice(0, -1).trim();
|
|
470
|
+
}
|
|
471
|
+
const limitMatches = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/gi);
|
|
472
|
+
if (limitMatches && limitMatches.length > 0) {
|
|
473
|
+
if (limitMatches.length > 1) {
|
|
474
|
+
console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
|
|
475
|
+
trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
|
|
476
|
+
} else {
|
|
477
|
+
const existingLimitMatch = trimmedQuery.match(/\bLIMIT\s+(\d+)\b/i);
|
|
478
|
+
if (existingLimitMatch) {
|
|
479
|
+
const existingLimit = parseInt(existingLimitMatch[1], 10);
|
|
480
|
+
if (existingLimit <= maxLimit) {
|
|
481
|
+
if (hadSemicolon) {
|
|
482
|
+
trimmedQuery += ";";
|
|
483
|
+
}
|
|
484
|
+
return trimmedQuery;
|
|
485
|
+
}
|
|
486
|
+
console.warn(`\u26A0\uFE0F Query LIMIT ${existingLimit} exceeds maximum of ${maxLimit}. Reducing to ${maxLimit}...`);
|
|
487
|
+
trimmedQuery = trimmedQuery.replace(/\bLIMIT\s+\d+\b/i, `LIMIT ${maxLimit}`);
|
|
488
|
+
if (hadSemicolon) {
|
|
489
|
+
trimmedQuery += ";";
|
|
490
|
+
}
|
|
491
|
+
return trimmedQuery;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
|
|
496
|
+
if (hadSemicolon) {
|
|
497
|
+
trimmedQuery += ";";
|
|
498
|
+
}
|
|
499
|
+
return trimmedQuery;
|
|
500
|
+
}
|
|
501
|
+
function fixScalarSubqueries(query) {
|
|
502
|
+
if (!query || query.trim().length === 0) {
|
|
503
|
+
return query;
|
|
504
|
+
}
|
|
505
|
+
let modifiedQuery = query;
|
|
506
|
+
let hasChanges = false;
|
|
507
|
+
const scalarOperatorPattern = /([=<>!]=?|<>)\s*\(\s*SELECT\s/gi;
|
|
508
|
+
const matches = [...modifiedQuery.matchAll(scalarOperatorPattern)];
|
|
509
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
510
|
+
const match = matches[i];
|
|
511
|
+
const startPos = match.index + match[0].length - "SELECT ".length;
|
|
512
|
+
let parenDepth = 1;
|
|
513
|
+
let endPos = startPos;
|
|
514
|
+
let foundEnd = false;
|
|
515
|
+
for (let j = startPos; j < modifiedQuery.length; j++) {
|
|
516
|
+
const char = modifiedQuery[j];
|
|
517
|
+
if (char === "(") parenDepth++;
|
|
518
|
+
if (char === ")") {
|
|
519
|
+
parenDepth--;
|
|
520
|
+
if (parenDepth === 0) {
|
|
521
|
+
endPos = j;
|
|
522
|
+
foundEnd = true;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!foundEnd) continue;
|
|
528
|
+
const subquery = modifiedQuery.substring(startPos, endPos);
|
|
529
|
+
if (/\bLIMIT\s+\d+/i.test(subquery)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const fixedSubquery = subquery.trim() + " LIMIT 1";
|
|
533
|
+
modifiedQuery = modifiedQuery.substring(0, startPos) + fixedSubquery + modifiedQuery.substring(endPos);
|
|
534
|
+
hasChanges = true;
|
|
535
|
+
console.warn(`\u26A0\uFE0F Fixed scalar subquery: added LIMIT 1 to prevent multiple row error`);
|
|
536
|
+
}
|
|
537
|
+
if (hasChanges) {
|
|
538
|
+
console.log("\u2713 Query validated and fixed for PostgreSQL scalar subquery compatibility");
|
|
539
|
+
}
|
|
540
|
+
return modifiedQuery;
|
|
541
|
+
}
|
|
542
|
+
function getJsonSizeInBytes(obj) {
|
|
543
|
+
const jsonString = JSON.stringify(obj);
|
|
544
|
+
return Buffer.byteLength(jsonString, "utf8");
|
|
545
|
+
}
|
|
546
|
+
function validateMessageSize(message, maxSize = 1048576) {
|
|
547
|
+
const size = getJsonSizeInBytes(message);
|
|
548
|
+
return {
|
|
549
|
+
isValid: size <= maxSize,
|
|
550
|
+
size,
|
|
551
|
+
maxSize
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
var init_utils = __esm({
|
|
555
|
+
"src/userResponse/utils.ts"() {
|
|
556
|
+
"use strict";
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
431
560
|
// src/websocket.ts
|
|
432
561
|
import WebSocket from "ws";
|
|
433
562
|
function createWebSocket(url) {
|
|
@@ -624,6 +753,18 @@ var DSLRendererPropsSchema2 = z2.object({
|
|
|
624
753
|
});
|
|
625
754
|
|
|
626
755
|
// src/types.ts
|
|
756
|
+
var UserSchema = z3.object({
|
|
757
|
+
username: z3.string().min(1, "Username is required"),
|
|
758
|
+
email: z3.string().email("Invalid email format").optional(),
|
|
759
|
+
password: z3.string().min(1, "Password is required"),
|
|
760
|
+
fullname: z3.string().optional(),
|
|
761
|
+
role: z3.string().optional(),
|
|
762
|
+
wsIds: z3.array(z3.string()).optional()
|
|
763
|
+
// Only in memory, not persisted to file
|
|
764
|
+
});
|
|
765
|
+
var UsersDataSchema = z3.object({
|
|
766
|
+
users: z3.array(UserSchema)
|
|
767
|
+
});
|
|
627
768
|
var MessageParticipantSchema = z3.object({
|
|
628
769
|
id: z3.string().optional(),
|
|
629
770
|
type: z3.string().optional()
|
|
@@ -677,7 +818,8 @@ var UserPromptRequestPayloadSchema = z3.object({
|
|
|
677
818
|
SA_RUNTIME: z3.object({
|
|
678
819
|
threadId: z3.string(),
|
|
679
820
|
uiBlockId: z3.string()
|
|
680
|
-
}).optional()
|
|
821
|
+
}).optional(),
|
|
822
|
+
responseMode: z3.enum(["component", "text"]).optional()
|
|
681
823
|
});
|
|
682
824
|
var UserPromptRequestMessageSchema = z3.object({
|
|
683
825
|
id: z3.string(),
|
|
@@ -724,7 +866,10 @@ var UsersRequestPayloadSchema = z3.object({
|
|
|
724
866
|
operation: z3.enum(["create", "update", "delete", "getAll", "getOne"]),
|
|
725
867
|
data: z3.object({
|
|
726
868
|
username: z3.string().optional(),
|
|
727
|
-
|
|
869
|
+
email: z3.string().email("Invalid email format").optional(),
|
|
870
|
+
password: z3.string().optional(),
|
|
871
|
+
fullname: z3.string().optional(),
|
|
872
|
+
role: z3.string().optional()
|
|
728
873
|
}).optional()
|
|
729
874
|
});
|
|
730
875
|
var UsersRequestMessageSchema = z3.object({
|
|
@@ -789,21 +934,97 @@ var ReportsRequestMessageSchema = z3.object({
|
|
|
789
934
|
});
|
|
790
935
|
|
|
791
936
|
// src/utils/logger.ts
|
|
937
|
+
import fs from "fs";
|
|
792
938
|
var PREFIX = "[SuperatomSDK]";
|
|
793
|
-
var
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
939
|
+
var LOGSTREAM = fs.createWriteStream("superatom-sdk.log", { flags: "a" });
|
|
940
|
+
var LOG_LEVEL_PRIORITY = {
|
|
941
|
+
errors: 0,
|
|
942
|
+
warnings: 1,
|
|
943
|
+
info: 2,
|
|
944
|
+
verbose: 3
|
|
945
|
+
};
|
|
946
|
+
var MESSAGE_LEVEL_PRIORITY = {
|
|
947
|
+
error: 0,
|
|
948
|
+
warn: 1,
|
|
949
|
+
info: 2,
|
|
950
|
+
debug: 3
|
|
951
|
+
};
|
|
952
|
+
var Logger = class {
|
|
953
|
+
constructor() {
|
|
954
|
+
const envLevel = (process.env.SUPERATOM_LOG_LEVEL || "info").toLowerCase();
|
|
955
|
+
if (this.isValidLogLevel(envLevel)) {
|
|
956
|
+
this.currentLevel = envLevel;
|
|
957
|
+
} else {
|
|
958
|
+
this.currentLevel = "info";
|
|
959
|
+
console.warn(
|
|
960
|
+
`${PREFIX} Invalid log level "${envLevel}". Using default "info". Valid levels: errors, warnings, info, verbose`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
this.currentLevelPriority = LOG_LEVEL_PRIORITY[this.currentLevel];
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Check if a string is a valid log level
|
|
967
|
+
*/
|
|
968
|
+
isValidLogLevel(level) {
|
|
969
|
+
return level === "errors" || level === "warnings" || level === "info" || level === "verbose";
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Check if a message should be logged based on current log level
|
|
973
|
+
*/
|
|
974
|
+
shouldLog(messageLevel) {
|
|
975
|
+
const messagePriority = MESSAGE_LEVEL_PRIORITY[messageLevel];
|
|
976
|
+
return messagePriority <= this.currentLevelPriority;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Get current log level
|
|
980
|
+
*/
|
|
981
|
+
getLogLevel() {
|
|
982
|
+
return this.currentLevel;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Set log level programmatically
|
|
986
|
+
*/
|
|
987
|
+
setLogLevel(level) {
|
|
988
|
+
this.currentLevel = level;
|
|
989
|
+
this.currentLevelPriority = LOG_LEVEL_PRIORITY[level];
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Log info message (shown for info and verbose levels)
|
|
993
|
+
*/
|
|
994
|
+
info(...args) {
|
|
995
|
+
if (this.shouldLog("info")) {
|
|
996
|
+
console.log(PREFIX, ...args);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Log error message (shown for all levels)
|
|
1001
|
+
*/
|
|
1002
|
+
error(...args) {
|
|
1003
|
+
if (this.shouldLog("error")) {
|
|
1004
|
+
console.error(PREFIX, ...args);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Log warning message (shown for warnings, info, and verbose levels)
|
|
1009
|
+
*/
|
|
1010
|
+
warn(...args) {
|
|
1011
|
+
if (this.shouldLog("warn")) {
|
|
1012
|
+
console.warn(PREFIX, ...args);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Log debug message (only shown for verbose level)
|
|
1017
|
+
*/
|
|
1018
|
+
debug(...args) {
|
|
1019
|
+
if (this.shouldLog("debug")) {
|
|
1020
|
+
console.log(PREFIX, "[DEBUG]", ...args);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
file(...args) {
|
|
1024
|
+
LOGSTREAM.write(args.join(" ") + "\n");
|
|
805
1025
|
}
|
|
806
1026
|
};
|
|
1027
|
+
var logger = new Logger();
|
|
807
1028
|
|
|
808
1029
|
// src/threads/uiblock.ts
|
|
809
1030
|
import { randomUUID } from "crypto";
|
|
@@ -838,13 +1059,15 @@ var UIBlock = class {
|
|
|
838
1059
|
* @param generatedComponentMetadata - Optional metadata about the generated component
|
|
839
1060
|
* @param actions - Optional array of available actions
|
|
840
1061
|
* @param id - Optional custom ID, generates UUID if not provided
|
|
1062
|
+
* @param textResponse - Optional text response from LLM
|
|
841
1063
|
*/
|
|
842
|
-
constructor(userQuestion, componentData = {}, generatedComponentMetadata = {}, actions = [], id) {
|
|
1064
|
+
constructor(userQuestion, componentData = {}, generatedComponentMetadata = {}, actions = [], id, textResponse = null) {
|
|
843
1065
|
this.id = id || randomUUID();
|
|
844
1066
|
this.userQuestion = userQuestion;
|
|
845
1067
|
this.componentData = componentData;
|
|
846
1068
|
this.generatedComponentMetadata = generatedComponentMetadata;
|
|
847
1069
|
this.actions = actions;
|
|
1070
|
+
this.textResponse = textResponse;
|
|
848
1071
|
this.createdAt = /* @__PURE__ */ new Date();
|
|
849
1072
|
}
|
|
850
1073
|
/**
|
|
@@ -871,6 +1094,9 @@ var UIBlock = class {
|
|
|
871
1094
|
getComponentMetadata() {
|
|
872
1095
|
return this.generatedComponentMetadata;
|
|
873
1096
|
}
|
|
1097
|
+
getTextResponse() {
|
|
1098
|
+
return this.textResponse || "";
|
|
1099
|
+
}
|
|
874
1100
|
/**
|
|
875
1101
|
* Set or update component metadata
|
|
876
1102
|
*/
|
|
@@ -963,6 +1189,12 @@ var UIBlock = class {
|
|
|
963
1189
|
const processedData = this.processDataForStorage(data);
|
|
964
1190
|
this.componentData = { ...this.componentData, ...processedData };
|
|
965
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Set or update text response
|
|
1194
|
+
*/
|
|
1195
|
+
setTextResponse(textResponse) {
|
|
1196
|
+
this.textResponse = textResponse;
|
|
1197
|
+
}
|
|
966
1198
|
/**
|
|
967
1199
|
* Get all actions (only if they are resolved, not if fetching)
|
|
968
1200
|
*/
|
|
@@ -1046,6 +1278,7 @@ var UIBlock = class {
|
|
|
1046
1278
|
userQuestion: this.userQuestion,
|
|
1047
1279
|
generatedComponentMetadata: this.generatedComponentMetadata,
|
|
1048
1280
|
componentData: this.componentData,
|
|
1281
|
+
textResponse: this.textResponse,
|
|
1049
1282
|
actions: actionsValue,
|
|
1050
1283
|
isFetchingActions: this.actions instanceof Promise,
|
|
1051
1284
|
createdAt: this.createdAt.toISOString()
|
|
@@ -1146,8 +1379,11 @@ var Thread = class {
|
|
|
1146
1379
|
const questionNum = index + 1;
|
|
1147
1380
|
const question = block.getUserQuestion();
|
|
1148
1381
|
const metadata = block.getComponentMetadata();
|
|
1149
|
-
|
|
1150
|
-
|
|
1382
|
+
const textResponse = block.getTextResponse();
|
|
1383
|
+
let assistantResponse = "";
|
|
1384
|
+
const hasComponent = metadata && Object.keys(metadata).length > 0 && metadata.type;
|
|
1385
|
+
const hasTextResponse = textResponse && textResponse.trim().length > 0;
|
|
1386
|
+
if (hasComponent) {
|
|
1151
1387
|
const parts = [];
|
|
1152
1388
|
if (metadata.type) {
|
|
1153
1389
|
parts.push(`Component Type: ${metadata.type}`);
|
|
@@ -1167,11 +1403,17 @@ var Thread = class {
|
|
|
1167
1403
|
const componentTypes = metadata.props.config.components.map((c) => c.type).join(", ");
|
|
1168
1404
|
parts.push(`Multi-component with: ${componentTypes}`);
|
|
1169
1405
|
}
|
|
1170
|
-
|
|
1406
|
+
assistantResponse = parts.join(", ");
|
|
1407
|
+
} else if (hasTextResponse) {
|
|
1408
|
+
assistantResponse = textResponse;
|
|
1409
|
+
} else {
|
|
1410
|
+
assistantResponse = "No response generated";
|
|
1171
1411
|
}
|
|
1172
|
-
contextLines.push(`
|
|
1173
|
-
|
|
1174
|
-
contextLines.push(
|
|
1412
|
+
contextLines.push(`User:
|
|
1413
|
+
${question}`);
|
|
1414
|
+
contextLines.push(`Assistant:
|
|
1415
|
+
${assistantResponse}`);
|
|
1416
|
+
contextLines.push("---");
|
|
1175
1417
|
});
|
|
1176
1418
|
return contextLines.join("\n").trim();
|
|
1177
1419
|
}
|
|
@@ -1347,7 +1589,7 @@ function sendDataResponse(id, collection, op, data, meta, sendMessage) {
|
|
|
1347
1589
|
}
|
|
1348
1590
|
|
|
1349
1591
|
// src/bundle.ts
|
|
1350
|
-
import * as
|
|
1592
|
+
import * as fs2 from "fs";
|
|
1351
1593
|
import * as path from "path";
|
|
1352
1594
|
function getBundleDir(configDir) {
|
|
1353
1595
|
const bundleDir = configDir || process.env.SA_BUNDLE_DIR;
|
|
@@ -1360,16 +1602,16 @@ function getBundleDir(configDir) {
|
|
|
1360
1602
|
}
|
|
1361
1603
|
function getJS(bundleDir) {
|
|
1362
1604
|
try {
|
|
1363
|
-
if (!
|
|
1605
|
+
if (!fs2.existsSync(bundleDir)) {
|
|
1364
1606
|
throw new Error(`Bundle directory does not exist: ${bundleDir}`);
|
|
1365
1607
|
}
|
|
1366
|
-
const stats =
|
|
1608
|
+
const stats = fs2.statSync(bundleDir);
|
|
1367
1609
|
if (!stats.isDirectory()) {
|
|
1368
1610
|
throw new Error(`Bundle path is not a directory: ${bundleDir}`);
|
|
1369
1611
|
}
|
|
1370
1612
|
let files;
|
|
1371
1613
|
try {
|
|
1372
|
-
files =
|
|
1614
|
+
files = fs2.readdirSync(bundleDir);
|
|
1373
1615
|
} catch (error) {
|
|
1374
1616
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1375
1617
|
throw new Error(`Failed to read bundle directory: ${errorMessage}`);
|
|
@@ -1384,7 +1626,7 @@ function getJS(bundleDir) {
|
|
|
1384
1626
|
const filePath = path.join(bundleDir, indexFile);
|
|
1385
1627
|
logger.info(`Loading bundle from ${filePath}`);
|
|
1386
1628
|
try {
|
|
1387
|
-
return
|
|
1629
|
+
return fs2.readFileSync(filePath, "utf8");
|
|
1388
1630
|
} catch (error) {
|
|
1389
1631
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1390
1632
|
throw new Error(`Failed to read bundle file: ${errorMessage}`);
|
|
@@ -1480,9 +1722,9 @@ function getUserManager() {
|
|
|
1480
1722
|
}
|
|
1481
1723
|
return currentUserManager;
|
|
1482
1724
|
}
|
|
1483
|
-
function
|
|
1725
|
+
function findUserByUsernameOrEmail(identifier) {
|
|
1484
1726
|
const manager = getUserManager();
|
|
1485
|
-
const user = manager.
|
|
1727
|
+
const user = manager.getUserByUsernameOrEmail(identifier);
|
|
1486
1728
|
return user || null;
|
|
1487
1729
|
}
|
|
1488
1730
|
function addWsIdToUser(username, wsId) {
|
|
@@ -1504,60 +1746,81 @@ async function cleanupUserStorage() {
|
|
|
1504
1746
|
|
|
1505
1747
|
// src/auth/validator.ts
|
|
1506
1748
|
function validateUser(credentials) {
|
|
1507
|
-
const { username, password } = credentials;
|
|
1508
|
-
|
|
1509
|
-
|
|
1749
|
+
const { username, email, password } = credentials;
|
|
1750
|
+
const identifier = username || email;
|
|
1751
|
+
logger.debug("[validateUser] Starting user validation");
|
|
1752
|
+
logger.debug(`[validateUser] Username provided: ${username ? "\u2713" : "\u2717"}, Email provided: ${email ? "\u2713" : "\u2717"}, Password provided: ${password ? "\u2713" : "\u2717"}`);
|
|
1753
|
+
if (!identifier || !password) {
|
|
1754
|
+
logger.warn("[validateUser] Validation failed: Username/email and password are required");
|
|
1510
1755
|
return {
|
|
1511
1756
|
success: false,
|
|
1512
|
-
error: "Username and password are required"
|
|
1757
|
+
error: "Username or email and password are required"
|
|
1513
1758
|
};
|
|
1514
1759
|
}
|
|
1515
|
-
|
|
1760
|
+
logger.debug(`[validateUser] Looking up user by identifier: ${identifier}`);
|
|
1761
|
+
const user = findUserByUsernameOrEmail(identifier);
|
|
1516
1762
|
if (!user) {
|
|
1517
|
-
logger.warn(`Validation failed: User not found - ${
|
|
1763
|
+
logger.warn(`[validateUser] Validation failed: User not found - ${identifier}`);
|
|
1518
1764
|
return {
|
|
1519
1765
|
success: false,
|
|
1520
|
-
error: "Invalid username"
|
|
1766
|
+
error: "Invalid username or email"
|
|
1521
1767
|
};
|
|
1522
1768
|
}
|
|
1769
|
+
logger.debug(`[validateUser] User found: ${user.username}, verifying password`);
|
|
1523
1770
|
const hashedPassword = hashPassword(user.password);
|
|
1524
1771
|
if (hashedPassword !== password) {
|
|
1525
|
-
logger.warn(`Validation failed: Invalid password for user - ${username}`);
|
|
1772
|
+
logger.warn(`[validateUser] Validation failed: Invalid password for user - ${user.username}`);
|
|
1773
|
+
logger.debug(`[validateUser] Password hash mismatch for user: ${user.username}`);
|
|
1526
1774
|
return {
|
|
1527
1775
|
success: false,
|
|
1528
1776
|
error: "Invalid password"
|
|
1529
1777
|
};
|
|
1530
1778
|
}
|
|
1531
|
-
logger.
|
|
1779
|
+
logger.info(`[validateUser] \u2713 User validated successfully: ${user.username}`);
|
|
1780
|
+
logger.debug(`[validateUser] Returning user data for: ${user.username}`);
|
|
1532
1781
|
return {
|
|
1533
1782
|
success: true,
|
|
1534
|
-
data: user.username
|
|
1783
|
+
data: user.username,
|
|
1784
|
+
username: user.username
|
|
1535
1785
|
};
|
|
1536
1786
|
}
|
|
1537
1787
|
function authenticateAndStoreWsId(credentials, wsId) {
|
|
1788
|
+
const identifier = credentials.username || credentials.email;
|
|
1789
|
+
logger.debug("[authenticateAndStoreWsId] Starting authentication and WebSocket ID storage");
|
|
1790
|
+
logger.debug("[authenticateAndStoreWsId] Validating user credentials");
|
|
1538
1791
|
const validationResult = validateUser(credentials);
|
|
1539
1792
|
if (!validationResult.success) {
|
|
1793
|
+
logger.warn(`[authenticateAndStoreWsId] User validation failed for: ${identifier}`);
|
|
1540
1794
|
return validationResult;
|
|
1541
1795
|
}
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
error: "Failed to store user session"
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
logger.info(`WebSocket ID stored for user: ${credentials.username}`);
|
|
1796
|
+
const username = validationResult.username;
|
|
1797
|
+
logger.info(`[authenticateAndStoreWsId] User ${username} validated, storing WebSocket ID`);
|
|
1798
|
+
logger.debug(`[authenticateAndStoreWsId] Calling addWsIdToUser for ${username}`);
|
|
1799
|
+
addWsIdToUser(username, wsId);
|
|
1800
|
+
logger.debug(`[authenticateAndStoreWsId] WebSocket ID ${wsId} associated with user ${username}`);
|
|
1551
1801
|
return validationResult;
|
|
1552
1802
|
}
|
|
1553
1803
|
function verifyAuthToken(authToken) {
|
|
1554
1804
|
try {
|
|
1805
|
+
logger.debug("[verifyAuthToken] Starting token verification");
|
|
1806
|
+
logger.debug("[verifyAuthToken] Decoding base64 token");
|
|
1555
1807
|
const decodedString = Buffer.from(authToken, "base64").toString("utf-8");
|
|
1808
|
+
logger.debug("[verifyAuthToken] Parsing decoded token as JSON");
|
|
1556
1809
|
const credentials = JSON.parse(decodedString);
|
|
1557
|
-
logger.debug("Token decoded and parsed successfully");
|
|
1558
|
-
|
|
1810
|
+
logger.debug("[verifyAuthToken] Token decoded and parsed successfully");
|
|
1811
|
+
logger.debug(`[verifyAuthToken] Token contains username: ${credentials.username ? "\u2713" : "\u2717"}`);
|
|
1812
|
+
logger.debug("[verifyAuthToken] Validating credentials from token");
|
|
1813
|
+
const result = validateUser(credentials);
|
|
1814
|
+
if (result.success) {
|
|
1815
|
+
logger.info(`[verifyAuthToken] \u2713 Token verified successfully for user: ${credentials.username || "unknown"}`);
|
|
1816
|
+
} else {
|
|
1817
|
+
logger.warn(`[verifyAuthToken] Token verification failed: ${result.error}`);
|
|
1818
|
+
}
|
|
1819
|
+
return result;
|
|
1559
1820
|
} catch (error) {
|
|
1560
|
-
|
|
1821
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1822
|
+
logger.error(`[verifyAuthToken] Failed to verify auth token: ${errorMsg}`);
|
|
1823
|
+
logger.debug("[verifyAuthToken] Token verification error details:", error);
|
|
1561
1824
|
return {
|
|
1562
1825
|
success: false,
|
|
1563
1826
|
error: "Invalid token format"
|
|
@@ -1568,36 +1831,49 @@ function verifyAuthToken(authToken) {
|
|
|
1568
1831
|
// src/handlers/auth-login-requests.ts
|
|
1569
1832
|
async function handleAuthLoginRequest(data, sendMessage) {
|
|
1570
1833
|
try {
|
|
1834
|
+
logger.debug("[AUTH_LOGIN_REQ] Parsing incoming auth login request");
|
|
1571
1835
|
const authRequest = AuthLoginRequestMessageSchema.parse(data);
|
|
1572
1836
|
const { id, payload } = authRequest;
|
|
1573
1837
|
const login_data = payload.login_data;
|
|
1574
1838
|
const wsId = authRequest.from.id;
|
|
1839
|
+
logger.info(`[AUTH_LOGIN_REQ ${id}] Processing auth login request from client: ${wsId}`);
|
|
1840
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Login data present: ${!!login_data}`);
|
|
1575
1841
|
if (!login_data) {
|
|
1842
|
+
logger.error(`[AUTH_LOGIN_REQ ${id}] Login data not found in request`);
|
|
1576
1843
|
sendDataResponse2(id, {
|
|
1577
1844
|
success: false,
|
|
1578
1845
|
error: "Login data not found"
|
|
1579
1846
|
}, sendMessage, wsId);
|
|
1580
1847
|
return;
|
|
1581
1848
|
}
|
|
1849
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Decoding base64 login data`);
|
|
1582
1850
|
let loginData;
|
|
1583
1851
|
try {
|
|
1584
1852
|
loginData = decodeBase64ToJson(login_data);
|
|
1853
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Login data decoded successfully`);
|
|
1585
1854
|
} catch (error) {
|
|
1855
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1856
|
+
logger.error(`[AUTH_LOGIN_REQ ${id}] Failed to decode login data: ${errorMsg}`);
|
|
1857
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Decode error details:`, error);
|
|
1586
1858
|
sendDataResponse2(id, {
|
|
1587
1859
|
success: false,
|
|
1588
1860
|
error: "Invalid login data format"
|
|
1589
1861
|
}, sendMessage, wsId);
|
|
1590
1862
|
return;
|
|
1591
1863
|
}
|
|
1592
|
-
const { username, password } = loginData;
|
|
1593
|
-
|
|
1864
|
+
const { username, email, password } = loginData;
|
|
1865
|
+
const identifier = username || email;
|
|
1866
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Validating credentials - username: ${username ? "\u2713" : "\u2717"}, email: ${email ? "\u2713" : "\u2717"}, password: ${password ? "\u2713" : "\u2717"}`);
|
|
1867
|
+
if (!identifier) {
|
|
1868
|
+
logger.error(`[AUTH_LOGIN_REQ ${id}] Username or email not found in login data`);
|
|
1594
1869
|
sendDataResponse2(id, {
|
|
1595
1870
|
success: false,
|
|
1596
|
-
error: "Username
|
|
1871
|
+
error: "Username or email is required"
|
|
1597
1872
|
}, sendMessage, wsId);
|
|
1598
1873
|
return;
|
|
1599
1874
|
}
|
|
1600
1875
|
if (!password) {
|
|
1876
|
+
logger.error(`[AUTH_LOGIN_REQ ${id}] Password not found in login data`);
|
|
1601
1877
|
sendDataResponse2(id, {
|
|
1602
1878
|
success: false,
|
|
1603
1879
|
error: "Password not found in login data"
|
|
@@ -1605,20 +1881,46 @@ async function handleAuthLoginRequest(data, sendMessage) {
|
|
|
1605
1881
|
return;
|
|
1606
1882
|
}
|
|
1607
1883
|
if (!wsId) {
|
|
1884
|
+
logger.error(`[AUTH_LOGIN_REQ ${id}] WebSocket ID not found in request`);
|
|
1608
1885
|
sendDataResponse2(id, {
|
|
1609
1886
|
success: false,
|
|
1610
1887
|
error: "WebSocket ID not found"
|
|
1611
1888
|
}, sendMessage, wsId);
|
|
1612
1889
|
return;
|
|
1613
1890
|
}
|
|
1891
|
+
logger.info(`[AUTH_LOGIN_REQ ${id}] Credentials validated, authenticating user: ${identifier}`);
|
|
1892
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] WebSocket ID: ${wsId}`);
|
|
1893
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Calling authenticateAndStoreWsId for user: ${identifier}`);
|
|
1614
1894
|
const authResult = authenticateAndStoreWsId(
|
|
1615
|
-
{ username, password },
|
|
1895
|
+
{ username, email, password },
|
|
1616
1896
|
wsId
|
|
1617
1897
|
);
|
|
1898
|
+
logger.info(`[AUTH_LOGIN_REQ ${id}] Authentication result for ${identifier}: ${authResult.success ? "success" : "failed"}`);
|
|
1899
|
+
if (!authResult.success) {
|
|
1900
|
+
logger.warn(`[AUTH_LOGIN_REQ ${id}] Authentication failed for ${identifier}: ${authResult.error}`);
|
|
1901
|
+
} else {
|
|
1902
|
+
logger.info(`[AUTH_LOGIN_REQ ${id}] User ${authResult.username || identifier} authenticated successfully`);
|
|
1903
|
+
}
|
|
1904
|
+
logger.debug(`[AUTH_LOGIN_REQ ${id}] Sending auth response to client`);
|
|
1618
1905
|
sendDataResponse2(id, authResult, sendMessage, wsId);
|
|
1906
|
+
logger.info(`[AUTH_LOGIN_REQ ${id}] ${authResult.success ? "\u2713" : "\u2717"} Auth login request completed`);
|
|
1619
1907
|
return;
|
|
1620
1908
|
} catch (error) {
|
|
1621
|
-
|
|
1909
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1910
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
1911
|
+
logger.error(`[AUTH_LOGIN_REQ] Failed to handle auth login request: ${errorMessage}`);
|
|
1912
|
+
logger.debug(`[AUTH_LOGIN_REQ] Error stack trace:`, errorStack);
|
|
1913
|
+
try {
|
|
1914
|
+
const parsedData = data;
|
|
1915
|
+
if (parsedData?.id) {
|
|
1916
|
+
sendDataResponse2(parsedData.id, {
|
|
1917
|
+
success: false,
|
|
1918
|
+
error: `Internal error: ${errorMessage}`
|
|
1919
|
+
}, sendMessage, parsedData.from?.id);
|
|
1920
|
+
}
|
|
1921
|
+
} catch (sendError) {
|
|
1922
|
+
logger.error("[AUTH_LOGIN_REQ] Failed to send error response:", sendError);
|
|
1923
|
+
}
|
|
1622
1924
|
}
|
|
1623
1925
|
}
|
|
1624
1926
|
function sendDataResponse2(id, res, sendMessage, clientId) {
|
|
@@ -1634,17 +1936,27 @@ function sendDataResponse2(id, res, sendMessage, clientId) {
|
|
|
1634
1936
|
...res
|
|
1635
1937
|
}
|
|
1636
1938
|
};
|
|
1939
|
+
logger.debug(`[AUTH_LOGIN_RES ${id}] Sending ${res.success ? "successful" : "failed"} auth response to client: ${clientId}`);
|
|
1940
|
+
logger.debug(`[AUTH_LOGIN_RES ${id}] Response payload size: ${JSON.stringify(response).length} bytes`);
|
|
1941
|
+
if (res.error) {
|
|
1942
|
+
logger.debug(`[AUTH_LOGIN_RES ${id}] Error message: ${res.error}`);
|
|
1943
|
+
}
|
|
1637
1944
|
sendMessage(response);
|
|
1638
1945
|
}
|
|
1639
1946
|
|
|
1640
1947
|
// src/handlers/auth-verify-request.ts
|
|
1641
1948
|
async function handleAuthVerifyRequest(data, sendMessage) {
|
|
1642
1949
|
try {
|
|
1950
|
+
logger.debug("[AUTH_VERIFY_REQ] Parsing incoming auth verify request");
|
|
1643
1951
|
const authRequest = AuthVerifyRequestMessageSchema.parse(data);
|
|
1644
1952
|
const { id, payload } = authRequest;
|
|
1645
1953
|
const token = payload.token;
|
|
1646
1954
|
const wsId = authRequest.from.id;
|
|
1955
|
+
logger.info(`[AUTH_VERIFY_REQ ${id}] Processing auth verify request from client: ${wsId}`);
|
|
1956
|
+
logger.debug(`[AUTH_VERIFY_REQ ${id}] Token present: ${!!token}`);
|
|
1957
|
+
logger.debug(`[AUTH_VERIFY_REQ ${id}] Token length: ${token ? token.length : 0} characters`);
|
|
1647
1958
|
if (!token) {
|
|
1959
|
+
logger.error(`[AUTH_VERIFY_REQ ${id}] Token not found in request`);
|
|
1648
1960
|
sendDataResponse3(id, {
|
|
1649
1961
|
success: false,
|
|
1650
1962
|
error: "Token not found"
|
|
@@ -1652,17 +1964,45 @@ async function handleAuthVerifyRequest(data, sendMessage) {
|
|
|
1652
1964
|
return;
|
|
1653
1965
|
}
|
|
1654
1966
|
if (!wsId) {
|
|
1967
|
+
logger.error(`[AUTH_VERIFY_REQ ${id}] WebSocket ID not found in request`);
|
|
1655
1968
|
sendDataResponse3(id, {
|
|
1656
1969
|
success: false,
|
|
1657
1970
|
error: "WebSocket ID not found"
|
|
1658
1971
|
}, sendMessage, wsId);
|
|
1659
1972
|
return;
|
|
1660
1973
|
}
|
|
1974
|
+
logger.info(`[AUTH_VERIFY_REQ ${id}] Token validation starting`);
|
|
1975
|
+
logger.debug(`[AUTH_VERIFY_REQ ${id}] WebSocket ID: ${wsId}`);
|
|
1976
|
+
logger.debug(`[AUTH_VERIFY_REQ ${id}] Calling verifyAuthToken`);
|
|
1977
|
+
const startTime = Date.now();
|
|
1661
1978
|
const authResult = verifyAuthToken(token);
|
|
1979
|
+
const verificationTime = Date.now() - startTime;
|
|
1980
|
+
logger.info(`[AUTH_VERIFY_REQ ${id}] Token verification completed in ${verificationTime}ms - ${authResult.success ? "valid" : "invalid"}`);
|
|
1981
|
+
if (!authResult.success) {
|
|
1982
|
+
logger.warn(`[AUTH_VERIFY_REQ ${id}] Token verification failed: ${authResult.error}`);
|
|
1983
|
+
} else {
|
|
1984
|
+
logger.info(`[AUTH_VERIFY_REQ ${id}] Token verified successfully for user: ${authResult.data || "unknown"}`);
|
|
1985
|
+
}
|
|
1986
|
+
logger.debug(`[AUTH_VERIFY_REQ ${id}] Sending verification response to client`);
|
|
1662
1987
|
sendDataResponse3(id, authResult, sendMessage, wsId);
|
|
1988
|
+
logger.info(`[AUTH_VERIFY_REQ ${id}] ${authResult.success ? "\u2713" : "\u2717"} Auth verify request completed`);
|
|
1663
1989
|
return;
|
|
1664
1990
|
} catch (error) {
|
|
1665
|
-
|
|
1991
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1992
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
1993
|
+
logger.error(`[AUTH_VERIFY_REQ] Failed to handle auth verify request: ${errorMessage}`);
|
|
1994
|
+
logger.debug(`[AUTH_VERIFY_REQ] Error stack trace:`, errorStack);
|
|
1995
|
+
try {
|
|
1996
|
+
const parsedData = data;
|
|
1997
|
+
if (parsedData?.id) {
|
|
1998
|
+
sendDataResponse3(parsedData.id, {
|
|
1999
|
+
success: false,
|
|
2000
|
+
error: `Internal error: ${errorMessage}`
|
|
2001
|
+
}, sendMessage, parsedData.from?.id);
|
|
2002
|
+
}
|
|
2003
|
+
} catch (sendError) {
|
|
2004
|
+
logger.error("[AUTH_VERIFY_REQ] Failed to send error response:", sendError);
|
|
2005
|
+
}
|
|
1666
2006
|
}
|
|
1667
2007
|
}
|
|
1668
2008
|
function sendDataResponse3(id, res, sendMessage, clientId) {
|
|
@@ -1678,59 +2018,26 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
|
|
|
1678
2018
|
...res
|
|
1679
2019
|
}
|
|
1680
2020
|
};
|
|
2021
|
+
logger.debug(`[AUTH_VERIFY_RES ${id}] Sending ${res.success ? "successful" : "failed"} verification response to client: ${clientId}`);
|
|
2022
|
+
logger.debug(`[AUTH_VERIFY_RES ${id}] Response payload size: ${JSON.stringify(response).length} bytes`);
|
|
2023
|
+
if (res.error) {
|
|
2024
|
+
logger.debug(`[AUTH_VERIFY_RES ${id}] Error message: ${res.error}`);
|
|
2025
|
+
}
|
|
2026
|
+
if (res.data) {
|
|
2027
|
+
logger.debug(`[AUTH_VERIFY_RES ${id}] User verified: ${res.data}`);
|
|
2028
|
+
}
|
|
1681
2029
|
sendMessage(response);
|
|
1682
2030
|
}
|
|
1683
2031
|
|
|
1684
2032
|
// src/userResponse/groq.ts
|
|
1685
2033
|
var import_dotenv = __toESM(require_main());
|
|
1686
2034
|
|
|
1687
|
-
// src/userResponse/
|
|
1688
|
-
|
|
1689
|
-
if (!query || query.trim().length === 0) {
|
|
1690
|
-
return query;
|
|
1691
|
-
}
|
|
1692
|
-
let modifiedQuery = query.replace(/\bSELECT\s+TOP\s+(\d+)\b/gi, "SELECT");
|
|
1693
|
-
if (modifiedQuery !== query) {
|
|
1694
|
-
console.warn(`\u26A0\uFE0F Query had TOP syntax. Converting to LIMIT for Snowflake compatibility.`);
|
|
1695
|
-
}
|
|
1696
|
-
return modifiedQuery;
|
|
1697
|
-
}
|
|
1698
|
-
function ensureQueryLimit(query, defaultLimit = 50) {
|
|
1699
|
-
if (!query || query.trim().length === 0) {
|
|
1700
|
-
return query;
|
|
1701
|
-
}
|
|
1702
|
-
let trimmedQuery = query.trim();
|
|
1703
|
-
const isSelectQuery = /^\s*SELECT\b/i.test(trimmedQuery) || /^\s*WITH\b.*\bSELECT\b/is.test(trimmedQuery);
|
|
1704
|
-
if (!isSelectQuery) {
|
|
1705
|
-
return query;
|
|
1706
|
-
}
|
|
1707
|
-
trimmedQuery = convertTopToLimit(trimmedQuery);
|
|
1708
|
-
const hadSemicolon = trimmedQuery.endsWith(";");
|
|
1709
|
-
if (hadSemicolon) {
|
|
1710
|
-
trimmedQuery = trimmedQuery.slice(0, -1).trim();
|
|
1711
|
-
}
|
|
1712
|
-
const limitMatches = trimmedQuery.match(/\bLIMIT\s+\d+\b/gi);
|
|
1713
|
-
if (limitMatches && limitMatches.length > 0) {
|
|
1714
|
-
if (limitMatches.length > 1) {
|
|
1715
|
-
console.warn(`\u26A0\uFE0F Query had ${limitMatches.length} LIMIT clauses. Removing duplicates...`);
|
|
1716
|
-
trimmedQuery = trimmedQuery.replace(/\s*\bLIMIT\s+\d+\b/gi, "").trim();
|
|
1717
|
-
} else {
|
|
1718
|
-
if (hadSemicolon) {
|
|
1719
|
-
trimmedQuery += ";";
|
|
1720
|
-
}
|
|
1721
|
-
return trimmedQuery;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
trimmedQuery = `${trimmedQuery} LIMIT ${defaultLimit}`;
|
|
1725
|
-
if (hadSemicolon) {
|
|
1726
|
-
trimmedQuery += ";";
|
|
1727
|
-
}
|
|
1728
|
-
return trimmedQuery;
|
|
1729
|
-
}
|
|
2035
|
+
// src/userResponse/base-llm.ts
|
|
2036
|
+
init_utils();
|
|
1730
2037
|
|
|
1731
2038
|
// src/userResponse/schema.ts
|
|
1732
2039
|
import path2 from "path";
|
|
1733
|
-
import
|
|
2040
|
+
import fs3 from "fs";
|
|
1734
2041
|
var Schema = class {
|
|
1735
2042
|
constructor(schemaFilePath) {
|
|
1736
2043
|
this.cachedSchema = null;
|
|
@@ -1744,11 +2051,11 @@ var Schema = class {
|
|
|
1744
2051
|
logger.info(`SCHEMA_FILE_PATH: ${this.schemaFilePath}`);
|
|
1745
2052
|
try {
|
|
1746
2053
|
const dir = path2.dirname(this.schemaFilePath);
|
|
1747
|
-
if (!
|
|
2054
|
+
if (!fs3.existsSync(dir)) {
|
|
1748
2055
|
logger.info(`Creating directory structure: ${dir}`);
|
|
1749
|
-
|
|
2056
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1750
2057
|
}
|
|
1751
|
-
if (!
|
|
2058
|
+
if (!fs3.existsSync(this.schemaFilePath)) {
|
|
1752
2059
|
logger.info(`Schema file does not exist at ${this.schemaFilePath}, creating with empty schema`);
|
|
1753
2060
|
const initialSchema = {
|
|
1754
2061
|
database: "",
|
|
@@ -1757,11 +2064,11 @@ var Schema = class {
|
|
|
1757
2064
|
tables: [],
|
|
1758
2065
|
relationships: []
|
|
1759
2066
|
};
|
|
1760
|
-
|
|
2067
|
+
fs3.writeFileSync(this.schemaFilePath, JSON.stringify(initialSchema, null, 4));
|
|
1761
2068
|
this.cachedSchema = initialSchema;
|
|
1762
2069
|
return initialSchema;
|
|
1763
2070
|
}
|
|
1764
|
-
const fileContent =
|
|
2071
|
+
const fileContent = fs3.readFileSync(this.schemaFilePath, "utf-8");
|
|
1765
2072
|
const schema2 = JSON.parse(fileContent);
|
|
1766
2073
|
this.cachedSchema = schema2;
|
|
1767
2074
|
return schema2;
|
|
@@ -1862,7 +2169,7 @@ var Schema = class {
|
|
|
1862
2169
|
var schema = new Schema();
|
|
1863
2170
|
|
|
1864
2171
|
// src/userResponse/prompt-loader.ts
|
|
1865
|
-
import
|
|
2172
|
+
import fs4 from "fs";
|
|
1866
2173
|
import path3 from "path";
|
|
1867
2174
|
var PromptLoader = class {
|
|
1868
2175
|
constructor(config) {
|
|
@@ -1889,7 +2196,9 @@ var PromptLoader = class {
|
|
|
1889
2196
|
"single-component",
|
|
1890
2197
|
"mutli-component",
|
|
1891
2198
|
"actions",
|
|
1892
|
-
"container-metadata"
|
|
2199
|
+
"container-metadata",
|
|
2200
|
+
"text-response",
|
|
2201
|
+
"match-text-components"
|
|
1893
2202
|
];
|
|
1894
2203
|
for (const promptType of promptTypes) {
|
|
1895
2204
|
try {
|
|
@@ -1914,9 +2223,9 @@ var PromptLoader = class {
|
|
|
1914
2223
|
try {
|
|
1915
2224
|
const systemPath = path3.join(dir, promptName, "system.md");
|
|
1916
2225
|
const userPath = path3.join(dir, promptName, "user.md");
|
|
1917
|
-
if (
|
|
1918
|
-
const system =
|
|
1919
|
-
const user =
|
|
2226
|
+
if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
|
|
2227
|
+
const system = fs4.readFileSync(systemPath, "utf-8");
|
|
2228
|
+
const user = fs4.readFileSync(userPath, "utf-8");
|
|
1920
2229
|
logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
|
|
1921
2230
|
return { system, user };
|
|
1922
2231
|
}
|
|
@@ -2038,6 +2347,14 @@ var LLM = class {
|
|
|
2038
2347
|
throw new Error(`Unsupported provider: ${provider}. Use "anthropic" or "groq"`);
|
|
2039
2348
|
}
|
|
2040
2349
|
}
|
|
2350
|
+
/* Stream response with tool calling support (Anthropic only for now) */
|
|
2351
|
+
static async streamWithTools(messages, tools, toolHandler, options = {}, maxIterations = 3) {
|
|
2352
|
+
const [provider, modelName] = this._parseModel(options.model);
|
|
2353
|
+
if (provider !== "anthropic") {
|
|
2354
|
+
throw new Error(`Tool calling is only supported for Anthropic models`);
|
|
2355
|
+
}
|
|
2356
|
+
return this._anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations);
|
|
2357
|
+
}
|
|
2041
2358
|
// ============================================================
|
|
2042
2359
|
// PRIVATE HELPER METHODS
|
|
2043
2360
|
// ============================================================
|
|
@@ -2115,6 +2432,86 @@ var LLM = class {
|
|
|
2115
2432
|
}
|
|
2116
2433
|
return fullText;
|
|
2117
2434
|
}
|
|
2435
|
+
static async _anthropicStreamWithTools(messages, tools, toolHandler, modelName, options, maxIterations) {
|
|
2436
|
+
const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || "";
|
|
2437
|
+
const client = new Anthropic({
|
|
2438
|
+
apiKey
|
|
2439
|
+
});
|
|
2440
|
+
const conversationMessages = [{
|
|
2441
|
+
role: "user",
|
|
2442
|
+
content: messages.user
|
|
2443
|
+
}];
|
|
2444
|
+
let iterations = 0;
|
|
2445
|
+
let finalText = "";
|
|
2446
|
+
while (iterations < maxIterations) {
|
|
2447
|
+
iterations++;
|
|
2448
|
+
const response = await client.messages.create({
|
|
2449
|
+
model: modelName,
|
|
2450
|
+
max_tokens: options.maxTokens || 4e3,
|
|
2451
|
+
temperature: options.temperature,
|
|
2452
|
+
system: messages.sys,
|
|
2453
|
+
messages: conversationMessages,
|
|
2454
|
+
tools
|
|
2455
|
+
});
|
|
2456
|
+
if (response.stop_reason === "end_turn") {
|
|
2457
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
2458
|
+
if (textBlock && textBlock.type === "text") {
|
|
2459
|
+
finalText = textBlock.text;
|
|
2460
|
+
if (options.partial) {
|
|
2461
|
+
options.partial(finalText);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
break;
|
|
2465
|
+
}
|
|
2466
|
+
if (response.stop_reason === "tool_use") {
|
|
2467
|
+
const toolUses = response.content.filter((block) => block.type === "tool_use");
|
|
2468
|
+
if (toolUses.length === 0) {
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2471
|
+
conversationMessages.push({
|
|
2472
|
+
role: "assistant",
|
|
2473
|
+
content: response.content
|
|
2474
|
+
});
|
|
2475
|
+
const toolResults = {
|
|
2476
|
+
role: "user",
|
|
2477
|
+
content: []
|
|
2478
|
+
};
|
|
2479
|
+
for (const toolUse of toolUses) {
|
|
2480
|
+
if (toolUse.type === "tool_use") {
|
|
2481
|
+
try {
|
|
2482
|
+
const result = await toolHandler(toolUse.name, toolUse.input);
|
|
2483
|
+
toolResults.content.push({
|
|
2484
|
+
type: "tool_result",
|
|
2485
|
+
tool_use_id: toolUse.id,
|
|
2486
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
2487
|
+
});
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
toolResults.content.push({
|
|
2490
|
+
type: "tool_result",
|
|
2491
|
+
tool_use_id: toolUse.id,
|
|
2492
|
+
content: error instanceof Error ? error.message : String(error),
|
|
2493
|
+
is_error: true
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
conversationMessages.push(toolResults);
|
|
2499
|
+
} else {
|
|
2500
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
2501
|
+
if (textBlock && textBlock.type === "text") {
|
|
2502
|
+
finalText = textBlock.text;
|
|
2503
|
+
if (options.partial) {
|
|
2504
|
+
options.partial(finalText);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
if (iterations >= maxIterations) {
|
|
2511
|
+
throw new Error(`Max iterations (${maxIterations}) reached in tool calling loop`);
|
|
2512
|
+
}
|
|
2513
|
+
return finalText;
|
|
2514
|
+
}
|
|
2118
2515
|
// ============================================================
|
|
2119
2516
|
// GROQ IMPLEMENTATION
|
|
2120
2517
|
// ============================================================
|
|
@@ -2241,7 +2638,9 @@ var BaseLLM = class {
|
|
|
2241
2638
|
needsMultipleComponents: result.needsMultipleComponents || false
|
|
2242
2639
|
};
|
|
2243
2640
|
} catch (error) {
|
|
2244
|
-
|
|
2641
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2642
|
+
logger.error(`[${this.getProviderName()}] Error classifying user question: ${errorMsg}`);
|
|
2643
|
+
logger.debug(`[${this.getProviderName()}] Classification error details:`, error);
|
|
2245
2644
|
throw error;
|
|
2246
2645
|
}
|
|
2247
2646
|
}
|
|
@@ -2279,6 +2678,7 @@ var BaseLLM = class {
|
|
|
2279
2678
|
);
|
|
2280
2679
|
const props = result.props || originalProps;
|
|
2281
2680
|
if (props && props.query) {
|
|
2681
|
+
props.query = fixScalarSubqueries(props.query);
|
|
2282
2682
|
props.query = ensureQueryLimit(props.query, this.defaultLimit);
|
|
2283
2683
|
}
|
|
2284
2684
|
if (props && props.query) {
|
|
@@ -2305,7 +2705,9 @@ var BaseLLM = class {
|
|
|
2305
2705
|
modifications: result.modifications || []
|
|
2306
2706
|
};
|
|
2307
2707
|
} catch (error) {
|
|
2308
|
-
|
|
2708
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2709
|
+
logger.error(`[${this.getProviderName()}] Error validating/modifying props: ${errorMsg}`);
|
|
2710
|
+
logger.debug(`[${this.getProviderName()}] Props validation error details:`, error);
|
|
2309
2711
|
throw error;
|
|
2310
2712
|
}
|
|
2311
2713
|
}
|
|
@@ -2425,7 +2827,9 @@ var BaseLLM = class {
|
|
|
2425
2827
|
isGenerated: true
|
|
2426
2828
|
};
|
|
2427
2829
|
} catch (error) {
|
|
2428
|
-
|
|
2830
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2831
|
+
logger.error(`[${this.getProviderName()}] Error generating analytical component: ${errorMsg}`);
|
|
2832
|
+
logger.debug(`[${this.getProviderName()}] Analytical component generation error details:`, error);
|
|
2429
2833
|
throw error;
|
|
2430
2834
|
}
|
|
2431
2835
|
}
|
|
@@ -2467,7 +2871,9 @@ var BaseLLM = class {
|
|
|
2467
2871
|
description: result.description || `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
|
|
2468
2872
|
};
|
|
2469
2873
|
} catch (error) {
|
|
2470
|
-
|
|
2874
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2875
|
+
logger.error(`[${this.getProviderName()}] Error generating container metadata: ${errorMsg}`);
|
|
2876
|
+
logger.debug(`[${this.getProviderName()}] Container metadata error details:`, error);
|
|
2471
2877
|
return {
|
|
2472
2878
|
title: `${userPrompt} - Dashboard`,
|
|
2473
2879
|
description: `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
|
|
@@ -2519,24 +2925,24 @@ var BaseLLM = class {
|
|
|
2519
2925
|
component = components[componentIndex - 1];
|
|
2520
2926
|
}
|
|
2521
2927
|
const matchedMsg = `${this.getProviderName()} matched component: ${component?.name || "None"}`;
|
|
2522
|
-
|
|
2928
|
+
logger.info(`[${this.getProviderName()}] \u2713 ${matchedMsg}`);
|
|
2523
2929
|
logCollector?.info(matchedMsg);
|
|
2524
2930
|
if (result.alternativeMatches && result.alternativeMatches.length > 0) {
|
|
2525
|
-
|
|
2931
|
+
logger.debug(`[${this.getProviderName()}] Alternative matches found: ${result.alternativeMatches.length}`);
|
|
2526
2932
|
const altMatches = result.alternativeMatches.map(
|
|
2527
2933
|
(alt) => `${components[alt.index - 1]?.name} (${alt.score}%): ${alt.reason}`
|
|
2528
2934
|
).join(" | ");
|
|
2529
2935
|
logCollector?.info(`Alternative matches: ${altMatches}`);
|
|
2530
2936
|
result.alternativeMatches.forEach((alt) => {
|
|
2531
|
-
|
|
2937
|
+
logger.debug(`[${this.getProviderName()}] - ${components[alt.index - 1]?.name} (${alt.score}%): ${alt.reason}`);
|
|
2532
2938
|
});
|
|
2533
2939
|
}
|
|
2534
2940
|
if (!component) {
|
|
2535
2941
|
const noMatchMsg = `No matching component found (confidence: ${confidence}%)`;
|
|
2536
|
-
|
|
2942
|
+
logger.warn(`[${this.getProviderName()}] \u2717 ${noMatchMsg}`);
|
|
2537
2943
|
logCollector?.warn(noMatchMsg);
|
|
2538
2944
|
const genMsg = "Attempting to match component from analytical question...";
|
|
2539
|
-
|
|
2945
|
+
logger.info(`[${this.getProviderName()}] \u2713 ${genMsg}`);
|
|
2540
2946
|
logCollector?.info(genMsg);
|
|
2541
2947
|
const generatedResult = await this.generateAnalyticalComponent(userPrompt, components, void 0, apiKey, logCollector, conversationHistory);
|
|
2542
2948
|
if (generatedResult.component) {
|
|
@@ -2597,8 +3003,10 @@ var BaseLLM = class {
|
|
|
2597
3003
|
confidence
|
|
2598
3004
|
};
|
|
2599
3005
|
} catch (error) {
|
|
2600
|
-
|
|
2601
|
-
|
|
3006
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3007
|
+
logger.error(`[${this.getProviderName()}] Error matching component: ${errorMsg}`);
|
|
3008
|
+
logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
|
|
3009
|
+
logCollector?.error(`Error matching component: ${errorMsg}`);
|
|
2602
3010
|
throw error;
|
|
2603
3011
|
}
|
|
2604
3012
|
}
|
|
@@ -2629,7 +3037,9 @@ var BaseLLM = class {
|
|
|
2629
3037
|
isGenerated: true
|
|
2630
3038
|
};
|
|
2631
3039
|
} catch (error) {
|
|
2632
|
-
|
|
3040
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3041
|
+
logger.error(`[${this.getProviderName()}] Error matching multiple analytical components: ${errorMsg}`);
|
|
3042
|
+
logger.debug(`[${this.getProviderName()}] Multiple components matching error details:`, error);
|
|
2633
3043
|
return {
|
|
2634
3044
|
components: [],
|
|
2635
3045
|
reasoning: "Error occurred while matching components",
|
|
@@ -2708,63 +3118,538 @@ var BaseLLM = class {
|
|
|
2708
3118
|
isGenerated: true
|
|
2709
3119
|
};
|
|
2710
3120
|
} catch (error) {
|
|
2711
|
-
|
|
3121
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3122
|
+
logger.error(`[${this.getProviderName()}] Error generating multi-component response: ${errorMsg}`);
|
|
3123
|
+
logger.debug(`[${this.getProviderName()}] Multi-component response error details:`, error);
|
|
2712
3124
|
throw error;
|
|
2713
3125
|
}
|
|
2714
3126
|
}
|
|
2715
3127
|
/**
|
|
2716
|
-
*
|
|
2717
|
-
*
|
|
2718
|
-
*
|
|
3128
|
+
* Match components from text response suggestions
|
|
3129
|
+
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
3130
|
+
* @param textResponse - The text response containing component suggestions
|
|
3131
|
+
* @param components - List of available components
|
|
3132
|
+
* @param apiKey - Optional API key
|
|
3133
|
+
* @param logCollector - Optional log collector
|
|
3134
|
+
* @returns Object containing matched components, selected layout, and reasoning
|
|
2719
3135
|
*/
|
|
2720
|
-
async
|
|
3136
|
+
async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
|
|
2721
3137
|
try {
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3138
|
+
logger.debug(`[${this.getProviderName()}] Starting component matching from text response`);
|
|
3139
|
+
let availableComponentsText = "No components available";
|
|
3140
|
+
if (components && components.length > 0) {
|
|
3141
|
+
availableComponentsText = components.map((comp, idx) => {
|
|
3142
|
+
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
3143
|
+
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
3144
|
+
return `${idx + 1}. ID: ${comp.id}
|
|
3145
|
+
Name: ${comp.name}
|
|
3146
|
+
Type: ${comp.type}
|
|
3147
|
+
Description: ${comp.description || "No description"}
|
|
3148
|
+
Keywords: ${keywords}
|
|
3149
|
+
Props Structure: ${propsPreview}`;
|
|
3150
|
+
}).join("\n\n");
|
|
3151
|
+
}
|
|
3152
|
+
const schemaDoc = schema.generateSchemaDocumentation();
|
|
3153
|
+
const prompts = await promptLoader.loadPrompts("match-text-components", {
|
|
3154
|
+
TEXT_RESPONSE: textResponse,
|
|
3155
|
+
AVAILABLE_COMPONENTS: availableComponentsText,
|
|
3156
|
+
SCHEMA_DOC: schemaDoc
|
|
3157
|
+
});
|
|
3158
|
+
logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
|
|
3159
|
+
logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
|
|
3160
|
+
logCollector?.info("Matching components from text response...");
|
|
3161
|
+
const rawResponse = await LLM.stream(
|
|
3162
|
+
{
|
|
3163
|
+
sys: prompts.system,
|
|
3164
|
+
user: prompts.user
|
|
3165
|
+
},
|
|
3166
|
+
{
|
|
3167
|
+
model: this.model,
|
|
3168
|
+
maxTokens: 3e3,
|
|
3169
|
+
temperature: 0.2,
|
|
3170
|
+
apiKey: this.getApiKey(apiKey)
|
|
3171
|
+
},
|
|
3172
|
+
false
|
|
3173
|
+
// Don't parse as JSON yet, get raw response
|
|
3174
|
+
);
|
|
3175
|
+
logger.debug(`[${this.getProviderName()}] Raw component matching response length: ${rawResponse?.length || 0}`);
|
|
3176
|
+
logger.file(`[${this.getProviderName()}] Component matching raw response:`, rawResponse);
|
|
3177
|
+
let result;
|
|
3178
|
+
try {
|
|
3179
|
+
let cleanedResponse = rawResponse || "";
|
|
3180
|
+
cleanedResponse = cleanedResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
3181
|
+
const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
|
|
3182
|
+
if (jsonMatch) {
|
|
3183
|
+
cleanedResponse = jsonMatch[0];
|
|
3184
|
+
}
|
|
3185
|
+
result = JSON.parse(cleanedResponse);
|
|
3186
|
+
} catch (parseError) {
|
|
3187
|
+
logger.error(`[${this.getProviderName()}] Failed to parse component matching JSON response`);
|
|
3188
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
3189
|
+
const posMatch = errorMsg.match(/position (\d+)/);
|
|
3190
|
+
const errorPos = posMatch ? parseInt(posMatch[1]) : -1;
|
|
3191
|
+
if (errorPos > 0 && rawResponse) {
|
|
3192
|
+
const start = Math.max(0, errorPos - 200);
|
|
3193
|
+
const end = Math.min(rawResponse.length, errorPos + 200);
|
|
3194
|
+
logger.debug(`[${this.getProviderName()}] Error context (position ${errorPos}):`);
|
|
3195
|
+
logger.debug(rawResponse.substring(start, end));
|
|
3196
|
+
logger.debug(" ".repeat(Math.min(200, errorPos - start)) + "^--- Error here");
|
|
3197
|
+
}
|
|
3198
|
+
logger.debug(`[${this.getProviderName()}] Raw response (first 2000 chars):`, rawResponse?.substring(0, 2e3));
|
|
3199
|
+
logger.debug(`[${this.getProviderName()}] Parse error:`, parseError);
|
|
3200
|
+
logCollector?.error(`Failed to parse component matching response: ${errorMsg}`);
|
|
3201
|
+
try {
|
|
3202
|
+
logger.info(`[${this.getProviderName()}] Attempting aggressive JSON cleanup...`);
|
|
3203
|
+
let aggressive = rawResponse || "";
|
|
3204
|
+
aggressive = aggressive.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
3205
|
+
const match = aggressive.match(/\{[\s\S]*\}/);
|
|
3206
|
+
if (match) {
|
|
3207
|
+
aggressive = match[0];
|
|
2757
3208
|
}
|
|
2758
|
-
|
|
3209
|
+
aggressive = aggressive.replace(/,(\s*[}\]])/g, "$1");
|
|
3210
|
+
result = JSON.parse(aggressive);
|
|
3211
|
+
logger.info(`[${this.getProviderName()}] Aggressive cleanup succeeded!`);
|
|
3212
|
+
} catch (secondError) {
|
|
3213
|
+
logger.error(`[${this.getProviderName()}] Aggressive cleanup also failed`);
|
|
3214
|
+
return {
|
|
3215
|
+
components: [],
|
|
3216
|
+
selectedLayout: "MultiComponentContainer",
|
|
3217
|
+
selectedLayoutId: "",
|
|
3218
|
+
selectedLayoutComponent: null,
|
|
3219
|
+
layoutReasoning: "No layout selected"
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
const matchedComponents = result.matchedComponents || [];
|
|
3224
|
+
const selectedLayout = result.selectedLayout || "MultiComponentContainer";
|
|
3225
|
+
const selectedLayoutId = result.selectedLayoutId || "";
|
|
3226
|
+
const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
|
|
3227
|
+
let selectedLayoutComponent = null;
|
|
3228
|
+
if (selectedLayoutId) {
|
|
3229
|
+
selectedLayoutComponent = components.find((c) => c.id === selectedLayoutId) || null;
|
|
3230
|
+
if (!selectedLayoutComponent) {
|
|
3231
|
+
logger.warn(`[${this.getProviderName()}] Layout component ${selectedLayoutId} not found in available components`);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
|
|
3235
|
+
logger.info(`[${this.getProviderName()}] Selected layout: ${selectedLayout} (ID: ${selectedLayoutId})`);
|
|
3236
|
+
logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
|
|
3237
|
+
if (matchedComponents.length > 0) {
|
|
3238
|
+
logCollector?.info(`Matched ${matchedComponents.length} components for visualization using ${selectedLayout}`);
|
|
3239
|
+
logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
|
|
3240
|
+
matchedComponents.forEach((comp, idx) => {
|
|
3241
|
+
logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
|
|
3242
|
+
if (comp.props?.query) {
|
|
3243
|
+
logCollector?.logQuery(
|
|
3244
|
+
`Component ${idx + 1} query`,
|
|
3245
|
+
comp.props.query,
|
|
3246
|
+
{ componentName: comp.componentName, title: comp.props.title }
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
const finalComponents = matchedComponents.map((mc) => {
|
|
3252
|
+
const originalComponent = components.find((c) => c.id === mc.componentId);
|
|
3253
|
+
if (!originalComponent) {
|
|
3254
|
+
logger.warn(`[${this.getProviderName()}] Component ${mc.componentId} not found in available components`);
|
|
3255
|
+
return null;
|
|
3256
|
+
}
|
|
3257
|
+
return {
|
|
3258
|
+
...originalComponent,
|
|
3259
|
+
props: {
|
|
3260
|
+
...originalComponent.props,
|
|
3261
|
+
...mc.props
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
}).filter(Boolean);
|
|
3265
|
+
return {
|
|
3266
|
+
components: finalComponents,
|
|
3267
|
+
selectedLayout,
|
|
3268
|
+
selectedLayoutId,
|
|
3269
|
+
selectedLayoutComponent,
|
|
3270
|
+
layoutReasoning
|
|
3271
|
+
};
|
|
3272
|
+
} catch (error) {
|
|
3273
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3274
|
+
logger.error(`[${this.getProviderName()}] Error matching components from text response: ${errorMsg}`);
|
|
3275
|
+
logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
|
|
3276
|
+
logCollector?.error(`Error matching components: ${errorMsg}`);
|
|
3277
|
+
return {
|
|
3278
|
+
components: [],
|
|
3279
|
+
selectedLayout: "MultiComponentContainer",
|
|
3280
|
+
selectedLayoutId: "",
|
|
3281
|
+
selectedLayoutComponent: null,
|
|
3282
|
+
layoutReasoning: "Error occurred during component matching"
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Generate text-based response for user question
|
|
3288
|
+
* This provides conversational text responses instead of component generation
|
|
3289
|
+
* Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
|
|
3290
|
+
* After generating text response, if components are provided, matches suggested components
|
|
3291
|
+
* @param streamCallback - Optional callback function to receive text chunks as they stream
|
|
3292
|
+
* @param collections - Collection registry for executing database queries via database.execute
|
|
3293
|
+
* @param components - Optional list of available components for matching suggestions
|
|
3294
|
+
*/
|
|
3295
|
+
async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components) {
|
|
3296
|
+
const errors = [];
|
|
3297
|
+
logger.debug(`[${this.getProviderName()}] Starting text response generation`);
|
|
3298
|
+
logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
|
|
3299
|
+
try {
|
|
3300
|
+
const schemaDoc = schema.generateSchemaDocumentation();
|
|
3301
|
+
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
3302
|
+
USER_PROMPT: userPrompt,
|
|
3303
|
+
CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
|
|
3304
|
+
SCHEMA_DOC: schemaDoc
|
|
3305
|
+
});
|
|
3306
|
+
logger.file("\n=============================\nsystem prompt:", prompts.system);
|
|
3307
|
+
logger.file("\n=============================\nuser prompt:", prompts.user);
|
|
3308
|
+
logger.debug(`[${this.getProviderName()}] Loaded text-response prompts with schema`);
|
|
3309
|
+
logger.debug(`[${this.getProviderName()}] System prompt length: ${prompts.system.length}, User prompt length: ${prompts.user.length}`);
|
|
3310
|
+
logCollector?.info("Generating text response with query execution capability...");
|
|
3311
|
+
const tools = [{
|
|
3312
|
+
name: "execute_query",
|
|
3313
|
+
description: "Executes a SQL query against the database and returns the results. Use this when the user asks for data. If the query fails, you will receive the error and can retry with a corrected query.",
|
|
3314
|
+
input_schema: {
|
|
3315
|
+
type: "object",
|
|
3316
|
+
properties: {
|
|
3317
|
+
query: {
|
|
3318
|
+
type: "string",
|
|
3319
|
+
description: "The SQL query to execute. Must be valid SQL syntax using table and column names from the schema."
|
|
3320
|
+
},
|
|
3321
|
+
reasoning: {
|
|
3322
|
+
type: "string",
|
|
3323
|
+
description: "Brief explanation of what this query does and why it answers the user's question."
|
|
3324
|
+
}
|
|
3325
|
+
},
|
|
3326
|
+
required: ["query"]
|
|
3327
|
+
}
|
|
3328
|
+
}];
|
|
3329
|
+
const queryAttempts = /* @__PURE__ */ new Map();
|
|
3330
|
+
const MAX_QUERY_ATTEMPTS = 6;
|
|
3331
|
+
let maxAttemptsReached = false;
|
|
3332
|
+
let fullStreamedText = "";
|
|
3333
|
+
const wrappedStreamCallback = streamCallback ? (chunk) => {
|
|
3334
|
+
fullStreamedText += chunk;
|
|
3335
|
+
streamCallback(chunk);
|
|
3336
|
+
} : void 0;
|
|
3337
|
+
const toolHandler = async (toolName, toolInput) => {
|
|
3338
|
+
if (toolName === "execute_query") {
|
|
3339
|
+
let query = toolInput.query;
|
|
3340
|
+
const reasoning = toolInput.reasoning;
|
|
3341
|
+
const { ensureQueryLimit: ensureQueryLimit2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
3342
|
+
query = ensureQueryLimit2(query, 32, 32);
|
|
3343
|
+
const queryKey = query.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3344
|
+
const attempts = (queryAttempts.get(queryKey) || 0) + 1;
|
|
3345
|
+
queryAttempts.set(queryKey, attempts);
|
|
3346
|
+
logger.info(`[${this.getProviderName()}] Executing query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${query.substring(0, 100)}...`);
|
|
3347
|
+
if (reasoning) {
|
|
3348
|
+
logCollector?.info(`Query reasoning: ${reasoning}`);
|
|
3349
|
+
}
|
|
3350
|
+
if (attempts > MAX_QUERY_ATTEMPTS) {
|
|
3351
|
+
const errorMsg = `Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`;
|
|
3352
|
+
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
3353
|
+
logCollector?.error(errorMsg);
|
|
3354
|
+
maxAttemptsReached = true;
|
|
3355
|
+
if (wrappedStreamCallback) {
|
|
3356
|
+
wrappedStreamCallback(`
|
|
3357
|
+
|
|
3358
|
+
\u274C ${errorMsg}
|
|
3359
|
+
|
|
3360
|
+
Please try rephrasing your question or simplifying your request.
|
|
3361
|
+
|
|
3362
|
+
`);
|
|
3363
|
+
}
|
|
3364
|
+
throw new Error(errorMsg);
|
|
3365
|
+
}
|
|
3366
|
+
try {
|
|
3367
|
+
if (wrappedStreamCallback) {
|
|
3368
|
+
if (attempts === 1) {
|
|
3369
|
+
wrappedStreamCallback(`
|
|
3370
|
+
|
|
3371
|
+
\u{1F50D} **Analyzing your question...**
|
|
3372
|
+
|
|
3373
|
+
`);
|
|
3374
|
+
if (reasoning) {
|
|
3375
|
+
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
3376
|
+
|
|
3377
|
+
`);
|
|
3378
|
+
}
|
|
3379
|
+
wrappedStreamCallback(`\u{1F4DD} **Generated SQL Query:**
|
|
3380
|
+
\`\`\`sql
|
|
3381
|
+
${query}
|
|
3382
|
+
\`\`\`
|
|
3383
|
+
|
|
3384
|
+
`);
|
|
3385
|
+
wrappedStreamCallback(`\u26A1 **Executing query...**
|
|
3386
|
+
|
|
3387
|
+
`);
|
|
3388
|
+
} else {
|
|
3389
|
+
wrappedStreamCallback(`
|
|
3390
|
+
|
|
3391
|
+
\u{1F504} **Retrying with corrected query (attempt ${attempts}/${MAX_QUERY_ATTEMPTS})...**
|
|
3392
|
+
|
|
3393
|
+
`);
|
|
3394
|
+
if (reasoning) {
|
|
3395
|
+
wrappedStreamCallback(`\u{1F4AD} ${reasoning}
|
|
3396
|
+
|
|
3397
|
+
`);
|
|
3398
|
+
}
|
|
3399
|
+
wrappedStreamCallback(`\u{1F4DD} **Corrected SQL Query:**
|
|
3400
|
+
\`\`\`sql
|
|
3401
|
+
${query}
|
|
3402
|
+
\`\`\`
|
|
3403
|
+
|
|
3404
|
+
`);
|
|
3405
|
+
wrappedStreamCallback(`\u26A1 **Executing query...**
|
|
3406
|
+
|
|
3407
|
+
`);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
logCollector?.logQuery(
|
|
3411
|
+
`Executing SQL query (attempt ${attempts})`,
|
|
3412
|
+
query,
|
|
3413
|
+
{ reasoning, attempt: attempts }
|
|
3414
|
+
);
|
|
3415
|
+
if (!collections || !collections["database"] || !collections["database"]["execute"]) {
|
|
3416
|
+
throw new Error("Database collection not registered. Please register database.execute collection to execute queries.");
|
|
3417
|
+
}
|
|
3418
|
+
const result2 = await collections["database"]["execute"]({ sql: query });
|
|
3419
|
+
const data = result2?.data || result2;
|
|
3420
|
+
const rowCount = result2?.count ?? (Array.isArray(data) ? data.length : "N/A");
|
|
3421
|
+
logger.info(`[${this.getProviderName()}] Query executed successfully, rows returned: ${rowCount}`);
|
|
3422
|
+
logCollector?.info(`Query successful, returned ${rowCount} rows`);
|
|
3423
|
+
if (wrappedStreamCallback) {
|
|
3424
|
+
wrappedStreamCallback(`\u2705 **Query executed successfully!**
|
|
3425
|
+
|
|
3426
|
+
`);
|
|
3427
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
3428
|
+
const firstRow = data[0];
|
|
3429
|
+
const columns = Object.keys(firstRow);
|
|
3430
|
+
if (data.length === 1 && columns.length === 1) {
|
|
3431
|
+
const value = firstRow[columns[0]];
|
|
3432
|
+
wrappedStreamCallback(`**Result:** ${value}
|
|
3433
|
+
|
|
3434
|
+
`);
|
|
3435
|
+
} else if (data.length > 0) {
|
|
3436
|
+
wrappedStreamCallback(`**Retrieved ${rowCount} rows**
|
|
3437
|
+
|
|
3438
|
+
`);
|
|
3439
|
+
wrappedStreamCallback(`<DataTable>${JSON.stringify(data)}</DataTable>
|
|
3440
|
+
|
|
3441
|
+
`);
|
|
3442
|
+
}
|
|
3443
|
+
} else if (Array.isArray(data) && data.length === 0) {
|
|
3444
|
+
wrappedStreamCallback(`**No rows returned.**
|
|
3445
|
+
|
|
3446
|
+
`);
|
|
3447
|
+
}
|
|
3448
|
+
wrappedStreamCallback(`\u{1F4CA} **Analyzing results...**
|
|
3449
|
+
|
|
3450
|
+
`);
|
|
3451
|
+
}
|
|
3452
|
+
return JSON.stringify(data, null, 2);
|
|
3453
|
+
} catch (error) {
|
|
3454
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3455
|
+
logger.error(`[${this.getProviderName()}] Query execution failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
3456
|
+
logCollector?.error(`Query failed (attempt ${attempts}/${MAX_QUERY_ATTEMPTS}): ${errorMsg}`);
|
|
3457
|
+
if (wrappedStreamCallback) {
|
|
3458
|
+
wrappedStreamCallback(`\u274C **Query execution failed:**
|
|
3459
|
+
\`\`\`
|
|
3460
|
+
${errorMsg}
|
|
3461
|
+
\`\`\`
|
|
3462
|
+
|
|
3463
|
+
`);
|
|
3464
|
+
if (attempts < MAX_QUERY_ATTEMPTS) {
|
|
3465
|
+
wrappedStreamCallback(`\u{1F527} **Generating corrected query...**
|
|
3466
|
+
|
|
3467
|
+
`);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
3474
|
+
};
|
|
3475
|
+
const result = await LLM.streamWithTools(
|
|
3476
|
+
{
|
|
3477
|
+
sys: prompts.system,
|
|
3478
|
+
user: prompts.user
|
|
3479
|
+
},
|
|
3480
|
+
tools,
|
|
3481
|
+
toolHandler,
|
|
3482
|
+
{
|
|
3483
|
+
model: this.model,
|
|
3484
|
+
maxTokens: 4e3,
|
|
3485
|
+
temperature: 0.7,
|
|
3486
|
+
apiKey: this.getApiKey(apiKey),
|
|
3487
|
+
partial: wrappedStreamCallback
|
|
3488
|
+
// Pass the wrapped streaming callback to LLM
|
|
3489
|
+
},
|
|
3490
|
+
10
|
|
3491
|
+
// max iterations: allows for 6 retries + final response + buffer
|
|
3492
|
+
);
|
|
3493
|
+
logger.info(`[${this.getProviderName()}] Text response stream completed`);
|
|
3494
|
+
const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
|
|
3495
|
+
if (maxAttemptsReached) {
|
|
3496
|
+
logger.warn(`[${this.getProviderName()}] Max query attempts reached, returning failure response`);
|
|
3497
|
+
logCollector?.error("Failed to generate valid query after maximum attempts");
|
|
3498
|
+
return {
|
|
3499
|
+
success: false,
|
|
3500
|
+
errors: [`Maximum query attempts (${MAX_QUERY_ATTEMPTS}) reached. Unable to generate a valid query for your question.`],
|
|
3501
|
+
data: {
|
|
3502
|
+
text: textResponse,
|
|
3503
|
+
// Include the streamed text showing all attempts
|
|
3504
|
+
matchedComponents: [],
|
|
3505
|
+
method: `${this.getProviderName()}-text-response-max-attempts`
|
|
3506
|
+
}
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
logCollector?.info(`Text response: ${textResponse.substring(0, 100)}${textResponse.length > 100 ? "..." : ""}`);
|
|
3510
|
+
logCollector?.logExplanation(
|
|
3511
|
+
"Text response generated",
|
|
3512
|
+
"Generated plain text response with component suggestions",
|
|
3513
|
+
{
|
|
3514
|
+
textLength: textResponse.length
|
|
3515
|
+
}
|
|
3516
|
+
);
|
|
3517
|
+
let matchedComponents = [];
|
|
3518
|
+
let selectedLayoutComponent = null;
|
|
3519
|
+
let layoutReasoning = "No layout selected";
|
|
3520
|
+
if (components && components.length > 0) {
|
|
3521
|
+
logger.info(`[${this.getProviderName()}] Matching components from text response...`);
|
|
3522
|
+
const matchResult = await this.matchComponentsFromTextResponse(
|
|
3523
|
+
textResponse,
|
|
3524
|
+
components,
|
|
3525
|
+
apiKey,
|
|
3526
|
+
logCollector
|
|
3527
|
+
);
|
|
3528
|
+
matchedComponents = matchResult.components;
|
|
3529
|
+
selectedLayoutComponent = matchResult.selectedLayoutComponent;
|
|
3530
|
+
layoutReasoning = matchResult.layoutReasoning;
|
|
3531
|
+
}
|
|
3532
|
+
let container_componet = null;
|
|
3533
|
+
if (matchedComponents.length > 0) {
|
|
3534
|
+
if (selectedLayoutComponent) {
|
|
3535
|
+
container_componet = {
|
|
3536
|
+
...selectedLayoutComponent,
|
|
3537
|
+
id: `${selectedLayoutComponent.id}_${Date.now()}`,
|
|
3538
|
+
description: layoutReasoning,
|
|
3539
|
+
props: {
|
|
3540
|
+
...selectedLayoutComponent.props,
|
|
3541
|
+
config: {
|
|
3542
|
+
...selectedLayoutComponent.props?.config || {},
|
|
3543
|
+
components: matchedComponents
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components`);
|
|
3548
|
+
logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components: ${layoutReasoning}`);
|
|
3549
|
+
} else {
|
|
3550
|
+
container_componet = {
|
|
3551
|
+
id: `multi_container_${Date.now()}`,
|
|
3552
|
+
name: "MultiComponentContainer",
|
|
3553
|
+
type: "Container",
|
|
3554
|
+
description: layoutReasoning,
|
|
3555
|
+
category: "dynamic",
|
|
3556
|
+
keywords: ["dashboard", "layout", "container"],
|
|
3557
|
+
props: {
|
|
3558
|
+
config: {
|
|
3559
|
+
components: matchedComponents
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
};
|
|
3563
|
+
logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components`);
|
|
3564
|
+
logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components: ${layoutReasoning}`);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
return {
|
|
3568
|
+
success: true,
|
|
3569
|
+
data: {
|
|
3570
|
+
text: textResponse,
|
|
3571
|
+
matchedComponents,
|
|
3572
|
+
component: container_componet,
|
|
3573
|
+
layoutReasoning,
|
|
3574
|
+
method: `${this.getProviderName()}-text-response`
|
|
3575
|
+
},
|
|
3576
|
+
errors: []
|
|
3577
|
+
};
|
|
3578
|
+
} catch (error) {
|
|
3579
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3580
|
+
logger.error(`[${this.getProviderName()}] Error generating text response: ${errorMsg}`);
|
|
3581
|
+
logCollector?.error(`Error generating text response: ${errorMsg}`);
|
|
3582
|
+
errors.push(errorMsg);
|
|
3583
|
+
return {
|
|
3584
|
+
success: false,
|
|
3585
|
+
errors,
|
|
3586
|
+
data: {
|
|
3587
|
+
text: "I apologize, but I encountered an error while processing your question. Please try rephrasing or ask something else.",
|
|
3588
|
+
matchedComponents: [],
|
|
3589
|
+
method: `${this.getProviderName()}-text-response-error`
|
|
3590
|
+
}
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Generate component response for user question
|
|
3596
|
+
* This provides conversational component suggestions based on user question
|
|
3597
|
+
* Supports component generation and matching
|
|
3598
|
+
*/
|
|
3599
|
+
async generateComponentResponse(userPrompt, components, apiKey, logCollector, conversationHistory) {
|
|
3600
|
+
const errors = [];
|
|
3601
|
+
try {
|
|
3602
|
+
logger.info(`[${this.getProviderName()}] Using component response mode`);
|
|
3603
|
+
const classifyMsg = "Classifying user question...";
|
|
3604
|
+
logCollector?.info(classifyMsg);
|
|
3605
|
+
const classification = await this.classifyUserQuestion(userPrompt, apiKey, logCollector, conversationHistory);
|
|
3606
|
+
const classInfo = `Question type: ${classification.questionType}, Visualizations: ${classification.visualizations.join(", ") || "None"}, Multiple components: ${classification.needsMultipleComponents}`;
|
|
3607
|
+
logCollector?.info(classInfo);
|
|
3608
|
+
if (classification.questionType === "analytical") {
|
|
3609
|
+
if (classification.visualizations.length > 1) {
|
|
3610
|
+
const multiMsg = `Matching ${classification.visualizations.length} components for types: ${classification.visualizations.join(", ")}`;
|
|
3611
|
+
logCollector?.info(multiMsg);
|
|
3612
|
+
const componentPromises = classification.visualizations.map((vizType) => {
|
|
3613
|
+
logCollector?.info(`Matching component for type: ${vizType}`);
|
|
3614
|
+
return this.generateAnalyticalComponent(
|
|
3615
|
+
userPrompt,
|
|
3616
|
+
components,
|
|
3617
|
+
vizType,
|
|
3618
|
+
apiKey,
|
|
3619
|
+
logCollector,
|
|
3620
|
+
conversationHistory
|
|
3621
|
+
).then((result) => ({ vizType, result }));
|
|
3622
|
+
});
|
|
3623
|
+
const settledResults = await Promise.allSettled(componentPromises);
|
|
3624
|
+
const matchedComponents = [];
|
|
3625
|
+
for (const settledResult of settledResults) {
|
|
3626
|
+
if (settledResult.status === "fulfilled") {
|
|
3627
|
+
const { vizType, result } = settledResult.value;
|
|
3628
|
+
if (result.component) {
|
|
3629
|
+
matchedComponents.push(result.component);
|
|
3630
|
+
logCollector?.info(`Matched: ${result.component.name}`);
|
|
3631
|
+
logger.info("Component : ", result.component.name, " props: ", result.component.props);
|
|
3632
|
+
} else {
|
|
3633
|
+
logCollector?.warn(`Failed to match component for type: ${vizType}`);
|
|
3634
|
+
}
|
|
3635
|
+
} else {
|
|
3636
|
+
logCollector?.warn(`Error matching component: ${settledResult.reason?.message || "Unknown error"}`);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
logger.debug(`[${this.getProviderName()}] Matched ${matchedComponents.length} components for multi-component container`);
|
|
2759
3640
|
if (matchedComponents.length === 0) {
|
|
2760
3641
|
return {
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3642
|
+
success: true,
|
|
3643
|
+
data: {
|
|
3644
|
+
component: null,
|
|
3645
|
+
reasoning: "Failed to match any components for the requested visualization types",
|
|
3646
|
+
method: "classification-multi-failed",
|
|
3647
|
+
questionType: classification.questionType,
|
|
3648
|
+
needsMultipleComponents: true,
|
|
3649
|
+
propsModified: false,
|
|
3650
|
+
queryModified: false
|
|
3651
|
+
},
|
|
3652
|
+
errors: []
|
|
2768
3653
|
};
|
|
2769
3654
|
}
|
|
2770
3655
|
logCollector?.info("Generating container metadata...");
|
|
@@ -2794,38 +3679,50 @@ var BaseLLM = class {
|
|
|
2794
3679
|
};
|
|
2795
3680
|
logCollector?.info(`Created multi-component container with ${matchedComponents.length} components: "${containerMetadata.title}"`);
|
|
2796
3681
|
return {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
3682
|
+
success: true,
|
|
3683
|
+
data: {
|
|
3684
|
+
component: containerComponent,
|
|
3685
|
+
reasoning: `Matched ${matchedComponents.length} components for visualization types: ${classification.visualizations.join(", ")}`,
|
|
3686
|
+
method: "classification-multi-generated",
|
|
3687
|
+
questionType: classification.questionType,
|
|
3688
|
+
needsMultipleComponents: true,
|
|
3689
|
+
propsModified: false,
|
|
3690
|
+
queryModified: false
|
|
3691
|
+
},
|
|
3692
|
+
errors: []
|
|
2804
3693
|
};
|
|
2805
3694
|
} else if (classification.visualizations.length === 1) {
|
|
2806
3695
|
const vizType = classification.visualizations[0];
|
|
2807
3696
|
logCollector?.info(`Matching single component for type: ${vizType}`);
|
|
2808
3697
|
const result = await this.generateAnalyticalComponent(userPrompt, components, vizType, apiKey, logCollector, conversationHistory);
|
|
2809
3698
|
return {
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
3699
|
+
success: true,
|
|
3700
|
+
data: {
|
|
3701
|
+
component: result.component,
|
|
3702
|
+
reasoning: result.reasoning,
|
|
3703
|
+
method: "classification-generated",
|
|
3704
|
+
questionType: classification.questionType,
|
|
3705
|
+
needsMultipleComponents: false,
|
|
3706
|
+
propsModified: false,
|
|
3707
|
+
queryModified: false
|
|
3708
|
+
},
|
|
3709
|
+
errors: []
|
|
2817
3710
|
};
|
|
2818
3711
|
} else {
|
|
2819
3712
|
logCollector?.info("No specific visualization type - matching from all components");
|
|
2820
3713
|
const result = await this.generateAnalyticalComponent(userPrompt, components, void 0, apiKey, logCollector, conversationHistory);
|
|
2821
3714
|
return {
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
3715
|
+
success: true,
|
|
3716
|
+
data: {
|
|
3717
|
+
component: result.component,
|
|
3718
|
+
reasoning: result.reasoning,
|
|
3719
|
+
method: "classification-generated-auto",
|
|
3720
|
+
questionType: classification.questionType,
|
|
3721
|
+
needsMultipleComponents: false,
|
|
3722
|
+
propsModified: false,
|
|
3723
|
+
queryModified: false
|
|
3724
|
+
},
|
|
3725
|
+
errors: []
|
|
2829
3726
|
};
|
|
2830
3727
|
}
|
|
2831
3728
|
} else if (classification.questionType === "data_modification" || classification.questionType === "general") {
|
|
@@ -2833,29 +3730,104 @@ var BaseLLM = class {
|
|
|
2833
3730
|
logCollector?.info(matchMsg);
|
|
2834
3731
|
const matchResult = await this.matchComponent(userPrompt, components, apiKey, logCollector, conversationHistory);
|
|
2835
3732
|
return {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
3733
|
+
success: true,
|
|
3734
|
+
data: {
|
|
3735
|
+
component: matchResult.component,
|
|
3736
|
+
reasoning: matchResult.reasoning,
|
|
3737
|
+
method: "classification-matched",
|
|
3738
|
+
questionType: classification.questionType,
|
|
3739
|
+
needsMultipleComponents: false,
|
|
3740
|
+
propsModified: matchResult.propsModified,
|
|
3741
|
+
queryModified: matchResult.queryModified
|
|
3742
|
+
},
|
|
3743
|
+
errors: []
|
|
2843
3744
|
};
|
|
2844
3745
|
} else {
|
|
2845
3746
|
logCollector?.info("General question - no component needed");
|
|
2846
3747
|
return {
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
3748
|
+
success: true,
|
|
3749
|
+
data: {
|
|
3750
|
+
component: null,
|
|
3751
|
+
reasoning: "General question - no component needed",
|
|
3752
|
+
method: "classification-general",
|
|
3753
|
+
questionType: classification.questionType,
|
|
3754
|
+
needsMultipleComponents: false,
|
|
3755
|
+
propsModified: false,
|
|
3756
|
+
queryModified: false
|
|
3757
|
+
},
|
|
3758
|
+
errors: []
|
|
2852
3759
|
};
|
|
2853
3760
|
}
|
|
2854
3761
|
} catch (error) {
|
|
2855
|
-
|
|
2856
|
-
|
|
3762
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3763
|
+
logger.error(`[${this.getProviderName()}] Error generating component response: ${errorMsg}`);
|
|
3764
|
+
logger.debug(`[${this.getProviderName()}] Component response generation error details:`, error);
|
|
3765
|
+
logCollector?.error(`Error generating component response: ${errorMsg}`);
|
|
3766
|
+
errors.push(errorMsg);
|
|
3767
|
+
return {
|
|
3768
|
+
success: false,
|
|
3769
|
+
errors,
|
|
3770
|
+
data: void 0
|
|
3771
|
+
};
|
|
2857
3772
|
}
|
|
2858
3773
|
}
|
|
3774
|
+
/**
|
|
3775
|
+
* Main orchestration function that classifies question and routes to appropriate handler
|
|
3776
|
+
* This is the NEW recommended entry point for handling user requests
|
|
3777
|
+
* Supports both component generation and text response modes
|
|
3778
|
+
*
|
|
3779
|
+
* @param responseMode - 'component' for component generation (default), 'text' for text responses
|
|
3780
|
+
* @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
|
|
3781
|
+
* @param collections - Collection registry for executing database queries (required for text mode)
|
|
3782
|
+
*/
|
|
3783
|
+
async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) {
|
|
3784
|
+
const startTime = Date.now();
|
|
3785
|
+
logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
|
|
3786
|
+
if (responseMode === "text") {
|
|
3787
|
+
logger.info(`[${this.getProviderName()}] Using text response mode`);
|
|
3788
|
+
logCollector?.info("Generating text response...");
|
|
3789
|
+
const textResponse = await this.generateTextResponse(
|
|
3790
|
+
userPrompt,
|
|
3791
|
+
apiKey,
|
|
3792
|
+
logCollector,
|
|
3793
|
+
conversationHistory,
|
|
3794
|
+
streamCallback,
|
|
3795
|
+
collections,
|
|
3796
|
+
components
|
|
3797
|
+
);
|
|
3798
|
+
if (!textResponse.success) {
|
|
3799
|
+
const elapsedTime3 = Date.now() - startTime;
|
|
3800
|
+
logger.error(`[${this.getProviderName()}] Text response generation failed`);
|
|
3801
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
|
|
3802
|
+
logCollector?.info(`Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
|
|
3803
|
+
return textResponse;
|
|
3804
|
+
}
|
|
3805
|
+
const elapsedTime2 = Date.now() - startTime;
|
|
3806
|
+
logger.info(`[${this.getProviderName()}] Text response generated successfully`);
|
|
3807
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
3808
|
+
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
3809
|
+
return textResponse;
|
|
3810
|
+
}
|
|
3811
|
+
const componentResponse = await this.generateComponentResponse(
|
|
3812
|
+
userPrompt,
|
|
3813
|
+
components,
|
|
3814
|
+
apiKey,
|
|
3815
|
+
logCollector,
|
|
3816
|
+
conversationHistory
|
|
3817
|
+
);
|
|
3818
|
+
if (!componentResponse.success) {
|
|
3819
|
+
const elapsedTime2 = Date.now() - startTime;
|
|
3820
|
+
logger.error(`[${this.getProviderName()}] Component response generation failed`);
|
|
3821
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
3822
|
+
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
3823
|
+
return componentResponse;
|
|
3824
|
+
}
|
|
3825
|
+
const elapsedTime = Date.now() - startTime;
|
|
3826
|
+
logger.info(`[${this.getProviderName()}] Component response generated successfully`);
|
|
3827
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
3828
|
+
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
3829
|
+
return componentResponse;
|
|
3830
|
+
}
|
|
2859
3831
|
/**
|
|
2860
3832
|
* Generate next questions that the user might ask based on the original prompt and generated component
|
|
2861
3833
|
* This helps provide intelligent suggestions for follow-up queries
|
|
@@ -2900,8 +3872,10 @@ var BaseLLM = class {
|
|
|
2900
3872
|
);
|
|
2901
3873
|
return nextQuestions;
|
|
2902
3874
|
} catch (error) {
|
|
2903
|
-
|
|
2904
|
-
|
|
3875
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3876
|
+
logger.error(`[${this.getProviderName()}] Error generating next questions: ${errorMsg}`);
|
|
3877
|
+
logger.debug(`[${this.getProviderName()}] Next questions generation error details:`, error);
|
|
3878
|
+
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
2905
3879
|
return [];
|
|
2906
3880
|
}
|
|
2907
3881
|
}
|
|
@@ -2965,112 +3939,130 @@ function getLLMProviders() {
|
|
|
2965
3939
|
return DEFAULT_PROVIDERS;
|
|
2966
3940
|
}
|
|
2967
3941
|
}
|
|
2968
|
-
var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory) => {
|
|
2969
|
-
|
|
2970
|
-
|
|
3942
|
+
var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
|
|
3943
|
+
logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
|
|
3944
|
+
logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
|
|
3945
|
+
const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
2971
3946
|
logCollector?.info(msg);
|
|
2972
|
-
if (components.length === 0) {
|
|
3947
|
+
if (responseMode === "component" && components.length === 0) {
|
|
2973
3948
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
3949
|
+
logger.error("[useAnthropicMethod] No components available");
|
|
2974
3950
|
logCollector?.error(emptyMsg);
|
|
2975
|
-
return { success: false,
|
|
2976
|
-
}
|
|
2977
|
-
try {
|
|
2978
|
-
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
|
|
2979
|
-
return { success: true, data: matchResult };
|
|
2980
|
-
} catch (error) {
|
|
2981
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2982
|
-
logCollector?.error(`Anthropic method failed: ${errorMsg}`);
|
|
2983
|
-
throw error;
|
|
3951
|
+
return { success: false, errors: [emptyMsg] };
|
|
2984
3952
|
}
|
|
3953
|
+
logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
|
|
3954
|
+
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
|
|
3955
|
+
logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
|
|
3956
|
+
return matchResult;
|
|
2985
3957
|
};
|
|
2986
|
-
var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory) => {
|
|
2987
|
-
|
|
2988
|
-
|
|
3958
|
+
var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
|
|
3959
|
+
logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
|
|
3960
|
+
logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
|
|
3961
|
+
const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
3962
|
+
logger.info(msg);
|
|
2989
3963
|
logCollector?.info(msg);
|
|
2990
|
-
if (components.length === 0) {
|
|
3964
|
+
if (responseMode === "component" && components.length === 0) {
|
|
2991
3965
|
const emptyMsg = "Components not loaded in memory. Please ensure components are fetched first.";
|
|
3966
|
+
logger.error("[useGroqMethod] No components available");
|
|
2992
3967
|
logCollector?.error(emptyMsg);
|
|
2993
|
-
return { success: false,
|
|
2994
|
-
}
|
|
2995
|
-
try {
|
|
2996
|
-
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
|
|
2997
|
-
return { success: true, data: matchResult };
|
|
2998
|
-
} catch (error) {
|
|
2999
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3000
|
-
logCollector?.error(`Groq method failed: ${errorMsg}`);
|
|
3001
|
-
throw error;
|
|
3968
|
+
return { success: false, errors: [emptyMsg] };
|
|
3002
3969
|
}
|
|
3970
|
+
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
3971
|
+
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
|
|
3972
|
+
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
3973
|
+
return matchResult;
|
|
3003
3974
|
};
|
|
3004
3975
|
var getUserResponseFromCache = async (prompt) => {
|
|
3005
3976
|
return false;
|
|
3006
3977
|
};
|
|
3007
|
-
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory) => {
|
|
3978
|
+
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections) => {
|
|
3979
|
+
logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
|
|
3980
|
+
logger.debug(`[get_user_response] Response mode: ${responseMode}`);
|
|
3981
|
+
logger.debug("[get_user_response] Checking cache for existing response");
|
|
3008
3982
|
const userResponse = await getUserResponseFromCache(prompt);
|
|
3009
3983
|
if (userResponse) {
|
|
3984
|
+
logger.info("[get_user_response] User response found in cache - returning cached result");
|
|
3010
3985
|
logCollector?.info("User response found in cache");
|
|
3011
3986
|
return {
|
|
3012
3987
|
success: true,
|
|
3013
|
-
data: userResponse
|
|
3988
|
+
data: userResponse,
|
|
3989
|
+
errors: []
|
|
3014
3990
|
};
|
|
3015
3991
|
}
|
|
3992
|
+
logger.debug("[get_user_response] No cached response found, proceeding with LLM providers");
|
|
3016
3993
|
const providers = llmProviders || getLLMProviders();
|
|
3017
3994
|
const errors = [];
|
|
3018
3995
|
const providerOrder = providers.join(", ");
|
|
3019
3996
|
logCollector?.info(`LLM Provider order: [${providerOrder}]`);
|
|
3020
3997
|
if (conversationHistory && conversationHistory.length > 0) {
|
|
3021
|
-
|
|
3998
|
+
const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
|
|
3999
|
+
logger.debug(`[get_user_response] Using conversation history with ${exchangeCount} previous exchanges`);
|
|
4000
|
+
logCollector?.info(`Using conversation history with ${exchangeCount} previous exchanges`);
|
|
4001
|
+
} else {
|
|
4002
|
+
logger.debug("[get_user_response] No conversation history available");
|
|
3022
4003
|
}
|
|
3023
4004
|
for (let i = 0; i < providers.length; i++) {
|
|
3024
4005
|
const provider = providers[i];
|
|
3025
4006
|
const isLastProvider = i === providers.length - 1;
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
const errorMsg = `Provider ${provider} failed: ${errorMessage}`;
|
|
3050
|
-
logCollector?.error(errorMsg);
|
|
4007
|
+
const attemptMsg = `Attempting provider: ${provider} (${i + 1}/${providers.length})`;
|
|
4008
|
+
logCollector?.info(attemptMsg);
|
|
4009
|
+
let result;
|
|
4010
|
+
if (provider === "anthropic") {
|
|
4011
|
+
result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
|
|
4012
|
+
} else if (provider === "groq") {
|
|
4013
|
+
result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections);
|
|
4014
|
+
} else {
|
|
4015
|
+
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
4016
|
+
errors.push(`Unknown provider: ${provider}`);
|
|
4017
|
+
continue;
|
|
4018
|
+
}
|
|
4019
|
+
if (result.success) {
|
|
4020
|
+
const successMsg = `Success with provider: ${provider}`;
|
|
4021
|
+
logger.info(`${successMsg}`);
|
|
4022
|
+
logCollector?.info(successMsg);
|
|
4023
|
+
return result;
|
|
4024
|
+
} else {
|
|
4025
|
+
const providerErrors = result.errors.map((err) => `${provider}: ${err}`);
|
|
4026
|
+
errors.push(...providerErrors);
|
|
4027
|
+
const warnMsg = `Provider ${provider} returned unsuccessful result: ${result.errors.join(", ")}`;
|
|
4028
|
+
logger.warn(`[get_user_response] ${warnMsg}`);
|
|
4029
|
+
logCollector?.warn(warnMsg);
|
|
3051
4030
|
if (!isLastProvider) {
|
|
3052
4031
|
const fallbackMsg = "Falling back to next provider...";
|
|
4032
|
+
logger.info(`[get_user_response] ${fallbackMsg}`);
|
|
3053
4033
|
logCollector?.info(fallbackMsg);
|
|
3054
|
-
continue;
|
|
3055
4034
|
}
|
|
3056
4035
|
}
|
|
3057
4036
|
}
|
|
3058
|
-
const
|
|
3059
|
-
|
|
3060
|
-
logCollector?.error(failureMsg);
|
|
4037
|
+
const failureMsg = `All LLM providers failed`;
|
|
4038
|
+
logger.error(`[get_user_response] ${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
4039
|
+
logCollector?.error(`${failureMsg}. Errors: ${errors.join("; ")}`);
|
|
3061
4040
|
return {
|
|
3062
4041
|
success: false,
|
|
3063
|
-
|
|
4042
|
+
errors
|
|
3064
4043
|
};
|
|
3065
4044
|
};
|
|
3066
4045
|
|
|
3067
4046
|
// src/utils/log-collector.ts
|
|
4047
|
+
var LOG_LEVEL_PRIORITY2 = {
|
|
4048
|
+
errors: 0,
|
|
4049
|
+
warnings: 1,
|
|
4050
|
+
info: 2,
|
|
4051
|
+
verbose: 3
|
|
4052
|
+
};
|
|
4053
|
+
var MESSAGE_LEVEL_PRIORITY2 = {
|
|
4054
|
+
error: 0,
|
|
4055
|
+
warn: 1,
|
|
4056
|
+
info: 2,
|
|
4057
|
+
debug: 3
|
|
4058
|
+
};
|
|
3068
4059
|
var UILogCollector = class {
|
|
3069
4060
|
constructor(clientId, sendMessage, uiBlockId) {
|
|
3070
4061
|
this.logs = [];
|
|
3071
4062
|
this.uiBlockId = uiBlockId || null;
|
|
3072
4063
|
this.clientId = clientId;
|
|
3073
4064
|
this.sendMessage = sendMessage;
|
|
4065
|
+
this.currentLogLevel = logger.getLogLevel();
|
|
3074
4066
|
}
|
|
3075
4067
|
/**
|
|
3076
4068
|
* Check if logging is enabled (uiBlockId is provided)
|
|
@@ -3078,10 +4070,22 @@ var UILogCollector = class {
|
|
|
3078
4070
|
isEnabled() {
|
|
3079
4071
|
return this.uiBlockId !== null;
|
|
3080
4072
|
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Check if a message should be logged based on current log level
|
|
4075
|
+
*/
|
|
4076
|
+
shouldLog(messageLevel) {
|
|
4077
|
+
const currentLevelPriority = LOG_LEVEL_PRIORITY2[this.currentLogLevel];
|
|
4078
|
+
const messagePriority = MESSAGE_LEVEL_PRIORITY2[messageLevel];
|
|
4079
|
+
return messagePriority <= currentLevelPriority;
|
|
4080
|
+
}
|
|
3081
4081
|
/**
|
|
3082
4082
|
* Add a log entry with timestamp and immediately send to runtime
|
|
4083
|
+
* Only logs that pass the log level filter are captured and sent
|
|
3083
4084
|
*/
|
|
3084
4085
|
addLog(level, message, type, data) {
|
|
4086
|
+
if (!this.shouldLog(level)) {
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
3085
4089
|
const log = {
|
|
3086
4090
|
timestamp: Date.now(),
|
|
3087
4091
|
level,
|
|
@@ -3091,7 +4095,20 @@ var UILogCollector = class {
|
|
|
3091
4095
|
};
|
|
3092
4096
|
this.logs.push(log);
|
|
3093
4097
|
this.sendLogImmediately(log);
|
|
3094
|
-
|
|
4098
|
+
switch (level) {
|
|
4099
|
+
case "error":
|
|
4100
|
+
logger.error("UILogCollector:", log);
|
|
4101
|
+
break;
|
|
4102
|
+
case "warn":
|
|
4103
|
+
logger.warn("UILogCollector:", log);
|
|
4104
|
+
break;
|
|
4105
|
+
case "info":
|
|
4106
|
+
logger.info("UILogCollector:", log);
|
|
4107
|
+
break;
|
|
4108
|
+
case "debug":
|
|
4109
|
+
logger.debug("UILogCollector:", log);
|
|
4110
|
+
break;
|
|
4111
|
+
}
|
|
3095
4112
|
}
|
|
3096
4113
|
/**
|
|
3097
4114
|
* Send a single log to runtime immediately
|
|
@@ -3221,103 +4238,153 @@ var CONTEXT_CONFIG = {
|
|
|
3221
4238
|
};
|
|
3222
4239
|
|
|
3223
4240
|
// src/handlers/user-prompt-request.ts
|
|
3224
|
-
var
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
4241
|
+
var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) => {
|
|
4242
|
+
const errors = [];
|
|
4243
|
+
logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
|
|
4244
|
+
const parseResult = UserPromptRequestMessageSchema.safeParse(data);
|
|
4245
|
+
if (!parseResult.success) {
|
|
4246
|
+
const zodError = parseResult.error;
|
|
4247
|
+
zodError.errors.forEach((err) => {
|
|
4248
|
+
errors.push(`${err.path.join(".")}: ${err.message}`);
|
|
4249
|
+
});
|
|
4250
|
+
return { success: false, errors };
|
|
4251
|
+
}
|
|
4252
|
+
const userPromptRequest = parseResult.data;
|
|
4253
|
+
const { id, payload } = userPromptRequest;
|
|
4254
|
+
const prompt = payload.prompt;
|
|
4255
|
+
const SA_RUNTIME = payload.SA_RUNTIME;
|
|
4256
|
+
const wsId = userPromptRequest.from.id || "unknown";
|
|
4257
|
+
if (!SA_RUNTIME) {
|
|
4258
|
+
errors.push("SA_RUNTIME is required");
|
|
4259
|
+
}
|
|
4260
|
+
const threadId = SA_RUNTIME?.threadId;
|
|
4261
|
+
const existingUiBlockId = SA_RUNTIME?.uiBlockId;
|
|
4262
|
+
if (!threadId) {
|
|
4263
|
+
errors.push("threadId in SA_RUNTIME is required");
|
|
4264
|
+
}
|
|
4265
|
+
if (!existingUiBlockId) {
|
|
4266
|
+
errors.push("uiBlockId in SA_RUNTIME is required");
|
|
4267
|
+
}
|
|
4268
|
+
if (!prompt) {
|
|
4269
|
+
errors.push("Prompt not found");
|
|
4270
|
+
}
|
|
4271
|
+
logger.debug(`[REQUEST ${id}] Full request details - uiBlockId: ${existingUiBlockId}, threadId: ${threadId}, prompt: ${prompt}`);
|
|
4272
|
+
if (errors.length > 0) {
|
|
4273
|
+
return { success: false, errors, id, wsId };
|
|
4274
|
+
}
|
|
4275
|
+
const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
|
|
4276
|
+
const threadManager = ThreadManager.getInstance();
|
|
4277
|
+
let thread = threadManager.getThread(threadId);
|
|
4278
|
+
if (!thread) {
|
|
4279
|
+
thread = threadManager.createThread(threadId);
|
|
4280
|
+
logger.info(`Created new thread: ${threadId}`);
|
|
4281
|
+
}
|
|
4282
|
+
logCollector.info(`Starting user prompt request with ${components.length} components`);
|
|
4283
|
+
const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
|
|
4284
|
+
logger.info("conversationHistory", conversationHistory);
|
|
4285
|
+
const responseMode = payload.responseMode || "component";
|
|
4286
|
+
logger.info("responseMode", responseMode);
|
|
4287
|
+
let streamCallback;
|
|
4288
|
+
let accumulatedStreamResponse = "";
|
|
4289
|
+
if (responseMode === "text") {
|
|
4290
|
+
streamCallback = (chunk) => {
|
|
4291
|
+
accumulatedStreamResponse += chunk;
|
|
4292
|
+
logger.debug(`[STREAM] Sending chunk (${chunk.length} chars): "${chunk.substring(0, 20)}..."`);
|
|
4293
|
+
const streamMessage = {
|
|
4294
|
+
id: `stream_${existingUiBlockId}`,
|
|
4295
|
+
// Different ID pattern for streaming
|
|
4296
|
+
type: "USER_PROMPT_STREAM",
|
|
4297
|
+
from: { type: "data-agent" },
|
|
4298
|
+
to: {
|
|
4299
|
+
type: "runtime",
|
|
4300
|
+
id: wsId
|
|
4301
|
+
},
|
|
4302
|
+
payload: {
|
|
4303
|
+
uiBlockId: existingUiBlockId,
|
|
4304
|
+
chunk
|
|
4305
|
+
}
|
|
4306
|
+
};
|
|
4307
|
+
sendMessage(streamMessage);
|
|
4308
|
+
logger.debug(`[STREAM] Chunk sent to wsId: ${wsId}`);
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
const userResponse = await get_user_response(
|
|
4312
|
+
prompt,
|
|
4313
|
+
components,
|
|
4314
|
+
anthropicApiKey,
|
|
4315
|
+
groqApiKey,
|
|
4316
|
+
llmProviders,
|
|
4317
|
+
logCollector,
|
|
4318
|
+
conversationHistory,
|
|
4319
|
+
responseMode,
|
|
4320
|
+
streamCallback,
|
|
4321
|
+
collections
|
|
4322
|
+
);
|
|
4323
|
+
logCollector.info("User prompt request completed");
|
|
4324
|
+
const uiBlockId = existingUiBlockId;
|
|
4325
|
+
if (!userResponse.success) {
|
|
4326
|
+
logger.error(`User prompt request failed with errors: ${userResponse.errors.join(", ")}`);
|
|
4327
|
+
return {
|
|
4328
|
+
success: false,
|
|
4329
|
+
data: userResponse.data,
|
|
4330
|
+
errors: userResponse.errors,
|
|
4331
|
+
uiBlockId,
|
|
4332
|
+
threadId,
|
|
4333
|
+
id,
|
|
4334
|
+
wsId
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
let component = null;
|
|
4338
|
+
let textResponse = null;
|
|
4339
|
+
if (userResponse.data) {
|
|
4340
|
+
if (typeof userResponse.data === "object") {
|
|
4341
|
+
if ("component" in userResponse.data) {
|
|
4342
|
+
component = userResponse.data.component;
|
|
4343
|
+
}
|
|
4344
|
+
if ("textResponse" in userResponse.data) {
|
|
4345
|
+
textResponse = userResponse.data.text;
|
|
3242
4346
|
}
|
|
3243
4347
|
}
|
|
3244
|
-
if (!SA_RUNTIME) {
|
|
3245
|
-
sendDataResponse4(id, {
|
|
3246
|
-
success: false,
|
|
3247
|
-
error: "SA_RUNTIME is required"
|
|
3248
|
-
}, sendMessage, wsId);
|
|
3249
|
-
return;
|
|
3250
|
-
}
|
|
3251
|
-
const threadId = SA_RUNTIME.threadId;
|
|
3252
|
-
const existingUiBlockId = SA_RUNTIME.uiBlockId;
|
|
3253
|
-
if (!threadId) {
|
|
3254
|
-
sendDataResponse4(id, {
|
|
3255
|
-
success: false,
|
|
3256
|
-
error: "threadId in SA_RUNTIME is required"
|
|
3257
|
-
}, sendMessage, wsId);
|
|
3258
|
-
return;
|
|
3259
|
-
}
|
|
3260
|
-
if (!existingUiBlockId) {
|
|
3261
|
-
sendDataResponse4(id, {
|
|
3262
|
-
success: false,
|
|
3263
|
-
error: "uiBlockId in SA_RUNTIME is required"
|
|
3264
|
-
}, sendMessage, wsId);
|
|
3265
|
-
return;
|
|
3266
|
-
}
|
|
3267
|
-
const logCollector = new UILogCollector(wsId, sendMessage, existingUiBlockId);
|
|
3268
|
-
if (!prompt) {
|
|
3269
|
-
sendDataResponse4(id, {
|
|
3270
|
-
success: false,
|
|
3271
|
-
error: "Prompt not found"
|
|
3272
|
-
}, sendMessage, wsId);
|
|
3273
|
-
return;
|
|
3274
|
-
}
|
|
3275
|
-
if (!components || components.length === 0) {
|
|
3276
|
-
sendDataResponse4(id, {
|
|
3277
|
-
success: false,
|
|
3278
|
-
error: "Components not found"
|
|
3279
|
-
}, sendMessage, wsId);
|
|
3280
|
-
return;
|
|
3281
|
-
}
|
|
3282
|
-
const threadManager = ThreadManager.getInstance();
|
|
3283
|
-
let thread = threadManager.getThread(threadId);
|
|
3284
|
-
if (!thread) {
|
|
3285
|
-
thread = threadManager.createThread(threadId);
|
|
3286
|
-
logger.info(`Created new thread: ${threadId}`);
|
|
3287
|
-
}
|
|
3288
|
-
logCollector.info(`Starting user prompt request with ${components.length} components`);
|
|
3289
|
-
const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, existingUiBlockId);
|
|
3290
|
-
logger.info("conversationHistory", conversationHistory);
|
|
3291
|
-
const userResponse = await get_user_response(prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory);
|
|
3292
|
-
logger.info("llm userResponse", userResponse);
|
|
3293
|
-
logCollector.info("User prompt request completed");
|
|
3294
|
-
if (userResponse.success && userResponse.data && typeof userResponse.data === "object" && "component" in userResponse.data) {
|
|
3295
|
-
const component = userResponse.data.component;
|
|
3296
|
-
const uiBlockId = existingUiBlockId;
|
|
3297
|
-
const uiBlock = new UIBlock(
|
|
3298
|
-
prompt,
|
|
3299
|
-
{},
|
|
3300
|
-
// componentData: initially empty, will be filled later
|
|
3301
|
-
component || {},
|
|
3302
|
-
// generatedComponentMetadata: full component object (ComponentSchema)
|
|
3303
|
-
[],
|
|
3304
|
-
// actions: empty initially
|
|
3305
|
-
uiBlockId
|
|
3306
|
-
);
|
|
3307
|
-
thread.addUIBlock(uiBlock);
|
|
3308
|
-
logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
|
|
3309
|
-
sendDataResponse4(id, {
|
|
3310
|
-
...userResponse,
|
|
3311
|
-
uiBlockId,
|
|
3312
|
-
threadId
|
|
3313
|
-
}, sendMessage, wsId);
|
|
3314
|
-
} else {
|
|
3315
|
-
sendDataResponse4(id, userResponse, sendMessage, wsId);
|
|
3316
|
-
}
|
|
3317
|
-
return;
|
|
3318
|
-
} catch (error) {
|
|
3319
|
-
logger.error("Failed to handle user prompt request:", error);
|
|
3320
4348
|
}
|
|
4349
|
+
const finalTextResponse = responseMode === "text" && accumulatedStreamResponse ? accumulatedStreamResponse : textResponse;
|
|
4350
|
+
const uiBlock = new UIBlock(
|
|
4351
|
+
prompt,
|
|
4352
|
+
{},
|
|
4353
|
+
// componentData: initially empty, will be filled later
|
|
4354
|
+
component,
|
|
4355
|
+
// generatedComponentMetadata: full component object (ComponentSchema)
|
|
4356
|
+
[],
|
|
4357
|
+
// actions: empty initially
|
|
4358
|
+
uiBlockId,
|
|
4359
|
+
finalTextResponse
|
|
4360
|
+
// textResponse: FULL streaming response including all intermediate messages
|
|
4361
|
+
);
|
|
4362
|
+
thread.addUIBlock(uiBlock);
|
|
4363
|
+
logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
|
|
4364
|
+
return {
|
|
4365
|
+
success: userResponse.success,
|
|
4366
|
+
data: userResponse.data,
|
|
4367
|
+
errors: userResponse.errors,
|
|
4368
|
+
uiBlockId,
|
|
4369
|
+
threadId,
|
|
4370
|
+
id,
|
|
4371
|
+
wsId
|
|
4372
|
+
};
|
|
4373
|
+
};
|
|
4374
|
+
async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections) {
|
|
4375
|
+
const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections);
|
|
4376
|
+
sendDataResponse4(
|
|
4377
|
+
response.id || data.id,
|
|
4378
|
+
{
|
|
4379
|
+
success: response.success,
|
|
4380
|
+
errors: response.errors,
|
|
4381
|
+
data: response.data,
|
|
4382
|
+
uiBlockId: response.uiBlockId,
|
|
4383
|
+
threadId: response.threadId
|
|
4384
|
+
},
|
|
4385
|
+
sendMessage,
|
|
4386
|
+
response.wsId || data.from?.id
|
|
4387
|
+
);
|
|
3321
4388
|
}
|
|
3322
4389
|
function sendDataResponse4(id, res, sendMessage, clientId) {
|
|
3323
4390
|
const response = {
|
|
@@ -3430,12 +4497,27 @@ function sendResponse(id, res, sendMessage, clientId) {
|
|
|
3430
4497
|
// src/userResponse/next-questions.ts
|
|
3431
4498
|
async function generateNextQuestions(originalUserPrompt, component, componentData, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory) {
|
|
3432
4499
|
try {
|
|
4500
|
+
logger.debug("[generateNextQuestions] Starting next questions generation");
|
|
4501
|
+
logger.debug(`[generateNextQuestions] User prompt: "${originalUserPrompt?.substring(0, 50)}..."`);
|
|
4502
|
+
logger.debug(`[generateNextQuestions] Component: ${component?.name || "unknown"} (${component?.type || "unknown"})`);
|
|
4503
|
+
logger.debug(`[generateNextQuestions] Component data available: ${componentData ? "yes" : "no"}`);
|
|
3433
4504
|
const providers = llmProviders || ["anthropic"];
|
|
3434
|
-
|
|
4505
|
+
logger.info(`[generateNextQuestions] Using LLM providers: [${providers.join(", ")}]`);
|
|
4506
|
+
if (conversationHistory && conversationHistory.length > 0) {
|
|
4507
|
+
const exchangeCount = conversationHistory.split("\n").filter((l) => l.startsWith("Q")).length;
|
|
4508
|
+
logger.debug(`[generateNextQuestions] Using conversation history with ${exchangeCount} previous exchanges`);
|
|
4509
|
+
} else {
|
|
4510
|
+
logger.debug("[generateNextQuestions] No conversation history available");
|
|
4511
|
+
}
|
|
4512
|
+
for (let i = 0; i < providers.length; i++) {
|
|
4513
|
+
const provider = providers[i];
|
|
4514
|
+
const isLastProvider = i === providers.length - 1;
|
|
3435
4515
|
try {
|
|
3436
|
-
logger.info(`
|
|
4516
|
+
logger.info(`[generateNextQuestions] Attempting provider: ${provider} (${i + 1}/${providers.length})`);
|
|
4517
|
+
logCollector?.info(`Generating questions with ${provider}...`);
|
|
3437
4518
|
let result = [];
|
|
3438
4519
|
if (provider === "groq") {
|
|
4520
|
+
logger.debug("[generateNextQuestions] Using Groq LLM for next questions");
|
|
3439
4521
|
result = await groqLLM.generateNextQuestions(
|
|
3440
4522
|
originalUserPrompt,
|
|
3441
4523
|
component,
|
|
@@ -3445,6 +4527,7 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
3445
4527
|
conversationHistory
|
|
3446
4528
|
);
|
|
3447
4529
|
} else {
|
|
4530
|
+
logger.debug("[generateNextQuestions] Using Anthropic LLM for next questions");
|
|
3448
4531
|
result = await anthropicLLM.generateNextQuestions(
|
|
3449
4532
|
originalUserPrompt,
|
|
3450
4533
|
component,
|
|
@@ -3455,21 +4538,39 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
3455
4538
|
);
|
|
3456
4539
|
}
|
|
3457
4540
|
if (result && result.length > 0) {
|
|
3458
|
-
logger.info(`Successfully generated ${result.length} questions with ${provider}`);
|
|
4541
|
+
logger.info(`[generateNextQuestions] Successfully generated ${result.length} questions with ${provider}`);
|
|
4542
|
+
logger.debug(`[generateNextQuestions] Questions: ${JSON.stringify(result)}`);
|
|
4543
|
+
logCollector?.info(`Generated ${result.length} follow-up questions`);
|
|
3459
4544
|
return result;
|
|
3460
4545
|
}
|
|
3461
|
-
|
|
4546
|
+
const warnMsg = `No questions generated from ${provider}${!isLastProvider ? ", trying next provider..." : ""}`;
|
|
4547
|
+
logger.warn(`[generateNextQuestions] ${warnMsg}`);
|
|
4548
|
+
if (!isLastProvider) {
|
|
4549
|
+
logCollector?.warn(warnMsg);
|
|
4550
|
+
}
|
|
3462
4551
|
} catch (providerError) {
|
|
3463
|
-
|
|
3464
|
-
|
|
4552
|
+
const errorMsg = providerError instanceof Error ? providerError.message : String(providerError);
|
|
4553
|
+
logger.error(`[generateNextQuestions] Provider ${provider} failed: ${errorMsg}`);
|
|
4554
|
+
logger.debug(`[generateNextQuestions] Provider error details:`, providerError);
|
|
4555
|
+
if (!isLastProvider) {
|
|
4556
|
+
const fallbackMsg = `Provider ${provider} failed, trying next provider...`;
|
|
4557
|
+
logger.info(`[generateNextQuestions] ${fallbackMsg}`);
|
|
4558
|
+
logCollector?.warn(fallbackMsg);
|
|
4559
|
+
} else {
|
|
4560
|
+
logCollector?.error(`Failed to generate questions with ${provider}`);
|
|
4561
|
+
}
|
|
3465
4562
|
continue;
|
|
3466
4563
|
}
|
|
3467
4564
|
}
|
|
3468
|
-
logger.warn("All providers failed or returned no questions");
|
|
4565
|
+
logger.warn("[generateNextQuestions] All providers failed or returned no questions");
|
|
4566
|
+
logCollector?.warn("Unable to generate follow-up questions");
|
|
3469
4567
|
return [];
|
|
3470
4568
|
} catch (error) {
|
|
3471
|
-
|
|
3472
|
-
|
|
4569
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4570
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
4571
|
+
logger.error(`[generateNextQuestions] Error generating next questions: ${errorMsg}`);
|
|
4572
|
+
logger.debug("[generateNextQuestions] Error stack trace:", errorStack);
|
|
4573
|
+
logCollector?.error(`Error generating next questions: ${errorMsg}`);
|
|
3473
4574
|
return [];
|
|
3474
4575
|
}
|
|
3475
4576
|
}
|
|
@@ -3477,11 +4578,15 @@ async function generateNextQuestions(originalUserPrompt, component, componentDat
|
|
|
3477
4578
|
// src/handlers/actions-request.ts
|
|
3478
4579
|
async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiKey, llmProviders) {
|
|
3479
4580
|
try {
|
|
4581
|
+
logger.debug("[ACTIONS_REQ] Parsing incoming actions request");
|
|
3480
4582
|
const actionsRequest = ActionsRequestMessageSchema.parse(data);
|
|
3481
4583
|
const { id, payload } = actionsRequest;
|
|
3482
4584
|
const { SA_RUNTIME } = payload;
|
|
3483
4585
|
const wsId = actionsRequest.from.id || "unknown";
|
|
4586
|
+
logger.info(`[ACTIONS_REQ ${id}] Processing actions request from client: ${wsId}`);
|
|
4587
|
+
logger.debug(`[ACTIONS_REQ ${id}] Request payload:`, JSON.stringify(payload, null, 2).substring(0, 200));
|
|
3484
4588
|
if (!SA_RUNTIME) {
|
|
4589
|
+
logger.error(`[ACTIONS_REQ ${id}] SA_RUNTIME missing from request`);
|
|
3485
4590
|
sendResponse2(id, {
|
|
3486
4591
|
success: false,
|
|
3487
4592
|
error: "SA_RUNTIME with threadId and uiBlockId is required"
|
|
@@ -3490,31 +4595,54 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
3490
4595
|
}
|
|
3491
4596
|
const uiBlockId = SA_RUNTIME.uiBlockId;
|
|
3492
4597
|
const threadId = SA_RUNTIME.threadId;
|
|
4598
|
+
logger.debug(`[ACTIONS_REQ ${id}] SA_RUNTIME validated - threadId: ${threadId}, uiBlockId: ${uiBlockId}`);
|
|
4599
|
+
logger.debug(`[ACTIONS_REQ ${id}] Retrieving thread: ${threadId}`);
|
|
3493
4600
|
const threadManager = ThreadManager.getInstance();
|
|
3494
4601
|
const thread = threadManager.getThread(threadId);
|
|
3495
4602
|
if (!thread) {
|
|
4603
|
+
logger.error(`[ACTIONS_REQ ${id}] Thread '${threadId}' not found`);
|
|
3496
4604
|
sendResponse2(id, {
|
|
3497
4605
|
success: false,
|
|
3498
4606
|
error: `Thread '${threadId}' not found`
|
|
3499
4607
|
}, sendMessage, wsId);
|
|
3500
4608
|
return;
|
|
3501
4609
|
}
|
|
4610
|
+
logger.debug(`[ACTIONS_REQ ${id}] Thread found with ${thread.getUIBlocks().length} UIBlocks`);
|
|
4611
|
+
logger.debug(`[ACTIONS_REQ ${id}] Retrieving UIBlock: ${uiBlockId}`);
|
|
3502
4612
|
const uiBlock = thread.getUIBlock(uiBlockId);
|
|
3503
4613
|
if (!uiBlock) {
|
|
4614
|
+
logger.error(`[ACTIONS_REQ ${id}] UIBlock '${uiBlockId}' not found in thread '${threadId}'`);
|
|
3504
4615
|
sendResponse2(id, {
|
|
3505
4616
|
success: false,
|
|
3506
4617
|
error: `UIBlock '${uiBlockId}' not found in thread '${threadId}'`
|
|
3507
4618
|
}, sendMessage, wsId);
|
|
3508
4619
|
return;
|
|
3509
4620
|
}
|
|
4621
|
+
logger.info(`[ACTIONS_REQ ${id}] UIBlock retrieved successfully`);
|
|
4622
|
+
logger.debug(`[ACTIONS_REQ ${id}] Creating UILogCollector for uiBlockId: ${uiBlockId}`);
|
|
3510
4623
|
const logCollector = new UILogCollector(wsId, sendMessage, uiBlockId);
|
|
4624
|
+
logger.info(`[ACTIONS_REQ ${id}] UILogCollector initialized`);
|
|
4625
|
+
logger.debug(`[ACTIONS_REQ ${id}] Extracting data from UIBlock`);
|
|
3511
4626
|
const userQuestion = uiBlock.getUserQuestion();
|
|
3512
4627
|
const component = uiBlock.getComponentMetadata();
|
|
3513
4628
|
const componentData = uiBlock.getComponentData();
|
|
4629
|
+
logger.debug(`[ACTIONS_REQ ${id}] User question: "${userQuestion?.substring(0, 50)}..."`);
|
|
4630
|
+
logger.debug(`[ACTIONS_REQ ${id}] Component: ${component?.name || "unknown"} (${component?.type || "unknown"})`);
|
|
4631
|
+
logger.debug(`[ACTIONS_REQ ${id}] Component data available: ${componentData ? "yes" : "no"}`);
|
|
4632
|
+
logger.debug(`[ACTIONS_REQ ${id}] Extracting conversation history (max ${CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS} blocks)`);
|
|
3514
4633
|
const conversationHistory = thread.getConversationContext(CONTEXT_CONFIG.MAX_CONVERSATION_CONTEXT_BLOCKS, uiBlockId);
|
|
4634
|
+
const historyLineCount = conversationHistory.split("\n").filter((l) => l.trim()).length;
|
|
4635
|
+
logger.info(`[ACTIONS_REQ ${id}] Conversation history extracted: ${historyLineCount} lines`);
|
|
4636
|
+
logger.debug(`[ACTIONS_REQ ${id}] Conversation history preview:
|
|
4637
|
+
${conversationHistory.substring(0, 200)}...`);
|
|
3515
4638
|
logCollector.info(`Generating actions for UIBlock: ${uiBlockId}`);
|
|
3516
|
-
logger.info(`Generating actions for component: ${component?.name || "unknown"}`);
|
|
4639
|
+
logger.info(`[ACTIONS_REQ ${id}] Generating actions for component: ${component?.name || "unknown"}`);
|
|
4640
|
+
logger.debug(`[ACTIONS_REQ ${id}] Checking if actions are already cached`);
|
|
4641
|
+
const startTime = Date.now();
|
|
3517
4642
|
const actions = await uiBlock.getOrFetchActions(async () => {
|
|
4643
|
+
logger.info(`[ACTIONS_REQ ${id}] Actions not cached, generating new actions...`);
|
|
4644
|
+
logCollector.info("Generating follow-up questions...");
|
|
4645
|
+
logger.info(`[ACTIONS_REQ ${id}] Starting next questions generation with ${llmProviders?.join(", ") || "default"} providers`);
|
|
3518
4646
|
const nextQuestions = await generateNextQuestions(
|
|
3519
4647
|
userQuestion,
|
|
3520
4648
|
component,
|
|
@@ -3525,14 +4653,28 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
3525
4653
|
logCollector,
|
|
3526
4654
|
conversationHistory
|
|
3527
4655
|
);
|
|
3528
|
-
|
|
4656
|
+
logger.info(`[ACTIONS_REQ ${id}] Generated ${nextQuestions.length} questions`);
|
|
4657
|
+
logger.debug(`[ACTIONS_REQ ${id}] Questions: ${JSON.stringify(nextQuestions)}`);
|
|
4658
|
+
logger.debug(`[ACTIONS_REQ ${id}] Converting questions to actions format`);
|
|
4659
|
+
const convertedActions = nextQuestions.map((question, index) => ({
|
|
3529
4660
|
id: `action_${index}_${Date.now()}`,
|
|
3530
4661
|
name: question,
|
|
3531
4662
|
type: "next_question",
|
|
3532
4663
|
question
|
|
3533
4664
|
}));
|
|
4665
|
+
logger.debug(`[ACTIONS_REQ ${id}] Converted ${convertedActions.length} actions`);
|
|
4666
|
+
return convertedActions;
|
|
3534
4667
|
});
|
|
3535
|
-
|
|
4668
|
+
const processingTime = Date.now() - startTime;
|
|
4669
|
+
logger.info(`[ACTIONS_REQ ${id}] Actions retrieved in ${processingTime}ms - ${actions.length} actions total`);
|
|
4670
|
+
if (actions.length > 0) {
|
|
4671
|
+
logCollector.info(`Generated ${actions.length} follow-up questions successfully`);
|
|
4672
|
+
logger.debug(`[ACTIONS_REQ ${id}] Actions: ${actions.map((a) => a.name).join(", ")}`);
|
|
4673
|
+
} else {
|
|
4674
|
+
logger.warn(`[ACTIONS_REQ ${id}] No actions generated`);
|
|
4675
|
+
logCollector.warn("No follow-up questions could be generated");
|
|
4676
|
+
}
|
|
4677
|
+
logger.debug(`[ACTIONS_REQ ${id}] Sending successful response to client`);
|
|
3536
4678
|
sendResponse2(id, {
|
|
3537
4679
|
success: true,
|
|
3538
4680
|
data: {
|
|
@@ -3543,12 +4685,26 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
|
|
|
3543
4685
|
threadId
|
|
3544
4686
|
}
|
|
3545
4687
|
}, sendMessage, wsId);
|
|
4688
|
+
logger.info(`[ACTIONS_REQ ${id}] \u2713 Actions request completed successfully`);
|
|
3546
4689
|
} catch (error) {
|
|
3547
|
-
|
|
4690
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
4691
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
4692
|
+
logger.error(`[ACTIONS_REQ] Failed to handle actions request: ${errorMessage}`);
|
|
4693
|
+
logger.debug(`[ACTIONS_REQ] Error stack trace:`, errorStack);
|
|
4694
|
+
try {
|
|
4695
|
+
const parsedData = data;
|
|
4696
|
+
if (parsedData?.id && parsedData?.from?.id) {
|
|
4697
|
+
const logCollector = parsedData?.payload?.SA_RUNTIME?.uiBlockId ? new UILogCollector(parsedData.from.id, sendMessage, parsedData.payload.SA_RUNTIME.uiBlockId) : void 0;
|
|
4698
|
+
logCollector?.error(`Failed to generate actions: ${errorMessage}`);
|
|
4699
|
+
}
|
|
4700
|
+
} catch (logError) {
|
|
4701
|
+
logger.debug("[ACTIONS_REQ] Failed to send error logs to UI:", logError);
|
|
4702
|
+
}
|
|
3548
4703
|
sendResponse2(null, {
|
|
3549
4704
|
success: false,
|
|
3550
|
-
error:
|
|
4705
|
+
error: errorMessage
|
|
3551
4706
|
}, sendMessage);
|
|
4707
|
+
logger.info("[ACTIONS_REQ] \u2717 Actions request completed with errors");
|
|
3552
4708
|
}
|
|
3553
4709
|
}
|
|
3554
4710
|
function sendResponse2(id, res, sendMessage, clientId) {
|
|
@@ -3564,6 +4720,11 @@ function sendResponse2(id, res, sendMessage, clientId) {
|
|
|
3564
4720
|
...res
|
|
3565
4721
|
}
|
|
3566
4722
|
};
|
|
4723
|
+
logger.debug(`[ACTIONS_RES ${id || "unknown"}] Sending ${res.success ? "successful" : "failed"} response to client`);
|
|
4724
|
+
logger.debug(`[ACTIONS_RES ${id || "unknown"}] Response payload size: ${JSON.stringify(response).length} bytes`);
|
|
4725
|
+
if (res.data?.actions) {
|
|
4726
|
+
logger.debug(`[ACTIONS_RES ${id || "unknown"}] Sending ${res.data.actions.length} actions`);
|
|
4727
|
+
}
|
|
3567
4728
|
sendMessage(response);
|
|
3568
4729
|
}
|
|
3569
4730
|
|
|
@@ -3592,7 +4753,10 @@ async function handleUsersRequest(data, sendMessage) {
|
|
|
3592
4753
|
const { id, payload, from } = request;
|
|
3593
4754
|
const { operation, data: requestData } = payload;
|
|
3594
4755
|
const username = requestData?.username;
|
|
4756
|
+
const email = requestData?.email;
|
|
3595
4757
|
const password = requestData?.password;
|
|
4758
|
+
const fullname = requestData?.fullname;
|
|
4759
|
+
const role = requestData?.role;
|
|
3596
4760
|
if (from.type !== "admin") {
|
|
3597
4761
|
sendResponse3(id, {
|
|
3598
4762
|
success: false,
|
|
@@ -3604,10 +4768,10 @@ async function handleUsersRequest(data, sendMessage) {
|
|
|
3604
4768
|
const userManager = getUserManager();
|
|
3605
4769
|
switch (operation) {
|
|
3606
4770
|
case "create":
|
|
3607
|
-
await handleCreate(id, username, password, userManager, sendMessage, from.id);
|
|
4771
|
+
await handleCreate(id, { username, email, password, fullname, role }, userManager, sendMessage, from.id);
|
|
3608
4772
|
break;
|
|
3609
4773
|
case "update":
|
|
3610
|
-
await handleUpdate(id, username, password, userManager, sendMessage, from.id);
|
|
4774
|
+
await handleUpdate(id, { username, email, password, fullname, role }, userManager, sendMessage, from.id);
|
|
3611
4775
|
break;
|
|
3612
4776
|
case "delete":
|
|
3613
4777
|
await handleDelete(id, username, userManager, sendMessage, from.id);
|
|
@@ -3632,7 +4796,8 @@ async function handleUsersRequest(data, sendMessage) {
|
|
|
3632
4796
|
}, sendMessage);
|
|
3633
4797
|
}
|
|
3634
4798
|
}
|
|
3635
|
-
async function handleCreate(id,
|
|
4799
|
+
async function handleCreate(id, userData, userManager, sendMessage, clientId) {
|
|
4800
|
+
const { username, email, password, fullname, role } = userData;
|
|
3636
4801
|
if (!username || username.trim().length === 0) {
|
|
3637
4802
|
sendResponse3(id, {
|
|
3638
4803
|
success: false,
|
|
@@ -3647,6 +4812,16 @@ async function handleCreate(id, username, password, userManager, sendMessage, cl
|
|
|
3647
4812
|
}, sendMessage, clientId);
|
|
3648
4813
|
return;
|
|
3649
4814
|
}
|
|
4815
|
+
if (email && email.trim().length > 0) {
|
|
4816
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4817
|
+
if (!emailRegex.test(email)) {
|
|
4818
|
+
sendResponse3(id, {
|
|
4819
|
+
success: false,
|
|
4820
|
+
error: "Invalid email format"
|
|
4821
|
+
}, sendMessage, clientId);
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
3650
4825
|
if (userManager.userExists(username)) {
|
|
3651
4826
|
sendResponse3(id, {
|
|
3652
4827
|
success: false,
|
|
@@ -3654,25 +4829,41 @@ async function handleCreate(id, username, password, userManager, sendMessage, cl
|
|
|
3654
4829
|
}, sendMessage, clientId);
|
|
3655
4830
|
return;
|
|
3656
4831
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
4832
|
+
if (email && userManager.getUserByEmail(email)) {
|
|
4833
|
+
sendResponse3(id, {
|
|
4834
|
+
success: false,
|
|
4835
|
+
error: `User with email '${email}' already exists`
|
|
4836
|
+
}, sendMessage, clientId);
|
|
4837
|
+
return;
|
|
3660
4838
|
}
|
|
3661
|
-
const
|
|
4839
|
+
const newUserData = {
|
|
3662
4840
|
username,
|
|
3663
|
-
password
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
4841
|
+
password
|
|
4842
|
+
};
|
|
4843
|
+
if (email && email.trim().length > 0) {
|
|
4844
|
+
newUserData.email = email.trim();
|
|
4845
|
+
}
|
|
4846
|
+
if (fullname && fullname.trim().length > 0) {
|
|
4847
|
+
newUserData.fullname = fullname.trim();
|
|
4848
|
+
}
|
|
4849
|
+
if (role && role.trim().length > 0) {
|
|
4850
|
+
newUserData.role = role.trim();
|
|
4851
|
+
}
|
|
4852
|
+
const newUser = userManager.createUser(newUserData);
|
|
4853
|
+
logger.info(`User created by admin: ${username}${email ? ` (${email})` : ""}`);
|
|
3667
4854
|
sendResponse3(id, {
|
|
3668
4855
|
success: true,
|
|
3669
4856
|
data: {
|
|
3670
4857
|
username: newUser.username,
|
|
4858
|
+
email: newUser.email,
|
|
4859
|
+
fullname: newUser.fullname,
|
|
4860
|
+
role: newUser.role,
|
|
3671
4861
|
message: `User '${username}' created successfully`
|
|
3672
4862
|
}
|
|
3673
4863
|
}, sendMessage, clientId);
|
|
3674
4864
|
}
|
|
3675
|
-
async function handleUpdate(id,
|
|
4865
|
+
async function handleUpdate(id, userData, userManager, sendMessage, clientId) {
|
|
4866
|
+
const { username, email, password, fullname, role } = userData;
|
|
3676
4867
|
if (!username || username.trim().length === 0) {
|
|
3677
4868
|
sendResponse3(id, {
|
|
3678
4869
|
success: false,
|
|
@@ -3688,13 +4879,42 @@ async function handleUpdate(id, username, password, userManager, sendMessage, cl
|
|
|
3688
4879
|
return;
|
|
3689
4880
|
}
|
|
3690
4881
|
const updates = {};
|
|
3691
|
-
if (
|
|
3692
|
-
|
|
4882
|
+
if (email !== void 0) {
|
|
4883
|
+
if (email.trim().length > 0) {
|
|
4884
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4885
|
+
if (!emailRegex.test(email)) {
|
|
4886
|
+
sendResponse3(id, {
|
|
4887
|
+
success: false,
|
|
4888
|
+
error: "Invalid email format"
|
|
4889
|
+
}, sendMessage, clientId);
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4892
|
+
const existingUser = userManager.getUserByEmail(email);
|
|
4893
|
+
if (existingUser && existingUser.username !== username) {
|
|
4894
|
+
sendResponse3(id, {
|
|
4895
|
+
success: false,
|
|
4896
|
+
error: `Email '${email}' is already used by another user`
|
|
4897
|
+
}, sendMessage, clientId);
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
updates.email = email.trim();
|
|
4901
|
+
} else {
|
|
4902
|
+
updates.email = void 0;
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
if (password !== void 0 && password.trim().length > 0) {
|
|
4906
|
+
updates.password = password.trim();
|
|
4907
|
+
}
|
|
4908
|
+
if (fullname !== void 0) {
|
|
4909
|
+
updates.fullname = fullname.trim().length > 0 ? fullname.trim() : void 0;
|
|
4910
|
+
}
|
|
4911
|
+
if (role !== void 0) {
|
|
4912
|
+
updates.role = role.trim().length > 0 ? role.trim() : void 0;
|
|
3693
4913
|
}
|
|
3694
4914
|
if (Object.keys(updates).length === 0) {
|
|
3695
4915
|
sendResponse3(id, {
|
|
3696
4916
|
success: false,
|
|
3697
|
-
error: "No fields to update. Please provide
|
|
4917
|
+
error: "No fields to update. Please provide at least one field to update."
|
|
3698
4918
|
}, sendMessage, clientId);
|
|
3699
4919
|
return;
|
|
3700
4920
|
}
|
|
@@ -3704,6 +4924,9 @@ async function handleUpdate(id, username, password, userManager, sendMessage, cl
|
|
|
3704
4924
|
success: true,
|
|
3705
4925
|
data: {
|
|
3706
4926
|
username: updatedUser.username,
|
|
4927
|
+
email: updatedUser.email,
|
|
4928
|
+
fullname: updatedUser.fullname,
|
|
4929
|
+
role: updatedUser.role,
|
|
3707
4930
|
message: `User '${username}' updated successfully`
|
|
3708
4931
|
}
|
|
3709
4932
|
}, sendMessage, clientId);
|
|
@@ -3744,6 +4967,9 @@ async function handleGetAll(id, userManager, sendMessage, clientId) {
|
|
|
3744
4967
|
const users = userManager.getAllUsers();
|
|
3745
4968
|
const sanitizedUsers = users.map((user) => ({
|
|
3746
4969
|
username: user.username,
|
|
4970
|
+
email: user.email,
|
|
4971
|
+
fullname: user.fullname,
|
|
4972
|
+
role: user.role,
|
|
3747
4973
|
wsIds: user.wsIds || []
|
|
3748
4974
|
}));
|
|
3749
4975
|
logger.info(`Admin retrieved all users (count: ${sanitizedUsers.length})`);
|
|
@@ -3774,6 +5000,9 @@ async function handleGetOne(id, username, userManager, sendMessage, clientId) {
|
|
|
3774
5000
|
const user = userManager.getUser(username);
|
|
3775
5001
|
const sanitizedUser = {
|
|
3776
5002
|
username: user.username,
|
|
5003
|
+
email: user.email,
|
|
5004
|
+
fullname: user.fullname,
|
|
5005
|
+
role: user.role,
|
|
3777
5006
|
wsIds: user.wsIds || []
|
|
3778
5007
|
};
|
|
3779
5008
|
logger.info(`Admin retrieved user: ${username}`);
|
|
@@ -4219,7 +5448,7 @@ function sendResponse5(id, res, sendMessage, clientId) {
|
|
|
4219
5448
|
}
|
|
4220
5449
|
|
|
4221
5450
|
// src/auth/user-manager.ts
|
|
4222
|
-
import
|
|
5451
|
+
import fs5 from "fs";
|
|
4223
5452
|
import path4 from "path";
|
|
4224
5453
|
import os from "os";
|
|
4225
5454
|
var UserManager = class {
|
|
@@ -4259,21 +5488,22 @@ var UserManager = class {
|
|
|
4259
5488
|
async loadUsersFromFile() {
|
|
4260
5489
|
try {
|
|
4261
5490
|
const dir = path4.dirname(this.filePath);
|
|
4262
|
-
if (!
|
|
5491
|
+
if (!fs5.existsSync(dir)) {
|
|
4263
5492
|
logger.info(`Creating directory structure: ${dir}`);
|
|
4264
|
-
|
|
5493
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4265
5494
|
}
|
|
4266
|
-
if (!
|
|
5495
|
+
if (!fs5.existsSync(this.filePath)) {
|
|
4267
5496
|
logger.info(`Users file does not exist at ${this.filePath}, creating with empty users`);
|
|
4268
5497
|
const initialData = { users: [] };
|
|
4269
|
-
|
|
5498
|
+
fs5.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
|
|
4270
5499
|
this.users = [];
|
|
4271
5500
|
this.hasChanged = false;
|
|
4272
5501
|
return;
|
|
4273
5502
|
}
|
|
4274
|
-
const fileContent =
|
|
4275
|
-
const
|
|
4276
|
-
|
|
5503
|
+
const fileContent = fs5.readFileSync(this.filePath, "utf-8");
|
|
5504
|
+
const rawData = JSON.parse(fileContent);
|
|
5505
|
+
const validatedData = UsersDataSchema.parse(rawData);
|
|
5506
|
+
this.users = validatedData.users;
|
|
4277
5507
|
this.hasChanged = false;
|
|
4278
5508
|
logger.debug(`Loaded ${this.users.length} users from file`);
|
|
4279
5509
|
} catch (error) {
|
|
@@ -4290,13 +5520,17 @@ var UserManager = class {
|
|
|
4290
5520
|
}
|
|
4291
5521
|
try {
|
|
4292
5522
|
const dir = path4.dirname(this.filePath);
|
|
4293
|
-
if (!
|
|
4294
|
-
|
|
5523
|
+
if (!fs5.existsSync(dir)) {
|
|
5524
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4295
5525
|
}
|
|
4296
|
-
const
|
|
4297
|
-
|
|
5526
|
+
const usersToSave = this.users.map((user) => {
|
|
5527
|
+
const { wsIds, ...userWithoutWsIds } = user;
|
|
5528
|
+
return userWithoutWsIds;
|
|
5529
|
+
});
|
|
5530
|
+
const data = { users: usersToSave };
|
|
5531
|
+
fs5.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
|
|
4298
5532
|
this.hasChanged = false;
|
|
4299
|
-
logger.debug(`Synced ${this.users.length} users to file`);
|
|
5533
|
+
logger.debug(`Synced ${this.users.length} users to file (wsIds excluded)`);
|
|
4300
5534
|
} catch (error) {
|
|
4301
5535
|
logger.error("Failed to save users to file:", error);
|
|
4302
5536
|
throw new Error(`Failed to save users to file: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -4343,13 +5577,17 @@ var UserManager = class {
|
|
|
4343
5577
|
* @returns The created user
|
|
4344
5578
|
*/
|
|
4345
5579
|
createUser(user) {
|
|
4346
|
-
|
|
4347
|
-
|
|
5580
|
+
const validatedUser = UserSchema.parse(user);
|
|
5581
|
+
if (this.users.some((u) => u.username === validatedUser.username)) {
|
|
5582
|
+
throw new Error(`User with username ${validatedUser.username} already exists`);
|
|
5583
|
+
}
|
|
5584
|
+
if (validatedUser.email && this.users.some((u) => u.email === validatedUser.email)) {
|
|
5585
|
+
throw new Error(`User with email ${validatedUser.email} already exists`);
|
|
4348
5586
|
}
|
|
4349
|
-
this.users.push(
|
|
5587
|
+
this.users.push(validatedUser);
|
|
4350
5588
|
this.hasChanged = true;
|
|
4351
|
-
logger.debug(`User created: ${
|
|
4352
|
-
return
|
|
5589
|
+
logger.debug(`User created: ${validatedUser.username}`);
|
|
5590
|
+
return validatedUser;
|
|
4353
5591
|
}
|
|
4354
5592
|
/**
|
|
4355
5593
|
* Read a user by username
|
|
@@ -4359,6 +5597,22 @@ var UserManager = class {
|
|
|
4359
5597
|
getUser(username) {
|
|
4360
5598
|
return this.users.find((u) => u.username === username);
|
|
4361
5599
|
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Read a user by email
|
|
5602
|
+
* @param email - Email to retrieve
|
|
5603
|
+
* @returns The user if found, undefined otherwise
|
|
5604
|
+
*/
|
|
5605
|
+
getUserByEmail(email) {
|
|
5606
|
+
return this.users.find((u) => u.email === email);
|
|
5607
|
+
}
|
|
5608
|
+
/**
|
|
5609
|
+
* Find user by username or email
|
|
5610
|
+
* @param identifier - Username or email to search for
|
|
5611
|
+
* @returns The user if found, undefined otherwise
|
|
5612
|
+
*/
|
|
5613
|
+
getUserByUsernameOrEmail(identifier) {
|
|
5614
|
+
return this.users.find((u) => u.username === identifier || u.email === identifier);
|
|
5615
|
+
}
|
|
4362
5616
|
/**
|
|
4363
5617
|
* Read all users
|
|
4364
5618
|
* @returns Array of all users
|
|
@@ -4447,7 +5701,6 @@ var UserManager = class {
|
|
|
4447
5701
|
}
|
|
4448
5702
|
if (!user.wsIds.includes(wsId)) {
|
|
4449
5703
|
user.wsIds.push(wsId);
|
|
4450
|
-
this.hasChanged = true;
|
|
4451
5704
|
logger.debug(`WebSocket ID added to user ${username}: ${wsId}`);
|
|
4452
5705
|
}
|
|
4453
5706
|
return true;
|
|
@@ -4469,7 +5722,6 @@ var UserManager = class {
|
|
|
4469
5722
|
const initialLength = user.wsIds.length;
|
|
4470
5723
|
user.wsIds = user.wsIds.filter((id) => id !== wsId);
|
|
4471
5724
|
if (user.wsIds.length < initialLength) {
|
|
4472
|
-
this.hasChanged = true;
|
|
4473
5725
|
logger.debug(`WebSocket ID removed from user ${username}: ${wsId}`);
|
|
4474
5726
|
}
|
|
4475
5727
|
return true;
|
|
@@ -4494,7 +5746,7 @@ var UserManager = class {
|
|
|
4494
5746
|
};
|
|
4495
5747
|
|
|
4496
5748
|
// src/dashboards/dashboard-manager.ts
|
|
4497
|
-
import
|
|
5749
|
+
import fs6 from "fs";
|
|
4498
5750
|
import path5 from "path";
|
|
4499
5751
|
import os2 from "os";
|
|
4500
5752
|
var DashboardManager = class {
|
|
@@ -4529,12 +5781,12 @@ var DashboardManager = class {
|
|
|
4529
5781
|
createDashboard(dashboardId, dashboard) {
|
|
4530
5782
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4531
5783
|
const dashboardDir = path5.dirname(dashboardPath);
|
|
4532
|
-
if (
|
|
5784
|
+
if (fs6.existsSync(dashboardPath)) {
|
|
4533
5785
|
throw new Error(`Dashboard '${dashboardId}' already exists`);
|
|
4534
5786
|
}
|
|
4535
5787
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
4536
|
-
|
|
4537
|
-
|
|
5788
|
+
fs6.mkdirSync(dashboardDir, { recursive: true });
|
|
5789
|
+
fs6.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
|
|
4538
5790
|
logger.info(`Dashboard created: ${dashboardId}`);
|
|
4539
5791
|
return validated;
|
|
4540
5792
|
}
|
|
@@ -4545,12 +5797,12 @@ var DashboardManager = class {
|
|
|
4545
5797
|
*/
|
|
4546
5798
|
getDashboard(dashboardId) {
|
|
4547
5799
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4548
|
-
if (!
|
|
5800
|
+
if (!fs6.existsSync(dashboardPath)) {
|
|
4549
5801
|
logger.warn(`Dashboard not found: ${dashboardId}`);
|
|
4550
5802
|
return null;
|
|
4551
5803
|
}
|
|
4552
5804
|
try {
|
|
4553
|
-
const fileContent =
|
|
5805
|
+
const fileContent = fs6.readFileSync(dashboardPath, "utf-8");
|
|
4554
5806
|
const dashboard = JSON.parse(fileContent);
|
|
4555
5807
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
4556
5808
|
return validated;
|
|
@@ -4564,16 +5816,16 @@ var DashboardManager = class {
|
|
|
4564
5816
|
* @returns Array of dashboard objects with their IDs
|
|
4565
5817
|
*/
|
|
4566
5818
|
getAllDashboards() {
|
|
4567
|
-
if (!
|
|
4568
|
-
|
|
5819
|
+
if (!fs6.existsSync(this.dashboardsBasePath)) {
|
|
5820
|
+
fs6.mkdirSync(this.dashboardsBasePath, { recursive: true });
|
|
4569
5821
|
return [];
|
|
4570
5822
|
}
|
|
4571
5823
|
const dashboards = [];
|
|
4572
5824
|
try {
|
|
4573
|
-
const dashboardDirs =
|
|
5825
|
+
const dashboardDirs = fs6.readdirSync(this.dashboardsBasePath);
|
|
4574
5826
|
for (const dashboardId of dashboardDirs) {
|
|
4575
5827
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4576
|
-
if (
|
|
5828
|
+
if (fs6.existsSync(dashboardPath)) {
|
|
4577
5829
|
const dashboard = this.getDashboard(dashboardId);
|
|
4578
5830
|
if (dashboard) {
|
|
4579
5831
|
dashboards.push({ dashboardId, dashboard });
|
|
@@ -4595,13 +5847,13 @@ var DashboardManager = class {
|
|
|
4595
5847
|
*/
|
|
4596
5848
|
updateDashboard(dashboardId, dashboard) {
|
|
4597
5849
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4598
|
-
if (!
|
|
5850
|
+
if (!fs6.existsSync(dashboardPath)) {
|
|
4599
5851
|
logger.warn(`Dashboard not found for update: ${dashboardId}`);
|
|
4600
5852
|
return null;
|
|
4601
5853
|
}
|
|
4602
5854
|
try {
|
|
4603
5855
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
4604
|
-
|
|
5856
|
+
fs6.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
|
|
4605
5857
|
logger.info(`Dashboard updated: ${dashboardId}`);
|
|
4606
5858
|
return validated;
|
|
4607
5859
|
} catch (error) {
|
|
@@ -4617,12 +5869,12 @@ var DashboardManager = class {
|
|
|
4617
5869
|
deleteDashboard(dashboardId) {
|
|
4618
5870
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4619
5871
|
const dashboardDir = path5.dirname(dashboardPath);
|
|
4620
|
-
if (!
|
|
5872
|
+
if (!fs6.existsSync(dashboardPath)) {
|
|
4621
5873
|
logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
|
|
4622
5874
|
return false;
|
|
4623
5875
|
}
|
|
4624
5876
|
try {
|
|
4625
|
-
|
|
5877
|
+
fs6.rmSync(dashboardDir, { recursive: true, force: true });
|
|
4626
5878
|
logger.info(`Dashboard deleted: ${dashboardId}`);
|
|
4627
5879
|
return true;
|
|
4628
5880
|
} catch (error) {
|
|
@@ -4637,21 +5889,21 @@ var DashboardManager = class {
|
|
|
4637
5889
|
*/
|
|
4638
5890
|
dashboardExists(dashboardId) {
|
|
4639
5891
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
4640
|
-
return
|
|
5892
|
+
return fs6.existsSync(dashboardPath);
|
|
4641
5893
|
}
|
|
4642
5894
|
/**
|
|
4643
5895
|
* Get dashboard count
|
|
4644
5896
|
* @returns Number of dashboards
|
|
4645
5897
|
*/
|
|
4646
5898
|
getDashboardCount() {
|
|
4647
|
-
if (!
|
|
5899
|
+
if (!fs6.existsSync(this.dashboardsBasePath)) {
|
|
4648
5900
|
return 0;
|
|
4649
5901
|
}
|
|
4650
5902
|
try {
|
|
4651
|
-
const dashboardDirs =
|
|
5903
|
+
const dashboardDirs = fs6.readdirSync(this.dashboardsBasePath);
|
|
4652
5904
|
return dashboardDirs.filter((dir) => {
|
|
4653
5905
|
const dashboardPath = this.getDashboardPath(dir);
|
|
4654
|
-
return
|
|
5906
|
+
return fs6.existsSync(dashboardPath);
|
|
4655
5907
|
}).length;
|
|
4656
5908
|
} catch (error) {
|
|
4657
5909
|
logger.error("Failed to get dashboard count:", error);
|
|
@@ -4661,7 +5913,7 @@ var DashboardManager = class {
|
|
|
4661
5913
|
};
|
|
4662
5914
|
|
|
4663
5915
|
// src/reports/report-manager.ts
|
|
4664
|
-
import
|
|
5916
|
+
import fs7 from "fs";
|
|
4665
5917
|
import path6 from "path";
|
|
4666
5918
|
import os3 from "os";
|
|
4667
5919
|
var ReportManager = class {
|
|
@@ -4696,12 +5948,12 @@ var ReportManager = class {
|
|
|
4696
5948
|
createReport(reportId, report) {
|
|
4697
5949
|
const reportPath = this.getReportPath(reportId);
|
|
4698
5950
|
const reportDir = path6.dirname(reportPath);
|
|
4699
|
-
if (
|
|
5951
|
+
if (fs7.existsSync(reportPath)) {
|
|
4700
5952
|
throw new Error(`Report '${reportId}' already exists`);
|
|
4701
5953
|
}
|
|
4702
5954
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
4703
|
-
|
|
4704
|
-
|
|
5955
|
+
fs7.mkdirSync(reportDir, { recursive: true });
|
|
5956
|
+
fs7.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
|
|
4705
5957
|
logger.info(`Report created: ${reportId}`);
|
|
4706
5958
|
return validated;
|
|
4707
5959
|
}
|
|
@@ -4712,12 +5964,12 @@ var ReportManager = class {
|
|
|
4712
5964
|
*/
|
|
4713
5965
|
getReport(reportId) {
|
|
4714
5966
|
const reportPath = this.getReportPath(reportId);
|
|
4715
|
-
if (!
|
|
5967
|
+
if (!fs7.existsSync(reportPath)) {
|
|
4716
5968
|
logger.warn(`Report not found: ${reportId}`);
|
|
4717
5969
|
return null;
|
|
4718
5970
|
}
|
|
4719
5971
|
try {
|
|
4720
|
-
const fileContent =
|
|
5972
|
+
const fileContent = fs7.readFileSync(reportPath, "utf-8");
|
|
4721
5973
|
const report = JSON.parse(fileContent);
|
|
4722
5974
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
4723
5975
|
return validated;
|
|
@@ -4731,16 +5983,16 @@ var ReportManager = class {
|
|
|
4731
5983
|
* @returns Array of report objects with their IDs
|
|
4732
5984
|
*/
|
|
4733
5985
|
getAllReports() {
|
|
4734
|
-
if (!
|
|
4735
|
-
|
|
5986
|
+
if (!fs7.existsSync(this.reportsBasePath)) {
|
|
5987
|
+
fs7.mkdirSync(this.reportsBasePath, { recursive: true });
|
|
4736
5988
|
return [];
|
|
4737
5989
|
}
|
|
4738
5990
|
const reports = [];
|
|
4739
5991
|
try {
|
|
4740
|
-
const reportDirs =
|
|
5992
|
+
const reportDirs = fs7.readdirSync(this.reportsBasePath);
|
|
4741
5993
|
for (const reportId of reportDirs) {
|
|
4742
5994
|
const reportPath = this.getReportPath(reportId);
|
|
4743
|
-
if (
|
|
5995
|
+
if (fs7.existsSync(reportPath)) {
|
|
4744
5996
|
const report = this.getReport(reportId);
|
|
4745
5997
|
if (report) {
|
|
4746
5998
|
reports.push({ reportId, report });
|
|
@@ -4762,13 +6014,13 @@ var ReportManager = class {
|
|
|
4762
6014
|
*/
|
|
4763
6015
|
updateReport(reportId, report) {
|
|
4764
6016
|
const reportPath = this.getReportPath(reportId);
|
|
4765
|
-
if (!
|
|
6017
|
+
if (!fs7.existsSync(reportPath)) {
|
|
4766
6018
|
logger.warn(`Report not found for update: ${reportId}`);
|
|
4767
6019
|
return null;
|
|
4768
6020
|
}
|
|
4769
6021
|
try {
|
|
4770
6022
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
4771
|
-
|
|
6023
|
+
fs7.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
|
|
4772
6024
|
logger.info(`Report updated: ${reportId}`);
|
|
4773
6025
|
return validated;
|
|
4774
6026
|
} catch (error) {
|
|
@@ -4784,12 +6036,12 @@ var ReportManager = class {
|
|
|
4784
6036
|
deleteReport(reportId) {
|
|
4785
6037
|
const reportPath = this.getReportPath(reportId);
|
|
4786
6038
|
const reportDir = path6.dirname(reportPath);
|
|
4787
|
-
if (!
|
|
6039
|
+
if (!fs7.existsSync(reportPath)) {
|
|
4788
6040
|
logger.warn(`Report not found for deletion: ${reportId}`);
|
|
4789
6041
|
return false;
|
|
4790
6042
|
}
|
|
4791
6043
|
try {
|
|
4792
|
-
|
|
6044
|
+
fs7.rmSync(reportDir, { recursive: true, force: true });
|
|
4793
6045
|
logger.info(`Report deleted: ${reportId}`);
|
|
4794
6046
|
return true;
|
|
4795
6047
|
} catch (error) {
|
|
@@ -4804,21 +6056,21 @@ var ReportManager = class {
|
|
|
4804
6056
|
*/
|
|
4805
6057
|
reportExists(reportId) {
|
|
4806
6058
|
const reportPath = this.getReportPath(reportId);
|
|
4807
|
-
return
|
|
6059
|
+
return fs7.existsSync(reportPath);
|
|
4808
6060
|
}
|
|
4809
6061
|
/**
|
|
4810
6062
|
* Get report count
|
|
4811
6063
|
* @returns Number of reports
|
|
4812
6064
|
*/
|
|
4813
6065
|
getReportCount() {
|
|
4814
|
-
if (!
|
|
6066
|
+
if (!fs7.existsSync(this.reportsBasePath)) {
|
|
4815
6067
|
return 0;
|
|
4816
6068
|
}
|
|
4817
6069
|
try {
|
|
4818
|
-
const reportDirs =
|
|
6070
|
+
const reportDirs = fs7.readdirSync(this.reportsBasePath);
|
|
4819
6071
|
return reportDirs.filter((dir) => {
|
|
4820
6072
|
const reportPath = this.getReportPath(dir);
|
|
4821
|
-
return
|
|
6073
|
+
return fs7.existsSync(reportPath);
|
|
4822
6074
|
}).length;
|
|
4823
6075
|
} catch (error) {
|
|
4824
6076
|
logger.error("Failed to get report count:", error);
|
|
@@ -5019,6 +6271,9 @@ var SuperatomSDK = class {
|
|
|
5019
6271
|
this.maxReconnectAttempts = 5;
|
|
5020
6272
|
this.collections = {};
|
|
5021
6273
|
this.components = [];
|
|
6274
|
+
if (config.logLevel) {
|
|
6275
|
+
logger.setLogLevel(config.logLevel);
|
|
6276
|
+
}
|
|
5022
6277
|
this.apiKey = config.apiKey;
|
|
5023
6278
|
this.projectId = config.projectId;
|
|
5024
6279
|
this.userId = config.userId || "anonymous";
|
|
@@ -5039,9 +6294,6 @@ var SuperatomSDK = class {
|
|
|
5039
6294
|
});
|
|
5040
6295
|
this.initializeDashboardManager();
|
|
5041
6296
|
this.initializeReportManager();
|
|
5042
|
-
this.connect().catch((error) => {
|
|
5043
|
-
logger.error("Failed to connect to Superatom:", error);
|
|
5044
|
-
});
|
|
5045
6297
|
}
|
|
5046
6298
|
/**
|
|
5047
6299
|
* Initialize PromptLoader and load prompts into memory
|
|
@@ -5107,6 +6359,10 @@ var SuperatomSDK = class {
|
|
|
5107
6359
|
* Connect to the Superatom WebSocket service
|
|
5108
6360
|
*/
|
|
5109
6361
|
async connect() {
|
|
6362
|
+
if (this.connected && this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
6363
|
+
logger.info("Already connected to WebSocket");
|
|
6364
|
+
return Promise.resolve();
|
|
6365
|
+
}
|
|
5110
6366
|
return new Promise((resolve, reject) => {
|
|
5111
6367
|
try {
|
|
5112
6368
|
const url = new URL(this.url);
|
|
@@ -5169,7 +6425,7 @@ var SuperatomSDK = class {
|
|
|
5169
6425
|
});
|
|
5170
6426
|
break;
|
|
5171
6427
|
case "USER_PROMPT_REQ":
|
|
5172
|
-
handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders).catch((error) => {
|
|
6428
|
+
handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections).catch((error) => {
|
|
5173
6429
|
logger.error("Failed to handle user prompt request:", error);
|
|
5174
6430
|
});
|
|
5175
6431
|
break;
|
|
@@ -5321,6 +6577,7 @@ export {
|
|
|
5321
6577
|
ThreadManager,
|
|
5322
6578
|
UIBlock,
|
|
5323
6579
|
UILogCollector,
|
|
5324
|
-
UserManager
|
|
6580
|
+
UserManager,
|
|
6581
|
+
logger
|
|
5325
6582
|
};
|
|
5326
6583
|
//# sourceMappingURL=index.mjs.map
|