@retrivora-ai/rag-engine 1.5.9 → 1.6.0

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.
@@ -2279,7 +2279,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
2279
2279
  "chartType": "pie" | "bar" | "line",
2280
2280
  "xAxisKey": "label",
2281
2281
  "dataKeys": ["value"],
2282
- "columns": ["name", "category", "price", "stockStatus"],
2282
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
2283
2283
  "insights": ["Short takeaway 1", "Short takeaway 2"],
2284
2284
  "data": []
2285
2285
  }
@@ -2292,7 +2292,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
2292
2292
  - If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
2293
2293
  - If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
2294
2294
  - For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
2295
- - For tables, keep "data" row-oriented and include "columns" when possible.
2295
+ - For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
2296
2296
 
2297
2297
  4. FORMAT RULES
2298
2298
  - Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
@@ -3842,7 +3842,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
3842
3842
  "chartType": "pie" | "bar" | "line",
3843
3843
  "xAxisKey": "label",
3844
3844
  "dataKeys": ["value"],
3845
- "columns": ["name", "category", "price", "stockStatus"],
3845
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3846
3846
  "insights": ["Short takeaway 1", "Short takeaway 2"],
3847
3847
  "data": []
3848
3848
  }
@@ -3855,7 +3855,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
3855
3855
  - If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
3856
3856
  - If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
3857
3857
  - For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
3858
- - For tables, keep "data" row-oriented and include "columns" when possible.
3858
+ - For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
3859
3859
 
3860
3860
  4. FORMAT RULES
3861
3861
  - Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
@@ -9,7 +9,7 @@ import {
9
9
  sseFrame,
10
10
  sseMetaFrame,
11
11
  sseTextFrame
12
- } from "../chunk-ID2Q4CF7.mjs";
12
+ } from "../chunk-5K7EU4KW.mjs";
13
13
  import "../chunk-YLTMFW4M.mjs";
14
14
  import "../chunk-X4TOT24V.mjs";
15
15
  export {
package/dist/index.js CHANGED
@@ -248,9 +248,11 @@ function DynamicChart({
248
248
  const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
249
249
  const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
250
250
  const chartHeightClass = isTiny ? "h-48 min-h-[180px]" : isCompact ? "h-56 min-h-[220px]" : isMedium ? "h-64 min-h-[250px]" : "h-72 min-h-[280px]";
251
- const pieCanvasMaxWidthClass = isTiny ? "max-w-[11rem]" : isCompact ? "max-w-[14rem]" : isMedium ? "max-w-[16rem]" : "max-w-[18rem]";
252
- const pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
253
- const pieLabel = ({ name, percent }) => isTiny ? "" : isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
251
+ const pieOuterRadius = (() => {
252
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
253
+ const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
254
+ return Math.max(36, Math.floor(squareSize * 0.28));
255
+ })();
254
256
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
255
257
  const showLegend = !isTiny;
256
258
  const truncateAxisLabel = (value) => {
@@ -358,7 +360,7 @@ function DynamicChart({
358
360
  return row;
359
361
  };
360
362
  if (type === "pie") {
361
- return /* @__PURE__ */ import_react4.default.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: `mx-auto h-full w-full ${pieCanvasMaxWidthClass}` }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.PieChart, null, /* @__PURE__ */ import_react4.default.createElement(
363
+ return /* @__PURE__ */ import_react4.default.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "mx-auto h-full aspect-square max-w-full" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.PieChart, null, /* @__PURE__ */ import_react4.default.createElement(
362
364
  import_recharts.Pie,
363
365
  {
364
366
  data: sanitizedData,
@@ -367,10 +369,11 @@ function DynamicChart({
367
369
  cx: "50%",
368
370
  cy: "50%",
369
371
  outerRadius: pieOuterRadius,
370
- label: isTiny ? false : pieLabel
372
+ label: false,
373
+ labelLine: false
371
374
  },
372
375
  sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
373
- ), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 }, verticalAlign: "bottom" })))));
376
+ ), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && !isCompact && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })))));
374
377
  }
