@retrivora-ai/rag-engine 1.5.9 → 1.6.1

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
@@ -247,10 +247,19 @@ function DynamicChart({
247
247
  const isTiny = containerWidth > 0 && containerWidth < 300;
248
248
  const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
249
249
  const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
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)}%`;
250
+ const chartHeight = (() => {
251
+ if (type === "pie") {
252
+ if (containerWidth <= 0) return isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280;
253
+ return Math.max(180, Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280));
254
+ }
255
+ if (containerWidth <= 0) return isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300;
256
+ return Math.max(190, Math.min(Math.round(containerWidth * 0.7), isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300));
257
+ })();
258
+ const pieOuterRadius = (() => {
259
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
260
+ const squareSize = Math.min(containerWidth, chartHeight);
261
+ return Math.max(36, Math.floor(squareSize * 0.28));
262
+ })();
254
263
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
255
264
  const showLegend = !isTiny;
256
265
  const truncateAxisLabel = (value) => {
@@ -358,41 +367,58 @@ function DynamicChart({
358
367
  return row;
359
368
  };
360
369
  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(
362
- import_recharts.Pie,
370
+ return /* @__PURE__ */ import_react4.default.createElement(
371
+ "div",
363
372
  {
364
- data: sanitizedData,
365
- dataKey: pieDataKey,
366
- nameKey: finalXKey,
367
- cx: "50%",
368
- cy: "50%",
369
- outerRadius: pieOuterRadius,
370
- label: isTiny ? false : pieLabel
373
+ ref: containerRef,
374
+ className: "w-full min-w-0 mt-4 mb-2 select-none overflow-hidden",
375
+ style: { height: chartHeight }
371
376
  },
372
- 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" })))));
377
+ /* @__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(
378
+ import_recharts.Pie,
379
+ {
380
+ data: sanitizedData,
381
+ dataKey: pieDataKey,
382
+ nameKey: finalXKey,
383
+ cx: "50%",
384
+ cy: "50%",
385
+ outerRadius: pieOuterRadius,
386
+ label: false,
387
+ labelLine: false
388
+ },
389
+ sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
390
+ ), /* @__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" }))))
391
+ );
374
392
  }
375
- 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
- import_recharts.Bar,
377
- {
378
- key,
379
- dataKey: key,
380
- fill: colors[index % colors.length],
381
- radius: [4, 4, 0, 0],
382
- barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
383
- }
384
- ))) : /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { 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) }), showLegend && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
385
- import_recharts.Line,
393
+ return /* @__PURE__ */ import_react4.default.createElement(
394
+ "div",
386
395
  {
387
- key,
388
- type: "monotone",
389
- dataKey: key,
390
- stroke: colors[index % colors.length],
391
- strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
392
- dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
393
- activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
394
- }
395
- )))));
396
+ ref: containerRef,
397
+ className: "w-full min-w-0 mt-4 mb-2 select-none overflow-hidden",
398
+ style: { height: chartHeight }
399
+ },
400
+ /* @__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(
401
+ import_recharts.Bar,
402
+ {
403
+ key,
404
+ dataKey: key,
405
+ fill: colors[index % colors.length],
406
+ radius: [4, 4, 0, 0],
407
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
408
+ }
409
+ ))) : /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { 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) }), showLegend && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
410
+ import_recharts.Line,
411
+ {
412
+ key,
413
+ type: "monotone",
414
+ dataKey: key,
415
+ stroke: colors[index % colors.length],
416
+ strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
417
+ dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
418
+ activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
419
+ }
420
+ ))))
421
+ );
396
422
  }
397
423
 
398
424
  // src/components/MessageBubble.tsx
@@ -466,6 +492,15 @@ function stripMarkdownTables(raw) {
466
492
  "\n\n"
467
493
  );
468
494
  }
495
+ function stripStructuredUiLabels(raw) {
496
+ return raw.replace(
497
+ /(?:^|\n)\*\*(?:Chart Data|Table Data|Visualization|Visual Representation)\*\:\s*(?=\n|$)/gi,
498
+ "\n"
499
+ ).replace(
500
+ /(?:^|\n)(?:Chart Data|Table Data|Visualization|Visual Representation)\s*\:\s*(?=\n|$)/gi,
501
+ "\n"
502
+ );
503
+ }
469
504
  function resolveImage(data) {
470
505
  for (const key of ["image", "img", "thumbnail"]) {
471
506
  const v = data[key];
@@ -482,23 +517,45 @@ function normaliseChild(children) {
482
517
  }
483
518
  return children;
484
519
  }
520
+ function normaliseFieldName(value) {
521
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
522
+ }
523
+ function formatColumnLabel(value) {
524
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
525
+ }
485
526
  function DataTable({ config, viewportSize = "large" }) {
486
527
  const isCompact = viewportSize === "compact";
487
528
  const isMedium = viewportSize === "medium";
488
- const keys = import_react5.default.useMemo(() => {
529
+ const columns = import_react5.default.useMemo(() => {
530
+ const rowKeys = Array.from(
531
+ new Set(
532
+ config.data.flatMap((row) => Object.keys(row))
533
+ )
534
+ );
535
+ const findAccessor = (label) => {
536
+ const exact = rowKeys.find((key) => key === label);
537
+ if (exact) return exact;
538
+ const normalisedLabel = normaliseFieldName(label);
539
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
540
+ if (normalisedMatch) return normalisedMatch;
541
+ return label;
542
+ };
489
543
  if (Array.isArray(config.columns) && config.columns.length) {
490
- return config.columns;
544
+ return config.columns.map((label) => ({
545
+ label: formatColumnLabel(label),
546
+ accessor: findAccessor(label)
547
+ }));
491
548
  }
492
549
  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;
550
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
551
+ return keys.map((key) => ({
552
+ label: formatColumnLabel(key),
553
+ accessor: findAccessor(key)
554
+ }));
497
555
  }
498
- if (config.data.length > 0) return Object.keys(config.data[0]);
499
- return [];
556
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
500
557
  }, [config]);
501
- if (!config.data.length || !keys.length) {
558
+ if (!config.data.length || !columns.length) {
502
559
  return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
503
560
  }
504
561
  const formatCell = (val) => {
@@ -506,26 +563,26 @@ function DataTable({ config, viewportSize = "large" }) {
506
563
  if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
507
564
  return String(val);
508
565
  };
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(
566
+ 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
567
  "th",
511
568
  {
512
- key: k,
569
+ key: column.label,
513
570
  className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
514
571
  },
515
- k
572
+ column.label
516
573
  )))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
517
574
  "tr",
518
575
  {
519
576
  key: ri,
520
577
  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
578
  },
522
- keys.map((k) => /* @__PURE__ */ import_react5.default.createElement(
579
+ columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
523
580
  "td",
524
581
  {
525
- key: k,
526
- className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
582
+ key: column.label,
583
+ 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
584
  },
528
- formatCell(row[k])
585
+ formatCell(row[column.accessor])
529
586
  ))
530
587
  ))))), /* @__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
588
  }
@@ -739,7 +796,7 @@ ${match.trim()}
739
796
  });
740
797
  }
741
798
  if (hasStructuredUiBlock) {
742
- raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
799
+ raw = stripStructuredUiLabels(stripMarkdownTables(raw)).replace(/\n{3,}/g, "\n\n").trim();
743
800
  }
744
801
  if (isStreaming) {
745
802
  raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
@@ -853,6 +910,22 @@ ${match.trim()}
853
910
  );
854
911
  }
855
912
  }
913
+ if (!inline) {
914
+ const content = String(children != null ? children : "").trim();
915
+ if (looksLikeStructuredPayload(content)) {
916
+ return /* @__PURE__ */ import_react5.default.createElement(
917
+ UIDispatcher,
918
+ {
919
+ rawContent: content,
920
+ primaryColor,
921
+ accentColor,
922
+ isStreaming,
923
+ onAddToCart,
924
+ viewportSize
925
+ }
926
+ );
927
+ }
928
+ }
856
929
  if (!inline && lang === "table-loading") {
857
930
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-4 h-4 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 italic animate-pulse" }, "Generating data table..."));
858
931
  }
package/dist/index.mjs CHANGED
@@ -210,10 +210,19 @@ function DynamicChart({
210
210
  const isTiny = containerWidth > 0 && containerWidth < 300;
211
211
  const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
212
212
  const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
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)}%`;
213
+ const chartHeight = (() => {
214
+ if (type === "pie") {
215
+ if (containerWidth <= 0) return isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280;
216
+ return Math.max(180, Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280));
217
+ }
218
+ if (containerWidth <= 0) return isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300;
219
+ return Math.max(190, Math.min(Math.round(containerWidth * 0.7), isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300));
220
+ })();
221
+ const pieOuterRadius = (() => {
222
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
223
+ const squareSize = Math.min(containerWidth, chartHeight);
224
+ return Math.max(36, Math.floor(squareSize * 0.28));
225
+ })();
217
226
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
218
227
  const showLegend = !isTiny;
