@retrivora-ai/rag-engine 1.5.9 → 1.6.0
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/{chunk-ID2Q4CF7.mjs → chunk-5K7EU4KW.mjs} +2 -2
- package/dist/handlers/index.js +2 -2
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +47 -22
- package/dist/index.mjs +47 -22
- package/dist/server.js +2 -2
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +9 -17
- package/src/components/MessageBubble.tsx +54 -19
- package/src/core/Pipeline.ts +2 -2
|
@@ -2279,7 +2279,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
2279
2279
|
"chartType": "pie" | "bar" | "line",
|
|
2280
2280
|
"xAxisKey": "label",
|
|
2281
2281
|
"dataKeys": ["value"],
|
|
2282
|
-
"columns": ["
|
|
2282
|
+
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
2283
2283
|
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
2284
2284
|
"data": []
|
|
2285
2285
|
}
|
|
@@ -2292,7 +2292,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
2292
2292
|
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
2293
2293
|
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
2294
2294
|
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
2295
|
-
- For tables, keep "data" row-oriented and include "columns"
|
|
2295
|
+
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
2296
2296
|
|
|
2297
2297
|
4. FORMAT RULES
|
|
2298
2298
|
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
package/dist/handlers/index.js
CHANGED
|
@@ -3842,7 +3842,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3842
3842
|
"chartType": "pie" | "bar" | "line",
|
|
3843
3843
|
"xAxisKey": "label",
|
|
3844
3844
|
"dataKeys": ["value"],
|
|
3845
|
-
"columns": ["
|
|
3845
|
+
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
3846
3846
|
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3847
3847
|
"data": []
|
|
3848
3848
|
}
|
|
@@ -3855,7 +3855,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3855
3855
|
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
3856
3856
|
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
3857
3857
|
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
3858
|
-
- For tables, keep "data" row-oriented and include "columns"
|
|
3858
|
+
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
3859
3859
|
|
|
3860
3860
|
4. FORMAT RULES
|
|
3861
3861
|
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -248,9 +248,11 @@ function DynamicChart({
|
|
|
248
248
|
const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
|
|
249
249
|
const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
|
|
250
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
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
const pieOuterRadius = (() => {
|
|
252
|
+
if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
|
|
253
|
+
const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
|
|
254
|
+
return Math.max(36, Math.floor(squareSize * 0.28));
|
|
255
|
+
})();
|
|
254
256
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
|
|
255
257
|
const showLegend = !isTiny;
|
|
256
258
|
const truncateAxisLabel = (value) => {
|
|
@@ -358,7 +360,7 @@ function DynamicChart({
|
|
|
358
360
|
return row;
|
|
359
361
|
};
|
|
360
362
|
if (type === "pie") {
|
|
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:
|
|
363
|
+
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 aspect-square max-w-full" }, /* @__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(
|
|
362
364
|
import_recharts.Pie,
|
|
363
365
|
{
|
|
364
366
|
data: sanitizedData,
|
|
@@ -367,10 +369,11 @@ function DynamicChart({
|
|
|
367
369
|
cx: "50%",
|
|
368
370
|
cy: "50%",
|
|
369
371
|
outerRadius: pieOuterRadius,
|
|
370
|
-
label:
|
|
372
|
+
label: false,
|
|
373
|
+
labelLine: false
|
|
371
374
|
},
|
|
372
375
|
sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
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:
|
|
376
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && !isCompact && /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })))));
|
|
374
377
|
}
|
|
375
378
|
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(
|
|
376
379
|
import_recharts.Bar,
|
|
@@ -482,23 +485,45 @@ function normaliseChild(children) {
|
|
|
482
485
|
}
|
|
483
486
|
return children;
|
|
484
487
|
}
|
|
488
|
+
function normaliseFieldName(value) {
|
|
489
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
490
|
+
}
|
|
491
|
+
function formatColumnLabel(value) {
|
|
492
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
|
|
493
|
+
}
|
|
485
494
|
function DataTable({ config, viewportSize = "large" }) {
|
|
486
495
|
const isCompact = viewportSize === "compact";
|
|
487
496
|
const isMedium = viewportSize === "medium";
|
|
488
|
-
const
|
|
497
|
+
const columns = import_react5.default.useMemo(() => {
|
|
498
|
+
const rowKeys = Array.from(
|
|
499
|
+
new Set(
|
|
500
|
+
config.data.flatMap((row) => Object.keys(row))
|
|
501
|
+
)
|
|
502
|
+
);
|
|
503
|
+
const findAccessor = (label) => {
|
|
504
|
+
const exact = rowKeys.find((key) => key === label);
|
|
505
|
+
if (exact) return exact;
|
|
506
|
+
const normalisedLabel = normaliseFieldName(label);
|
|
507
|
+
const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
|
|
508
|
+
if (normalisedMatch) return normalisedMatch;
|
|
509
|
+
return label;
|
|
510
|
+
};
|
|
489
511
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
490
|
-
return config.columns
|
|
512
|
+
return config.columns.map((label) => ({
|
|
513
|
+
label: formatColumnLabel(label),
|
|
514
|
+
accessor: findAccessor(label)
|
|
515
|
+
}));
|
|
491
516
|
}
|
|
492
517
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
518
|
+
const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
|
|
519
|
+
return keys.map((key) => ({
|
|
520
|
+
label: formatColumnLabel(key),
|
|
521
|
+
accessor: findAccessor(key)
|
|
522
|
+
}));
|
|
497
523
|
}
|
|
498
|
-
|
|
499
|
-
return [];
|
|
524
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
500
525
|
}, [config]);
|
|
501
|
-
if (!config.data.length || !
|
|
526
|
+
if (!config.data.length || !columns.length) {
|
|
502
527
|
return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
|
|
503
528
|
}
|
|
504
529
|
const formatCell = (val) => {
|
|
@@ -506,26 +531,26 @@ function DataTable({ config, viewportSize = "large" }) {
|
|
|
506
531
|
if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
|
|
507
532
|
return String(val);
|
|
508
533
|
};
|
|
509
|
-
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react5.default.createElement("table", { className: `w-full text-left border-collapse ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "min-w-[320px] text-sm"}` }, /* @__PURE__ */ import_react5.default.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("tr", null,
|
|
534
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ import_react5.default.createElement("table", { className: `w-full text-left border-collapse ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "min-w-[320px] text-sm"}` }, /* @__PURE__ */ import_react5.default.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ import_react5.default.createElement("tr", null, columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
|
|
510
535
|
"th",
|
|
511
536
|
{
|
|
512
|
-
key:
|
|
537
|
+
key: column.label,
|
|
513
538
|
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
|
|
514
539
|
},
|
|
515
|
-
|
|
540
|
+
column.label
|
|
516
541
|
)))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
|
|
517
542
|
"tr",
|
|
518
543
|
{
|
|
519
544
|
key: ri,
|
|
520
545
|
className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
|
|
521
546
|
},
|
|
522
|
-
|
|
547
|
+
columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
|
|
523
548
|
"td",
|
|
524
549
|
{
|
|
525
|
-
key:
|
|
526
|
-
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
|
|
550
|
+
key: column.label,
|
|
551
|
+
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes("description") ? "whitespace-normal break-words" : "whitespace-nowrap"}`
|
|
527
552
|
},
|
|
528
|
-
formatCell(row[
|
|
553
|
+
formatCell(row[column.accessor])
|
|
529
554
|
))
|
|
530
555
|
))))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
|
|
531
556
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -211,9 +211,11 @@ function DynamicChart({
|
|
|
211
211
|
const isCompact = viewportSize === "compact" || containerWidth > 0 && containerWidth < 420;
|
|
212
212
|
const isMedium = !isCompact && (viewportSize === "medium" || containerWidth > 0 && containerWidth < 640);
|
|
213
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
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
const pieOuterRadius = (() => {
|
|
215
|
+
if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
|
|
216
|
+
const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
|
|
217
|
+
return Math.max(36, Math.floor(squareSize * 0.28));
|
|
218
|
+
})();
|
|
217
219
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
|
|
218
220
|
const showLegend = !isTiny;
|
|
219
221
|
const truncateAxisLabel = (value) => {
|
|
@@ -321,7 +323,7 @@ function DynamicChart({
|
|
|
321
323
|
return row;
|
|
322
324
|
};
|
|
323
325
|
if (type === "pie") {
|
|
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:
|
|
326
|
+
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 aspect-square max-w-full" }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
|
|
325
327
|
Pie,
|
|
326
328
|
{
|
|
327
329
|
data: sanitizedData,
|
|
@@ -330,10 +332,11 @@ function DynamicChart({
|
|
|
330
332
|
cx: "50%",
|
|
331
333
|
cy: "50%",
|
|
332
334
|
outerRadius: pieOuterRadius,
|
|
333
|
-
label:
|
|
335
|
+
label: false,
|
|
336
|
+
labelLine: false
|
|
334
337
|
},
|
|
335
338
|
sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
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:
|
|
339
|
+
), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), showLegend && !isCompact && /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })))));
|
|
337
340
|
}
|
|
338
341
|
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(
|
|
339
342
|
Bar,
|
|
@@ -445,23 +448,45 @@ function normaliseChild(children) {
|
|
|
445
448
|
}
|
|
446
449
|
return children;
|
|
447
450
|
}
|
|
451
|
+
function normaliseFieldName(value) {
|
|
452
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
453
|
+
}
|
|
454
|
+
function formatColumnLabel(value) {
|
|
455
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
|
|
456
|
+
}
|
|
448
457
|
function DataTable({ config, viewportSize = "large" }) {
|
|
449
458
|
const isCompact = viewportSize === "compact";
|
|
450
459
|
const isMedium = viewportSize === "medium";
|
|
451
|
-
const
|
|
460
|
+
const columns = React5.useMemo(() => {
|
|
461
|
+
const rowKeys = Array.from(
|
|
462
|
+
new Set(
|
|
463
|
+
config.data.flatMap((row) => Object.keys(row))
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
const findAccessor = (label) => {
|
|
467
|
+
const exact = rowKeys.find((key) => key === label);
|
|
468
|
+
if (exact) return exact;
|
|
469
|
+
const normalisedLabel = normaliseFieldName(label);
|
|
470
|
+
const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
|
|
471
|
+
if (normalisedMatch) return normalisedMatch;
|
|
472
|
+
return label;
|
|
473
|
+
};
|
|
452
474
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
453
|
-
return config.columns
|
|
475
|
+
return config.columns.map((label) => ({
|
|
476
|
+
label: formatColumnLabel(label),
|
|
477
|
+
accessor: findAccessor(label)
|
|
478
|
+
}));
|
|
454
479
|
}
|
|
455
480
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
481
|
+
const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey) ? [config.xAxisKey, ...config.dataKeys] : config.dataKeys;
|
|
482
|
+
return keys.map((key) => ({
|
|
483
|
+
label: formatColumnLabel(key),
|
|
484
|
+
accessor: findAccessor(key)
|
|
485
|
+
}));
|
|
460
486
|
}
|
|
461
|
-
|
|
462
|
-
return [];
|
|
487
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
463
488
|
}, [config]);
|
|
464
|
-
if (!config.data.length || !
|
|
489
|
+
if (!config.data.length || !columns.length) {
|
|
465
490
|
return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
|
|
466
491
|
}
|
|
467
492
|
const formatCell = (val) => {
|
|
@@ -469,26 +494,26 @@ function DataTable({ config, viewportSize = "large" }) {
|
|
|
469
494
|
if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
|
|
470
495
|
return String(val);
|
|
471
496
|
};
|
|
472
|
-
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: `w-full text-left border-collapse ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "min-w-[320px] text-sm"}` }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null,
|
|
497
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: `w-full text-left border-collapse ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "min-w-[320px] text-sm"}` }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null, columns.map((column) => /* @__PURE__ */ React5.createElement(
|
|
473
498
|
"th",
|
|
474
499
|
{
|
|
475
|
-
key:
|
|
500
|
+
key: column.label,
|
|
476
501
|
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
|
|
477
502
|
},
|
|
478
|
-
|
|
503
|
+
column.label
|
|
479
504
|
)))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
|
|
480
505
|
"tr",
|
|
481
506
|
{
|
|
482
507
|
key: ri,
|
|
483
508
|
className: "border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
|
|
484
509
|
},
|
|
485
|
-
|
|
510
|
+
columns.map((column) => /* @__PURE__ */ React5.createElement(
|
|
486
511
|
"td",
|
|
487
512
|
{
|
|
488
|
-
key:
|
|
489
|
-
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
|
|
513
|
+
key: column.label,
|
|
514
|
+
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes("description") ? "whitespace-normal break-words" : "whitespace-nowrap"}`
|
|
490
515
|
},
|
|
491
|
-
formatCell(row[
|
|
516
|
+
formatCell(row[column.accessor])
|
|
492
517
|
))
|
|
493
518
|
))))), /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2 bg-slate-50 dark:bg-white/5 border-t border-slate-100 dark:border-white/5" }, /* @__PURE__ */ React5.createElement("p", { className: "text-[11px] text-slate-400 dark:text-white/30" }, config.data.length, " row", config.data.length !== 1 ? "s" : "")));
|
|
494
519
|
}
|
package/dist/server.js
CHANGED
|
@@ -3933,7 +3933,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3933
3933
|
"chartType": "pie" | "bar" | "line",
|
|
3934
3934
|
"xAxisKey": "label",
|
|
3935
3935
|
"dataKeys": ["value"],
|
|
3936
|
-
"columns": ["
|
|
3936
|
+
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
3937
3937
|
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3938
3938
|
"data": []
|
|
3939
3939
|
}
|
|
@@ -3946,7 +3946,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3946
3946
|
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
3947
3947
|
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
3948
3948
|
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
3949
|
-
- For tables, keep "data" row-oriented and include "columns"
|
|
3949
|
+
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
3950
3950
|
|
|
3951
3951
|
4. FORMAT RULES
|
|
3952
3952
|
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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",
|
|
@@ -64,20 +64,11 @@ export function DynamicChart({
|
|
|
64
64
|
: isMedium
|
|
65
65
|
? 'h-64 min-h-[250px]'
|
|
66
66
|
: 'h-72 min-h-[280px]';
|
|
67
|
-
const
|
|
68
|
-
?
|
|
69
|
-
: isCompact
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
? 'max-w-[16rem]'
|
|
73
|
-
: 'max-w-[18rem]';
|
|
74
|
-
const pieOuterRadius = isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
|
|
75
|
-
const pieLabel = ({ name, percent }: { name?: string; percent?: number }) =>
|
|
76
|
-
isTiny
|
|
77
|
-
? ''
|
|
78
|
-
: isCompact
|
|
79
|
-
? `${name}`
|
|
80
|
-
: `${name} ${((percent ?? 0) * 100).toFixed(0)}%`;
|
|
67
|
+
const pieOuterRadius = (() => {
|
|
68
|
+
if (containerWidth <= 0) return isTiny ? 44 : isCompact ? 56 : isMedium ? 68 : 80;
|
|
69
|
+
const squareSize = Math.min(containerWidth, isTiny ? 180 : isCompact ? 220 : isMedium ? 250 : 280);
|
|
70
|
+
return Math.max(36, Math.floor(squareSize * 0.28));
|
|
71
|
+
})();
|
|
81
72
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
|
|
82
73
|
const showLegend = !isTiny;
|
|
83
74
|
const truncateAxisLabel = (value: string | number) => {
|
|
@@ -242,7 +233,7 @@ export function DynamicChart({
|
|
|
242
233
|
if (type === 'pie') {
|
|
243
234
|
return (
|
|
244
235
|
<div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
|
|
245
|
-
<div className=
|
|
236
|
+
<div className="mx-auto h-full aspect-square max-w-full">
|
|
246
237
|
<ResponsiveContainer width="100%" height="100%">
|
|
247
238
|
<PieChart>
|
|
248
239
|
<Pie
|
|
@@ -252,14 +243,15 @@ export function DynamicChart({
|
|
|
252
243
|
cx="50%"
|
|
253
244
|
cy="50%"
|
|
254
245
|
outerRadius={pieOuterRadius}
|
|
255
|
-
label={
|
|
246
|
+
label={false}
|
|
247
|
+
labelLine={false}
|
|
256
248
|
>
|
|
257
249
|
{sanitizedData.map((entry, index) => (
|
|
258
250
|
<Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
|
|
259
251
|
))}
|
|
260
252
|
</Pie>
|
|
261
253
|
<Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
|
|
262
|
-
{showLegend && <Legend wrapperStyle={{ fontSize:
|
|
254
|
+
{showLegend && !isCompact && <Legend wrapperStyle={{ fontSize: 12 }} verticalAlign="bottom" />}
|
|
263
255
|
</PieChart>
|
|
264
256
|
</ResponsiveContainer>
|
|
265
257
|
</div>
|
|
@@ -139,6 +139,19 @@ function normaliseChild(children: React.ReactNode): React.ReactNode {
|
|
|
139
139
|
return children;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function normaliseFieldName(value: string): string {
|
|
143
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function formatColumnLabel(value: string): string {
|
|
147
|
+
return value
|
|
148
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
149
|
+
.replace(/[_-]+/g, ' ')
|
|
150
|
+
.replace(/\s+/g, ' ')
|
|
151
|
+
.trim()
|
|
152
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
153
|
+
}
|
|
154
|
+
|
|
142
155
|
// ─── Inline data table renderer ───────────────────────────────────────────────
|
|
143
156
|
// Handles chart configs where type === "table". The LLM may emit any of:
|
|
144
157
|
// columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
|
|
@@ -156,24 +169,46 @@ interface TableConfig {
|
|
|
156
169
|
function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; viewportSize?: ChatViewportSize }) {
|
|
157
170
|
const isCompact = viewportSize === 'compact';
|
|
158
171
|
const isMedium = viewportSize === 'medium';
|
|
159
|
-
const
|
|
160
|
-
|
|
172
|
+
const columns = React.useMemo<Array<{ label: string; accessor: string }>>(() => {
|
|
173
|
+
const rowKeys = Array.from(
|
|
174
|
+
new Set(
|
|
175
|
+
config.data.flatMap((row) => Object.keys(row)),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const findAccessor = (label: string) => {
|
|
180
|
+
const exact = rowKeys.find((key) => key === label);
|
|
181
|
+
if (exact) return exact;
|
|
182
|
+
|
|
183
|
+
const normalisedLabel = normaliseFieldName(label);
|
|
184
|
+
const normalisedMatch = rowKeys.find((key) => normaliseFieldName(key) === normalisedLabel);
|
|
185
|
+
if (normalisedMatch) return normalisedMatch;
|
|
186
|
+
|
|
187
|
+
return label;
|
|
188
|
+
};
|
|
189
|
+
|
|
161
190
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
162
|
-
return config.columns
|
|
191
|
+
return config.columns.map((label) => ({
|
|
192
|
+
label: formatColumnLabel(label),
|
|
193
|
+
accessor: findAccessor(label),
|
|
194
|
+
}));
|
|
163
195
|
}
|
|
164
|
-
|
|
196
|
+
|
|
165
197
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
198
|
+
const keys = config.xAxisKey && !config.dataKeys.includes(config.xAxisKey)
|
|
199
|
+
? [config.xAxisKey, ...config.dataKeys]
|
|
200
|
+
: config.dataKeys;
|
|
201
|
+
|
|
202
|
+
return keys.map((key) => ({
|
|
203
|
+
label: formatColumnLabel(key),
|
|
204
|
+
accessor: findAccessor(key),
|
|
205
|
+
}));
|
|
170
206
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return [];
|
|
207
|
+
|
|
208
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
174
209
|
}, [config]);
|
|
175
210
|
|
|
176
|
-
if (!config.data.length || !
|
|
211
|
+
if (!config.data.length || !columns.length) {
|
|
177
212
|
return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
|
|
178
213
|
}
|
|
179
214
|
|
|
@@ -197,12 +232,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
|
|
|
197
232
|
<table className={`w-full text-left border-collapse ${isCompact ? 'min-w-[240px] text-xs' : isMedium ? 'min-w-[280px] text-[13px]' : 'min-w-[320px] text-sm'}`}>
|
|
198
233
|
<thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
|
|
199
234
|
<tr>
|
|
200
|
-
{
|
|
235
|
+
{columns.map((column) => (
|
|
201
236
|
<th
|
|
202
|
-
key={
|
|
237
|
+
key={column.label}
|
|
203
238
|
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`}
|
|
204
239
|
>
|
|
205
|
-
{
|
|
240
|
+
{column.label}
|
|
206
241
|
</th>
|
|
207
242
|
))}
|
|
208
243
|
</tr>
|
|
@@ -213,12 +248,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
|
|
|
213
248
|
key={ri}
|
|
214
249
|
className="border-b border-slate-100 dark:border-white/5 last:border-0 hover:bg-slate-50/60 dark:hover:bg-white/5 transition-colors"
|
|
215
250
|
>
|
|
216
|
-
{
|
|
251
|
+
{columns.map((column) => (
|
|
217
252
|
<td
|
|
218
|
-
key={
|
|
219
|
-
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} text-slate-600 dark:text-white/70 whitespace-nowrap`}
|
|
253
|
+
key={column.label}
|
|
254
|
+
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} text-slate-600 dark:text-white/70 ${column.accessor.toLowerCase().includes('description') ? 'whitespace-normal break-words' : 'whitespace-nowrap'}`}
|
|
220
255
|
>
|
|
221
|
-
{formatCell(row[
|
|
256
|
+
{formatCell(row[column.accessor])}
|
|
222
257
|
</td>
|
|
223
258
|
))}
|
|
224
259
|
</tr>
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -119,7 +119,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
119
119
|
"chartType": "pie" | "bar" | "line",
|
|
120
120
|
"xAxisKey": "label",
|
|
121
121
|
"dataKeys": ["value"],
|
|
122
|
-
"columns": ["
|
|
122
|
+
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
123
123
|
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
124
124
|
"data": []
|
|
125
125
|
}
|
|
@@ -132,7 +132,7 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
132
132
|
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
133
133
|
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
134
134
|
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
135
|
-
- For tables, keep "data" row-oriented and include "columns"
|
|
135
|
+
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
136
136
|
|
|
137
137
|
4. FORMAT RULES
|
|
138
138
|
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|