@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 +57 -18
- package/dist/index.mjs +57 -18
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +89 -38
- package/src/components/MessageBubble.tsx +26 -3
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
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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: "
|
|
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: "
|
|
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:
|
|
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*\{
|
|
728
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
694
729
|
if (match.includes("```")) return match;
|
|
695
|
-
|
|
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 (
|
|
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
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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: "
|
|
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: "
|
|
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:
|
|
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*\{
|
|
691
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
657
692
|
if (match.includes("```")) return match;
|
|
658
|
-
|
|
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 (
|
|
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.
|
|
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
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
197
|
-
<
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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="
|
|
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={
|
|
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={
|
|
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*\{
|
|
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
|
-
|
|
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 (
|
|
664
|
+
if (looksLikeStructuredPayload(content)) {
|
|
642
665
|
return (
|
|
643
666
|
<UIDispatcher
|
|
644
667
|
rawContent={content}
|