@retrivora-ai/rag-engine 1.6.9 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Q7FLITXB.mjs → chunk-OHXRQTQH.mjs} +5 -4
- package/dist/handlers/index.js +5 -4
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +38 -30
- package/dist/index.mjs +38 -30
- package/dist/server.js +5 -4
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +54 -34
- package/src/core/Pipeline.ts +4 -4
|
@@ -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
|
|
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
|
-
-
|
|
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": "
|
|
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}
|
|
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) : ""}`
|
package/dist/handlers/index.js
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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": "
|
|
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}
|
|
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) : ""}`
|
package/dist/handlers/index.mjs
CHANGED
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
|
-
|
|
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)
|
|
543
|
-
|
|
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,
|
|
657
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, viewportSize = "large" }) {
|
|
647
658
|
var _a;
|
|
648
659
|
const result = import_react5.default.useMemo(() => {
|
|
649
|
-
if (isStreaming)
|
|
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)
|
|
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]
|
|
@@ -822,20 +828,26 @@ function MessageBubble({
|
|
|
822
828
|
return { productsFromContent: products, cleanContent: content.trim() };
|
|
823
829
|
}, [message.content, isUser, structuredContent]);
|
|
824
830
|
const allProducts = import_react5.default.useMemo(() => {
|
|
825
|
-
if (productsFromContent.length > 0) return productsFromContent;
|
|
826
831
|
const seen = /* @__PURE__ */ new Set();
|
|
832
|
+
const merged = [];
|
|
833
|
+
for (const p of productsFromContent) {
|
|
834
|
+
const id = String(p.id || p.name);
|
|
835
|
+
if (!seen.has(id)) {
|
|
836
|
+
seen.add(id);
|
|
837
|
+
merged.push(p);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
827
840
|
const contentLower = message.content.toLowerCase();
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
if (seen.has(id)) return false;
|
|
841
|
+
for (const p of productsFromSources) {
|
|
842
|
+
const id = String(p.id || p.name);
|
|
843
|
+
if (seen.has(id)) continue;
|
|
832
844
|
const mentioned = contentLower.includes(p.name.toLowerCase()) || (p.brand ? contentLower.includes(p.brand.toLowerCase()) : false);
|
|
833
845
|
if (mentioned) {
|
|
834
846
|
seen.add(id);
|
|
835
|
-
|
|
847
|
+
merged.push(p);
|
|
836
848
|
}
|
|
837
|
-
|
|
838
|
-
|
|
849
|
+
}
|
|
850
|
+
return merged;
|
|
839
851
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
840
852
|
const processedMarkdown = import_react5.default.useMemo(() => {
|
|
841
853
|
let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
|
|
@@ -950,7 +962,6 @@ ${match.trim()}
|
|
|
950
962
|
primaryColor,
|
|
951
963
|
accentColor,
|
|
952
964
|
isStreaming,
|
|
953
|
-
onAddToCart,
|
|
954
965
|
viewportSize
|
|
955
966
|
}
|
|
956
967
|
);
|
|
@@ -965,7 +976,6 @@ ${match.trim()}
|
|
|
965
976
|
primaryColor,
|
|
966
977
|
accentColor,
|
|
967
978
|
isStreaming,
|
|
968
|
-
onAddToCart,
|
|
969
979
|
viewportSize
|
|
970
980
|
}
|
|
971
981
|
);
|
|
@@ -981,7 +991,6 @@ ${match.trim()}
|
|
|
981
991
|
primaryColor,
|
|
982
992
|
accentColor,
|
|
983
993
|
isStreaming,
|
|
984
|
-
onAddToCart,
|
|
985
994
|
viewportSize
|
|
986
995
|
}
|
|
987
996
|
);
|
|
@@ -993,7 +1002,7 @@ ${match.trim()}
|
|
|
993
1002
|
return /* @__PURE__ */ import_react5.default.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
|
|
994
1003
|
}
|
|
995
1004
|
}),
|
|
996
|
-
[primaryColor, accentColor, isStreaming,
|
|
1005
|
+
[primaryColor, accentColor, isStreaming, viewportSize]
|
|
997
1006
|
);
|
|
998
1007
|
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
1008
|
"div",
|
|
@@ -1015,11 +1024,10 @@ ${match.trim()}
|
|
|
1015
1024
|
primaryColor,
|
|
1016
1025
|
accentColor,
|
|
1017
1026
|
isStreaming,
|
|
1018
|
-
onAddToCart,
|
|
1019
1027
|
viewportSize
|
|
1020
1028
|
}
|
|
1021
1029
|
), !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 &&
|
|
1030
|
+
), !isUser && allProducts.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
1023
1031
|
ProductCarousel,
|
|
1024
1032
|
{
|
|
1025
1033
|
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
|
-
|
|
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)
|
|
506
|
-
|
|
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,
|
|
620
|
+
function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, viewportSize = "large" }) {
|
|
610
621
|
var _a;
|
|
611
622
|
const result = React5.useMemo(() => {
|
|
612
|
-
if (isStreaming)
|
|
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)
|
|
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]
|
|
@@ -785,20 +791,26 @@ function MessageBubble({
|
|
|
785
791
|
return { productsFromContent: products, cleanContent: content.trim() };
|
|
786
792
|
}, [message.content, isUser, structuredContent]);
|
|
787
793
|
const allProducts = React5.useMemo(() => {
|
|
788
|
-
if (productsFromContent.length > 0) return productsFromContent;
|
|
789
794
|
const seen = /* @__PURE__ */ new Set();
|
|
795
|
+
const merged = [];
|
|
796
|
+
for (const p of productsFromContent) {
|
|
797
|
+
const id = String(p.id || p.name);
|
|
798
|
+
if (!seen.has(id)) {
|
|
799
|
+
seen.add(id);
|
|
800
|
+
merged.push(p);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
790
803
|
const contentLower = message.content.toLowerCase();
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (seen.has(id)) return false;
|
|
804
|
+
for (const p of productsFromSources) {
|
|
805
|
+
const id = String(p.id || p.name);
|
|
806
|
+
if (seen.has(id)) continue;
|
|
795
807
|
const mentioned = contentLower.includes(p.name.toLowerCase()) || (p.brand ? contentLower.includes(p.brand.toLowerCase()) : false);
|
|
796
808
|
if (mentioned) {
|
|
797
809
|
seen.add(id);
|
|
798
|
-
|
|
810
|
+
merged.push(p);
|
|
799
811
|
}
|
|
800
|
-
|
|
801
|
-
|
|
812
|
+
}
|
|
813
|
+
return merged;
|
|
802
814
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
803
815
|
const processedMarkdown = React5.useMemo(() => {
|
|
804
816
|
let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
|
|
@@ -913,7 +925,6 @@ ${match.trim()}
|
|
|
913
925
|
primaryColor,
|
|
914
926
|
accentColor,
|
|
915
927
|
isStreaming,
|
|
916
|
-
onAddToCart,
|
|
917
928
|
viewportSize
|
|
918
929
|
}
|
|
919
930
|
);
|
|
@@ -928,7 +939,6 @@ ${match.trim()}
|
|
|
928
939
|
primaryColor,
|
|
929
940
|
accentColor,
|
|
930
941
|
isStreaming,
|
|
931
|
-
onAddToCart,
|
|
932
942
|
viewportSize
|
|
933
943
|
}
|
|
934
944
|
);
|
|
@@ -944,7 +954,6 @@ ${match.trim()}
|
|
|
944
954
|
primaryColor,
|
|
945
955
|
accentColor,
|
|
946
956
|
isStreaming,
|
|
947
|
-
onAddToCart,
|
|
948
957
|
viewportSize
|
|
949
958
|
}
|
|
950
959
|
);
|
|
@@ -956,7 +965,7 @@ ${match.trim()}
|
|
|
956
965
|
return /* @__PURE__ */ React5.createElement("code", __spreadValues({ className }, props), typeof children === "string" || typeof children === "number" ? children : JSON.stringify(children));
|
|
957
966
|
}
|
|
958
967
|
}),
|
|
959
|
-
[primaryColor, accentColor, isStreaming,
|
|
968
|
+
[primaryColor, accentColor, isStreaming, viewportSize]
|
|
960
969
|
);
|
|
961
970
|
return /* @__PURE__ */ React5.createElement("div", { className: `flex ${isCompact ? "gap-2" : "gap-3"} ${isUser ? "flex-row-reverse" : "flex-row"} items-start` }, /* @__PURE__ */ React5.createElement(
|
|
962
971
|
"div",
|
|
@@ -978,11 +987,10 @@ ${match.trim()}
|
|
|
978
987
|
primaryColor,
|
|
979
988
|
accentColor,
|
|
980
989
|
isStreaming,
|
|
981
|
-
onAddToCart,
|
|
982
990
|
viewportSize
|
|
983
991
|
}
|
|
984
992
|
), !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 &&
|
|
993
|
+
), !isUser && allProducts.length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React5.createElement(
|
|
986
994
|
ProductCarousel,
|
|
987
995
|
{
|
|
988
996
|
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
|
|
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
|
-
-
|
|
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": "
|
|
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}
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
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
|
-
|
|
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)
|
|
212
|
-
|
|
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,
|
|
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)
|
|
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],
|
|
@@ -693,20 +703,34 @@ export function MessageBubble({
|
|
|
693
703
|
|
|
694
704
|
// ── Merge & deduplicate products ───────────────────────────────────────────
|
|
695
705
|
const allProducts = React.useMemo<Product[]>(() => {
|
|
696
|
-
if (productsFromContent.length > 0) return productsFromContent;
|
|
697
|
-
|
|
698
706
|
const seen = new Set<string>();
|
|
707
|
+
const merged: Product[] = [];
|
|
708
|
+
|
|
709
|
+
// 1. Add products extracted from the LLM content (highest priority)
|
|
710
|
+
for (const p of productsFromContent) {
|
|
711
|
+
const id = String(p.id || p.name);
|
|
712
|
+
if (!seen.has(id)) {
|
|
713
|
+
seen.add(id);
|
|
714
|
+
merged.push(p);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// 2. Add products from sources that were mentioned in the text
|
|
699
719
|
const contentLower = message.content.toLowerCase();
|
|
720
|
+
for (const p of productsFromSources) {
|
|
721
|
+
const id = String(p.id || p.name);
|
|
722
|
+
if (seen.has(id)) continue;
|
|
700
723
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
724
|
+
const mentioned = contentLower.includes(p.name.toLowerCase()) ||
|
|
725
|
+
(p.brand ? contentLower.includes(p.brand.toLowerCase()) : false);
|
|
726
|
+
|
|
727
|
+
if (mentioned) {
|
|
728
|
+
seen.add(id);
|
|
729
|
+
merged.push(p);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return merged;
|
|
710
734
|
}, [productsFromSources, productsFromContent, message.content]);
|
|
711
735
|
|
|
712
736
|
const processedMarkdown = React.useMemo(() => {
|
|
@@ -830,7 +854,6 @@ export function MessageBubble({
|
|
|
830
854
|
primaryColor={primaryColor}
|
|
831
855
|
accentColor={accentColor}
|
|
832
856
|
isStreaming={isStreaming}
|
|
833
|
-
onAddToCart={onAddToCart}
|
|
834
857
|
viewportSize={viewportSize}
|
|
835
858
|
/>
|
|
836
859
|
);
|
|
@@ -845,7 +868,6 @@ export function MessageBubble({
|
|
|
845
868
|
primaryColor={primaryColor}
|
|
846
869
|
accentColor={accentColor}
|
|
847
870
|
isStreaming={isStreaming}
|
|
848
|
-
onAddToCart={onAddToCart}
|
|
849
871
|
viewportSize={viewportSize}
|
|
850
872
|
/>
|
|
851
873
|
);
|
|
@@ -861,7 +883,6 @@ export function MessageBubble({
|
|
|
861
883
|
primaryColor={primaryColor}
|
|
862
884
|
accentColor={accentColor}
|
|
863
885
|
isStreaming={isStreaming}
|
|
864
|
-
onAddToCart={onAddToCart}
|
|
865
886
|
viewportSize={viewportSize}
|
|
866
887
|
/>
|
|
867
888
|
);
|
|
@@ -888,7 +909,7 @@ export function MessageBubble({
|
|
|
888
909
|
);
|
|
889
910
|
},
|
|
890
911
|
}),
|
|
891
|
-
[primaryColor, accentColor, isStreaming,
|
|
912
|
+
[primaryColor, accentColor, isStreaming, viewportSize],
|
|
892
913
|
);
|
|
893
914
|
|
|
894
915
|
// ── Render ─────────────────────────────────────────────────────────────────
|
|
@@ -930,7 +951,6 @@ export function MessageBubble({
|
|
|
930
951
|
primaryColor={primaryColor}
|
|
931
952
|
accentColor={accentColor}
|
|
932
953
|
isStreaming={isStreaming}
|
|
933
|
-
onAddToCart={onAddToCart}
|
|
934
954
|
viewportSize={viewportSize}
|
|
935
955
|
/>
|
|
936
956
|
)}
|
|
@@ -949,7 +969,7 @@ export function MessageBubble({
|
|
|
949
969
|
</div>
|
|
950
970
|
|
|
951
971
|
{/* Product Carousel */}
|
|
952
|
-
{!isUser &&
|
|
972
|
+
{!isUser && allProducts.length > 0 && (
|
|
953
973
|
<div className="w-full mt-1">
|
|
954
974
|
<ProductCarousel
|
|
955
975
|
products={allProducts}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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": "
|
|
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}]\
|
|
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) {
|