@retrivora-ai/rag-engine 1.6.9 → 1.7.0

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.
@@ -2282,9 +2282,9 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
2282
2282
  }
2283
2283
 
2284
2284
  3. DATA RULES
2285
- - Build the UI payload from retrieved context only.
2285
+ - Build the UI payload from retrieved context and metadata.
2286
2286
  - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
2287
- - For CHART: "data" items MUST have { label, value }.
2287
+ - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
2288
2288
 
2289
2289
  4. FORMAT RULES
2290
2290
  - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
@@ -2299,7 +2299,7 @@ Assistant: "I found several beauty products in our catalog:"
2299
2299
  "title": "Beauty Products",
2300
2300
  "description": "Latest skincare and makeup items.",
2301
2301
  "data": [
2302
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
2302
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
2303
2303
  ]
2304
2304
  }
2305
2305
  \`\`\`
@@ -2511,7 +2511,8 @@ Assistant: "I found several beauty products in our catalog:"
2511
2511
  sources = sources.slice(0, topK);
2512
2512
  }
2513
2513
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2514
- ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
2514
+ Content: ${m.content}
2515
+ Metadata: ${JSON.stringify(m.metadata)}`).join("\n\n---\n\n") : "No relevant context found.";
2515
2516
  if (graphData && graphData.nodes.length > 0) {
2516
2517
  const graphContext = graphData.nodes.map(
2517
2518
  (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
@@ -3845,9 +3845,9 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
3845
3845
  }
3846
3846
 
3847
3847
  3. DATA RULES
3848
- - Build the UI payload from retrieved context only.
3848
+ - Build the UI payload from retrieved context and metadata.
3849
3849
  - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
3850
- - For CHART: "data" items MUST have { label, value }.
3850
+ - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
3851
3851
 
3852
3852
  4. FORMAT RULES
3853
3853
  - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
@@ -3862,7 +3862,7 @@ Assistant: "I found several beauty products in our catalog:"
3862
3862
  "title": "Beauty Products",
3863
3863
  "description": "Latest skincare and makeup items.",
3864
3864
  "data": [
3865
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
3865
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
3866
3866
  ]
3867
3867
  }
3868
3868
  \`\`\`
@@ -4074,7 +4074,8 @@ Assistant: "I found several beauty products in our catalog:"
4074
4074
  sources = sources.slice(0, topK);
4075
4075
  }
4076
4076
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
4077
- ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
4077
+ Content: ${m.content}
4078
+ Metadata: ${JSON.stringify(m.metadata)}`).join("\n\n---\n\n") : "No relevant context found.";
4078
4079
  if (graphData && graphData.nodes.length > 0) {
4079
4080
  const graphContext = graphData.nodes.map(
4080
4081
  (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
@@ -9,7 +9,7 @@ import {
9
9
  sseFrame,
10
10
  sseMetaFrame,
11
11
  sseTextFrame
12
- } from "../chunk-Q7FLITXB.mjs";
12
+ } from "../chunk-OHXRQTQH.mjs";
13
13
  import "../chunk-YLTMFW4M.mjs";
14
14
  import "../chunk-X4TOT24V.mjs";
15
15
  export {
package/dist/index.js CHANGED
@@ -535,12 +535,23 @@ function isLikelyUiOnlyMessage(text) {
535
535
  return trimmed.length === 0 || /^(chart data|table data|visualization|visual representation|product catalog|product recommendations|catalog summary)\s*:?\s*$/i.test(trimmed);
536
536
  }
537
537
  function resolveImage(data) {
538
- for (const key of ["image", "img", "thumbnail"]) {
538
+ const candidates = ["image", "img", "thumbnail", "url", "picture", "link"];
539
+ for (const key of candidates) {
539
540
  const v = data[key];
540
- if (typeof v === "string" && v) return v;
541
+ if (typeof v === "string" && v.startsWith("http")) return v;
542
+ if (v && typeof v === "object" && !Array.isArray(v)) {
543
+ const subVal = v.url || v.link || v.src;
544
+ if (typeof subVal === "string" && subVal.startsWith("http")) return subVal;
545
+ }
541
546
  }
542
- if (Array.isArray(data.images) && typeof data.images[0] === "string") {
543
- return data.images[0];
547
+ if (Array.isArray(data.images)) {
548
+ for (const item of data.images) {
549
+ if (typeof item === "string" && item.startsWith("http")) return item;
550
+ if (item && typeof item === "object") {
551
+ const url = item.url || item.link || item.src;
552
+ if (typeof url === "string" && url.startsWith("http")) return url;
553
+ }
554
+ }
544
555
  }
545
556
  return void 0;
546
557
  }
@@ -643,10 +654,13 @@ function resolveStructuredRows(config) {
643
654
  if (Array.isArray(config.results) && config.results.length > 0) return config.results;
644
655
  return [];
645
656
  }
646
- function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
657
+ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, viewportSize = "large" }) {
647
658
  var _a;
648
659
  const result = import_react5.default.useMemo(() => {
649
- if (isStreaming) return { loading: true };
660
+ if (isStreaming) {
661
+ const looksLikeCarousel = rawContent.includes('"view": "carousel"') || rawContent.includes('"view":"carousel"') || rawContent.includes('"type": "products"') || rawContent.includes('"type":"products"');
662
+ return { loading: true, silent: looksLikeCarousel };
663
+ }
650
664
  try {
651
665
  const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
652
666
  const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
@@ -679,6 +693,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
679
693
  }
680
694
  }, [rawContent, isStreaming]);
681
695
  if ("loading" in result) {
696
+ if (result.silent) return null;
682
697
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 animate-pulse" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-xs text-slate-500 font-medium italic" }, "Preparing view..."));
683
698
  }
684
699
  if ("error" in result) {
@@ -713,12 +728,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
713
728
  insight
714
729
  ))));
