@retrivora-ai/rag-engine 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1736,7 +1736,7 @@ function getRagConfig(baseConfig, env = process.env) {
1736
1736
  return getEnvConfig(env, baseConfig);
1737
1737
  }
1738
1738
  function getEnvConfig(env = process.env, base) {
1739
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa;
1739
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va;
1740
1740
  const projectId = (_c = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : base == null ? void 0 : base.projectId) != null ? _c : "__default__";
1741
1741
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
1742
1742
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -1745,34 +1745,35 @@ function getEnvConfig(env = process.env, base) {
1745
1745
  const vectorDbOptions = {};
1746
1746
  if (vectorProvider === "pinecone") {
1747
1747
  vectorDbOptions.apiKey = (_g = (_f = readString(env, "PINECONE_API_KEY")) != null ? _f : (_e = (_d = base == null ? void 0 : base.vectorDb) == null ? void 0 : _d.options) == null ? void 0 : _e.apiKey) != null ? _g : "";
1748
+ vectorDbOptions.indexName = (_l = (_i = readString(env, "PINECONE_INDEX")) != null ? _i : (_h = base == null ? void 0 : base.vectorDb) == null ? void 0 : _h.indexName) != null ? _l : (_k = (_j = base == null ? void 0 : base.vectorDb) == null ? void 0 : _j.options) == null ? void 0 : _k.indexName;
1748
1749
  } else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
1749
- vectorDbOptions.connectionString = (_k = (_j = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _j : (_i = (_h = base == null ? void 0 : base.vectorDb) == null ? void 0 : _h.options) == null ? void 0 : _i.connectionString) != null ? _k : "";
1750
+ vectorDbOptions.connectionString = (_p = (_o = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _o : (_n = (_m = base == null ? void 0 : base.vectorDb) == null ? void 0 : _m.options) == null ? void 0 : _n.connectionString) != null ? _p : "";
1750
1751
  vectorDbOptions.dimensions = embeddingDimensions;
1751
1752
  } else if (vectorProvider === "mongodb") {
1752
- vectorDbOptions.uri = (_o = (_n = readString(env, "MONGODB_URI")) != null ? _n : (_m = (_l = base == null ? void 0 : base.vectorDb) == null ? void 0 : _l.options) == null ? void 0 : _m.uri) != null ? _o : "";
1753
- vectorDbOptions.database = (_s = (_r = readString(env, "MONGODB_DB")) != null ? _r : (_q = (_p = base == null ? void 0 : base.vectorDb) == null ? void 0 : _p.options) == null ? void 0 : _q.database) != null ? _s : "";
1754
- vectorDbOptions.collection = (_w = (_v = readString(env, "MONGODB_COLLECTION")) != null ? _v : (_u = (_t = base == null ? void 0 : base.vectorDb) == null ? void 0 : _t.options) == null ? void 0 : _u.collection) != null ? _w : "";
1755
- vectorDbOptions.indexName = (_C = (_B = (_y = readString(env, "MONGODB_INDEX_NAME")) != null ? _y : (_x = base == null ? void 0 : base.vectorDb) == null ? void 0 : _x.indexName) != null ? _B : (_A = (_z = base == null ? void 0 : base.vectorDb) == null ? void 0 : _z.options) == null ? void 0 : _A.indexName) != null ? _C : "vector_index";
1753
+ vectorDbOptions.uri = (_t = (_s = readString(env, "MONGODB_URI")) != null ? _s : (_r = (_q = base == null ? void 0 : base.vectorDb) == null ? void 0 : _q.options) == null ? void 0 : _r.uri) != null ? _t : "";
1754
+ vectorDbOptions.database = (_x = (_w = readString(env, "MONGODB_DB")) != null ? _w : (_v = (_u = base == null ? void 0 : base.vectorDb) == null ? void 0 : _u.options) == null ? void 0 : _v.database) != null ? _x : "";
1755
+ vectorDbOptions.collection = (_B = (_A = readString(env, "MONGODB_COLLECTION")) != null ? _A : (_z = (_y = base == null ? void 0 : base.vectorDb) == null ? void 0 : _y.options) == null ? void 0 : _z.collection) != null ? _B : "";
1756
+ vectorDbOptions.indexName = (_H = (_G = (_D = readString(env, "MONGODB_INDEX_NAME")) != null ? _D : (_C = base == null ? void 0 : base.vectorDb) == null ? void 0 : _C.indexName) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.indexName) != null ? _H : "vector_index";
1756
1757
  } else if (vectorProvider === "qdrant") {
1757
- vectorDbOptions.baseUrl = (_G = (_F = readString(env, "QDRANT_URL")) != null ? _F : (_E = (_D = base == null ? void 0 : base.vectorDb) == null ? void 0 : _D.options) == null ? void 0 : _E.baseUrl) != null ? _G : "http://localhost:6333";
1758
- vectorDbOptions.apiKey = (_J = readString(env, "QDRANT_API_KEY")) != null ? _J : (_I = (_H = base == null ? void 0 : base.vectorDb) == null ? void 0 : _H.options) == null ? void 0 : _I.apiKey;
1758
+ vectorDbOptions.baseUrl = (_L = (_K = readString(env, "QDRANT_URL")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.baseUrl) != null ? _L : "http://localhost:6333";
1759
+ vectorDbOptions.apiKey = (_O = readString(env, "QDRANT_API_KEY")) != null ? _O : (_N = (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.options) == null ? void 0 : _N.apiKey;
1759
1760
  vectorDbOptions.dimensions = embeddingDimensions;
1760
1761
  } else if (vectorProvider === "milvus") {
1761
- vectorDbOptions.baseUrl = (_K = readString(env, "MILVUS_URL")) != null ? _K : "http://localhost:19530";
1762
+ vectorDbOptions.baseUrl = (_P = readString(env, "MILVUS_URL")) != null ? _P : "http://localhost:19530";
1762
1763
  vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
1763
1764
  } else if (vectorProvider === "chromadb") {
1764
- vectorDbOptions.baseUrl = (_L = readString(env, "CHROMADB_URL")) != null ? _L : "http://localhost:8000";
1765
+ vectorDbOptions.baseUrl = (_Q = readString(env, "CHROMADB_URL")) != null ? _Q : "http://localhost:8000";
1765
1766
  } else if (vectorProvider === "weaviate") {
1766
- vectorDbOptions.baseUrl = (_M = readString(env, "WEAVIATE_URL")) != null ? _M : "http://localhost:8080";
1767
+ vectorDbOptions.baseUrl = (_R = readString(env, "WEAVIATE_URL")) != null ? _R : "http://localhost:8080";
1767
1768
  vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
1768
1769
  } else if (vectorProvider === "redis") {
1769
- vectorDbOptions.baseUrl = (_N = readString(env, "REDIS_URL")) != null ? _N : "";
1770
+ vectorDbOptions.baseUrl = (_S = readString(env, "REDIS_URL")) != null ? _S : "";
1770
1771
  vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
1771
1772
  } else if (vectorProvider === "rest") {
1772
- vectorDbOptions.baseUrl = (_O = readString(env, "VECTOR_DB_REST_URL")) != null ? _O : "";
1773
+ vectorDbOptions.baseUrl = (_T = readString(env, "VECTOR_DB_REST_URL")) != null ? _T : "";
1773
1774
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
1774
1775
  } else if (vectorProvider === "universal_rest") {
1775
- vectorDbOptions.baseUrl = (_Q = (_P = readString(env, "VECTOR_BASE_URL")) != null ? _P : readString(env, "VECTOR_DB_REST_URL")) != null ? _Q : "";
1776
+ vectorDbOptions.baseUrl = (_V = (_U = readString(env, "VECTOR_BASE_URL")) != null ? _U : readString(env, "VECTOR_DB_REST_URL")) != null ? _V : "";
1776
1777
  vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
1777
1778
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
1778
1779
  }
@@ -1789,20 +1790,20 @@ function getEnvConfig(env = process.env, base) {
1789
1790
  openai: readString(env, "OPENAI_API_KEY"),
1790
1791
  gemini: readString(env, "GEMINI_API_KEY"),
1791
1792
  ollama: void 0,
1792
- universal_rest: (_R = readString(env, "EMBEDDING_API_KEY")) != null ? _R : readString(env, "OPENAI_API_KEY"),
1793
- custom: (_S = readString(env, "EMBEDDING_API_KEY")) != null ? _S : readString(env, "OPENAI_API_KEY")
1793
+ universal_rest: (_W = readString(env, "EMBEDDING_API_KEY")) != null ? _W : readString(env, "OPENAI_API_KEY"),
1794
+ custom: (_X = readString(env, "EMBEDDING_API_KEY")) != null ? _X : readString(env, "OPENAI_API_KEY")
1794
1795
  };
1795
1796
  return {
1796
1797
  projectId,
1797
1798
  vectorDb: {
1798
1799
  provider: vectorProvider,
1799
- indexName: (_T = readString(env, "VECTOR_DB_INDEX")) != null ? _T : "rag-index",
1800
+ indexName: (_Z = (_Y = readString(env, "VECTOR_DB_INDEX")) != null ? _Y : vectorDbOptions.indexName) != null ? _Z : "rag-index",
1800
1801
  options: vectorDbOptions
1801
1802
  },
1802
1803
  llm: {
1803
1804
  provider: llmProvider,
1804
- model: (_U = readString(env, "LLM_MODEL")) != null ? _U : "gpt-4o",
1805
- apiKey: (_V = llmApiKeyByProvider[llmProvider]) != null ? _V : "",
1805
+ model: (__ = readString(env, "LLM_MODEL")) != null ? __ : "gpt-4o",
1806
+ apiKey: (_$ = llmApiKeyByProvider[llmProvider]) != null ? _$ : "",
1806
1807
  baseUrl: readString(env, "LLM_BASE_URL"),
1807
1808
  systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
1808
1809
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 1024),
@@ -1813,7 +1814,7 @@ function getEnvConfig(env = process.env, base) {
1813
1814
  },
1814
1815
  embedding: {
1815
1816
  provider: embeddingProvider,
1816
- model: (_W = readString(env, "EMBEDDING_MODEL")) != null ? _W : "text-embedding-3-small",
1817
+ model: (_aa = readString(env, "EMBEDDING_MODEL")) != null ? _aa : "text-embedding-3-small",
1817
1818
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1818
1819
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1819
1820
  dimensions: embeddingDimensions,
@@ -1824,17 +1825,17 @@ function getEnvConfig(env = process.env, base) {
1824
1825
  }
1825
1826
  },
1826
1827
  ui: {
1827
- title: (_Y = (_X = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _X : readString(env, "UI_TITLE")) != null ? _Y : "AI Assistant",
1828
- subtitle: (__ = (_Z = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _Z : readString(env, "UI_SUBTITLE")) != null ? __ : "Powered by RAG",
1829
- primaryColor: (_aa = (_$ = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _$ : readString(env, "UI_PRIMARY_COLOR")) != null ? _aa : "#10b981",
1830
- accentColor: (_ca = (_ba = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ba : readString(env, "UI_ACCENT_COLOR")) != null ? _ca : "#3b82f6",
1831
- logoUrl: (_da = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _da : readString(env, "UI_LOGO_URL"),
1832
- placeholder: (_fa = (_ea = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ea : readString(env, "UI_PLACEHOLDER")) != null ? _fa : "Ask me anything\u2026",
1833
- showSources: ((_ha = (_ga = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _ga : readString(env, "UI_SHOW_SOURCES")) != null ? _ha : "true") !== "false",
1834
- welcomeMessage: (_ja = (_ia = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _ia : readString(env, "UI_WELCOME_MESSAGE")) != null ? _ja : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1835
- visualStyle: (_la = (_ka = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _ka : readString(env, "UI_VISUAL_STYLE")) != null ? _la : "glass",
1836
- borderRadius: (_na = (_ma = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _ma : readString(env, "UI_BORDER_RADIUS")) != null ? _na : "xl",
1837
- allowUpload: ((_pa = (_oa = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _oa : readString(env, "UI_ALLOW_UPLOAD")) != null ? _pa : "false") === "true"
1828
+ title: (_ca = (_ba = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _ba : readString(env, "UI_TITLE")) != null ? _ca : "AI Assistant",
1829
+ subtitle: (_ea = (_da = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _da : readString(env, "UI_SUBTITLE")) != null ? _ea : "Powered by RAG",
1830
+ primaryColor: (_ga = (_fa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _fa : readString(env, "UI_PRIMARY_COLOR")) != null ? _ga : "#10b981",
1831
+ accentColor: (_ia = (_ha = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ha : readString(env, "UI_ACCENT_COLOR")) != null ? _ia : "#3b82f6",
1832
+ logoUrl: (_ja = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ja : readString(env, "UI_LOGO_URL"),
1833
+ placeholder: (_la = (_ka = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ka : readString(env, "UI_PLACEHOLDER")) != null ? _la : "Ask me anything\u2026",
1834
+ showSources: ((_na = (_ma = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _ma : readString(env, "UI_SHOW_SOURCES")) != null ? _na : "true") !== "false",
1835
+ welcomeMessage: (_pa = (_oa = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _oa : readString(env, "UI_WELCOME_MESSAGE")) != null ? _pa : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1836
+ visualStyle: (_ra = (_qa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _qa : readString(env, "UI_VISUAL_STYLE")) != null ? _ra : "glass",
1837
+ borderRadius: (_ta = (_sa = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _sa : readString(env, "UI_BORDER_RADIUS")) != null ? _ta : "xl",
1838
+ allowUpload: ((_va = (_ua = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _ua : readString(env, "UI_ALLOW_UPLOAD")) != null ? _va : "false") === "true"
1838
1839
  },
1839
1840
  rag: {
1840
1841
  topK: readNumber(env, "RAG_TOP_K", 5),
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-JBZEUFW3.mjs";
42
+ } from "./chunk-OCDCJUNE.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.1.3",
3
+ "version": "1.1.5",
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",
@@ -65,6 +65,15 @@ body {
65
65
  background: rgba(255, 255, 255, 0.2);
66
66
  }
67
67
 
68
+ .scrollbar-hide::-webkit-scrollbar {
69
+ display: none;
70
+ }
71
+
72
+ .scrollbar-hide {
73
+ -ms-overflow-style: none;
74
+ scrollbar-width: none;
75
+ }
76
+
68
77
  /* ─── Prose Overrides (markdown in chat) ───────────────────── */
69
78
  .prose-invert {
70
79
  color: inherit;
@@ -7,6 +7,8 @@ import remarkGfm from 'remark-gfm';
7
7
  import { ChatMessage } from '../llm/ILLMProvider';
8
8
  import { VectorMatch } from '../types';
9
9
  import { SourceCard } from './SourceCard';
10
+ import { ProductCarousel } from './ProductCarousel';
11
+ import { Product } from './ProductCard';
10
12
 
11
13
  interface MessageBubbleProps {
12
14
  message: ChatMessage;
@@ -26,6 +28,81 @@ export function MessageBubble({
26
28
  const isUser = message.role === 'user';
27
29
  const [showSources, setShowSources] = React.useState(false);
28
30
 
31
+ // Helper to extract image from metadata or object
32
+ const resolveImage = (data: Record<string, unknown>): string | undefined => {
33
+ if (!data) return undefined;
34
+
35
+ // Check single string fields
36
+ const singleFields = ['image', 'img', 'thumbnail'];
37
+ for (const field of singleFields) {
38
+ const val = data[field];
39
+ if (typeof val === 'string' && val) return val;
40
+ }
41
+
42
+ // Check images array
43
+ if (Array.isArray(data.images) && data.images.length > 0) {
44
+ if (typeof data.images[0] === 'string') return data.images[0];
45
+ }
46
+
47
+ return undefined;
48
+ };
49
+
50
+ // Extract products from sources
51
+ const productsFromSources = React.useMemo(() => {
52
+ if (isUser || !sources) return [];
53
+ return sources
54
+ .filter(s => {
55
+ const m = s.metadata || {};
56
+ return m.price || m.image || m.img || m.thumbnail || m.images || m.brand || m.type === 'product';
57
+ })
58
+ .map(s => {
59
+ const m = (s.metadata || {}) as Record<string, unknown>;
60
+ return {
61
+ id: s.id,
62
+ name: (m.name || m.title || s.content.split('\n')[0] || 'Unknown Product') as string,
63
+ brand: m.brand as string,
64
+ price: m.price as string | number,
65
+ image: resolveImage(m),
66
+ link: m.link as string,
67
+ description: s.content
68
+ };
69
+ });
70
+ }, [sources, isUser]);
71
+
72
+ // Extract products from content (structured JSON)
73
+ const { productsFromContent, cleanContent } = React.useMemo(() => {
74
+ if (isUser) return { productsFromContent: [], cleanContent: message.content };
75
+
76
+ const jsonRegex = /```json\s*([\s\S]*?)\s*```/g;
77
+ const products: Product[] = [];
78
+ let content = message.content;
79
+
80
+ // Only process if it looks like it might contain product data
81
+ if (content.includes('"type": "products"') || content.includes('"type":"products"')) {
82
+ const matches = Array.from(content.matchAll(jsonRegex));
83
+ for (const m of matches) {
84
+ try {
85
+ const data = JSON.parse(m[1]);
86
+ if (data.type === 'products' && Array.isArray(data.items)) {
87
+ const formattedItems = data.items.map((item: Record<string, unknown>) => ({
88
+ ...item,
89
+ image: (item.image as string) || resolveImage(item)
90
+ })) as Product[];
91
+ products.push(...formattedItems);
92
+ content = content.replace(m[0], '');
93
+ }
94
+ } catch {
95
+ // Skip invalid JSON
96
+ }
97
+ }
98
+ }
99
+
100
+ return { productsFromContent: products, cleanContent: content.trim() };
101
+ }, [message.content, isUser]);
102
+
103
+ const allProducts = [...productsFromSources, ...productsFromContent];
104
+ const hasProducts = allProducts.length > 0;
105
+
29
106
  return (
30
107
  <div className={`flex gap-3 ${isUser ? 'flex-row-reverse' : 'flex-row'} items-start`}>
31
108
  {/* Avatar */}
@@ -64,8 +141,14 @@ export function MessageBubble({
64
141
  ) : (
65
142
  <div className={`prose prose-sm max-w-none ${isUser ? 'prose-invert' : 'dark:prose-invert'}`}>
66
143
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
67
- {message.content}
144
+ {cleanContent || message.content}
68
145
  </ReactMarkdown>
146
+
147
+ {/* Product Carousel */}
148
+ {hasProducts && (
149
+ <ProductCarousel products={allProducts} primaryColor={primaryColor} />
150
+ )}
151
+
69
152
  {isStreaming && message.content && (
70
153
  <span className="inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" />
71
154
  )}
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import Image from 'next/image';
5
+ import { ShoppingCart, ExternalLink } from 'lucide-react';
6
+
7
+ export interface Product {
8
+ id: string | number;
9
+ name: string;
10
+ brand?: string;
11
+ price?: string | number;
12
+ image?: string;
13
+ link?: string;
14
+ description?: string;
15
+ }
16
+
17
+ interface ProductCardProps {
18
+ product: Product;
19
+ primaryColor?: string;
20
+ }
21
+
22
+ export function ProductCard({ product, primaryColor = '#6366f1' }: ProductCardProps) {
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">
25
+ {/* Product Image */}
26
+ <div className="relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden">
27
+ {product.image ? (
28
+ <Image
29
+ src={product.image}
30
+ alt={product.name}
31
+ fill
32
+ unoptimized
33
+ className="object-cover group-hover:scale-105 transition-transform duration-500"
34
+ />
35
+ ) : (
36
+ <div className="text-slate-400 dark:text-white/20">
37
+ <ShoppingCart className="w-12 h-12" />
38
+ </div>
39
+ )}
40
+
41
+ {/* Brand Badge */}
42
+ {product.brand && (
43
+ <div className="absolute top-2 left-2 px-2 py-1 bg-white/90 dark:bg-black/60 backdrop-blur-md rounded-md text-[10px] font-bold uppercase tracking-wider text-slate-700 dark:text-white/90 shadow-sm">
44
+ {product.brand}
45
+ </div>
46
+ )}
47
+ </div>
48
+
49
+ {/* Product Info */}
50
+ <div className="p-4 flex flex-col gap-2">
51
+ <div className="flex justify-between items-start gap-2">
52
+ <h3 className="text-sm font-semibold text-slate-800 dark:text-white/90 line-clamp-1">
53
+ {product.name}
54
+ </h3>
55
+ {product.price && (
56
+ <span className="text-sm font-bold" style={{ color: primaryColor }}>
57
+ {typeof product.price === 'number' ? `$${product.price}` : product.price}
58
+ </span>
59
+ )}
60
+ </div>
61
+
62
+ {product.description && (
63
+ <p className="text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed">
64
+ {product.description}
65
+ </p>
66
+ )}
67
+
68
+ <div className="mt-2 flex gap-2">
69
+ <button
70
+ className="flex-1 py-2 px-3 rounded-lg text-[11px] font-medium text-white shadow-sm hover:opacity-90 transition-opacity flex items-center justify-center gap-1.5"
71
+ style={{ background: primaryColor }}
72
+ >
73
+ <ShoppingCart className="w-3 h-3" />
74
+ Add to Cart
75
+ </button>
76
+ {product.link && (
77
+ <a
78
+ href={product.link}
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ className="p-2 rounded-lg bg-slate-100 dark:bg-white/10 text-slate-600 dark:text-white/70 hover:bg-slate-200 dark:hover:bg-white/20 transition-colors"
82
+ >
83
+ <ExternalLink className="w-3.5 h-3.5" />
84
+ </a>
85
+ )}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import React, { useRef } from 'react';
4
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
5
+ import { Product, ProductCard } from './ProductCard';
6
+
7
+ interface ProductCarouselProps {
8
+ products: Product[];
9
+ primaryColor?: string;
10
+ }
11
+
12
+ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductCarouselProps) {
13
+ const scrollRef = useRef<HTMLDivElement>(null);
14
+
15
+ const scroll = (direction: 'left' | 'right') => {
16
+ if (scrollRef.current) {
17
+ const scrollAmount = direction === 'left' ? -280 : 280;
18
+ scrollRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
19
+ }
20
+ };
21
+
22
+ if (!products || products.length === 0) return null;
23
+
24
+ return (
25
+ <div className="relative w-full my-4 group/carousel">
26
+ {/* 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">
28
+ <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"
31
+ >
32
+ <ChevronLeft className="w-4 h-4" />
33
+ </button>
34
+ </div>
35
+
36
+ <div className="absolute -right-3 top-1/2 -translate-y-1/2 z-10 opacity-0 group-hover/carousel:opacity-100 transition-opacity">
37
+ <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"
40
+ >
41
+ <ChevronRight className="w-4 h-4" />
42
+ </button>
43
+ </div>
44
+
45
+ {/* Carousel Container */}
46
+ <div
47
+ ref={scrollRef}
48
+ className="flex gap-4 overflow-x-auto pb-4 px-1 scrollbar-hide snap-x snap-mandatory"
49
+ style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
50
+ >
51
+ {products.map((product) => (
52
+ <div key={product.id} className="snap-start">
53
+ <ProductCard product={product} primaryColor={primaryColor} />
54
+ </div>
55
+ ))}
56
+ </div>
57
+ </div>
58
+ );
59
+ }
@@ -74,6 +74,7 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
74
74
 
75
75
  if (vectorProvider === 'pinecone') {
76
76
  vectorDbOptions.apiKey = readString(env, 'PINECONE_API_KEY') ?? (base?.vectorDb?.options?.apiKey as string) ?? '';
77
+ vectorDbOptions.indexName = readString(env, 'PINECONE_INDEX') ?? (base?.vectorDb?.indexName as string) ?? (base?.vectorDb?.options?.indexName as string);
77
78
  } else if (vectorProvider === 'pgvector' || vectorProvider === 'postgresql') {
78
79
  vectorDbOptions.connectionString = readString(env, 'PGVECTOR_CONNECTION_STRING') ?? (base?.vectorDb?.options?.connectionString as string) ?? '';
79
80
  vectorDbOptions.dimensions = embeddingDimensions;
@@ -133,7 +134,10 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
133
134
  projectId,
134
135
  vectorDb: {
135
136
  provider: vectorProvider,
136
- indexName: readString(env, 'VECTOR_DB_INDEX') ?? 'rag-index',
137
+ indexName:
138
+ readString(env, 'VECTOR_DB_INDEX') ??
139
+ (vectorDbOptions.indexName as string) ??
140
+ 'rag-index',
137
141
  options: vectorDbOptions,
138
142
  },
139
143
  llm: {