375
378
  return /* @__PURE__ */ import_react4.default.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, type === "bar" ? /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: sanitizedData, margin: chartMargin }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: finalXKey, tick: axisTick, tickFormatter: truncateAxisLabel, tickLine: false, axisLine: { stroke: "#cbd5e1" }, interval: 0, minTickGap: isTiny ? 2 : isCompact ? 6 : 12 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: axisTick, tickLine: false, axisLine: false }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0), cursor: { fill: "#f1f5f9" } }), showLegend && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
376
379
  import_recharts.Bar,
@@ -482,23 +485,45 @@ function normaliseChild(children) {
482
485
  }
483
486
  return children;
484
487
  }
488
+ function normaliseFieldName(value) {
489
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
490
+ }
491
+ function formatColumnLabel(value) {
492
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
493
+ }
485
494
  function DataTable({ config, viewportSize = "large" }) {
486
495
  const isCompact = viewportSize === "compact";
487
496
  const isMedium = viewportSize === "medium";
488
- const keys = import_react5.default.useMemo(() => {
497
+ const columns = import_react5.default.useMemo(() => {
498
+ const rowKeys = Array.from(
499
+ new Set(
500
+ config.data.flatMap((row) => Object.keys(row))
501
+ )
502
+ );
503
+ const findAccessor = (label) => {
504
+ const exact = rowKeys.find((key) => key === label);
505
+ if (exact) return exact;
506
+ const normalisedLabel = normaliseFieldName(label);
507
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
508
+ if (normalisedMatch) return normalisedMatch;
509
+ return label;
510
+ };
489
511
  if (Array.isArray(config.columns) && config.columns.length) {
490
- return config.columns;
512
+ return config.columns.map((label) => ({
513
+ label: formatColumnLabel(label),
514
+ accessor: findAccessor(label)
515
+ }));
491
516
  }
492
517
  if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
493
- if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
494
- return [config.xAxisKey, ...config.dataKeys];
495
- }
496
- return config.dataKeys;
518
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
519
+ return keys.map((key) => ({
520
+ label: formatColumnLabel(key),
521
+ accessor: findAccessor(key)
522
+ }));
497
523
  }
498
- if (config.data.length > 0) return Object.keys(config.data[0]);
499
- return [];
524
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
500
525
  }, [config]);
501
- if (!config.data.length || !keys.length) {
526
+ if (!config.data.length || !columns.length) {
502
527
  return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
503
528
  }
504
529
  const formatCell = (val) => {
@@ -506,26 +531,26 @@ function DataTable({ config, viewportSize = "large" }) {
506
531
  if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
507
532
  return String(val);
508
533
  };
509
- 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 ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "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(
534
+ 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 ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "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, columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
510
535
  "th",
511
536
  {
512
- key: k,
537
+ key: column.label,
513
538
  className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
514
539
  },
515
- k
540
+ column.label
516
541
  )))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
517
542
  "tr",
518
543
  {
519
544
  key: ri,
520
545
  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"
521
546
  },
522
- keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
547
+ columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
523
548
  "td",