715
730
  case "carousel":
716
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: `${isCompact ? "mb-1.5 text-[11px]" : "mb-2 text-xs"} font-semibold text-slate-500` }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ import_react5.default.createElement(ProductCarousel, { products: config.data.map((item) => {
717
- var _a2;
718
- return __spreadProps(__spreadValues({}, item), {
719
- image: (_a2 = item.image) != null ? _a2 : typeof resolveImage === "function" ? resolveImage(item) : void 0
720
- });
721
- }), primaryColor, onAddToCart }));
731
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: `${isCompact ? "mb-1.5 text-[11px]" : "mb-2 text-xs"} font-semibold text-slate-500` }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description));
722
732
  case "table":
723
733
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: `${isCompact ? "my-3 p-3 max-h-[320px]" : "my-4 p-4 max-h-[400px]"} bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto` }, config.title && /* @__PURE__ */ import_react5.default.createElement("h4", { className: `${isCompact ? "text-[11px]" : "text-xs"} font-semibold text-slate-500 mb-2` }, config.title), hasDescription && /* @__PURE__ */ import_react5.default.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ import_react5.default.createElement(DataTable, { config: {
724
734
  type: "table",
@@ -746,10 +756,6 @@ function MessageBubble({
746
756
  const isCompact = viewportSize === "compact";
747
757
  const isMedium = viewportSize === "medium";
748
758
  const [showSources, setShowSources] = import_react5.default.useState(false);
749
- const hasStructuredProductBlock = import_react5.default.useMemo(
750
- () => /```(?:ui|json|chart)\s*[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products|table)"?/i.test(message.content) || /\{[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products)"?[\s\S]*\}/i.test(message.content) && looksLikeStructuredPayload(message.content),
751
- [message.content]
752
- );
753
759
  const structuredContent = import_react5.default.useMemo(
754
760
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
755
761
  [isUser, message.content]
@@ -950,7 +956,6 @@ ${match.trim()}
950
956
  primaryColor,
951
957
  accentColor,
952
958
  isStreaming,
953
- onAddToCart,
954
959
  viewportSize
955
960
  }
956
961
  );
@@ -965,7 +970,6 @@ ${match.trim()}
965
970
  primaryColor,
966
971
  accentColor,
967
972
  isStreaming,
968
- onAddToCart,
969
973
  viewportSize
970
974
  }
971
975
  );
@@ -981,7 +985,6 @@ ${match.trim()}
981
985
  primaryColor,
982
986
  accentColor,
983
987
  isStreaming,
984
- onAddToCart,
985
988
  viewportSize
986
989
  }
987
990
  );
@@ -993,7 +996,7 @@ ${match.trim()}
993
996
  return /* @__PURE__ */ import_react5.default.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
994
997
  }
995
998
  }),
996
- [primaryColor, accentColor, isStreaming, onAddToCart, viewportSize]
999
+ [primaryColor, accentColor, isStreaming, viewportSize]
997
1000
  );
998
1001
  return /* @__PURE__ */ import_react5.default.createElement("div", { className: `flex ${isCompact ? "gap-2" : "gap-3"} ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ import_react5.default.createElement(
999
1002
  "div",
@@ -1015,11 +1018,10 @@ ${match.trim()}
1015
1018
  primaryColor,
1016
1019
  accentColor,
1017
1020
  isStreaming,
1018
- onAddToCart,
1019
1021
  viewportSize
1020
1022
  }
1021
1023
  ), !shouldRenderStructuredOnly && /* @__PURE__ */ import_react5.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ import_react5.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
1022
- ), !isUser && !hasStructuredProductBlock && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
1024
+ ), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
1023
1025
  ProductCarousel,
1024
1026
  {
1025
1027
  products: allProducts,
package/dist/index.mjs CHANGED
@@ -498,12 +498,23 @@ function isLikelyUiOnlyMessage(text) {
498
498
  return trimmed.length === 0 || /^(chart data|table data|visualization|visual representation|product catalog|product recommendations|catalog summary)\s*:?\s*$/i.test(trimmed);
499
499
  }
500
500
  function resolveImage(data) {
501
- for (const key of ["image", "img", "thumbnail"]) {
501
+ const candidates = ["image", "img", "thumbnail", "url", "picture", "link"];
502
+ for (const key of candidates) {
502
503
  const v = data[key];
503
- if (typeof v === "string" && v) return v;
504
+ if (typeof v === "string" && v.startsWith("http")) return v;
505
+ if (v && typeof v === "object" && !Array.isArray(v)) {
506
+ const subVal = v.url || v.link || v.src;
507
+ if (typeof subVal === "string" && subVal.startsWith("http")) return subVal;
508
+ }
504
509
  }
505
- if (Array.isArray(data.images) && typeof data.images[0] === "string") {
506
- return data.images[0];
510
+ if (Array.isArray(data.images)) {
511
+ for (const item of data.images) {
512
+ if (typeof item === "string" && item.startsWith("http")) return item;
513
+ if (item && typeof item === "object") {
514
+ const url = item.url || item.link || item.src;
515
+ if (typeof url === "string" && url.startsWith("http")) return url;
516
+ }
517
+ }
507
518
  }
508
519
  return void 0;
509
520
  }
@@ -606,10 +617,13 @@ function resolveStructuredRows(config) {
606
617
  if (Array.isArray(config.results) && config.results.length > 0) return config.results;
607
618
  return [];
608
619
  }
609
- function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = "large" }) {
620
+ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, viewportSize = "large" }) {
610
621
  var _a;
611
622
  const result = React5.useMemo(() => {
612
- if (isStreaming) return { loading: true };
623
+ if (isStreaming) {
624
+ const looksLikeCarousel = rawContent.includes('"view": "carousel"') || rawContent.includes('"view":"carousel"') || rawContent.includes('"type": "products"') || rawContent.includes('"type":"products"');
625
+ return { loading: true, silent: looksLikeCarousel };
626
+ }
613
627
  try {
614
628
  const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
615
629
  const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
@@ -642,6 +656,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
642
656
  }
643
657
  }, [rawContent, isStreaming]);
644
658
  if ("loading" in result) {
659
+ if (result.silent) return null;
645
660
  return /* @__PURE__ */ React5.createElement("div", { className: "my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 animate-pulse" }, /* @__PURE__ */ React5.createElement("div", { className: "w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-slate-500 font-medium italic" }, "Preparing view..."));
646
661
  }
647
662
  if ("error" in result) {
@@ -676,12 +691,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
676
691
  insight
677
692
  ))));
678
693
  case "carousel":
679
- return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: `${isCompact ? "mb-1.5 text-[11px]" : "mb-2 text-xs"} font-semibold text-slate-500` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ React5.createElement(ProductCarousel, { products: config.data.map((item) => {
680
- var _a2;
681
- return __spreadProps(__spreadValues({}, item), {
682
- image: (_a2 = item.image) != null ? _a2 : typeof resolveImage === "function" ? resolveImage(item) : void 0
683
- });
684
- }), primaryColor, onAddToCart }));
694
+ return /* @__PURE__ */ React5.createElement("div", { className: "my-4" }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: `${isCompact ? "mb-1.5 text-[11px]" : "mb-2 text-xs"} font-semibold text-slate-500` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description));
685
695
  case "table":
