@retrivora-ai/rag-engine 1.5.6 → 1.5.8

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
@@ -228,12 +228,36 @@ function DynamicChart({
228
228
  }) {
229
229
  const { type, data } = config;
230
230
  const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
231
- const isCompact = viewportSize === "compact";
232
- const isMedium = viewportSize === "medium";
233
- const chartHeightClass = isCompact ? "h-56 min-h-[220px]" : isMedium ? "h-64 min-h-[250px]" : "h-72 min-h-[280px]";
234
- const pieOuterRadius = isCompact ? 56 : isMedium ? 68 : 80;
235
- const pieLabel = ({ name, percent }) => isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
236
- const axisTick = { fontSize: isCompact ? 10 : 12, fill: "#64748b" };
231
+ const containerRef = import_react4.default.useRef(null);
232
+ const [containerWidth, setContainerWidth] = import_react4.default.useState(0);
233
+ import_react4.default.useEffect(() => {
234
+ const element = containerRef.current;
235
+ if (!element || typeof ResizeObserver === "undefined") return;
236
+ const updateWidth = (width) => {
237
+ setContainerWidth(Math.round(width));
238
+ };
239
+ const observer = new ResizeObserver(([entry]) => {
240
+ if (!entry) return;
241
+ updateWidth(entry.contentRect.width);
242
+ });
243
+ updateWidth(element.getBoundingClientRect().width);
244
+ observer.observe(element);
245
+ return () => observer.disconnect();
246
+ }, []);
247
+ const isTiny = containerWidth > 0 && containerWidth < 300;
248
+ const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
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 pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
252
+ const pieLabel = ({ name, percent }) => isTiny ? "" : isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
253
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
254
+ const showLegend = !isTiny;
255
+ const truncateAxisLabel = (value) => {
256
+ const text = String(value);
257
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
258
+ return text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
259
+ };
260
+ const chartMargin = isTiny ? { top: 8, right: 4, left: -28, bottom: 0 } : isCompact ? { top: 10, right: 6, left: -24, bottom: 0 } : { top: 10, right: 10, left: -20, bottom: 0 };
237
261
  const sanitizedData = import_react4.default.useMemo(() => {
238
262
  if (!data) return [];
239
263
  return data.map((item) => {
@@ -333,7 +357,7 @@ function DynamicChart({
333
357
  return row;
334
358
  };
335
359
  if (type === "pie") {
336
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: `w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "99%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.PieChart, null, /* @__PURE__ */ import_react4.default.createElement(
360
+ 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%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.PieChart, null, /* @__PURE__ */ import_react4.default.createElement(
337
361
  import_recharts.Pie,
338
362
  {
339
363
  data: sanitizedData,
@@ -342,30 +366,30 @@ function DynamicChart({
342
366
  cx: "50%",
343
367
  cy: "50%",
344
368
  outerRadius: pieOuterRadius,
345
- label: pieLabel
369
+ label: isTiny ? false : pieLabel
346
370
  },
347
371
  sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
348
- ), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }))));
372
+ ), /* @__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 } }))));
349
373
  }
350
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: `w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__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" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
374
+ 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(
351
375
  import_recharts.Bar,
352
376
  {
353
377
  key,
354
378
  dataKey: key,
355
379
  fill: colors[index % colors.length],
356
380
  radius: [4, 4, 0, 0],
357
- barSize: isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
381
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
358
382
  }
359
- ))) : /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__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) }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
383
+ ))) : /* @__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(
360
384
  import_recharts.Line,
361
385
  {
362
386
  key,
363
387
  type: "monotone",
364
388
  dataKey: key,
365
389
  stroke: colors[index % colors.length],
366
- strokeWidth: isCompact ? 2 : 3,
367
- dot: { r: isCompact ? 3 : 4, strokeWidth: 2 },
368
- activeDot: { r: isCompact ? 5 : 6 }
390
+ strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
391
+ dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
392
+ activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
369
393
  }
370
394
  )))));
371
395
  }
@@ -432,6 +456,9 @@ function extractLikelyJsonBlock(raw) {
432
456
  if (starts.length === 0) return trimmed;
433
457
  return trimmed.slice(Math.min(...starts));
434
458
  }
459
+ function looksLikeStructuredPayload(raw) {
460
+ return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
461
+ }
435
462
  function resolveImage(data) {
436
463
  for (const key of ["image", "img", "thumbnail"]) {
437
464
  const v = data[key];
@@ -511,6 +538,14 @@ function normalizeTabularRows(rows, columns) {
511
538
  return { value: row };
512
539
  }).filter((row) => Object.keys(row).length > 0);
513
540
  }
541
+ function resolveStructuredRows(config) {
542
+ if (Array.isArray(config.data) && config.data.length > 0) return config.data;
543
+ if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
544
+ if (Array.isArray(config.items) && config.items.length > 0) return config.items;
545
+ if (Array.isArray(config.products) && config.products.length > 0) return config.products;
546
+ if (Array.isArray(config.results) && config.results.length > 0) return config.results;
547
+ return [];
548
+ }
514
549
  function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
515
550
  var _a;
516
551
  const result = import_react5.default.useMemo(() => {
@@ -533,8 +568,9 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
533
568
  config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
534
569
  }
535
570
  }
571
+ const resolvedRows = resolveStructuredRows(config2);
536
572
  const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
537
- data: normalizeTabularRows(config2.data, config2.columns)
573
+ data: normalizeTabularRows(resolvedRows, config2.columns)
538
574
  });
539
575
  return { config: normalizedConfig };
540
576
  } catch (err) {
@@ -681,9 +717,10 @@ function MessageBubble({
681
717
  let raw = cleanContent || message.content;
682
718
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
683
719
  if (!isStreaming) {
684
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
720
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
685
721
  if (match.includes("```")) return match;
686
- const type = match.includes('"products"') ? "json" : "chart";
722
+ if (!looksLikeStructuredPayload(match)) return match;
723
+ const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
687
724
  return `
688
725
 
689
726
  \`\`\`${type}
@@ -791,7 +828,7 @@ ${match.trim()}
791
828
  }
792
829
  if (!inline && lang === "json") {
793
830
  const content = String(children != null ? children : "").trim();
794
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
831
+ if (looksLikeStructuredPayload(content)) {
795
832
  return /* @__PURE__ */ import_react5.default.createElement(
796
833
  UIDispatcher,
797
834
  {
package/dist/index.mjs CHANGED
@@ -191,12 +191,36 @@ function DynamicChart({
191
191
  }) {
192
192
  const { type, data } = config;
193
193
  const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
194
- const isCompact = viewportSize === "compact";
195
- const isMedium = viewportSize === "medium";
196
- const chartHeightClass = isCompact ? "h-56 min-h-[220px]" : isMedium ? "h-64 min-h-[250px]" : "h-72 min-h-[280px]";
197
- const pieOuterRadius = isCompact ? 56 : isMedium ? 68 : 80;
198
- const pieLabel = ({ name, percent }) => isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
199
- const axisTick = { fontSize: isCompact ? 10 : 12, fill: "#64748b" };
194
+ const containerRef = React4.useRef(null);
195
+ const [containerWidth, setContainerWidth] = React4.useState(0);
196
+ React4.useEffect(() => {
197
+ const element = containerRef.current;
198
+ if (!element || typeof ResizeObserver === "undefined") return;
199
+ const updateWidth = (width) => {
200
+ setContainerWidth(Math.round(width));
201
+ };
202
+ const observer = new ResizeObserver(([entry]) => {
203
+ if (!entry) return;
204
+ updateWidth(entry.contentRect.width);
205
+ });
206
+ updateWidth(element.getBoundingClientRect().width);
207
+ observer.observe(element);
208
+ return () => observer.disconnect();
209
+ }, []);
210
+ const isTiny = containerWidth > 0 && containerWidth < 300;
211
+ const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
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 pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
215
+ const pieLabel = ({ name, percent }) => isTiny ? "" : isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
216
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
217
+ const showLegend = !isTiny;
218
+ const truncateAxisLabel = (value) => {
219
+ const text = String(value);
220
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
221
+ return text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
222
+ };
223
+ const chartMargin = isTiny ? { top: 8, right: 4, left: -28, bottom: 0 } : isCompact ? { top: 10, right: 6, left: -24, bottom: 0 } : { top: 10, right: 10, left: -20, bottom: 0 };
200
224
  const sanitizedData = React4.useMemo(() => {
201
225
  if (!data) return [];
202
226
  return data.map((item) => {
@@ -296,7 +320,7 @@ function DynamicChart({
296
320
  return row;
297
321
  };
298
322
  if (type === "pie") {
299
- return /* @__PURE__ */ React4.createElement("div", { className: `w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "99%", height: "100%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
323
+ 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%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
300
324
  Pie,
301
325
  {
302
326
  data: sanitizedData,
@@ -305,30 +329,30 @@ function DynamicChart({
305
329
  cx: "50%",
306
330
  cy: "50%",
307
331
  outerRadius: pieOuterRadius,
308
- label: pieLabel
332
+ label: isTiny ? false : pieLabel
309
333
  },
310
334
  sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
311
- ), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }))));
335
+ ), /* @__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 } }))));
312
336
  }
313
- return /* @__PURE__ */ React4.createElement("div", { className: `w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden` }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ React4.createElement(BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: axisTick, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__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" } }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
337
+ 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(
314
338
  Bar,
315
339
  {
316
340
  key,
317
341
  dataKey: key,
318
342
  fill: colors[index % colors.length],
319
343
  radius: [4, 4, 0, 0],
320
- barSize: isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
344
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
321
345
  }
322
- ))) : /* @__PURE__ */ React4.createElement(LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: axisTick, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__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) }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
346
+ ))) : /* @__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(
323
347
  Line,
324
348
  {
325
349
  key,
326
350
  type: "monotone",
327
351
  dataKey: key,
328
352
  stroke: colors[index % colors.length],
329
- strokeWidth: isCompact ? 2 : 3,
330
- dot: { r: isCompact ? 3 : 4, strokeWidth: 2 },
331
- activeDot: { r: isCompact ? 5 : 6 }
353
+ strokeWidth: isTiny ? 1.75 : isCompact ? 2 : 3,
354
+ dot: { r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 },
355
+ activeDot: { r: isTiny ? 4 : isCompact ? 5 : 6 }
332
356
  }
333
357
  )))));
334
358
  }
@@ -395,6 +419,9 @@ function extractLikelyJsonBlock(raw) {
395
419
  if (starts.length === 0) return trimmed;
396
420
  return trimmed.slice(Math.min(...starts));
397
421
  }
422
+ function looksLikeStructuredPayload(raw) {
423
+ return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
424
+ }
398
425
  function resolveImage(data) {
399
426
  for (const key of ["image", "img", "thumbnail"]) {
400
427
  const v = data[key];
@@ -474,6 +501,14 @@ function normalizeTabularRows(rows, columns) {
474
501
  return { value: row };
475
502
  }).filter((row) => Object.keys(row).length > 0);
476
503
  }
504
+ function resolveStructuredRows(config) {
505
+ if (Array.isArray(config.data) && config.data.length > 0) return config.data;
506
+ if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
507
+ if (Array.isArray(config.items) && config.items.length > 0) return config.items;
508
+ if (Array.isArray(config.products) && config.products.length > 0) return config.products;
509
+ if (Array.isArray(config.results) && config.results.length > 0) return config.results;
510
+ return [];
511
+ }
477
512
  function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
478
513
  var _a;
479
514
  const result = React5.useMemo(() => {
@@ -496,8 +531,9 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
496
531
  config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
497
532
  }
498
533
  }
534
+ const resolvedRows = resolveStructuredRows(config2);
499
535
  const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
500
- data: normalizeTabularRows(config2.data, config2.columns)
536
+ data: normalizeTabularRows(resolvedRows, config2.columns)
501
537
  });
502
538
  return { config: normalizedConfig };
503
539
  } catch (err) {
@@ -644,9 +680,10 @@ function MessageBubble({
644
680
  let raw = cleanContent || message.content;
645
681
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
646
682
  if (!isStreaming) {
647
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
683
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
648
684
  if (match.includes("```")) return match;
649
- const type = match.includes('"products"') ? "json" : "chart";
685
+ if (!looksLikeStructuredPayload(match)) return match;
686
+ const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
650
687
  return `
651
688
 
652
689
  \`\`\`${type}
@@ -754,7 +791,7 @@ ${match.trim()}
754
791
  }
755
792
  if (!inline && lang === "json") {
756
793
  const content = String(children != null ? children : "").trim();
757
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
794
+ if (looksLikeStructuredPayload(content)) {
758
795
  return /* @__PURE__ */ React5.createElement(
759
796
  UIDispatcher,
760
797
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
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",
@@ -32,15 +32,57 @@ export function DynamicChart({
32
32
  }: DynamicChartProps) {
33
33
  const { type, data } = config;
34
34
  const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
35
- const isCompact = viewportSize === 'compact';
36
- const isMedium = viewportSize === 'medium';
37
- const chartHeightClass = isCompact ? 'h-56 min-h-[220px]' : isMedium ? 'h-64 min-h-[250px]' : 'h-72 min-h-[280px]';
38
- const pieOuterRadius = isCompact ? 56 : isMedium ? 68 : 80;
35
+ const containerRef = React.useRef<HTMLDivElement>(null);
36
+ const [containerWidth, setContainerWidth] = React.useState(0);
37
+
38
+ React.useEffect(() => {
39
+ const element = containerRef.current;
40
+ if (!element || typeof ResizeObserver === 'undefined') return;
41
+
42
+ const updateWidth = (width: number) => {
43
+ setContainerWidth(Math.round(width));
44
+ };
45
+
46
+ const observer = new ResizeObserver(([entry]) => {
47
+ if (!entry) return;
48
+ updateWidth(entry.contentRect.width);
49
+ });
50
+
51
+ updateWidth(element.getBoundingClientRect().width);
52
+ observer.observe(element);
53
+
54
+ return () => observer.disconnect();
55
+ }, []);
56
+
57
+ const isTiny = containerWidth > 0 && containerWidth < 300;
58
+ const isCompact = viewportSize === 'compact' || (containerWidth > 0 && containerWidth < 420);
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 pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
39
68
  const pieLabel = ({ name, percent }: { name?: string; percent?: number }) =>
40
- isCompact
69
+ isTiny
70
+ ? ''
71
+ : isCompact
41
72
  ? `${name}`
42
73
  : `${name} ${((percent ?? 0) * 100).toFixed(0)}%`;
43
- const axisTick = { fontSize: isCompact ? 10 : 12, fill: '#64748b' };
74
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
75
+ const showLegend = !isTiny;
76
+ const truncateAxisLabel = (value: string | number) => {
77
+ const text = String(value);
78
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
79
+ return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
80
+ };
81
+ const chartMargin = isTiny
82
+ ? { top: 8, right: 4, left: -28, bottom: 0 }
83
+ : isCompact
84
+ ? { top: 10, right: 6, left: -24, bottom: 0 }
85
+ : { top: 10, right: 10, left: -20, bottom: 0 };
44
86
 
45
87
  // CRITICAL: Sanitize the data array to remove any non-primitive values (objects/arrays)
46
88
  // This prevents the "Objects are not valid as a React child" error in Tooltips/Legends
@@ -192,8 +234,8 @@ export function DynamicChart({
192
234
  // Handle Pie Chart separately as it has a different structure
193
235
  if (type === 'pie') {
194
236
  return (
195
- <div className={`w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
196
- <ResponsiveContainer width="99%" height="100%">
237
+ <div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
238
+ <ResponsiveContainer width="100%" height="100%">
197
239
  <PieChart>
198
240
  <Pie
199
241
  data={sanitizedData}
@@ -202,14 +244,14 @@ export function DynamicChart({
202
244
  cx="50%"
203
245
  cy="50%"
204
246
  outerRadius={pieOuterRadius}
205
- label={pieLabel}
247
+ label={isTiny ? false : pieLabel}
206
248
  >
207
249
  {sanitizedData.map((entry, index) => (
208
250
  <Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
209
251
  ))}
210
252
  </Pie>
211
253
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
212
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
254
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />}
213
255
  </PieChart>
214
256
  </ResponsiveContainer>
215
257
  </div>
@@ -218,41 +260,41 @@ export function DynamicChart({
218
260
 
219
261
  // Bar and Line charts
220
262
  return (
221
- <div className={`w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
222
- <ResponsiveContainer width="99%" height="100%">
263
+ <div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
264
+ <ResponsiveContainer width="100%" height="100%">
223
265
  {type === 'bar' ? (
224
- <BarChart data={sanitizedData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
266
+ <BarChart data={sanitizedData} margin={chartMargin}>
225
267
  <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
226
- <XAxis dataKey={finalXKey} tick={axisTick} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
268
+ <XAxis dataKey={finalXKey} tick={axisTick} tickFormatter={truncateAxisLabel} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} interval={0} minTickGap={isTiny ? 2 : isCompact ? 6 : 12} />
227
269
  <YAxis tick={axisTick} tickLine={false} axisLine={false} />
228
270
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} cursor={{ fill: '#f1f5f9' }} />
229
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
271
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />}
230
272
  {finalDataKeys.map((key, index) => (
231
273
  <Bar
232
274
  key={key}
233
275
  dataKey={key}
234
276
  fill={colors[index % colors.length]}
235
277
  radius={[4, 4, 0, 0]}
236
- barSize={isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)}
278
+ barSize={isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)}
237
279
  />
238
280
  ))}
239
281
  </BarChart>
240
282
  ) : (
241
- <LineChart data={sanitizedData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
283
+ <LineChart data={sanitizedData} margin={chartMargin}>
242
284
  <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
243
- <XAxis dataKey={finalXKey} tick={axisTick} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
285
+ <XAxis dataKey={finalXKey} tick={axisTick} tickFormatter={truncateAxisLabel} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} interval={0} minTickGap={isTiny ? 2 : isCompact ? 6 : 12} />
244
286
  <YAxis tick={axisTick} tickLine={false} axisLine={false} />
245
287
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
246
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
288
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />}
247
289
  {finalDataKeys.map((key, index) => (
248
290
  <Line
249
291
  key={key}
250
292
  type="monotone"
251
293
  dataKey={key}
252
294
  stroke={colors[index % colors.length]}
253
- strokeWidth={isCompact ? 2 : 3}
254
- dot={{ r: isCompact ? 3 : 4, strokeWidth: 2 }}
255
- activeDot={{ r: isCompact ? 5 : 6 }}
295
+ strokeWidth={isTiny ? 1.75 : isCompact ? 2 : 3}
296
+ dot={{ r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 }}
297
+ activeDot={{ r: isTiny ? 4 : isCompact ? 5 : 6 }}
256
298
  />
257
299
  ))}
258
300
  </LineChart>
@@ -102,6 +102,10 @@ function extractLikelyJsonBlock(raw: string): string {
102
102
  return trimmed.slice(Math.min(...starts));
103
103
  }
104
104
 
105
+ function looksLikeStructuredPayload(raw: string): boolean {
106
+ return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
107
+ }
108
+
105
109
  // ─── Tiny helpers ─────────────────────────────────────────────────────────────
106
110
 
107
111
  function resolveImage(data: Record<string, unknown>): string | undefined {
@@ -241,10 +245,17 @@ interface UIConfig {
241
245
  insights?: string[];
242
246
  type?: string;
243
247
  items?: Record<string, unknown>[];
248
+ rows?: Record<string, unknown>[];
249
+ products?: Record<string, unknown>[];
250
+ results?: Record<string, unknown>[];
244
251
  }
245
252
 
246
- interface RawUIConfig extends Omit<UIConfig, 'data'> {
253
+ interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results'> {
247
254
  data: Array<Record<string, unknown> | unknown[]>;
255
+ items?: Array<Record<string, unknown> | unknown[]>;
256
+ rows?: Array<Record<string, unknown> | unknown[]>;
257
+ products?: Array<Record<string, unknown> | unknown[]>;
258
+ results?: Array<Record<string, unknown> | unknown[]>;
248
259
  }
249
260
 
250
261
  function normalizeTabularRows(
@@ -275,6 +286,15 @@ function normalizeTabularRows(
275
286
  .filter((row) => Object.keys(row).length > 0);
276
287
  }
277
288
 
289
+ function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unknown>) {
290
+ if (Array.isArray(config.data) && config.data.length > 0) return config.data;
291
+ if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
292
+ if (Array.isArray(config.items) && config.items.length > 0) return config.items;
293
+ if (Array.isArray(config.products) && config.products.length > 0) return config.products;
294
+ if (Array.isArray(config.results) && config.results.length > 0) return config.results;
295
+ return [];
296
+ }
297
+
278
298
  function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = 'large' }: {
279
299
  rawContent: string;
280
300
  primaryColor?: string;
@@ -317,9 +337,10 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
317
337
  }
318
338
  }
319
339
 
340
+ const resolvedRows = resolveStructuredRows(config);
320
341
  const normalizedConfig: UIConfig = {
321
342
  ...(config as Omit<UIConfig, 'data'>),
322
- data: normalizeTabularRows(config.data, config.columns),
343
+ data: normalizeTabularRows(resolvedRows, config.columns),
323
344
  };
324
345
 
325
346
  return { config: normalizedConfig };
@@ -521,10 +542,13 @@ export function MessageBubble({
521
542
  // This handles models (like Llama 3.2) that often omit the ```chart markers.
522
543
  if (!isStreaming) {
523
544
  // Look for objects that look like chart or product data
524
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
545
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
525
546
  // Only wrap if not already in a code block
526
547
  if (match.includes('```')) return match;
527
- const type = match.includes('"products"') ? 'json' : 'chart';
548
+ if (!looksLikeStructuredPayload(match)) return match;
549
+ const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"')
550
+ ? 'ui'
551
+ : 'json';
528
552
  return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
529
553
  });
530
554
  }
@@ -621,7 +645,7 @@ export function MessageBubble({
621
645
 
622
646
  if (!inline && lang === 'json') {
623
647
  const content = String(children ?? '').trim();
624
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
648
+ if (looksLikeStructuredPayload(content)) {
625
649
  return (
626
650
  <UIDispatcher
627
651
  rawContent={content}