@retrivora-ai/rag-engine 1.9.0 → 1.9.2

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.
Files changed (47) hide show
  1. package/dist/{ILLMProvider-BOJFz3Na.d.mts → ILLMProvider-Bw2A28nU.d.mts} +12 -0
  2. package/dist/{ILLMProvider-BOJFz3Na.d.ts → ILLMProvider-Bw2A28nU.d.ts} +12 -0
  3. package/dist/handlers/index.d.mts +2 -2
  4. package/dist/handlers/index.d.ts +2 -2
  5. package/dist/handlers/index.js +1874 -542
  6. package/dist/handlers/index.mjs +1873 -541
  7. package/dist/{index-D3V9Et2M.d.mts → index-B70ZLkfG.d.mts} +1 -1
  8. package/dist/{index-BwpcaziY.d.ts → index-DVu-mkAM.d.ts} +1 -1
  9. package/dist/index.css +83 -0
  10. package/dist/index.d.mts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +330 -106
  13. package/dist/index.mjs +333 -107
  14. package/dist/server.d.mts +32 -5
  15. package/dist/server.d.ts +32 -5
  16. package/dist/server.js +1871 -736
  17. package/dist/server.mjs +1870 -735
  18. package/package.json +1 -1
  19. package/src/components/ChatWindow.tsx +24 -14
  20. package/src/components/MarkdownComponents.tsx +3 -3
  21. package/src/components/MessageBubble.tsx +89 -7
  22. package/src/components/ProductCard.tsx +29 -2
  23. package/src/components/UIDispatcher.tsx +1 -0
  24. package/src/components/VisualizationRenderer.tsx +143 -11
  25. package/src/config/EmbeddingStrategy.ts +5 -4
  26. package/src/config/RagConfig.ts +10 -0
  27. package/src/config/serverConfig.ts +16 -1
  28. package/src/core/LLMRouter.ts +79 -0
  29. package/src/core/Pipeline.ts +295 -51
  30. package/src/core/ProviderRegistry.ts +6 -0
  31. package/src/core/QueryProcessor.ts +108 -9
  32. package/src/handlers/index.ts +37 -11
  33. package/src/hooks/useRagChat.ts +77 -17
  34. package/src/llm/providers/UniversalLLMAdapter.ts +110 -13
  35. package/src/providers/vectordb/ChromaDBProvider.ts +13 -2
  36. package/src/providers/vectordb/MilvusProvider.ts +18 -2
  37. package/src/providers/vectordb/MultiTablePostgresProvider.ts +48 -16
  38. package/src/providers/vectordb/PostgreSQLProvider.ts +1 -1
  39. package/src/providers/vectordb/QdrantProvider.ts +1 -1
  40. package/src/providers/vectordb/RedisProvider.ts +3 -4
  41. package/src/providers/vectordb/WeaviateProvider.ts +41 -3
  42. package/src/types/chat.ts +2 -0
  43. package/src/types/index.ts +26 -0
  44. package/src/utils/ProductExtractor.ts +5 -3
  45. package/src/utils/SchemaMapper.ts +6 -4
  46. package/src/utils/UITransformer.ts +1350 -490
  47. package/src/utils/synonyms.ts +6 -4
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
@@ -368,7 +368,20 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
368
368
  import Image from "next/image";
369
369
  import { ShoppingCart, ExternalLink } from "lucide-react";
370
370
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
371
+ function cleanProductDescription(raw) {
372
+ var _a;
373
+ if (raw === null || raw === void 0) return "";
374
+ const source = String(raw);
375
+ const bodyMatch = source.match(
376
+ /\bBody\s*\(HTML\)\s*:\s*([\s\S]*?)(?=\s*[,.]?\s+\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\b\s*:|$)/i
377
+ );
378
+ const descriptionMatch = bodyMatch != null ? bodyMatch : source.match(
379
+ /\b(?:Description|Summary|Details|Body)\s*:\s*([\s\S]*?)(?=\s*[,.]?\s+\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\b\s*:|$)/i
380
+ );
381
+ return ((_a = descriptionMatch == null ? void 0 : descriptionMatch[1]) != null ? _a : source).replace(/<[^>]*>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/\[[^\]]*TYPE[^\]]*\]/gi, " ").replace(/\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\s*:\s*[^,.\n:]+[,.]?/gi, " ").replace(/\bBody\s*\(HTML\)\s*:\s*/gi, " ").replace(/\b(?:Description|Summary|Details|Body|Content)\s*:\s*/gi, " ").replace(/\s+/g, " ").trim();
382
+ }
371
383
  function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
