@retrivora-ai/rag-engine 1.3.8 → 1.3.9

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
@@ -381,14 +381,65 @@ function resolveImage(data) {
381
381
  }
382
382
  return void 0;
383
383
  }
384
+ function normaliseChild(children) {
385
+ if (typeof children === "object" && children !== null && !Array.isArray(children) && !import_react5.default.isValidElement(children)) {
386
+ return JSON.stringify(children);
387
+ }
388
+ return children;
389
+ }
390
+ function DataTable({ config }) {
391
+ const keys = import_react5.default.useMemo(() => {
392
+ if (Array.isArray(config.columns) && config.columns.length) {
393
+ return config.columns;
394
+ }
395
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
396
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
397
+ return [config.xAxisKey, ...config.dataKeys];
398
+ }
399
+ return config.dataKeys;
400
+ }
401
+ if (config.data.length > 0) return Object.keys(config.data[0]);
402
+ return [];
403
+ }, [config]);
404
+ if (!config.data.length || !keys.length) {
405
+ return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
406
+ }
407
+ const formatCell = (val) => {
408
+ if (val === null || val === void 0) return "\u2014";
409
+ if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
410
+ return String(val);
411
+ };
412
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react5.default.createElement("table", { className: "w-full text-left border-collapse min-w-[320px] text-sm" }, /* @__PURE__ */ import_react5.default.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("tr", null, keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
413
+ "th",
414
+ {
415
+ key: k,
416
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
417
+ },
418
+ k
419
+ )))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
420
+ "tr",
421
+ {
422
+ key: ri,
423
+ className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
424
+ },
425
+ keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
426
+ "td",
427
+ {
428
+ key: k,
429
+ className: "px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
430
+ },
431
+ formatCell(row[k])
432
+ ))
433
+ ))))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
434
+ }
384
435
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
385
436
  const result = import_react5.default.useMemo(() => {
386
437
  if (isStreaming) return { loading: true };
387
- if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
438
+ if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
388
439
  try {
389
440
  const sanitized = sanitizeJson(rawContent);
390
- const config = JSON.parse(sanitized);
391
- return { config };
441
+ const config2 = JSON.parse(sanitized);
442
+ return { config: config2 };
392
443
  } catch (err) {
393
444
  const sanitized = sanitizeJson(rawContent);
394
445
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
@@ -396,12 +447,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
396
447
  }
397
448
  }, [rawContent, isStreaming]);
398
449
  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..."));
450
+ 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\u2026"));
400
451
  }
401
452
  if ("error" in result) {
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."));
453
+ 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 visualization"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
403
454
  }
404
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ import_react5.default.createElement(DynamicChart, { config: result.config, primaryColor, accentColor }));
455
+ const { config } = result;
456
+ if (config.type === "table") {
457
+ return /* @__PURE__ */ import_react5.default.createElement(DataTable, { config });
458
+ }
459
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ import_react5.default.createElement(
460
+ DynamicChart,
461
+ {
462
+ config,
463
+ primaryColor,
464
+ accentColor
465
+ }
466
+ ));
405
467
  }
406
468
  function MessageBubble({
407
469
  message,
@@ -475,6 +537,10 @@ function MessageBubble({
475
537
  return false;
476
538
  });
477
539
  }, [productsFromSources, productsFromContent, message.content]);
