@retrivora-ai/rag-engine 1.5.4 → 1.5.6

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/index.mjs CHANGED
@@ -123,7 +123,7 @@ function ProductCarousel({ products, primaryColor = "#6366f1", onAddToCart }) {
123
123
  };
124
124
  if (!products || products.length === 0) return null;
125
125
  if (products.length === 1) {
126
- return /* @__PURE__ */ React3.createElement("div", { className: "my-4 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px] animate-fade-in-up" }, /* @__PURE__ */ React3.createElement(ProductCard, { product: products[0], primaryColor, onAddToCart }));
126
+ return /* @__PURE__ */ React3.createElement("div", { className: "my-4 w-[min(88%,18rem)] min-w-0 max-w-full animate-fade-in-up" }, /* @__PURE__ */ React3.createElement(ProductCard, { product: products[0], primaryColor, onAddToCart }));
127
127
  }
128
128
  return /* @__PURE__ */ React3.createElement("div", { className: "relative w-full my-4 group/carousel animate-fade-in-up" }, canScroll && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", { className: "absolute -left-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ React3.createElement(
129
129
  "button",
@@ -158,7 +158,7 @@ function ProductCarousel({ products, primaryColor = "#6366f1", onAddToCart }) {
158
158
  "div",
159
159
  {
160
160
  key: product.id,
161
- className: "snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
161
+ className: "snap-start flex-shrink-0 w-[min(88%,18rem)] min-w-[11rem] max-w-full"
162
162
  },
163
163
  /* @__PURE__ */ React3.createElement(ProductCard, { product, primaryColor, onAddToCart })
164
164
  ))
@@ -183,9 +183,20 @@ import {
183
183
  ResponsiveContainer
184
184
  } from "recharts";
185
185
  var DEFAULT_COLORS = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
186
- function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6" }) {
186
+ function DynamicChart({
187
+ config,
188
+ primaryColor = "#6366f1",
189
+ accentColor = "#8b5cf6",
190
+ viewportSize = "large"
191
+ }) {
187
192
  const { type, data } = config;
188
193
  const colors = config.colors || [primaryColor, accentColor, ...DEFAULT_COLORS];
194
+ const isCompact = viewportSize === "compact";
195
+ const isMedium = viewportSize === "medium";
196
+ const chartHeightClass = isCompact ? "h-56 min-h-[220px]" : isMedium ? "h-64 min-h-[250px]" : "h-72 min-h-[280px]";
197
+ const pieOuterRadius = isCompact ? 56 : isMedium ? 68 : 80;
198
+ const pieLabel = ({ name, percent }) => isCompact ? `${name}` : `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`;
199
+ const axisTick = { fontSize: isCompact ? 10 : 12, fill: "#64748b" };
189
200
  const sanitizedData = React4.useMemo(() => {
190
201
  if (!data) return [];
191
202
  return data.map((item) => {
@@ -233,9 +244,59 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
233
244
  }).slice(0, 5);
234
245
  const finalXKey = String(resolvedXKey);
235
246
  const finalDataKeys = resolvedDataKeys.map(String).filter((k) => k !== "undefined");
247
+ const pieDataKey = finalDataKeys[0] || "value";
248
+ const stockAwareColor = (entry, index) => {
249
+ var _a, _b, _c;
250
+ const stockStatus = String(
251
+ (_c = (_b = (_a = entry.stockStatus) != null ? _a : entry.status) != null ? _b : entry.availability) != null ? _c : ""
252
+ ).toLowerCase();
253
+ const inStockCount = typeof entry.inStockCount === "number" ? entry.inStockCount : void 0;
254
+ const outOfStockCount = typeof entry.outOfStockCount === "number" ? entry.outOfStockCount : void 0;
255
+ const inStockFlag = entry.inStock;
256
+ if (stockStatus.includes("in stock")) return "#10b981";
257
+ if (stockStatus.includes("out of stock")) return "#f97316";
258
+ if (typeof inStockFlag === "string" && inStockFlag.toLowerCase() === "true") return "#10b981";
259
+ if (inStockFlag === 1) return "#10b981";
260
+ if (inStockFlag === 0) return "#f97316";
261
+ if (typeof inStockCount === "number" || typeof outOfStockCount === "number") {
262
+ const inCount = inStockCount != null ? inStockCount : 0;
263
+ const outCount = outOfStockCount != null ? outOfStockCount : 0;
264
+ if (inCount > outCount) return "#10b981";
265
+ if (outCount > inCount) return "#f97316";
266
+ }
267
+ return colors[index % colors.length];
268
+ };
269
+ const tooltipLabelMap = {
270
+ value: "Value",
271
+ count: "Count",
272
+ inStock: "In stock",
273
+ inStockCount: "In stock",
274
+ outOfStockCount: "Out of stock",
275
+ stockStatus: "Stock status",
276
+ category: "Category",
277
+ label: "Label",
278
+ name: "Name"
279
+ };
280
+ const renderTooltip = (row, label) => {
281
+ var _a, _b;
282
+ if (!row) return null;
283
+ 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]) => {
284
+ var _a2;
285
+ if (value === null || value === void 0 || key === finalXKey) return null;
286
+ if (typeof value === "object") return null;
287
+ 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)));
288
+ }));
289
+ };
290
+ const getTooltipRow = (payload) => {
291
+ if (!Array.isArray(payload) || payload.length === 0) return null;
292
+ const first = payload[0];
293
+ if (!first || typeof first !== "object" || !("payload" in first)) return null;
294
+ const row = first.payload;
295
+ if (!row || typeof row !== "object" || Array.isArray(row)) return null;
296
+ return row;
297
+ };
236
298
  if (type === "pie") {
237
- const pieDataKey = finalDataKeys[0] || "value";
238
- 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(
299
+ return /* @__PURE__ */ React4.createElement("div", { className: `w-full ${chartHeightClass} 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
300
  Pie,
240
301
  {
241
302
  data: sanitizedData,
@@ -243,47 +304,31 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
243
304
  nameKey: finalXKey,
244
305
  cx: "50%",
245
306
  cy: "50%",
246
- outerRadius: 80,
247
- label: ({ name, percent }) => `${name} ${((percent != null ? percent : 0) * 100).toFixed(0)}%`
307
+ outerRadius: pieOuterRadius,
308
+ label: pieLabel
248
309
  },
249
- data.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: colors[index % colors.length] }))
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))));
310
+ sanitizedData.map((entry, index) => /* @__PURE__ */ React4.createElement(Cell, { key: `cell-${index}`, fill: stockAwareColor(entry, index) }))
311
+ ), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }))));
256
312
  }
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(
313
+ return /* @__PURE__ */ React4.createElement("div", { className: `w-full ${chartHeightClass} 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: axisTick, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: axisTick, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0), cursor: { fill: "#f1f5f9" } }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
264
314
  Bar,
265
315
  {
266
316
  key,
267
317
  dataKey: key,
268
318
  fill: colors[index % colors.length],
269
319
  radius: [4, 4, 0, 0],
270
- barSize: Math.max(20, 60 / data.length)
320
+ barSize: isCompact ? Math.max(14, 42 / data.length) : Math.max(20, 60 / data.length)
271
321
  }
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(
322
+ ))) : /* @__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: axisTick, tickLine: false, axisLine: { stroke: "#cbd5e1" } }), /* @__PURE__ */ React4.createElement(YAxis, { tick: axisTick, tickLine: false, axisLine: false }), /* @__PURE__ */ React4.createElement(Tooltip, { content: (props) => renderTooltip(getTooltipRow(props.payload), typeof props.label === "string" ? props.label : void 0) }), /* @__PURE__ */ React4.createElement(Legend, { wrapperStyle: { fontSize: isCompact ? 10 : 12 } }), finalDataKeys.map((key, index) => /* @__PURE__ */ React4.createElement(
278
323
  Line,
279
324
  {
280
325
  key,
281
326
  type: "monotone",
282
327
  dataKey: key,
283
328
  stroke: colors[index % colors.length],
284
- strokeWidth: 3,
285
- dot: { r: 4, strokeWidth: 2 },
286
- activeDot: { r: 6 }
329
+ strokeWidth: isCompact ? 2 : 3,
330
+ dot: { r: isCompact ? 3 : 4, strokeWidth: 2 },
331
+ activeDot: { r: isCompact ? 5 : 6 }
287
332
  }
288
333
  )))));
