@retrivora-ai/rag-engine 1.3.3 → 1.3.5

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/index.js CHANGED
@@ -335,10 +335,9 @@ function sanitizeJson(raw) {
335
335
  s = s.replace(/,\s*([}\]])/g, "$1");
336
336
  s = s.replace(/([{,])\s*""\s*(?=[,}])/g, "$1");
337
337
  {
338
+ const stack = [];
338
339
  let inString = false;
339
340
  let escape = false;
340
- let braces = 0;
341
- let brackets = 0;
342
341
  for (const ch of s) {
343
342
  if (escape) {
344
343
  escape = false;
@@ -353,23 +352,21 @@ function sanitizeJson(raw) {
353
352
  continue;
354
353
  }
355
354
  if (inString) continue;
356
- if (ch === "{") braces++;
357
- else if (ch === "}") braces--;
358
- else if (ch === "[") brackets++;
359
- else if (ch === "]") brackets--;
355
+ if (ch === "{" || ch === "[") {
356
+ stack.push(ch);
357
+ } else if (ch === "}") {
358
+ if (stack[stack.length - 1] === "{") stack.pop();
359
+ } else if (ch === "]") {
360
+ if (stack[stack.length - 1] === "[") stack.pop();
361
+ }
360
362
  }
361
363
  if (inString) s += '"';
362
- while (brackets > 0) {
363
- s += "]";
364
- brackets--;
365
- }
366
- while (braces > 0) {
367
- s += "}";
368
- braces--;
364
+ for (let i = stack.length - 1; i >= 0; i--) {
365
+ s += stack[i] === "{" ? "}" : "]";
369
366
  }
370
367
  }
371
368
  const lastClose = Math.max(s.lastIndexOf("}"), s.lastIndexOf("]"));
372
- if (lastClose !== -1 && lastClose < s.length - 1) {
369
+ if (lastClose !== -1 && lastClose < s.length - 1 && /\S/.test(s.slice(lastClose + 1))) {
373
370
  s = s.substring(0, lastClose + 1);
374
371
  }
375
372
  return s;
@@ -384,8 +381,9 @@ function resolveImage(data) {
384
381
  }
385
382
  return void 0;
386
383
  }
387
- function ChartBlock({ rawContent, primaryColor, accentColor }) {
384
+ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
388
385
  const result = import_react5.default.useMemo(() => {
386
+ if (isStreaming) return { loading: true };
389
387
  if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
390
388
  try {
391
389
  const sanitized = sanitizeJson(rawContent);
@@ -396,7 +394,10 @@ function ChartBlock({ rawContent, primaryColor, accentColor }) {
396
394
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
397
395
  return { error: String(err) };
398
396
  }
399
- }, [rawContent]);
397
+ }, [rawContent, isStreaming]);
398
+ if ("loading" in result) {
399
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization..."));
400
+ }
400
401
  if ("error" in result) {
401
402
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "font-medium mb-1" }, "Failed to render chart"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
402
403
  }
@@ -527,14 +528,15 @@ function MessageBubble({
527
528
  {
528
529
  rawContent: String(children != null ? children : "").trim(),
529
530
  primaryColor,
530
- accentColor
531
+ accentColor,
532
+ isStreaming
531
533
  }
532
534
  );
533
535
  }
534
536
  return /* @__PURE__ */ import_react5.default.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
535
537
  }
536
538
  }),
537
- [primaryColor, accentColor]
539
+ [primaryColor, accentColor, isStreaming]
538
540
  );
539
541
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ import_react5.default.createElement(
540
542
  "div",
package/dist/index.mjs CHANGED
@@ -298,10 +298,9 @@ function sanitizeJson(raw) {
298
298
  s = s.replace(/,\s*([}\]])/g, "$1");
299
299
  s = s.replace(/([{,])\s*""\s*(?=[,}])/g, "$1");
