@retrivora-ai/rag-engine 1.5.7 → 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 +46 -18
- package/dist/index.mjs +46 -18
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +64 -22
- package/src/components/MessageBubble.tsx +10 -3
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
|
|
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 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: "
|
|
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: "
|
|
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:
|
|
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];
|
|
@@ -690,9 +717,10 @@ function MessageBubble({
|
|
|
690
717
|
let raw = cleanContent || message.content;
|
|
691
718
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
692
719
|
if (!isStreaming) {
|
|
693
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
720
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
694
721
|
if (match.includes("```")) return match;
|
|
695
|
-
|
|
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";
|
|
696
724
|
return `
|
|
697
725
|
|
|
698
726
|
\`\`\`${type}
|
|
@@ -800,7 +828,7 @@ ${match.trim()}
|
|
|
800
828
|
}
|
|
801
829
|
if (!inline && lang === "json") {
|
|
802
830
|
const content = String(children != null ? children : "").trim();
|
|
803
|
-
if (
|
|
831
|
+
if (looksLikeStructuredPayload(content)) {
|
|
804
832
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
805
833
|
UIDispatcher,
|
|
806
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
|
|
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 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: "
|
|
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: "
|
|
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:
|
|
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];
|
|
@@ -653,9 +680,10 @@ function MessageBubble({
|
|
|
653
680
|
let raw = cleanContent || message.content;
|
|
654
681
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
655
682
|
if (!isStreaming) {
|
|
656
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
683
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
657
684
|
if (match.includes("```")) return match;
|
|
658
|
-
|
|
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";
|
|
659
687
|
return `
|
|
660
688
|
|
|
661
689
|
\`\`\`${type}
|
|
@@ -763,7 +791,7 @@ ${match.trim()}
|
|
|
763
791
|
}
|
|
764
792
|
if (!inline && lang === "json") {
|
|
765
793
|
const content = String(children != null ? children : "").trim();
|
|
766
|
-
if (
|
|
794
|
+
if (looksLikeStructuredPayload(content)) {
|
|
767
795
|
return /* @__PURE__ */ React5.createElement(
|
|
768
796
|
UIDispatcher,
|
|
769
797
|
{
|
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.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
|
|
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 pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
|
|
39
68
|
const pieLabel = ({ name, percent }: { name?: string; percent?: number }) =>
|
|
40
|
-
|
|
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="
|
|
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="
|
|
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={
|
|
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={
|
|
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 {
|
|
@@ -538,10 +542,13 @@ export function MessageBubble({
|
|
|
538
542
|
// This handles models (like Llama 3.2) that often omit the ```chart markers.
|
|
539
543
|
if (!isStreaming) {
|
|
540
544
|
// Look for objects that look like chart or product data
|
|
541
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
545
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
542
546
|
// Only wrap if not already in a code block
|
|
543
547
|
if (match.includes('```')) return match;
|
|
544
|
-
|
|
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';
|
|
545
552
|
return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
|
|
546
553
|
});
|
|
547
554
|
}
|
|
@@ -638,7 +645,7 @@ export function MessageBubble({
|
|
|
638
645
|
|
|
639
646
|
if (!inline && lang === 'json') {
|
|
640
647
|
const content = String(children ?? '').trim();
|
|
641
|
-
if (
|
|
648
|
+
if (looksLikeStructuredPayload(content)) {
|
|
642
649
|
return (
|
|
643
650
|
<UIDispatcher
|
|
644
651
|
rawContent={content}
|