384
+ const description = cleanProductDescription(product.description);
372
385
  return /* @__PURE__ */ jsxs3("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", children: [
373
386
  /* @__PURE__ */ jsxs3("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden", children: [
374
387
  product.image ? /* @__PURE__ */ jsx4(
@@ -388,7 +401,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
388
401
  /* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-slate-800 dark:text-white/90 line-clamp-1", children: product.name }),
389
402
  product.price && /* @__PURE__ */ jsx4("span", { className: "text-sm font-bold", style: { color: primaryColor }, children: typeof product.price === "number" ? `$${product.price}` : String(product.price).match(/^[\d.]/) ? `$${product.price}` : product.price })
390
403
  ] }),
391
- product.description && /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed", children: product.description }),
404
+ description && /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed", children: description }),
392
405
  /* @__PURE__ */ jsxs3("div", { className: "mt-2 flex gap-2", children: [
393
406
  /* @__PURE__ */ jsxs3(
394
407
  "button",
@@ -503,6 +516,8 @@ import {
503
516
  Bar as Bar2,
504
517
  LineChart as LineChart2,
505
518
  Line as Line2,
519
+ ScatterChart,
520
+ Scatter,
506
521
  XAxis as XAxis2,
507
522
  YAxis as YAxis2,
508
523
  CartesianGrid as CartesianGrid2,
@@ -726,6 +741,9 @@ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-run
726
741
  function getColor(index) {
727
742
  return CHART_COLORS[index % CHART_COLORS.length];
728
743
  }
744
+ function stripLeakedAnswerWrappers(raw) {
745
+ return raw.replace(/<prose\s+answer(?:\s+in\s+[^>]*)?>\s*([\s\S]*?)\s*<\/prose>/gi, "$1").replace(/<\/?prose(?:\s+answer)?[^>]*>/gi, "").trim();
746
+ }
729
747
  function CustomTooltip({
730
748
  active,
731
749
  payload,
@@ -775,10 +793,20 @@ function renderVisualization(type, data, onAddToCart, primaryColor, isStreaming)
775
793
  return /* @__PURE__ */ jsx7(PieChartView, { data, isStreaming });
776
794
  case "bar_chart":
777
795
  return /* @__PURE__ */ jsx7(BarChartView, { data, primaryColor, isStreaming });
796
+ case "histogram":
797
+ return /* @__PURE__ */ jsx7(BarChartView, { data, primaryColor, isStreaming });
798
+ case "horizontal_bar":
799
+ return /* @__PURE__ */ jsx7(BarChartView, { data, primaryColor, isStreaming, layout: "vertical" });
778
800
  case "line_chart":
779
801
  return /* @__PURE__ */ jsx7(LineChartView, { data, primaryColor, isStreaming });
802
+ case "scatter_plot":
803
+ return /* @__PURE__ */ jsx7(ScatterPlotView, { data, primaryColor, isStreaming });
780
804
  case "radar_chart":
781
805
  return /* @__PURE__ */ jsx7(RadarChartView, { data, isStreaming });
806
+ case "metric_card":
807
+ return /* @__PURE__ */ jsx7(MetricCardView, { data, isStreaming });
808
+ case "geo_map":
809
+ return /* @__PURE__ */ jsx7(BarChartView, { data, primaryColor, isStreaming });
782
810
  case "table":
783
811
  return /* @__PURE__ */ jsx7(TableView, { data, isStreaming });
784
812
  case "product_carousel":
@@ -813,28 +841,74 @@ function PieChartView({ data, isStreaming }) {
813
841
  }
814
842
  const chartData = data.map((item) => ({
815
843
  name: item.label,
816
- value: item.value
844
+ value: item.value,
845
+ inStockCount: item.inStockCount,
846
+ outOfStockCount: item.outOfStockCount
817
847
  }));
818
- return /* @__PURE__ */ jsx7("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: /* @__PURE__ */ jsx7(ResponsiveContainer2, { width: "100%", height: 280, children: /* @__PURE__ */ jsxs6(PieChart2, { children: [
819
- /* @__PURE__ */ jsx7(
820
- Pie2,
821
- {
822
- data: chartData,
823
- dataKey: "value",
824
- nameKey: "name",
825
- cx: "50%",
826
- cy: "50%",
827
- outerRadius: "68%",
828
- label: false,
829
- labelLine: false,
830
- children: chartData.map((_entry, index) => /* @__PURE__ */ jsx7(Cell2, { fill: getColor(index) }, `cell-${index}`))
831
- }
832
- ),
833
- /* @__PURE__ */ jsx7(Tooltip2, { content: /* @__PURE__ */ jsx7(CustomTooltip, {}) }),
834
- /* @__PURE__ */ jsx7(Legend2, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })
835
- ] }) }) });
848
+ const hasStockBreakdown = chartData.some(
849
+ (item) => item.inStockCount !== void 0 || item.outOfStockCount !== void 0
850
+ );
851
+ return /* @__PURE__ */ jsxs6("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: [
852
+ /* @__PURE__ */ jsx7(ResponsiveContainer2, { width: "100%", height: 280, children: /* @__PURE__ */ jsxs6(PieChart2, { children: [
853
+ /* @__PURE__ */ jsx7(
854
+ Pie2,
855
+ {
856
+ data: chartData,
857
+ dataKey: "value",
858
+ nameKey: "name",
859
+ cx: "50%",
860
+ cy: "50%",
861
+ outerRadius: "68%",
862
+ label: false,
863
+ labelLine: false,
864
+ children: chartData.map((_entry, index) => /* @__PURE__ */ jsx7(Cell2, { fill: getColor(index) }, `cell-${index}`))
865
+ }
866
+ ),
867
+ /* @__PURE__ */ jsx7(Tooltip2, { content: /* @__PURE__ */ jsx7(CustomTooltip, {}) }),
868
+ /* @__PURE__ */ jsx7(Legend2, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })
869
+ ] }) }),
870
+ hasStockBreakdown && /* @__PURE__ */ jsx7("div", { className: "mt-3 grid gap-2 sm:grid-cols-2", children: chartData.map((item, index) => {
871
+ var _a, _b;
872
+ const inStock = Number((_a = item.inStockCount) != null ? _a : 0);
873
+ const outOfStock = Number((_b = item.outOfStockCount) != null ? _b : 0);
874
+ const status = inStock > 0 ? `${inStock} in stock` : "Out of stock";
875
+ const mutedStatus = outOfStock > 0 ? `${outOfStock} out` : null;
876
+ return /* @__PURE__ */ jsxs6(
877
+ "div",
878
+ {
879
+ className: "flex min-w-0 items-center justify-between gap-3 rounded-md border border-slate-100 px-2.5 py-2 text-xs dark:border-white/10",
880
+ children: [
881
+ /* @__PURE__ */ jsxs6("span", { className: "flex min-w-0 items-center gap-2 text-slate-700 dark:text-white/75", children: [
882
+ /* @__PURE__ */ jsx7(
883
+ "span",
884
+ {
885
+ className: "h-2.5 w-2.5 flex-shrink-0 rounded-full",
886
+ style: { background: getColor(index) }
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsx7("span", { className: "truncate", children: item.name })
890
+ ] }),
891
+ /* @__PURE__ */ jsxs6("span", { className: inStock > 0 ? "whitespace-nowrap font-medium text-emerald-600" : "whitespace-nowrap font-medium text-rose-600", children: [
892
+ status,
893
+ mutedStatus && /* @__PURE__ */ jsxs6("span", { className: "ml-1 text-slate-400", children: [
894
+ "(",
895
+ mutedStatus,
896
+ ")"
897
+ ] })
898
+ ] })
899
+ ]
900
+ },
901
+ item.name
902
+ );
903
+ }) })
904
+ ] });
836
905
  }
