@retrivora-ai/rag-engine 1.5.8 → 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 +57 -21
- package/dist/index.mjs +57 -21
- package/dist/server.js +2 -2
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +27 -26
- package/src/components/MessageBubble.tsx +70 -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,8 +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 pieOuterRadius =
|
|
252
|
-
|
|
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
|
+
})();
|
|
253
256
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
|
|
254
257
|
const showLegend = !isTiny;
|
|
255
258
|
const truncateAxisLabel = (value) => {
|
|
@@ -357,7 +360,7 @@ function DynamicChart({
|
|
|
357
360
|
return row;
|
|
358
361
|
};
|
|
359
362
|
if (type === "pie") {
|
|
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(
|
|
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(
|
|
361
364
|
import_recharts.Pie,
|
|
362
365
|
{
|
|
363
366
|
data: sanitizedData,
|
|
@@ -366,10 +369,11 @@ function DynamicChart({
|
|
|
366
369
|
cx: "50%",
|
|
367
370
|
cy: "50%",
|
|
368
371
|
outerRadius: pieOuterRadius,
|
|
369
|
-
label:
|
|
372
|
+
label: false,
|
|
373
|
+
labelLine: false
|
|
370
374
|
},
|
|
371
375
|
sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
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:
|
|
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" })))));
|
|
373
377
|
}
|
|
374
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(
|
|
375
379
|
import_recharts.Bar,
|
|
@@ -459,6 +463,12 @@ function extractLikelyJsonBlock(raw) {
|
|
|
459
463
|
function looksLikeStructuredPayload(raw) {
|
|
460
464
|
return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
|
|
461
465
|
}
|
|
466
|
+
function stripMarkdownTables(raw) {
|
|
467
|
+
return raw.replace(
|
|
468
|
+
/(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
|
|
469
|
+
"\n\n"
|
|
470
|
+
);
|
|
471
|
+
}
|
|
462
472
|
function resolveImage(data) {
|
|
463
473
|
for (const key of ["image", "img", "thumbnail"]) {
|
|
464
474
|
const v = data[key];
|
|
@@ -475,23 +485,45 @@ function normaliseChild(children) {
|
|
|
475
485
|
}
|
|
476
486
|
return children;
|
|
477
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
|
+
}
|
|
478
494
|
function DataTable({ config, viewportSize = "large" }) {
|
|
479
495
|
const isCompact = viewportSize === "compact";
|
|
480
496
|
const isMedium = viewportSize === "medium";
|
|
481
|
-
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
|
+
};
|
|
482
511
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
483
|
-
return config.columns
|
|
512
|
+
return config.columns.map((label) => ({
|
|
513
|
+
label: formatColumnLabel(label),
|
|
514
|
+
accessor: findAccessor(label)
|
|
515
|
+
}));
|
|
484
516
|
}
|
|
485
517
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
+
}));
|
|
490
523
|
}
|
|
491
|
-
|
|
492
|
-
return [];
|
|
524
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
493
525
|
}, [config]);
|
|
494
|
-
if (!config.data.length || !
|
|
526
|
+
if (!config.data.length || !columns.length) {
|
|
495
527
|
return /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
|
|
496
528
|
}
|
|
497
529
|
const formatCell = (val) => {
|
|
@@ -499,26 +531,26 @@ function DataTable({ config, viewportSize = "large" }) {
|
|
|
499
531
|
if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
|
|
500
532
|
return String(val);
|
|
501
533
|
};
|
|
502
|
-
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(
|
|
503
535
|
"th",
|
|
504
536
|
{
|
|
505
|
-
key:
|
|
537
|
+
key: column.label,
|
|
506
538
|
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
|
|
507
539
|
},
|
|
508
|
-
|
|
540
|
+
column.label
|
|
509
541
|
)))), /* @__PURE__ */ import_react5.default.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ import_react5.default.createElement(
|
|
510
542
|
"tr",
|
|
511
543
|
{
|
|
512
544
|
key: ri,
|
|
513
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"
|
|
514
546
|
},
|
|
515
|
-
|
|
547
|
+
columns.map((column) => /* @__PURE__ */ import_react5.default.createElement(
|
|
516
548
|
"td",
|
|
517
549
|
{
|
|
518
|
-
key:
|
|
519
|
-
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"}`
|
|
520
552
|
},
|
|
521
|
-
formatCell(row[
|
|
553
|
+
formatCell(row[column.accessor])
|
|
522
554
|
))
|
|
523
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" : "")));
|
|
524
556
|
}
|
|
@@ -715,6 +747,7 @@ function MessageBubble({
|
|
|
715
747
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
716
748
|
const processedMarkdown = import_react5.default.useMemo(() => {
|
|
717
749
|
let raw = cleanContent || message.content;
|
|
750
|
+
const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
718
751
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
719
752
|
if (!isStreaming) {
|
|
720
753
|
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
@@ -730,6 +763,9 @@ ${match.trim()}
|
|
|
730
763
|
`;
|
|
731
764
|
});
|
|
732
765
|
}
|
|
766
|
+
if (hasStructuredUiBlock) {
|
|
767
|
+
raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
|
|
768
|
+
}
|
|
733
769
|
if (isStreaming) {
|
|
734
770
|
raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
|
|
735
771
|
return `
|
package/dist/index.mjs
CHANGED
|
@@ -211,8 +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 pieOuterRadius =
|
|
215
|
-
|
|
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
|
+
})();
|
|
216
219
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: "#64748b" };
|
|
217
220
|
const showLegend = !isTiny;
|
|
218
221
|
const truncateAxisLabel = (value) => {
|
|
@@ -320,7 +323,7 @@ function DynamicChart({
|
|
|
320
323
|
return row;
|
|
321
324
|
};
|
|
322
325
|
if (type === "pie") {
|
|
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(
|
|
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(
|
|
324
327
|
Pie,
|
|
325
328
|
{
|
|
326
329
|
data: sanitizedData,
|
|
@@ -329,10 +332,11 @@ function DynamicChart({
|
|
|
329
332
|
cx: "50%",
|
|
330
333
|
cy: "50%",
|
|
331
334
|
outerRadius: pieOuterRadius,
|
|
332
|
-
label:
|
|
335
|
+
label: false,
|
|
336
|
+
labelLine: false
|
|
333
337
|
},
|
|
334
338
|
sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
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:
|
|
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" })))));
|
|
336
340
|
}
|
|
337
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(
|
|
338
342
|
Bar,
|
|
@@ -422,6 +426,12 @@ function extractLikelyJsonBlock(raw) {
|
|
|
422
426
|
function looksLikeStructuredPayload(raw) {
|
|
423
427
|
return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
|
|
424
428
|
}
|
|
429
|
+
function stripMarkdownTables(raw) {
|
|
430
|
+
return raw.replace(
|
|
431
|
+
/(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
|
|
432
|
+
"\n\n"
|
|
433
|
+
);
|
|
434
|
+
}
|
|
425
435
|
function resolveImage(data) {
|
|
426
436
|
for (const key of ["image", "img", "thumbnail"]) {
|
|
427
437
|
const v = data[key];
|
|
@@ -438,23 +448,45 @@ function normaliseChild(children) {
|
|
|
438
448
|
}
|
|
439
449
|
return children;
|
|
440
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
|
+
}
|
|
441
457
|
function DataTable({ config, viewportSize = "large" }) {
|
|
442
458
|
const isCompact = viewportSize === "compact";
|
|
443
459
|
const isMedium = viewportSize === "medium";
|
|
444
|
-
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
|
+
};
|
|
445
474
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
446
|
-
return config.columns
|
|
475
|
+
return config.columns.map((label) => ({
|
|
476
|
+
label: formatColumnLabel(label),
|
|
477
|
+
accessor: findAccessor(label)
|
|
478
|
+
}));
|
|
447
479
|
}
|
|
448
480
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
+
}));
|
|
453
486
|
}
|
|
454
|
-
|
|
455
|
-
return [];
|
|
487
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
456
488
|
}, [config]);
|
|
457
|
-
if (!config.data.length || !
|
|
489
|
+
if (!config.data.length || !columns.length) {
|
|
458
490
|
return /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-400 italic my-2" }, "No data to display.");
|
|
459
491
|
}
|
|
460
492
|
const formatCell = (val) => {
|
|
@@ -462,26 +494,26 @@ function DataTable({ config, viewportSize = "large" }) {
|
|
|
462
494
|
if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
|
|
463
495
|
return String(val);
|
|
464
496
|
};
|
|
465
|
-
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(
|
|
466
498
|
"th",
|
|
467
499
|
{
|
|
468
|
-
key:
|
|
500
|
+
key: column.label,
|
|
469
501
|
className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
|
|
470
502
|
},
|
|
471
|
-
|
|
503
|
+
column.label
|
|
472
504
|
)))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
|
|
473
505
|
"tr",
|
|
474
506
|
{
|
|
475
507
|
key: ri,
|
|
476
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"
|
|
477
509
|
},
|
|
478
|
-
|
|
510
|
+
columns.map((column) => /* @__PURE__ */ React5.createElement(
|
|
479
511
|
"td",
|
|
480
512
|
{
|
|
481
|
-
key:
|
|
482
|
-
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"}`
|
|
483
515
|
},
|
|
484
|
-
formatCell(row[
|
|
516
|
+
formatCell(row[column.accessor])
|
|
485
517
|
))
|
|
486
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" : "")));
|
|
487
519
|
}
|
|
@@ -678,6 +710,7 @@ function MessageBubble({
|
|
|
678
710
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
679
711
|
const processedMarkdown = React5.useMemo(() => {
|
|
680
712
|
let raw = cleanContent || message.content;
|
|
713
|
+
const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
681
714
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
682
715
|
if (!isStreaming) {
|
|
683
716
|
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
@@ -693,6 +726,9 @@ ${match.trim()}
|
|
|
693
726
|
`;
|
|
694
727
|
});
|
|
695
728
|
}
|
|
729
|
+
if (hasStructuredUiBlock) {
|
|
730
|
+
raw = stripMarkdownTables(raw).replace(/\n{3,}/g, "\n\n").trim();
|
|
731
|
+
}
|
|
696
732
|
if (isStreaming) {
|
|
697
733
|
raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
|
|
698
734
|
return `
|
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,13 +64,11 @@ export function DynamicChart({
|
|
|
64
64
|
: isMedium
|
|
65
65
|
? 'h-64 min-h-[250px]'
|
|
66
66
|
: 'h-72 min-h-[280px]';
|
|
67
|
-
const pieOuterRadius =
|
|
68
|
-
|
|
69
|
-
isTiny
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
? `${name}`
|
|
73
|
-
: `${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
|
+
})();
|
|
74
72
|
const axisTick = { fontSize: isTiny ? 9 : isCompact ? 10 : 12, fill: '#64748b' };
|
|
75
73
|
const showLegend = !isTiny;
|
|
76
74
|
const truncateAxisLabel = (value: string | number) => {
|
|
@@ -235,25 +233,28 @@ export function DynamicChart({
|
|
|
235
233
|
if (type === 'pie') {
|
|
236
234
|
return (
|
|
237
235
|
<div ref={containerRef} className={`w-full min-w-0 ${chartHeightClass} mt-4 mb-2 select-none overflow-hidden`}>
|
|
238
|
-
<
|
|
239
|
-
<
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
236
|
+
<div className="mx-auto h-full aspect-square max-w-full">
|
|
237
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
238
|
+
<PieChart>
|
|
239
|
+
<Pie
|
|
240
|
+
data={sanitizedData}
|
|
241
|
+
dataKey={pieDataKey}
|
|
242
|
+
nameKey={finalXKey}
|
|
243
|
+
cx="50%"
|
|
244
|
+
cy="50%"
|
|
245
|
+
outerRadius={pieOuterRadius}
|
|
246
|
+
label={false}
|
|
247
|
+
labelLine={false}
|
|
248
|
+
>
|
|
249
|
+
{sanitizedData.map((entry, index) => (
|
|
250
|
+
<Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
|
|
251
|
+
))}
|
|
252
|
+
</Pie>
|
|
253
|
+
<Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
|
|
254
|
+
{showLegend && !isCompact && <Legend wrapperStyle={{ fontSize: 12 }} verticalAlign="bottom" />}
|
|
255
|
+
</PieChart>
|
|
256
|
+
</ResponsiveContainer>
|
|
257
|
+
</div>
|
|
257
258
|
</div>
|
|
258
259
|
);
|
|
259
260
|
}
|
|
@@ -106,6 +106,13 @@ function looksLikeStructuredPayload(raw: string): boolean {
|
|
|
106
106
|
return /"(view|chartType|type|data|items|rows|products|results)"\s*:/.test(raw);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function stripMarkdownTables(raw: string): string {
|
|
110
|
+
return raw.replace(
|
|
111
|
+
/(?:^|\n)\|[^\n]+\|\n\|(?:\s*:?-+:?\s*\|)+\n(?:\|[^\n]*\|\n?)*/g,
|
|
112
|
+
'\n\n',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
// ─── Tiny helpers ─────────────────────────────────────────────────────────────
|
|
110
117
|
|
|
111
118
|
function resolveImage(data: Record<string, unknown>): string | undefined {
|
|
@@ -132,6 +139,19 @@ function normaliseChild(children: React.ReactNode): React.ReactNode {
|
|
|
132
139
|
return children;
|
|
133
140
|
}
|
|
134
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
|
+
|
|
135
155
|
// ─── Inline data table renderer ───────────────────────────────────────────────
|
|
136
156
|
// Handles chart configs where type === "table". The LLM may emit any of:
|
|
137
157
|
// columns, dataKeys (+optional xAxisKey), or no key hints (auto-detect from row).
|
|
@@ -149,24 +169,46 @@ interface TableConfig {
|
|
|
149
169
|
function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; viewportSize?: ChatViewportSize }) {
|
|
150
170
|
const isCompact = viewportSize === 'compact';
|
|
151
171
|
const isMedium = viewportSize === 'medium';
|
|
152
|
-
const
|
|
153
|
-
|
|
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
|
+
|
|
154
190
|
if (Array.isArray(config.columns) && config.columns.length) {
|
|
155
|
-
return config.columns
|
|
191
|
+
return config.columns.map((label) => ({
|
|
192
|
+
label: formatColumnLabel(label),
|
|
193
|
+
accessor: findAccessor(label),
|
|
194
|
+
}));
|
|
156
195
|
}
|
|
157
|
-
|
|
196
|
+
|
|
158
197
|
if (Array.isArray(config.dataKeys) && config.dataKeys.length) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}));
|
|
163
206
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return [];
|
|
207
|
+
|
|
208
|
+
return rowKeys.map((key) => ({ label: formatColumnLabel(key), accessor: key }));
|
|
167
209
|
}, [config]);
|
|
168
210
|
|
|
169
|
-
if (!config.data.length || !
|
|
211
|
+
if (!config.data.length || !columns.length) {
|
|
170
212
|
return <p className="text-xs text-slate-400 italic my-2">No data to display.</p>;
|
|
171
213
|
}
|
|
172
214
|
|
|
@@ -190,12 +232,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
|
|
|
190
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'}`}>
|
|
191
233
|
<thead className="bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10">
|
|
192
234
|
<tr>
|
|
193
|
-
{
|
|
235
|
+
{columns.map((column) => (
|
|
194
236
|
<th
|
|
195
|
-
key={
|
|
237
|
+
key={column.label}
|
|
196
238
|
className={`${isCompact ? 'px-2.5 py-2' : 'px-4 py-3'} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`}
|
|
197
239
|
>
|
|
198
|
-
{
|
|
240
|
+
{column.label}
|
|
199
241
|
</th>
|
|
200
242
|
))}
|
|
201
243
|
</tr>
|
|
@@ -206,12 +248,12 @@ function DataTable({ config, viewportSize = 'large' }: { config: TableConfig; vi
|
|
|
206
248
|
key={ri}
|
|
207
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"
|
|
208
250
|
>
|
|
209
|
-
{
|
|
251
|
+
{columns.map((column) => (
|
|
210
252
|
<td
|
|
211
|
-
key={
|
|
212
|
-
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'}`}
|
|
213
255
|
>
|
|
214
|
-
{formatCell(row[
|
|
256
|
+
{formatCell(row[column.accessor])}
|
|
215
257
|
</td>
|
|
216
258
|
))}
|
|
217
259
|
</tr>
|
|
@@ -533,6 +575,7 @@ export function MessageBubble({
|
|
|
533
575
|
|
|
534
576
|
const processedMarkdown = React.useMemo(() => {
|
|
535
577
|
let raw = cleanContent || message.content;
|
|
578
|
+
const hasStructuredUiBlock = /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
536
579
|
|
|
537
580
|
// 1. Structural fixes
|
|
538
581
|
raw = raw.replace(/([^\n])\n\|/g, '$1\n\n|') // Ensure blank line before table
|
|
@@ -553,6 +596,14 @@ export function MessageBubble({
|
|
|
553
596
|
});
|
|
554
597
|
}
|
|
555
598
|
|
|
599
|
+
// 2b. If the model already provided a structured UI block, suppress duplicated
|
|
600
|
+
// markdown tables that often follow as a fallback explanation.
|
|
601
|
+
if (hasStructuredUiBlock) {
|
|
602
|
+
raw = stripMarkdownTables(raw)
|
|
603
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
604
|
+
.trim();
|
|
605
|
+
}
|
|
606
|
+
|
|
556
607
|
// 3. HIJACKING: If streaming, hide table-like content to prevent raw markdown flashing.
|
|
557
608
|
if (isStreaming) {
|
|
558
609
|
raw = raw.replace(/((?:^|\n)\|.*)+/g, (match) => {
|
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.
|