289
334
  }
@@ -342,6 +387,14 @@ function sanitizeJson(raw) {
342
387
  }
343
388
  return s;
344
389
  }
390
+ function extractLikelyJsonBlock(raw) {
391
+ const trimmed = raw.trim();
392
+ const objectStart = trimmed.indexOf("{");
393
+ const arrayStart = trimmed.indexOf("[");
394
+ const starts = [objectStart, arrayStart].filter((index) => index >= 0);
395
+ if (starts.length === 0) return trimmed;
396
+ return trimmed.slice(Math.min(...starts));
397
+ }
345
398
  function resolveImage(data) {
346
399
  for (const key of ["image", "img", "thumbnail"]) {
347
400
  const v = data[key];
@@ -358,7 +411,9 @@ function normaliseChild(children) {
358
411
  }
359
412
  return children;
360
413
  }
361
- function DataTable({ config }) {
414
+ function DataTable({ config, viewportSize = "large" }) {
415
+ const isCompact = viewportSize === "compact";
416
+ const isMedium = viewportSize === "medium";
362
417
  const keys = React5.useMemo(() => {
363
418
  if (Array.isArray(config.columns) && config.columns.length) {
364
419
  return config.columns;
@@ -380,11 +435,11 @@ function DataTable({ config }) {
380
435
  if (typeof val === "boolean") return val ? "\u2713" : "\u2717";
381
436
  return String(val);
382
437
  };
383
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: "w-full text-left border-collapse min-w-[320px] text-sm" }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null, keys.map((k) => /* @__PURE__ */ React5.createElement(
438
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-hidden" }, config.title && /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-2.5 bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("p", { className: "text-xs font-semibold text-slate-600 dark:text-white/70 uppercase tracking-wide" }, config.title)), /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: `w-full text-left border-collapse ${isCompact ? "min-w-[240px] text-xs" : isMedium ? "min-w-[280px] text-[13px]" : "min-w-[320px] text-sm"}` }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-slate-50 dark:bg-white/5 border-b border-slate-200 dark:border-white/10" }, /* @__PURE__ */ React5.createElement("tr", null, keys.map((k) => /* @__PURE__ */ React5.createElement(
384
439
  "th",
385
440
  {
386
441
  key: k,
387
- className: "px-4 py-3 font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap"
442
+ className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} font-semibold text-slate-700 dark:text-white/90 whitespace-nowrap`
388
443
  },
389
444
  k
390
445
  )))), /* @__PURE__ */ React5.createElement("tbody", null, config.data.map((row, ri) => /* @__PURE__ */ React5.createElement(
@@ -397,33 +452,54 @@ function DataTable({ config }) {
397
452
  "td",
398
453
  {
399
454
  key: k,
400
- className: "px-4 py-3 text-slate-600 dark:text-white/70 whitespace-nowrap"
455
+ className: `${isCompact ? "px-2.5 py-2" : "px-4 py-3"} text-slate-600 dark:text-white/70 whitespace-nowrap`
401
456
  },
402
457
  formatCell(row[k])
403
458
  ))
404
459
  ))))), /* @__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
460
  }
406
- function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming }) {
461
+ function normalizeTabularRows(rows, columns) {
462
+ if (!Array.isArray(rows)) return [];
463
+ return rows.map((row) => {
464
+ if (Array.isArray(row)) {
465
+ const keys = Array.isArray(columns) && columns.length > 0 ? columns : row.map((_, index) => `column_${index + 1}`);
466
+ return keys.reduce((acc, key, index) => {
467
+ acc[key] = row[index];
468
+ return acc;
469
+ }, {});
470
+ }
471
+ if (row && typeof row === "object") {
472
+ return row;
473
+ }
474
+ return { value: row };
475
+ }).filter((row) => Object.keys(row).length > 0);
476
+ }
477
+ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
478
+ var _a;
407
479
  const result = React5.useMemo(() => {
408
480
  if (isStreaming) return { loading: true };
409
481
  try {
410
482
  const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
411
- const parsed = JSON.parse(sanitizeJson(clean));
483
+ const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
412
484
  const config2 = __spreadValues({}, parsed);
413
485
  if (!config2.view) {
414
486
  if (config2.type === "products" || Array.isArray(config2.items)) {
415
487
  config2.view = "carousel";
416
488
  config2.data = config2.data || config2.items || [];
417
- } else if (["pie", "bar", "line"].includes(config2.type) || ["pie", "bar", "line"].includes(config2.chartType)) {
489
+ } else if (typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
490
+ const resolvedChartType = config2.chartType === "pie" || config2.chartType === "bar" || config2.chartType === "line" ? config2.chartType : config2.type === "pie" || config2.type === "bar" || config2.type === "line" ? config2.type : "bar";
418
491
  config2.view = "chart";
419
- config2.chartType = config2.chartType || config2.type || "bar";
492
+ config2.chartType = resolvedChartType;
420
493
  config2.data = config2.data || [];
421
494
  } else {
422
495
  config2.view = "table";
423
496
  config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
424
497
  }
425
498
  }
426
- return { config: config2 };
499
+ const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
500
+ data: normalizeTabularRows(config2.data, config2.columns)
501
+ });
502
+ return { config: normalizedConfig };
427
503
  } catch (err) {
428
504
  return { error: String(err) };
429
505
  }
@@ -435,28 +511,50 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming }) {
435
511
  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));
436
512
  }
437
513
  const { config } = result;
514
+ const isCompact = viewportSize === "compact";
515
+ const hasInsights = Array.isArray(config.insights) && config.insights.length > 0;
516
+ const hasDescription = typeof config.description === "string" && config.description.trim().length > 0;
438
517
  switch (config.view) {
439
518
  case "chart":
440
- 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), /* @__PURE__ */ React5.createElement(
519
+ return /* @__PURE__ */ React5.createElement("div", { className: `${isCompact ? "my-4 p-3" : "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: `${isCompact ? "text-[11px] mb-3" : "text-xs mb-4"} font-semibold text-slate-500 px-2` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `px-2 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ React5.createElement(
441
520
  DynamicChart,
442
521
  {
443
522
  config: {
444
523
  type: config.chartType || "bar",
445
- data: config.data
524
+ data: config.data,
525
+ xAxisKey: config.xAxisKey,
526
+ dataKeys: config.dataKeys,
527
+ colors: config.colors
446
528
  },
447
529
  primaryColor,
448
- accentColor
530
+ accentColor,
531
+ viewportSize
449
532
  }
450
- ));
533
+ ), 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(
534
+ "span",
535
+ {
536
+ key: insight,
537
+ className: `rounded-full border border-emerald-200 bg-emerald-50 ${isCompact ? "px-2.5 py-1 text-[10px]" : "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`
538
+ },
539
+ insight
540
+ ))));
451
541
  case "carousel":
452
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, /* @__PURE__ */ React5.createElement(ProductCarousel, { products: config.data.map((item) => {
453
- var _a;
542
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: `${isCompact ? "mb-1.5 text-[11px]" : "mb-2 text-xs"} font-semibold text-slate-500` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ React5.createElement(ProductCarousel, { products: config.data.map((item) => {
543
+ var _a2;
454
544
  return __spreadProps(__spreadValues({}, item), {
455
- image: (_a = item.image) != null ? _a : typeof resolveImage === "function" ? resolveImage(item) : void 0
545
+ image: (_a2 = item.image) != null ? _a2 : typeof resolveImage === "function" ? resolveImage(item) : void 0
456
546
  });
457
- }) }));
547
+ }), primaryColor, onAddToCart }));
458
548
  case "table":
459
- 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), /* @__PURE__ */ React5.createElement(DataTable, { config: { type: "table", data: config.data } }));
549
+ return /* @__PURE__ */ React5.createElement("div", { className: `${isCompact ? "my-3 p-3 max-h-[320px]" : "my-4 p-4 max-h-[400px]"} bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto` }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: `${isCompact ? "text-[11px]" : "text-xs"} font-semibold text-slate-500 mb-2` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ React5.createElement(DataTable, { config: {
550
+ type: "table",
551
+ title: config.title,
552
+ description: config.description,
553
+ columns: config.columns,
554
+ dataKeys: config.dataKeys,
555
+ xAxisKey: config.xAxisKey,
556
+ data: config.data
557
+ }, viewportSize }));
460
558
  default:
461
559
  return null;
462
560
  }
@@ -467,10 +565,17 @@ function MessageBubble({
467
565
  isStreaming = false,
468
566
  primaryColor = "#6366f1",
469
567
  accentColor = "#8b5cf6",
470
- onAddToCart
568
+ onAddToCart,
569
+ viewportSize = "large"
471
570
  }) {
472
571
  const isUser = message.role === "user";
572
+ const isCompact = viewportSize === "compact";
573
+ const isMedium = viewportSize === "medium";
473
574
  const [showSources, setShowSources] = React5.useState(false);
575
+ const hasStructuredProductBlock = React5.useMemo(
576
+ () => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
577
+ [message.content]
578
+ );
474
579
  const productsFromSources = React5.useMemo(() => {
475
580
  if (isUser || !sources) return [];
476
581
  return sources.filter((s) => {
@@ -641,7 +746,9 @@ ${match.trim()}
641
746
  rawContent: String(children != null ? children : "").trim(),
642
747
  primaryColor,
643
748
  accentColor,
644
- isStreaming
749
+ isStreaming,
750
+ onAddToCart,
751
+ viewportSize
645
752
  }
646
753
  );
647
754
  }
@@ -654,7 +761,9 @@ ${match.trim()}
654
761
  rawContent: content,
655
762
  primaryColor,
656
763
  accentColor,
657
- isStreaming
764
+ isStreaming,
765
+ onAddToCart,
766
+ viewportSize
658
767
  }
659
768
  );