524
549
  {
525
- key: k,
526
- className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
550
+ key: column.label,
551
+ className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes("description") ? "whitespace-normal break-words" : "whitespace-nowrap"}`
527
552
  },
528
- formatCell(row[k])
553
+ formatCell(row[column.accessor])
529
554
  ))
530
555
  ))))), /* @__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" : "")));
531
556
  }
package/dist/index.mjs CHANGED
@@ -211,9 +211,11 @@ function DynamicChart({
211
211
  const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
212
212
  const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
213
213
  const chartHeightClass = isTiny ? "h-48 min-h-[180px]" : isCompact ? "h-56 min-h-[220px]" : isMedium ? "h-64 min-h-[250px]" : "h-72 min-h-[280px]";
214
- const pieCanvasMaxWidthClass = isTiny ? "max-w-[11rem]" : isCompact ? "max-w-[14rem]" : isMedium ? "max-w-[16rem]" : "max-w-[18rem]";
215
- const pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
216
- const pieLabel = ({ name, percent }) => isTiny ? "" : isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
214
+ const pieOuterRadius = (() => {
215
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
216
+ const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
217
+ return Math.max(36, Math.floor(squareSize * 0.28));
218
+ })();
217
219
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
218
220
  const showLegend = !isTiny;
219
221
  const truncateAxisLabel = (value) => {
@@ -321,7 +323,7 @@ function DynamicChart({
321
323
  return row;
322
324
  };
323
325
  if (type === "pie") {
324
- return /* @__PURE__ */ React4.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ React4.createElement("div", { className: `mx-auto h-full w-full ${pieCanvasMaxWidthClass}` }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
326
+ return /* @__PURE__ */ React4.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ React4.createElement("div", { className: "mx-auto h-full aspect-square max-w-full" }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
325
327
  Pie,
326
328
  {
327
329
  data: sanitizedData,
@@ -330,10 +332,11 @@ function DynamicChart({
330
332
  cx: "50%",
331
333
  cy: "50%",
332
334
  outerRadius: pieOuterRadius,
333
- label: isTiny ? false : pieLabel
335
+ label: false,
336
+ labelLine: false
334
337
  },
335
338
  sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
336
- ), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 }, verticalAlign: "bottom" })))));
339
+ ), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && !isCompact && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })))));
337
340
  }
338
341
  return /* @__PURE__ */ React4.createElement("div", { ref: containerRef, className: `w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, type === "bar" ? /* @__PURE__ */ React4.createElement(BarChart, { data: sanitizedData, margin: chartMargin }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: axisTick, tickFormatter: truncateAxisLabel, tickLine: false, axisLine: { stroke: "#cbd5e1" }, interval: 0, minTickGap: isTiny ? 2 : isCompact ? 6 : 12 }), /* @__PURE__ */ React4.createElement(YAxis, { tick: axisTick, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0), cursor: { fill: "#f1f5f9" } }), showLegend && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
339
342
  Bar,
@@ -445,23 +448,45 @@ function normaliseChild(children) {
445
448
  }
446
449
  return children;
447
450
  }
451
+ function normaliseFieldName(value) {
452
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
453
+ }
454
+ function formatColumnLabel(value) {
455
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
456
+ }
448
457
  function DataTable({ config, viewportSize = "large" }) {
449
458
  const isCompact = viewportSize === "compact";
450
459
  const isMedium = viewportSize === "medium";
451
- const keys = React5.useMemo(() => {
460
+ const columns = React5.useMemo(() => {
461
+ const rowKeys = Array.from(
462
+ new Set(
463
+ config.data.flatMap((row) => Object.keys(row))
464
+ )
465
+ );
466
+ const findAccessor = (label) => {
467
+ const exact = rowKeys.find((key) => key === label);
468
+ if (exact) return exact;
469
+ const normalisedLabel = normaliseFieldName(label);
470
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
471
+ if (normalisedMatch) return normalisedMatch;
472
+ return label;
473
+ };
452
474
  if (Array.isArray(config.columns) && config.columns.length) {
453
- return config.columns;
475
+ return config.columns.map((label) => ({
476
+ label: formatColumnLabel(label),
477
+ accessor: findAccessor(label)
478
+ }));
454
479
  }
455
480
  if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
456
- if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
457
- return [config.xAxisKey, ...config.dataKeys];
458
- }
459
- return config.dataKeys;
481
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
482
+ return keys.map((key) => ({
483
+ label: formatColumnLabel(key),
484
+ accessor: findAccessor(key)
485
+ }));
460
486
  }
461
- if (config.data.length > 0) return Object.keys(config.data[0]);
462
- return [];
487
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
463
488
  }, [config]);
464
- if (!config.data.length || !keys.length) {
489
+ if (!config.data.length || !columns.length) {
465
490
  return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
466
491
  }
467
492
  const formatCell = (val) => {
@@ -469,26 +494,26 @@ function DataTable({ config, viewportSize = "large" }) {
469
494
  if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
470
495
  return String(val);
471
496
  };
472
- 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 ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "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(
497
+ 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 ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "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, columns.map((column) => /* @__PURE__ */ React5.createElement(
473
498
  "th",
474
499
  {
475
- key: k,
500
+ key: column.label,
476
501
  className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
477
502
  },
478
- k
503
+ column.label
479
504
  )))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
480
505
  "tr",
481
506
  {
482
507
  key: ri,
483
508
  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"
484
509
  },
