@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 +47 -16
- package/dist/index.mjs +47 -16
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +28 -7
- package/src/components/ProductCard.tsx +1 -1
- package/src/components/ProductCarousel.tsx +21 -11
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-
|
|
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
|
|
142
|
-
|
|
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-
|
|
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: () =>
|
|
150
|
-
|
|
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-
|
|
153
|
-
)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -right-
|
|
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: () =>
|
|
157
|
-
|
|
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-
|
|
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(
|
|
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 =
|
|
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-[
|
|
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),
|
|
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-
|
|
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
|
|
100
|
-
|
|
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-
|
|
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: () =>
|
|
108
|
-
|
|
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-
|
|
111
|
-
)), /* @__PURE__ */ React3.createElement("div", { className: "absolute -right-
|
|
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: () =>
|
|
115
|
-
|
|
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-
|
|
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(
|
|
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 =
|
|
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-[
|
|
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),
|
|
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.
|
|
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 =
|
|
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-[
|
|
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-
|
|
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
|
|
18
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
38
|
+
<ChevronLeft className="w-5 h-5" />
|
|
33
39
|
</button>
|
|
34
40
|
</div>
|
|
35
41
|
|
|
36
|
-
<div className="absolute -right-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
))}
|