837
- function BarChartView({ data, primaryColor, isStreaming }) {
906
+ function BarChartView({
907
+ data,
908
+ primaryColor,
909
+ isStreaming,
910
+ layout = "horizontal"
911
+ }) {
838
912
  if (isStreaming) return /* @__PURE__ */ jsx7(ChartSkeleton, { type: "bar" });
839
913
  if (!Array.isArray(data) || data.length === 0) {
840
914
  return /* @__PURE__ */ jsx7("div", { className: "text-xs text-red-500", children: "Invalid bar chart data" });
@@ -845,26 +919,66 @@ function BarChartView({ data, primaryColor, isStreaming }) {
845
919
  }, item.inStockCount !== void 0 && { inStockCount: item.inStockCount }), item.outOfStockCount !== void 0 && { outOfStockCount: item.outOfStockCount }));
846
920
  const barColor = primaryColor != null ? primaryColor : getColor(0);
847
921
  const hasStock = chartData.some((d) => "inStockCount" in d);
848
- return /* @__PURE__ */ jsx7("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm overflow-x-auto", children: /* @__PURE__ */ jsx7(ResponsiveContainer2, { width: "100%", height: 260, children: /* @__PURE__ */ jsxs6(BarChart2, { data: chartData, margin: { top: 10, right: 10, left: -20, bottom: 0 }, children: [
849
- /* @__PURE__ */ jsx7(CartesianGrid2, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }),
850
- /* @__PURE__ */ jsx7(
851
- XAxis2,
852
- {
853
- dataKey: "category",
854
- tick: { fontSize: 11, fill: "#64748b" },
855
- tickLine: false,
856
- axisLine: { stroke: "#cbd5e1" }
857
- }
858
- ),
859
- /* @__PURE__ */ jsx7(YAxis2, { tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false }),
860
- /* @__PURE__ */ jsx7(Tooltip2, { content: /* @__PURE__ */ jsx7(CustomTooltip, {}), cursor: { fill: "#f1f5f9" } }),
861
- /* @__PURE__ */ jsx7(Legend2, { wrapperStyle: { fontSize: 12 } }),
862
- hasStock ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
863
- /* @__PURE__ */ jsx7(Bar2, { dataKey: "inStockCount", name: "In Stock", fill: getColor(1), radius: [4, 4, 0, 0] }),
864
- /* @__PURE__ */ jsx7(Bar2, { dataKey: "outOfStockCount", name: "Out of Stock", fill: getColor(3), radius: [4, 4, 0, 0] })
865
- ] }) : /* @__PURE__ */ jsx7(Bar2, { dataKey: "value", name: "Value", fill: barColor, radius: [4, 4, 0, 0] })
922
+ return /* @__PURE__ */ jsx7("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm overflow-x-auto", children: /* @__PURE__ */ jsx7(ResponsiveContainer2, { width: "100%", height: 260, children: /* @__PURE__ */ jsxs6(
923
+ BarChart2,
924
+ {
925
+ data: chartData,
926
+ layout,
927
+ margin: { top: 10, right: 10, left: layout === "vertical" ? 20 : -20, bottom: 0 },
928
+ children: [
929
+ /* @__PURE__ */ jsx7(CartesianGrid2, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }),
930
+ layout === "vertical" ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
931
+ /* @__PURE__ */ jsx7(XAxis2, { type: "number", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }),
932
+ /* @__PURE__ */ jsx7(YAxis2, { type: "category", dataKey: "category", width: 110, tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false })
933
+ ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
934
+ /* @__PURE__ */ jsx7(
935
+ XAxis2,
936
+ {
937
+ dataKey: "category",
938
+ tick: { fontSize: 11, fill: "#64748b" },
939
+ tickLine: false,
940
+ axisLine: { stroke: "#cbd5e1" }
941
+ }
942
+ ),
943
+ /* @__PURE__ */ jsx7(YAxis2, { tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false })
944
+ ] }),
945
+ /* @__PURE__ */ jsx7(Tooltip2, { content: /* @__PURE__ */ jsx7(CustomTooltip, {}), cursor: { fill: "#f1f5f9" } }),
946
+ /* @__PURE__ */ jsx7(Legend2, { wrapperStyle: { fontSize: 12 } }),
947
+ hasStock ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
948
+ /* @__PURE__ */ jsx7(Bar2, { dataKey: "inStockCount", name: "In Stock", fill: getColor(1), radius: [4, 4, 0, 0] }),
949
+ /* @__PURE__ */ jsx7(Bar2, { dataKey: "outOfStockCount", name: "Out of Stock", fill: getColor(3), radius: [4, 4, 0, 0] })
950
+ ] }) : /* @__PURE__ */ jsx7(Bar2, { dataKey: "value", name: "Value", fill: barColor, radius: [4, 4, 0, 0] })
951
+ ]
952
+ }
953
+ ) }) });
954
+ }
955
+ function ScatterPlotView({ data, primaryColor, isStreaming }) {
956
+ if (isStreaming) return /* @__PURE__ */ jsx7(ChartSkeleton, { type: "bar" });
957
+ if (!Array.isArray(data) || data.length === 0) {
958
+ return /* @__PURE__ */ jsx7("div", { className: "text-xs text-red-500", children: "Invalid scatter plot data" });
959
+ }
960
+ return /* @__PURE__ */ jsx7("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm overflow-x-auto", children: /* @__PURE__ */ jsx7(ResponsiveContainer2, { width: "100%", height: 260, children: /* @__PURE__ */ jsxs6(ScatterChart, { margin: { top: 10, right: 10, left: -20, bottom: 0 }, children: [
961
+ /* @__PURE__ */ jsx7(CartesianGrid2, { strokeDasharray: "3 3", stroke: "#e2e8f0" }),
962
+ /* @__PURE__ */ jsx7(XAxis2, { type: "number", dataKey: "x", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }),
963
+ /* @__PURE__ */ jsx7(YAxis2, { type: "number", dataKey: "y", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false }),
964
+ /* @__PURE__ */ jsx7(Tooltip2, { content: /* @__PURE__ */ jsx7(CustomTooltip, {}), cursor: { strokeDasharray: "3 3" } }),
965
+ /* @__PURE__ */ jsx7(Scatter, { name: "Value", data, fill: primaryColor != null ? primaryColor : getColor(0) })
866
966
  ] }) }) });
