@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.
package/dist/server.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 (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,
@@ -4525,14 +4539,18 @@ var UniversalLLMAdapter = class {
4525
4539
  });
4526
4540
  }
4527
4541
  async chat(messages, context) {
4528
- var _a2, _b, _c;
4542
+ var _a2, _b, _c, _d, _e;
4529
4543
  const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
4544
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4545
+ role: m.role || "user",
4546
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4547
+ }));
4530
4548
  const formattedMessages = [
4531
4549
  { role: "system", content: `${this.systemPrompt}
4532
4550
 
4533
4551
  Context:
4534
4552
  ${context != null ? context : "None"}` },
4535
- ...messages
4553
+ ...safeMessages
4536
4554
  ];
4537
4555
  let payload;
4538
4556
  if (this.opts.chatPayloadTemplate) {
@@ -4550,8 +4568,15 @@ ${context != null ? context : "None"}` },
4550
4568
  temperature: this.temperature
4551
4569
  };
4552
4570
  }
4553
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4554
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4571
+ try {
4572
+ const { data: data2 } = await this.http.post(path2, payload);
4573
+ const extractPath2 = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4574
+ const result2 = (_c = resolvePath(data2, extractPath2)) != null ? _c : extractContent(data2);
4575
+ if (result2 !== void 0) {
4576
+ return String(result2);
4577
+ }
4578
+ } catch (httpErr) {
4579
+ console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4555
4580
  const _g2 = globalThis;
4556
4581
  let dispatch = _g2.__retrivoraDispatchChat;
4557
4582
  if (typeof dispatch !== "function") {
@@ -4566,28 +4591,22 @@ ${context != null ? context : "None"}` },
4566
4591
  }
4567
4592
  }
4568
4593
  if (typeof dispatch === "function") {
4569
- try {
4570
- const res = await dispatch({
4571
- model: this.model,
4572
- messages: formattedMessages,
4573
- max_tokens: this.maxTokens,
4574
- temperature: this.temperature
4575
- }, this.apiKey);
4576
- const content = extractContent(res == null ? void 0 : res.response);
4577
- if (content !== void 0) {
4578
- return content;
4579
- }
4580
- throw new Error(`[UniversalLLMAdapter] In-process dispatch returned empty content for model: ${this.model}`);
4581
- } catch (dispatchErr) {
4582
- throw dispatchErr;
4594
+ const res = await dispatch({
4595
+ model: this.model,
4596
+ messages: formattedMessages,
4597
+ max_tokens: this.maxTokens,
4598
+ temperature: this.temperature
4599
+ }, this.apiKey);
4600
+ const content = extractContent(res == null ? void 0 : res.response);
4601
+ if (content !== void 0) {
4602
+ return content;
4583
4603
  }
4584
- } else {
4585
- 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.`);
4586
4604
  }
4605
+ throw httpErr;
4587
4606
  }
4588
4607
  const { data } = await this.http.post(path2, payload);
4589
- const extractPath = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4590
- const result = (_c = resolvePath(data, extractPath)) != null ? _c : extractContent(data);
4608
+ const extractPath = (_d = this.opts.responseExtractPath) != null ? _d : "choices[0].message.content";
4609
+ const result = (_e = resolvePath(data, extractPath)) != null ? _e : extractContent(data);
4591
4610
  if (result === void 0) {
4592
4611
  throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
4593
4612
  }
@@ -4604,12 +4623,16 @@ ${context != null ? context : "None"}` },
4604
4623
  const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
4605
4624
  const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
4606
4625
  const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
4626
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4627
+ role: m.role || "user",
4628
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4629
+ }));
4607
4630
  const formattedMessages = [
4608
4631
  { role: "system", content: `${this.systemPrompt}
4609
4632
 
4610
4633
  Context:
4611
4634
  ${context != null ? context : "None"}` },
4612
- ...messages
4635
+ ...safeMessages
4613
4636
  ];
4614
4637
  let payload;
4615
4638
  if (this.opts.chatPayloadTemplate) {
@@ -4725,7 +4748,7 @@ ${context != null ? context : "None"}` },
4725
4748
  });
4726
4749
  }
4727
4750
  async embed(text) {
4728
- var _a2, _b, _c, _d;
4751
+ var _a2, _b, _c, _d, _e;
4729
4752
  const path2 = (_a2 = this.opts.embedPath) != null ? _a2 : "/embeddings";
4730
4753
  let payload;
4731
4754
  if (this.opts.embedPayloadTemplate) {
@@ -4739,8 +4762,15 @@ ${context != null ? context : "None"}` },
4739
4762
  input: text
4740
4763
  };
4741
4764
  }
