@retrivora-ai/rag-engine 1.5.7 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -228,12 +228,37 @@ 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 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)}%`;
254
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
255
+ const showLegend = !isTiny;
256
+ const truncateAxisLabel = (value) => {
257
+ const text = String(value);
258
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
259
+ return text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
260
+ };
261
+ 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
262
  const sanitizedData = import_react4.default.useMemo(() => {
238
263
  if (!data) return [];
239
264
  return data.map((item) => {
@@ -333,7 +358,7 @@ function DynamicChart({
333
358
  return row;
334
359
  };
335
360
  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(
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(
337
362
  import_recharts.Pie,
338
363
  {
339
364
  data: sanitizedData,
@@ -342,30 +367,30 @@ function DynamicChart({
342
367
  cx: "50%",
343
368
  cy: "50%",
344
369
  outerRadius: pieOuterRadius,
345
- label: pieLabel
370
+ label: isTiny ? false : pieLabel
346
371
  },
347
372
  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 } }))));
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" })))));
349
374
  }
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(
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(
351
376
  import_recharts.Bar,
352
377
  {
353
378
  key,
354
379
  dataKey: key,
355
380
  fill: colors[index % colors.length],
356
381
  radius: [4, 4, 0, 0],
357
- barSize: isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
382
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
358
383
  }
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(
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(
360
385
  import_recharts.Line,
361
386
  {
362
387
  key,
363
388
  type: "monotone",
364
389
  dataKey: key,
365
390
  stroke: colors[index % colors.length],
366
- strokeWidth: isCompact ? 2 : 3,
367
- dot: { r: isCompact ? 3 : 4, strokeWidth: 2 },
368
- activeDot: { r: isCompact ? 5 : 6 }
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 }
369
394
  }
370
395
  )))));
371
396
  }
@@ -432,6 +457,15 @@ function extractLikelyJsonBlock(raw) {
432
457
  if (starts.length === 0) return trimmed;
433
458
  return trimmed.slice(Math.min(...starts));
434
459
  }
460
+ function looksLikeStructuredPayload(raw) {
461
+ return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
462
+ }
463
+ function stripMarkdownTables(raw) {
464
+ return raw.replace(
465
+ /(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
466
+ "\n\n"
467
+ );
468
+ }
435
469
  function resolveImage(data) {
436
470
  for (const key of ["image", "img", "thumbnail"]) {
437
471
  const v = data[key];
@@ -688,11 +722,13 @@ function MessageBubble({
688
722
  }, [productsFromSources, productsFromContent, message.content]);
689
723
  const processedMarkdown = import_react5.default.useMemo(() => {
690
724
  let raw = cleanContent || message.content;
725
+ const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
691
726
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
692
727
  if (!isStreaming) {
693
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
728
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
694
729
  if (match.includes("```")) return match;
695
- const type = match.includes('"products"') ? "json" : "chart";
730
+ if (!looksLikeStructuredPayload(match)) return match;
731
+ 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";
696
732
  return `
697
733
 
698
734
  \`\`\`${type}
@@ -702,6 +738,9 @@ ${match.trim()}
702
738
  `;
703
739
  });
704
740
  }
741
+ if (hasStructuredUiBlock) {
742
+ raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
743
+ }
705
744
  if (isStreaming) {
706
745
  raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
707
746
  return `
@@ -800,7 +839,7 @@ ${match.trim()}
800
839
  }
801
840
  if (!inline && lang === "json") {
802
841
  const content = String(children != null ? children : "").trim();
803
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
842
+ if (looksLikeStructuredPayload(content)) {
804
843
  return /* @__PURE__ */ import_react5.default.createElement(
805
844
  UIDispatcher,
806
845
  {
package/dist/index.mjs CHANGED
@@ -191,12 +191,37 @@ 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 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)}%`;
217
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
218
+ const showLegend = !isTiny;
219
+ const truncateAxisLabel = (value) => {
220
+ const text = String(value);
221
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
222
+ return text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
223
+ };
224
+ 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
225
  const sanitizedData = React4.useMemo(() => {
201
226
  if (!data) return [];
202
227
  return data.map((item) => {
@@ -296,7 +321,7 @@ function DynamicChart({
296
321
  return row;
297
322
  };
298
323
  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(
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(
300
325
  Pie,
301
326
  {
302
327
  data: sanitizedData,
@@ -305,30 +330,30 @@ function DynamicChart({
305
330
  cx: "50%",
306
331
  cy: "50%",
307
332
  outerRadius: pieOuterRadius,
308
- label: pieLabel
333
+ label: isTiny ? false : pieLabel
309
334
  },
310
335
  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 } }))));
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" })))));
312
337
  }
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(
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(
314
339
  Bar,
315
340
  {
316
341
  key,
317
342
  dataKey: key,
318
343
  fill: colors[index % colors.length],
319
344
  radius: [4, 4, 0, 0],
320
- barSize: isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
345
+ barSize: isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
321
346
  }
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(
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(
323
348
  Line,
324
349
  {
325
350
  key,
326
351
  type: "monotone",
327
352
  dataKey: key,
328
353
  stroke: colors[index % colors.length],
329
- strokeWidth: isCompact ? 2 : 3,
330
- dot: { r: isCompact ? 3 : 4, strokeWidth: 2 },
331
- activeDot: { r: isCompact ? 5 : 6 }
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 }
332
357
  }
333
358
  )))));
334
359
  }