485
- keys.map((k) => /* @__PURE__ */ React5.createElement(
510
+ columns.map((column) => /* @__PURE__ */ React5.createElement(
486
511
  "td",
487
512
  {
488
- key: k,
489
- className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
513
+ key: column.label,
514
+ className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes("description") ? "whitespace-normal break-words" : "whitespace-nowrap"}`
490
515
  },
491
- formatCell(row[k])
516
+ formatCell(row[column.accessor])
492
517
  ))
493
518
  ))))), /* @__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" : "")));
494
519
  }
package/dist/server.js CHANGED
@@ -3933,7 +3933,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
3933
3933
  "chartType": "pie" | "bar" | "line",
3934
3934
  "xAxisKey": "label",
3935
3935
  "dataKeys": ["value"],
3936
- "columns": ["name", "category", "price", "stockStatus"],
3936
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3937
3937
  "insights": ["Short takeaway 1", "Short takeaway 2"],
3938
3938
  "data": []
3939
3939
  }
@@ -3946,7 +3946,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
3946
3946
  - If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
3947
3947
  - If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
3948
3948
  - For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
3949
- - For tables, keep "data" row-oriented and include "columns" when possible.
3949
+ - For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
3950
3950
 
3951
3951
  4. FORMAT RULES
3952
3952
  - Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-ID2Q4CF7.mjs";
42
+ } from "./chunk-5K7EU4KW.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.5.9",
3
+ "version": "1.6.0",
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",
@@ -64,20 +64,11 @@ export function DynamicChart({
64
64
  : isMedium
65
65
  ? 'h-64 min-h-[250px]'
66
66
  : 'h-72 min-h-[280px]';
67
- const pieCanvasMaxWidthClass = isTiny
68
- ? 'max-w-[11rem]'
69
- : isCompact
70
- ? 'max-w-[14rem]'
71
- : isMedium
72
- ? 'max-w-[16rem]'
73
- : 'max-w-[18rem]';
74
- const pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
75
- const pieLabel = ({ name, percent }: { name?: string; percent?: number }) =>
76
- isTiny
77
- ? ''
78
- : isCompact
79
- ? `${name}`
80
- : `${name} ${((percent ?? 0) * 100).toFixed(0)}%`;
67
+ const pieOuterRadius = (() => {
68
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
69
+ const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
70
+ return Math.max(36, Math.floor(squareSize * 0.28));
71
+ })();
81
72
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
82
73
  const showLegend = !isTiny;
83
74
  const truncateAxisLabel = (value: string | number) => {
@@ -242,7 +233,7 @@ export function DynamicChart({
242
233
  if (type === 'pie') {
243
234
  return (
244
235
  <div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
245
- <div className={`mx-auto h-full w-full ${pieCanvasMaxWidthClass}`}>
236
+ <div className="mx-auto h-full aspect-square max-w-full">
246
237
  <ResponsiveContainer width="100%" height="100%">
247
238
  <PieChart>
248
239
  <Pie
@@ -252,14 +243,15 @@ export function DynamicChart({
252
243
  cx="50%"
253
244
  cy="50%"
254
245
  outerRadius={pieOuterRadius}
255
- label={isTiny ? false : pieLabel}
246
+ label={false}
247
+ labelLine={false}
256
248
  >
257
249
  {sanitizedData.map((entry, index) => (
258
250
  <Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
259
251
  ))}
260
252
  </Pie>
261
253
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
262
- {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} verticalAlign="bottom" />}
254
+ {showLegend && !isCompact && <Legend wrapperStyle={{ fontSize: 12 }} verticalAlign="bottom" />}
263
255
  </PieChart>
264
256
  </ResponsiveContainer>
265
257
  </div>
@@ -139,6 +139,19 @@ function normaliseChild(children: React.ReactNode): React.ReactNode {
139
139
  return children;
140
140
  }
141
141
 
142
+ function normaliseFieldName(value: string): string {
143
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
144
+ }
145
+
146
+ function formatColumnLabel(value: string): string {
147
+ return value
148
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
149
+ .replace(/[_-]+/g, ' ')
150
+ .replace(/\s+/g, ' ')
151
+ .trim()
152
+ .replace(/\b\w/g, (char) => char.toUpperCase());
153
+ }
154
+
142
155
  // ─── Inline data table renderer ───────────────────────────────────────────────
143
156
  // Handles chart configs where type === "table". The LLM may emit any of:
144
157
  // columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
@@ -156,24 +169,46 @@ interface TableConfig {
156
169
  function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; viewportSize?: ChatViewportSize }) {
157
170
  const isCompact = viewportSize === 'compact';
158
171
  const isMedium = viewportSize === 'medium';
159
- const keys = React.useMemo<string[]>(() => {
160
- // Priority 1: explicit columns list
172
+ const columns = React.useMemo<Array<{ label: string; accessor: string }>>(() => {
173
+ const rowKeys = Array.from(
174
+ new Set(
175
+ config.data.flatMap((row) => Object.keys(row)),
176
+ ),
177
+ );
178
+
179
+ const findAccessor = (label: string) => {
180
+ const exact = rowKeys.find((key) => key === label);
181
+ if (exact) return exact;
182
+
183
+ const normalisedLabel = normaliseFieldName(label);
184
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
185
+ if (normalisedMatch) return normalisedMatch;
186
+
187
+ return label;
188
+ };
189
+
161
190
  if (Array.isArray(config.columns) && config.columns.length) {
162
- return config.columns;
191
+ return config.columns.map((label) => ({
192
+ label: formatColumnLabel(label),
193
+ accessor: findAccessor(label),
194
+ }));
163
195
  }
164
- // Priority 2: dataKeys, with xAxisKey prepended if not already present
196
+
165
197
  if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
166
- if (config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)) {
167
- return [config.xAxisKey, ...config.dataKeys];
168
- }
169
- return config.dataKeys;
198
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)
199
+ ? [config.xAxisKey, ...config.dataKeys]
200
+ : config.dataKeys;
201
+
202
+ return keys.map((key) => ({
203
+ label: formatColumnLabel(key),
204
+ accessor: findAccessor(key),
205
+ }));
170
206
  }
171
- // Priority 3: auto-detect from first data row
172
- if (config.data.length > 0) return Object.keys(config.data[0]);
173
- return [];
207
+
208
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
174
209
  }, [config]);
175
210
 
176
- if (!config.data.length || !keys.length) {
211
+ if (!config.data.length || !columns.length) {
177
212
  return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
178
213
  }
179
214
 
@@ -197,12 +232,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
197
232
  <table className={`w-full text-left border-collapse ${isCompact ? 'min-w-[240px] text-xs' : isMedium ? 'min-w-[280px] text-[13px]' : 'min-w-[320px] text-sm'}`}>
198
233
  <thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
199
234
  <tr>
200
- {keys.map(k => (
235
+ {columns.map((column) => (
201
236
  <th
202
- key={k}
237
+ key={column.label}
203
238
  className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`}
204
239
  >
205
- {k}
240
+ {column.label}
206
241
  </th>
207
242
  ))}
208
243
  </tr>
@@ -213,12 +248,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
213
248
  key={ri}
214
249
  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"
215
250
  >
216
- {keys.map(k => (
251
+ {columns.map((column) => (
217
252
  <td
218
- key={k}
219
- className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} text-slate-600 dark:text-white/70 whitespace-nowrap`}
253
+ key={column.label}
254
+ className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes('description') ? 'whitespace-normal break-words' : 'whitespace-nowrap'}`}
220
255
  >
221
- {formatCell(row[k])}
256
+ {formatCell(row[column.accessor])}
222
257
  </td>
223
258
  ))}
224
259
  </tr>
@@ -119,7 +119,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
119
119
  "chartType": "pie" | "bar" | "line",
120
120
  "xAxisKey": "label",
121
121
  "dataKeys": ["value"],
122
- "columns": ["name", "category", "price", "stockStatus"],
122
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
123
123
  "insights": ["Short takeaway 1", "Short takeaway 2"],
124
124
  "data": []
125
125
  }
@@ -132,7 +132,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
132
132
  - If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
133
133
  - If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
134
134
  - For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
135
- - For tables, keep "data" row-oriented and include "columns" when possible.
135
+ - For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
136
136
 
137
137
  4. FORMAT RULES
138
138
  - Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.