219
228
  const truncateAxisLabel = (value) => {
@@ -321,41 +330,58 @@ function DynamicChart({
321
330
  return row;
322
331
  };
323
332
  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(
325
- Pie,
333
+ return /* @__PURE__ */ React4.createElement(
334
+ "div",
326
335
  {
327
- data: sanitizedData,
328
- dataKey: pieDataKey,
329
- nameKey: finalXKey,
330
- cx: "50%",
331
- cy: "50%",
332
- outerRadius: pieOuterRadius,
333
- label: isTiny ? false : pieLabel
336
+ ref: containerRef,
337
+ className: "w-full min-w-0 mt-4 mb-2 select-none overflow-hidden",
338
+ style: { height: chartHeight }
334
339
  },
335
- 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" })))));
340
+ /* @__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(
341
+ Pie,
342
+ {
343
+ data: sanitizedData,
344
+ dataKey: pieDataKey,
345
+ nameKey: finalXKey,
346
+ cx: "50%",
347
+ cy: "50%",
348
+ outerRadius: pieOuterRadius,
349
+ label: false,
350
+ labelLine: false
351
+ },
352
+ sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
353
+ ), /* @__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" }))))
354
+ );
337
355
  }
338
- 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
- Bar,
340
- {
341
- key,
342
- dataKey: key,
343
- fill: colors[index % colors.length],
344
- radius: [4, 4, 0, 0],
345
- barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
346
- }
347
- ))) : /* @__PURE__ */ React4.createElement(LineChart, { 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) }), showLegend && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
348
- Line,
356
+ return /* @__PURE__ */ React4.createElement(
357
+ "div",
349
358
  {
350
- key,
351
- type: "monotone",
352
- dataKey: key,
353
- stroke: colors[index % colors.length],
354
- strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
355
- dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
356
- activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
357
- }
358
- )))));
359
+ ref: containerRef,
360
+ className: "w-full min-w-0 mt-4 mb-2 select-none overflow-hidden",
361
+ style: { height: chartHeight }
362
+ },
363
+ /* @__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(
364
+ Bar,
365
+ {
366
+ key,
367
+ dataKey: key,
368
+ fill: colors[index % colors.length],
369
+ radius: [4, 4, 0, 0],
370
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
371
+ }
372
+ ))) : /* @__PURE__ */ React4.createElement(LineChart, { 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) }), showLegend && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
373
+ Line,
374
+ {
375
+ key,
376
+ type: "monotone",
377
+ dataKey: key,
378
+ stroke: colors[index % colors.length],
379
+ strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
380
+ dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
381
+ activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
382
+ }
383
+ ))))
384
+ );
359
385
  }