540
+ const processedMarkdown = import_react5.default.useMemo(() => {
541
+ const raw = cleanContent || message.content;
542
+ return raw.replace(/([^\n])\n\|/g, "$1\n\n|");
543
+ }, [cleanContent, message.content]);
478
544
  const markdownComponents = import_react5.default.useMemo(
479
545
  () => ({
480
546
  table: (_a) => {
@@ -495,7 +561,7 @@ function MessageBubble({
495
561
  return /* @__PURE__ */ import_react5.default.createElement(
496
562
  "th",
497
563
  __spreadValues({
498
- className: "p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
564
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
499
565
  }, props),
500
566
  normaliseChild(children)
501
567
  );
@@ -505,7 +571,7 @@ function MessageBubble({
505
571
  return /* @__PURE__ */ import_react5.default.createElement(
506
572
  "td",
507
573
  __spreadValues({
508
- className: "p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
574
+ className: "px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
509
575
  }, props),
510
576
  normaliseChild(children)
511
577
  );
@@ -551,7 +617,7 @@ function MessageBubble({
551
617
  className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
552
618
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
553
619
  },
554
- isStreaming && !message.content ? /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react5.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
620
+ isStreaming && !message.content ? /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react5.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
555
621
  ), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
556
622
  ProductCarousel,
557
623
  {
@@ -572,12 +638,6 @@ function MessageBubble({
572
638
  " used"
573
639
  ), showSources && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mt-2 flex flex-col gap-2" }, sources.map((src, i) => /* @__PURE__ */ import_react5.default.createElement(SourceCard, { key: src.id, source: src, index: i }))))));
574
640
  }
575
- function normaliseChild(children) {
576
- if (typeof children === "object" && children !== null && !Array.isArray(children) && !import_react5.default.isValidElement(children)) {
577
- return JSON.stringify(children);
578
- }
579
- return children;
580
- }
581
641
 
582
642
  // src/components/ConfigProvider.tsx
583
643
  var import_react6 = __toESM(require("react"));
package/dist/index.mjs CHANGED
@@ -344,14 +344,65 @@ function resolveImage(data) {
344
344
  }
345
345
  return void 0;
346
346
  }
347
+ function normaliseChild(children) {
348
+ if (typeof children === "object" && children !== null && !Array.isArray(children) && !React5.isValidElement(children)) {
349
+ return JSON.stringify(children);
350
+ }
351
+ return children;
352
+ }
353
+ function DataTable({ config }) {
354
+ const keys = React5.useMemo(() => {
355
+ if (Array.isArray(config.columns) && config.columns.length) {
356
+ return config.columns;
357
+ }
358
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
359
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
360
+ return [config.xAxisKey, ...config.dataKeys];
361
+ }
362
+ return config.dataKeys;
363
+ }
364
+ if (config.data.length > 0) return Object.keys(config.data[0]);
365
+ return [];
366
+ }, [config]);
367
+ if (!config.data.length || !keys.length) {
368
+ return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
369
+ }
370
+ const formatCell = (val) => {
371
+ if (val === null || val === void 0) return "\u2014";
372
+ if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
373
+ return String(val);
374
+ };
375
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: "w-full text-left border-collapse min-w-[320px] text-sm" }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null, keys.map((k) => /* @__PURE__ */ React5.createElement(
376
+ "th",
377
+ {
378
+ key: k,
379
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
380
+ },
381
+ k
382
+ )))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
383
+ "tr",
384
+ {
385
+ key: ri,
386
+ className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
387
+ },
388
+ keys.map((k) => /* @__PURE__ */ React5.createElement(
389
+ "td",
390
+ {
391
+ key: k,
392
+ className: "px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
393
+ },
394
+ formatCell(row[k])
395
+ ))
396
+ ))))), /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ React5.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
397
+ }
347
398
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
348
399
  const result = React5.useMemo(() => {
349
400
  if (isStreaming) return { loading: true };
350
- if (!rawContent || rawContent === "undefined") return { error: "Empty chart config." };
401
+ if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
351
402
  try {
352
403
  const sanitized = sanitizeJson(rawContent);
353
- const config = JSON.parse(sanitized);
354
- return { config };
404
+ const config2 = JSON.parse(sanitized);
405
+ return { config: config2 };
355
406
  } catch (err) {
356
407
  const sanitized = sanitizeJson(rawContent);
357
408
  console.error("[ChartBlock] Parsing failed.\nError:", err, "\nSanitized:\n", sanitized);
@@ -359,12 +410,23 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
359
410
  }
360
411
  }, [rawContent, isStreaming]);
361
412
  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..."));
413
+ 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\u2026"));
363
414
  }
364
415
  if ("error" in result) {
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."));
416
+ 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 visualization"), /* @__PURE__ */ React5.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
366
417
  }
367
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ React5.createElement(DynamicChart, { config: result.config, primaryColor, accentColor }));
418
+ const { config } = result;
419
+ if (config.type === "table") {
420
+ return /* @__PURE__ */ React5.createElement(DataTable, { config });
421
+ }
422
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ React5.createElement(
423
+ DynamicChart,
424
+ {
425
+ config,
426
+ primaryColor,
427
+ accentColor
428
+ }
429
+ ));
368
430
  }
