@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.js CHANGED
@@ -395,7 +395,20 @@ var import_lucide_react4 = require("lucide-react");
395
395
  var import_image = __toESM(require("next/image"));
396
396
  var import_lucide_react3 = require("lucide-react");
397
397
  var import_jsx_runtime4 = require("react/jsx-runtime");
398
+ function cleanProductDescription(raw) {
399
+ var _a;
400
+ if (raw === null || raw === void 0) return "";
401
+ const source = String(raw);
402
+ const bodyMatch = source.match(
403
+ /\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
404
+ );
405
+ const descriptionMatch = bodyMatch != null ? bodyMatch : source.match(
406
+ /\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
407
+ );
408
+ 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();
409
+ }
398
410
  function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
411
+ const description = cleanProductDescription(product.description);
399
412
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
400
413
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden", children: [
401
414
  product.image ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -415,7 +428,7 @@ function ProductCard({ product, primaryColor = "#6366f1", onAddToCart }) {
415
428
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-sm font-semibold text-slate-800 dark:text-white/90 line-clamp-1", children: product.name }),
416
429
  product.price && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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 })
417
430
  ] }),
418
- product.description && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed", children: product.description }),
431
+ description && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] text-slate-500 dark:text-white/50 line-clamp-2 leading-relaxed", children: description }),
419
432
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-2 flex gap-2", children: [
420
433
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
421
434
  "button",
@@ -720,6 +733,9 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
720
733
  function getColor(index) {
721
734
  return CHART_COLORS[index % CHART_COLORS.length];
722
735
  }
736
+ function stripLeakedAnswerWrappers(raw) {
737
+ return raw.replace(/<prose\s+answer(?:\s+in\s+[^>]*)?>\s*([\s\S]*?)\s*<\/prose>/gi, "$1").replace(/<\/?prose(?:\s+answer)?[^>]*>/gi, "").trim();
738
+ }
723
739
  function CustomTooltip({
724
740
  active,
725
741
  payload,
@@ -769,10 +785,20 @@ function renderVisualization(type, data, onAddToCart, primaryColor, isStreaming)
769
785
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PieChartView, { data, isStreaming });
770
786
  case "bar_chart":
771
787
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BarChartView, { data, primaryColor, isStreaming });
788
+ case "histogram":
789
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BarChartView, { data, primaryColor, isStreaming });
790
+ case "horizontal_bar":
791
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BarChartView, { data, primaryColor, isStreaming, layout: "vertical" });
772
792
  case "line_chart":
773
793
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LineChartView, { data, primaryColor, isStreaming });
794
+ case "scatter_plot":
795
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ScatterPlotView, { data, primaryColor, isStreaming });
774
796
  case "radar_chart":
775
797
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RadarChartView, { data, isStreaming });
798
+ case "metric_card":
799
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MetricCardView, { data, isStreaming });
800
+ case "geo_map":
801
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BarChartView, { data, primaryColor, isStreaming });
776
802
  case "table":
777
803
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableView, { data, isStreaming });
778
804
  case "product_carousel":
@@ -807,28 +833,74 @@ function PieChartView({ data, isStreaming }) {
807
833
  }
808
834
  const chartData = data.map((item) => ({
809
835
  name: item.label,
810
- value: item.value
836
+ value: item.value,
837
+ inStockCount: item.inStockCount,
838
+ outOfStockCount: item.outOfStockCount
811
839
  }));