300
300
  {
301
+ const stack = [];
301
302
  let inString = false;
302
303
  let escape = false;
303
- let braces = 0;
304
- let brackets = 0;
305
304
  for (const ch of s) {
306
305
  if (escape) {
307
306
  escape = false;
@@ -316,23 +315,21 @@ function sanitizeJson(raw) {
316
315
  continue;
317
316
  }
318
317
  if (inString) continue;
319
- if (ch === "{") braces++;
320
- else if (ch === "}") braces--;
321
- else if (ch === "[") brackets++;
322
- else if (ch === "]") brackets--;
318
+ if (ch === "{" || ch === "[") {
319
+ stack.push(ch);
320
+ } else if (ch === "}") {
321
+ if (stack[stack.length - 1] === "{") stack.pop();
322
+ } else if (ch === "]") {
323
+ if (stack[stack.length - 1] === "[") stack.pop();
324
+ }
323
325
  }
324
326
  if (inString) s += '"';
325
- while (brackets > 0) {
326
- s += "]";
327
- brackets--;
328
- }
329
- while (braces > 0) {
330
- s += "}";
331
- braces--;
327
+ for (let i = stack.length - 1; i >= 0; i--) {
328
+ s += stack[i] === "{" ? "}" : "]";
332
329
  }
333
330
  }
334
331
  const lastClose = Math.max(s.lastIndexOf("}"), s.lastIndexOf("]"));
335
- if (lastClose !== -1 && lastClose < s.length - 1) {
332
+ if (lastClose !== -1 && lastClose < s.length - 1 && /\S/.test(s.slice(lastClose + 1))) {
336
333
  s = s.substring(0, lastClose + 1);
337
334
  }
338
335
  return s;
@@ -347,8 +344,9 @@ function resolveImage(data) {
347
344
  }
348
345
  return void 0;
349
346
  }
350
- function ChartBlock({ rawContent, primaryColor, accentColor }) {
347
+ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
351
348
  const result = React5.useMemo(() => {
349
+ if (isStreaming) return { loading: true };
352
350
  if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
353
351
  try {
354
352
  const sanitized = sanitizeJson(rawContent);
@@ -359,7 +357,10 @@ function ChartBlock({ rawContent, primaryColor, accentColor }) {
359
357
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
360
358
  return { error: String(err) };
361
359
  }
362
- }, [rawContent]);
360
+ }, [rawContent, isStreaming]);
361
+ if ("loading" in result) {
362
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium animate-pulse" }, "Preparing data visualization..."));
363
+ }
363
364
  if ("error" in result) {
364
365
  return /* @__PURE__ */ React5.createElement("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ React5.createElement("p", { className: "font-medium mb-1" }, "Failed to render chart"), /* @__PURE__ */ React5.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
365
366
  }
@@ -490,14 +491,15 @@ function MessageBubble({
490
491
  {
491
492
  rawContent: String(children != null ? children : "").trim(),
492
493
  primaryColor,
493
- accentColor
494
+ accentColor,
495
+ isStreaming
494
496
  }
495
497
  );
496
498
  }
497
499
  return /* @__PURE__ */ React5.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
498
500
  }
499
501
  }),
500
- [primaryColor, accentColor]
502
+ [primaryColor, accentColor, isStreaming]
501
503
  );
502
504
  return /* @__PURE__ */ React5.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
