@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.
@@ -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 = "<!-- VIZ_PROTOCOL_V2 -->";
2262
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
2263
2263
  const chartInstruction = `
2264
2264
 
2265
2265
  ${CHART_MARKER}
2266
- ### MANDATORY VISUALIZATION RULES:
2267
- - If asked for statistics, distribution, or comparisons: You MUST use the \`\`\`chart block.
2268
- - FORMAT: \`\`\`chart { "type": "pie" | "bar" | "line", "data": [ { "label": "X", "value": 10 } ] } \`\`\`
2269
- - RULES:
2270
- 1. ALWAYS include the \`\`\`chart and \`\`\` markers.
2271
- 2. NEVER use text, tables, or ASCII art for charts.
2272
- 3. Output ONLY the chart block. No Python, no text descriptions.`;
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
  }
@@ -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 = "<!-- VIZ_PROTOCOL_V2 -->";
3825
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
3826
3826
  const chartInstruction = `
3827
3827
 
3828
3828
  ${CHART_MARKER}
3829
- ### MANDATORY VISUALIZATION RULES:
3830
- - If asked for statistics, distribution, or comparisons: You MUST use the \`\`\`chart block.
3831
- - FORMAT: \`\`\`chart { "type": "pie" | "bar" | "line", "data": [ { "label": "X", "value": 10 } ] } \`\`\`
3832
- - RULES:
3833
- 1. ALWAYS include the \`\`\`chart and \`\`\` markers.
3834
- 2. NEVER use text, tables, or ASCII art for charts.
3835
- 3. Output ONLY the chart block. No Python, no text descriptions.`;
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
  }
@@ -9,7 +9,7 @@ import {
9
9
  sseFrame,
10
10
  sseMetaFrame,
11
11
  sseTextFrame
12
- } from "../chunk-C4UPQX3E.mjs";
12
+ } from "../chunk-Q4MDH6C4.mjs";
13
13
  import "../chunk-YLTMFW4M.mjs";
14
14
  import "../chunk-X4TOT24V.mjs";
15
15
  export {
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
- data.map((entry, index) => /* @__PURE__ */ import_react4.default.createElement(import_recharts.Cell, { key: `cell-${index}`, fill: colors[index % colors.length] }))
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 ChartBlock({ rawContent, primaryColor, accentColor, isStreaming }) {
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 config2 = JSON.parse(sanitizeJson(clean));
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 select-none" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-6 h-6 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 animate-pulse" }, "Preparing data visualization\u2026"));
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("div", { className: "p-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm border border-red-100 dark:border-red-900/30" }, /* @__PURE__ */ import_react5.default.createElement("p", { className: "font-medium mb-1" }, "Failed to render visualization"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "opacity-70 text-xs" }, "The generated configuration is invalid. Check the console for details."));
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
- if (config.type === "table") {
464
- return /* @__PURE__ */ import_react5.default.createElement(DataTable, { config });
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
- ChartBlock,
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,