@retrivora-ai/rag-engine 1.5.3 → 1.5.5
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-C4UPQX3E.mjs → chunk-Q4MDH6C4.mjs} +59 -8
- package/dist/handlers/index.js +59 -8
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +143 -42
- package/dist/index.mjs +143 -42
- package/dist/server.js +59 -8
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/app/api/chat/route.ts +2 -2
- package/src/components/DynamicChart.tsx +81 -14
- package/src/components/MessageBubble.tsx +143 -44
- package/src/core/Pipeline.ts +59 -8
package/dist/index.mjs
CHANGED
|
@@ -233,8 +233,58 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
233
233
|
}).slice(0, 5);
|
|
234
234
|
const finalXKey = String(resolvedXKey);
|
|
235
235
|
const finalDataKeys = resolvedDataKeys.map(String).filter((k) => k !== "undefined");
|
|
236
|
+
const pieDataKey = finalDataKeys[0] || "value";
|
|
237
|
+
const stockAwareColor = (entry, index) => {
|
|
238
|
+
var _a, _b, _c;
|
|
239
|
+
const stockStatus = String(
|
|
240
|
+
(_c = (_b = (_a = entry.stockStatus) != null ? _a : entry.status) != null ? _b : entry.availability) != null ? _c : ""
|
|
241
|
+
).toLowerCase();
|
|
242
|
+
const inStockCount = typeof entry.inStockCount === "number" ? entry.inStockCount : void 0;
|
|
243
|
+
const outOfStockCount = typeof entry.outOfStockCount === "number" ? entry.outOfStockCount : void 0;
|
|
244
|
+
const inStockFlag = entry.inStock;
|
|
245
|
+
if (stockStatus.includes("in stock")) return "#10b981";
|
|
246
|
+
if (stockStatus.includes("out of stock")) return "#f97316";
|
|
247
|
+
if (typeof inStockFlag === "string" && inStockFlag.toLowerCase() === "true") return "#10b981";
|
|
248
|
+
if (inStockFlag === 1) return "#10b981";
|
|
249
|
+
if (inStockFlag === 0) return "#f97316";
|
|
250
|
+
if (typeof inStockCount === "number" || typeof outOfStockCount === "number") {
|
|
251
|
+
const inCount = inStockCount != null ? inStockCount : 0;
|
|
252
|
+
const outCount = outOfStockCount != null ? outOfStockCount : 0;
|
|
253
|
+
if (inCount > outCount) return "#10b981";
|
|
254
|
+
if (outCount > inCount) return "#f97316";
|
|
255
|
+
}
|
|
256
|
+
return colors[index % colors.length];
|
|
257
|
+
};
|
|
258
|
+
const tooltipLabelMap = {
|
|
259
|
+
value: "Value",
|
|
260
|
+
count: "Count",
|
|
261
|
+
inStock: "In stock",
|
|
262
|
+
inStockCount: "In stock",
|
|
263
|
+
outOfStockCount: "Out of stock",
|
|
264
|
+
stockStatus: "Stock status",
|
|
265
|
+
category: "Category",
|
|
266
|
+
label: "Label",
|
|
267
|
+
name: "Name"
|
|
268
|
+
};
|
|
269
|
+
const renderTooltip = (row, label) => {
|
|
270
|
+
var _a, _b;
|
|
271
|
+
if (!row) return null;
|
|
272
|
+
return /* @__PURE__ */ React4.createElement("div", { className: "rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs shadow-xl dark:border-white/10 dark:bg-slate-950" }, /* @__PURE__ */ React4.createElement("p", { className: "mb-2 font-semibold text-slate-800 dark:text-white/90" }, String((_b = (_a = row[finalXKey]) != null ? _a : label) != null ? _b : "Details")), Object.entries(row).map(([key, value]) => {
|
|
273
|
+
var _a2;
|
|
274
|
+
if (value === null || value === void 0 || key === finalXKey) return null;
|
|
275
|
+
if (typeof value === "object") return null;
|
|
276
|
+
return /* @__PURE__ */ React4.createElement("div", { key, className: "flex items-center justify-between gap-3 text-slate-600 dark:text-white/70" }, /* @__PURE__ */ React4.createElement("span", null, (_a2 = tooltipLabelMap[key]) != null ? _a2 : key), /* @__PURE__ */ React4.createElement("span", { className: "font-medium text-slate-900 dark:text-white/90" }, String(value)));
|
|
277
|
+
}));
|
|
278
|
+
};
|
|
279
|
+
const getTooltipRow = (payload) => {
|
|
280
|
+
if (!Array.isArray(payload) || payload.length === 0) return null;
|
|
281
|
+
const first = payload[0];
|
|
282
|
+
if (!first || typeof first !== "object" || !("payload" in first)) return null;
|
|
283
|
+
const row = first.payload;
|
|
284
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return null;
|
|
285
|
+
return row;
|
|
286
|
+
};
|
|
236
287
|
if (type === "pie") {
|
|
237
|
-
const pieDataKey = finalDataKeys[0] || "value";
|
|
238
288
|
return /* @__PURE__ */ React4.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "99%", height: "100%" }, /* @__PURE__ */ React4.createElement(PieChart, null, /* @__PURE__ */ React4.createElement(
|
|
239
289
|
Pie,
|
|
240
290
|
{
|
|
@@ -246,21 +296,10 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
246
296
|
outerRadius: 80,
|
|
247
297
|
label: ({ name, percent }) => `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`
|
|
248
298
|
},
|
|
249
|
-
|
|
250
|
-
), /* @__PURE__ */ React4.createElement(
|
|
251
|
-
Tooltip,
|
|
252
|
-
{
|
|
253
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" }
|
|
254
|
-
}
|
|
255
|
-
), /* @__PURE__ */ React4.createElement(Legend, null))));
|
|
299
|
+
sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
300
|
+
), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ React4.createElement(Legend, null))));
|
|
256
301
|
}
|
|
257
|
-
return /* @__PURE__ */ React4.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ React4.createElement(BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(
|
|
258
|
-
Tooltip,
|
|
259
|
-
{
|
|
260
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" },
|
|
261
|
-
cursor: { fill: "#f1f5f9" }
|
|
262
|
-
}
|
|
263
|
-
), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
|
|
302
|
+
return /* @__PURE__ */ React4.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ React4.createElement(BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: { fontSize: 12, fill: "#64748b" }, 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" } }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
|
|
264
303
|
Bar,
|
|
265
304
|
{
|
|
266
305
|
key,
|
|
@@ -269,12 +308,7 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
269
308
|
radius: [4, 4, 0, 0],
|
|
270
309
|
barSize: Math.max(20, 60 / data.length)
|
|
271
310
|
}
|
|
272
|
-
))) : /* @__PURE__ */ React4.createElement(LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(
|
|
273
|
-
Tooltip,
|
|
274
|
-
{
|
|
275
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" }
|
|
276
|
-
}
|
|
277
|
-
), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
|
|
311
|
+
))) : /* @__PURE__ */ React4.createElement(LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: finalXKey, tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
|
|
278
312
|
Line,
|
|
279
313
|
{
|
|
280
314
|
key,
|
|
@@ -403,37 +437,84 @@ function DataTable({ config }) {
|
|
|
403
437
|
))
|
|
404
438
|
))))), /* @__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" : "")));
|
|
405
439
|
}
|
|
406
|
-
function
|
|
440
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart }) {
|
|
441
|
+
var _a;
|
|
407
442
|
const result = React5.useMemo(() => {
|
|
408
443
|
if (isStreaming) return { loading: true };
|
|
409
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
410
444
|
try {
|
|
411
445
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
412
|
-
const
|
|
446
|
+
const parsed = JSON.parse(sanitizeJson(clean));
|
|
447
|
+
const config2 = __spreadValues({}, parsed);
|
|
448
|
+
if (!config2.view) {
|
|
449
|
+
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
450
|
+
config2.view = "carousel";
|
|
451
|
+
config2.data = config2.data || config2.items || [];
|
|
452
|
+
} else if (["pie", "bar", "line"].includes(config2.type) || ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
453
|
+
config2.view = "chart";
|
|
454
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
455
|
+
config2.data = config2.data || [];
|
|
456
|
+
} else {
|
|
457
|
+
config2.view = "table";
|
|
458
|
+
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
413
461
|
return { config: config2 };
|
|
414
462
|
} catch (err) {
|
|
415
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err);
|
|
416
463
|
return { error: String(err) };
|
|
417
464
|
}
|
|
418
465
|
}, [rawContent, isStreaming]);
|
|
419
466
|
if ("loading" in result) {
|
|
420
|
-
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3
|
|
467
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 animate-pulse" }, /* @__PURE__ */ React5.createElement("div", { className: "w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic" }, "Preparing view..."));
|
|
421
468
|
}
|
|
422
469
|
if ("error" in result) {
|
|
423
|
-
return /* @__PURE__ */ React5.createElement("
|
|
470
|
+
return /* @__PURE__ */ React5.createElement("pre", { className: "p-4 my-2 bg-slate-900 text-slate-100 rounded-lg text-[10px] overflow-auto max-h-40" }, /* @__PURE__ */ React5.createElement("code", null, rawContent));
|
|
424
471
|
}
|
|
425
472
|
const { config } = result;
|
|
426
|
-
|
|
427
|
-
|
|
473
|
+
const hasInsights = Array.isArray(config.insights) && config.insights.length > 0;
|
|
474
|
+
const hasDescription = typeof config.description === "string" && config.description.trim().length > 0;
|
|
475
|
+
switch (config.view) {
|
|
476
|
+
case "chart":
|
|
477
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-6 p-4 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-4 px-2" }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: "px-2 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ React5.createElement(
|
|
478
|
+
DynamicChart,
|
|
479
|
+
{
|
|
480
|
+
config: {
|
|
481
|
+
type: config.chartType || "bar",
|
|
482
|
+
data: config.data,
|
|
483
|
+
xAxisKey: config.xAxisKey,
|
|
484
|
+
dataKeys: config.dataKeys,
|
|
485
|
+
colors: config.colors
|
|
486
|
+
},
|
|
487
|
+
primaryColor,
|
|
488
|
+
accentColor
|
|
489
|
+
}
|
|
490
|
+
), hasInsights && /* @__PURE__ */ React5.createElement("div", { className: "mt-4 flex flex-wrap gap-2 px-2" }, (_a = config.insights) == null ? void 0 : _a.map((insight) => /* @__PURE__ */ React5.createElement(
|
|
491
|
+
"span",
|
|
492
|
+
{
|
|
493
|
+
key: insight,
|
|
494
|
+
className: "rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-[11px] font-medium text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300"
|
|
495
|
+
},
|
|
496
|
+
insight
|
|
497
|
+
))));
|
|
498
|
+
case "carousel":
|
|
499
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: "mb-2 text-xs font-semibold text-slate-500" }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: "mb-3 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ React5.createElement(ProductCarousel, { products: config.data.map((item) => {
|
|
500
|
+
var _a2;
|
|
501
|
+
return __spreadProps(__spreadValues({}, item), {
|
|
502
|
+
image: (_a2 = item.image) != null ? _a2 : typeof resolveImage === "function" ? resolveImage(item) : void 0
|
|
503
|
+
});
|
|
504
|
+
}), primaryColor, onAddToCart }));
|
|
505
|
+
case "table":
|
|
506
|
+
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto max-h-[400px]" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-2" }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: "mb-3 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ React5.createElement(DataTable, { config: {
|
|
507
|
+
type: "table",
|
|
508
|
+
title: config.title,
|
|
509
|
+
description: config.description,
|
|
510
|
+
columns: config.columns,
|
|
511
|
+
dataKeys: config.dataKeys,
|
|
512
|
+
xAxisKey: config.xAxisKey,
|
|
513
|
+
data: config.data
|
|
514
|
+
} }));
|
|
515
|
+
default:
|
|
516
|
+
return null;
|
|
428
517
|
}
|
|
429
|
-
return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm" }, /* @__PURE__ */ React5.createElement(
|
|
430
|
-
DynamicChart,
|
|
431
|
-
{
|
|
432
|
-
config,
|
|
433
|
-
primaryColor,
|
|
434
|
-
accentColor
|
|
435
|
-
}
|
|
436
|
-
));
|
|
437
518
|
}
|
|
438
519
|
function MessageBubble({
|
|
439
520
|
message,
|
|
@@ -445,6 +526,10 @@ function MessageBubble({
|
|
|
445
526
|
}) {
|
|
446
527
|
const isUser = message.role === "user";
|
|
447
528
|
const [showSources, setShowSources] = React5.useState(false);
|
|
529
|
+
const hasStructuredProductBlock = React5.useMemo(
|
|
530
|
+
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
|
|
531
|
+
[message.content]
|
|
532
|
+
);
|
|
448
533
|
const productsFromSources = React5.useMemo(() => {
|
|
449
534
|
if (isUser || !sources) return [];
|
|
450
535
|
return sources.filter((s) => {
|
|
@@ -608,24 +693,40 @@ ${match.trim()}
|
|
|
608
693
|
]);
|
|
609
694
|
var _a;
|
|
610
695
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
611
|
-
if (!inline && lang === "chart") {
|
|
696
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
612
697
|
return /* @__PURE__ */ React5.createElement(
|
|
613
|
-
|
|
698
|
+
UIDispatcher,
|
|
614
699
|
{
|
|
615
700
|
rawContent: String(children != null ? children : "").trim(),
|
|
616
701
|
primaryColor,
|
|
617
702
|
accentColor,
|
|
618
|
-
isStreaming
|
|
703
|
+
isStreaming,
|
|
704
|
+
onAddToCart
|
|
619
705
|
}
|
|
620
706
|
);
|
|
621
707
|
}
|
|
708
|
+
if (!inline && lang === "json") {
|
|
709
|
+
const content = String(children != null ? children : "").trim();
|
|
710
|
+
if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
|
|
711
|
+
return /* @__PURE__ */ React5.createElement(
|
|
712
|
+
UIDispatcher,
|
|
713
|
+
{
|
|
714
|
+
rawContent: content,
|
|
715
|
+
primaryColor,
|
|
716
|
+
accentColor,
|
|
717
|
+
isStreaming,
|
|
718
|
+
onAddToCart
|
|
719
|
+
}
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
622
723
|
if (!inline && lang === "table-loading") {
|
|
623
724
|
return /* @__PURE__ */ React5.createElement("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none" }, /* @__PURE__ */ React5.createElement("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic animate-pulse" }, "Generating data table..."));
|
|
624
725
|
}
|
|
625
726
|
return /* @__PURE__ */ React5.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
|
|
626
727
|
}
|
|
627
728
|
}),
|
|
628
|
-
[primaryColor, accentColor, isStreaming]
|
|
729
|
+
[primaryColor, accentColor, isStreaming, onAddToCart]
|
|
629
730
|
);
|
|
630
731
|
return /* @__PURE__ */ React5.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
|
|
631
732
|
"div",
|
|
@@ -641,7 +742,7 @@ ${match.trim()}
|
|
|
641
742
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
642
743
|
},
|
|
643
744
|
isStreaming && !message.content ? /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React5.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
644
|
-
), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
|
|
745
|
+
), !isUser && !hasStructuredProductBlock && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
|
|
645
746
|
ProductCarousel,
|
|
646
747
|
{
|
|
647
748
|
products: allProducts,
|
package/dist/server.js
CHANGED
|
@@ -3913,17 +3913,68 @@ var Pipeline = class {
|
|
|
3913
3913
|
async initialize() {
|
|
3914
3914
|
var _a, _b;
|
|
3915
3915
|
if (this.initialised) return;
|
|
3916
|
-
const CHART_MARKER = "<!--
|
|
3916
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
|
|
3917
3917
|
const chartInstruction = `
|
|
3918
3918
|
|
|
3919
3919
|
${CHART_MARKER}
|
|
3920
|
-
###
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3920
|
+
### UNIVERSAL UI PROTOCOL
|
|
3921
|
+
When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
|
|
3922
|
+
|
|
3923
|
+
1. VIEW SELECTION
|
|
3924
|
+
- Use "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
|
|
3925
|
+
- Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
|
|
3926
|
+
- Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
|
|
3927
|
+
|
|
3928
|
+
2. REQUIRED JSON SHAPE
|
|
3929
|
+
{
|
|
3930
|
+
"view": "chart" | "carousel" | "table",
|
|
3931
|
+
"title": "Short heading",
|
|
3932
|
+
"description": "One sentence describing the view",
|
|
3933
|
+
"chartType": "pie" | "bar" | "line",
|
|
3934
|
+
"xAxisKey": "label",
|
|
3935
|
+
"dataKeys": ["value"],
|
|
3936
|
+
"columns": ["name", "category", "price", "stockStatus"],
|
|
3937
|
+
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3938
|
+
"data": []
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
3. DATA RULES
|
|
3942
|
+
- Build the UI payload from retrieved context only. Do not invent products, categories, or stock values.
|
|
3943
|
+
- Aggregate raw product rows when the user asks for a chart.
|
|
3944
|
+
- For charts, every row in "data" must be flat JSON with primitive values only.
|
|
3945
|
+
- Prefer { "label": "...", "value": number } for pie charts.
|
|
3946
|
+
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
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
|
+
- 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" when possible.
|
|
3950
|
+
|
|
3951
|
+
4. FORMAT RULES
|
|
3952
|
+
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
3953
|
+
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
3954
|
+
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3955
|
+
|
|
3956
|
+
5. EXAMPLE
|
|
3957
|
+
User: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
|
|
3958
|
+
Assistant:
|
|
3959
|
+
\`\`\`ui
|
|
3960
|
+
{
|
|
3961
|
+
"view": "chart",
|
|
3962
|
+
"title": "Product Distribution Across Categories",
|
|
3963
|
+
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
3964
|
+
"chartType": "pie",
|
|
3965
|
+
"xAxisKey": "label",
|
|
3966
|
+
"dataKeys": ["value"],
|
|
3967
|
+
"insights": [
|
|
3968
|
+
"Beauty has the highest product count.",
|
|
3969
|
+
"Electronics has the largest number of in-stock products."
|
|
3970
|
+
],
|
|
3971
|
+
"data": [
|
|
3972
|
+
{ "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
|
|
3973
|
+
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
3974
|
+
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3975
|
+
]
|
|
3976
|
+
}
|
|
3977
|
+
\`\`\``;
|
|
3927
3978
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3928
3979
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3929
3980
|
}
|
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.5.
|
|
3
|
+
"version": "1.5.5",
|
|
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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createStreamHandler } from '@/handlers';
|
|
2
2
|
import { getRagConfig } from '@/config/serverConfig';
|
|
3
3
|
|
|
4
|
-
export const POST =
|
|
4
|
+
export const POST = createStreamHandler(getRagConfig());
|
|
@@ -96,11 +96,85 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
96
96
|
// Final sanity check: ensure all keys are strings
|
|
97
97
|
const finalXKey = String(resolvedXKey);
|
|
98
98
|
const finalDataKeys = resolvedDataKeys.map(String).filter(k => k !== 'undefined');
|
|
99
|
+
const pieDataKey = finalDataKeys[0] || 'value';
|
|
100
|
+
|
|
101
|
+
const stockAwareColor = (entry: Record<string, string | number | null>, index: number) => {
|
|
102
|
+
const stockStatus = String(
|
|
103
|
+
entry.stockStatus ??
|
|
104
|
+
entry.status ??
|
|
105
|
+
entry.availability ??
|
|
106
|
+
'',
|
|
107
|
+
).toLowerCase();
|
|
108
|
+
const inStockCount = typeof entry.inStockCount === 'number' ? entry.inStockCount : undefined;
|
|
109
|
+
const outOfStockCount = typeof entry.outOfStockCount === 'number' ? entry.outOfStockCount : undefined;
|
|
110
|
+
const inStockFlag = entry.inStock;
|
|
111
|
+
|
|
112
|
+
if (stockStatus.includes('in stock')) return '#10b981';
|
|
113
|
+
if (stockStatus.includes('out of stock')) return '#f97316';
|
|
114
|
+
if (typeof inStockFlag === 'string' && inStockFlag.toLowerCase() === 'true') return '#10b981';
|
|
115
|
+
if (inStockFlag === 1) return '#10b981';
|
|
116
|
+
if (inStockFlag === 0) return '#f97316';
|
|
117
|
+
if (typeof inStockCount === 'number' || typeof outOfStockCount === 'number') {
|
|
118
|
+
const inCount = inStockCount ?? 0;
|
|
119
|
+
const outCount = outOfStockCount ?? 0;
|
|
120
|
+
if (inCount > outCount) return '#10b981';
|
|
121
|
+
if (outCount > inCount) return '#f97316';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return colors[index % colors.length];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const tooltipLabelMap: Record<string, string> = {
|
|
128
|
+
value: 'Value',
|
|
129
|
+
count: 'Count',
|
|
130
|
+
inStock: 'In stock',
|
|
131
|
+
inStockCount: 'In stock',
|
|
132
|
+
outOfStockCount: 'Out of stock',
|
|
133
|
+
stockStatus: 'Stock status',
|
|
134
|
+
category: 'Category',
|
|
135
|
+
label: 'Label',
|
|
136
|
+
name: 'Name',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const renderTooltip = (
|
|
140
|
+
row: Record<string, string | number | null> | null,
|
|
141
|
+
label?: string,
|
|
142
|
+
) => {
|
|
143
|
+
if (!row) return null;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs shadow-xl dark:border-white/10 dark:bg-slate-950">
|
|
147
|
+
<p className="mb-2 font-semibold text-slate-800 dark:text-white/90">
|
|
148
|
+
{String(row[finalXKey] ?? label ?? 'Details')}
|
|
149
|
+
</p>
|
|
150
|
+
{Object.entries(row).map(([key, value]) => {
|
|
151
|
+
if (value === null || value === undefined || key === finalXKey) return null;
|
|
152
|
+
if (typeof value === 'object') return null;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div key={key} className="flex items-center justify-between gap-3 text-slate-600 dark:text-white/70">
|
|
156
|
+
<span>{tooltipLabelMap[key] ?? key}</span>
|
|
157
|
+
<span className="font-medium text-slate-900 dark:text-white/90">{String(value)}</span>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getTooltipRow = (payload: unknown): Record<string, string | number | null> | null => {
|
|
166
|
+
if (!Array.isArray(payload) || payload.length === 0) return null;
|
|
167
|
+
const first = payload[0];
|
|
168
|
+
if (!first || typeof first !== 'object' || !('payload' in first)) return null;
|
|
169
|
+
|
|
170
|
+
const row = (first as { payload?: unknown }).payload;
|
|
171
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) return null;
|
|
172
|
+
|
|
173
|
+
return row as Record<string, string | number | null>;
|
|
174
|
+
};
|
|
99
175
|
|
|
100
176
|
// Handle Pie Chart separately as it has a different structure
|
|
101
177
|
if (type === 'pie') {
|
|
102
|
-
const pieDataKey = finalDataKeys[0] || 'value';
|
|
103
|
-
|
|
104
178
|
return (
|
|
105
179
|
<div className="w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden">
|
|
106
180
|
<ResponsiveContainer width="99%" height="100%">
|
|
@@ -114,13 +188,11 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
114
188
|
outerRadius={80}
|
|
115
189
|
label={({ name, percent }) => `${name} ${((percent ?? 0) * 100).toFixed(0)}%`}
|
|
116
190
|
>
|
|
117
|
-
{
|
|
118
|
-
<Cell key={`cell-${index}`} fill={
|
|
191
|
+
{sanitizedData.map((entry, index) => (
|
|
192
|
+
<Cell key={`cell-${index}`} fill={stockAwareColor(entry, index)} />
|
|
119
193
|
))}
|
|
120
194
|
</Pie>
|
|
121
|
-
<Tooltip
|
|
122
|
-
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
123
|
-
/>
|
|
195
|
+
<Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
|
|
124
196
|
<Legend />
|
|
125
197
|
</PieChart>
|
|
126
198
|
</ResponsiveContainer>
|
|
@@ -137,10 +209,7 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
137
209
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
138
210
|
<XAxis dataKey={finalXKey} tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
|
|
139
211
|
<YAxis tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
140
|
-
<Tooltip
|
|
141
|
-
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
142
|
-
cursor={{ fill: '#f1f5f9' }}
|
|
143
|
-
/>
|
|
212
|
+
<Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} cursor={{ fill: '#f1f5f9' }} />
|
|
144
213
|
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
145
214
|
{finalDataKeys.map((key, index) => (
|
|
146
215
|
<Bar
|
|
@@ -157,9 +226,7 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
157
226
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
|
|
158
227
|
<XAxis dataKey={finalXKey} tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={{ stroke: '#cbd5e1' }} />
|
|
159
228
|
<YAxis tick={{ fontSize: 12, fill: '#64748b' }} tickLine={false} axisLine={false} />
|
|
160
|
-
<Tooltip
|
|
161
|
-
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
|
162
|
-
/>
|
|
229
|
+
<Tooltip content={(props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === 'string' ? props.label : undefined)} />
|
|
163
230
|
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
164
231
|
{finalDataKeys.map((key, index) => (
|
|
165
232
|
<Line
|