812
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_recharts2.PieChart, { children: [
813
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
814
- import_recharts2.Pie,
815
- {
816
- data: chartData,
817
- dataKey: "value",
818
- nameKey: "name",
819
- cx: "50%",
820
- cy: "50%",
821
- outerRadius: "68%",
822
- label: false,
823
- labelLine: false,
824
- children: chartData.map((_entry, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Cell, { fill: getColor(index) }, `cell-${index}`))
825
- }
826
- ),
827
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CustomTooltip, {}) }),
828
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })
829
- ] }) }) });
840
+ const hasStockBreakdown = chartData.some(
841
+ (item) => item.inStockCount !== void 0 || item.outOfStockCount !== void 0
842
+ );
843
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: [
844
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_recharts2.PieChart, { children: [
845
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
846
+ import_recharts2.Pie,
847
+ {
848
+ data: chartData,
849
+ dataKey: "value",
850
+ nameKey: "name",
851
+ cx: "50%",
852
+ cy: "50%",
853
+ outerRadius: "68%",
854
+ label: false,
855
+ labelLine: false,
856
+ children: chartData.map((_entry, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Cell, { fill: getColor(index) }, `cell-${index}`))
857
+ }
858
+ ),
859
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CustomTooltip, {}) }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Legend, { wrapperStyle: { fontSize: 12 }, verticalAlign: "bottom" })
861
+ ] }) }),
862
+ hasStockBreakdown && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-3 grid gap-2 sm:grid-cols-2", children: chartData.map((item, index) => {
863
+ var _a, _b;
864
+ const inStock = Number((_a = item.inStockCount) != null ? _a : 0);
865
+ const outOfStock = Number((_b = item.outOfStockCount) != null ? _b : 0);
866
+ const status = inStock > 0 ? `${inStock} in stock` : "Out of stock";
867
+ const mutedStatus = outOfStock > 0 ? `${outOfStock} out` : null;
868
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
869
+ "div",
870
+ {
871
+ 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",
872
+ children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex min-w-0 items-center gap-2 text-slate-700 dark:text-white/75", children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
875
+ "span",
876
+ {
877
+ className: "h-2.5 w-2.5 flex-shrink-0 rounded-full",
878
+ style: { background: getColor(index) }
879
+ }
880
+ ),
881
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: item.name })
882
+ ] }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: inStock > 0 ? "whitespace-nowrap font-medium text-emerald-600" : "whitespace-nowrap font-medium text-rose-600", children: [
884
+ status,
885
+ mutedStatus && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ml-1 text-slate-400", children: [
886
+ "(",
887
+ mutedStatus,
888
+ ")"
889
+ ] })
890
+ ] })
891
+ ]
892
+ },
893
+ item.name
894
+ );
895
+ }) })
896
+ ] });
830
897
  }
831
- function BarChartView({ data, primaryColor, isStreaming }) {
898
+ function BarChartView({
899
+ data,
900
+ primaryColor,
901
+ isStreaming,
902
+ layout = "horizontal"
903
+ }) {
832
904
  if (isStreaming) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChartSkeleton, { type: "bar" });
833
905
  if (!Array.isArray(data) || data.length === 0) {
834
906
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-xs text-red-500", children: "Invalid bar chart data" });
@@ -839,26 +911,66 @@ function BarChartView({ data, primaryColor, isStreaming }) {
839
911
  }, item.inStockCount !== void 0 && { inStockCount: item.inStockCount }), item.outOfStockCount !== void 0 && { outOfStockCount: item.outOfStockCount }));
840
912
  const barColor = primaryColor != null ? primaryColor : getColor(0);
841
913
  const hasStock = chartData.some((d) => "inStockCount" in d);