360
386
 
361
387
  // src/components/MessageBubble.tsx
@@ -429,6 +455,15 @@ function stripMarkdownTables(raw) {
429
455
  "\n\n"
430
456
  );
431
457
  }
458
+ function stripStructuredUiLabels(raw) {
459
+ return raw.replace(
460
+ /(?:^|\n)\*\*(?:Chart Data|Table Data|Visualization|Visual Representation)\*\:\s*(?=\n|$)/gi,
461
+ "\n"
462
+ ).replace(
463
+ /(?:^|\n)(?:Chart Data|Table Data|Visualization|Visual Representation)\s*\:\s*(?=\n|$)/gi,
464
+ "\n"
465
+ );
466
+ }
432
467
  function resolveImage(data) {
433
468
  for (const key of ["image", "img", "thumbnail"]) {
434
469
  const v = data[key];
@@ -445,23 +480,45 @@ function normaliseChild(children) {
445
480
  }
446
481
  return children;
447
482
  }
483
+ function normaliseFieldName(value) {
484
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
485
+ }
486
+ function formatColumnLabel(value) {
487
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
488
+ }
448
489
  function DataTable({ config, viewportSize = "large" }) {
449
490
  const isCompact = viewportSize === "compact";
450
491
  const isMedium = viewportSize === "medium";
451
- const keys = React5.useMemo(() => {
492
+ const columns = React5.useMemo(() => {
493
+ const rowKeys = Array.from(
494
+ new Set(
495
+ config.data.flatMap((row) => Object.keys(row))
496
+ )
497
+ );
498
+ const findAccessor = (label) => {
499
+ const exact = rowKeys.find((key) => key === label);
500
+ if (exact) return exact;
501
+ const normalisedLabel = normaliseFieldName(label);
502
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
503
+ if (normalisedMatch) return normalisedMatch;
504
+ return label;
505
+ };
452
506
  if (Array.isArray(config.columns) && config.columns.length) {
453
- return config.columns;
507
+ return config.columns.map((label) => ({
508
+ label: formatColumnLabel(label),
509
+ accessor: findAccessor(label)
510
+ }));
454
511
  }
455
512
  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;
513
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
514
+ return keys.map((key) => ({
515
+ label: formatColumnLabel(key),
516
+ accessor: findAccessor(key)
517
+ }));
460
518
  }
461
- if (config.data.length > 0) return Object.keys(config.data[0]);
462
- return [];
519
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
463
520
  }, [config]);
464
- if (!config.data.length || !keys.length) {
521
+ if (!config.data.length || !columns.length) {
465
522
  return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
466
523
  }
467
524
  const formatCell = (val) => {
@@ -469,26 +526,26 @@ function DataTable({ config, viewportSize = "large" }) {
469
526
  if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
470
527
  return String(val);
471
528
  };
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(
529
+ 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
530
  "th",
474
531
  {
475
- key: k,
532
+ key: column.label,
476
533
  className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
477
534
  },
478
- k
535
+ column.label
479
536
  )))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
480
537
  "tr",
481
538
  {
482
539
  key: ri,
483
540
  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
541
  },
485
- keys.map((k) => /* @__PURE__ */ React5.createElement(
542
+ columns.map((column) => /* @__PURE__ */ React5.createElement(
486
543
  "td",
487
544
  {
488
- key: k,
489
- className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
545
+ key: column.label,
546
+ 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
547
  },
491
- formatCell(row[k])
548
+ formatCell(row[column.accessor])
492
549
  ))
493
550
  ))))), /* @__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
551
  }
@@ -702,7 +759,7 @@ ${match.trim()}
702
759
  });
703
760
  }
704
761
  if (hasStructuredUiBlock) {
705
- raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
762
+ raw = stripStructuredUiLabels(stripMarkdownTables(raw)).replace(/\n{3,}/g, "\n\n").trim();
706
763
  }
707
764
  if (isStreaming) {
708
765
  raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
@@ -816,6 +873,22 @@ ${match.trim()}
816
873
  );
817
874
  }
818
875
  }
876
+ if (!inline) {
877
+ const content = String(children != null ? children : "").trim();
878
+ if (looksLikeStructuredPayload(content)) {
879
+ return /* @__PURE__ */ React5.createElement(
880
+ UIDispatcher,
881
+ {
882
+ rawContent: content,
883
+ primaryColor,
884
+ accentColor,
885
+ isStreaming,
886
+ onAddToCart,
887
+ viewportSize
888
+ }
889
+ );
890
+ }
891
+ }
819
892
  if (!inline && lang === "table-loading") {
820
893
  return /* @__PURE__ */ React5.createElement("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic animate-pulse" }, "Generating data table..."));
