@retrivora-ai/rag-engine 1.9.1 → 1.9.3

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
@@ -420,7 +420,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
420
420
  unoptimized: true,
421
421
  className: "object-cover group-hover:scale-105 transition-transform duration-500"
422
422
  }
423
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-slate-400 dark:text-white/20 cursor-pointer", onClick: () => onAddToCart == null ? void 0 : onAddToCart(product), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ShoppingCart, { className: "w-12 h-12" }) }),
423
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-slate-400 dark:text-white/20", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ShoppingCart, { className: "w-12 h-12" }) }),
424
424
  product.brand && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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", children: product.brand })
425
425
  ] }),
426
426
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-4 flex flex-col gap-2", children: [
@@ -434,7 +434,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
434
434
  "button",
435
435
  {
436
436
  onClick: () => onAddToCart == null ? void 0 : onAddToCart(product),
437
- 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 cursor-pointer",
437
+ 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",
438
438
  style: { background: primaryColor },
439
439
  children: [
440
440
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ShoppingCart, { className: "w-3 h-3" }),
@@ -1516,7 +1516,7 @@ var HourglassLoader_default = HourglassLoader;
1516
1516
  // src/utils/synonyms.ts
1517
1517
  var FIELD_SYNONYMS = {
1518
1518
  name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
1519
- price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
1519
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd", "variant price"],
1520
1520
  brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
1521
1521
  image: [
1522
1522
  "imageUrl",
@@ -1529,7 +1529,9 @@ var FIELD_SYNONYMS = {
1529
1529
  "image_url",
1530
1530
  "main_image",
1531
1531
  "product_image",
1532
- "thumb"
1532
+ "thumb",
1533
+ "image src",
1534
+ "variant image"
1533
1535
  ],
1534
1536
  stock: [
1535
1537
  "inventory",
@@ -1540,7 +1542,8 @@ var FIELD_SYNONYMS = {
1540
1542
  "inStock",
1541
1543
  "is_available",
1542
1544
  "in stock",
1543
- "status"
1545
+ "status",
1546
+ "variant inventory qty"
1544
1547
  ],
1545
1548
  category: [
1546
1549
  "product_category",
@@ -1548,9 +1551,10 @@ var FIELD_SYNONYMS = {
1548
1551
  "category_name",
1549
1552
  "category name",
1550
1553
  "department",
1551
- "collection"
1554
+ "collection",
1555
+ "type"
1552
1556
  ],
1553
- description: ["summary", "content", "body", "text", "info", "details"],
1557
+ description: ["summary", "content", "body", "text", "info", "details", "body (html)", "seo description"],
1554
1558
  link: ["url", "href", "product_url", "page_url", "link"]
1555
1559
  };
1556
1560
  function addSynonyms(customSynonyms) {
@@ -2226,6 +2230,16 @@ function MessageBubble({
2226
2230
  const isMedium = viewportSize === "medium";
2227
2231
  const [showSources, setShowSources] = import_react9.default.useState(false);
2228
2232
  const [showTrace, setShowTrace] = import_react9.default.useState(false);
2233
+ const [copied, setCopied] = import_react9.default.useState(false);
2234
+ const handleCopy = import_react9.default.useCallback(async () => {
2235
+ try {
2236
+ await navigator.clipboard.writeText(message.content);
2237
+ setCopied(true);
2238
+ setTimeout(() => setCopied(false), 2e3);
2239
+ } catch (err) {
2240
+ console.error("Failed to copy text:", err);
2241
+ }
2242
+ }, [message.content]);
2229
2243
  const structuredContent = import_react9.default.useMemo(
2230
2244
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
2231
2245
  [isUser, message.content]
@@ -2312,41 +2326,52 @@ ${match.trim()}
2312
2326
  }
2313
2327
  ),
2314
2328
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `flex flex-col gap-1 min-w-0 ${isCompact ? "max-w-[94%]" : isMedium ? "max-w-[92%]" : "max-w-[90%]"} ${isUser ? "items-end" : "items-start"}`, children: [
2315
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2329
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2316
2330
  "div",
2317
2331
  {
2318
- 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"}`,
2332
+ className: `relative group ${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"}`,
2319
2333
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {},
2320
- children: isStreaming && !message.content ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1 py-1", children: [
2321
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }),
2322
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }),
2323
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })
2324
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `prose ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${isUser ? "prose-invert" : "dark:prose-invert"}`, children: [
2325
- !isUser && structuredContent.payload && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2326
- UIDispatcher,
2334
+ children: [
2335
+ !isUser && !isStreaming && message.content && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2336
+ "button",
2327
2337
  {
2328
- rawContent: structuredContent.payload,
2329
- primaryColor,
2330
- accentColor,
2331
- isStreaming,
2332
- onAddToCart,
2333
- viewportSize
2338
+ onClick: handleCopy,
2339
+ className: `absolute top-2 right-2 p-1.5 rounded-lg border transition-all duration-200 cursor-pointer ${copied ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-500 dark:text-emerald-400" : "bg-slate-500/5 hover:bg-slate-500/10 border-slate-200 dark:border-white/10 text-slate-400 dark:text-white/30 hover:text-slate-600 dark:hover:text-white/60"} opacity-0 group-hover:opacity-100 backdrop-blur-sm shadow-sm scale-95 group-hover:scale-100`,
2340
+ title: copied ? "Copied!" : "Copy response",
2341
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Check, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Copy, { className: "w-3.5 h-3.5" })
2334
2342
  }
2335
2343
  ),
2336
- !shouldRenderStructuredOnly && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_markdown.default, { components: markdownComponents, children: processedMarkdown }),
2337
- isStreaming && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "inline-flex items-center gap-1.5 ml-2 text-emerald-500 animate-pulse align-middle text-xs font-medium", children: [
2338
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2339
- HourglassLoader_default,
2344
+ isStreaming && !message.content ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1 py-1", children: [
2345
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })
2348
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `prose ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${isUser ? "prose-invert" : "dark:prose-invert"}`, children: [
2349
+ !isUser && structuredContent.payload && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2350
+ UIDispatcher,
2340
2351
  {
2341
- size: 18,
2352
+ rawContent: structuredContent.payload,
2342
2353
  primaryColor,
2343
2354
  accentColor,
2344
- glow: false
2355
+ isStreaming,
2356
+ onAddToCart,
2357
+ viewportSize
2345
2358
  }
2346
2359
  ),