867
967
  }
968
+ function MetricCardView({ data, isStreaming }) {
969
+ if (isStreaming) return /* @__PURE__ */ jsx7(ChartSkeleton, { type: "bar" });
970
+ if (!data || typeof data.value !== "number") {
971
+ return /* @__PURE__ */ jsx7("div", { className: "text-xs text-red-500", children: "Invalid metric data" });
972
+ }
973
+ return /* @__PURE__ */ jsxs6("div", { className: "rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-slate-900", children: [
974
+ /* @__PURE__ */ jsx7("div", { className: "text-xs font-medium uppercase text-slate-500 dark:text-slate-400", children: data.label }),
975
+ /* @__PURE__ */ jsxs6("div", { className: "mt-2 text-3xl font-semibold text-slate-900 dark:text-white", children: [
976
+ data.value.toLocaleString(),
977
+ data.unit && /* @__PURE__ */ jsx7("span", { className: "ml-1 text-base text-slate-500", children: data.unit })
978
+ ] }),
979
+ data.operation && /* @__PURE__ */ jsx7("div", { className: "mt-1 text-xs text-slate-500 dark:text-slate-400", children: data.operation })
980
+ ] });
981
+ }
868
982
  function LineChartView({ data, primaryColor, isStreaming }) {
869
983
  if (isStreaming) return /* @__PURE__ */ jsx7(ChartSkeleton, { type: "bar" });
870
984
  if (!Array.isArray(data) || data.length === 0) {
@@ -998,7 +1112,7 @@ function CarouselView({
998
1112
  ) });
999
1113
  }