4742
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4743
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4765
+ try {
4766
+ const { data: data2 } = await this.http.post(path2, payload);
4767
+ const extractPath2 = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
4768
+ const vector2 = resolvePath(data2, extractPath2);
4769
+ if (Array.isArray(vector2)) {
4770
+ return vector2;
4771
+ }
4772
+ } catch (httpErr) {
4773
+ console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4744
4774
  const _g2 = globalThis;
4745
4775
  let dispatch = _g2.__retrivoraDispatchEmbedding;
4746
4776
  if (typeof dispatch !== "function") {
@@ -4755,24 +4785,18 @@ ${context != null ? context : "None"}` },
4755
4785
  }
4756
4786
  }
4757
4787
  if (typeof dispatch === "function") {
4758
- try {
4759
- const res = await dispatch({
4760
- model: this.model,
4761
- input: text
4762
- }, this.apiKey);
4763
- if ((_c = (_b = res == null ? void 0 : res.data) == null ? void 0 : _b[0]) == null ? void 0 : _c.embedding) {
4764
- return res.data[0].embedding;
4765
- }
4766
- throw new Error(`[UniversalLLMAdapter] In-process embedding dispatch returned invalid vector for model: ${this.model}`);
4767
- } catch (dispatchErr) {
4768
- throw dispatchErr;
4788
+ const res = await dispatch({
4789
+ model: this.model,
4790
+ input: text
4791
+ }, this.apiKey);
4792
+ if ((_d = (_c = res == null ? void 0 : res.data) == null ? void 0 : _c[0]) == null ? void 0 : _d.embedding) {
4793
+ return res.data[0].embedding;
4769
4794
  }
4770
- } else {
4771
- 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.`);
4772
4795
  }
4796
+ throw httpErr;
4773
4797
  }
4774
4798
  const { data } = await this.http.post(path2, payload);
4775
- const extractPath = (_d = this.opts.embedExtractPath) != null ? _d : "data[0].embedding";
4799
+ const extractPath = (_e = this.opts.embedExtractPath) != null ? _e : "data[0].embedding";
4776
4800
  const vector = resolvePath(data, extractPath);
4777
4801
  if (!Array.isArray(vector)) {
4778
4802
  throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
@@ -6753,8 +6777,8 @@ var IntentClassifier = class {
6753
6777
  }
6754
6778
  // ─── Intent Heuristic Checkers ─────────────────────────────────────────────
6755
6779
  static isInformationLookup(query) {
6756
- 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)) {
6757
- if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
6780
+ 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)) {
6781
+ if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
6758
6782
  return true;
6759
6783
  }
6760
6784
  }
@@ -6791,7 +6815,7 @@ var Rule1SpecificInfoRule = class {
6791
6815
  }
6792
6816
  evaluate(context, intent) {
6793
6817
  const q = (context.userQuery || "").toLowerCase().trim();
6794
- 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);
6818
+ 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);
6795
6819
  const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
6796
6820
  if (isFactQuery && isNonVisual) {
6797
6821
  return {
@@ -8078,6 +8102,11 @@ ${schemaProfileText}` : ""}`;
8078
8102
  }
8079
8103
  static chooseAutomaticVisualization(data, profile, query) {
8080
8104
  if (profile.records.length === 0) return null;
8105
+ const q = query.toLowerCase().trim();
8106
+ 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);
8107
+ if (!isExplicitVisualOrAnalytic) {
8108
+ return null;
8109
+ }
8081
8110
  if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
8082
8111
  return this.transformToLineChart(profile);
8083
8112
  }
@@ -8193,6 +8222,7 @@ ${schemaProfileText}` : ""}`;
8193
8222
  return Array.from(categories);
8194
8223
  }
8195
8224
  static getProductCategory(item) {
8225
+ if (!item) return null;
8196
8226
  const meta = item.metadata || {};
8197
8227
  const metadataCategory = resolveMetadataValue(meta, "category");
8198
8228
  if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
@@ -8309,6 +8339,7 @@ ${schemaProfileText}` : ""}`;
8309
8339
  if (!fields.has(normalized)) fields.set(normalized, clean);
8310
8340
  };