686
696
  return /* @__PURE__ */ React5.createElement("div", { className: `${isCompact ? "my-3 p-3 max-h-[320px]" : "my-4 p-4 max-h-[400px]"} bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm overflow-auto` }, config.title && /* @__PURE__ */ React5.createElement("h4", { className: `${isCompact ? "text-[11px]" : "text-xs"} font-semibold text-slate-500 mb-2` }, config.title), hasDescription && /* @__PURE__ */ React5.createElement("p", { className: `mb-3 ${isCompact ? "text-xs" : "text-sm"} text-slate-500 dark:text-white/60` }, config.description), /* @__PURE__ */ React5.createElement(DataTable, { config: {
687
697
  type: "table",
@@ -709,10 +719,6 @@ function MessageBubble({
709
719
  const isCompact = viewportSize === "compact";
710
720
  const isMedium = viewportSize === "medium";
711
721
  const [showSources, setShowSources] = React5.useState(false);
712
- const hasStructuredProductBlock = React5.useMemo(
713
- () => /```(?:ui|json|chart)\s*[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products|table)"?/i.test(message.content) || /\{[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products)"?[\s\S]*\}/i.test(message.content) && looksLikeStructuredPayload(message.content),
714
- [message.content]
715
- );
716
722
  const structuredContent = React5.useMemo(
717
723
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
718
724
  [isUser, message.content]
@@ -913,7 +919,6 @@ ${match.trim()}
913
919
  primaryColor,
914
920
  accentColor,
915
921
  isStreaming,
916
- onAddToCart,
917
922
  viewportSize
918
923
  }
919
924
  );
@@ -928,7 +933,6 @@ ${match.trim()}
928
933
  primaryColor,
929
934
  accentColor,
930
935
  isStreaming,
931
- onAddToCart,
932
936
  viewportSize
933
937
  }
934
938
  );
@@ -944,7 +948,6 @@ ${match.trim()}
944
948
  primaryColor,
945
949
  accentColor,
946
950
  isStreaming,
947
- onAddToCart,
948
951
  viewportSize
949
952
  }
950
953
  );
@@ -956,7 +959,7 @@ ${match.trim()}
956
959
  return /* @__PURE__ */ React5.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
957
960
  }
958
961
  }),
959
- [primaryColor, accentColor, isStreaming, onAddToCart, viewportSize]
962
+ [primaryColor, accentColor, isStreaming, viewportSize]
960
963
  );
961
964
  return /* @__PURE__ */ React5.createElement("div", { className: `flex ${isCompact ? "gap-2" : "gap-3"} ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
962
965
  "div",
@@ -978,11 +981,10 @@ ${match.trim()}
978
981
  primaryColor,
979
982
  accentColor,
980
983
  isStreaming,
981
- onAddToCart,
982
984
  viewportSize
983
985
  }
984
986
  ), !shouldRenderStructuredOnly && /* @__PURE__ */ React5.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents }, processedMarkdown), isStreaming && message.content && /* @__PURE__ */ React5.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
985
- ), !isUser && !hasStructuredProductBlock && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
987
+ ), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
986
988
  ProductCarousel,
987
989
  {
988
990
  products: allProducts,
package/dist/server.js CHANGED
@@ -3936,9 +3936,9 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
3936
3936
  }
3937
3937
 
3938
3938
  3. DATA RULES
3939
- - Build the UI payload from retrieved context only.
3939
+ - Build the UI payload from retrieved context and metadata.
3940
3940
  - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
3941
- - For CHART: "data" items MUST have { label, value }.
3941
+ - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
3942
3942
 
3943
3943
  4. FORMAT RULES
3944
3944
  - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
@@ -3953,7 +3953,7 @@ Assistant: "I found several beauty products in our catalog:"
3953
3953
  "title": "Beauty Products",
3954
3954
  "description": "Latest skincare and makeup items.",
3955
3955
  "data": [
3956
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
3956
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
3957
3957
  ]
3958
3958
  }