369
431
  function MessageBubble({
370
432
  message,
@@ -438,6 +500,10 @@ function MessageBubble({
438
500
  return false;
439
501
  });
440
502
  }, [productsFromSources, productsFromContent, message.content]);
503
+ const processedMarkdown = React5.useMemo(() => {
504
+ const raw = cleanContent || message.content;
505
+ return raw.replace(/([^\n])\n\|/g, "$1\n\n|");
506
+ }, [cleanContent, message.content]);
441
507
  const markdownComponents = React5.useMemo(
442
508
  () => ({
443
509
  table: (_a) => {
@@ -458,7 +524,7 @@ function MessageBubble({
458
524
  return /* @__PURE__ */ React5.createElement(
459
525
  "th",
460
526
  __spreadValues({
461
- className: "p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
527
+ className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
462
528
  }, props),
463
529
  normaliseChild(children)
464
530
  );
@@ -468,7 +534,7 @@ function MessageBubble({
468
534
  return /* @__PURE__ */ React5.createElement(
469
535
  "td",
470
536
  __spreadValues({
471
- className: "p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
537
+ className: "px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
472
538
  }, props),
473
539
  normaliseChild(children)
474
540
  );
@@ -514,7 +580,7 @@ function MessageBubble({
514
580
  className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
515
581
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
516
582
  },
517
- isStreaming && !message.content ? /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React5.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
583
+ isStreaming && !message.content ? /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React5.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
518
584
  ), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
519
585
  ProductCarousel,
520
586
  {
@@ -535,12 +601,6 @@ function MessageBubble({
535
601
  " used"
536
602
  ), showSources && /* @__PURE__ */ React5.createElement("div", { className: "mt-2 flex flex-col gap-2" }, sources.map((src, i) => /* @__PURE__ */ React5.createElement(SourceCard, { key: src.id, source: src, index: i }))))));
537
603
  }
538
- function normaliseChild(children) {
539
- if (typeof children === "object" && children !== null && !Array.isArray(children) && !React5.isValidElement(children)) {
540
- return JSON.stringify(children);
541
- }
542
- return children;
543
- }
544
604
 
545
605
  // src/components/ConfigProvider.tsx
546
606
  import React6, { createContext, useContext } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
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",
@@ -49,8 +49,8 @@ function sanitizeJson(raw: string): string {
49
49
  // 7. Stack-based structure balancer.
50
50
  // Walk every character, maintain a stack of openers so we close each
51
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 {..., [..., {...}]}.
52
+ // A flat counter approach appends all ]'s then all }'s, which corrupts
53
+ // nested structures like {..., [..., {...}]}.
54
54
  {
55
55
  const stack: ('{' | '[')[] = [];
56
56
  let inString = false;
@@ -66,7 +66,6 @@ function sanitizeJson(raw: string): string {
66
66
  stack.push(ch);
67
67
  } else if (ch === '}') {
68
68
  if (stack[stack.length - 1] === '{') stack.pop();
69
- // mismatched closer — leave stack alone (sanitize step 5 handles trailing commas)
70
69
  } else if (ch === ']') {
71
70
  if (stack[stack.length - 1] === '[') stack.pop();
72
71
  }
@@ -104,7 +103,116 @@ function resolveImage(data: Record<string, unknown>): string | undefined {
104
103
  return undefined;
105
104
  }
106
105
 
107
- // ─── Chart code block renderer ────────────────────────────────────────────────
106
+ /** Safely render table cell children that might be plain objects. */
107
+ function normaliseChild(children: React.ReactNode): React.ReactNode {
108
+ if (
109
+ typeof children === 'object' &&
110
+ children !== null &&
111
+ !Array.isArray(children) &&
112
+ !React.isValidElement(children)
113
+ ) {
114
+ return JSON.stringify(children);
115
+ }
116
+ return children;
117
+ }
118
+
119
+ // ─── Inline data table renderer ───────────────────────────────────────────────
120
+ // Handles chart configs where type === "table". The LLM may emit any of:
121
+ // columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
122
+
123
+ interface TableConfig {
124
+ type: 'table';
125
+ title?: string;
126
+ columns?: string[]; // preferred: explicit ordered column labels
127
+ dataKeys?: string[]; // fallback column keys (may omit xAxisKey)
128
+ xAxisKey?: string; // row-label key to prepend when using dataKeys
129
+ data: Record<string, unknown>[];
130
+ }
131
+
132
+ function DataTable({ config }: { config: TableConfig }) {
133
+ const keys = React.useMemo<string[]>(() => {
134
+ // Priority 1: explicit columns list
135
+ if (Array.isArray(config.columns) && config.columns.length) {
136
+ return config.columns;
137
+ }
138
+ // Priority 2: dataKeys, with xAxisKey prepended if not already present
139
+ if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
140
+ if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
141
+ return [config.xAxisKey, ...config.dataKeys];
142
+ }
143
+ return config.dataKeys;
144
+ }
145
+ // Priority 3: auto-detect from first data row
146
+ if (config.data.length > 0) return Object.keys(config.data[0]);
147
+ return [];
148
+ }, [config]);
149
+
150
+ if (!config.data.length || !keys.length) {
151
+ return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
152
+ }
153
+
154
+ const formatCell = (val: unknown): string => {
155
+ if (val === null || val === undefined) return '—';
156
+ if (typeof val === 'boolean') return val ? '✓' : '✗';
157
+ return String(val);
158
+ };
159
+
160
+ return (
161
+ <div className="my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden">
162
+ {config.title && (
163
+ <div className="px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
164
+ <p className="text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide">
165
+ {config.title}
166
+ </p>
167
+ </div>
168
+ )}
169
+
170
+ <div className="overflow-x-auto">
171
+ <table className="w-full text-left border-collapse min-w-[320px] text-sm">
172
+ <thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
173
+ <tr>
174
+ {keys.map(k => (
175
+ <th
176
+ key={k}
177
+ className="px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
178
+ >
179
+ {k}
180
+ </th>
181
+ ))}
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ {config.data.map((row, ri) => (
186
+ <tr
187
+ key={ri}
188
+ className="border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
189
+ >
190
+ {keys.map(k => (
191
+ <td
192
+ key={k}
193
+ className="px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
194
+ >
195
+ {formatCell(row[k])}
196
+ </td>
197
+ ))}
198
+ </tr>
199
+ ))}
200
+ </tbody>
201
+ </table>
202
+ </div>
203
+
204
+ <div className="px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5">
205
+ <p className="text-[11px] text-slate-400 dark:text-white/30">
206
+ {config.data.length} row{config.data.length !== 1 ? 's' : ''}
207
+ </p>
208
+ </div>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ // ─── Chart / table code block dispatcher ─────────────────────────────────────
214
+
215
+ type ParsedConfig = ChartConfig | TableConfig;
108
216
 
109
217
  interface ChartBlockProps {
110
218
  rawContent: string;
@@ -114,12 +222,14 @@ interface ChartBlockProps {
114
222
  }
115
223
 
116
224
  function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: ChartBlockProps) {
117
- const result = React.useMemo<{ config: ChartConfig } | { error: string } | { loading: boolean }>(() => {
225
+ const result = React.useMemo<
226
+ { config: ParsedConfig } | { error: string } | { loading: true }
227
+ >(() => {
118
228
  if (isStreaming) return { loading: true };
119
- if (!rawContent || rawContent === 'undefined') return { error: 'Empty chart config.' };
229
+ if (!rawContent || rawContent === 'undefined') return { error: 'Empty visualization config.' };
120
230
  try {
121
231
  const sanitized = sanitizeJson(rawContent);
122
- const config = JSON.parse(sanitized) as ChartConfig;
232
+ const config = JSON.parse(sanitized) as ParsedConfig;
123
233
  return { config };
124
234
  } catch (err) {
125
235
  const sanitized = sanitizeJson(rawContent);
@@ -132,7 +242,7 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
132
242
  return (
133
243
  <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
244
  <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>
245
+ <p className="text-xs text-slate-500 font-medium animate-pulse">Preparing data visualization…</p>
136
246
  </div>
137
247
  );
138
248
  }
@@ -140,20 +250,31 @@ function ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }: Char
140
250
  if ('error' in result) {
141
251
  return (
142
252
  <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">
143
- <p className="font-medium mb-1">Failed to render chart</p>
253
+ <p className="font-medium mb-1">Failed to render visualization</p>
144
254
  <p className="opacity-70 text-xs">The generated configuration is invalid. Check the console for details.</p>
145
255
  </div>
146
256
  );
147
257
  }
148
258
 
259
+ const { config } = result;
260
+
261
+ // Route table type to dedicated renderer; everything else goes to DynamicChart
262
+ if (config.type === 'table') {
263
+ return <DataTable config={config as TableConfig} />;
264
+ }
265
+
149
266
  return (
150
267
  <div className="my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
151
- <DynamicChart config={result.config} primaryColor={primaryColor} accentColor={accentColor} />
268
+ <DynamicChart
269
+ config={config as ChartConfig}
270
+ primaryColor={primaryColor}
271
+ accentColor={accentColor}
272
+ />
152
273
  </div>
153
274
  );
154
275
  }
155
276
 
156
- // ─── Component ────────────────────────────────────────────────────────────────
277
+ // ─── Main component ───────────────────────────────────────────────────────────
157
278
 
158
279
  export function MessageBubble({
159
280
  message,
@@ -236,6 +357,13 @@ export function MessageBubble({
236
357
  });
237
358
  }, [productsFromSources, productsFromContent, message.content]);
238
359
 
360
+ // ── Markdown source fixer ──────────────────────────────────────────────────
361
+ // GFM tables require a blank line before the | row when preceded by text.
362
+ const processedMarkdown = React.useMemo(() => {
363
+ const raw = cleanContent || message.content;
364
+ return raw.replace(/([^\n])\n\|/g, '$1\n\n|');
365
+ }, [cleanContent, message.content]);
366
+
239
367
  // ── Markdown component overrides ───────────────────────────────────────────
240
368
  const markdownComponents = React.useMemo(
241
369
  () => ({
@@ -252,7 +380,7 @@ export function MessageBubble({
252
380
  ),
253
381
  th: ({ children, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
254
382
  <th
255
- className="p-3 font-semibold text-slate-700 dark:text-white/90 first:rounded-tl-xl last:rounded-tr-xl"
383
+ className="px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
256
384
  {...props}
257
385
  >
258
386
  {normaliseChild(children)}
@@ -260,7 +388,7 @@ export function MessageBubble({
260
388
  ),
261
389
  td: ({ children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
262
390
  <td
263
- className="p-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70"
391
+ className="px-4 py-3 border-b border-slate-100 dark:border-white/5 last:border-0 text-slate-600 dark:text-white/70 whitespace-nowrap"
264
392
  {...props}
265
393
  >
266
394
  {normaliseChild(children)}
@@ -303,8 +431,8 @@ export function MessageBubble({
303
431
  {/* Avatar */}
304
432
  <div
305
433
  className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser
306
- ? 'text-white'
307
- : 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
434
+ ? 'text-white'
435
+ : 'bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80'
308
436
  }`}
309
437
  style={isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}}
310
438
  >
@@ -315,8 +443,8 @@ export function MessageBubble({
315
443
  {/* Bubble */}
316
444
  <div
317
445
  className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser
318
- ? 'text-white rounded-tr-sm'
319
- : 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
446
+ ? 'text-white rounded-tr-sm'
447
+ : 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
320
448
  }`}
321
449
  style={
322
450
  isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
@@ -331,7 +459,7 @@ export function MessageBubble({
331
459
  ) : (
332
460
  <div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
333
461
  <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
334
- {cleanContent || message.content}
462
+ {processedMarkdown}
335
463
  </ReactMarkdown>
336
464
 
337
465
  {isStreaming && message.content && (
@@ -375,19 +503,4 @@ export function MessageBubble({
375
503
  </div>
376
504
  </div>
377
505
  );
378
- }
379
-
380
- // ─── Util ─────────────────────────────────────────────────────────────────────
381
-
382
- /** Safely render table cell children that might be plain objects. */
383
- function normaliseChild(children: React.ReactNode): React.ReactNode {
384
- if (
385
- typeof children === 'object' &&
386
- children !== null &&
387
- !Array.isArray(children) &&
388
- !React.isValidElement(children)
389
- ) {
390
- return JSON.stringify(children);
391
- }
392
- return children;
393
506
  }