@retrivora-ai/rag-engine 1.5.6 → 1.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +56 -19
- package/dist/index.mjs +56 -19
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +64 -22
- package/src/components/MessageBubble.tsx +29 -5
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];
|
|
@@ -511,6 +538,14 @@ function normalizeTabularRows(rows, columns) {
|
|
|
511
538
|
return { value: row };
|
|
512
539
|
}).filter((row) => Object.keys(row).length > 0);
|
|
513
540
|
}
|
|
541
|
+
function resolveStructuredRows(config) {
|
|
542
|
+
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
543
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
544
|
+
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
545
|
+
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
546
|
+
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
514
549
|
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
|
|
515
550
|
var _a;
|
|
516
551
|
const result = import_react5.default.useMemo(() => {
|
|
@@ -533,8 +568,9 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
533
568
|
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
534
569
|
}
|
|
535
570
|
}
|
|
571
|
+
const resolvedRows = resolveStructuredRows(config2);
|
|
536
572
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
537
|
-
data: normalizeTabularRows(
|
|
573
|
+
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
538
574
|
});
|
|
539
575
|
return { config: normalizedConfig };
|
|
540
576
|
} catch (err) {
|
|
@@ -681,9 +717,10 @@ function MessageBubble({
|
|
|
681
717
|
let raw = cleanContent || message.content;
|
|
682
718
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
683
719
|
if (!isStreaming) {
|
|
684
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
720
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
685
721
|
if (match.includes("```")) return match;
|
|
686
|
-
|
|
722
|
+
if (!looksLikeStructuredPayload(match)) return match;
|
|
723
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
|
|
687
724
|
return `
|
|
688
725
|
|
|
689
726
|
\`\`\`${type}
|
|
@@ -791,7 +828,7 @@ ${match.trim()}
|
|
|
791
828
|
}
|
|
792
829
|
if (!inline && lang === "json") {
|
|
793
830
|
const content = String(children != null ? children : "").trim();
|
|
794
|
-
if (
|
|
831
|
+
if (looksLikeStructuredPayload(content)) {
|
|
795
832
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
796
833
|
UIDispatcher,
|
|
797
834
|
{
|
package/dist/index.mjs
CHANGED
|
@@ -191,12 +191,36 @@ function DynamicChart({
|
|
|
191
191
|
}) {
|
|
192
192
|
const { type, data } = config;
|
|
193
193
|
const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
|
|
194
|
-
const
|
|
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];
|
|
@@ -474,6 +501,14 @@ function normalizeTabularRows(rows, columns) {
|
|
|
474
501
|
return { value: row };
|
|
475
502
|
}).filter((row) => Object.keys(row).length > 0);
|
|
476
503
|
}
|
|
504
|
+
function resolveStructuredRows(config) {
|
|
505
|
+
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
506
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
507
|
+
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
508
|
+
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
509
|
+
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
477
512
|
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
|
|
478
513
|
var _a;
|
|
479
514
|
const result = React5.useMemo(() => {
|
|
@@ -496,8 +531,9 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
496
531
|
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
497
532
|
}
|
|
498
533
|
}
|
|
534
|
+
const resolvedRows = resolveStructuredRows(config2);
|
|
499
535
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
500
|
-
data: normalizeTabularRows(
|
|
536
|
+
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
501
537
|
});
|
|
502
538
|
return { config: normalizedConfig };
|
|
503
539
|
} catch (err) {
|
|
@@ -644,9 +680,10 @@ function MessageBubble({
|
|
|
644
680
|
let raw = cleanContent || message.content;
|
|
645
681
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
646
682
|
if (!isStreaming) {
|
|
647
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
683
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
648
684
|
if (match.includes("```")) return match;
|
|
649
|
-
|
|
685
|
+
if (!looksLikeStructuredPayload(match)) return match;
|
|
686
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
|
|
650
687
|
return `
|
|
651
688
|
|
|
652
689
|
\`\`\`${type}
|
|
@@ -754,7 +791,7 @@ ${match.trim()}
|
|
|
754
791
|
}
|
|
755
792
|
if (!inline && lang === "json") {
|
|
756
793
|
const content = String(children != null ? children : "").trim();
|
|
757
|
-
if (
|
|
794
|
+
if (looksLikeStructuredPayload(content)) {
|
|
758
795
|
return /* @__PURE__ */ React5.createElement(
|
|
759
796
|
UIDispatcher,
|
|
760
797
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.5.
|
|
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 {
|
|
@@ -241,10 +245,17 @@ interface UIConfig {
|
|
|
241
245
|
insights?: string[];
|
|
242
246
|
type?: string;
|
|
243
247
|
items?: Record<string, unknown>[];
|
|
248
|
+
rows?: Record<string, unknown>[];
|
|
249
|
+
products?: Record<string, unknown>[];
|
|
250
|
+
results?: Record<string, unknown>[];
|
|
244
251
|
}
|
|
245
252
|
|
|
246
|
-
interface RawUIConfig extends Omit<UIConfig, 'data'> {
|
|
253
|
+
interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results'> {
|
|
247
254
|
data: Array<Record<string, unknown> | unknown[]>;
|
|
255
|
+
items?: Array<Record<string, unknown> | unknown[]>;
|
|
256
|
+
rows?: Array<Record<string, unknown> | unknown[]>;
|
|
257
|
+
products?: Array<Record<string, unknown> | unknown[]>;
|
|
258
|
+
results?: Array<Record<string, unknown> | unknown[]>;
|
|
248
259
|
}
|
|
249
260
|
|
|
250
261
|
function normalizeTabularRows(
|
|
@@ -275,6 +286,15 @@ function normalizeTabularRows(
|
|
|
275
286
|
.filter((row) => Object.keys(row).length > 0);
|
|
276
287
|
}
|
|
277
288
|
|
|
289
|
+
function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unknown>) {
|
|
290
|
+
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
291
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
292
|
+
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
293
|
+
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
294
|
+
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
|
|
278
298
|
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = 'large' }: {
|
|
279
299
|
rawContent: string;
|
|
280
300
|
primaryColor?: string;
|
|
@@ -317,9 +337,10 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
317
337
|
}
|
|
318
338
|
}
|
|
319
339
|
|
|
340
|
+
const resolvedRows = resolveStructuredRows(config);
|
|
320
341
|
const normalizedConfig: UIConfig = {
|
|
321
342
|
...(config as Omit<UIConfig, 'data'>),
|
|
322
|
-
data: normalizeTabularRows(
|
|
343
|
+
data: normalizeTabularRows(resolvedRows, config.columns),
|
|
323
344
|
};
|
|
324
345
|
|
|
325
346
|
return { config: normalizedConfig };
|
|
@@ -521,10 +542,13 @@ export function MessageBubble({
|
|
|
521
542
|
// This handles models (like Llama 3.2) that often omit the ```chart markers.
|
|
522
543
|
if (!isStreaming) {
|
|
523
544
|
// Look for objects that look like chart or product data
|
|
524
|
-
raw = raw.replace(/(?:^|\n)\s*\{
|
|
545
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
525
546
|
// Only wrap if not already in a code block
|
|
526
547
|
if (match.includes('```')) return match;
|
|
527
|
-
|
|
548
|
+
if (!looksLikeStructuredPayload(match)) return match;
|
|
549
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"')
|
|
550
|
+
? 'ui'
|
|
551
|
+
: 'json';
|
|
528
552
|
return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
|
|
529
553
|
});
|
|
530
554
|
}
|
|
@@ -621,7 +645,7 @@ export function MessageBubble({
|
|
|
621
645
|
|
|
622
646
|
if (!inline && lang === 'json') {
|
|
623
647
|
const content = String(children ?? '').trim();
|
|
624
|
-
if (
|
|
648
|
+
if (looksLikeStructuredPayload(content)) {
|
|
625
649
|
return (
|
|
626
650
|
<UIDispatcher
|
|
627
651
|
rawContent={content}
|