821
894
  }
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.1",
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",
@@ -57,27 +57,20 @@ export function DynamicChart({
57
57
  const isTiny = containerWidth > 0 && containerWidth < 300;
58
58
  const isCompact = viewportSize === 'compact' || (containerWidth > 0 && containerWidth < 420);
59
59
  const isMedium = !isCompact && (viewportSize === 'medium' || (containerWidth > 0 && containerWidth < 640));
60
- const chartHeightClass = isTiny
61
- ? 'h-48 min-h-[180px]'
62
- : isCompact
63
- ? 'h-56 min-h-[220px]'
64
- : isMedium
65
- ? 'h-64 min-h-[250px]'
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)}%`;
60
+ const chartHeight = (() => {
61
+ if (type === 'pie') {
62
+ if (containerWidth <= 0) return isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280;
63
+ return Math.max(180, Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280));
64
+ }
65
+
66
+ if (containerWidth <= 0) return isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300;
67
+ return Math.max(190, Math.min(Math.round(containerWidth * 0.7), isTiny ? 190 : isCompact ? 230 : isMedium ? 260 : 300));
68
+ })();
69
+ const pieOuterRadius = (() => {
70
+ if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
71
+ const squareSize = Math.min(containerWidth, chartHeight);
72
+ return Math.max(36, Math.floor(squareSize * 0.28));
73
+ })();
81
74
  const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
82
75
  const showLegend = !isTiny;
83
76
  const truncateAxisLabel = (value: string | number) => {
@@ -241,8 +234,12 @@ export function DynamicChart({
241
234
  // Handle Pie Chart separately as it has a different structure
242
235
  if (type === 'pie') {
243
236
  return (
244
- <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}`}>
237
+ <div
238
+ ref={containerRef}
239
+ className="w-full min-w-0 mt-4 mb-2 select-none overflow-hidden"
240
+ style={{ height: chartHeight }}
241
+ >
242
+ <div className="mx-auto h-full aspect-square max-w-full">
246
243
  <ResponsiveContainer width="100%" height="100%">