503
505
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
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": "MIT",
@@ -46,36 +46,45 @@ function sanitizeJson(raw: string): string {
46
46
  // 6. Remove completely empty key-value pairs left by prior cleanup
47
47
  s = s.replace(/([{,])\s*""\s*(?=[,}])/g, '$1');
48
48
 
49
- // 7. Balance braces/brackets: walk char-by-char, track depth, append closers
50
- // Do this BEFORE the lastValidIndex trim so we don't cut valid JSON.
49
+ // 7. Stack-based structure balancer.
50
+ // Walk every character, maintain a stack of openers so we close each
51
+ // unclosed structure with its exact matching closer — in the right order.
52
+ // This is the critical fix: a flat counter approach appends all ]'s then
53
+ // all }'s, which corrupts nested structures like {..., [..., {...}]}.
51
54
  {
55
+ const stack: ('{' | '[')[] = [];
52
56
  let inString = false;
53
57
  let escape = false;
54
- let braces = 0;
55
- let brackets = 0;
56
58
 
57
59
  for (const ch of s) {
58
60
  if (escape) { escape = false; continue; }
59
61
  if (ch === '\\' && inString) { escape = true; continue; }
60
62
  if (ch === '"') { inString = !inString; continue; }
61
63
  if (inString) continue;
62
- if (ch === '{') braces++;
63
- else if (ch === '}') braces--;
64
- else if (ch === '[') brackets++;
65
- else if (ch === ']') brackets--;
64
+
65
+ if (ch === '{' || ch === '[') {
66
+ stack.push(ch);
67
+ } else if (ch === '}') {
68
+ if (stack[stack.length - 1] === '{') stack.pop();
69
+ // mismatched closer — leave stack alone (sanitize step 5 handles trailing commas)
70
+ } else if (ch === ']') {
71
+ if (stack[stack.length - 1] === '[') stack.pop();
72
+ }
66
73
  }
67
74
 
68
- // Close any unclosed string first
75
+ // Close any unterminated string literal first
69
76
  if (inString) s += '"';
70
- // Then close structures in the correct order
71
- while (brackets > 0) { s += ']'; brackets--; }
72
- while (braces > 0) { s += '}'; braces--; }
77
+
78
+ // Close every unclosed opener in reverse (innermost first)
79
+ for (let i = stack.length - 1; i >= 0; i--) {
80
+ s += stack[i] === '{' ? '}' : ']';
81
+ }
73
82
  }
74
83
 
75
- // 8. Trim to the last valid closing char only if there is trailing garbage
76
- // (e.g. a partial second object after the first one ends)
84
+ // 8. Trim trailing garbage that appears after the outermost closing char.
85
+ // Only fires when something non-whitespace follows the last } or ].
77
86
  const lastClose = Math.max(s.lastIndexOf('}'), s.lastIndexOf(']'));
78
- if (lastClose !== -1 && lastClose < s.length - 1) {
87
+ if (lastClose !== -1 && lastClose < s.length - 1 && /\S/.test(s.slice(lastClose + 1))) {
79
88
  s = s.substring(0, lastClose + 1);
80
89
  }
81
90
 
@@ -101,10 +110,12 @@ interface ChartBlockProps {
101
110
  rawContent: string;
102
111
  primaryColor: string;
103
112
  accentColor: string;
113
+ isStreaming?: boolean;
104
114
  }
105
115
 
106
- function ChartBlock({ rawContent, primaryColor, accentColor }: ChartBlockProps) {
107
- const result = React.useMemo<{ config: ChartConfig } | { error: string }>(() => {
116
+ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: ChartBlockProps) {
117
+ const result = React.useMemo<{ config: ChartConfig } | { error: string } | { loading: boolean }>(() => {
118
+ if (isStreaming) return { loading: true };
108
119
  if (!rawContent || rawContent === 'undefined') return { error: 'Empty chart config.' };
109
120
  try {
110
121
  const sanitized = sanitizeJson(rawContent);
@@ -115,7 +126,16 @@ function ChartBlock({ rawContent, primaryColor, accentColor }: ChartBlockProps)
115
126
  console.error('[ChartBlock] Parsing failed.\nError:', err, '\nSanitized:\n', sanitized);
116
127
  return { error: String(err) };
117
128
  }
118
- }, [rawContent]);
129
+ }, [rawContent, isStreaming]);
130
+
131
+ if ('loading' in result) {
132
+ return (
133
+ <div className="my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 select-none">
134
+ <div className="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
135
+ <p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization...</p>
136
+ </div>
137
+ );
138
+ }
119
139
 
120
140
  if ('error' in result) {
121
141
  return (
@@ -211,10 +231,7 @@ export function MessageBubble({
211
231
  const mentioned =
212
232
  contentLower.includes(p.name.toLowerCase()) ||
213
233
  (p.brand ? contentLower.includes(p.brand.toLowerCase()) : false);
214
- if (mentioned) {
215
- seen.add(id);
216
- return true;
217
- }
234
+ if (mentioned) { seen.add(id); return true; }
218
235
  return false;
219
236
  });
220
237
  }, [productsFromSources, productsFromContent, message.content]);
@@ -263,6 +280,7 @@ export function MessageBubble({
263
280
  rawContent={String(children ?? '').trim()}
264
281
  primaryColor={primaryColor}
265
282
  accentColor={accentColor}
283
+ isStreaming={isStreaming}
266
284
  />
267
285
  );
268
286
  }
@@ -276,7 +294,7 @@ export function MessageBubble({
276
294
  );
277
295
  },
278
296
  }),
279
- [primaryColor, accentColor],
297
+ [primaryColor, accentColor, isStreaming],
280
298
  );
281
299
 
282
300
  // ── Render ─────────────────────────────────────────────────────────────────