842
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", height: 260, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_recharts2.BarChart, { data: chartData, margin: { top: 10, right: 10, left: -20, bottom: 0 }, children: [
843
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }),
844
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
845
- import_recharts2.XAxis,
846
- {
847
- dataKey: "category",
848
- tick: { fontSize: 11, fill: "#64748b" },
849
- tickLine: false,
850
- axisLine: { stroke: "#cbd5e1" }
851
- }
852
- ),
853
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.YAxis, { tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false }),
854
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CustomTooltip, {}), cursor: { fill: "#f1f5f9" } }),
855
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Legend, { wrapperStyle: { fontSize: 12 } }),
856
- hasStock ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
857
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "inStockCount", name: "In Stock", fill: getColor(1), radius: [4, 4, 0, 0] }),
858
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "outOfStockCount", name: "Out of Stock", fill: getColor(3), radius: [4, 4, 0, 0] })
859
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "value", name: "Value", fill: barColor, radius: [4, 4, 0, 0] })
914
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", height: 260, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
915
+ import_recharts2.BarChart,
916
+ {
917
+ data: chartData,
918
+ layout,
919
+ margin: { top: 10, right: 10, left: layout === "vertical" ? 20 : -20, bottom: 0 },
920
+ children: [
921
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e2e8f0" }),
922
+ layout === "vertical" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
923
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.XAxis, { type: "number", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.YAxis, { type: "category", dataKey: "category", width: 110, tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false })
925
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
926
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
927
+ import_recharts2.XAxis,
928
+ {
929
+ dataKey: "category",
930
+ tick: { fontSize: 11, fill: "#64748b" },
931
+ tickLine: false,
932
+ axisLine: { stroke: "#cbd5e1" }
933
+ }
934
+ ),
935
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.YAxis, { tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false })
936
+ ] }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CustomTooltip, {}), cursor: { fill: "#f1f5f9" } }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Legend, { wrapperStyle: { fontSize: 12 } }),
939
+ hasStock ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
940
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "inStockCount", name: "In Stock", fill: getColor(1), radius: [4, 4, 0, 0] }),
941
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "outOfStockCount", name: "Out of Stock", fill: getColor(3), radius: [4, 4, 0, 0] })
942
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Bar, { dataKey: "value", name: "Value", fill: barColor, radius: [4, 4, 0, 0] })
943
+ ]
944
+ }
945
+ ) }) });
946
+ }
947
+ function ScatterPlotView({ data, primaryColor, isStreaming }) {
948
+ if (isStreaming) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChartSkeleton, { type: "bar" });
949
+ if (!Array.isArray(data) || data.length === 0) {
950
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-xs text-red-500", children: "Invalid scatter plot data" });
951
+ }
952
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.ResponsiveContainer, { width: "100%", height: 260, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_recharts2.ScatterChart, { margin: { top: 10, right: 10, left: -20, bottom: 0 }, children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", stroke: "#e2e8f0" }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.XAxis, { type: "number", dataKey: "x", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: { stroke: "#cbd5e1" } }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.YAxis, { type: "number", dataKey: "y", tick: { fontSize: 11, fill: "#64748b" }, tickLine: false, axisLine: false }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CustomTooltip, {}), cursor: { strokeDasharray: "3 3" } }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_recharts2.Scatter, { name: "Value", data, fill: primaryColor != null ? primaryColor : getColor(0) })
860
958
  ] }) }) });
861
959
  }
960
+ function MetricCardView({ data, isStreaming }) {
961
+ if (isStreaming) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChartSkeleton, { type: "bar" });
962
+ if (!data || typeof data.value !== "number") {
963
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-xs text-red-500", children: "Invalid metric data" });
964
+ }
965
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-slate-900", children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-xs font-medium uppercase text-slate-500 dark:text-slate-400", children: data.label }),
967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-2 text-3xl font-semibold text-slate-900 dark:text-white", children: [
968
+ data.value.toLocaleString(),
969
+ data.unit && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ml-1 text-base text-slate-500", children: data.unit })
970
+ ] }),
971
+ data.operation && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-1 text-xs text-slate-500 dark:text-slate-400", children: data.operation })
972
+ ] });
973
+ }
862
974
  function LineChartView({ data, primaryColor, isStreaming }) {
863
975
  if (isStreaming) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChartSkeleton, { type: "bar" });
864
976
  if (!Array.isArray(data) || data.length === 0) {
@@ -992,7 +1104,7 @@ function CarouselView({
992
1104
  ) });
993
1105
  }