247
244
  <PieChart>
248
245
  <Pie
@@ -252,14 +249,15 @@ export function DynamicChart({
252
249
  cx="50%"
253
250
  cy="50%"
254
251
  outerRadius={pieOuterRadius}
255
- label={isTiny ? false : pieLabel}
252
+ label={false}
253
+ labelLine={false}
256
254
  >
257
255
  {sanitizedData.map((entry, index) => (
258
256
  <Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
259
257
  ))}
260
258
  </Pie>
261
259
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
262
- {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} verticalAlign="bottom" />}
260
+ {showLegend && !isCompact && <Legend wrapperStyle={{ fontSize: 12 }} verticalAlign="bottom" />}
263
261
  </PieChart>
264
262
  </ResponsiveContainer>
265
263
  </div>
@@ -269,7 +267,11 @@ export function DynamicChart({
269
267
 
270
268
  // Bar and Line charts
271
269
  return (
272
- <div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
270
+ <div
271
+ ref={containerRef}
272
+ className="w-full min-w-0 mt-4 mb-2 select-none overflow-hidden"
273
+ style={{ height: chartHeight }}
274
+ >
273
275
  <ResponsiveContainer width="100%" height="100%">
274
276
  {type === 'bar' ? (
275
277
  <BarChart data={sanitizedData} margin={chartMargin}>
@@ -113,6 +113,16 @@ function stripMarkdownTables(raw: string): string {
113
113
  );
114
114
  }
115
115
 
116
+ function stripStructuredUiLabels(raw: string): string {
117
+ return raw.replace(
118
+ /(?:^|\n)\*\*(?:Chart Data|Table Data|Visualization|Visual Representation)\*\:\s*(?=\n|$)/gi,
119
+ '\n',
120
+ ).replace(
121
+ /(?:^|\n)(?:Chart Data|Table Data|Visualization|Visual Representation)\s*\:\s*(?=\n|$)/gi,
122
+ '\n',
123
+ );
124
+ }
125
+
116
126
  // ─── Tiny helpers ─────────────────────────────────────────────────────────────
117
127
 
118
128
  function resolveImage(data: Record<string, unknown>): string | undefined {
@@ -139,6 +149,19 @@ function normaliseChild(children: React.ReactNode): React.ReactNode {
139
149
  return children;
140
150
  }
141
151
 
152
+ function normaliseFieldName(value: string): string {
153
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
154
+ }
155
+
156
+ function formatColumnLabel(value: string): string {
157
+ return value
158
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
159
+ .replace(/[_-]+/g, ' ')
160
+ .replace(/\s+/g, ' ')
161
+ .trim()
162
+ .replace(/\b\w/g, (char) => char.toUpperCase());
163
+ }
164
+
142
165
  // ─── Inline data table renderer ───────────────────────────────────────────────
143
166
  // Handles chart configs where type === "table". The LLM may emit any of:
144
167
  // columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
@@ -156,24 +179,46 @@ interface TableConfig {
156
179
  function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; viewportSize?: ChatViewportSize }) {
157
180
  const isCompact = viewportSize === 'compact';
158
181
  const isMedium = viewportSize === 'medium';
159
- const keys = React.useMemo<string[]>(() => {
160
- // Priority 1: explicit columns list
182
+ const columns = React.useMemo<Array<{ label: string; accessor: string }>>(() => {
183
+ const rowKeys = Array.from(
184
+ new Set(
185
+ config.data.flatMap((row) => Object.keys(row)),
186
+ ),
187
+ );
188
+
189
+ const findAccessor = (label: string) => {
190
+ const exact = rowKeys.find((key) => key === label);
191
+ if (exact) return exact;
192
+
193
+ const normalisedLabel = normaliseFieldName(label);
194
+ const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
195
+ if (normalisedMatch) return normalisedMatch;
196
+
197
+ return label;
198
+ };
199
+
161
200
  if (Array.isArray(config.columns) && config.columns.length) {
162
- return config.columns;
201
+ return config.columns.map((label) => ({
202
+ label: formatColumnLabel(label),
203
+ accessor: findAccessor(label),
204
+ }));
163
205
  }
164
- // Priority 2: dataKeys, with xAxisKey prepended if not already present
206
+
165
207
  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;
208
+ const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)
209
+ ? [config.xAxisKey, ...config.dataKeys]
210
+ : config.dataKeys;
211
+
212
+ return keys.map((key) => ({
213
+ label: formatColumnLabel(key),
214
+ accessor: findAccessor(key),
215
+ }));
170
216
  }
171
- // Priority 3: auto-detect from first data row
172
- if (config.data.length > 0) return Object.keys(config.data[0]);
173
- return [];
217
+
218
+ return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
174
219
  }, [config]);
175
220
 
176
- if (!config.data.length || !keys.length) {
221
+ if (!config.data.length || !columns.length) {
177
222
  return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
178
223
  }
179
224
 
@@ -197,12 +242,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
197
242
  <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
243
  <thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
199
244
  <tr>
200
- {keys.map(k => (
245
+ {columns.map((column) => (
201
246
  <th
202
- key={k}
247
+ key={column.label}
203
248
  className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`}
204
249
  >
205
- {k}
250
+ {column.label}
206
251
  </th>
207
252
  ))}
208
253
  </tr>
@@ -213,12 +258,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
213
258
  key={ri}
214
259
  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
260
  >
216
- {keys.map(k => (
261
+ {columns.map((column) => (
217
262
  <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`}
263
+ key={column.label}
264
+ 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
265
  >
221
- {formatCell(row[k])}
266
+ {formatCell(row[column.accessor])}
222
267
  </td>
223
268
  ))}
224
269
  </tr>
@@ -564,7 +609,7 @@ export function MessageBubble({
564
609
  // 2b. If the model already provided a structured UI block, suppress duplicated
565
610
  // markdown tables that often follow as a fallback explanation.
566
611
  if (hasStructuredUiBlock) {
567
- raw = stripMarkdownTables(raw)
612
+ raw = stripStructuredUiLabels(stripMarkdownTables(raw))
568
613
  .replace(/\n{3,}/g, '\n\n')
569
614
  .trim();
570
615
  }
@@ -675,6 +720,22 @@ export function MessageBubble({
675
720
  }
676
721
  }
677
722
 
723
+ if (!inline) {
724
+ const content = String(children ?? '').trim();
725
+ if (looksLikeStructuredPayload(content)) {
726
+ return (
727
+ <UIDispatcher
728
+ rawContent={content}
729
+ primaryColor={primaryColor}
730
+ accentColor={accentColor}
731
+ isStreaming={isStreaming}
732
+ onAddToCart={onAddToCart}
733
+ viewportSize={viewportSize}
734
+ />
735
+ );
736
+ }
737
+ }
738
+
678
739
  if (!inline && lang === 'table-loading') {
679
740
  return (
680
741
  <div className="my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none">
@@ -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.