@retrivora-ai/rag-engine 1.6.7 → 1.6.9
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-5K7EU4KW.mjs → chunk-Q7FLITXB.mjs} +21 -40
- package/dist/handlers/index.js +21 -40
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +20 -17
- package/dist/index.mjs +20 -17
- package/dist/server.js +21 -40
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +43 -26
- package/src/core/Pipeline.ts +21 -40
|
@@ -2259,69 +2259,50 @@ var Pipeline = class {
|
|
|
2259
2259
|
async initialize() {
|
|
2260
2260
|
var _a, _b;
|
|
2261
2261
|
if (this.initialised) return;
|
|
2262
|
-
const CHART_MARKER = "<!--
|
|
2262
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
|
|
2263
2263
|
const chartInstruction = `
|
|
2264
2264
|
|
|
2265
2265
|
${CHART_MARKER}
|
|
2266
|
-
### UNIVERSAL UI PROTOCOL
|
|
2267
|
-
When
|
|
2266
|
+
### UNIVERSAL UI PROTOCOL (MANDATORY)
|
|
2267
|
+
CRITICAL: When providing visual data (carousels, charts, tables), you MUST wrap the JSON payload in a \`\`\`ui\`\`\` code block.
|
|
2268
|
+
NEVER output naked JSON. If you provide a visualization, do not also provide a markdown table of the same data unless explicitly asked.
|
|
2268
2269
|
|
|
2269
2270
|
1. VIEW SELECTION
|
|
2270
|
-
- Use "
|
|
2271
|
-
- Use "
|
|
2272
|
-
- Use "table" for
|
|
2271
|
+
- Use "carousel" for browsing products, items, or catalogs. (e.g., "show me beauty products").
|
|
2272
|
+
- Use "chart" ONLY for numerical aggregations (counts, sums, percentages).
|
|
2273
|
+
- Use "table" for technical specs or row-heavy comparisons.
|
|
2273
2274
|
|
|
2274
2275
|
2. REQUIRED JSON SHAPE
|
|
2275
2276
|
{
|
|
2276
2277
|
"view": "chart" | "carousel" | "table",
|
|
2277
2278
|
"title": "Short heading",
|
|
2278
2279
|
"description": "One sentence describing the view",
|
|
2279
|
-
"chartType": "pie" | "bar" | "line",
|
|
2280
|
-
"
|
|
2281
|
-
"dataKeys": ["value"],
|
|
2282
|
-
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
2283
|
-
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
2284
|
-
"data": []
|
|
2280
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
2281
|
+
"data": [] // Array of objects
|
|
2285
2282
|
}
|
|
2286
2283
|
|
|
2287
2284
|
3. DATA RULES
|
|
2288
|
-
- Build the UI payload from retrieved context only.
|
|
2289
|
-
-
|
|
2290
|
-
- For
|
|
2291
|
-
- Prefer { "label": "...", "value": number } for pie charts.
|
|
2292
|
-
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
2293
|
-
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
2294
|
-
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
2295
|
-
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
2285
|
+
- Build the UI payload from retrieved context only.
|
|
2286
|
+
- For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
|
|
2287
|
+
- For CHART: "data" items MUST have { label, value }.
|
|
2296
2288
|
|
|
2297
2289
|
4. FORMAT RULES
|
|
2298
|
-
-
|
|
2299
|
-
-
|
|
2300
|
-
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
2301
|
-
- If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
|
|
2302
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
2290
|
+
- Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
|
|
2291
|
+
- Place natural language text OUTSIDE the block.
|
|
2303
2292
|
|
|
2304
2293
|
5. EXAMPLE
|
|
2305
|
-
User: "
|
|
2306
|
-
Assistant:
|
|
2294
|
+
User: "find me beauty products"
|
|
2295
|
+
Assistant: "I found several beauty products in our catalog:"
|
|
2307
2296
|
\`\`\`ui
|
|
2308
2297
|
{
|
|
2309
|
-
"view": "
|
|
2310
|
-
"title": "
|
|
2311
|
-
"description": "
|
|
2312
|
-
"chartType": "pie",
|
|
2313
|
-
"xAxisKey": "label",
|
|
2314
|
-
"dataKeys": ["value"],
|
|
2315
|
-
"insights": [
|
|
2316
|
-
"Beauty has the highest product count.",
|
|
2317
|
-
"Electronics has the largest number of in-stock products."
|
|
2318
|
-
],
|
|
2298
|
+
"view": "carousel",
|
|
2299
|
+
"title": "Beauty Products",
|
|
2300
|
+
"description": "Latest skincare and makeup items.",
|
|
2319
2301
|
"data": [
|
|
2320
|
-
{ "
|
|
2321
|
-
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
2322
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
2302
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
|
|
2323
2303
|
]
|
|
2324
2304
|
}
|
|
2305
|
+
\`\`\`
|
|
2325
2306
|
\`\`\``;
|
|
2326
2307
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
2327
2308
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
package/dist/handlers/index.js
CHANGED
|
@@ -3822,69 +3822,50 @@ var Pipeline = class {
|
|
|
3822
3822
|
async initialize() {
|
|
3823
3823
|
var _a, _b;
|
|
3824
3824
|
if (this.initialised) return;
|
|
3825
|
-
const CHART_MARKER = "<!--
|
|
3825
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
|
|
3826
3826
|
const chartInstruction = `
|
|
3827
3827
|
|
|
3828
3828
|
${CHART_MARKER}
|
|
3829
|
-
### UNIVERSAL UI PROTOCOL
|
|
3830
|
-
When
|
|
3829
|
+
### UNIVERSAL UI PROTOCOL (MANDATORY)
|
|
3830
|
+
CRITICAL: When providing visual data (carousels, charts, tables), you MUST wrap the JSON payload in a \`\`\`ui\`\`\` code block.
|
|
3831
|
+
NEVER output naked JSON. If you provide a visualization, do not also provide a markdown table of the same data unless explicitly asked.
|
|
3831
3832
|
|
|
3832
3833
|
1. VIEW SELECTION
|
|
3833
|
-
- Use "
|
|
3834
|
-
- Use "
|
|
3835
|
-
- Use "table" for
|
|
3834
|
+
- Use "carousel" for browsing products, items, or catalogs. (e.g., "show me beauty products").
|
|
3835
|
+
- Use "chart" ONLY for numerical aggregations (counts, sums, percentages).
|
|
3836
|
+
- Use "table" for technical specs or row-heavy comparisons.
|
|
3836
3837
|
|
|
3837
3838
|
2. REQUIRED JSON SHAPE
|
|
3838
3839
|
{
|
|
3839
3840
|
"view": "chart" | "carousel" | "table",
|
|
3840
3841
|
"title": "Short heading",
|
|
3841
3842
|
"description": "One sentence describing the view",
|
|
3842
|
-
"chartType": "pie" | "bar" | "line",
|
|
3843
|
-
"
|
|
3844
|
-
"dataKeys": ["value"],
|
|
3845
|
-
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
3846
|
-
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3847
|
-
"data": []
|
|
3843
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
3844
|
+
"data": [] // Array of objects
|
|
3848
3845
|
}
|
|
3849
3846
|
|
|
3850
3847
|
3. DATA RULES
|
|
3851
|
-
- Build the UI payload from retrieved context only.
|
|
3852
|
-
-
|
|
3853
|
-
- For
|
|
3854
|
-
- Prefer { "label": "...", "value": number } for pie charts.
|
|
3855
|
-
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
3856
|
-
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
3857
|
-
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
3858
|
-
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
3848
|
+
- Build the UI payload from retrieved context only.
|
|
3849
|
+
- For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
|
|
3850
|
+
- For CHART: "data" items MUST have { label, value }.
|
|
3859
3851
|
|
|
3860
3852
|
4. FORMAT RULES
|
|
3861
|
-
-
|
|
3862
|
-
-
|
|
3863
|
-
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3864
|
-
- If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
|
|
3865
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
3853
|
+
- Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
|
|
3854
|
+
- Place natural language text OUTSIDE the block.
|
|
3866
3855
|
|
|
3867
3856
|
5. EXAMPLE
|
|
3868
|
-
User: "
|
|
3869
|
-
Assistant:
|
|
3857
|
+
User: "find me beauty products"
|
|
3858
|
+
Assistant: "I found several beauty products in our catalog:"
|
|
3870
3859
|
\`\`\`ui
|
|
3871
3860
|
{
|
|
3872
|
-
"view": "
|
|
3873
|
-
"title": "
|
|
3874
|
-
"description": "
|
|
3875
|
-
"chartType": "pie",
|
|
3876
|
-
"xAxisKey": "label",
|
|
3877
|
-
"dataKeys": ["value"],
|
|
3878
|
-
"insights": [
|
|
3879
|
-
"Beauty has the highest product count.",
|
|
3880
|
-
"Electronics has the largest number of in-stock products."
|
|
3881
|
-
],
|
|
3861
|
+
"view": "carousel",
|
|
3862
|
+
"title": "Beauty Products",
|
|
3863
|
+
"description": "Latest skincare and makeup items.",
|
|
3882
3864
|
"data": [
|
|
3883
|
-
{ "
|
|
3884
|
-
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
3885
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3865
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
|
|
3886
3866
|
]
|
|
3887
3867
|
}
|
|
3868
|
+
\`\`\`
|
|
3888
3869
|
\`\`\``;
|
|
3889
3870
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3890
3871
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -405,7 +405,7 @@ function sanitizeJson(raw) {
|
|
|
405
405
|
return `: ${result}`;
|
|
406
406
|
}
|
|
407
407
|
);
|
|
408
|
-
s = s.replace(
|
|
408
|
+
s = s.replace(/(^|[^\:])\/\/[^\n]*/g, "$1");
|
|
409
409
|
s = s.replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g, '$1"$2":');
|
|
410
410
|
s = s.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
|
|
411
411
|
s = s.replace(/,\s*([}\]])/g, "$1");
|
|
@@ -456,7 +456,7 @@ function extractLikelyJsonBlock(raw) {
|
|
|
456
456
|
return trimmed.slice(Math.min(...starts));
|
|
457
457
|
}
|
|
458
458
|
function looksLikeStructuredPayload(raw) {
|
|
459
|
-
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
459
|
+
return /"?(view|chartType|type|data|items|rows|products|results|columns|xAxisKey)"?\s*:/.test(raw);
|
|
460
460
|
}
|
|
461
461
|
function stripMarkdownTables(raw) {
|
|
462
462
|
return raw.replace(
|
|
@@ -651,23 +651,25 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
651
651
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
652
652
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
653
653
|
const config2 = __spreadValues({}, parsed);
|
|
654
|
+
const resolvedRows = resolveStructuredRows(config2);
|
|
655
|
+
const hasProductLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["price", "brand", "image", "link", "thumbnail"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length >= 4);
|
|
656
|
+
const hasAggregationLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["value", "count", "total", "percentage"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some((v) => typeof v === "number"));
|
|
657
|
+
if (config2.view === "chart" && hasProductLikeData && !hasAggregationLikeData) {
|
|
658
|
+
config2.view = "carousel";
|
|
659
|
+
} else if (config2.view === "carousel" && hasAggregationLikeData && !hasProductLikeData) {
|
|
660
|
+
config2.view = "chart";
|
|
661
|
+
config2.chartType = config2.chartType || "bar";
|
|
662
|
+
}
|
|
654
663
|
if (!config2.view) {
|
|
655
|
-
|
|
656
|
-
const hasProductLikeData = resolvedRows2.length > 0 && typeof resolvedRows2[0] === "object" && resolvedRows2[0] !== null && ("price" in resolvedRows2[0] || "brand" in resolvedRows2[0] || "image" in resolvedRows2[0]);
|
|
657
|
-
if (config2.type === "products" || Array.isArray(config2.items) || Array.isArray(config2.data) && hasProductLikeData) {
|
|
664
|
+
if (config2.type === "products" || Array.isArray(config2.items) || hasProductLikeData) {
|
|
658
665
|
config2.view = "carousel";
|
|
659
|
-
|
|
660
|
-
} else if (typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
661
|
-
const resolvedChartType = config2.chartType === "pie" || config2.chartType === "bar" || config2.chartType === "line" ? config2.chartType : config2.type === "pie" || config2.type === "bar" || config2.type === "line" ? config2.type : "bar";
|
|
666
|
+
} else if (hasAggregationLikeData || typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
662
667
|
config2.view = "chart";
|
|
663
|
-
config2.chartType =
|
|
664
|
-
config2.data = resolvedRows2;
|
|
668
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
665
669
|
} else {
|
|
666
670
|
config2.view = "table";
|
|
667
|
-
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
668
671
|
}
|
|
669
672
|
}
|
|
670
|
-
const resolvedRows = resolveStructuredRows(config2);
|
|
671
673
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
672
674
|
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
673
675
|
});
|
|
@@ -839,15 +841,16 @@ function MessageBubble({
|
|
|
839
841
|
let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
|
|
840
842
|
const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
841
843
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
842
|
-
if (!isStreaming) {
|
|
843
|
-
raw = raw.replace(/(?:^|\n)\s
|
|
844
|
+
if (!isStreaming && !hasStructuredUiBlock) {
|
|
845
|
+
raw = raw.replace(/(?:^|\n)\s*(\{[\s\S]+?\})(?:\s*\n|$)/g, (match, body) => {
|
|
844
846
|
if (match.includes("```")) return match;
|
|
845
|
-
|
|
846
|
-
|
|
847
|
+
const trimmedBody = body.trim();
|
|
848
|
+
if (!looksLikeStructuredPayload(trimmedBody)) return match;
|
|
849
|
+
const type = trimmedBody.includes('"view"') || trimmedBody.includes('"chartType"') || trimmedBody.includes('"type"') || trimmedBody.includes('"xAxisKey"') ? "ui" : "json";
|
|
847
850
|
return `
|
|
848
851
|
|
|
849
852
|
\`\`\`${type}
|
|
850
|
-
${
|
|
853
|
+
${trimmedBody}
|
|
851
854
|
\`\`\`
|
|
852
855
|
|
|
853
856
|
`;
|
package/dist/index.mjs
CHANGED
|
@@ -368,7 +368,7 @@ function sanitizeJson(raw) {
|
|
|
368
368
|
return `: ${result}`;
|
|
369
369
|
}
|
|
370
370
|
);
|
|
371
|
-
s = s.replace(
|
|
371
|
+
s = s.replace(/(^|[^\:])\/\/[^\n]*/g, "$1");
|
|
372
372
|
s = s.replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g, '$1"$2":');
|
|
373
373
|
s = s.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
|
|
374
374
|
s = s.replace(/,\s*([}\]])/g, "$1");
|
|
@@ -419,7 +419,7 @@ function extractLikelyJsonBlock(raw) {
|
|
|
419
419
|
return trimmed.slice(Math.min(...starts));
|
|
420
420
|
}
|
|
421
421
|
function looksLikeStructuredPayload(raw) {
|
|
422
|
-
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
422
|
+
return /"?(view|chartType|type|data|items|rows|products|results|columns|xAxisKey)"?\s*:/.test(raw);
|
|
423
423
|
}
|
|
424
424
|
function stripMarkdownTables(raw) {
|
|
425
425
|
return raw.replace(
|
|
@@ -614,23 +614,25 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
614
614
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
615
615
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
616
616
|
const config2 = __spreadValues({}, parsed);
|
|
617
|
+
const resolvedRows = resolveStructuredRows(config2);
|
|
618
|
+
const hasProductLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["price", "brand", "image", "link", "thumbnail"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length >= 4);
|
|
619
|
+
const hasAggregationLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["value", "count", "total", "percentage"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some((v) => typeof v === "number"));
|
|
620
|
+
if (config2.view === "chart" && hasProductLikeData && !hasAggregationLikeData) {
|
|
621
|
+
config2.view = "carousel";
|
|
622
|
+
} else if (config2.view === "carousel" && hasAggregationLikeData && !hasProductLikeData) {
|
|
623
|
+
config2.view = "chart";
|
|
624
|
+
config2.chartType = config2.chartType || "bar";
|
|
625
|
+
}
|
|
617
626
|
if (!config2.view) {
|
|
618
|
-
|
|
619
|
-
const hasProductLikeData = resolvedRows2.length > 0 && typeof resolvedRows2[0] === "object" && resolvedRows2[0] !== null && ("price" in resolvedRows2[0] || "brand" in resolvedRows2[0] || "image" in resolvedRows2[0]);
|
|
620
|
-
if (config2.type === "products" || Array.isArray(config2.items) || Array.isArray(config2.data) && hasProductLikeData) {
|
|
627
|
+
if (config2.type === "products" || Array.isArray(config2.items) || hasProductLikeData) {
|
|
621
628
|
config2.view = "carousel";
|
|
622
|
-
|
|
623
|
-
} else if (typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
624
|
-
const resolvedChartType = config2.chartType === "pie" || config2.chartType === "bar" || config2.chartType === "line" ? config2.chartType : config2.type === "pie" || config2.type === "bar" || config2.type === "line" ? config2.type : "bar";
|
|
629
|
+
} else if (hasAggregationLikeData || typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
625
630
|
config2.view = "chart";
|
|
626
|
-
config2.chartType =
|
|
627
|
-
config2.data = resolvedRows2;
|
|
631
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
628
632
|
} else {
|
|
629
633
|
config2.view = "table";
|
|
630
|
-
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
631
634
|
}
|
|
632
635
|
}
|
|
633
|
-
const resolvedRows = resolveStructuredRows(config2);
|
|
634
636
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
635
637
|
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
636
638
|
});
|
|
@@ -802,15 +804,16 @@ function MessageBubble({
|
|
|
802
804
|
let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
|
|
803
805
|
const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
804
806
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
805
|
-
if (!isStreaming) {
|
|
806
|
-
raw = raw.replace(/(?:^|\n)\s
|
|
807
|
+
if (!isStreaming && !hasStructuredUiBlock) {
|
|
808
|
+
raw = raw.replace(/(?:^|\n)\s*(\{[\s\S]+?\})(?:\s*\n|$)/g, (match, body) => {
|
|
807
809
|
if (match.includes("```")) return match;
|
|
808
|
-
|
|
809
|
-
|
|
810
|
+
const trimmedBody = body.trim();
|
|
811
|
+
if (!looksLikeStructuredPayload(trimmedBody)) return match;
|
|
812
|
+
const type = trimmedBody.includes('"view"') || trimmedBody.includes('"chartType"') || trimmedBody.includes('"type"') || trimmedBody.includes('"xAxisKey"') ? "ui" : "json";
|
|
810
813
|
return `
|
|
811
814
|
|
|
812
815
|
\`\`\`${type}
|
|
813
|
-
${
|
|
816
|
+
${trimmedBody}
|
|
814
817
|
\`\`\`
|
|
815
818
|
|
|
816
819
|
`;
|
package/dist/server.js
CHANGED
|
@@ -3913,69 +3913,50 @@ var Pipeline = class {
|
|
|
3913
3913
|
async initialize() {
|
|
3914
3914
|
var _a, _b;
|
|
3915
3915
|
if (this.initialised) return;
|
|
3916
|
-
const CHART_MARKER = "<!--
|
|
3916
|
+
const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
|
|
3917
3917
|
const chartInstruction = `
|
|
3918
3918
|
|
|
3919
3919
|
${CHART_MARKER}
|
|
3920
|
-
### UNIVERSAL UI PROTOCOL
|
|
3921
|
-
When
|
|
3920
|
+
### UNIVERSAL UI PROTOCOL (MANDATORY)
|
|
3921
|
+
CRITICAL: When providing visual data (carousels, charts, tables), you MUST wrap the JSON payload in a \`\`\`ui\`\`\` code block.
|
|
3922
|
+
NEVER output naked JSON. If you provide a visualization, do not also provide a markdown table of the same data unless explicitly asked.
|
|
3922
3923
|
|
|
3923
3924
|
1. VIEW SELECTION
|
|
3924
|
-
- Use "
|
|
3925
|
-
- Use "
|
|
3926
|
-
- Use "table" for
|
|
3925
|
+
- Use "carousel" for browsing products, items, or catalogs. (e.g., "show me beauty products").
|
|
3926
|
+
- Use "chart" ONLY for numerical aggregations (counts, sums, percentages).
|
|
3927
|
+
- Use "table" for technical specs or row-heavy comparisons.
|
|
3927
3928
|
|
|
3928
3929
|
2. REQUIRED JSON SHAPE
|
|
3929
3930
|
{
|
|
3930
3931
|
"view": "chart" | "carousel" | "table",
|
|
3931
3932
|
"title": "Short heading",
|
|
3932
3933
|
"description": "One sentence describing the view",
|
|
3933
|
-
"chartType": "pie" | "bar" | "line",
|
|
3934
|
-
"
|
|
3935
|
-
"dataKeys": ["value"],
|
|
3936
|
-
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
3937
|
-
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
3938
|
-
"data": []
|
|
3934
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
3935
|
+
"data": [] // Array of objects
|
|
3939
3936
|
}
|
|
3940
3937
|
|
|
3941
3938
|
3. DATA RULES
|
|
3942
|
-
- Build the UI payload from retrieved context only.
|
|
3943
|
-
-
|
|
3944
|
-
- For
|
|
3945
|
-
- Prefer { "label": "...", "value": number } for pie charts.
|
|
3946
|
-
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
3947
|
-
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
3948
|
-
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
3949
|
-
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
3939
|
+
- Build the UI payload from retrieved context only.
|
|
3940
|
+
- For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
|
|
3941
|
+
- For CHART: "data" items MUST have { label, value }.
|
|
3950
3942
|
|
|
3951
3943
|
4. FORMAT RULES
|
|
3952
|
-
-
|
|
3953
|
-
-
|
|
3954
|
-
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3955
|
-
- If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
|
|
3956
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
3944
|
+
- Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
|
|
3945
|
+
- Place natural language text OUTSIDE the block.
|
|
3957
3946
|
|
|
3958
3947
|
5. EXAMPLE
|
|
3959
|
-
User: "
|
|
3960
|
-
Assistant:
|
|
3948
|
+
User: "find me beauty products"
|
|
3949
|
+
Assistant: "I found several beauty products in our catalog:"
|
|
3961
3950
|
\`\`\`ui
|
|
3962
3951
|
{
|
|
3963
|
-
"view": "
|
|
3964
|
-
"title": "
|
|
3965
|
-
"description": "
|
|
3966
|
-
"chartType": "pie",
|
|
3967
|
-
"xAxisKey": "label",
|
|
3968
|
-
"dataKeys": ["value"],
|
|
3969
|
-
"insights": [
|
|
3970
|
-
"Beauty has the highest product count.",
|
|
3971
|
-
"Electronics has the largest number of in-stock products."
|
|
3972
|
-
],
|
|
3952
|
+
"view": "carousel",
|
|
3953
|
+
"title": "Beauty Products",
|
|
3954
|
+
"description": "Latest skincare and makeup items.",
|
|
3973
3955
|
"data": [
|
|
3974
|
-
{ "
|
|
3975
|
-
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
3976
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3956
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
|
|
3977
3957
|
]
|
|
3978
3958
|
}
|
|
3959
|
+
\`\`\`
|
|
3979
3960
|
\`\`\``;
|
|
3980
3961
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3981
3962
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
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.6.
|
|
3
|
+
"version": "1.6.9",
|
|
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",
|
|
@@ -32,8 +32,8 @@ function sanitizeJson(raw: string): string {
|
|
|
32
32
|
},
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
-
// 2. Strip JS-style single-line comments
|
|
36
|
-
s = s.replace(
|
|
35
|
+
// 2. Strip JS-style single-line comments, but avoid URL protocols like https://
|
|
36
|
+
s = s.replace(/(^|[^\:])\/\/[^\n]*/g, '$1');
|
|
37
37
|
|
|
38
38
|
// 3. Quote bare (unquoted) object keys
|
|
39
39
|
s = s.replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g, '$1"$2":');
|
|
@@ -103,7 +103,7 @@ function extractLikelyJsonBlock(raw: string): string {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function looksLikeStructuredPayload(raw: string): boolean {
|
|
106
|
-
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
106
|
+
return /"?(view|chartType|type|data|items|rows|products|results|columns|xAxisKey)"?\s*:/.test(raw);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function stripMarkdownTables(raw: string): string {
|
|
@@ -442,36 +442,47 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
442
442
|
.trim();
|
|
443
443
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
444
444
|
const config = { ...parsed } as Partial<RawUIConfig> & Record<string, unknown>;
|
|
445
|
+
const resolvedRows = resolveStructuredRows(config);
|
|
445
446
|
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
447
|
+
// ─── Intent Healing & Auto-Detection ───────────────────────────────────
|
|
448
|
+
|
|
449
|
+
const hasProductLikeData = resolvedRows.length > 0 &&
|
|
450
|
+
typeof resolvedRows[0] === 'object' && resolvedRows[0] !== null &&
|
|
451
|
+
!Array.isArray(resolvedRows[0]) &&
|
|
452
|
+
(['price', 'brand', 'image', 'link', 'thumbnail'].some(key => key in resolvedRows[0]) ||
|
|
453
|
+
Object.keys(resolvedRows[0]).length >= 4);
|
|
454
|
+
|
|
455
|
+
const hasAggregationLikeData = resolvedRows.length > 0 &&
|
|
456
|
+
typeof resolvedRows[0] === 'object' && resolvedRows[0] !== null &&
|
|
457
|
+
!Array.isArray(resolvedRows[0]) &&
|
|
458
|
+
(['value', 'count', 'total', 'percentage'].some(key => key in resolvedRows[0]) ||
|
|
459
|
+
(Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some(v => typeof v === 'number')));
|
|
460
|
+
|
|
461
|
+
// 1. Healing: Override explicit view if it contradicts the data shape
|
|
462
|
+
if (config.view === 'chart' && hasProductLikeData && !hasAggregationLikeData) {
|
|
463
|
+
config.view = 'carousel';
|
|
464
|
+
} else if (config.view === 'carousel' && hasAggregationLikeData && !hasProductLikeData) {
|
|
465
|
+
config.view = 'chart';
|
|
466
|
+
config.chartType = (config.chartType as 'pie' | 'bar' | 'line') || 'bar';
|
|
467
|
+
}
|
|
451
468
|
|
|
452
|
-
|
|
469
|
+
// 2. Auto-Detection: Fill in missing view
|
|
470
|
+
if (!config.view) {
|
|
471
|
+
if (config.type === 'products' || Array.isArray(config.items) || hasProductLikeData) {
|
|
453
472
|
config.view = 'carousel';
|
|
454
|
-
config.data = resolvedRows;
|
|
455
473
|
} else if (
|
|
474
|
+
hasAggregationLikeData ||
|
|
456
475
|
(typeof config.type === 'string' && ['pie', 'bar', 'line'].includes(config.type)) ||
|
|
457
476
|
(typeof config.chartType === 'string' && ['pie', 'bar', 'line'].includes(config.chartType))
|
|
458
477
|
) {
|
|
459
|
-
const resolvedChartType =
|
|
460
|
-
config.chartType === 'pie' || config.chartType === 'bar' || config.chartType === 'line'
|
|
461
|
-
? config.chartType
|
|
462
|
-
: config.type === 'pie' || config.type === 'bar' || config.type === 'line'
|
|
463
|
-
? config.type
|
|
464
|
-
: 'bar';
|
|
465
478
|
config.view = 'chart';
|
|
466
|
-
config.chartType =
|
|
467
|
-
|
|
479
|
+
config.chartType = (config.chartType as 'pie' | 'bar' | 'line') ||
|
|
480
|
+
(config.type as 'pie' | 'bar' | 'line') || 'bar';
|
|
468
481
|
} else {
|
|
469
482
|
config.view = 'table';
|
|
470
|
-
config.data = Array.isArray(config.data) ? config.data : (Array.isArray(config) ? config : []);
|
|
471
483
|
}
|
|
472
484
|
}
|
|
473
485
|
|
|
474
|
-
const resolvedRows = resolveStructuredRows(config);
|
|
475
486
|
const normalizedConfig: UIConfig = {
|
|
476
487
|
...(config as Omit<UIConfig, 'data'>),
|
|
477
488
|
data: normalizeTabularRows(resolvedRows, config.columns),
|
|
@@ -707,17 +718,23 @@ export function MessageBubble({
|
|
|
707
718
|
.replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, '$1\n'); // Remove blank lines between rows
|
|
708
719
|
|
|
709
720
|
// 2. HEALING: Detect naked JSON charts/products and wrap them if markers are missing
|
|
710
|
-
// This handles models (like Llama 3.2) that often omit the ```
|
|
711
|
-
if (!isStreaming) {
|
|
721
|
+
// This handles models (like Llama 3.2) that often omit the ```ui markers.
|
|
722
|
+
if (!isStreaming && !hasStructuredUiBlock) {
|
|
712
723
|
// Look for objects that look like chart or product data
|
|
713
|
-
raw = raw.replace(/(?:^|\n)\s
|
|
724
|
+
raw = raw.replace(/(?:^|\n)\s*(\{[\s\S]+?\})(?:\s*\n|$)/g, (match, body) => {
|
|
714
725
|
// Only wrap if not already in a code block
|
|
715
726
|
if (match.includes('```')) return match;
|
|
716
|
-
|
|
717
|
-
|
|
727
|
+
const trimmedBody = body.trim();
|
|
728
|
+
if (!looksLikeStructuredPayload(trimmedBody)) return match;
|
|
729
|
+
|
|
730
|
+
const type = trimmedBody.includes('"view"') ||
|
|
731
|
+
trimmedBody.includes('"chartType"') ||
|
|
732
|
+
trimmedBody.includes('"type"') ||
|
|
733
|
+
trimmedBody.includes('"xAxisKey"')
|
|
718
734
|
? 'ui'
|
|
719
735
|
: 'json';
|
|
720
|
-
|
|
736
|
+
|
|
737
|
+
return `\n\n\`\`\`${type}\n${trimmedBody}\n\`\`\`\n\n`;
|
|
721
738
|
});
|
|
722
739
|
}
|
|
723
740
|
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -101,67 +101,48 @@ export class Pipeline {
|
|
|
101
101
|
|
|
102
102
|
// Augment system prompt with a Universal Visualization Protocol
|
|
103
103
|
// We use a unique marker to ensure this is only injected once but is ALWAYS present.
|
|
104
|
-
const CHART_MARKER = '<!--
|
|
104
|
+
const CHART_MARKER = '<!-- UI_PROTOCOL_V7 -->';
|
|
105
105
|
const chartInstruction = `\n\n${CHART_MARKER}
|
|
106
|
-
### UNIVERSAL UI PROTOCOL
|
|
107
|
-
When
|
|
106
|
+
### UNIVERSAL UI PROTOCOL (MANDATORY)
|
|
107
|
+
CRITICAL: When providing visual data (carousels, charts, tables), you MUST wrap the JSON payload in a \`\`\`ui\`\`\` code block.
|
|
108
|
+
NEVER output naked JSON. If you provide a visualization, do not also provide a markdown table of the same data unless explicitly asked.
|
|
108
109
|
|
|
109
110
|
1. VIEW SELECTION
|
|
110
|
-
- Use "
|
|
111
|
-
- Use "
|
|
112
|
-
- Use "table" for
|
|
111
|
+
- Use "carousel" for browsing products, items, or catalogs. (e.g., "show me beauty products").
|
|
112
|
+
- Use "chart" ONLY for numerical aggregations (counts, sums, percentages).
|
|
113
|
+
- Use "table" for technical specs or row-heavy comparisons.
|
|
113
114
|
|
|
114
115
|
2. REQUIRED JSON SHAPE
|
|
115
116
|
{
|
|
116
117
|
"view": "chart" | "carousel" | "table",
|
|
117
118
|
"title": "Short heading",
|
|
118
119
|
"description": "One sentence describing the view",
|
|
119
|
-
"chartType": "pie" | "bar" | "line",
|
|
120
|
-
"
|
|
121
|
-
"dataKeys": ["value"],
|
|
122
|
-
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
123
|
-
"insights": ["Short takeaway 1", "Short takeaway 2"],
|
|
124
|
-
"data": []
|
|
120
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
121
|
+
"data": [] // Array of objects
|
|
125
122
|
}
|
|
126
123
|
|
|
127
124
|
3. DATA RULES
|
|
128
|
-
- Build the UI payload from retrieved context only.
|
|
129
|
-
-
|
|
130
|
-
- For
|
|
131
|
-
- Prefer { "label": "...", "value": number } for pie charts.
|
|
132
|
-
- If stock information exists, preserve it in each row with fields like "inStock", "inStockCount", "outOfStockCount", or "stockStatus".
|
|
133
|
-
- If the user asks to highlight what is in stock, include those stock-related fields in the chart data and mention the pattern in "insights".
|
|
134
|
-
- For carousel rows, include product-friendly fields such as id, name, brand, price, image, link, category, stockStatus.
|
|
135
|
-
- For tables, keep "data" row-oriented and include "columns" only as dynamic field keys/order hints from the fetched data, never as hardcoded UI labels.
|
|
125
|
+
- Build the UI payload from retrieved context only.
|
|
126
|
+
- For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
|
|
127
|
+
- For CHART: "data" items MUST have { label, value }.
|
|
136
128
|
|
|
137
129
|
4. FORMAT RULES
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
141
|
-
- If the user explicitly asks for a pie chart, return "view": "chart" with "chartType": "pie", not a table fallback.
|
|
142
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
130
|
+
- Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
|
|
131
|
+
- Place natural language text OUTSIDE the block.
|
|
143
132
|
|
|
144
133
|
5. EXAMPLE
|
|
145
|
-
User: "
|
|
146
|
-
Assistant:
|
|
134
|
+
User: "find me beauty products"
|
|
135
|
+
Assistant: "I found several beauty products in our catalog:"
|
|
147
136
|
\`\`\`ui
|
|
148
137
|
{
|
|
149
|
-
"view": "
|
|
150
|
-
"title": "
|
|
151
|
-
"description": "
|
|
152
|
-
"chartType": "pie",
|
|
153
|
-
"xAxisKey": "label",
|
|
154
|
-
"dataKeys": ["value"],
|
|
155
|
-
"insights": [
|
|
156
|
-
"Beauty has the highest product count.",
|
|
157
|
-
"Electronics has the largest number of in-stock products."
|
|
158
|
-
],
|
|
138
|
+
"view": "carousel",
|
|
139
|
+
"title": "Beauty Products",
|
|
140
|
+
"description": "Latest skincare and makeup items.",
|
|
159
141
|
"data": [
|
|
160
|
-
{ "
|
|
161
|
-
{ "label": "Electronics", "value": 8, "inStockCount": 7, "outOfStockCount": 1 },
|
|
162
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
142
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
|
|
163
143
|
]
|
|
164
144
|
}
|
|
145
|
+
\`\`\`
|
|
165
146
|
\`\`\``;
|
|
166
147
|
|
|
167
148
|
if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {
|