@@ -395,6 +420,15 @@ function extractLikelyJsonBlock(raw) {
395
420
  if (starts.length === 0) return trimmed;
396
421
  return trimmed.slice(Math.min(...starts));
397
422
  }
423
+ function looksLikeStructuredPayload(raw) {
424
+ return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
425
+ }
426
+ function stripMarkdownTables(raw) {
427
+ return raw.replace(
428
+ /(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
429
+ "\n\n"
430
+ );
431
+ }
398
432
  function resolveImage(data) {
399
433
  for (const key of ["image", "img", "thumbnail"]) {
400
434
  const v = data[key];
@@ -651,11 +685,13 @@ function MessageBubble({
651
685
  }, [productsFromSources, productsFromContent, message.content]);
652
686
  const processedMarkdown = React5.useMemo(() => {
653
687
  let raw = cleanContent || message.content;
688
+ const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
654
689
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
655
690
  if (!isStreaming) {
656
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
691
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
657
692
  if (match.includes("```")) return match;
658
- const type = match.includes('"products"') ? "json" : "chart";
693
+ if (!looksLikeStructuredPayload(match)) return match;
694
+ 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";
659
695
  return `
660
696
 
661
697
  \`\`\`${type}
@@ -665,6 +701,9 @@ ${match.trim()}
665
701
  `;
666
702
  });
667
703
  }
704
+ if (hasStructuredUiBlock) {
705
+ raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
706
+ }
668
707
  if (isStreaming) {
669
708
  raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
670
709
  return `
@@ -763,7 +802,7 @@ ${match.trim()}
763
802
  }
764
803
  if (!inline && lang === "json") {
765
804
  const content = String(children != null ? children : "").trim();
766
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
805
+ if (looksLikeStructuredPayload(content)) {
767
806
  return /* @__PURE__ */ React5.createElement(
768
807
  UIDispatcher,
769
808
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -32,15 +32,64 @@ 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 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;
39
75
  const pieLabel = ({ name, percent }: { name?: string; percent?: number }) =>
40
- isCompact
76
+ isTiny
77
+ ? ''
78
+ : isCompact
41
79
  ? `${name}`
42
80
  : `${name} ${((percent ?? 0) * 100).toFixed(0)}%`;
43
- const axisTick = { fontSize: isCompact ? 10 : 12, fill: '#64748b' };
81
+ const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
82
+ const showLegend = !isTiny;
83
+ const truncateAxisLabel = (value: string | number) => {
84
+ const text = String(value);
85
+ const maxLength = isTiny ? 6 : isCompact ? 10 : 16;
86
+ return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
87
+ };
88
+ const chartMargin = isTiny
89
+ ? { top: 8, right: 4, left: -28, bottom: 0 }
90
+ : isCompact
91
+ ? { top: 10, right: 6, left: -24, bottom: 0 }
92
+ : { top: 10, right: 10, left: -20, bottom: 0 };
44
93
 
45
94
  // CRITICAL: Sanitize the data array to remove any non-primitive values (objects/arrays)
46
95
  // This prevents the "Objects are not valid as a React child" error in Tooltips/Legends
@@ -192,67 +241,69 @@ export function DynamicChart({
192
241
  // Handle Pie Chart separately as it has a different structure
193
242
  if (type === 'pie') {
194
243
  return (
195
- <div className={`w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
196
- <ResponsiveContainer width="99%" height="100%">
197
- <PieChart>
198
- <Pie
199
- data={sanitizedData}
200
- dataKey={pieDataKey}
201
- nameKey={finalXKey}
202
- cx="50%"
203
- cy="50%"
204
- outerRadius={pieOuterRadius}
205
- label={pieLabel}
206
- >
207
- {sanitizedData.map((entry, index) => (
208
- <Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
209
- ))}
210
- </Pie>
211
- <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
212
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
213
- </PieChart>
214
- </ResponsiveContainer>
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}`}>
246
+ <ResponsiveContainer width="100%" height="100%">
247
+ <PieChart>
248
+ <Pie
249
+ data={sanitizedData}
250
+ dataKey={pieDataKey}
251
+ nameKey={finalXKey}
252
+ cx="50%"
253
+ cy="50%"
254
+ outerRadius={pieOuterRadius}
255
+ label={isTiny ? false : pieLabel}
256
+ >
257
+ {sanitizedData.map((entry, index) => (
258
+ <Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
259
+ ))}
260
+ </Pie>
261
+ <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
262
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} verticalAlign="bottom" />}
263
+ </PieChart>
264
+ </ResponsiveContainer>
265
+ </div>
215
266
  </div>
216
267
  );
217
268
  }
218
269
 
219
270
  // Bar and Line charts
220
271
  return (
221
- <div className={`w-full ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
222
- <ResponsiveContainer width="99%" height="100%">
272
+ <div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
273
+ <ResponsiveContainer width="100%" height="100%">
223
274
  {type === 'bar' ? (
224
- <BarChart data={sanitizedData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
275
+ <BarChart data={sanitizedData} margin={chartMargin}>
225
276
  <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
226
- <XAxis dataKey={finalXKey} tick={axisTick} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
277
+ <XAxis dataKey={finalXKey} tick={axisTick} tickFormatter={truncateAxisLabel} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} interval={0} minTickGap={isTiny ? 2 : isCompact ? 6 : 12} />
227
278
  <YAxis tick={axisTick} tickLine={false} axisLine={false} />
228
279
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} cursor={{ fill: '#f1f5f9' }} />
229
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
280
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />}
230
281
  {finalDataKeys.map((key, index) => (
231
282
  <Bar
232
283
  key={key}
233
284
  dataKey={key}
234
285
  fill={colors[index % colors.length]}
235
286
  radius={[4, 4, 0, 0]}
236
- barSize={isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)}
287
+ barSize={isTiny ? Math.max(10, 28 / data.length) : isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)}
237
288
  />
238
289
  ))}
239
290
  </BarChart>
240
291
  ) : (
241
- <LineChart data={sanitizedData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
292
+ <LineChart data={sanitizedData} margin={chartMargin}>
242
293
  <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
243
- <XAxis dataKey={finalXKey} tick={axisTick} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
294
+ <XAxis dataKey={finalXKey} tick={axisTick} tickFormatter={truncateAxisLabel} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} interval={0} minTickGap={isTiny ? 2 : isCompact ? 6 : 12} />
244
295
  <YAxis tick={axisTick} tickLine={false} axisLine={false} />
245
296
  <Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
246
- <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />
297
+ {showLegend && <Legend wrapperStyle={{ fontSize: isCompact ? 10 : 12 }} />}
247
298
  {finalDataKeys.map((key, index) => (
248
299
  <Line
249
300
  key={key}
250
301
  type="monotone"
251
302
  dataKey={key}
252
303
  stroke={colors[index % colors.length]}
253
- strokeWidth={isCompact ? 2 : 3}
254
- dot={{ r: isCompact ? 3 : 4, strokeWidth: 2 }}
255
- activeDot={{ r: isCompact ? 5 : 6 }}
304
+ strokeWidth={isTiny ? 1.75 : isCompact ? 2 : 3}
305
+ dot={{ r: isTiny ? 2.5 : isCompact ? 3 : 4, strokeWidth: 2 }}
306
+ activeDot={{ r: isTiny ? 4 : isCompact ? 5 : 6 }}
256
307
  />
257
308
  ))}
258
309
  </LineChart>
@@ -102,6 +102,17 @@ 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
+
109
+ function stripMarkdownTables(raw: string): string {
110
+ return raw.replace(
111
+ /(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
112
+ '\n\n',
113
+ );
114
+ }
115
+
105
116
  // ─── Tiny helpers ─────────────────────────────────────────────────────────────
106
117
 
107
118
  function resolveImage(data: Record<string, unknown>): string | undefined {
@@ -529,6 +540,7 @@ export function MessageBubble({
529
540
 
530
541
  const processedMarkdown = React.useMemo(() => {
531
542
  let raw = cleanContent || message.content;
543
+ const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
532
544
 
533
545
  // 1. Structural fixes
534
546
  raw = raw.replace(/([^\n])\n\|/g, '$1\n\n|') // Ensure blank line before table
@@ -538,14 +550,25 @@ export function MessageBubble({
538
550
  // This handles models (like Llama 3.2) that often omit the ```chart markers.
539
551
  if (!isStreaming) {
540
552
  // Look for objects that look like chart or product data
541
- raw = raw.replace(/(?:^|\n)\s*\{\s*[\s\S]*?"type":\s*"(?:pie|bar|line|products)"[\s\S]*?\}\s*(?:\n|$)/g, (match) => {
553
+ raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
542
554
  // Only wrap if not already in a code block
543
555
  if (match.includes('```')) return match;
544
- const type = match.includes('"products"') ? 'json' : 'chart';
556
+ if (!looksLikeStructuredPayload(match)) return match;
557
+ 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"')
558
+ ? 'ui'
559
+ : 'json';
545
560
  return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
546
561
  });
547
562
  }
548
563
 
564
+ // 2b. If the model already provided a structured UI block, suppress duplicated
565
+ // markdown tables that often follow as a fallback explanation.
566
+ if (hasStructuredUiBlock) {
567
+ raw = stripMarkdownTables(raw)
568
+ .replace(/\n{3,}/g, '\n\n')
569
+ .trim();
570
+ }
571
+
549
572
  // 3. HIJACKING: If streaming, hide table-like content to prevent raw markdown flashing.
550
573
  if (isStreaming) {
551
574
  raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
@@ -638,7 +661,7 @@ export function MessageBubble({
638
661
 
639
662
  if (!inline && lang === 'json') {
640
663
  const content = String(children ?? '').trim();
641
- if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
664
+ if (looksLikeStructuredPayload(content)) {
642
665
  return (
643
666
  <UIDispatcher
644
667
  rawContent={content}