@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
|
@@ -2259,17 +2259,68 @@ var Pipeline = class {
|
|
|
2259
2259
|
async initialize() {
|
|
2260
2260
|
var _a, _b;
|
|
2261
2261
|
if (this.initialised) return;
|
|
2262
|
-
const CHART_MARKER = "<!--
|
|
2262
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
|
|
2263
2263
|
const chartInstruction = `
|
|
2264
2264
|
|
|
2265
2265
|
${CHART_MARKER}
|
|
2266
|
-
###
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2266
|
+
### UNIVERSAL UI PROTOCOL
|
|
2267
|
+
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.
|
|
2268
|
+
|
|
2269
|
+
1. VIEW SELECTION
|
|
2270
|
+
- Use "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
|
|
2271
|
+
- Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
|
|
2272
|
+
- Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
|
|
2273
|
+
|
|
2274
|
+
2. REQUIRED JSON SHAPE
|
|
2275
|
+
{
|
|
2276
|
+
"view": "chart" | "carousel" | "table",
|
|
2277
|
+
"title": "Short heading",
|
|
2278
|
+
"description": "One sentence describing the view",
|
|
2279
|
+
"chartType": "pie" | "bar" | "line",
|
|
2280
|
+
"xAxisKey": "label",
|
|
2281
|
+
"dataKeys": ["value"],
|
|
2282
|
+
"columns": ["name", "category", "price", "stockStatus"],
|
|
2283
|
+
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
2284
|
+
"data": []
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
3. DATA RULES
|
|
2288
|
+
- Build the UI payload from retrieved context only. Do not invent products, categories, or stock values.
|
|
2289
|
+
- Aggregate raw product rows when the user asks for a chart.
|
|
2290
|
+
- For charts, every row in "data" must be flat JSON with primitive values only.
|
|
2291
|
+
- Prefer { "label": "...", "value": number } for pie charts.
|
|
2292
|
+
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
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
|
+
- 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" when possible.
|
|
2296
|
+
|
|
2297
|
+
4. FORMAT RULES
|
|
2298
|
+
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
2299
|
+
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
2300
|
+
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
2301
|
+
|
|
2302
|
+
5. EXAMPLE
|
|
2303
|
+
User: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
|
|
2304
|
+
Assistant:
|
|
2305
|
+
\`\`\`ui
|
|
2306
|
+
{
|
|
2307
|
+
"view": "chart",
|
|
2308
|
+
"title": "Product Distribution Across Categories",
|
|
2309
|
+
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
2310
|
+
"chartType": "pie",
|
|
2311
|
+
"xAxisKey": "label",
|
|
2312
|
+
"dataKeys": ["value"],
|
|
2313
|
+
"insights": [
|
|
2314
|
+
"Beauty has the highest product count.",
|
|
2315
|
+
"Electronics has the largest number of in-stock products."
|
|
2316
|
+
],
|
|
2317
|
+
"data": [
|
|
2318
|
+
{ "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
|
|
2319
|
+
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
2320
|
+
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
2321
|
+
]
|
|
2322
|
+
}
|
|
2323
|
+
\`\`\``;
|
|
2273
2324
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
2274
2325
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
2275
2326
|
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -3822,17 +3822,68 @@ var Pipeline = class {
|
|
|
3822
3822
|
async initialize() {
|
|
3823
3823
|
var _a, _b;
|
|
3824
3824
|
if (this.initialised) return;
|
|
3825
|
-
const CHART_MARKER = "<!--
|
|
3825
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
|
|
3826
3826
|
const chartInstruction = `
|
|
3827
3827
|
|
|
3828
3828
|
${CHART_MARKER}
|
|
3829
|
-
###
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3829
|
+
### UNIVERSAL UI PROTOCOL
|
|
3830
|
+
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.
|
|
3831
|
+
|
|
3832
|
+
1. VIEW SELECTION
|
|
3833
|
+
- Use "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
|
|
3834
|
+
- Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
|
|
3835
|
+
- Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
|
|
3836
|
+
|
|
3837
|
+
2. REQUIRED JSON SHAPE
|
|
3838
|
+
{
|
|
3839
|
+
"view": "chart" | "carousel" | "table",
|
|
3840
|
+
"title": "Short heading",
|
|
3841
|
+
"description": "One sentence describing the view",
|
|
3842
|
+
"chartType": "pie" | "bar" | "line",
|
|
3843
|
+
"xAxisKey": "label",
|
|
3844
|
+
"dataKeys": ["value"],
|
|
3845
|
+
"columns": ["name", "category", "price", "stockStatus"],
|
|
3846
|
+
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3847
|
+
"data": []
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
3. DATA RULES
|
|
3851
|
+
- Build the UI payload from retrieved context only. Do not invent products, categories, or stock values.
|
|
3852
|
+
- Aggregate raw product rows when the user asks for a chart.
|
|
3853
|
+
- For charts, every row in "data" must be flat JSON with primitive values only.
|
|
3854
|
+
- Prefer { "label": "...", "value": number } for pie charts.
|
|
3855
|
+
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
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
|
+
- 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" when possible.
|
|
3859
|
+
|
|
3860
|
+
4. FORMAT RULES
|
|
3861
|
+
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
3862
|
+
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
3863
|
+
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3864
|
+
|
|
3865
|
+
5. EXAMPLE
|
|
3866
|
+
User: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
|
|
3867
|
+
Assistant:
|
|
3868
|
+
\`\`\`ui
|
|
3869
|
+
{
|
|
3870
|
+
"view": "chart",
|
|
3871
|
+
"title": "Product Distribution Across Categories",
|
|
3872
|
+
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
3873
|
+
"chartType": "pie",
|
|
3874
|
+
"xAxisKey": "label",
|
|
3875
|
+
"dataKeys": ["value"],
|
|
3876
|
+
"insights": [
|
|
3877
|
+
"Beauty has the highest product count.",
|
|
3878
|
+
"Electronics has the largest number of in-stock products."
|
|
3879
|
+
],
|
|
3880
|
+
"data": [
|
|
3881
|
+
{ "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
|
|
3882
|
+
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
3883
|
+
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3884
|
+
]
|
|
3885
|
+
}
|
|
3886
|
+
\`\`\``;
|
|
3836
3887
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3837
3888
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3838
3889
|
}
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -270,8 +270,58 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
270
270
|
}).slice(0, 5);
|
|
271
271
|
const finalXKey = String(resolvedXKey);
|
|
272
272
|
const finalDataKeys = resolvedDataKeys.map(String).filter((k) => k !== "undefined");
|
|
273
|
+
const pieDataKey = finalDataKeys[0] || "value";
|
|
274
|
+
const stockAwareColor = (entry, index) => {
|
|
275
|
+
var _a, _b, _c;
|
|
276
|
+
const stockStatus = String(
|
|
277
|
+
(_c = (_b = (_a = entry.stockStatus) != null ? _a : entry.status) != null ? _b : entry.availability) != null ? _c : ""
|
|
278
|
+
).toLowerCase();
|
|
279
|
+
const inStockCount = typeof entry.inStockCount === "number" ? entry.inStockCount : void 0;
|
|
280
|
+
const outOfStockCount = typeof entry.outOfStockCount === "number" ? entry.outOfStockCount : void 0;
|
|
281
|
+
const inStockFlag = entry.inStock;
|
|
282
|
+
if (stockStatus.includes("in stock")) return "#10b981";
|
|
283
|
+
if (stockStatus.includes("out of stock")) return "#f97316";
|
|
284
|
+
if (typeof inStockFlag === "string" && inStockFlag.toLowerCase() === "true") return "#10b981";
|
|
285
|
+
if (inStockFlag === 1) return "#10b981";
|
|
286
|
+
if (inStockFlag === 0) return "#f97316";
|
|
287
|
+
if (typeof inStockCount === "number" || typeof outOfStockCount === "number") {
|
|
288
|
+
const inCount = inStockCount != null ? inStockCount : 0;
|
|
289
|
+
const outCount = outOfStockCount != null ? outOfStockCount : 0;
|
|
290
|
+
if (inCount > outCount) return "#10b981";
|
|
291
|
+
if (outCount > inCount) return "#f97316";
|
|
292
|
+
}
|
|
293
|
+
return colors[index % colors.length];
|
|
294
|
+
};
|
|
295
|
+
const tooltipLabelMap = {
|
|
296
|
+
value: "Value",
|
|
297
|
+
count: "Count",
|
|
298
|
+
inStock: "In stock",
|
|
299
|
+
inStockCount: "In stock",
|
|
300
|
+
outOfStockCount: "Out of stock",
|
|
301
|
+
stockStatus: "Stock status",
|
|
302
|
+
category: "Category",
|
|
303
|
+
label: "Label",
|
|
304
|
+
name: "Name"
|
|
305
|
+
};
|
|
306
|
+
const renderTooltip = (row, label) => {
|
|
307
|
+
var _a, _b;
|
|
308
|
+
if (!row) return null;
|
|
309
|
+
return /* @__PURE__ */ import_react4.default.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__ */ import_react4.default.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]) => {
|
|
310
|
+
var _a2;
|
|
311
|
+
if (value === null || value === void 0 || key === finalXKey) return null;
|
|
312
|
+
if (typeof value === "object") return null;
|
|
313
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { key, className: "flex items-center justify-between gap-3 text-slate-600 dark:text-white/70" }, /* @__PURE__ */ import_react4.default.createElement("span", null, (_a2 = tooltipLabelMap[key]) != null ? _a2 : key), /* @__PURE__ */ import_react4.default.createElement("span", { className: "font-medium text-slate-900 dark:text-white/90" }, String(value)));
|
|
314
|
+
}));
|
|
315
|
+
};
|
|
316
|
+
const getTooltipRow = (payload) => {
|
|
317
|
+
if (!Array.isArray(payload) || payload.length === 0) return null;
|
|
318
|
+
const first = payload[0];
|
|
319
|
+
if (!first || typeof first !== "object" || !("payload" in first)) return null;
|
|
320
|
+
const row = first.payload;
|
|
321
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return null;
|
|
322
|
+
return row;
|
|
323
|
+
};
|
|
273
324
|
if (type === "pie") {
|
|
274
|
-
const pieDataKey = finalDataKeys[0] || "value";
|
|
275
325
|
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "99%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.PieChart, null, /* @__PURE__ */ import_react4.default.createElement(
|
|
276
326
|
import_recharts.Pie,
|
|
277
327
|
{
|
|
@@ -283,21 +333,10 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
283
333
|
outerRadius: 80,
|
|
284
334
|
label: ({ name, percent }) => `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`
|
|
285
335
|
},
|
|
286
|
-
|
|
287
|
-
), /* @__PURE__ */ import_react4.default.createElement(
|
|
288
|
-
import_recharts.Tooltip,
|
|
289
|
-
{
|
|
290
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" }
|
|
291
|
-
}
|
|
292
|
-
), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null))));
|
|
336
|
+
sanitizedData.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
|
|
337
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null))));
|
|
293
338
|
}
|
|
294
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: false }), /* @__PURE__ */ import_react4.default.createElement(
|
|
295
|
-
import_recharts.Tooltip,
|
|
296
|
-
{
|
|
297
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" },
|
|
298
|
-
cursor: { fill: "#f1f5f9" }
|
|
299
|
-
}
|
|
300
|
-
), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
|
|
339
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full h-72 min-h-[280px] mt-4 mb-2 select-none overflow-hidden" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "99%", height: "100%" }, type === "bar" ? /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12, fill: "#64748b" }, 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" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
|
|
301
340
|
import_recharts.Bar,
|
|
302
341
|
{
|
|
303
342
|
key,
|
|
@@ -306,12 +345,7 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
306
345
|
radius: [4, 4, 0, 0],
|
|
307
346
|
barSize: Math.max(20, 60 / data.length)
|
|
308
347
|
}
|
|
309
|
-
))) : /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: false }), /* @__PURE__ */ import_react4.default.createElement(
|
|
310
|
-
import_recharts.Tooltip,
|
|
311
|
-
{
|
|
312
|
-
contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" }
|
|
313
|
-
}
|
|
314
|
-
), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
|
|
348
|
+
))) : /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: sanitizedData, margin: { top: 10, right: 10, left: -20, bottom: 0 } }, /* @__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: { fontSize: 12, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12, fill: "#64748b" }, 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) }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, { wrapperStyle: { fontSize: 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ import_react4.default.createElement(
|
|
315
349
|
import_recharts.Line,
|
|
316
350
|
{
|
|
317
351
|
key,
|
|
@@ -440,37 +474,84 @@ function DataTable({ config }) {
|
|
|
440
474
|
))
|
|
441
475
|
))))), /* @__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" : "")));
|
|
442
476
|
}
|
|
443
|
-
function
|
|
477
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart }) {
|
|
478
|
+
var _a;
|
|
444
479
|
const result = import_react5.default.useMemo(() => {
|
|
445
480
|
if (isStreaming) return { loading: true };
|
|
446
|
-
if (!rawContent || rawContent === "undefined") return { error: "Empty visualization config." };
|
|
447
481
|
try {
|
|
448
482
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
449
|
-
const
|
|
483
|
+
const parsed = JSON.parse(sanitizeJson(clean));
|
|
484
|
+
const config2 = __spreadValues({}, parsed);
|
|
485
|
+
if (!config2.view) {
|
|
486
|
+
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
487
|
+
config2.view = "carousel";
|
|
488
|
+
config2.data = config2.data || config2.items || [];
|
|
489
|
+
} else if (["pie", "bar", "line"].includes(config2.type) || ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
490
|
+
config2.view = "chart";
|
|
491
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
492
|
+
config2.data = config2.data || [];
|
|
493
|
+
} else {
|
|
494
|
+
config2.view = "table";
|
|
495
|
+
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
450
498
|
return { config: config2 };
|
|
451
499
|
} catch (err) {
|
|
452
|
-
console.error("[ChartBlock] Parsing failed.\nError:", err);
|
|
453
500
|
return { error: String(err) };
|
|
454
501
|
}
|
|
455
502
|
}, [rawContent, isStreaming]);
|
|
456
503
|
if ("loading" in result) {
|
|
457
|
-
return /* @__PURE__ */ import_react5.default.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
|
|
504
|
+
return /* @__PURE__ */ import_react5.default.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__ */ import_react5.default.createElement("div", { className: "w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium italic" }, "Preparing view..."));
|
|
458
505
|
}
|
|
459
506
|
if ("error" in result) {
|
|
460
|
-
return /* @__PURE__ */ import_react5.default.createElement("
|
|
507
|
+
return /* @__PURE__ */ import_react5.default.createElement("pre", { className: "p-4 my-2 bg-slate-900 text-slate-100 rounded-lg text-[10px] overflow-auto max-h-40" }, /* @__PURE__ */ import_react5.default.createElement("code", null, rawContent));
|
|
461
508
|
}
|
|
462
509
|
const { config } = result;
|
|
463
|
-
|
|
464
|
-
|
|
510
|
+
const hasInsights = Array.isArray(config.insights) && config.insights.length > 0;
|
|
511
|
+
const hasDescription = typeof config.description === "string" && config.description.trim().length > 0;
|
|
512
|
+
switch (config.view) {
|
|
513
|
+
case "chart":
|
|
514
|
+
return /* @__PURE__ */ import_react5.default.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__ */ import_react5.default.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-4 px-2" }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: "px-2 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ import_react5.default.createElement(
|
|
515
|
+
DynamicChart,
|
|
516
|
+
{
|
|
517
|
+
config: {
|
|
518
|
+
type: config.chartType || "bar",
|
|
519
|
+
data: config.data,
|
|
520
|
+
xAxisKey: config.xAxisKey,
|
|
521
|
+
dataKeys: config.dataKeys,
|
|
522
|
+
colors: config.colors
|
|
523
|
+
},
|
|
524
|
+
primaryColor,
|
|
525
|
+
accentColor
|
|
526
|
+
}
|
|
527
|
+
), hasInsights && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mt-4 flex flex-wrap gap-2 px-2" }, (_a = config.insights) == null ? void 0 : _a.map((insight) => /* @__PURE__ */ import_react5.default.createElement(
|
|
528
|
+
"span",
|
|
529
|
+
{
|
|
530
|
+
key: insight,
|
|
531
|
+
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"
|
|
532
|
+
},
|
|
533
|
+
insight
|
|
534
|
+
))));
|
|
535
|
+
case "carousel":
|
|
536
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: "mb-2 text-xs font-semibold text-slate-500" }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mb-3 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ import_react5.default.createElement(ProductCarousel, { products: config.data.map((item) => {
|
|
537
|
+
var _a2;
|
|
538
|
+
return __spreadProps(__spreadValues({}, item), {
|
|
539
|
+
image: (_a2 = item.image) != null ? _a2 : typeof resolveImage === "function" ? resolveImage(item) : void 0
|
|
540
|
+
});
|
|
541
|
+
}), primaryColor, onAddToCart }));
|
|
542
|
+
case "table":
|
|
543
|
+
return /* @__PURE__ */ import_react5.default.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__ */ import_react5.default.createElement("h4", { className: "text-xs font-semibold text-slate-500 mb-2" }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mb-3 text-sm text-slate-500 dark:text-white/60" }, config.description), /* @__PURE__ */ import_react5.default.createElement(DataTable, { config: {
|
|
544
|
+
type: "table",
|
|
545
|
+
title: config.title,
|
|
546
|
+
description: config.description,
|
|
547
|
+
columns: config.columns,
|
|
548
|
+
dataKeys: config.dataKeys,
|
|
549
|
+
xAxisKey: config.xAxisKey,
|
|
550
|
+
data: config.data
|
|
551
|
+
} }));
|
|
552
|
+
default:
|
|
553
|
+
return null;
|
|
465
554
|
}
|
|
466
|
-
return /* @__PURE__ */ import_react5.default.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__ */ import_react5.default.createElement(
|
|
467
|
-
DynamicChart,
|
|
468
|
-
{
|
|
469
|
-
config,
|
|
470
|
-
primaryColor,
|
|
471
|
-
accentColor
|
|
472
|
-
}
|
|
473
|
-
));
|
|
474
555
|
}
|
|
475
556
|
function MessageBubble({
|
|
476
557
|
message,
|
|
@@ -482,6 +563,10 @@ function MessageBubble({
|
|
|
482
563
|
}) {
|
|
483
564
|
const isUser = message.role === "user";
|
|
484
565
|
const [showSources, setShowSources] = import_react5.default.useState(false);
|
|
566
|
+
const hasStructuredProductBlock = import_react5.default.useMemo(
|
|
567
|
+
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
|
|
568
|
+
[message.content]
|
|
569
|
+
);
|
|
485
570
|
const productsFromSources = import_react5.default.useMemo(() => {
|
|
486
571
|
if (isUser || !sources) return [];
|
|
487
572
|
return sources.filter((s) => {
|
|
@@ -645,24 +730,40 @@ ${match.trim()}
|
|
|
645
730
|
]);
|
|
646
731
|
var _a;
|
|
647
732
|
const lang = (_a = /language-(\w+)/.exec(className != null ? className : "")) == null ? void 0 : _a[1];
|
|
648
|
-
if (!inline && lang === "chart") {
|
|
733
|
+
if (!inline && (lang === "ui" || lang === "chart")) {
|
|
649
734
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
650
|
-
|
|
735
|
+
UIDispatcher,
|
|
651
736
|
{
|
|
652
737
|
rawContent: String(children != null ? children : "").trim(),
|
|
653
738
|
primaryColor,
|
|
654
739
|
accentColor,
|
|
655
|
-
isStreaming
|
|
740
|
+
isStreaming,
|
|
741
|
+
onAddToCart
|
|
656
742
|
}
|
|
657
743
|
);
|
|
658
744
|
}
|
|
745
|
+
if (!inline && lang === "json") {
|
|
746
|
+
const content = String(children != null ? children : "").trim();
|
|
747
|
+
if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
|
|
748
|
+
return /* @__PURE__ */ import_react5.default.createElement(
|
|
749
|
+
UIDispatcher,
|
|
750
|
+
{
|
|
751
|
+
rawContent: content,
|
|
752
|
+
primaryColor,
|
|
753
|
+
accentColor,
|
|
754
|
+
isStreaming,
|
|
755
|
+
onAddToCart
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
659
760
|
if (!inline && lang === "table-loading") {
|
|
660
761
|
return /* @__PURE__ */ import_react5.default.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__ */ import_react5.default.createElement("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium italic animate-pulse" }, "Generating data table..."));
|
|
661
762
|
}
|
|
662
763
|
return /* @__PURE__ */ import_react5.default.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
|
|
663
764
|
}
|
|
664
765
|
}),
|
|
665
|
-
[primaryColor, accentColor, isStreaming]
|
|
766
|
+
[primaryColor, accentColor, isStreaming, onAddToCart]
|
|
666
767
|
);
|
|
667
768
|
return /* @__PURE__ */ import_react5.default.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ import_react5.default.createElement(
|
|
668
769
|
"div",
|
|
@@ -678,7 +779,7 @@ ${match.trim()}
|
|
|
678
779
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
679
780
|
},
|
|
680
781
|
isStreaming && !message.content ? /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react5.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
681
|
-
), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
782
|
+
), !isUser && !hasStructuredProductBlock && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
682
783
|
ProductCarousel,
|
|
683
784
|
{
|
|
684
785
|
products: allProducts,
|