994
1106
  function TextView({ data }) {
995
- const content = (data == null ? void 0 : data.content) || String(data != null ? data : "");
1107
+ const content = stripLeakedAnswerWrappers((data == null ? void 0 : data.content) || String(data != null ? data : ""));
996
1108
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 p-4 shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm text-slate-700 dark:text-slate-300 whitespace-pre-wrap leading-relaxed", children: content }) });
997
1109
  }
998
1110
 
@@ -1404,7 +1516,7 @@ var HourglassLoader_default = HourglassLoader;
1404
1516
  // src/utils/synonyms.ts
1405
1517
  var FIELD_SYNONYMS = {
1406
1518
  name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
1407
- price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
1519
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd", "variant price"],
1408
1520
  brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
1409
1521
  image: [
1410
1522
  "imageUrl",
@@ -1417,7 +1529,9 @@ var FIELD_SYNONYMS = {
1417
1529
  "image_url",
1418
1530
  "main_image",
1419
1531
  "product_image",
1420
- "thumb"
1532
+ "thumb",
1533
+ "image src",
1534
+ "variant image"
1421
1535
  ],
1422
1536
  stock: [
1423
1537
  "inventory",
@@ -1428,9 +1542,19 @@ var FIELD_SYNONYMS = {
1428
1542
  "inStock",
1429
1543
  "is_available",
1430
1544
  "in stock",
1431
- "status"
1545
+ "status",
1546
+ "variant inventory qty"
1432
1547
  ],
1433
- description: ["summary", "content", "body", "text", "info", "details"],
1548
+ category: [
1549
+ "product_category",
1550
+ "product category",
1551
+ "category_name",
1552
+ "category name",
1553
+ "department",
1554
+ "collection",
1555
+ "type"
1556
+ ],
1557
+ description: ["summary", "content", "body", "text", "info", "details", "body (html)", "seo description"],
1434
1558
  link: ["url", "href", "product_url", "page_url", "link"]
1435
1559
  };
1436
1560
  function addSynonyms(customSynonyms) {
@@ -1637,11 +1761,12 @@ function extractProductsFromSources(sources, isUser) {
1637
1761
  var _a;
1638
1762
  const m = (_a = s.metadata) != null ? _a : {};
1639
1763
  const keys = Object.keys(m).map((k) => k.toLowerCase());
1640
- const hasProductKey = keys.some(
1641
- (k) => ["price", "image", "img", "thumbnail", "images", "brand", "product", "sku", "category", "model", "cost"].includes(k)
1764
+ const hasStrongProductKey = keys.some(
1765
+ (k) => ["price", "image", "img", "thumbnail", "images", "product", "sku", "cost"].includes(k)
1642
1766
  );
1767
+ const hasProductIdentity = keys.some((k) => ["brand", "model"].includes(k)) && keys.some((k) => ["name", "title", "product_name", "product"].includes(k));
1643
1768
  const hasPricePattern = /\$\s*\d+/.test(s.content);
1644
- return hasProductKey || hasPricePattern || m.type === "product";
1769
+ return hasStrongProductKey || hasProductIdentity || hasPricePattern || m.type === "product";
1645
1770
  }).map((s) => {
1646
1771
  var _a, _b;
1647
1772
  const m = (_a = s.metadata) != null ? _a : {};
@@ -1828,6 +1953,7 @@ function resolveStructuredRows(config) {
1828
1953
  }
1829
1954
  function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
1830
1955
  var _a;
1956
+ console.log("rawContent", rawContent);
1831
1957
  const result = import_react7.default.useMemo(() => {
1832
1958
  try {
1833
1959
  const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
@@ -1962,12 +2088,11 @@ function createMarkdownComponents({
1962
2088
  viewportSize
1963
2089
  }) {
1964
2090
  return {
1965
- p: (_a) => {
2091
+ // p: ({ ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
2092
+ // <p className="whitespace-pre-line" {...props} />
2093
+ // ),
2094
+ table: (_a) => {
1966
2095
  var props = __objRest(_a, []);
1967
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", __spreadValues({ className: "whitespace-pre-line" }, props));
1968
- },
1969
- table: (_b) => {
1970
- var props = __objRest(_b, []);
1971
2096
  if (isStreaming) {
1972
2097
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
1973
2098
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }),
@@ -1981,8 +2106,8 @@ function createMarkdownComponents({
1981
2106
  }, props)
1982
2107
  ) }) });
1983
2108
  },
1984
- thead: (_c) => {
1985
- var props = __objRest(_c, []);
2109
+ thead: (_b) => {
2110
+ var props = __objRest(_b, []);
1986
2111
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1987
2112
  "thead",
1988
2113
  __spreadValues({
@@ -1990,12 +2115,12 @@ function createMarkdownComponents({
1990
2115
  }, props)
1991
2116
  );
1992
2117
  },
1993
- tbody: (_d) => {
1994
- var props = __objRest(_d, []);
2118
+ tbody: (_c) => {
2119
+ var props = __objRest(_c, []);
1995
2120
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", __spreadValues({ className: "!table-row-group divide-y divide-slate-100 dark:divide-white/5" }, props));
1996
2121
  },
1997
- tr: (_e) => {
1998
- var props = __objRest(_e, []);
2122
+ tr: (_d) => {
2123
+ var props = __objRest(_d, []);
1999
2124
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2000
2125
  "tr",
2001
2126
  __spreadValues({
@@ -2003,8 +2128,8 @@ function createMarkdownComponents({
2003
2128
  }, props)
2004
2129
  );
2005
2130
  },
2006
- th: (_f) => {
2007
- var _g = _f, { children } = _g, props = __objRest(_g, ["children"]);
2131
+ th: (_e) => {
2132
+ var _f = _e, { children } = _f, props = __objRest(_f, ["children"]);
2008
2133
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2009
2134
  "th",
2010
2135
  __spreadProps(__spreadValues({
@@ -2014,8 +2139,8 @@ function createMarkdownComponents({
2014
2139
  })
2015
2140
  );
2016
2141
  },
2017
- td: (_h) => {
2018
- var _i = _h, { children } = _i, props = __objRest(_i, ["children"]);
2142
+ td: (_g) => {
2143
+ var _h = _g, { children } = _h, props = __objRest(_h, ["children"]);
2019
2144
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2020
2145
  "td",
2021
2146
  __spreadProps(__spreadValues({
@@ -2025,12 +2150,12 @@ function createMarkdownComponents({
2025
2150
  })
2026
2151
  );
2027
2152
  },
2028
- code(_j) {
2029
- var _k = _j, {
2153
+ code(_i) {
2154
+ var _j = _i, {
2030
2155
  inline,
2031
2156
  className,
2032
2157
  children
2033
- } = _k, props = __objRest(_k, [
2158
+ } = _j, props = __objRest(_j, [
2034
2159
  "inline",
2035
2160
  "className",
2036
2161
  "children"
@@ -2080,6 +2205,16 @@ function normaliseChild(children) {
2080
2205
 
2081
2206
  // src/components/MessageBubble.tsx
2082
2207
  var import_jsx_runtime12 = require("react/jsx-runtime");
2208
+ function normalizePlusSeparatedLists(raw) {
2209
+ return raw.split("\n").map((line) => {
2210
+ const productListLine = /[—:]\s*\d+\s+\+\s+[A-Za-z]/.test(line) || /\b(products?|categories?|in stock|out of stock)\b/i.test(line);
2211
+ if (!productListLine) return line;
2212
+ return line.replace(/\s+\+\s+(?=[A-Za-z0-9])/g, ", ");
2213
+ }).join("\n");
2214
+ }
2215
+ function stripLeakedAnswerWrappers2(raw) {
2216
+ 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();
2217
+ }
2083
2218
  function MessageBubble({
2084
2219
  message,
2085
2220
  sources,
@@ -2089,11 +2224,22 @@ function MessageBubble({
2089
2224
  onAddToCart,
2090
2225
  viewportSize = "large"
2091
2226
  }) {
2227
+ var _a;
2092
2228
  const isUser = message.role === "user";
2093
2229
  const isCompact = viewportSize === "compact";
2094
2230
  const isMedium = viewportSize === "medium";
2095
2231
  const [showSources, setShowSources] = import_react9.default.useState(false);
2096
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]);
2097
2243
  const structuredContent = import_react9.default.useMemo(
2098
2244
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
2099
2245
  [isUser, message.content]
@@ -2108,6 +2254,10 @@ function MessageBubble({
2108
2254
  }
2109
2255
  return false;
2110
2256
  }, [message.uiTransformation, structuredContent.payload]);
2257
+ const uiTransformationType = (_a = message.uiTransformation) == null ? void 0 : _a.type;
2258
+ const shouldSuppressProductCarousel = Boolean(
2259
+ uiTransformationType && !["text", "product_carousel", "carousel"].includes(uiTransformationType)
2260
+ );
2111
2261
  const shouldRenderStructuredOnly = !isUser && hasRichUI && isLikelyUiOnlyMessage(structuredContent.text);
2112
2262
  const productsFromSources = import_react9.default.useMemo(() => {
2113
2263
  return extractProductsFromSources(sources, isUser);
@@ -2133,6 +2283,14 @@ function MessageBubble({
2133
2283
  }, [productsFromSources, productsFromContent, message.content]);
2134
2284
  const processedMarkdown = import_react9.default.useMemo(() => {
2135
2285
  let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
2286
+ raw = raw.replace(/\$\s*(\d+)(?!\.\d)/g, "$1");
2287
+ raw = normalizePlusSeparatedLists(raw);
2288
+ raw = stripLeakedAnswerWrappers2(raw);
2289
+ raw = raw.replace(/\*\*([^*\n]+)\*\*(\s*[—\-–:]\s*)/g, "$1$2");
2290
+ raw = raw.split("\n").filter((line, idx, arr) => {
2291
+ const trimmed = line.trim();
2292
+ return !trimmed || arr.findIndex((l) => l.trim() === trimmed) === idx;
2293
+ }).join("\n");
2136
2294
  const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
2137
2295
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
2138
2296
  if (!isStreaming) {
@@ -2168,49 +2326,61 @@ ${match.trim()}
2168
2326
  }
2169
2327
  ),
2170
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: [
2171
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2329
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2172
2330
  "div",
2173
2331
  {
2174
- 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"}`,
2175
2333
  style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {},
2176
- children: isStreaming && !message.content ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1 py-1", children: [
2177
- /* @__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]" }),
2178
- /* @__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]" }),
2179
- /* @__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]" })
2180
- ] }) : /* @__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: [
2181
- !isUser && structuredContent.payload && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2182
- UIDispatcher,
2334
+ children: [
2335
+ !isUser && !isStreaming && message.content && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2336
+ "button",
2183
2337
  {
2184
- rawContent: structuredContent.payload,
2185
- primaryColor,
2186
- accentColor,
2187
- isStreaming,
2188
- onAddToCart,
2189
- 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" })
2190
2342
  }
2191
2343
  ),
2192
- !shouldRenderStructuredOnly && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_markdown.default, { components: markdownComponents, children: processedMarkdown }),
2193
- 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: [
2194
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2195
- 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,
2196
2351
  {
2197
- size: 18,
2352
+ rawContent: structuredContent.payload,
2198
2353
  primaryColor,
2199
2354
  accentColor,
2200
- glow: false
2355
+ isStreaming,
2356
+ onAddToCart,
2357
+ viewportSize
2201
2358
  }
2202
2359
  ),
2203
- "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
+ ] })
2204
2373
  ] })
2205
- ] })
2374
+ ]
2206
2375
  }
2207
2376
  ),
2208
2377
  (() => {
2209
- var _a, _b;
2378
+ var _a2, _b;
2210
2379
  if (isUser || structuredContent.payload || !message.uiTransformation) return null;
2211
2380
  const ui = message.uiTransformation;
2212
- const textContent = (_b = (_a = ui.data) == null ? void 0 : _a.content) != null ? _b : "";
2213
- 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());
2381
+ const textContent = (_b = (_a2 = ui.data) == null ? void 0 : _a2.content) != null ? _b : "";
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()));
2214
2384
  if (!shouldShow) return null;
2215
2385
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2216
2386
  VisualizationRenderer,
@@ -2223,7 +2393,7 @@ ${match.trim()}
2223
2393
  }
2224
2394
  ) });
2225
2395
  })(),
2226
- !isUser && !hasRichUI && allProducts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2396
+ !isUser && !hasRichUI && !shouldSuppressProductCarousel && allProducts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2227
2397
  ProductCarousel,
2228
2398
  {
2229
2399
  products: allProducts,
@@ -2332,6 +2502,7 @@ function useRagChat(projectId, options = {}) {
2332
2502
  const [error, setError] = (0, import_react10.useState)(null);
2333
2503
  const lastInputRef = (0, import_react10.useRef)("");
2334
2504
  const messagesRef = (0, import_react10.useRef)(messages);
2505
+ const abortControllerRef = (0, import_react10.useRef)(null);
2335
2506
  (0, import_react10.useEffect)(() => {
2336
2507
  messagesRef.current = messages;
2337
2508
  }, [messages]);
@@ -2352,11 +2523,17 @@ function useRagChat(projectId, options = {}) {
2352
2523
  }
2353
2524
  setError(null);
2354
2525
  setIsLoading(true);
2526
+ if (abortControllerRef.current) {
2527
+ abortControllerRef.current.abort();
2528
+ }
2529
+ const controller = new AbortController();
2530
+ abortControllerRef.current = controller;
2355
2531
  try {
2356
2532
  const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
2357
2533
  const response = await fetch(apiUrl, {
2358
2534
  method: "POST",
2359
2535
  headers: { "Content-Type": "application/json" },
2536
+ signal: controller.signal,
2360
2537
  body: JSON.stringify({
2361
2538
  message: trimmed,
2362
2539
  history,
@@ -2377,6 +2554,23 @@ function useRagChat(projectId, options = {}) {
2377
2554
  let uiTransformation = null;
2378
2555
  let trace;
2379
2556
  let buffer = "";
2557
+ let pendingFlush = false;
2558
+ const pendingContentRef = { current: "" };
2559
+ const flushTextUpdate = (msgId) => {
2560
+ pendingFlush = false;
2561
+ const snapshot = pendingContentRef.current;
2562
+ setMessages(
2563
+ (prev) => prev.map(
2564
+ (msg) => msg.id === msgId ? __spreadProps(__spreadValues({}, msg), { content: snapshot }) : msg
2565
+ )
2566
+ );
2567
+ };
2568
+ const scheduleFlush = (msgId) => {
2569
+ if (!pendingFlush) {
2570
+ pendingFlush = true;
2571
+ requestAnimationFrame(() => flushTextUpdate(msgId));
2572
+ }
2573
+ };
2380
2574
  const assistantMessageId = `assistant_${Date.now()}`;
2381
2575
  setMessages((prev) => [
2382
2576
  ...prev,
@@ -2395,28 +2589,37 @@ function useRagChat(projectId, options = {}) {
2395
2589
  if (lastBoundary === -1) continue;
2396
2590
  const complete = buffer.slice(0, lastBoundary + 2);
2397
2591
  buffer = buffer.slice(lastBoundary + 2);
2592
+ let hasNonTextFrame = false;
2398
2593
  for (const frame of parseSseChunk(complete)) {
2399
2594
  if (frame.type === "text" && frame.text) {
2400
2595
  assistantContent += frame.text;
2596
+ pendingContentRef.current = assistantContent;
2597
+ scheduleFlush(assistantMessageId);
2401
2598
  } else if (frame.type === "metadata") {
2402
2599
  sources = (_a = frame.sources) != null ? _a : [];
2600
+ hasNonTextFrame = true;
2403
2601
  } else if (frame.type === "ui_transformation") {
2404
2602
  uiTransformation = frame.data;
2603
+ hasNonTextFrame = true;
2405
2604
  } else if (frame.type === "observability") {
2406
2605
  trace = frame.data;
2606
+ hasNonTextFrame = true;
2407
2607
  } else if (frame.type === "error") {
2408
2608
  setError(frame.error || "Stream error");
2409
2609
  }
2410
2610
  }
2411
- setMessages(
2412
- (prev) => prev.map(
2413
- (msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), {
2414
- content: assistantContent,
2415
- sources: sources.length > 0 ? sources : msg.sources,
2416
- uiTransformation: uiTransformation || msg.uiTransformation
2417
- }) : msg
2418
- )
2419
- );
2611
+ if (hasNonTextFrame) {
2612
+ pendingFlush = false;
2613
+ setMessages(
2614
+ (prev) => prev.map(
2615
+ (msg) => msg.id === assistantMessageId ? __spreadProps(__spreadValues({}, msg), {
2616
+ content: assistantContent,
2617
+ sources: sources.length > 0 ? sources : msg.sources,
2618
+ uiTransformation: uiTransformation || msg.uiTransformation
2619
+ }) : msg
2620
+ )
2621
+ );
2622
+ }
2420
2623
  }
2421
2624
  if (buffer.trim()) {
2422
2625
  for (const frame of parseSseChunk(buffer)) {
@@ -2439,11 +2642,16 @@ function useRagChat(projectId, options = {}) {
2439
2642
  );
2440
2643
  onReply == null ? void 0 : onReply(finalMsg);
2441
2644
  } catch (err) {
2645
+ if (err instanceof Error && err.name === "AbortError") {
2646
+ console.log("[useRagChat] Streaming stopped by user.");
2647
+ return;
2648
+ }
2442
2649
  const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
2443
2650
  setError(msg);
2444
2651
  onError == null ? void 0 : onError(msg);
2445
2652
  } finally {
2446
2653
  setIsLoading(false);
2654
+ abortControllerRef.current = null;
2447
2655
  }
2448
2656
  },
2449
2657
  [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages]
@@ -2461,6 +2669,13 @@ function useRagChat(projectId, options = {}) {
2461
2669
  });
2462
2670
  }
2463
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
+ }, []);
2464
2679
  return {
2465
2680
  messages,
2466
2681
  isLoading,
@@ -2468,7 +2683,8 @@ function useRagChat(projectId, options = {}) {
2468
2683
  send: sendMessage,
2469
2684
  clear,
2470
2685
  retry,
2471
- setMessages
2686
+ setMessages,
2687
+ stop
2472
2688
  };
2473
2689
  }
2474
2690
 
@@ -2494,7 +2710,7 @@ function ChatWindow({
2494
2710
  const windowRef = (0, import_react11.useRef)(null);
2495
2711
  const bottomRef = (0, import_react11.useRef)(null);
2496
2712
  const inputRef = (0, import_react11.useRef)(null);
2497
- const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
2713
+ const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
2498
2714
  namespace: projectId
2499
2715
  });
2500
2716
  const [suggestions, setSuggestions] = (0, import_react11.useState)([]);
@@ -2858,14 +3074,22 @@ function ChatWindow({
2858
3074
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Paperclip, { className: "w-4 h-4" })
2859
3075
  }
2860
3076
  ),
2861
- /* @__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)(
2862
3086
  "button",
2863
3087
  {
2864
3088
  onClick: sendMessage,
2865
- disabled: !input.trim() || isLoading,
3089
+ disabled: !input.trim(),
2866
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",
2867
3091
  style: {
2868
- 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)"
2869
3093
  // slate-400/20 for light mode disabled
2870
3094
  },
2871
3095
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ArrowUp, { className: "w-4 h-4 text-white" })