2347
- "Thinking..."
2360
+ !shouldRenderStructuredOnly && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_markdown.default, { components: markdownComponents, children: processedMarkdown }),
2361
+ isStreaming && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "inline-flex items-center gap-1.5 ml-2 text-emerald-500 animate-pulse align-middle text-xs font-medium", children: [
2362
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2363
+ HourglassLoader_default,
2364
+ {
2365
+ size: 18,
2366
+ primaryColor,
2367
+ accentColor,
2368
+ glow: false
2369
+ }
2370
+ ),
2371
+ "Thinking..."
2372
+ ] })
2348
2373
  ] })
2349
- ] })
2374
+ ]
2350
2375
  }
2351
2376
  ),
2352
2377
  (() => {
@@ -2354,7 +2379,8 @@ ${match.trim()}
2354
2379
  if (isUser || structuredContent.payload || !message.uiTransformation) return null;
2355
2380
  const ui = message.uiTransformation;
2356
2381
  const textContent = (_b = (_a2 = ui.data) == null ? void 0 : _a2.content) != null ? _b : "";
2357
- const shouldShow = ui.type === "table" || !["text", "table"].includes(ui.type) || ui.type === "text" && !hasRichUI && allProducts.length === 0 && textContent && !processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase());
2382
+ const isNegativeOrFallbackText = textContent.toLowerCase().includes("cannot answer") || textContent.toLowerCase().includes("no relevant data") || textContent.toLowerCase().includes("no data available") || typeof ui.title === "string" && ui.title.toLowerCase().includes("no data") || typeof ui.description === "string" && ui.description.toLowerCase().includes("no relevant data");
2383
+ const shouldShow = !isNegativeOrFallbackText && (ui.type === "table" || !["text", "table"].includes(ui.type) || ui.type === "text" && !hasRichUI && allProducts.length === 0 && textContent && !processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase()));
2358
2384
  if (!shouldShow) return null;
2359
2385
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2360
2386
  VisualizationRenderer,
