@retrivora-ai/rag-engine 1.1.5 → 1.1.7

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.js CHANGED
@@ -104,7 +104,7 @@ var import_react2 = __toESM(require("react"));
104
104
  var import_image = __toESM(require("next/image"));
105
105
  var import_lucide_react2 = require("lucide-react");
106
106
  function ProductCard({ product, primaryColor = "#6366f1" }) {
107
- return /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-shrink-0 w-64 bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ import_react2.default.createElement(
107
+ return /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ import_react2.default.createElement(
108
108
  import_image.default,
109
109
  {
110
110
  src: product.image,
@@ -138,25 +138,37 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
138
138
  const scrollRef = (0, import_react3.useRef)(null);
139
139
  const scroll = (direction) => {
140
140
  if (scrollRef.current) {
141
- const scrollAmount = direction === "left" ? -280 : 280;
142
- scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
141
+ const firstCard = scrollRef.current.firstElementChild;
142
+ if (firstCard) {
143
+ const cardWidth = firstCard.offsetWidth + 16;
144
+ const scrollAmount = direction === "left" ? -cardWidth : cardWidth;
145
+ scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
146
+ }
143
147
  }
144
148
  };
145
149
  if (!products || products.length === 0) return null;
146
- return /* @__PURE__ */ import_react3.default.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -left-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react3.default.createElement(
150
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ import_react3.default.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__ */ import_react3.default.createElement(
147
151
  "button",
148
152
  {
149
- onClick: () => scroll("left"),
150
- className: "p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
153
+ onClick: (e) => {
154
+ e.stopPropagation();
155
+ scroll("left");
156
+ },
157
+ className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
158
+ "aria-label": "Scroll left"
151
159
  },
152
- /* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronLeft, { className: "w-4 h-4" })
153
- )), /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -right-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react3.default.createElement(
160
+ /* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronLeft, { className: "w-5 h-5" })
161
+ )), /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -right-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react3.default.createElement(
154
162
  "button",
155
163
  {
156
- onClick: () => scroll("right"),
157
- className: "p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
164
+ onClick: (e) => {
165
+ e.stopPropagation();
166
+ scroll("right");
167
+ },
168
+ className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
169
+ "aria-label": "Scroll right"
158
170
  },
159
- /* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronRight, { className: "w-4 h-4" })
171
+ /* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronRight, { className: "w-5 h-5" })
160
172
  )), /* @__PURE__ */ import_react3.default.createElement(
161
173
  "div",
162
174
  {
@@ -164,7 +176,14 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
164
176
  className: "flex gap-4 overflow-x-auto pb-4 px-1 scrollbar-hide snap-x snap-mandatory",
165
177
  style: { scrollbarWidth: "none", msOverflowStyle: "none" }
166
178
  },
167
- products.map((product) => /* @__PURE__ */ import_react3.default.createElement("div", { key: product.id, className: "snap-start" }, /* @__PURE__ */ import_react3.default.createElement(ProductCard, { product, primaryColor })))
179
+ products.map((product) => /* @__PURE__ */ import_react3.default.createElement(
180
+ "div",
181
+ {
182
+ key: product.id,
183
+ className: "snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
184
+ },
185
+ /* @__PURE__ */ import_react3.default.createElement(ProductCard, { product, primaryColor })
186
+ ))
168
187
  ));
169
188
  }
170
189
 
@@ -231,7 +250,19 @@ function MessageBubble({
231
250
  }
232
251
  return { productsFromContent: products, cleanContent: content.trim() };
233
252
  }, [message.content, isUser]);
234
- const allProducts = [...productsFromSources, ...productsFromContent];
253
+ const allProducts = import_react4.default.useMemo(() => {
254
+ if (productsFromContent.length > 0) return productsFromContent;
255
+ const uniqueProducts = [];
256
+ const seenIds = /* @__PURE__ */ new Set();
257
+ for (const p of productsFromSources) {
258
+ const identifier = p.id || p.name;
259
+ if (!seenIds.has(identifier)) {
260
+ seenIds.add(identifier);
261
+ uniqueProducts.push(p);
262
+ }
263
+ }
264
+ return uniqueProducts;
265
+ }, [productsFromSources, productsFromContent]);
235
266
  const hasProducts = allProducts.length > 0;
236
267
  return /* @__PURE__ */ import_react4.default.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ import_react4.default.createElement(
237
268
  "div",
@@ -240,14 +271,14 @@ function MessageBubble({
240
271
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
241
272
  },
242
273
  isUser ? /* @__PURE__ */ import_react4.default.createElement(import_lucide_react4.User, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ import_react4.default.createElement(import_lucide_react4.Bot, { className: "w-4 h-4" })
243
- ), /* @__PURE__ */ import_react4.default.createElement("div", { className: `flex flex-col gap-1 max-w-[75%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ import_react4.default.createElement(
274
+ ), /* @__PURE__ */ import_react4.default.createElement("div", { className: `flex flex-col gap-1 max-w-[90%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ import_react4.default.createElement(
244
275
  "div",
245
276
  {
246
277
  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"}`,
247
278
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
248
279
  },
249
- isStreaming && !message.content ? /* @__PURE__ */ import_react4.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react4.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_react4.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_react4.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_react4.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react4.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, cleanContent || message.content), hasProducts && /* @__PURE__ */ import_react4.default.createElement(ProductCarousel, { products: allProducts, primaryColor }), isStreaming && message.content && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
250
- ), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react4.default.createElement(
280
+ isStreaming && !message.content ? /* @__PURE__ */ import_react4.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react4.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_react4.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_react4.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_react4.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react4.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
281
+ ), !isUser && hasProducts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react4.default.createElement(ProductCarousel, { products: allProducts, primaryColor })), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react4.default.createElement(
251
282
  "button",
252
283
  {
253
284
  onClick: () => setShowSources((s) => !s),
package/dist/index.mjs CHANGED
@@ -62,7 +62,7 @@ import React2 from "react";
62
62
  import Image from "next/image";
63
63
  import { ShoppingCart, ExternalLink } from "lucide-react";
64
64
  function ProductCard({ product, primaryColor = "#6366f1" }) {
65
- return /* @__PURE__ */ React2.createElement("div", { className: "flex-shrink-0 w-64 bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ React2.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ React2.createElement(
65
+ return /* @__PURE__ */ React2.createElement("div", { className: "flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ React2.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ React2.createElement(
66
66
  Image,
67
67
  {
68
68
  src: product.image,
@@ -96,25 +96,37 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
96
96
  const scrollRef = useRef(null);
97
97
  const scroll = (direction) => {
98
98
  if (scrollRef.current) {
99
- const scrollAmount = direction === "left" ? -280 : 280;
100
- scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
99
+ const firstCard = scrollRef.current.firstElementChild;
100
+ if (firstCard) {
101
+ const cardWidth = firstCard.offsetWidth + 16;
102
+ const scrollAmount = direction === "left" ? -cardWidth : cardWidth;
103
+ scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
104
+ }
101
105
  }
102
106
  };
103
107
  if (!products || products.length === 0) return null;
104
- return /* @__PURE__ */ React3.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ React3.createElement("div", { className: "absolute -left-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ React3.createElement(
108
+ return /* @__PURE__ */ React3.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__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(
105
109
  "button",
106
110
  {
107
- onClick: () => scroll("left"),
108
- className: "p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
111
+ onClick: (e) => {
112
+ e.stopPropagation();
113
+ scroll("left");
114
+ },
115
+ className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
116
+ "aria-label": "Scroll left"
109
117
  },
110
- /* @__PURE__ */ React3.createElement(ChevronLeft, { className: "w-4 h-4" })
111
- )), /* @__PURE__ */ React3.createElement("div", { className: "absolute -right-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ React3.createElement(
118
+ /* @__PURE__ */ React3.createElement(ChevronLeft, { className: "w-5 h-5" })
119
+ )), /* @__PURE__ */ React3.createElement("div", { className: "absolute -right-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(
112
120
  "button",
113
121
  {
114
- onClick: () => scroll("right"),
115
- className: "p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
122
+ onClick: (e) => {
123
+ e.stopPropagation();
124
+ scroll("right");
125
+ },
126
+ className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
127
+ "aria-label": "Scroll right"
116
128
  },
117
- /* @__PURE__ */ React3.createElement(ChevronRight, { className: "w-4 h-4" })
129
+ /* @__PURE__ */ React3.createElement(ChevronRight, { className: "w-5 h-5" })
118
130
  )), /* @__PURE__ */ React3.createElement(
119
131
  "div",
120
132
  {
@@ -122,7 +134,14 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
122
134
  className: "flex gap-4 overflow-x-auto pb-4 px-1 scrollbar-hide snap-x snap-mandatory",
123
135
  style: { scrollbarWidth: "none", msOverflowStyle: "none" }
124
136
  },
125
- products.map((product) => /* @__PURE__ */ React3.createElement("div", { key: product.id, className: "snap-start" }, /* @__PURE__ */ React3.createElement(ProductCard, { product, primaryColor })))
137
+ products.map((product) => /* @__PURE__ */ React3.createElement(
138
+ "div",
139
+ {
140
+ key: product.id,
141
+ className: "snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
142
+ },
143
+ /* @__PURE__ */ React3.createElement(ProductCard, { product, primaryColor })
144
+ ))
126
145
  ));
127
146
  }
128
147
 
@@ -189,7 +208,19 @@ function MessageBubble({
189
208
  }
190
209
  return { productsFromContent: products, cleanContent: content.trim() };
191
210
  }, [message.content, isUser]);
192
- const allProducts = [...productsFromSources, ...productsFromContent];
211
+ const allProducts = React4.useMemo(() => {
212
+ if (productsFromContent.length > 0) return productsFromContent;
213
+ const uniqueProducts = [];
214
+ const seenIds = /* @__PURE__ */ new Set();
215
+ for (const p of productsFromSources) {
216
+ const identifier = p.id || p.name;
217
+ if (!seenIds.has(identifier)) {
218
+ seenIds.add(identifier);
219
+ uniqueProducts.push(p);
220
+ }
221
+ }
222
+ return uniqueProducts;
223
+ }, [productsFromSources, productsFromContent]);
193
224
  const hasProducts = allProducts.length > 0;
194
225
  return /* @__PURE__ */ React4.createElement("div", { className: `flex gap-3 ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React4.createElement(
195
226
  "div",
@@ -198,14 +229,14 @@ function MessageBubble({
198
229
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
199
230
  },
200
231
  isUser ? /* @__PURE__ */ React4.createElement(User, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ React4.createElement(Bot, { className: "w-4 h-4" })
201
- ), /* @__PURE__ */ React4.createElement("div", { className: `flex flex-col gap-1 max-w-[75%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ React4.createElement(
232
+ ), /* @__PURE__ */ React4.createElement("div", { className: `flex flex-col gap-1 max-w-[90%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ React4.createElement(
202
233
  "div",
203
234
  {
204
235
  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"}`,
205
236
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
206
237
  },
207
- isStreaming && !message.content ? /* @__PURE__ */ React4.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React4.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React4.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, cleanContent || message.content), hasProducts && /* @__PURE__ */ React4.createElement(ProductCarousel, { products: allProducts, primaryColor }), isStreaming && message.content && /* @__PURE__ */ React4.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
208
- ), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React4.createElement("div", { className: "w-full" }, /* @__PURE__ */ React4.createElement(
238
+ isStreaming && !message.content ? /* @__PURE__ */ React4.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React4.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React4.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ React4.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
239
+ ), !isUser && hasProducts && /* @__PURE__ */ React4.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React4.createElement(ProductCarousel, { products: allProducts, primaryColor })), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React4.createElement("div", { className: "w-full" }, /* @__PURE__ */ React4.createElement(
209
240
  "button",
210
241
  {
211
242
  onClick: () => setShowSources((s) => !s),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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",
@@ -100,7 +100,26 @@ export function MessageBubble({
100
100
  return { productsFromContent: products, cleanContent: content.trim() };
101
101
  }, [message.content, isUser]);
102
102
 
103
- const allProducts = [...productsFromSources, ...productsFromContent];
103
+ const allProducts = React.useMemo(() => {
104
+ // If the LLM explicitly provided products in a structured format, only show those.
105
+ // This ensures we only show what the assistant actually "displayed" as a result.
106
+ if (productsFromContent.length > 0) return productsFromContent;
107
+
108
+ // Fallback: If no structured products, show unique products from sources.
109
+ const uniqueProducts: Product[] = [];
110
+ const seenIds = new Set();
111
+
112
+ for (const p of productsFromSources) {
113
+ const identifier = p.id || p.name;
114
+ if (!seenIds.has(identifier)) {
115
+ seenIds.add(identifier);
116
+ uniqueProducts.push(p);
117
+ }
118
+ }
119
+
120
+ return uniqueProducts;
121
+ }, [productsFromSources, productsFromContent]);
122
+
104
123
  const hasProducts = allProducts.length > 0;
105
124
 
106
125
  return (
@@ -118,7 +137,7 @@ export function MessageBubble({
118
137
  }
119
138
  </div>
120
139
 
121
- <div className={`flex flex-col gap-1 max-w-[75%] ${isUser ? 'items-end' : 'items-start'}`}>
140
+ <div className={`flex flex-col gap-1 max-w-[90%] ${isUser ? 'items-end' : 'items-start'}`}>
122
141
  {/* Bubble */}
123
142
  <div
124
143
  className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${
@@ -144,11 +163,6 @@ export function MessageBubble({
144
163
  {cleanContent || message.content}
145
164
  </ReactMarkdown>
146
165
 
147
- {/* Product Carousel */}
148
- {hasProducts && (
149
- <ProductCarousel products={allProducts} primaryColor={primaryColor} />
150
- )}
151
-
152
166
  {isStreaming && message.content && (
153
167
  <span className="inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" />
154
168
  )}
@@ -156,6 +170,13 @@ export function MessageBubble({
156
170
  )}
157
171
  </div>
158
172
 
173
+ {/* Product Carousel (Outside the bubble) */}
174
+ {!isUser && hasProducts && (
175
+ <div className="w-full mt-1">
176
+ <ProductCarousel products={allProducts} primaryColor={primaryColor} />
177
+ </div>
178
+ )}
179
+
159
180
  {/* Sources toggle */}
160
181
  {!isUser && sources && sources.length > 0 && (
161
182
  <div className="w-full">
@@ -21,7 +21,7 @@ interface ProductCardProps {
21
21
 
22
22
  export function ProductCard({ product, primaryColor = '#6366f1' }: ProductCardProps) {
23
23
  return (
24
- <div className="flex-shrink-0 w-64 bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group">
24
+ <div className="flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group">
25
25
  {/* Product Image */}
26
26
  <div className="relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden">
27
27
  {product.image ? (
@@ -14,8 +14,13 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
14
14
 
15
15
  const scroll = (direction: 'left' | 'right') => {
16
16
  if (scrollRef.current) {
17
- const scrollAmount = direction === 'left' ? -280 : 280;
18
- scrollRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
17
+ const firstCard = scrollRef.current.firstElementChild as HTMLElement;
18
+ if (firstCard) {
19
+ // Use actual rendered width + gap
20
+ const cardWidth = firstCard.offsetWidth + 16;
21
+ const scrollAmount = direction === 'left' ? -cardWidth : cardWidth;
22
+ scrollRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
23
+ }
19
24
  }
20
25
  };
21
26
 
@@ -24,21 +29,23 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
24
29
  return (
25
30
  <div className="relative w-full my-4 group/carousel">
26
31
  {/* Scroll Buttons */}
27
- <div className="absolute -left-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity">
32
+ <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">
28
33
  <button
29
- onClick={() => scroll('left')}
30
- className="p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
34
+ onClick={(e) => { e.stopPropagation(); scroll('left'); }}
35
+ className="p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95"
36
+ aria-label="Scroll left"
31
37
  >
32
- <ChevronLeft className="w-4 h-4" />
38
+ <ChevronLeft className="w-5 h-5" />
33
39
  </button>
34
40
  </div>
35
41
 
36
- <div className="absolute -right-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity">
42
+ <div className="absolute -right-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity">
37
43
  <button
38
- onClick={() => scroll('right')}
39
- className="p-1.5 rounded-full bg-white dark:bg-slate-800 shadow-lg border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-colors"
44
+ onClick={(e) => { e.stopPropagation(); scroll('right'); }}
45
+ className="p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95"
46
+ aria-label="Scroll right"
40
47
  >
41
- <ChevronRight className="w-4 h-4" />
48
+ <ChevronRight className="w-5 h-5" />
42
49
  </button>
43
50
  </div>
44
51
 
@@ -49,7 +56,10 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
49
56
  style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
50
57
  >
51
58
  {products.map((product) => (
52
- <div key={product.id} className="snap-start">
59
+ <div
60
+ key={product.id}
61
+ className="snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
62
+ >
53
63
  <ProductCard product={product} primaryColor={primaryColor} />
54
64
  </div>
55
65
  ))}