3959
3959
  \`\`\`
@@ -4165,7 +4165,8 @@ Assistant: "I found several beauty products in our catalog:"
4165
4165
  sources = sources.slice(0, topK);
4166
4166
  }
4167
4167
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
4168
- ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
4168
+ Content: ${m.content}
4169
+ Metadata: ${JSON.stringify(m.metadata)}`).join("\n\n---\n\n") : "No relevant context found.";
4169
4170
  if (graphData && graphData.nodes.length > 0) {
4170
4171
  const graphContext = graphData.nodes.map(
4171
4172
  (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-Q7FLITXB.mjs";
42
+ } from "./chunk-OHXRQTQH.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.6.9",
3
+ "version": "1.7.0",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -204,12 +204,23 @@ function isLikelyUiOnlyMessage(text: string): boolean {
204
204
  // ─── Tiny helpers ─────────────────────────────────────────────────────────────
205
205
 
206
206
  function resolveImage(data: Record<string, unknown>): string | undefined {
207
- for (const key of ['image', 'img', 'thumbnail']) {
207
+ const candidates = ['image', 'img', 'thumbnail', 'url', 'picture', 'link'];
208
+ for (const key of candidates) {
208
209
  const v = data[key];
209
- if (typeof v === 'string' && v) return v;
210
+ if (typeof v === 'string' && v.startsWith('http')) return v;
211
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
212
+ const subVal = (v as Record<string, unknown>).url || (v as Record<string, unknown>).link || (v as Record<string, unknown>).src;
213
+ if (typeof subVal === 'string' && subVal.startsWith('http')) return subVal;
214
+ }
210
215
  }
211
- if (Array.isArray(data.images) && typeof data.images[0] === 'string') {
212
- return data.images[0];
216
+ if (Array.isArray(data.images)) {
217
+ for (const item of data.images) {
218
+ if (typeof item === 'string' && item.startsWith('http')) return item;
219
+ if (item && typeof item === 'object') {
220
+ const url = item.url || item.link || item.src;
221
+ if (typeof url === 'string' && url.startsWith('http')) return url;
222
+ }
223
+ }
213
224
  }
214
225
  return undefined;
215
226
  }
@@ -425,16 +436,21 @@ function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unk
425
436
  return [];
426
437
  }
427
438
 
428
- function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAddToCart, viewportSize = 'large' }: {
439
+ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, viewportSize = 'large' }: {
429
440
  rawContent: string;
430
441
  primaryColor?: string;
431
442
  accentColor?: string;
432
443
  isStreaming?: boolean;
433
- onAddToCart?: (product: Product) => void;
434
444
  viewportSize?: ChatViewportSize;
435
445
  }) {
436
- const result = React.useMemo<{ config: UIConfig } | { error: string } | { loading: true }>(() => {
437
- if (isStreaming) return { loading: true };
446
+ const result = React.useMemo<{ config: UIConfig } | { error: string } | { loading: true; silent?: boolean }>(() => {
447
+ if (isStreaming) {
448
+ const looksLikeCarousel = rawContent.includes('"view": "carousel"') ||
449
+ rawContent.includes('"view":"carousel"') ||
450
+ rawContent.includes('"type": "products"') ||
451
+ rawContent.includes('"type":"products"');
452
+ return { loading: true, silent: looksLikeCarousel };
453
+ }
438
454
  try {
439
455
  const clean = rawContent
440
456
  .replace(/^```[a-z]*\s*/i, '')
@@ -495,6 +511,7 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
495
511
  }, [rawContent, isStreaming]);
496
512
 
497
513
  if ('loading' in result) {
514
+ if (result.silent) return null;
498
515
  return (
499
516
  <div className="my-4 p-8 bg-slate-50 dark:bg-white/5 rounded-xl border border-dashed border-slate-300 dark:border-white/10 flex flex-col items-center justify-center gap-3 animate-pulse">
500
517
  <div className="w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
@@ -547,16 +564,14 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
547
564
  </div>
548
565
  );
549
566
  case 'carousel':
567
+ // We only render title/description here. The actual carousel is rendered separately
568
+ // at the bottom of the MessageBubble to avoid being trapped in the streaming loading box.
550
569
  return (
551
570
  <div className="my-4">
552
571
  {config.title && <h4 className={`${isCompact ? 'mb-1.5 text-[11px]' : 'mb-2 text-xs'} font-semibold text-slate-500`}>{config.title}</h4>}
553
572
  {hasDescription && (
554
573
  <p className={`mb-3 ${isCompact ? 'text-xs' : 'text-sm'} text-slate-500 dark:text-white/60`}>{config.description}</p>
555
574
  )}
556
- <ProductCarousel products={(config.data as unknown as Product[]).map(item => ({
557
- ...item,
558
- image: item.image ?? (typeof resolveImage === 'function' ? resolveImage(item as unknown as Record<string, unknown>) : undefined)
559
- }))} primaryColor={primaryColor} onAddToCart={onAddToCart} />
560
575
  </div>
561
576
  );
562
577
  case 'table':
@@ -599,11 +614,6 @@ export function MessageBubble({
599
614
  const isCompact = viewportSize === 'compact';
600
615
  const isMedium = viewportSize === 'medium';
601
616
  const [showSources, setShowSources] = React.useState(false);
602
- const hasStructuredProductBlock = React.useMemo(
603
- () => /```(?:ui|json|chart)\s*[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products|table)"?/i.test(message.content) ||
604
- (/\{[\s\S]*?"?(?:view|type|data|products)"?\s*:\s*"?(?:carousel|products)"?[\s\S]*\}/i.test(message.content) && looksLikeStructuredPayload(message.content)),
605
- [message.content],
606
- );
607
617
  const structuredContent = React.useMemo(
608
618
  () => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
609
619
  [isUser, message.content],
@@ -830,7 +840,6 @@ export function MessageBubble({
830
840
  primaryColor={primaryColor}
831
841
  accentColor={accentColor}
832
842
  isStreaming={isStreaming}
833
- onAddToCart={onAddToCart}
834
843
  viewportSize={viewportSize}
835
844
  />
836
845
  );
@@ -845,7 +854,6 @@ export function MessageBubble({
845
854
  primaryColor={primaryColor}
846
855
  accentColor={accentColor}
847
856
  isStreaming={isStreaming}
848
- onAddToCart={onAddToCart}
849
857
  viewportSize={viewportSize}
850
858
  />
851
859
  );
@@ -861,7 +869,6 @@ export function MessageBubble({
861
869
  primaryColor={primaryColor}
862
870
  accentColor={accentColor}
863
871
  isStreaming={isStreaming}
864
- onAddToCart={onAddToCart}
865
872
  viewportSize={viewportSize}
866
873
  />
867
874
  );
@@ -888,7 +895,7 @@ export function MessageBubble({
888
895
  );
889
896
  },
890
897
  }),
891
- [primaryColor, accentColor, isStreaming, onAddToCart, viewportSize],
898
+ [primaryColor, accentColor, isStreaming, viewportSize],
892
899
  );
893
900
 
894
901
  // ── Render ─────────────────────────────────────────────────────────────────
@@ -930,7 +937,6 @@ export function MessageBubble({
930
937
  primaryColor={primaryColor}
931
938
  accentColor={accentColor}
932
939
  isStreaming={isStreaming}
933
- onAddToCart={onAddToCart}
934
940
  viewportSize={viewportSize}
935
941
  />
936
942
  )}
@@ -949,7 +955,7 @@ export function MessageBubble({
949
955
  </div>
950
956
 
951
957
  {/* Product Carousel */}
952
- {!isUser && !hasStructuredProductBlock && allProducts.length > 0 && (
958
+ {!isUser && allProducts.length > 0 && (
953
959
  <div className="w-full mt-1">
954
960
  <ProductCarousel
955
961
  products={allProducts}
@@ -122,9 +122,9 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
122
122
  }
123
123
 
124
124
  3. DATA RULES
125
- - Build the UI payload from retrieved context only.
125
+ - Build the UI payload from retrieved context and metadata.
126
126
  - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
127
- - For CHART: "data" items MUST have { label, value }.
127
+ - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
128
128
 
129
129
  4. FORMAT RULES
130
130
  - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
@@ -139,7 +139,7 @@ Assistant: "I found several beauty products in our catalog:"
139
139
  "title": "Beauty Products",
140
140
  "description": "Latest skincare and makeup items.",
141
141
  "data": [
142
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
142
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
143
143
  ]
144
144
  }
145
145
  \`\`\`
@@ -387,7 +387,7 @@ Assistant: "I found several beauty products in our catalog:"
387
387
 
388
388
  // 4. Context Augmentation
389
389
  let context = sources.length
390
- ? sources.map((m, i) => `[Source ${i + 1}]\n${m.content}`).join('\n\n---\n\n')
390
+ ? sources.map((m, i) => `[Source ${i + 1}]\nContent: ${m.content}\nMetadata: ${JSON.stringify(m.metadata)}`).join('\n\n---\n\n')
391
391
  : 'No relevant context found.';
392
392
 
393
393
  if (graphData && graphData.nodes.length > 0) {