@@ -2476,6 +2502,7 @@ function useRagChat(projectId, options = {}) {
2476
2502
  const [error, setError] = (0, import_react10.useState)(null);
2477
2503
  const lastInputRef = (0, import_react10.useRef)("");
2478
2504
  const messagesRef = (0, import_react10.useRef)(messages);
2505
+ const abortControllerRef = (0, import_react10.useRef)(null);
2479
2506
  (0, import_react10.useEffect)(() => {
2480
2507
  messagesRef.current = messages;
2481
2508
  }, [messages]);
@@ -2496,11 +2523,17 @@ function useRagChat(projectId, options = {}) {
2496
2523
  }
2497
2524
  setError(null);
2498
2525
  setIsLoading(true);
2526
+ if (abortControllerRef.current) {
2527
+ abortControllerRef.current.abort();
2528
+ }
2529
+ const controller = new AbortController();
2530
+ abortControllerRef.current = controller;
2499
2531
  try {
2500
2532
  const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
2501
2533
  const response = await fetch(apiUrl, {
2502
2534
  method: "POST",
2503
2535
  headers: { "Content-Type": "application/json" },
2536
+ signal: controller.signal,
2504
2537
  body: JSON.stringify({
2505
2538
  message: trimmed,
2506
2539
  history,
@@ -2609,11 +2642,16 @@ function useRagChat(projectId, options = {}) {
2609
2642
  );
2610
2643
  onReply == null ? void 0 : onReply(finalMsg);
2611
2644
  } catch (err) {
2645
+ if (err instanceof Error && err.name === "AbortError") {
2646
+ console.log("[useRagChat] Streaming stopped by user.");
2647
+ return;
2648
+ }
2612
2649
  const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
2613
2650
  setError(msg);
2614
2651
  onError == null ? void 0 : onError(msg);
2615
2652
  } finally {
2616
2653
  setIsLoading(false);
2654
+ abortControllerRef.current = null;
2617
2655
  }
2618
2656
  },
2619
2657
  [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages]
@@ -2631,6 +2669,13 @@ function useRagChat(projectId, options = {}) {
2631
2669
  });
2632
2670
  }
2633
2671
  }, [sendMessage]);
2672
+ const stop = (0, import_react10.useCallback)(() => {
2673
+ if (abortControllerRef.current) {
2674
+ abortControllerRef.current.abort();
2675
+ abortControllerRef.current = null;
2676
+ }
2677
+ setIsLoading(false);
2678
+ }, []);
2634
2679
  return {
2635
2680
  messages,
2636
2681
  isLoading,
@@ -2638,7 +2683,8 @@ function useRagChat(projectId, options = {}) {
2638
2683
  send: sendMessage,
2639
2684
  clear,
2640
2685
  retry,
2641
- setMessages
2686
+ setMessages,
2687
+ stop
2642
2688
  };
2643
2689
  }
2644
2690
 
@@ -2664,7 +2710,7 @@ function ChatWindow({
2664
2710
  const windowRef = (0, import_react11.useRef)(null);
2665
2711
  const bottomRef = (0, import_react11.useRef)(null);
2666
2712
  const inputRef = (0, import_react11.useRef)(null);
2667
- const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
2713
+ const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
2668
2714
  namespace: projectId
2669
2715
  });
2670
2716
  const [suggestions, setSuggestions] = (0, import_react11.useState)([]);
@@ -3028,14 +3074,22 @@ function ChatWindow({
3028
3074
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Paperclip, { className: "w-4 h-4" })
3029
3075
  }
3030
3076
  ),
3031
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3077
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3078
+ "button",
3079
+ {
3080
+ onClick: stop,
3081
+ className: "flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 shadow-md bg-rose-500 hover:bg-rose-600 text-white cursor-pointer",
3082
+ title: "Stop generating",
3083
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-3.5 h-3.5 bg-white rounded-[2px]" })
3084
+ }
3085
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3032
3086
  "button",
3033
3087
  {
3034
3088
  onClick: sendMessage,
3035
- disabled: !input.trim() || isLoading,
3089
+ disabled: !input.trim(),
3036
3090
  className: "flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:scale-105 active:scale-95 shadow-md",
3037
3091
  style: {
3038
- background: input.trim() && !isLoading ? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` : "rgba(148, 163, 184, 0.2)"
3092
+ background: input.trim() ? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` : "rgba(148, 163, 184, 0.2)"
3039
3093
  // slate-400/20 for light mode disabled
3040
3094
  },
3041
3095
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ArrowUp, { className: "w-4 h-4 text-white" })
package/dist/index.mjs CHANGED
@@ -316,7 +316,7 @@ function DocumentUpload({ namespace, onUploadComplete, className = "" }) {
316
316
 
317
317
  // src/components/MessageBubble.tsx
318
318
  import React9 from "react";
319
- import { Bot, User, ChevronDown as ChevronDown3, ChevronRight as ChevronRight3, Activity } from "lucide-react";
319
+ import { Bot, User, ChevronDown as ChevronDown3, ChevronRight as ChevronRight3, Activity, Copy, Check } from "lucide-react";
320
320
  import ReactMarkdown from "react-markdown";
321
321
 
322
322
  // src/components/SourceCard.tsx
@@ -393,7 +393,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
393
393
  unoptimized: true,
394
394
  className: "object-cover group-hover:scale-105 transition-transform duration-500"
395
395
  }