660
769
  }
@@ -665,23 +774,23 @@ ${match.trim()}
665
774
  return /* @__PURE__ */ React5.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
666
775
  }
667
776
  }),
668
- [primaryColor, accentColor, isStreaming]
777
+ [primaryColor, accentColor, isStreaming, onAddToCart, viewportSize]
669
778
  );
670
- return /* @__PURE__ */ React5.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
779
+ return /* @__PURE__ */ React5.createElement("div", { className: `flex ${isCompact ? "gap-2" : "gap-3"} ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
671
780
  "div",
672
781
  {
673
- className: `flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center shadow-lg ${isUser ? "text-white" : "bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80"}`,
782
+ className: `flex-shrink-0 ${isCompact ? "w-7 h-7" : "w-8 h-8"} rounded-full flex items-center justify-center shadow-lg ${isUser ? "text-white" : "bg-slate-100 dark:bg-white/10 text-slate-700 dark:text-white/80"}`,
674
783
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
675
784
  },
676
- isUser ? /* @__PURE__ */ React5.createElement(User, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ React5.createElement(Bot, { className: "w-4 h-4" })
677
- ), /* @__PURE__ */ React5.createElement("div", { className: `flex flex-col gap-1 max-w-[90%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ React5.createElement(
785
+ isUser ? /* @__PURE__ */ React5.createElement(User, { className: `${isCompact ? "w-3.5 h-3.5" : "w-4 h-4"} text-white` }) : /* @__PURE__ */ React5.createElement(Bot, { className: `${isCompact ? "w-3.5 h-3.5" : "w-4 h-4"}` })
786
+ ), /* @__PURE__ */ React5.createElement("div", { className: `flex flex-col gap-1 ${isCompact ? "max-w-[94%]" : isMedium ? "max-w-[92%]" : "max-w-[90%]"} ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ React5.createElement(
678
787
  "div",
679
788
  {
680
- className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
789
+ className: `relative ${isCompact ? "px-3 py-2.5 text-[13px]" : "px-4 py-3 text-sm"} rounded-2xl leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
681
790
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
682
791
  },
683
- 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" }))
684
- ), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
792
+ 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 ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${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" }))
793
+ ), !isUser && !hasStructuredProductBlock && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
685
794
  ProductCarousel,
686
795
  {
687
796
  products: allProducts,
@@ -963,6 +1072,8 @@ function ChatWindow({
963
1072
  const { ui, projectId } = useConfig();
964
1073
  const [input, setInput] = useState4("");
965
1074
  const [mounted, setMounted] = useState4(false);
1075
+ const [viewportSize, setViewportSize] = useState4("large");
1076
+ const windowRef = useRef3(null);
966
1077
  const bottomRef = useRef3(null);
967
1078
  const inputRef = useRef3(null);
968
1079
  const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
@@ -1021,6 +1132,22 @@ function ChatWindow({
1021
1132
  useEffect4(() => {
1022
1133
  setMounted(true);
1023
1134
  }, []);
1135
+ useEffect4(() => {
1136
+ const element = windowRef.current;
1137
+ if (!element || typeof ResizeObserver === "undefined") return;
1138
+ const resolveViewportSize = (width) => {
1139
+ if (width < 420) return "compact";
1140
+ if (width < 640) return "medium";
1141
+ return "large";
1142
+ };
1143
+ const observer = new ResizeObserver(([entry]) => {
1144
+ if (!entry) return;
1145
+ setViewportSize(resolveViewportSize(entry.contentRect.width));
1146
+ });
1147
+ setViewportSize(resolveViewportSize(element.getBoundingClientRect().width));
1148
+ observer.observe(element);
1149
+ return () => observer.disconnect();
1150
+ }, []);
1024
1151
  useEffect4(() => {
1025
1152
  var _a2;
1026
1153
  if (messages.length > 0 || isLoading) {
@@ -1084,6 +1211,7 @@ function ChatWindow({
1084
1211
  return /* @__PURE__ */ React8.createElement(
1085
1212
  "div",
1086
1213
  {
1214
+ ref: windowRef,
1087
1215
  className: `relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass ? "bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl" : "bg-white dark:bg-[#0f0f1a]"} ${className}`,
1088
1216
  style: __spreadValues({ "--primary": ui.primaryColor, "--accent": ui.accentColor }, style)
1089
1217
  },
@@ -1180,7 +1308,8 @@ function ChatWindow({
1180
1308
  isStreaming: isLoading && index === messages.length - 1 && msg.role === "assistant",
1181
1309
  primaryColor: ui.primaryColor,
1182
1310
  accentColor: ui.accentColor,
1183
- onAddToCart
1311
+ onAddToCart,
1312
+ viewportSize
1184
1313
  }
1185
1314
  )), isLoading && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.role) !== "assistant" && /* @__PURE__ */ React8.createElement(
1186
1315
  MessageBubble,
@@ -1189,7 +1318,8 @@ function ChatWindow({
1189
1318
  isStreaming: true,
1190
1319
  primaryColor: ui.primaryColor,
1191
1320
  accentColor: ui.accentColor,
1192
- onAddToCart
1321
+ onAddToCart,
1322
+ viewportSize
1193
1323
  }
1194
1324
  ), error && /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-2 text-rose-500 dark:text-rose-400 text-sm bg-rose-50 dark:bg-rose-400/10 border border-rose-200 dark:border-rose-400/20 rounded-xl px-4 py-3" }, /* @__PURE__ */ React8.createElement(TriangleAlert, { className: "w-4 h-4 flex-shrink-0" }), /* @__PURE__ */ React8.createElement("span", null, error)), /* @__PURE__ */ React8.createElement("div", { ref: bottomRef })),
1195
1325
  /* @__PURE__ */ React8.createElement("div", { className: `px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? "bg-transparent" : "bg-white dark:bg-[#0f0f1a]"}` }, (suggestions.length > 0 || isSuggesting) && !isLoading && /* @__PURE__ */ React8.createElement("div", { className: "mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300" }, suggestions.map((suggestion) => /* @__PURE__ */ React8.createElement(
package/dist/server.js CHANGED
@@ -3913,23 +3913,70 @@ var Pipeline = class {
3913
3913
  async initialize() {
3914
3914
  var _a, _b;
3915
3915
  if (this.initialised) return;
3916
- const CHART_MARKER = "<!-- UI_PROTOCOL_V3 -->";
3916
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V5 -->";
3917
3917
  const chartInstruction = `
3918
3918
 
3919
3919
  ${CHART_MARKER}
3920
- ### UNIVERSAL UI PROTOCOL:
3921
- You have a specialized UI system. For any structured data (charts, product lists, or tables), you MUST use a single \`\`\`ui\`\`\` block.
3922
- 1. DECIDE the best "view" for the user's query:
3923
- - "chart": For statistics, distributions, or comparisons. (Requires "chartType": "pie" | "bar" | "line")
3924
- - "carousel": For browsing specific product items.
3925
- - "table": For detailed raw data comparisons.
3926
- 2. OUTPUT a \`\`\`ui\`\`\` block with this schema:
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
3927
3929
  {
3928
3930
  "view": "chart" | "carousel" | "table",
3929
- "chartType": "pie" | "bar" | "line" (only for view: "chart"),
3930
- "data": [ ... objects ... ]
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": []
3931
3939
  }
3932
- 3. RULES: Valid JSON only. Data must be FLAT. Never draw charts/tables with text. Choose ONLY the most useful view.`;
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
+ - If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
3956
+ - Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
3957
+
3958
+ 5. EXAMPLE
3959
+ User: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
3960
+ Assistant:
3961
+ \`\`\`ui
3962
+ {
3963
+ "view": "chart",
3964
+ "title": "Product Distribution Across Categories",
3965
+ "description": "Pie chart showing product count by category with stock signals preserved per slice.",
3966
+ "chartType": "pie",
3967
+ "xAxisKey": "label",
3968
+ "dataKeys": ["value"],
3969
+ "insights": [
3970
+ "Beauty has the highest product count.",
3971
+ "Electronics has the largest number of in-stock products."
3972
+ ],
3973
+ "data": [
3974
+ { "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
3975
+ { "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
3976
+ { "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
3977
+ ]
3978
+ }
3979
+ \`\`\``;
3933
3980
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
3934
3981
  this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
3935
3982
  }
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-BLLMNP77.mjs";
42
+ } from "./chunk-ID2Q4CF7.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
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 { createChatHandler } from '@/handlers';
1
+ import { createStreamHandler } from '@/handlers';
2
2
  import { getRagConfig } from '@/config/serverConfig';
3
3
 
4
- export const POST = createChatHandler(getRagConfig());
4
+ export const POST = createStreamHandler(getRagConfig());
@@ -65,6 +65,8 @@ export function ChatWindow({
65
65
  const { ui, projectId } = useConfig();
66
66
  const [input, setInput] = useState('');
67
67
  const [mounted, setMounted] = useState(false);
68
+ const [viewportSize, setViewportSize] = useState<'compact' | 'medium' | 'large'>('large');
69
+ const windowRef = useRef<HTMLDivElement>(null);
68
70
  const bottomRef = useRef<HTMLDivElement>(null);
69
71
  const inputRef = useRef<HTMLTextAreaElement>(null);
70
72
  const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
@@ -136,6 +138,27 @@ export function ChatWindow({
136
138
  setMounted(true);
137
139
  }, []);
138
140
 
141
+ useEffect(() => {
142
+ const element = windowRef.current;
143
+ if (!element || typeof ResizeObserver === 'undefined') return;
144
+
145
+ const resolveViewportSize = (width: number) => {
146
+ if (width < 420) return 'compact';
147
+ if (width < 640) return 'medium';
148
+ return 'large';
149
+ };
150
+
151
+ const observer = new ResizeObserver(([entry]) => {
152
+ if (!entry) return;
153
+ setViewportSize(resolveViewportSize(entry.contentRect.width));
154
+ });
155
+
156
+ setViewportSize(resolveViewportSize(element.getBoundingClientRect().width));
157
+ observer.observe(element);
158
+
159
+ return () => observer.disconnect();
160
+ }, []);
161
+
139
162
  // Scroll to bottom on new message (but not on initial mount with empty messages)
140
163
  useEffect(() => {
141
164
  if (messages.length > 0 || isLoading) {
@@ -206,6 +229,7 @@ export function ChatWindow({
206
229
 
207
230
  return (
208
231
  <div
232
+ ref={windowRef}
209
233
  className={`relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
210
234
  isGlass
211
235
  ? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
@@ -344,6 +368,7 @@ export function ChatWindow({
344
368
  primaryColor={ui.primaryColor}
345
369
  accentColor={ui.accentColor}
346
370
  onAddToCart={onAddToCart}
371
+ viewportSize={viewportSize}
347
372
  />
348
373
  ))
349
374
  )}
@@ -356,6 +381,7 @@ export function ChatWindow({
356
381
  primaryColor={ui.primaryColor}
357
382
  accentColor={ui.accentColor}
358
383
  onAddToCart={onAddToCart}
384
+ viewportSize={viewportSize}
359
385
  />
360
386
  )}
361
387