@retrivora-ai/rag-engine 2.0.8 → 2.1.2

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.
@@ -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 (e) {
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 (e) {
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 (e) {
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
- ...messages.map((m) => ({
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
- ...messages.map((m) => ({
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
- ...messages.map((m) => ({
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
- ...messages.map((m) => ({
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
- ...messages.map((m) => ({
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
- ...messages.map((m) => ({
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,
@@ -4486,14 +4500,18 @@ var UniversalLLMAdapter = class {
4486
4500
  });
4487
4501
  }
4488
4502
  async chat(messages, context) {
4489
- var _a2, _b, _c;
4503
+ var _a2, _b, _c, _d, _e;
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
- ...messages
4514
+ ...safeMessages
4497
4515
  ];
4498
4516
  let payload;
4499
4517
  if (this.opts.chatPayloadTemplate) {
@@ -4511,8 +4529,15 @@ ${context != null ? context : "None"}` },
4511
4529
  temperature: this.temperature
4512
4530
  };
4513
4531
  }
4514
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4515
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4532
+ try {
4533
+ const { data: data2 } = await this.http.post(path2, payload);
4534
+ const extractPath2 = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4535
+ const result2 = (_c = resolvePath(data2, extractPath2)) != null ? _c : extractContent(data2);
4536
+ if (result2 !== void 0) {
4537
+ return String(result2);
4538
+ }
4539
+ } catch (httpErr) {
4540
+ console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4516
4541
  const _g2 = globalThis;
4517
4542
  let dispatch = _g2.__retrivoraDispatchChat;
4518
4543
  if (typeof dispatch !== "function") {
@@ -4527,28 +4552,22 @@ ${context != null ? context : "None"}` },
4527
4552
  }
4528
4553
  }
4529
4554
  if (typeof dispatch === "function") {
4530
- try {
4531
- const res = await dispatch({
4532
- model: this.model,
4533
- messages: formattedMessages,
4534
- max_tokens: this.maxTokens,
4535
- temperature: this.temperature
4536
- }, this.apiKey);
4537
- const content = extractContent(res == null ? void 0 : res.response);
4538
- if (content !== void 0) {
4539
- return content;
4540
- }
4541
- throw new Error(`[UniversalLLMAdapter] In-process dispatch returned empty content for model: ${this.model}`);
4542
- } catch (dispatchErr) {
4543
- throw dispatchErr;
4555
+ const res = await dispatch({
4556
+ model: this.model,
4557
+ messages: formattedMessages,
4558
+ max_tokens: this.maxTokens,
4559
+ temperature: this.temperature
4560
+ }, this.apiKey);
4561
+ const content = extractContent(res == null ? void 0 : res.response);
4562
+ if (content !== void 0) {
4563
+ return content;
4544
4564
  }
4545
- } else {
4546
- throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
4547
4565
  }
4566
+ throw httpErr;
4548
4567
  }
4549
4568
  const { data } = await this.http.post(path2, payload);
4550
- const extractPath = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4551
- const result = (_c = resolvePath(data, extractPath)) != null ? _c : extractContent(data);
4569
+ const extractPath = (_d = this.opts.responseExtractPath) != null ? _d : "choices[0].message.content";
4570
+ const result = (_e = resolvePath(data, extractPath)) != null ? _e : extractContent(data);
4552
4571
  if (result === void 0) {
4553
4572
  throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
4554
4573
  }
@@ -4565,12 +4584,16 @@ ${context != null ? context : "None"}` },
4565
4584
  const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
4566
4585
  const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
4567
4586
  const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
4587
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4588
+ role: m.role || "user",
4589
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4590
+ }));
4568
4591
  const formattedMessages = [
4569
4592
  { role: "system", content: `${this.systemPrompt}
4570
4593
 
4571
4594
  Context:
4572
4595
  ${context != null ? context : "None"}` },
4573
- ...messages
4596
+ ...safeMessages
4574
4597
  ];
4575
4598
  let payload;
4576
4599
  if (this.opts.chatPayloadTemplate) {
@@ -4686,7 +4709,7 @@ ${context != null ? context : "None"}` },
4686
4709
  });
4687
4710
  }
4688
4711
  async embed(text) {
4689
- var _a2, _b, _c, _d;
4712
+ var _a2, _b, _c, _d, _e;
4690
4713
  const path2 = (_a2 = this.opts.embedPath) != null ? _a2 : "/embeddings";
4691
4714
  let payload;
4692
4715
  if (this.opts.embedPayloadTemplate) {
@@ -4700,8 +4723,15 @@ ${context != null ? context : "None"}` },
4700
4723
  input: text
4701
4724
  };
4702
4725
  }
4703
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4704
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4726
+ try {
4727
+ const { data: data2 } = await this.http.post(path2, payload);
4728
+ const extractPath2 = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
4729
+ const vector2 = resolvePath(data2, extractPath2);
4730
+ if (Array.isArray(vector2)) {
4731
+ return vector2;
4732
+ }
4733
+ } catch (httpErr) {
4734
+ console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4705
4735
  const _g2 = globalThis;
4706
4736
  let dispatch = _g2.__retrivoraDispatchEmbedding;
4707
4737
  if (typeof dispatch !== "function") {
@@ -4716,24 +4746,18 @@ ${context != null ? context : "None"}` },
4716
4746
  }
4717
4747
  }
4718
4748
  if (typeof dispatch === "function") {
4719
- try {
4720
- const res = await dispatch({
4721
- model: this.model,
4722
- input: text
4723
- }, this.apiKey);
4724
- if ((_c = (_b = res == null ? void 0 : res.data) == null ? void 0 : _b[0]) == null ? void 0 : _c.embedding) {
4725
- return res.data[0].embedding;
4726
- }
4727
- throw new Error(`[UniversalLLMAdapter] In-process embedding dispatch returned invalid vector for model: ${this.model}`);
4728
- } catch (dispatchErr) {
4729
- throw dispatchErr;
4749
+ const res = await dispatch({
4750
+ model: this.model,
4751
+ input: text
4752
+ }, this.apiKey);
4753
+ if ((_d = (_c = res == null ? void 0 : res.data) == null ? void 0 : _c[0]) == null ? void 0 : _d.embedding) {
4754
+ return res.data[0].embedding;
4730
4755
  }
4731
- } else {
4732
- throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
4733
4756
  }
4757
+ throw httpErr;
4734
4758
  }
4735
4759
  const { data } = await this.http.post(path2, payload);
4736
- const extractPath = (_d = this.opts.embedExtractPath) != null ? _d : "data[0].embedding";
4760
+ const extractPath = (_e = this.opts.embedExtractPath) != null ? _e : "data[0].embedding";
4737
4761
  const vector = resolvePath(data, extractPath);
4738
4762
  if (!Array.isArray(vector)) {
4739
4763
  throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
@@ -6708,8 +6732,8 @@ var IntentClassifier = class {
6708
6732
  }
6709
6733
  // ─── Intent Heuristic Checkers ─────────────────────────────────────────────
6710
6734
  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)) {
6735
+ 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)) {
6736
+ if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
6713
6737
  return true;
6714
6738
  }
6715
6739
  }
@@ -6746,7 +6770,7 @@ var Rule1SpecificInfoRule = class {
6746
6770
  }
6747
6771
  evaluate(context, intent) {
6748
6772
  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);
6773
+ 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
6774
  const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
6751
6775
  if (isFactQuery && isNonVisual) {
6752
6776
  return {
@@ -8025,6 +8049,11 @@ ${schemaProfileText}` : ""}`;
8025
8049
  }
8026
8050
  static chooseAutomaticVisualization(data, profile, query) {
8027
8051
  if (profile.records.length === 0) return null;
8052
+ const q = query.toLowerCase().trim();
8053
+ 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);
8054
+ if (!isExplicitVisualOrAnalytic) {
8055
+ return null;
8056
+ }
8028
8057
  if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
8029
8058
  return this.transformToLineChart(profile);
8030
8059
  }
@@ -8140,6 +8169,7 @@ ${schemaProfileText}` : ""}`;
8140
8169
  return Array.from(categories);
8141
8170
  }
8142
8171
  static getProductCategory(item) {
8172
+ if (!item) return null;
8143
8173
  const meta = item.metadata || {};
8144
8174
  const metadataCategory = resolveMetadataValue(meta, "category");
8145
8175
  if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
@@ -8256,6 +8286,7 @@ ${schemaProfileText}` : ""}`;
8256
8286
  if (!fields.has(normalized)) fields.set(normalized, clean);
8257
8287
  };
8258
8288
  data.forEach((item) => {
8289
+ if (!item) return;
8259
8290
  Object.keys(item.metadata || {}).forEach(addField);
8260
8291
  for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
8261
8292
  addField(match[1]);
@@ -8372,6 +8403,7 @@ ${schemaProfileText}` : ""}`;
8372
8403
  return true;
8373
8404
  }
8374
8405
  static extractStockQuantity(item) {
8406
+ if (!item) return null;
8375
8407
  const meta = item.metadata || {};
8376
8408
  const stockValue = resolveMetadataValue(meta, "stock");
8377
8409
  const numericStock = this.toFiniteNumber(stockValue);
@@ -9632,7 +9664,10 @@ ${context}`;
9632
9664
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
9633
9665
 
9634
9666
  History:
9635
- ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
9667
+ ${(history || []).map((m) => {
9668
+ var _a2;
9669
+ return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
9670
+ }).join("\n")}
9636
9671
 
9637
9672
  New Question: ${question}
9638
9673
 
@@ -9656,8 +9691,11 @@ Optimized Search Query:`;
9656
9691
  const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
9657
9692
  try {
9658
9693
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
9659
- if (sources.length === 0) return [];
9660
- const context = sources.map((s) => s.content).join("\n\n---\n\n");
9694
+ if (!sources || sources.length === 0) return [];
9695
+ const context = sources.map((s) => {
9696
+ var _a2;
9697
+ return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
9698
+ }).join("\n\n---\n\n");
9661
9699
  const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
9662
9700
  Focus on questions that can be answered by the context.
9663
9701
  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: (_c = name != null ? name : ((_b = s == null ? void 0 : s.content) != null ? _b : "").split("\n")[0]) != null ? _c : "Unknown Product",
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 mr-2", children: [
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);
package/dist/index.mjs CHANGED
@@ -1741,32 +1741,31 @@ function isLikelyUiOnlyMessage(text) {
1741
1741
  }
1742
1742
  function extractProductsFromSources(sources, isUser) {
1743
1743
  if (isUser || !sources) return [];
1744
- return sources.filter((s) => {
1745
- var _a;
1744
+ return sources.filter((s) => Boolean(s && typeof s === "object")).filter((s) => {
1745
+ var _a, _b;
1746
1746
  const m = (_a = s.metadata) != null ? _a : {};
1747
1747
  const keys = Object.keys(m).map((k) => k.toLowerCase());
1748
1748
  const hasStrongProductKey = keys.some(
1749
1749
  (k) => ["price", "image", "img", "thumbnail", "images", "product", "sku", "cost"].includes(k)
1750
1750
  );
1751
1751
  const hasProductIdentity = keys.some((k) => ["brand", "model"].includes(k)) && keys.some((k) => ["name", "title", "product_name", "product"].includes(k));
1752
- const hasPricePattern = /\$\s*\d+/.test(s.content);
1752
+ const hasPricePattern = /\$\s*\d+/.test((_b = s == null ? void 0 : s.content) != null ? _b : "");
1753
1753
  return hasStrongProductKey || hasProductIdentity || hasPricePattern || m.type === "product";
1754
1754
  }).map((s) => {
1755
- var _a, _b, _c;
1755
+ var _a, _b, _c, _d;
1756
1756
  const m = (_a = s.metadata) != null ? _a : {};
1757
1757
  const name = resolveMetadataValue(m, "name");
1758
1758
  const brand = resolveMetadataValue(m, "brand");
1759
1759
  const price = resolveMetadataValue(m, "price");
1760
1760
  const description = resolveMetadataValue(m, "description");
1761
1761
  return {
1762
- id: s.id,
1763
- name: (_c = name != null ? name : ((_b = s == null ? void 0 : s.content) != null ? _b : "").split("\n")[0]) != null ? _c : "Unknown Product",
1762
+ id: (_b = s == null ? void 0 : s.id) != null ? _b : `prod_${Math.random().toString(36).substring(2, 8)}`,
1763
+ name: (_d = name != null ? name : ((_c = s == null ? void 0 : s.content) != null ? _c : "").split("\n")[0]) != null ? _d : "Unknown Product",
1764
1764
  brand,
1765
1765
  price,
1766
1766
  image: resolveImage(m),
1767
1767
  link: resolveMetadataValue(m, "link"),
1768
1768
  description: description ? description : ""
1769
- //description ?? s.content,
1770
1769
  };
1771
1770
  });
1772
1771
  }
@@ -3144,10 +3143,10 @@ function ChatWindow({
3144
3143
  ] })
3145
3144
  ] }),
3146
3145
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5", children: [
3147
- /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400 mr-2", children: [
3146
+ /* @__PURE__ */ jsx13("div", { className: "flex items-center gap-1.5 mr-2", children: /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400", children: [
3148
3147
  /* @__PURE__ */ jsx13("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }),
3149
3148
  "Online"
3150
- ] }),
3149
+ ] }) }),
3151
3150
  /* @__PURE__ */ jsxs12("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: [
3152
3151
  mounted && messages.length > 0 && /* @__PURE__ */ jsx13(
3153
3152
  "button",
@@ -3675,8 +3674,8 @@ var IntentClassifier = class {
3675
3674
  }
3676
3675
  // ─── Intent Heuristic Checkers ─────────────────────────────────────────────
3677
3676
  static isInformationLookup(query) {
3678
- 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)) {
3679
- if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
3677
+ 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)) {
3678
+ if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
3680
3679
  return true;
3681
3680
  }
3682
3681
  }
@@ -3713,7 +3712,7 @@ var Rule1SpecificInfoRule = class {
3713
3712
  }
3714
3713
  evaluate(context, intent) {
3715
3714
  const q = (context.userQuery || "").toLowerCase().trim();
3716
- 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);
3715
+ 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);
3717
3716
  const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
3718
3717
  if (isFactQuery && isNonVisual) {
3719
3718
  return {
@@ -4851,6 +4850,11 @@ ${schemaProfileText}` : ""}`;
4851
4850
  }
4852
4851
  static chooseAutomaticVisualization(data, profile, query) {
4853
4852
  if (profile.records.length === 0) return null;
4853
+ const q = query.toLowerCase().trim();
4854
+ 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);
4855
+ if (!isExplicitVisualOrAnalytic) {
4856
+ return null;
4857
+ }
4854
4858
  if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
4855
4859
  return this.transformToLineChart(profile);
4856
4860
  }
@@ -4966,6 +4970,7 @@ ${schemaProfileText}` : ""}`;
4966
4970
  return Array.from(categories);
4967
4971
  }
4968
4972
  static getProductCategory(item) {
4973
+ if (!item) return null;
4969
4974
  const meta = item.metadata || {};
4970
4975
  const metadataCategory = resolveMetadataValue(meta, "category");
4971
4976
  if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
@@ -5082,6 +5087,7 @@ ${schemaProfileText}` : ""}`;
5082
5087
  if (!fields.has(normalized)) fields.set(normalized, clean);
5083
5088
  };
5084
5089
  data.forEach((item) => {
5090
+ if (!item) return;
5085
5091
  Object.keys(item.metadata || {}).forEach(addField);
5086
5092
  for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
5087
5093
  addField(match[1]);
@@ -5198,6 +5204,7 @@ ${schemaProfileText}` : ""}`;
5198
5204
  return true;
5199
5205
  }
5200
5206
  static extractStockQuantity(item) {
5207
+ if (!item) return null;
5201
5208
  const meta = item.metadata || {};
5202
5209
  const stockValue = resolveMetadataValue(meta, "stock");
5203
5210
  const numericStock = this.toFiniteNumber(stockValue);