8311
8341
  data.forEach((item) => {
8342
+ if (!item) return;
8312
8343
  Object.keys(item.metadata || {}).forEach(addField);
8313
8344
  for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
8314
8345
  addField(match[1]);
@@ -8425,6 +8456,7 @@ ${schemaProfileText}` : ""}`;
8425
8456
  return true;
8426
8457
  }
8427
8458
  static extractStockQuantity(item) {
8459
+ if (!item) return null;
8428
8460
  const meta = item.metadata || {};
8429
8461
  const stockValue = resolveMetadataValue(meta, "stock");
8430
8462
  const numericStock = this.toFiniteNumber(stockValue);
@@ -9685,7 +9717,10 @@ ${context}`;
9685
9717
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
9686
9718
 
9687
9719
  History:
9688
- ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
9720
+ ${(history || []).map((m) => {
9721
+ var _a2;
9722
+ return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
9723
+ }).join("\n")}
9689
9724
 
9690
9725
  New Question: ${question}
9691
9726
 
@@ -9709,8 +9744,11 @@ Optimized Search Query:`;
9709
9744
  const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
9710
9745
  try {
9711
9746
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
9712
- if (sources.length === 0) return [];
9713
- const context = sources.map((s) => s.content).join("\n\n---\n\n");
9747
+ if (!sources || sources.length === 0) return [];
9748
+ const context = sources.map((s) => {
9749
+ var _a2;
9750
+ return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
9751
+ }).join("\n\n---\n\n");
9714
9752
  const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
9715
9753
  Focus on questions that can be answered by the context.
9716
9754
  Keep each question under 10 words and make them very specific to the content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "2.0.8",
3
+ "version": "2.1.2",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "UNLICENSED",
@@ -52,10 +52,10 @@ interface WindowWithSpeech extends Window {
52
52
  webkitSpeechRecognition?: new () => ISpeechRecognition;
53
53
  }
54
54
 
55
- export function ChatWindow({
56
- className = '',
57
- style,
58
- onClose,
55
+ export function ChatWindow({
56
+ className = '',
57
+ style,
58
+ onClose,
59
59
  showClose = false,
60
60
  onResizeStart,
61
61
  onResetResize,
@@ -129,7 +129,7 @@ export function ChatWindow({
129
129
  alert('Speech recognition is not supported in your browser.');
130
130
  return;
131
131
  }
132
-
132
+
133
133
  if (isListening) {
134
134
  recognitionRef.current.stop();
135
135
  } else {
@@ -178,7 +178,7 @@ export function ChatWindow({
178
178
  const sendMessage = useCallback(async () => {
179
179
  const text = input.trim();
180
180
  if (!text || isLoading) return;
181
-
181
+
182
182
  if (isListening) {
183
183
  recognitionRef.current?.stop();
184
184
  }
@@ -250,11 +250,10 @@ export function ChatWindow({
250
250
  return (
251
251
  <div
252
252
  ref={windowRef}
253
- className={`relative flex flex-col h-full flex-1 border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
254
- isGlass
255
- ? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
253
+ className={`relative flex flex-col h-full flex-1 border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass
254
+ ? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
256
255
  : 'bg-white dark:bg-[#0f0f1a]'
257
- } ${className}`}
256
+ } ${className}`}
258
257
  style={{ '--primary': ui.primaryColor, '--accent': ui.accentColor, ...style } as React.CSSProperties}
259
258
  >
260
259
  {/* Resize Handle (Top-Left) */}
@@ -293,11 +292,13 @@ export function ChatWindow({
293
292
  </div>
294
293
 
295
294
  <div className="flex items-center gap-1.5">
296
- {/* Online indicator */}
297
- <span className="flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400 mr-2">
298
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" />
299
- Online
300
- </span>
295
+ {/* Online indicator & Deployment ID badge */}
296
+ <div className="flex items-center gap-1.5 mr-2">
297
+ <span className="flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400">
298
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" />
299
+ Online
300
+ </span>
301
+ </div>
301
302
 
302
303
  <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">
303
304
  {mounted && messages.length > 0 && (
@@ -427,9 +428,9 @@ export function ChatWindow({
427
428
  >
428
429
  <X className="w-5 h-5" />
429
430
  </button>
430
- <DocumentUpload
431
- namespace={projectId}
432
- onUploadComplete={() => setIsUploadModalOpen(false)}
431
+ <DocumentUpload
432
+ namespace={projectId}
433
+ onUploadComplete={() => setIsUploadModalOpen(false)}
433
434
  />
434
435
  </div>
435
436
  </div>
@@ -437,7 +438,7 @@ export function ChatWindow({
437
438
 
438
439
  {/* ── Input ── */}
439
440
  <div className={`px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? 'bg-transparent' : 'bg-white dark:bg-[#0f0f1a]'}`}>
440
-
441
+
441
442
  {/* Dynamic Suggestions */}
442
443
  {(suggestions.length > 0 || isSuggesting) && !isLoading && (
443
444
  <div className="mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300">
@@ -488,11 +489,10 @@ export function ChatWindow({
488
489
  type="button"
489
490
  onClick={toggleListening}
490
491
  disabled={isLoading}
491
- className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${
492
- isListening
492
+ className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${isListening
493
493
  ? 'bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20'
494
494
  : 'text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10'
495
- }`}
495
+ }`}
496
496
  title={isListening ? 'Stop listening' : 'Start voice input'}
497
497
  >
498
498
  {isListening ? <MicOff className="w-4 h-4" /> : <Mic className="w-4 h-4" />}
@@ -1193,7 +1193,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
1193
1193
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
1194
1194
 
1195
1195
  History:
1196
- ${history.map(m => `${m.role}: ${m.content}`).join('\n')}
1196
+ ${(history || []).map(m => `${m?.role || 'user'}: ${m?.content ?? ''}`).join('\n')}
1197
1197
 
1198
1198
  New Question: ${question}
1199
1199
 
@@ -1222,9 +1222,9 @@ Optimized Search Query:`;
1222
1222
 
1223
1223
  try {
1224
1224
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
1225
- if (sources.length === 0) return [];
1225
+ if (!sources || sources.length === 0) return [];
1226
1226
 
1227
- const context = sources.map((s) => s.content).join('\n\n---\n\n');
1227
+ const context = sources.map((s) => s?.content ?? '').join('\n\n---\n\n');
1228
1228
  const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
1229
1229
  Focus on questions that can be answered by the context.
1230
1230
  Keep each question under 10 words and make them very specific to the content.
@@ -175,11 +175,11 @@ export class GeminiProvider implements ILLMProvider {
175
175
  * - Messages must strictly alternate between `user` and `model`.
176
176
  */
177
177
  private buildGeminiContents(messages: ChatMessage[]): Array<{ role: 'user' | 'model'; parts: [{ text: string }] }> {
178
- return messages
179
- .filter((m) => m.role !== 'system')
178
+ return (messages || [])
179
+ .filter((m): m is ChatMessage => Boolean(m && typeof m === 'object' && m.role !== 'system'))
180
180
  .map((m) => ({
181
- role: m.role === 'assistant' ? ('model' as const) : ('user' as const),
182
- parts: [{ text: m.content }] as [{ text: string }],
181
+ role: m.role === 'assistant' ? 'model' : 'user',
182
+ parts: [{ text: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : '') }],
183
183
  }));
184
184
  }
185
185
 
@@ -95,12 +95,16 @@ export class GroqProvider implements ILLMProvider {
95
95
  content: buildSystemContent(basePrompt, context),
96
96
  };
97
97
 
98
+ const safeMessages = (Array.isArray(messages) ? messages : [])
99
+ .filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
100
+ .map((m) => ({
101
+ role: (m.role || 'user') as 'user' | 'assistant',
102
+ content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
103
+ }));
104
+
98
105
  const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
99
106
  systemMessage,
100
- ...messages.map((m) => ({
101
- role: m.role as 'user' | 'assistant',
102
- content: m.content,
103
- })),
107
+ ...safeMessages,
104
108
  ];
105
109
 
106
110
  const completion = await this.client.chat.completions.create({
@@ -125,12 +129,16 @@ export class GroqProvider implements ILLMProvider {
125
129
  content: buildSystemContent(basePrompt, context),
126
130
  };
127
131
 
132
+ const safeStreamMessages = (Array.isArray(messages) ? messages : [])
133
+ .filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
134
+ .map((m) => ({
135
+ role: (m.role || 'user') as 'user' | 'assistant',
136
+ content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
137
+ }));
138
+
128
139
  const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
129
140
  systemMessage,
130
- ...messages.map((m) => ({
131
- role: m.role as 'user' | 'assistant',
132
- content: m.content,
133
- })),
141
+ ...safeStreamMessages,
134
142
  ];
135
143
 
136
144
  const stream = await this.client.chat.completions.create({
@@ -91,12 +91,16 @@ export class OpenAIProvider implements ILLMProvider {
91
91
  content: buildSystemContent(basePrompt, context),
92
92
  };
93
93
 
94
+ const safeMessages = (Array.isArray(messages) ? messages : [])
95
+ .filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
96
+ .map((m) => ({
97
+ role: (m.role || 'user') as 'user' | 'assistant',
98
+ content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
99
+ }));
100
+
94
101
  const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
95
102
  systemMessage,
96
- ...messages.map((m) => ({
97
- role: m.role as 'user' | 'assistant',
98
- content: m.content,
99
- })),
103
+ ...safeMessages,
100
104
  ];
101
105
 
102
106
  const completion = await this.client.chat.completions.create({
@@ -121,12 +125,16 @@ export class OpenAIProvider implements ILLMProvider {
121
125
  content: buildSystemContent(basePrompt, context),
122
126
  };
123
127
 
128
+ const safeStreamMessages = (Array.isArray(messages) ? messages : [])
129
+ .filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
130
+ .map((m) => ({
131
+ role: (m.role || 'user') as 'user' | 'assistant',
132
+ content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
133
+ }));
134
+
124
135
  const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
125
136
  systemMessage,
126
- ...messages.map((m) => ({
127
- role: m.role as 'user' | 'assistant',
128
- content: m.content,
129
- })),
137
+ ...safeStreamMessages,
130
138
  ];
131
139
 
132
140
  const stream = await this.client.chat.completions.create({