@retrivora-ai/rag-engine 2.0.8 → 2.1.1
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/dist/handlers/index.js +73 -37
- package/dist/handlers/index.mjs +73 -37
- package/dist/index.js +19 -12
- package/dist/index.mjs +19 -12
- package/dist/server.js +73 -37
- package/dist/server.mjs +73 -37
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +22 -22
- package/src/core/Pipeline.ts +3 -3
- package/src/llm/providers/GeminiProvider.ts +4 -4
- package/src/llm/providers/GroqProvider.ts +16 -8
- package/src/llm/providers/OpenAIProvider.ts +16 -8
- package/src/llm/providers/QwenProvider.ts +16 -8
- package/src/llm/providers/UniversalLLMAdapter.ts +16 -2
- package/src/rendering/IntentClassifier.ts +3 -3
- package/src/rendering/rules/Rule1SpecificInfoRule.ts +1 -1
- package/src/utils/ProductExtractor.ts +4 -3
- package/src/utils/UITransformer.ts +12 -0
package/dist/handlers/index.js
CHANGED
|
@@ -594,6 +594,7 @@ function cleanApiKeyOverride(apiKeyOverride) {
|
|
|
594
594
|
async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
595
595
|
const effectiveKey = cleanApiKeyOverride(apiKeyOverride);
|
|
596
596
|
const provider = resolveProvider(req.model);
|
|
597
|
+
console.log(`[LLM Gateway Router] Dispatching chat request: model=${req.model}, provider=${provider}, hasGroqKey=${Boolean(process.env.GROQ_API_KEY)}, hasGeminiKey=${Boolean(process.env.GROQ_API_KEY || process.env.GEMINI_API_KEY)}, hasHFToken=${Boolean(process.env.HF_TOKEN)}`);
|
|
597
598
|
try {
|
|
598
599
|
switch (provider) {
|
|
599
600
|
case "groq":
|
|
@@ -612,6 +613,10 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
612
613
|
throw new Error(`Unsupported LLM provider for model: ${req.model}`);
|
|
613
614
|
}
|
|
614
615
|
} catch (error) {
|
|
616
|
+
console.error(`[LLM Gateway Router] Provider '${provider}' failed for model '${req.model}':`, {
|
|
617
|
+
message: error.message,
|
|
618
|
+
stack: error.stack
|
|
619
|
+
});
|
|
615
620
|
if (/rate[- ]?limit|429|exhausted/i.test(error.message)) {
|
|
616
621
|
const isGroqKey = process.env.GROQ_API_KEY || effectiveKey && effectiveKey.startsWith("gsk_");
|
|
617
622
|
const reqModelLower = req.model.toLowerCase();
|
|
@@ -619,7 +624,8 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
619
624
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to secondary Groq model (llama-3.3-70b-versatile)...`);
|
|
620
625
|
try {
|
|
621
626
|
return await handleGroqRequest(__spreadProps(__spreadValues({}, req), { model: "llama-3.3-70b-versatile" }), effectiveKey);
|
|
622
|
-
} catch (
|
|
627
|
+
} catch (fErr) {
|
|
628
|
+
console.error("[LLM Gateway] Fallback groq/llama-3.3-70b-versatile failed:", fErr.message);
|
|
623
629
|
}
|
|
624
630
|
}
|
|
625
631
|
const geminiKey = process.env.GEMINI_API_KEY || effectiveKey;
|
|
@@ -627,14 +633,16 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
627
633
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Google Gemini (gemini-2.5-flash)...`);
|
|
628
634
|
try {
|
|
629
635
|
return await handleGeminiRequest(__spreadProps(__spreadValues({}, req), { model: "gemini-2.5-flash" }), effectiveKey);
|
|
630
|
-
} catch (
|
|
636
|
+
} catch (fErr) {
|
|
637
|
+
console.error("[LLM Gateway] Fallback gemini-2.5-flash failed:", fErr.message);
|
|
631
638
|
}
|
|
632
639
|
}
|
|
633
640
|
if (provider !== "huggingface" && (process.env.HF_TOKEN || process.env.HUGGINGFACE_API_KEY)) {
|
|
634
641
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Hugging Face (Qwen/Qwen2.5-Coder-32B-Instruct)...`);
|
|
635
642
|
try {
|
|
636
643
|
return await handleHuggingFaceChatRequest(__spreadProps(__spreadValues({}, req), { model: "Qwen/Qwen2.5-Coder-32B-Instruct" }), effectiveKey);
|
|
637
|
-
} catch (
|
|
644
|
+
} catch (fErr) {
|
|
645
|
+
console.error("[LLM Gateway] Fallback HuggingFace Qwen failed:", fErr.message);
|
|
638
646
|
}
|
|
639
647
|
}
|
|
640
648
|
if (provider !== "openai" && process.env.OPENAI_API_KEY) {
|
|
@@ -3428,12 +3436,13 @@ var OpenAIProvider = class {
|
|
|
3428
3436
|
role: "system",
|
|
3429
3437
|
content: buildSystemContent(basePrompt, context)
|
|
3430
3438
|
};
|
|
3439
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3440
|
+
role: m.role || "user",
|
|
3441
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3442
|
+
}));
|
|
3431
3443
|
const formattedMessages = [
|
|
3432
3444
|
systemMessage,
|
|
3433
|
-
...
|
|
3434
|
-
role: m.role,
|
|
3435
|
-
content: m.content
|
|
3436
|
-
}))
|
|
3445
|
+
...safeMessages
|
|
3437
3446
|
];
|
|
3438
3447
|
const completion = await this.client.chat.completions.create({
|
|
3439
3448
|
model: this.llmConfig.model,
|
|
@@ -3452,12 +3461,13 @@ var OpenAIProvider = class {
|
|
|
3452
3461
|
role: "system",
|
|
3453
3462
|
content: buildSystemContent(basePrompt, context)
|
|
3454
3463
|
};
|
|
3464
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3465
|
+
role: m.role || "user",
|
|
3466
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3467
|
+
}));
|
|
3455
3468
|
const formattedMessages = [
|
|
3456
3469
|
systemMessage,
|
|
3457
|
-
...
|
|
3458
|
-
role: m.role,
|
|
3459
|
-
content: m.content
|
|
3460
|
-
}))
|
|
3470
|
+
...safeStreamMessages
|
|
3461
3471
|
];
|
|
3462
3472
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
3463
3473
|
model: this.llmConfig.model,
|
|
@@ -3988,9 +3998,9 @@ var GeminiProvider = class {
|
|
|
3988
3998
|
* - Messages must strictly alternate between `user` and `model`.
|
|
3989
3999
|
*/
|
|
3990
4000
|
buildGeminiContents(messages) {
|
|
3991
|
-
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
4001
|
+
return (messages || []).filter((m) => Boolean(m && typeof m === "object" && m.role !== "system")).map((m) => ({
|
|
3992
4002
|
role: m.role === "assistant" ? "model" : "user",
|
|
3993
|
-
parts: [{ text: m.content }]
|
|
4003
|
+
parts: [{ text: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : "" }]
|
|
3994
4004
|
}));
|
|
3995
4005
|
}
|
|
3996
4006
|
// -------------------------------------------------------------------------
|
|
@@ -4193,12 +4203,13 @@ var GroqProvider = class {
|
|
|
4193
4203
|
role: "system",
|
|
4194
4204
|
content: buildSystemContent(basePrompt, context)
|
|
4195
4205
|
};
|
|
4206
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4207
|
+
role: m.role || "user",
|
|
4208
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4209
|
+
}));
|
|
4196
4210
|
const formattedMessages = [
|
|
4197
4211
|
systemMessage,
|
|
4198
|
-
...
|
|
4199
|
-
role: m.role,
|
|
4200
|
-
content: m.content
|
|
4201
|
-
}))
|
|
4212
|
+
...safeMessages
|
|
4202
4213
|
];
|
|
4203
4214
|
const completion = await this.client.chat.completions.create({
|
|
4204
4215
|
model: this.llmConfig.model,
|
|
@@ -4217,12 +4228,13 @@ var GroqProvider = class {
|
|
|
4217
4228
|
role: "system",
|
|
4218
4229
|
content: buildSystemContent(basePrompt, context)
|
|
4219
4230
|
};
|
|
4231
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4232
|
+
role: m.role || "user",
|
|
4233
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4234
|
+
}));
|
|
4220
4235
|
const formattedMessages = [
|
|
4221
4236
|
systemMessage,
|
|
4222
|
-
...
|
|
4223
|
-
role: m.role,
|
|
4224
|
-
content: m.content
|
|
4225
|
-
}))
|
|
4237
|
+
...safeStreamMessages
|
|
4226
4238
|
];
|
|
4227
4239
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4228
4240
|
model: this.llmConfig.model,
|
|
@@ -4351,12 +4363,13 @@ var QwenProvider = class {
|
|
|
4351
4363
|
role: "system",
|
|
4352
4364
|
content: buildSystemContent(basePrompt, context)
|
|
4353
4365
|
};
|
|
4366
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4367
|
+
role: m.role || "user",
|
|
4368
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4369
|
+
}));
|
|
4354
4370
|
const formattedMessages = [
|
|
4355
4371
|
systemMessage,
|
|
4356
|
-
...
|
|
4357
|
-
role: m.role,
|
|
4358
|
-
content: m.content
|
|
4359
|
-
}))
|
|
4372
|
+
...safeMessages
|
|
4360
4373
|
];
|
|
4361
4374
|
const completion = await this.client.chat.completions.create({
|
|
4362
4375
|
model: this.llmConfig.model,
|
|
@@ -4375,12 +4388,13 @@ var QwenProvider = class {
|
|
|
4375
4388
|
role: "system",
|
|
4376
4389
|
content: buildSystemContent(basePrompt, context)
|
|
4377
4390
|
};
|
|
4391
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4392
|
+
role: m.role || "user",
|
|
4393
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4394
|
+
}));
|
|
4378
4395
|
const formattedMessages = [
|
|
4379
4396
|
systemMessage,
|
|
4380
|
-
...
|
|
4381
|
-
role: m.role,
|
|
4382
|
-
content: m.content
|
|
4383
|
-
}))
|
|
4397
|
+
...safeStreamMessages
|
|
4384
4398
|
];
|
|
4385
4399
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4386
4400
|
model: this.llmConfig.model,
|
|
@@ -4523,12 +4537,16 @@ var UniversalLLMAdapter = class {
|
|
|
4523
4537
|
async chat(messages, context) {
|
|
4524
4538
|
var _a2, _b, _c;
|
|
4525
4539
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4540
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4541
|
+
role: m.role || "user",
|
|
4542
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4543
|
+
}));
|
|
4526
4544
|
const formattedMessages = [
|
|
4527
4545
|
{ role: "system", content: `${this.systemPrompt}
|
|
4528
4546
|
|
|
4529
4547
|
Context:
|
|
4530
4548
|
${context != null ? context : "None"}` },
|
|
4531
|
-
...
|
|
4549
|
+
...safeMessages
|
|
4532
4550
|
];
|
|
4533
4551
|
let payload;
|
|
4534
4552
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -4600,12 +4618,16 @@ ${context != null ? context : "None"}` },
|
|
|
4600
4618
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4601
4619
|
const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
|
|
4602
4620
|
const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
|
|
4621
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4622
|
+
role: m.role || "user",
|
|
4623
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4624
|
+
}));
|
|
4603
4625
|
const formattedMessages = [
|
|
4604
4626
|
{ role: "system", content: `${this.systemPrompt}
|
|
4605
4627
|
|
|
4606
4628
|
Context:
|
|
4607
4629
|
${context != null ? context : "None"}` },
|
|
4608
|
-
...
|
|
4630
|
+
...safeMessages
|
|
4609
4631
|
];
|
|
4610
4632
|
let payload;
|
|
4611
4633
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -6743,8 +6765,8 @@ var IntentClassifier = class {
|
|
|
6743
6765
|
}
|
|
6744
6766
|
// ─── Intent Heuristic Checkers ─────────────────────────────────────────────
|
|
6745
6767
|
static isInformationLookup(query) {
|
|
6746
|
-
if (/^(what|who|explain|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of)\s+/i.test(query)) {
|
|
6747
|
-
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
|
|
6768
|
+
if (/^(what|who|explain|explore|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(query)) {
|
|
6769
|
+
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
|
|
6748
6770
|
return true;
|
|
6749
6771
|
}
|
|
6750
6772
|
}
|
|
@@ -6781,7 +6803,7 @@ var Rule1SpecificInfoRule = class {
|
|
|
6781
6803
|
}
|
|
6782
6804
|
evaluate(context, intent) {
|
|
6783
6805
|
const q = (context.userQuery || "").toLowerCase().trim();
|
|
6784
|
-
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|define|where|when|why|meaning of)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6806
|
+
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|explore|define|where|when|why|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6785
6807
|
const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
|
|
6786
6808
|
if (isFactQuery && isNonVisual) {
|
|
6787
6809
|
return {
|
|
@@ -8060,6 +8082,11 @@ ${schemaProfileText}` : ""}`;
|
|
|
8060
8082
|
}
|
|
8061
8083
|
static chooseAutomaticVisualization(data, profile, query) {
|
|
8062
8084
|
if (profile.records.length === 0) return null;
|
|
8085
|
+
const q = query.toLowerCase().trim();
|
|
8086
|
+
const isExplicitVisualOrAnalytic = /\b(chart|graph|plot|visualize|trend|monthly|revenue|top\s*\d+|breakdown|analytics|compare|versus|vs\.?|histogram|pie|bar|line|distribution|percentage|share|summary of metrics)\b/i.test(q);
|
|
8087
|
+
if (!isExplicitVisualOrAnalytic) {
|
|
8088
|
+
return null;
|
|
8089
|
+
}
|
|
8063
8090
|
if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
|
|
8064
8091
|
return this.transformToLineChart(profile);
|
|
8065
8092
|
}
|
|
@@ -8175,6 +8202,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8175
8202
|
return Array.from(categories);
|
|
8176
8203
|
}
|
|
8177
8204
|
static getProductCategory(item) {
|
|
8205
|
+
if (!item) return null;
|
|
8178
8206
|
const meta = item.metadata || {};
|
|
8179
8207
|
const metadataCategory = resolveMetadataValue(meta, "category");
|
|
8180
8208
|
if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
|
|
@@ -8291,6 +8319,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8291
8319
|
if (!fields.has(normalized)) fields.set(normalized, clean);
|
|
8292
8320
|
};
|
|
8293
8321
|
data.forEach((item) => {
|
|
8322
|
+
if (!item) return;
|
|
8294
8323
|
Object.keys(item.metadata || {}).forEach(addField);
|
|
8295
8324
|
for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
|
|
8296
8325
|
addField(match[1]);
|
|
@@ -8407,6 +8436,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8407
8436
|
return true;
|
|
8408
8437
|
}
|
|
8409
8438
|
static extractStockQuantity(item) {
|
|
8439
|
+
if (!item) return null;
|
|
8410
8440
|
const meta = item.metadata || {};
|
|
8411
8441
|
const stockValue = resolveMetadataValue(meta, "stock");
|
|
8412
8442
|
const numericStock = this.toFiniteNumber(stockValue);
|
|
@@ -9667,7 +9697,10 @@ ${context}`;
|
|
|
9667
9697
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
9668
9698
|
|
|
9669
9699
|
History:
|
|
9670
|
-
${history.map((m) =>
|
|
9700
|
+
${(history || []).map((m) => {
|
|
9701
|
+
var _a2;
|
|
9702
|
+
return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
|
|
9703
|
+
}).join("\n")}
|
|
9671
9704
|
|
|
9672
9705
|
New Question: ${question}
|
|
9673
9706
|
|
|
@@ -9691,8 +9724,11 @@ Optimized Search Query:`;
|
|
|
9691
9724
|
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
9692
9725
|
try {
|
|
9693
9726
|
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
9694
|
-
if (sources.length === 0) return [];
|
|
9695
|
-
const context = sources.map((s) =>
|
|
9727
|
+
if (!sources || sources.length === 0) return [];
|
|
9728
|
+
const context = sources.map((s) => {
|
|
9729
|
+
var _a2;
|
|
9730
|
+
return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
|
|
9731
|
+
}).join("\n\n---\n\n");
|
|
9696
9732
|
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
9697
9733
|
Focus on questions that can be answered by the context.
|
|
9698
9734
|
Keep each question under 10 words and make them very specific to the content.
|
package/dist/handlers/index.mjs
CHANGED
|
@@ -579,6 +579,7 @@ function cleanApiKeyOverride(apiKeyOverride) {
|
|
|
579
579
|
async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
580
580
|
const effectiveKey = cleanApiKeyOverride(apiKeyOverride);
|
|
581
581
|
const provider = resolveProvider(req.model);
|
|
582
|
+
console.log(`[LLM Gateway Router] Dispatching chat request: model=${req.model}, provider=${provider}, hasGroqKey=${Boolean(process.env.GROQ_API_KEY)}, hasGeminiKey=${Boolean(process.env.GROQ_API_KEY || process.env.GEMINI_API_KEY)}, hasHFToken=${Boolean(process.env.HF_TOKEN)}`);
|
|
582
583
|
try {
|
|
583
584
|
switch (provider) {
|
|
584
585
|
case "groq":
|
|
@@ -597,6 +598,10 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
597
598
|
throw new Error(`Unsupported LLM provider for model: ${req.model}`);
|
|
598
599
|
}
|
|
599
600
|
} catch (error) {
|
|
601
|
+
console.error(`[LLM Gateway Router] Provider '${provider}' failed for model '${req.model}':`, {
|
|
602
|
+
message: error.message,
|
|
603
|
+
stack: error.stack
|
|
604
|
+
});
|
|
600
605
|
if (/rate[- ]?limit|429|exhausted/i.test(error.message)) {
|
|
601
606
|
const isGroqKey = process.env.GROQ_API_KEY || effectiveKey && effectiveKey.startsWith("gsk_");
|
|
602
607
|
const reqModelLower = req.model.toLowerCase();
|
|
@@ -604,7 +609,8 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
604
609
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to secondary Groq model (llama-3.3-70b-versatile)...`);
|
|
605
610
|
try {
|
|
606
611
|
return await handleGroqRequest(__spreadProps(__spreadValues({}, req), { model: "llama-3.3-70b-versatile" }), effectiveKey);
|
|
607
|
-
} catch (
|
|
612
|
+
} catch (fErr) {
|
|
613
|
+
console.error("[LLM Gateway] Fallback groq/llama-3.3-70b-versatile failed:", fErr.message);
|
|
608
614
|
}
|
|
609
615
|
}
|
|
610
616
|
const geminiKey = process.env.GEMINI_API_KEY || effectiveKey;
|
|
@@ -612,14 +618,16 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
612
618
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Google Gemini (gemini-2.5-flash)...`);
|
|
613
619
|
try {
|
|
614
620
|
return await handleGeminiRequest(__spreadProps(__spreadValues({}, req), { model: "gemini-2.5-flash" }), effectiveKey);
|
|
615
|
-
} catch (
|
|
621
|
+
} catch (fErr) {
|
|
622
|
+
console.error("[LLM Gateway] Fallback gemini-2.5-flash failed:", fErr.message);
|
|
616
623
|
}
|
|
617
624
|
}
|
|
618
625
|
if (provider !== "huggingface" && (process.env.HF_TOKEN || process.env.HUGGINGFACE_API_KEY)) {
|
|
619
626
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Hugging Face (Qwen/Qwen2.5-Coder-32B-Instruct)...`);
|
|
620
627
|
try {
|
|
621
628
|
return await handleHuggingFaceChatRequest(__spreadProps(__spreadValues({}, req), { model: "Qwen/Qwen2.5-Coder-32B-Instruct" }), effectiveKey);
|
|
622
|
-
} catch (
|
|
629
|
+
} catch (fErr) {
|
|
630
|
+
console.error("[LLM Gateway] Fallback HuggingFace Qwen failed:", fErr.message);
|
|
623
631
|
}
|
|
624
632
|
}
|
|
625
633
|
if (provider !== "openai" && process.env.OPENAI_API_KEY) {
|
|
@@ -3393,12 +3401,13 @@ var OpenAIProvider = class {
|
|
|
3393
3401
|
role: "system",
|
|
3394
3402
|
content: buildSystemContent(basePrompt, context)
|
|
3395
3403
|
};
|
|
3404
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3405
|
+
role: m.role || "user",
|
|
3406
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3407
|
+
}));
|
|
3396
3408
|
const formattedMessages = [
|
|
3397
3409
|
systemMessage,
|
|
3398
|
-
...
|
|
3399
|
-
role: m.role,
|
|
3400
|
-
content: m.content
|
|
3401
|
-
}))
|
|
3410
|
+
...safeMessages
|
|
3402
3411
|
];
|
|
3403
3412
|
const completion = await this.client.chat.completions.create({
|
|
3404
3413
|
model: this.llmConfig.model,
|
|
@@ -3417,12 +3426,13 @@ var OpenAIProvider = class {
|
|
|
3417
3426
|
role: "system",
|
|
3418
3427
|
content: buildSystemContent(basePrompt, context)
|
|
3419
3428
|
};
|
|
3429
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3430
|
+
role: m.role || "user",
|
|
3431
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3432
|
+
}));
|
|
3420
3433
|
const formattedMessages = [
|
|
3421
3434
|
systemMessage,
|
|
3422
|
-
...
|
|
3423
|
-
role: m.role,
|
|
3424
|
-
content: m.content
|
|
3425
|
-
}))
|
|
3435
|
+
...safeStreamMessages
|
|
3426
3436
|
];
|
|
3427
3437
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
3428
3438
|
model: this.llmConfig.model,
|
|
@@ -3953,9 +3963,9 @@ var GeminiProvider = class {
|
|
|
3953
3963
|
* - Messages must strictly alternate between `user` and `model`.
|
|
3954
3964
|
*/
|
|
3955
3965
|
buildGeminiContents(messages) {
|
|
3956
|
-
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
3966
|
+
return (messages || []).filter((m) => Boolean(m && typeof m === "object" && m.role !== "system")).map((m) => ({
|
|
3957
3967
|
role: m.role === "assistant" ? "model" : "user",
|
|
3958
|
-
parts: [{ text: m.content }]
|
|
3968
|
+
parts: [{ text: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : "" }]
|
|
3959
3969
|
}));
|
|
3960
3970
|
}
|
|
3961
3971
|
// -------------------------------------------------------------------------
|
|
@@ -4158,12 +4168,13 @@ var GroqProvider = class {
|
|
|
4158
4168
|
role: "system",
|
|
4159
4169
|
content: buildSystemContent(basePrompt, context)
|
|
4160
4170
|
};
|
|
4171
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4172
|
+
role: m.role || "user",
|
|
4173
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4174
|
+
}));
|
|
4161
4175
|
const formattedMessages = [
|
|
4162
4176
|
systemMessage,
|
|
4163
|
-
...
|
|
4164
|
-
role: m.role,
|
|
4165
|
-
content: m.content
|
|
4166
|
-
}))
|
|
4177
|
+
...safeMessages
|
|
4167
4178
|
];
|
|
4168
4179
|
const completion = await this.client.chat.completions.create({
|
|
4169
4180
|
model: this.llmConfig.model,
|
|
@@ -4182,12 +4193,13 @@ var GroqProvider = class {
|
|
|
4182
4193
|
role: "system",
|
|
4183
4194
|
content: buildSystemContent(basePrompt, context)
|
|
4184
4195
|
};
|
|
4196
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4197
|
+
role: m.role || "user",
|
|
4198
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4199
|
+
}));
|
|
4185
4200
|
const formattedMessages = [
|
|
4186
4201
|
systemMessage,
|
|
4187
|
-
...
|
|
4188
|
-
role: m.role,
|
|
4189
|
-
content: m.content
|
|
4190
|
-
}))
|
|
4202
|
+
...safeStreamMessages
|
|
4191
4203
|
];
|
|
4192
4204
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4193
4205
|
model: this.llmConfig.model,
|
|
@@ -4316,12 +4328,13 @@ var QwenProvider = class {
|
|
|
4316
4328
|
role: "system",
|
|
4317
4329
|
content: buildSystemContent(basePrompt, context)
|
|
4318
4330
|
};
|
|
4331
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4332
|
+
role: m.role || "user",
|
|
4333
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4334
|
+
}));
|
|
4319
4335
|
const formattedMessages = [
|
|
4320
4336
|
systemMessage,
|
|
4321
|
-
...
|
|
4322
|
-
role: m.role,
|
|
4323
|
-
content: m.content
|
|
4324
|
-
}))
|
|
4337
|
+
...safeMessages
|
|
4325
4338
|
];
|
|
4326
4339
|
const completion = await this.client.chat.completions.create({
|
|
4327
4340
|
model: this.llmConfig.model,
|
|
@@ -4340,12 +4353,13 @@ var QwenProvider = class {
|
|
|
4340
4353
|
role: "system",
|
|
4341
4354
|
content: buildSystemContent(basePrompt, context)
|
|
4342
4355
|
};
|
|
4356
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4357
|
+
role: m.role || "user",
|
|
4358
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4359
|
+
}));
|
|
4343
4360
|
const formattedMessages = [
|
|
4344
4361
|
systemMessage,
|
|
4345
|
-
...
|
|
4346
|
-
role: m.role,
|
|
4347
|
-
content: m.content
|
|
4348
|
-
}))
|
|
4362
|
+
...safeStreamMessages
|
|
4349
4363
|
];
|
|
4350
4364
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4351
4365
|
model: this.llmConfig.model,
|
|
@@ -4488,12 +4502,16 @@ var UniversalLLMAdapter = class {
|
|
|
4488
4502
|
async chat(messages, context) {
|
|
4489
4503
|
var _a2, _b, _c;
|
|
4490
4504
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4505
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4506
|
+
role: m.role || "user",
|
|
4507
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4508
|
+
}));
|
|
4491
4509
|
const formattedMessages = [
|
|
4492
4510
|
{ role: "system", content: `${this.systemPrompt}
|
|
4493
4511
|
|
|
4494
4512
|
Context:
|
|
4495
4513
|
${context != null ? context : "None"}` },
|
|
4496
|
-
...
|
|
4514
|
+
...safeMessages
|
|
4497
4515
|
];
|
|
4498
4516
|
let payload;
|
|
4499
4517
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -4565,12 +4583,16 @@ ${context != null ? context : "None"}` },
|
|
|
4565
4583
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4566
4584
|
const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
|
|
4567
4585
|
const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
|
|
4586
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4587
|
+
role: m.role || "user",
|
|
4588
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4589
|
+
}));
|
|
4568
4590
|
const formattedMessages = [
|
|
4569
4591
|
{ role: "system", content: `${this.systemPrompt}
|
|
4570
4592
|
|
|
4571
4593
|
Context:
|
|
4572
4594
|
${context != null ? context : "None"}` },
|
|
4573
|
-
...
|
|
4595
|
+
...safeMessages
|
|
4574
4596
|
];
|
|
4575
4597
|
let payload;
|
|
4576
4598
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -6708,8 +6730,8 @@ var IntentClassifier = class {
|
|
|
6708
6730
|
}
|
|
6709
6731
|
// ─── Intent Heuristic Checkers ─────────────────────────────────────────────
|
|
6710
6732
|
static isInformationLookup(query) {
|
|
6711
|
-
if (/^(what|who|explain|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of)\s+/i.test(query)) {
|
|
6712
|
-
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
|
|
6733
|
+
if (/^(what|who|explain|explore|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(query)) {
|
|
6734
|
+
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
|
|
6713
6735
|
return true;
|
|
6714
6736
|
}
|
|
6715
6737
|
}
|
|
@@ -6746,7 +6768,7 @@ var Rule1SpecificInfoRule = class {
|
|
|
6746
6768
|
}
|
|
6747
6769
|
evaluate(context, intent) {
|
|
6748
6770
|
const q = (context.userQuery || "").toLowerCase().trim();
|
|
6749
|
-
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|define|where|when|why|meaning of)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6771
|
+
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|explore|define|where|when|why|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6750
6772
|
const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
|
|
6751
6773
|
if (isFactQuery && isNonVisual) {
|
|
6752
6774
|
return {
|
|
@@ -8025,6 +8047,11 @@ ${schemaProfileText}` : ""}`;
|
|
|
8025
8047
|
}
|
|
8026
8048
|
static chooseAutomaticVisualization(data, profile, query) {
|
|
8027
8049
|
if (profile.records.length === 0) return null;
|
|
8050
|
+
const q = query.toLowerCase().trim();
|
|
8051
|
+
const isExplicitVisualOrAnalytic = /\b(chart|graph|plot|visualize|trend|monthly|revenue|top\s*\d+|breakdown|analytics|compare|versus|vs\.?|histogram|pie|bar|line|distribution|percentage|share|summary of metrics)\b/i.test(q);
|
|
8052
|
+
if (!isExplicitVisualOrAnalytic) {
|
|
8053
|
+
return null;
|
|
8054
|
+
}
|
|
8028
8055
|
if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
|
|
8029
8056
|
return this.transformToLineChart(profile);
|
|
8030
8057
|
}
|
|
@@ -8140,6 +8167,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8140
8167
|
return Array.from(categories);
|
|
8141
8168
|
}
|
|
8142
8169
|
static getProductCategory(item) {
|
|
8170
|
+
if (!item) return null;
|
|
8143
8171
|
const meta = item.metadata || {};
|
|
8144
8172
|
const metadataCategory = resolveMetadataValue(meta, "category");
|
|
8145
8173
|
if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
|
|
@@ -8256,6 +8284,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8256
8284
|
if (!fields.has(normalized)) fields.set(normalized, clean);
|
|
8257
8285
|
};
|
|
8258
8286
|
data.forEach((item) => {
|
|
8287
|
+
if (!item) return;
|
|
8259
8288
|
Object.keys(item.metadata || {}).forEach(addField);
|
|
8260
8289
|
for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
|
|
8261
8290
|
addField(match[1]);
|
|
@@ -8372,6 +8401,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8372
8401
|
return true;
|
|
8373
8402
|
}
|
|
8374
8403
|
static extractStockQuantity(item) {
|
|
8404
|
+
if (!item) return null;
|
|
8375
8405
|
const meta = item.metadata || {};
|
|
8376
8406
|
const stockValue = resolveMetadataValue(meta, "stock");
|
|
8377
8407
|
const numericStock = this.toFiniteNumber(stockValue);
|
|
@@ -9632,7 +9662,10 @@ ${context}`;
|
|
|
9632
9662
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
9633
9663
|
|
|
9634
9664
|
History:
|
|
9635
|
-
${history.map((m) =>
|
|
9665
|
+
${(history || []).map((m) => {
|
|
9666
|
+
var _a2;
|
|
9667
|
+
return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
|
|
9668
|
+
}).join("\n")}
|
|
9636
9669
|
|
|
9637
9670
|
New Question: ${question}
|
|
9638
9671
|
|
|
@@ -9656,8 +9689,11 @@ Optimized Search Query:`;
|
|
|
9656
9689
|
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
9657
9690
|
try {
|
|
9658
9691
|
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
9659
|
-
if (sources.length === 0) return [];
|
|
9660
|
-
const context = sources.map((s) =>
|
|
9692
|
+
if (!sources || sources.length === 0) return [];
|
|
9693
|
+
const context = sources.map((s) => {
|
|
9694
|
+
var _a2;
|
|
9695
|
+
return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
|
|
9696
|
+
}).join("\n\n---\n\n");
|
|
9661
9697
|
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
9662
9698
|
Focus on questions that can be answered by the context.
|
|
9663
9699
|
Keep each question under 10 words and make them very specific to the content.
|
package/dist/index.js
CHANGED
|
@@ -1739,32 +1739,31 @@ function isLikelyUiOnlyMessage(text) {
|
|
|
1739
1739
|
}
|
|
1740
1740
|
function extractProductsFromSources(sources, isUser) {
|
|
1741
1741
|
if (isUser || !sources) return [];
|
|
1742
|
-
return sources.filter((s) => {
|
|
1743
|
-
var _a;
|
|
1742
|
+
return sources.filter((s) => Boolean(s && typeof s === "object")).filter((s) => {
|
|
1743
|
+
var _a, _b;
|
|
1744
1744
|
const m = (_a = s.metadata) != null ? _a : {};
|
|
1745
1745
|
const keys = Object.keys(m).map((k) => k.toLowerCase());
|
|
1746
1746
|
const hasStrongProductKey = keys.some(
|
|
1747
1747
|
(k) => ["price", "image", "img", "thumbnail", "images", "product", "sku", "cost"].includes(k)
|
|
1748
1748
|
);
|
|
1749
1749
|
const hasProductIdentity = keys.some((k) => ["brand", "model"].includes(k)) && keys.some((k) => ["name", "title", "product_name", "product"].includes(k));
|
|
1750
|
-
const hasPricePattern = /\$\s*\d+/.test(s.content);
|
|
1750
|
+
const hasPricePattern = /\$\s*\d+/.test((_b = s == null ? void 0 : s.content) != null ? _b : "");
|
|
1751
1751
|
return hasStrongProductKey || hasProductIdentity || hasPricePattern || m.type === "product";
|
|
1752
1752
|
}).map((s) => {
|
|
1753
|
-
var _a, _b, _c;
|
|
1753
|
+
var _a, _b, _c, _d;
|
|
1754
1754
|
const m = (_a = s.metadata) != null ? _a : {};
|
|
1755
1755
|
const name = resolveMetadataValue(m, "name");
|
|
1756
1756
|
const brand = resolveMetadataValue(m, "brand");
|
|
1757
1757
|
const price = resolveMetadataValue(m, "price");
|
|
1758
1758
|
const description = resolveMetadataValue(m, "description");
|
|
1759
1759
|
return {
|
|
1760
|
-
id: s.id,
|
|
1761
|
-
name: (
|
|
1760
|
+
id: (_b = s == null ? void 0 : s.id) != null ? _b : `prod_${Math.random().toString(36).substring(2, 8)}`,
|
|
1761
|
+
name: (_d = name != null ? name : ((_c = s == null ? void 0 : s.content) != null ? _c : "").split("\n")[0]) != null ? _d : "Unknown Product",
|
|
1762
1762
|
brand,
|
|
1763
1763
|
price,
|
|
1764
1764
|
image: resolveImage(m),
|
|
1765
1765
|
link: resolveMetadataValue(m, "link"),
|
|
1766
1766
|
description: description ? description : ""
|
|
1767
|
-
//description ?? s.content,
|
|
1768
1767
|
};
|
|
1769
1768
|
});
|
|
1770
1769
|
}
|
|
@@ -3142,10 +3141,10 @@ function ChatWindow({
|
|
|
3142
3141
|
] })
|
|
3143
3142
|
] }),
|
|
3144
3143
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
3145
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400
|
|
3144
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex items-center gap-1.5 mr-2", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400", children: [
|
|
3146
3145
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }),
|
|
3147
3146
|
"Online"
|
|
3148
|
-
] }),
|
|
3147
|
+
] }) }),
|
|
3149
3148
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center bg-slate-100/50 dark:bg-white/5 rounded-lg p-0.5 border border-slate-200/50 dark:border-white/5", children: [
|
|
3150
3149
|
mounted && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3151
3150
|
"button",
|
|
@@ -3673,8 +3672,8 @@ var IntentClassifier = class {
|
|
|
3673
3672
|
}
|
|
3674
3673
|
// ─── Intent Heuristic Checkers ─────────────────────────────────────────────
|
|
3675
3674
|
static isInformationLookup(query) {
|
|
3676
|
-
if (/^(what|who|explain|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of)\s+/i.test(query)) {
|
|
3677
|
-
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
|
|
3675
|
+
if (/^(what|who|explain|explore|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(query)) {
|
|
3676
|
+
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
|
|
3678
3677
|
return true;
|
|
3679
3678
|
}
|
|
3680
3679
|
}
|
|
@@ -3711,7 +3710,7 @@ var Rule1SpecificInfoRule = class {
|
|
|
3711
3710
|
}
|
|
3712
3711
|
evaluate(context, intent) {
|
|
3713
3712
|
const q = (context.userQuery || "").toLowerCase().trim();
|
|
3714
|
-
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|define|where|when|why|meaning of)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
3713
|
+
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|explore|define|where|when|why|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
3715
3714
|
const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
|
|
3716
3715
|
if (isFactQuery && isNonVisual) {
|
|
3717
3716
|
return {
|
|
@@ -4849,6 +4848,11 @@ ${schemaProfileText}` : ""}`;
|
|
|
4849
4848
|
}
|
|
4850
4849
|
static chooseAutomaticVisualization(data, profile, query) {
|
|
4851
4850
|
if (profile.records.length === 0) return null;
|
|
4851
|
+
const q = query.toLowerCase().trim();
|
|
4852
|
+
const isExplicitVisualOrAnalytic = /\b(chart|graph|plot|visualize|trend|monthly|revenue|top\s*\d+|breakdown|analytics|compare|versus|vs\.?|histogram|pie|bar|line|distribution|percentage|share|summary of metrics)\b/i.test(q);
|
|
4853
|
+
if (!isExplicitVisualOrAnalytic) {
|
|
4854
|
+
return null;
|
|
4855
|
+
}
|
|
4852
4856
|
if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
|
|
4853
4857
|
return this.transformToLineChart(profile);
|
|
4854
4858
|
}
|
|
@@ -4964,6 +4968,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
4964
4968
|
return Array.from(categories);
|
|
4965
4969
|
}
|
|
4966
4970
|
static getProductCategory(item) {
|
|
4971
|
+
if (!item) return null;
|
|
4967
4972
|
const meta = item.metadata || {};
|
|
4968
4973
|
const metadataCategory = resolveMetadataValue(meta, "category");
|
|
4969
4974
|
if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
|
|
@@ -5080,6 +5085,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
5080
5085
|
if (!fields.has(normalized)) fields.set(normalized, clean);
|
|
5081
5086
|
};
|
|
5082
5087
|
data.forEach((item) => {
|
|
5088
|
+
if (!item) return;
|
|
5083
5089
|
Object.keys(item.metadata || {}).forEach(addField);
|
|
5084
5090
|
for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
|
|
5085
5091
|
addField(match[1]);
|
|
@@ -5196,6 +5202,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
5196
5202
|
return true;
|
|
5197
5203
|
}
|
|
5198
5204
|
static extractStockQuantity(item) {
|
|
5205
|
+
if (!item) return null;
|
|
5199
5206
|
const meta = item.metadata || {};
|
|
5200
5207
|
const stockValue = resolveMetadataValue(meta, "stock");
|
|
5201
5208
|
const numericStock = this.toFiniteNumber(stockValue);
|