396
- ) : /* @__PURE__ */ jsx4("div", { className: "text-slate-400 dark:text-white/20 cursor-pointer", onClick: () => onAddToCart == null ? void 0 : onAddToCart(product), children: /* @__PURE__ */ jsx4(ShoppingCart, { className: "w-12 h-12" }) }),
396
+ ) : /* @__PURE__ */ jsx4("div", { className: "text-slate-400 dark:text-white/20", children: /* @__PURE__ */ jsx4(ShoppingCart, { className: "w-12 h-12" }) }),
397
397
  product.brand && /* @__PURE__ */ jsx4("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", children: product.brand })
398
398
  ] }),
399
399
  /* @__PURE__ */ jsxs3("div", { className: "p-4 flex flex-col gap-2", children: [
@@ -407,7 +407,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
407
407
  "button",
408
408
  {
409
409
  onClick: () => onAddToCart == null ? void 0 : onAddToCart(product),
410
- 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 cursor-pointer",
410
+ 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",
411
411
  style: { background: primaryColor },
412
412
  children: [
413
413
  /* @__PURE__ */ jsx4(ShoppingCart, { className: "w-3 h-3" }),
@@ -1535,7 +1535,7 @@ var HourglassLoader_default = HourglassLoader;
1535
1535
  // src/utils/synonyms.ts
1536
1536
  var FIELD_SYNONYMS = {
1537
1537
  name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
1538
- price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
1538
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd", "variant price"],
1539
1539
  brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
1540
1540
  image: [
1541
1541
  "imageUrl",
@@ -1548,7 +1548,9 @@ var FIELD_SYNONYMS = {
1548
1548
  "image_url",
1549
1549
  "main_image",
1550
1550
  "product_image",
1551
- "thumb"
1551
+ "thumb",
1552
+ "image src",
1553
+ "variant image"
1552
1554
  ],
1553
1555
  stock: [
1554
1556
  "inventory",
@@ -1559,7 +1561,8 @@ var FIELD_SYNONYMS = {
1559
1561
  "inStock",
1560
1562
  "is_available",
1561
1563
  "in stock",
1562
- "status"
1564
+ "status",
1565
+ "variant inventory qty"
1563
1566
  ],
1564
1567
  category: [
1565
1568
  "product_category",
@@ -1567,9 +1570,10 @@ var FIELD_SYNONYMS = {
1567
1570
  "category_name",
1568
1571
  "category name",
1569
1572
  "department",
1570
- "collection"
1573
+ "collection",
1574
+ "type"
1571
1575
  ],
1572
- description: ["summary", "content", "body", "text", "info", "details"],
1576
+ description: ["summary", "content", "body", "text", "info", "details", "body (html)", "seo description"],
1573
1577
  link: ["url", "href", "product_url", "page_url", "link"]
1574
1578
  };
1575
1579
  function addSynonyms(customSynonyms) {
@@ -2245,6 +2249,16 @@ function MessageBubble({
2245
2249
  const isMedium = viewportSize === "medium";
2246
2250
  const [showSources, setShowSources] = React9.useState(false);
2247
2251
  const [showTrace, setShowTrace] = React9.useState(false);
2252
+ const [copied, setCopied] = React9.useState(false);
2253
+ const handleCopy = React9.useCallback(async () => {
2254
+ try {
2255
+ await navigator.clipboard.writeText(message.content);
2256
+ setCopied(true);
2257
+ setTimeout(() => setCopied(false), 2e3);
2258
+ } catch (err) {
2259
+ console.error("Failed to copy text:", err);
2260
+ }
2261
+ }, [message.content]);
2248
2262
  const structuredContent = React9.useMemo(
2249
2263
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
2250
2264
  [isUser, message.content]
@@ -2331,41 +2345,52 @@ ${match.trim()}
2331
2345
  }
2332
2346
  ),
2333
2347
  /* @__PURE__ */ jsxs11("div", { className: `flex flex-col gap-1 min-w-0 ${isCompact ? "max-w-[94%]" : isMedium ? "max-w-[92%]" : "max-w-[90%]"} ${isUser ? "items-end" : "items-start"}`, children: [
2334
- /* @__PURE__ */ jsx12(
2348
+ /* @__PURE__ */ jsxs11(
2335
2349
  "div",
2336
2350
  {
2337
- 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"}`,
2351
+ className: `relative group ${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"}`,
2338
2352
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {},
2339
- children: isStreaming && !message.content ? /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 py-1", children: [
2340
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }),
2341
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }),
2342
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })
2343
- ] }) : /* @__PURE__ */ jsxs11("div", { className: `prose ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${isUser ? "prose-invert" : "dark:prose-invert"}`, children: [
2344
- !isUser && structuredContent.payload && /* @__PURE__ */ jsx12(
2345
- UIDispatcher,
2353
+ children: [
2354
+ !isUser && !isStreaming && message.content && /* @__PURE__ */ jsx12(
2355
+ "button",
2346
2356
  {
2347
- rawContent: structuredContent.payload,
2348
- primaryColor,
2349
- accentColor,
2350
- isStreaming,
2351
- onAddToCart,
2352
- viewportSize
2357
+ onClick: handleCopy,
2358
+ className: `absolute top-2 right-2 p-1.5 rounded-lg border transition-all duration-200 cursor-pointer ${copied ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-500 dark:text-emerald-400" : "bg-slate-500/5 hover:bg-slate-500/10 border-slate-200 dark:border-white/10 text-slate-400 dark:text-white/30 hover:text-slate-600 dark:hover:text-white/60"} opacity-0 group-hover:opacity-100 backdrop-blur-sm shadow-sm scale-95 group-hover:scale-100`,
2359
+ title: copied ? "Copied!" : "Copy response",
2360
+ children: copied ? /* @__PURE__ */ jsx12(Check, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsx12(Copy, { className: "w-3.5 h-3.5" })
2353
2361
  }
2354
2362
  ),
2355
- !shouldRenderStructuredOnly && /* @__PURE__ */ jsx12(ReactMarkdown, { components: markdownComponents, children: processedMarkdown }),
2356
- isStreaming && /* @__PURE__ */ jsxs11("span", { className: "inline-flex items-center gap-1.5 ml-2 text-emerald-500 animate-pulse align-middle text-xs font-medium", children: [
2357
- /* @__PURE__ */ jsx12(
2358
- HourglassLoader_default,
2363
+ isStreaming && !message.content ? /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 py-1", children: [
2364
+ /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }),
2365
+ /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }),
2366
+ /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })
2367
+ ] }) : /* @__PURE__ */ jsxs11("div", { className: `prose ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${isUser ? "prose-invert" : "dark:prose-invert"}`, children: [
2368
+ !isUser && structuredContent.payload && /* @__PURE__ */ jsx12(
2369
+ UIDispatcher,
2359
2370
  {
2360
- size: 18,
2371
+ rawContent: structuredContent.payload,
2361
2372
  primaryColor,
2362
2373
  accentColor,
2363
- glow: false
2374
+ isStreaming,
2375
+ onAddToCart,
2376
+ viewportSize
2364
2377
  }
2365
2378
  ),
2366
- "Thinking..."
2379
+ !shouldRenderStructuredOnly && /* @__PURE__ */ jsx12(ReactMarkdown, { components: markdownComponents, children: processedMarkdown }),
2380
+ isStreaming && /* @__PURE__ */ jsxs11("span", { className: "inline-flex items-center gap-1.5 ml-2 text-emerald-500 animate-pulse align-middle text-xs font-medium", children: [
2381
+ /* @__PURE__ */ jsx12(
2382
+ HourglassLoader_default,
2383
+ {
2384
+ size: 18,
2385
+ primaryColor,
2386
+ accentColor,
2387
+ glow: false
2388
+ }
2389
+ ),
2390
+ "Thinking..."
2391
+ ] })
2367
2392
  ] })
2368
- ] })
2393
+ ]
2369
2394
  }
2370
2395
  ),
2371
2396
  (() => {
@@ -2373,7 +2398,8 @@ ${match.trim()}
2373
2398
  if (isUser || structuredContent.payload || !message.uiTransformation) return null;
2374
2399
  const ui = message.uiTransformation;
2375
2400
  const textContent = (_b = (_a2 = ui.data) == null ? void 0 : _a2.content) != null ? _b : "";
2376
- const shouldShow = ui.type === "table" || !["text", "table"].includes(ui.type) || ui.type === "text" && !hasRichUI && allProducts.length === 0 && textContent && !processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase());
2401
+ const isNegativeOrFallbackText = textContent.toLowerCase().includes("cannot answer") || textContent.toLowerCase().includes("no relevant data") || textContent.toLowerCase().includes("no data available") || typeof ui.title === "string" && ui.title.toLowerCase().includes("no data") || typeof ui.description === "string" && ui.description.toLowerCase().includes("no relevant data");
2402
+ const shouldShow = !isNegativeOrFallbackText && (ui.type === "table" || !["text", "table"].includes(ui.type) || ui.type === "text" && !hasRichUI && allProducts.length === 0 && textContent && !processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase()));
2377
2403
  if (!shouldShow) return null;
2378
2404
  return /* @__PURE__ */ jsx12("div", { className: "w-full mt-3", children: /* @__PURE__ */ jsx12(
2379
2405
  VisualizationRenderer,
@@ -2495,6 +2521,7 @@ function useRagChat(projectId, options = {}) {
2495
2521
  const [error, setError] = useState4(null);
2496
2522
  const lastInputRef = useRef3("");
2497
2523
  const messagesRef = useRef3(messages);
2524
+ const abortControllerRef = useRef3(null);
2498
2525
  useEffect3(() => {
2499
2526
  messagesRef.current = messages;
2500
2527
  }, [messages]);
@@ -2515,11 +2542,17 @@ function useRagChat(projectId, options = {}) {
2515
2542
  }
2516
2543
  setError(null);
2517
2544
  setIsLoading(true);
2545
+ if (abortControllerRef.current) {
2546
+ abortControllerRef.current.abort();
2547
+ }
2548
+ const controller = new AbortController();
2549
+ abortControllerRef.current = controller;
2518
2550
  try {
2519
2551
  const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
2520
2552
  const response = await fetch(apiUrl, {
2521
2553
  method: "POST",
2522
2554
  headers: { "Content-Type": "application/json" },
2555
+ signal: controller.signal,
2523
2556
  body: JSON.stringify({
2524
2557
  message: trimmed,
2525
2558
  history,
@@ -2628,11 +2661,16 @@ function useRagChat(projectId, options = {}) {
2628
2661
  );
2629
2662
  onReply == null ? void 0 : onReply(finalMsg);
2630
2663
  } catch (err) {
2664
+ if (err instanceof Error && err.name === "AbortError") {
2665
+ console.log("[useRagChat] Streaming stopped by user.");
2666
+ return;
2667
+ }
2631
2668
  const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
2632
2669
  setError(msg);
2633
2670
  onError == null ? void 0 : onError(msg);
2634
2671
  } finally {
2635
2672
  setIsLoading(false);
2673
+ abortControllerRef.current = null;
2636
2674
  }
2637
2675
  },
2638
2676
  [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages]
@@ -2650,6 +2688,13 @@ function useRagChat(projectId, options = {}) {
2650
2688
  });
2651
2689
  }
2652
2690
  }, [sendMessage]);
2691
+ const stop = useCallback(() => {
2692
+ if (abortControllerRef.current) {
2693
+ abortControllerRef.current.abort();
2694
+ abortControllerRef.current = null;
2695
+ }
2696
+ setIsLoading(false);
2697
+ }, []);
2653
2698
  return {
2654
2699
  messages,
2655
2700
  isLoading,
@@ -2657,7 +2702,8 @@ function useRagChat(projectId, options = {}) {
2657
2702
  send: sendMessage,
2658
2703
  clear,
2659
2704
  retry,
2660
- setMessages
2705
+ setMessages,
2706
+ stop
2661
2707
  };
2662
2708
  }
2663
2709
 
@@ -2683,7 +2729,7 @@ function ChatWindow({
2683
2729
  const windowRef = useRef4(null);
2684
2730
  const bottomRef = useRef4(null);
2685
2731
  const inputRef = useRef4(null);
2686
- const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
2732
+ const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
2687
2733
  namespace: projectId
2688
2734
  });
2689
2735
  const [suggestions, setSuggestions] = useState5([]);
@@ -3047,14 +3093,22 @@ function ChatWindow({
3047
3093
  children: /* @__PURE__ */ jsx13(Paperclip, { className: "w-4 h-4" })
3048
3094
  }
3049
3095
  ),
3050
- /* @__PURE__ */ jsx13(
3096
+ isLoading ? /* @__PURE__ */ jsx13(
3097
+ "button",
3098
+ {
3099
+ onClick: stop,
3100
+ className: "flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 shadow-md bg-rose-500 hover:bg-rose-600 text-white cursor-pointer",
3101
+ title: "Stop generating",
3102
+ children: /* @__PURE__ */ jsx13("div", { className: "w-3.5 h-3.5 bg-white rounded-[2px]" })
3103
+ }
3104
+ ) : /* @__PURE__ */ jsx13(
3051
3105
  "button",
3052
3106
  {
3053
3107
  onClick: sendMessage,
3054
- disabled: !input.trim() || isLoading,
3108
+ disabled: !input.trim(),
3055
3109
  className: "flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:scale-105 active:scale-95 shadow-md",
3056
3110
  style: {
3057
- background: input.trim() && !isLoading ? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` : "rgba(148, 163, 184, 0.2)"
3111
+ background: input.trim() ? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` : "rgba(148, 163, 184, 0.2)"
3058
3112
  // slate-400/20 for light mode disabled
3059
3113
  },
3060
3114
  children: /* @__PURE__ */ jsx13(ArrowUp, { className: "w-4 h-4 text-white" })
package/dist/server.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { n as VectorDBConfig, L as LLMConfig, e as EmbeddingConfig, k as RagConfig, I as ILLMProvider, g as IngestDocument, C as ChatMessage, d as ChatResponse, p as RetrievalResult, m as UpsertDocument, V as VectorMatch, G as GraphDBConfig, q as GraphNode, r as Edge, s as GraphSearchResult, o as VectorDBProvider, h as LLMProvider, f as EmbeddingProvider, j as RAGConfig, U as UIConfig, c as ChatOptions, E as EmbedOptions } from './ILLMProvider-BQyKZLnd.mjs';
1
+ import { n as VectorDBConfig, L as LLMConfig, e as EmbeddingConfig, k as RagConfig, I as ILLMProvider, g as IngestDocument, C as ChatMessage, d as ChatResponse, p as RetrievalResult, m as UpsertDocument, V as VectorMatch, G as GraphDBConfig, q as GraphNode, r as Edge, s as GraphSearchResult, o as VectorDBProvider, h as LLMProvider, f as EmbeddingProvider, j as RAGConfig, U as UIConfig, c as ChatOptions, E as EmbedOptions } from './ILLMProvider-Bw2A28nU.mjs';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.mjs';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CDftK3qs.mjs';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CDftK3qs.mjs';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-B70ZLkfG.mjs';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-B70ZLkfG.mjs';
5
5
  import 'next/server';
6
6
 
7
7
  /**
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { n as VectorDBConfig, L as LLMConfig, e as EmbeddingConfig, k as RagConfig, I as ILLMProvider, g as IngestDocument, C as ChatMessage, d as ChatResponse, p as RetrievalResult, m as UpsertDocument, V as VectorMatch, G as GraphDBConfig, q as GraphNode, r as Edge, s as GraphSearchResult, o as VectorDBProvider, h as LLMProvider, f as EmbeddingProvider, j as RAGConfig, U as UIConfig, c as ChatOptions, E as EmbedOptions } from './ILLMProvider-BQyKZLnd.js';
1
+ import { n as VectorDBConfig, L as LLMConfig, e as EmbeddingConfig, k as RagConfig, I as ILLMProvider, g as IngestDocument, C as ChatMessage, d as ChatResponse, p as RetrievalResult, m as UpsertDocument, V as VectorMatch, G as GraphDBConfig, q as GraphNode, r as Edge, s as GraphSearchResult, o as VectorDBProvider, h as LLMProvider, f as EmbeddingProvider, j as RAGConfig, U as UIConfig, c as ChatOptions, E as EmbedOptions } from './ILLMProvider-Bw2A28nU.js';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.js';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-A0GqPsaG.js';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-A0GqPsaG.js';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-DVu-mkAM.js';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-DVu-mkAM.js';
5
5
  import 'next/server';
6
6
 
7
7
  /**