1000
1114
  function TextView({ data }) {
1001
- const content = (data == null ? void 0 : data.content) || String(data != null ? data : "");
1115
+ const content = stripLeakedAnswerWrappers((data == null ? void 0 : data.content) || String(data != null ? data : ""));
1002
1116
  return /* @__PURE__ */ jsx7("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: /* @__PURE__ */ jsx7("p", { className: "text-sm text-slate-700 dark:text-slate-300 whitespace-pre-wrap leading-relaxed", children: content }) });
1003
1117
  }
1004
1118
 
@@ -1421,7 +1535,7 @@ var HourglassLoader_default = HourglassLoader;
1421
1535
  // src/utils/synonyms.ts
1422
1536
  var FIELD_SYNONYMS = {
1423
1537
  name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
1424
- price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
1538
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd", "variant price"],
1425
1539
  brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
1426
1540
  image: [
1427
1541
  "imageUrl",
@@ -1434,7 +1548,9 @@ var FIELD_SYNONYMS = {
1434
1548
  "image_url",
1435
1549
  "main_image",
1436
1550
  "product_image",
1437
- "thumb"
1551
+ "thumb",
1552
+ "image src",
1553
+ "variant image"
1438
1554
  ],
1439
1555
  stock: [
1440
1556
  "inventory",
@@ -1445,9 +1561,19 @@ var FIELD_SYNONYMS = {
1445
1561
  "inStock",
1446
1562
  "is_available",
1447
1563
  "in stock",
1448
- "status"
1564
+ "status",
1565
+ "variant inventory qty"
1449
1566
  ],
1450
- description: ["summary", "content", "body", "text", "info", "details"],
1567
+ category: [
1568
+ "product_category",
1569
+ "product category",
1570
+ "category_name",
1571
+ "category name",
1572
+ "department",
1573
+ "collection",
1574
+ "type"
1575
+ ],
1576
+ description: ["summary", "content", "body", "text", "info", "details", "body (html)", "seo description"],
1451
1577
  link: ["url", "href", "product_url", "page_url", "link"]
1452
1578
  };
1453
1579
  function addSynonyms(customSynonyms) {
@@ -1654,11 +1780,12 @@ function extractProductsFromSources(sources, isUser) {
1654
1780
  var _a;
1655
1781
  const m = (_a = s.metadata) != null ? _a : {};
1656
1782
  const keys = Object.keys(m).map((k) => k.toLowerCase());
1657
- const hasProductKey = keys.some(
1658
- (k) => ["price", "image", "img", "thumbnail", "images", "brand", "product", "sku", "category", "model", "cost"].includes(k)
1783
+ const hasStrongProductKey = keys.some(
1784
+ (k) => ["price", "image", "img", "thumbnail", "images", "product", "sku", "cost"].includes(k)
1659
1785
  );
1786
+ const hasProductIdentity = keys.some((k) => ["brand", "model"].includes(k)) && keys.some((k) => ["name", "title", "product_name", "product"].includes(k));
1660
1787
  const hasPricePattern = /\$\s*\d+/.test(s.content);
1661
- return hasProductKey || hasPricePattern || m.type === "product";
1788
+ return hasStrongProductKey || hasProductIdentity || hasPricePattern || m.type === "product";
1662
1789
  }).map((s) => {
1663
1790
  var _a, _b;
1664
1791
  const m = (_a = s.metadata) != null ? _a : {};
@@ -1845,6 +1972,7 @@ function resolveStructuredRows(config) {
1845
1972
  }
1846
1973
  function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
1847
1974
  var _a;
1975
+ console.log("rawContent", rawContent);
1848
1976
  const result = React7.useMemo(() => {
1849
1977
  try {
1850
1978
  const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
@@ -1979,12 +2107,11 @@ function createMarkdownComponents({
1979
2107
  viewportSize
1980
2108
  }) {
1981
2109
  return {
1982
- p: (_a) => {
2110
+ // p: ({ ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
2111
+ // <p className="whitespace-pre-line" {...props} />
2112
+ // ),
2113
+ table: (_a) => {
1983
2114
  var props = __objRest(_a, []);
1984
- return /* @__PURE__ */ jsx11("p", __spreadValues({ className: "whitespace-pre-line" }, props));
1985
- },
1986
- table: (_b) => {
1987
- var props = __objRest(_b, []);
1988
2115
  if (isStreaming) {
1989
2116
  return /* @__PURE__ */ jsxs10("div", { className: "my-5 p-6 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex items-center justify-center gap-3 select-none", children: [
1990
2117
  /* @__PURE__ */ jsx11("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }),
@@ -1998,8 +2125,8 @@ function createMarkdownComponents({
1998
2125
  }, props)
1999
2126
  ) }) });
2000
2127
  },
2001
- thead: (_c) => {
2002
- var props = __objRest(_c, []);
2128
+ thead: (_b) => {
2129
+ var props = __objRest(_b, []);
2003
2130
  return /* @__PURE__ */ jsx11(
2004
2131
  "thead",
2005
2132
  __spreadValues({
@@ -2007,12 +2134,12 @@ function createMarkdownComponents({
2007
2134
  }, props)
2008
2135
  );
2009
2136
  },
2010
- tbody: (_d) => {
2011
- var props = __objRest(_d, []);
2137
+ tbody: (_c) => {
2138
+ var props = __objRest(_c, []);
2012
2139
  return /* @__PURE__ */ jsx11("tbody", __spreadValues({ className: "!table-row-group divide-y divide-slate-100 dark:divide-white/5" }, props));
2013
2140
  },
2014
- tr: (_e) => {
2015
- var props = __objRest(_e, []);
2141
+ tr: (_d) => {
2142
+ var props = __objRest(_d, []);
2016
2143
  return /* @__PURE__ */ jsx11(
2017
2144
  "tr",
2018
2145
  __spreadValues({
@@ -2020,8 +2147,8 @@ function createMarkdownComponents({
2020
2147
  }, props)
2021
2148
  );
2022
2149
  },
2023
- th: (_f) => {
2024
- var _g = _f, { children } = _g, props = __objRest(_g, ["children"]);
2150
+ th: (_e) => {
2151
+ var _f = _e, { children } = _f, props = __objRest(_f, ["children"]);
2025
2152
  return /* @__PURE__ */ jsx11(
2026
2153
  "th",
2027
2154
  __spreadProps(__spreadValues({
@@ -2031,8 +2158,8 @@ function createMarkdownComponents({
2031
2158
  })
2032
2159
  );
2033
2160
  },
2034
- td: (_h) => {
2035
- var _i = _h, { children } = _i, props = __objRest(_i, ["children"]);
2161
+ td: (_g) => {
2162
+ var _h = _g, { children } = _h, props = __objRest(_h, ["children"]);
2036
2163
  return /* @__PURE__ */ jsx11(
2037
2164
  "td",
2038
2165
  __spreadProps(__spreadValues({
@@ -2042,12 +2169,12 @@ function createMarkdownComponents({
2042
2169
  })
2043
2170
  );
2044
2171
  },
2045
- code(_j) {
2046
- var _k = _j, {
2172
+ code(_i) {
2173
+ var _j = _i, {
2047
2174
  inline,
2048
2175
  className,
2049
2176
  children
2050
- } = _k, props = __objRest(_k, [
2177
+ } = _j, props = __objRest(_j, [
2051
2178
  "inline",
2052
2179
  "className",
2053
2180
  "children"
@@ -2097,6 +2224,16 @@ function normaliseChild(children) {
2097
2224
 
2098
2225
  // src/components/MessageBubble.tsx
2099
2226
  import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2227
+ function normalizePlusSeparatedLists(raw) {
2228
+ return raw.split("\n").map((line) => {
2229
+ const productListLine = /[—:]\s*\d+\s+\+\s+[A-Za-z]/.test(line) || /\b(products?|categories?|in stock|out of stock)\b/i.test(line);
2230
+ if (!productListLine) return line;
2231
+ return line.replace(/\s+\+\s+(?=[A-Za-z0-9])/g, ", ");
2232
+ }).join("\n");
2233
+ }
2234
+ function stripLeakedAnswerWrappers2(raw) {
2235
+ return raw.replace(/<prose\s+answer(?:\s+in\s+[^>]*)?>\s*([\s\S]*?)\s*<\/prose>/gi, "$1").replace(/<\/?prose(?:\s+answer)?[^>]*>/gi, "").replace(/\n{3,}/g, "\n\n").trim();
2236
+ }
2100
2237
  function MessageBubble({
2101
2238
  message,
2102
2239
  sources,
@@ -2106,11 +2243,22 @@ function MessageBubble({
2106
2243
  onAddToCart,
2107
2244
  viewportSize = "large"
2108
2245
  }) {
2246
+ var _a;
2109
2247
  const isUser = message.role === "user";
2110
2248
  const isCompact = viewportSize === "compact";
2111
2249
  const isMedium = viewportSize === "medium";
2112
2250
  const [showSources, setShowSources] = React9.useState(false);
2113
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]);
2114
2262
  const structuredContent = React9.useMemo(
2115
2263
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
2116
2264
  [isUser, message.content]
@@ -2125,6 +2273,10 @@ function MessageBubble({
2125
2273
  }
2126
2274
  return false;
2127
2275
  }, [message.uiTransformation, structuredContent.payload]);
2276
+ const uiTransformationType = (_a = message.uiTransformation) == null ? void 0 : _a.type;
2277
+ const shouldSuppressProductCarousel = Boolean(
2278
+ uiTransformationType && !["text", "product_carousel", "carousel"].includes(uiTransformationType)
2279
+ );
2128
2280
  const shouldRenderStructuredOnly = !isUser && hasRichUI && isLikelyUiOnlyMessage(structuredContent.text);
2129
2281
  const productsFromSources = React9.useMemo(() => {
2130
2282
  return extractProductsFromSources(sources, isUser);
@@ -2150,6 +2302,14 @@ function MessageBubble({
2150
2302
  }, [productsFromSources, productsFromContent, message.content]);
2151
2303
  const processedMarkdown = React9.useMemo(() => {
2152
2304
  let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
2305
+ raw = raw.replace(/\$\s*(\d+)(?!\.\d)/g, "$1");
2306
+ raw = normalizePlusSeparatedLists(raw);
2307
+ raw = stripLeakedAnswerWrappers2(raw);
2308
+ raw = raw.replace(/\*\*([^*\n]+)\*\*(\s*[—\-–:]\s*)/g, "$1$2");
2309
+ raw = raw.split("\n").filter((line, idx, arr) => {
2310
+ const trimmed = line.trim();
2311
+ return !trimmed || arr.findIndex((l) => l.trim() === trimmed) === idx;
2312
+ }).join("\n");
2153
2313
  const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
2154
2314
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
2155
2315
  if (!isStreaming) {
@@ -2185,49 +2345,61 @@ ${match.trim()}
2185
2345
  }
2186
2346
  ),
2187
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: [
2188
- /* @__PURE__ */ jsx12(
2348
+ /* @__PURE__ */ jsxs11(
2189
2349
  "div",
2190
2350
  {
2191
- 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"}`,
2192
2352
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {},
2193
- children: isStreaming && !message.content ? /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 py-1", children: [
2194
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }),
2195
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }),
2196
- /* @__PURE__ */ jsx12("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })
2197
- ] }) : /* @__PURE__ */ jsxs11("div", { className: `prose ${isCompact ? "prose-xs" : "prose-sm"} max-w-none break-words ${isUser ? "prose-invert" : "dark:prose-invert"}`, children: [
2198
- !isUser && structuredContent.payload && /* @__PURE__ */ jsx12(
2199
- UIDispatcher,
2353
+ children: [
2354
+ !isUser && !isStreaming && message.content && /* @__PURE__ */ jsx12(
2355
+ "button",
2200
2356
  {
2201
- rawContent: structuredContent.payload,
2202
- primaryColor,
2203
- accentColor,
2204
- isStreaming,
2205
- onAddToCart,
2206
- 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" })
2207
2361
  }
2208
2362
  ),
2209
- !shouldRenderStructuredOnly && /* @__PURE__ */ jsx12(ReactMarkdown, { components: markdownComponents, children: processedMarkdown }),
2210
- 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: [
2211
- /* @__PURE__ */ jsx12(
2212
- 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,
2213
2370
  {
2214
- size: 18,
2371
+ rawContent: structuredContent.payload,
2215
2372
  primaryColor,
2216
2373
  accentColor,
2217
- glow: false
2374
+ isStreaming,
2375
+ onAddToCart,
2376
+ viewportSize
2218
2377
  }
2219
2378
  ),
2220
- "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
+ ] })
2221
2392
  ] })
2222
- ] })
2393
+ ]
2223
2394
  }
2224
2395
  ),
2225
2396
  (() => {
2226
- var _a, _b;
2397
+ var _a2, _b;
2227
2398
  if (isUser || structuredContent.payload || !message.uiTransformation) return null;
2228
2399
  const ui = message.uiTransformation;
2229
- const textContent = (_b = (_a = ui.data) == null ? void 0 : _a.content) != null ? _b : "";
2230
- const shouldShow = ui.type === "table" && allProducts.length === 0 || !["text", "table"].includes(ui.type) || ui.type === "text" && !hasRichUI && allProducts.length === 0 && textContent && !processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase());
2400
+ const textContent = (_b = (_a2 = ui.data) == null ? void 0 : _a2.content) != null ? _b : "";
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()));
2231
2403
  if (!shouldShow) return null;
2232
2404
  return /* @__PURE__ */ jsx12("div", { className: "w-full mt-3", children: /* @__PURE__ */ jsx12(
2233
2405
  VisualizationRenderer,
@@ -2240,7 +2412,7 @@ ${match.trim()}
2240
2412
  }
2241
2413
  ) });
2242
2414
  })(),
2243
- !isUser && !hasRichUI && allProducts.length > 0 && /* @__PURE__ */ jsx12("div", { className: "w-full mt-1", children: /* @__PURE__ */ jsx12(
2415
+ !isUser && !hasRichUI && !shouldSuppressProductCarousel && allProducts.length > 0 && /* @__PURE__ */ jsx12("div", { className: "w-full mt-1", children: /* @__PURE__ */ jsx12(
2244
2416
  ProductCarousel,
2245
2417
  {
2246
2418
  products: allProducts,
@@ -2349,6 +2521,7 @@ function useRagChat(projectId, options = {}) {
2349
2521
  const [error, setError] = useState4(null);
2350
2522
  const lastInputRef = useRef3("");
2351
2523
  const messagesRef = useRef3(messages);
2524
+ const abortControllerRef = useRef3(null);
2352
2525
  useEffect3(() => {
2353
2526
  messagesRef.current = messages;
2354
2527
  }, [messages]);
@@ -2369,11 +2542,17 @@ function useRagChat(projectId, options = {}) {
2369
2542
  }
2370
2543
  setError(null);
2371
2544
  setIsLoading(true);
2545
+ if (abortControllerRef.current) {
2546
+ abortControllerRef.current.abort();
2547
+ }
2548
+ const controller = new AbortController();
2549
+ abortControllerRef.current = controller;
2372
2550
  try {
2373
2551
  const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
2374
2552
  const response = await fetch(apiUrl, {
2375
2553
  method: "POST",
2376
2554
  headers: { "Content-Type": "application/json" },
2555
+ signal: controller.signal,
2377
2556
  body: JSON.stringify({
2378
2557
  message: trimmed,
2379
2558
  history,
@@ -2394,6 +2573,23 @@ function useRagChat(projectId, options = {}) {
2394
2573
  let uiTransformation = null;
2395
2574
  let trace;
2396
2575
  let buffer = "";
2576
+ let pendingFlush = false;
2577
+ const pendingContentRef = { current: "" };
2578
+ const flushTextUpdate = (msgId) => {
2579
+ pendingFlush = false;
2580
+ const snapshot = pendingContentRef.current;
2581
+ setMessages(
2582
+ (prev) => prev.map(
2583
+ (msg) => msg.id === msgId ? __spreadProps(__spreadValues({}, msg), { content: snapshot }) : msg
2584
+ )
2585
+ );
2586
+ };
2587
+ const scheduleFlush = (msgId) => {
2588
+ if (!pendingFlush) {
2589
+ pendingFlush = true;
2590
+ requestAnimationFrame(() => flushTextUpdate(msgId));
2591
+ }
2592
+ };
2397
2593
  const assistantMessageId = `assistant_${Date.now()}`;
2398
2594
  setMessages((prev) => [
2399
2595
  ...prev,
@@ -2412,28 +2608,37 @@ function useRagChat(projectId, options = {}) {
2412
2608
  if (lastBoundary === -1) continue;
2413
2609
  const complete = buffer.slice(0, lastBoundary + 2);
2414
2610
  buffer = buffer.slice(lastBoundary + 2);
2611
+ let hasNonTextFrame = false;
2415
2612
  for (const frame of parseSseChunk(complete)) {
2416
2613
  if (frame.type === "text" && frame.text) {
2417
2614
  assistantContent += frame.text;
2615
+ pendingContentRef.current = assistantContent;
2616
+ scheduleFlush(assistantMessageId);
2418
2617
  } else if (frame.type === "metadata") {
2419
2618
  sources = (_a = frame.sources) != null ? _a : [];
2619
+ hasNonTextFrame = true;
2420
2620
  } else if (frame.type === "ui_transformation") {
2421
2621
  uiTransformation = frame.data;
2622
+ hasNonTextFrame = true;
2422
2623
  } else if (frame.type === "observability") {
2423
2624
  trace = frame.data;
2625
+ hasNonTextFrame = true;
2424
2626
  } else if (frame.type === "error") {
2425
2627
  setError(frame.error || "Stream error");
2426
2628
  }
2427
2629
  }
2428
- setMessages(
2429
- (prev) => prev.map(
2430
- (msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), {
2431
- content: assistantContent,
2432
- sources: sources.length > 0 ? sources : msg.sources,
2433
- uiTransformation: uiTransformation || msg.uiTransformation
2434
- }) : msg
2435
- )
2436
- );
2630
+ if (hasNonTextFrame) {
2631
+ pendingFlush = false;
2632
+ setMessages(
2633
+ (prev) => prev.map(
2634
+ (msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), {
2635
+ content: assistantContent,
2636
+ sources: sources.length > 0 ? sources : msg.sources,
2637
+ uiTransformation: uiTransformation || msg.uiTransformation
2638
+ }) : msg
2639
+ )
2640
+ );
2641
+ }
2437
2642
  }
2438
2643
  if (buffer.trim()) {
2439
2644
  for (const frame of parseSseChunk(buffer)) {
@@ -2456,11 +2661,16 @@ function useRagChat(projectId, options = {}) {
2456
2661
  );
2457
2662
  onReply == null ? void 0 : onReply(finalMsg);
2458
2663
  } catch (err) {
2664
+ if (err instanceof Error && err.name === "AbortError") {
2665
+ console.log("[useRagChat] Streaming stopped by user.");
2666
+ return;
2667
+ }
2459
2668
  const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
2460
2669
  setError(msg);
2461
2670
  onError == null ? void 0 : onError(msg);
2462
2671
  } finally {
2463
2672
  setIsLoading(false);
2673
+ abortControllerRef.current = null;
2464
2674
  }
2465
2675
  },
2466
2676
  [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages]
@@ -2478,6 +2688,13 @@ function useRagChat(projectId, options = {}) {
2478
2688
  });
2479
2689
  }
2480
2690
  }, [sendMessage]);
2691
+ const stop = useCallback(() => {
2692
+ if (abortControllerRef.current) {
2693
+ abortControllerRef.current.abort();
2694
+ abortControllerRef.current = null;
2695
+ }
2696
+ setIsLoading(false);
2697
+ }, []);
2481
2698
  return {
2482
2699
  messages,
2483
2700
  isLoading,
@@ -2485,7 +2702,8 @@ function useRagChat(projectId, options = {}) {
2485
2702
  send: sendMessage,
2486
2703
  clear,
2487
2704
  retry,
2488
- setMessages
2705
+ setMessages,
2706
+ stop
2489
2707
  };
2490
2708
  }
2491
2709
 
@@ -2511,7 +2729,7 @@ function ChatWindow({
2511
2729
  const windowRef = useRef4(null);
2512
2730
  const bottomRef = useRef4(null);
2513
2731
  const inputRef = useRef4(null);
2514
- const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
2732
+ const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
2515
2733
  namespace: projectId
2516
2734
  });
2517
2735
  const [suggestions, setSuggestions] = useState5([]);
@@ -2875,14 +3093,22 @@ function ChatWindow({
2875
3093
  children: /* @__PURE__ */ jsx13(Paperclip, { className: "w-4 h-4" })
2876
3094
  }
2877
3095
  ),
2878
- /* @__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(
2879
3105
  "button",
2880
3106
  {
2881
3107
  onClick: sendMessage,
2882
- disabled: !input.trim() || isLoading,
3108
+ disabled: !input.trim(),
2883
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",
2884
3110
  style: {
2885
- 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)"
2886
3112
  // slate-400/20 for light mode disabled
2887
3113
  },
2888
3114
  children: /* @__PURE__ */ jsx13(ArrowUp, { className: "w-4 h-4 text-white" })