@retrivora-ai/rag-engine 1.6.6 → 1.6.8
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-J7BCIPO2.mjs} +29 -28
- package/dist/handlers/index.js +29 -28
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +19 -18
- package/dist/index.mjs +19 -18
- package/dist/server.js +29 -28
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +44 -29
- package/src/core/Pipeline.ts +29 -28
|
@@ -2259,7 +2259,7 @@ 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_V6 -->";
|
|
2263
2263
|
const chartInstruction = `
|
|
2264
2264
|
|
|
2265
2265
|
${CHART_MARKER}
|
|
@@ -2267,16 +2267,16 @@ ${CHART_MARKER}
|
|
|
2267
2267
|
When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
|
|
2268
2268
|
|
|
2269
2269
|
1. VIEW SELECTION
|
|
2270
|
-
- Use "
|
|
2271
|
-
- Use "
|
|
2272
|
-
- Use "table" for
|
|
2270
|
+
- Use "carousel" for browsing specific products, items, or a catalog. This is the DEFAULT for "show me", "what are", or "find me" requests for a specific category (e.g. "beauty").
|
|
2271
|
+
- Use "chart" ONLY for numerical aggregations: distributions, percentages, counts, comparisons over time, or trends (e.g. "how many beauty products?").
|
|
2272
|
+
- Use "table" for detailed technical specifications, row-heavy comparisons, or when the user explicitly asks for a list/table format.
|
|
2273
2273
|
|
|
2274
2274
|
2. REQUIRED JSON SHAPE
|
|
2275
2275
|
{
|
|
2276
2276
|
"view": "chart" | "carousel" | "table",
|
|
2277
2277
|
"title": "Short heading",
|
|
2278
2278
|
"description": "One sentence describing the view",
|
|
2279
|
-
"chartType": "pie" | "bar" | "line",
|
|
2279
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
2280
2280
|
"xAxisKey": "label",
|
|
2281
2281
|
"dataKeys": ["value"],
|
|
2282
2282
|
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
@@ -2285,41 +2285,42 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
2285
2285
|
}
|
|
2286
2286
|
|
|
2287
2287
|
3. DATA RULES
|
|
2288
|
-
- Build the UI payload from retrieved context only. Do not invent products
|
|
2289
|
-
-
|
|
2290
|
-
- For
|
|
2291
|
-
-
|
|
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.
|
|
2288
|
+
- Build the UI payload from retrieved context only. Do not invent products or values.
|
|
2289
|
+
- For CAROUSEL: "data" must be a list of product objects (id, name, brand, price, image, link).
|
|
2290
|
+
- For CHART: "data" must be aggregated counts or sums. Never put raw product lists in a chart view.
|
|
2291
|
+
- If the user asks for "products in beauty", do NOT return a pie chart of categories; return a carousel of the beauty products themselves.
|
|
2296
2292
|
|
|
2297
2293
|
4. FORMAT RULES
|
|
2298
|
-
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
2299
|
-
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
2300
2294
|
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
2301
|
-
-
|
|
2302
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
2295
|
+
- Put natural language explanation before or after the block, not inside.
|
|
2303
2296
|
|
|
2304
|
-
5.
|
|
2305
|
-
User: "
|
|
2297
|
+
5. EXAMPLES
|
|
2298
|
+
User: "show me beauty products"
|
|
2299
|
+
Assistant:
|
|
2300
|
+
\`\`\`ui
|
|
2301
|
+
{
|
|
2302
|
+
"view": "carousel",
|
|
2303
|
+
"title": "Beauty Products",
|
|
2304
|
+
"description": "Browse our latest skincare and makeup collection.",
|
|
2305
|
+
"data": [
|
|
2306
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
|
|
2307
|
+
{ "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
|
|
2308
|
+
]
|
|
2309
|
+
}
|
|
2310
|
+
\`\`\`
|
|
2311
|
+
|
|
2312
|
+
User: "breakdown of products by category"
|
|
2306
2313
|
Assistant:
|
|
2307
2314
|
\`\`\`ui
|
|
2308
2315
|
{
|
|
2309
2316
|
"view": "chart",
|
|
2310
|
-
"title": "
|
|
2311
|
-
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
2317
|
+
"title": "Category Breakdown",
|
|
2312
2318
|
"chartType": "pie",
|
|
2313
2319
|
"xAxisKey": "label",
|
|
2314
2320
|
"dataKeys": ["value"],
|
|
2315
|
-
"insights": [
|
|
2316
|
-
"Beauty has the highest product count.",
|
|
2317
|
-
"Electronics has the largest number of in-stock products."
|
|
2318
|
-
],
|
|
2319
2321
|
"data": [
|
|
2320
|
-
{ "label": "Beauty", "value":
|
|
2321
|
-
{ "label": "
|
|
2322
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
2322
|
+
{ "label": "Beauty", "value": 15 },
|
|
2323
|
+
{ "label": "Fragrances", "value": 8 }
|
|
2323
2324
|
]
|
|
2324
2325
|
}
|
|
2325
2326
|
\`\`\``;
|
package/dist/handlers/index.js
CHANGED
|
@@ -3822,7 +3822,7 @@ 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_V6 -->";
|
|
3826
3826
|
const chartInstruction = `
|
|
3827
3827
|
|
|
3828
3828
|
${CHART_MARKER}
|
|
@@ -3830,16 +3830,16 @@ ${CHART_MARKER}
|
|
|
3830
3830
|
When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
|
|
3831
3831
|
|
|
3832
3832
|
1. VIEW SELECTION
|
|
3833
|
-
- Use "
|
|
3834
|
-
- Use "
|
|
3835
|
-
- Use "table" for
|
|
3833
|
+
- Use "carousel" for browsing specific products, items, or a catalog. This is the DEFAULT for "show me", "what are", or "find me" requests for a specific category (e.g. "beauty").
|
|
3834
|
+
- Use "chart" ONLY for numerical aggregations: distributions, percentages, counts, comparisons over time, or trends (e.g. "how many beauty products?").
|
|
3835
|
+
- Use "table" for detailed technical specifications, row-heavy comparisons, or when the user explicitly asks for a list/table format.
|
|
3836
3836
|
|
|
3837
3837
|
2. REQUIRED JSON SHAPE
|
|
3838
3838
|
{
|
|
3839
3839
|
"view": "chart" | "carousel" | "table",
|
|
3840
3840
|
"title": "Short heading",
|
|
3841
3841
|
"description": "One sentence describing the view",
|
|
3842
|
-
"chartType": "pie" | "bar" | "line",
|
|
3842
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
3843
3843
|
"xAxisKey": "label",
|
|
3844
3844
|
"dataKeys": ["value"],
|
|
3845
3845
|
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
@@ -3848,41 +3848,42 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3848
3848
|
}
|
|
3849
3849
|
|
|
3850
3850
|
3. DATA RULES
|
|
3851
|
-
- Build the UI payload from retrieved context only. Do not invent products
|
|
3852
|
-
-
|
|
3853
|
-
- For
|
|
3854
|
-
-
|
|
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.
|
|
3851
|
+
- Build the UI payload from retrieved context only. Do not invent products or values.
|
|
3852
|
+
- For CAROUSEL: "data" must be a list of product objects (id, name, brand, price, image, link).
|
|
3853
|
+
- For CHART: "data" must be aggregated counts or sums. Never put raw product lists in a chart view.
|
|
3854
|
+
- If the user asks for "products in beauty", do NOT return a pie chart of categories; return a carousel of the beauty products themselves.
|
|
3859
3855
|
|
|
3860
3856
|
4. FORMAT RULES
|
|
3861
|
-
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
3862
|
-
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
3863
3857
|
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3864
|
-
-
|
|
3865
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
3858
|
+
- Put natural language explanation before or after the block, not inside.
|
|
3866
3859
|
|
|
3867
|
-
5.
|
|
3868
|
-
User: "
|
|
3860
|
+
5. EXAMPLES
|
|
3861
|
+
User: "show me beauty products"
|
|
3862
|
+
Assistant:
|
|
3863
|
+
\`\`\`ui
|
|
3864
|
+
{
|
|
3865
|
+
"view": "carousel",
|
|
3866
|
+
"title": "Beauty Products",
|
|
3867
|
+
"description": "Browse our latest skincare and makeup collection.",
|
|
3868
|
+
"data": [
|
|
3869
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
|
|
3870
|
+
{ "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
|
|
3871
|
+
]
|
|
3872
|
+
}
|
|
3873
|
+
\`\`\`
|
|
3874
|
+
|
|
3875
|
+
User: "breakdown of products by category"
|
|
3869
3876
|
Assistant:
|
|
3870
3877
|
\`\`\`ui
|
|
3871
3878
|
{
|
|
3872
3879
|
"view": "chart",
|
|
3873
|
-
"title": "
|
|
3874
|
-
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
3880
|
+
"title": "Category Breakdown",
|
|
3875
3881
|
"chartType": "pie",
|
|
3876
3882
|
"xAxisKey": "label",
|
|
3877
3883
|
"dataKeys": ["value"],
|
|
3878
|
-
"insights": [
|
|
3879
|
-
"Beauty has the highest product count.",
|
|
3880
|
-
"Electronics has the largest number of in-stock products."
|
|
3881
|
-
],
|
|
3882
3884
|
"data": [
|
|
3883
|
-
{ "label": "Beauty", "value":
|
|
3884
|
-
{ "label": "
|
|
3885
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3885
|
+
{ "label": "Beauty", "value": 15 },
|
|
3886
|
+
{ "label": "Fragrances", "value": 8 }
|
|
3886
3887
|
]
|
|
3887
3888
|
}
|
|
3888
3889
|
\`\`\``;
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -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)"
|
|
459
|
+
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
460
460
|
}
|
|
461
461
|
function stripMarkdownTables(raw) {
|
|
462
462
|
return raw.replace(
|
|
@@ -532,7 +532,7 @@ function extractStructuredPayload(raw) {
|
|
|
532
532
|
}
|
|
533
533
|
function isLikelyUiOnlyMessage(text) {
|
|
534
534
|
const trimmed = text.trim();
|
|
535
|
-
return trimmed.length === 0 || /^(chart data|table data|visualization|visual representation)\s*:?\s*$/i.test(trimmed);
|
|
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
|
for (const key of ["image", "img", "thumbnail"]) {
|
|
@@ -637,9 +637,9 @@ function normalizeTabularRows(rows, columns) {
|
|
|
637
637
|
}
|
|
638
638
|
function resolveStructuredRows(config) {
|
|
639
639
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
640
|
-
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
641
640
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
642
641
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
642
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
643
643
|
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
644
644
|
return [];
|
|
645
645
|
}
|
|
@@ -651,21 +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
|
-
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
664
|
+
if (config2.type === "products" || Array.isArray(config2.items) || hasProductLikeData) {
|
|
656
665
|
config2.view = "carousel";
|
|
657
|
-
|
|
658
|
-
} else if (typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
659
|
-
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)) {
|
|
660
667
|
config2.view = "chart";
|
|
661
|
-
config2.chartType =
|
|
662
|
-
config2.data = config2.data || [];
|
|
668
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
663
669
|
} else {
|
|
664
670
|
config2.view = "table";
|
|
665
|
-
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
666
671
|
}
|
|
667
672
|
}
|
|
668
|
-
const resolvedRows = resolveStructuredRows(config2);
|
|
669
673
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
670
674
|
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
671
675
|
});
|
|
@@ -743,7 +747,7 @@ function MessageBubble({
|
|
|
743
747
|
const isMedium = viewportSize === "medium";
|
|
744
748
|
const [showSources, setShowSources] = import_react5.default.useState(false);
|
|
745
749
|
const hasStructuredProductBlock = import_react5.default.useMemo(
|
|
746
|
-
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
|
|
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),
|
|
747
751
|
[message.content]
|
|
748
752
|
);
|
|
749
753
|
const structuredContent = import_react5.default.useMemo(
|
|
@@ -838,13 +842,10 @@ function MessageBubble({
|
|
|
838
842
|
const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
839
843
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
840
844
|
if (!isStreaming) {
|
|
841
|
-
raw = raw.replace(/(\{[\s\S]
|
|
845
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
846
|
+
if (match.includes("```")) return match;
|
|
842
847
|
if (!looksLikeStructuredPayload(match)) return match;
|
|
843
|
-
const
|
|
844
|
-
const prefix = raw.substring(0, index);
|
|
845
|
-
const openBlocks = (prefix.match(/```/g) || []).length;
|
|
846
|
-
if (openBlocks % 2 !== 0) return match;
|
|
847
|
-
const type = match.includes('"view"') || match.includes('"chartType"') ? "ui" : "json";
|
|
848
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
|
|
848
849
|
return `
|
|
849
850
|
|
|
850
851
|
\`\`\`${type}
|
package/dist/index.mjs
CHANGED
|
@@ -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)"
|
|
422
|
+
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
423
423
|
}
|
|
424
424
|
function stripMarkdownTables(raw) {
|
|
425
425
|
return raw.replace(
|
|
@@ -495,7 +495,7 @@ function extractStructuredPayload(raw) {
|
|
|
495
495
|
}
|
|
496
496
|
function isLikelyUiOnlyMessage(text) {
|
|
497
497
|
const trimmed = text.trim();
|
|
498
|
-
return trimmed.length === 0 || /^(chart data|table data|visualization|visual representation)\s*:?\s*$/i.test(trimmed);
|
|
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
|
for (const key of ["image", "img", "thumbnail"]) {
|
|
@@ -600,9 +600,9 @@ function normalizeTabularRows(rows, columns) {
|
|
|
600
600
|
}
|
|
601
601
|
function resolveStructuredRows(config) {
|
|
602
602
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
603
|
-
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
604
603
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
605
604
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
605
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
606
606
|
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
607
607
|
return [];
|
|
608
608
|
}
|
|
@@ -614,21 +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
|
-
if (config2.type === "products" || Array.isArray(config2.items)) {
|
|
627
|
+
if (config2.type === "products" || Array.isArray(config2.items) || hasProductLikeData) {
|
|
619
628
|
config2.view = "carousel";
|
|
620
|
-
|
|
621
|
-
} else if (typeof config2.type === "string" && ["pie", "bar", "line"].includes(config2.type) || typeof config2.chartType === "string" && ["pie", "bar", "line"].includes(config2.chartType)) {
|
|
622
|
-
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)) {
|
|
623
630
|
config2.view = "chart";
|
|
624
|
-
config2.chartType =
|
|
625
|
-
config2.data = config2.data || [];
|
|
631
|
+
config2.chartType = config2.chartType || config2.type || "bar";
|
|
626
632
|
} else {
|
|
627
633
|
config2.view = "table";
|
|
628
|
-
config2.data = Array.isArray(config2.data) ? config2.data : Array.isArray(config2) ? config2 : [];
|
|
629
634
|
}
|
|
630
635
|
}
|
|
631
|
-
const resolvedRows = resolveStructuredRows(config2);
|
|
632
636
|
const normalizedConfig = __spreadProps(__spreadValues({}, config2), {
|
|
633
637
|
data: normalizeTabularRows(resolvedRows, config2.columns)
|
|
634
638
|
});
|
|
@@ -706,7 +710,7 @@ function MessageBubble({
|
|
|
706
710
|
const isMedium = viewportSize === "medium";
|
|
707
711
|
const [showSources, setShowSources] = React5.useState(false);
|
|
708
712
|
const hasStructuredProductBlock = React5.useMemo(
|
|
709
|
-
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"\s*:\s*"(?:carousel|products)"/i.test(message.content),
|
|
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),
|
|
710
714
|
[message.content]
|
|
711
715
|
);
|
|
712
716
|
const structuredContent = React5.useMemo(
|
|
@@ -801,13 +805,10 @@ function MessageBubble({
|
|
|
801
805
|
const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
|
|
802
806
|
raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
|
|
803
807
|
if (!isStreaming) {
|
|
804
|
-
raw = raw.replace(/(\{[\s\S]
|
|
808
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
809
|
+
if (match.includes("```")) return match;
|
|
805
810
|
if (!looksLikeStructuredPayload(match)) return match;
|
|
806
|
-
const
|
|
807
|
-
const prefix = raw.substring(0, index);
|
|
808
|
-
const openBlocks = (prefix.match(/```/g) || []).length;
|
|
809
|
-
if (openBlocks % 2 !== 0) return match;
|
|
810
|
-
const type = match.includes('"view"') || match.includes('"chartType"') ? "ui" : "json";
|
|
811
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"') ? "ui" : "json";
|
|
811
812
|
return `
|
|
812
813
|
|
|
813
814
|
\`\`\`${type}
|
package/dist/server.js
CHANGED
|
@@ -3913,7 +3913,7 @@ 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_V6 -->";
|
|
3917
3917
|
const chartInstruction = `
|
|
3918
3918
|
|
|
3919
3919
|
${CHART_MARKER}
|
|
@@ -3921,16 +3921,16 @@ ${CHART_MARKER}
|
|
|
3921
3921
|
When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
|
|
3922
3922
|
|
|
3923
3923
|
1. VIEW SELECTION
|
|
3924
|
-
- Use "
|
|
3925
|
-
- Use "
|
|
3926
|
-
- Use "table" for
|
|
3924
|
+
- Use "carousel" for browsing specific products, items, or a catalog. This is the DEFAULT for "show me", "what are", or "find me" requests for a specific category (e.g. "beauty").
|
|
3925
|
+
- Use "chart" ONLY for numerical aggregations: distributions, percentages, counts, comparisons over time, or trends (e.g. "how many beauty products?").
|
|
3926
|
+
- Use "table" for detailed technical specifications, row-heavy comparisons, or when the user explicitly asks for a list/table format.
|
|
3927
3927
|
|
|
3928
3928
|
2. REQUIRED JSON SHAPE
|
|
3929
3929
|
{
|
|
3930
3930
|
"view": "chart" | "carousel" | "table",
|
|
3931
3931
|
"title": "Short heading",
|
|
3932
3932
|
"description": "One sentence describing the view",
|
|
3933
|
-
"chartType": "pie" | "bar" | "line",
|
|
3933
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
3934
3934
|
"xAxisKey": "label",
|
|
3935
3935
|
"dataKeys": ["value"],
|
|
3936
3936
|
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
@@ -3939,41 +3939,42 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
3939
3939
|
}
|
|
3940
3940
|
|
|
3941
3941
|
3. DATA RULES
|
|
3942
|
-
- Build the UI payload from retrieved context only. Do not invent products
|
|
3943
|
-
-
|
|
3944
|
-
- For
|
|
3945
|
-
-
|
|
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.
|
|
3942
|
+
- Build the UI payload from retrieved context only. Do not invent products or values.
|
|
3943
|
+
- For CAROUSEL: "data" must be a list of product objects (id, name, brand, price, image, link).
|
|
3944
|
+
- For CHART: "data" must be aggregated counts or sums. Never put raw product lists in a chart view.
|
|
3945
|
+
- If the user asks for "products in beauty", do NOT return a pie chart of categories; return a carousel of the beauty products themselves.
|
|
3950
3946
|
|
|
3951
3947
|
4. FORMAT RULES
|
|
3952
|
-
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
3953
|
-
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
3954
3948
|
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
3955
|
-
-
|
|
3956
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
3949
|
+
- Put natural language explanation before or after the block, not inside.
|
|
3957
3950
|
|
|
3958
|
-
5.
|
|
3959
|
-
User: "
|
|
3951
|
+
5. EXAMPLES
|
|
3952
|
+
User: "show me beauty products"
|
|
3953
|
+
Assistant:
|
|
3954
|
+
\`\`\`ui
|
|
3955
|
+
{
|
|
3956
|
+
"view": "carousel",
|
|
3957
|
+
"title": "Beauty Products",
|
|
3958
|
+
"description": "Browse our latest skincare and makeup collection.",
|
|
3959
|
+
"data": [
|
|
3960
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
|
|
3961
|
+
{ "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
|
|
3962
|
+
]
|
|
3963
|
+
}
|
|
3964
|
+
\`\`\`
|
|
3965
|
+
|
|
3966
|
+
User: "breakdown of products by category"
|
|
3960
3967
|
Assistant:
|
|
3961
3968
|
\`\`\`ui
|
|
3962
3969
|
{
|
|
3963
3970
|
"view": "chart",
|
|
3964
|
-
"title": "
|
|
3965
|
-
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
3971
|
+
"title": "Category Breakdown",
|
|
3966
3972
|
"chartType": "pie",
|
|
3967
3973
|
"xAxisKey": "label",
|
|
3968
3974
|
"dataKeys": ["value"],
|
|
3969
|
-
"insights": [
|
|
3970
|
-
"Beauty has the highest product count.",
|
|
3971
|
-
"Electronics has the largest number of in-stock products."
|
|
3972
|
-
],
|
|
3973
3975
|
"data": [
|
|
3974
|
-
{ "label": "Beauty", "value":
|
|
3975
|
-
{ "label": "
|
|
3976
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
3976
|
+
{ "label": "Beauty", "value": 15 },
|
|
3977
|
+
{ "label": "Fragrances", "value": 8 }
|
|
3977
3978
|
]
|
|
3978
3979
|
}
|
|
3979
3980
|
\`\`\``;
|
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.8",
|
|
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",
|
|
@@ -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)"
|
|
106
|
+
return /"?(view|chartType|type|data|items|rows|products|results)"?\s*:/.test(raw);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function stripMarkdownTables(raw: string): string {
|
|
@@ -195,7 +195,10 @@ function extractStructuredPayload(raw: string): { payload: string | null; text:
|
|
|
195
195
|
|
|
196
196
|
function isLikelyUiOnlyMessage(text: string): boolean {
|
|
197
197
|
const trimmed = text.trim();
|
|
198
|
-
return
|
|
198
|
+
return (
|
|
199
|
+
trimmed.length === 0 ||
|
|
200
|
+
/^(chart data|table data|visualization|visual representation|product catalog|product recommendations|catalog summary)\s*:?\s*$/i.test(trimmed)
|
|
201
|
+
);
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
// ─── Tiny helpers ─────────────────────────────────────────────────────────────
|
|
@@ -415,9 +418,9 @@ function normalizeTabularRows(
|
|
|
415
418
|
|
|
416
419
|
function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unknown>) {
|
|
417
420
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
418
|
-
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
419
421
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
420
422
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
423
|
+
if (Array.isArray(config.rows) && config.rows.length > 0) return config.rows;
|
|
421
424
|
if (Array.isArray(config.results) && config.results.length > 0) return config.results;
|
|
422
425
|
return [];
|
|
423
426
|
}
|
|
@@ -439,32 +442,47 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
439
442
|
.trim();
|
|
440
443
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
441
444
|
const config = { ...parsed } as Partial<RawUIConfig> & Record<string, unknown>;
|
|
445
|
+
const resolvedRows = resolveStructuredRows(config);
|
|
446
|
+
|
|
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
|
+
}
|
|
442
468
|
|
|
443
|
-
//
|
|
469
|
+
// 2. Auto-Detection: Fill in missing view
|
|
444
470
|
if (!config.view) {
|
|
445
|
-
if (config.type === 'products' || Array.isArray(config.items)) {
|
|
471
|
+
if (config.type === 'products' || Array.isArray(config.items) || hasProductLikeData) {
|
|
446
472
|
config.view = 'carousel';
|
|
447
|
-
config.data = config.data || config.items || [];
|
|
448
473
|
} else if (
|
|
474
|
+
hasAggregationLikeData ||
|
|
449
475
|
(typeof config.type === 'string' && ['pie', 'bar', 'line'].includes(config.type)) ||
|
|
450
476
|
(typeof config.chartType === 'string' && ['pie', 'bar', 'line'].includes(config.chartType))
|
|
451
477
|
) {
|
|
452
|
-
const resolvedChartType =
|
|
453
|
-
config.chartType === 'pie' || config.chartType === 'bar' || config.chartType === 'line'
|
|
454
|
-
? config.chartType
|
|
455
|
-
: config.type === 'pie' || config.type === 'bar' || config.type === 'line'
|
|
456
|
-
? config.type
|
|
457
|
-
: 'bar';
|
|
458
478
|
config.view = 'chart';
|
|
459
|
-
config.chartType =
|
|
460
|
-
|
|
479
|
+
config.chartType = (config.chartType as 'pie' | 'bar' | 'line') ||
|
|
480
|
+
(config.type as 'pie' | 'bar' | 'line') || 'bar';
|
|
461
481
|
} else {
|
|
462
482
|
config.view = 'table';
|
|
463
|
-
config.data = Array.isArray(config.data) ? config.data : (Array.isArray(config) ? config : []);
|
|
464
483
|
}
|
|
465
484
|
}
|
|
466
485
|
|
|
467
|
-
const resolvedRows = resolveStructuredRows(config);
|
|
468
486
|
const normalizedConfig: UIConfig = {
|
|
469
487
|
...(config as Omit<UIConfig, 'data'>),
|
|
470
488
|
data: normalizeTabularRows(resolvedRows, config.columns),
|
|
@@ -582,7 +600,8 @@ export function MessageBubble({
|
|
|
582
600
|
const isMedium = viewportSize === 'medium';
|
|
583
601
|
const [showSources, setShowSources] = React.useState(false);
|
|
584
602
|
const hasStructuredProductBlock = React.useMemo(
|
|
585
|
-
() => /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|type)"
|
|
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)),
|
|
586
605
|
[message.content],
|
|
587
606
|
);
|
|
588
607
|
const structuredContent = React.useMemo(
|
|
@@ -699,20 +718,16 @@ export function MessageBubble({
|
|
|
699
718
|
.replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, '$1\n'); // Remove blank lines between rows
|
|
700
719
|
|
|
701
720
|
// 2. HEALING: Detect naked JSON charts/products and wrap them if markers are missing
|
|
702
|
-
//
|
|
703
|
-
// to catch JSON that is not preceded by a newline (common in many LLM outputs).
|
|
721
|
+
// This handles models (like Llama 3.2) that often omit the ```chart markers.
|
|
704
722
|
if (!isStreaming) {
|
|
705
|
-
//
|
|
706
|
-
raw = raw.replace(/(\{[\s\S]
|
|
723
|
+
// Look for objects that look like chart or product data
|
|
724
|
+
raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
|
|
725
|
+
// Only wrap if not already in a code block
|
|
726
|
+
if (match.includes('```')) return match;
|
|
707
727
|
if (!looksLikeStructuredPayload(match)) return match;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const prefix = raw.substring(0, index);
|
|
712
|
-
const openBlocks = (prefix.match(/```/g) || []).length;
|
|
713
|
-
if (openBlocks % 2 !== 0) return match;
|
|
714
|
-
|
|
715
|
-
const type = match.includes('"view"') || match.includes('"chartType"') ? 'ui' : 'json';
|
|
728
|
+
const type = match.includes('"view"') || match.includes('"chartType"') || match.includes('"type": "pie"') || match.includes('"type":"pie"') || match.includes('"type": "bar"') || match.includes('"type":"bar"') || match.includes('"type": "line"') || match.includes('"type":"line"')
|
|
729
|
+
? 'ui'
|
|
730
|
+
: 'json';
|
|
716
731
|
return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
|
|
717
732
|
});
|
|
718
733
|
}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -101,22 +101,22 @@ 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_V6 -->';
|
|
105
105
|
const chartInstruction = `\n\n${CHART_MARKER}
|
|
106
106
|
### UNIVERSAL UI PROTOCOL
|
|
107
107
|
When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
|
|
108
108
|
|
|
109
109
|
1. VIEW SELECTION
|
|
110
|
-
- Use "
|
|
111
|
-
- Use "
|
|
112
|
-
- Use "table" for
|
|
110
|
+
- Use "carousel" for browsing specific products, items, or a catalog. This is the DEFAULT for "show me", "what are", or "find me" requests for a specific category (e.g. "beauty").
|
|
111
|
+
- Use "chart" ONLY for numerical aggregations: distributions, percentages, counts, comparisons over time, or trends (e.g. "how many beauty products?").
|
|
112
|
+
- Use "table" for detailed technical specifications, row-heavy comparisons, or when the user explicitly asks for a list/table format.
|
|
113
113
|
|
|
114
114
|
2. REQUIRED JSON SHAPE
|
|
115
115
|
{
|
|
116
116
|
"view": "chart" | "carousel" | "table",
|
|
117
117
|
"title": "Short heading",
|
|
118
118
|
"description": "One sentence describing the view",
|
|
119
|
-
"chartType": "pie" | "bar" | "line",
|
|
119
|
+
"chartType": "pie" | "bar" | "line", // Required if view is "chart"
|
|
120
120
|
"xAxisKey": "label",
|
|
121
121
|
"dataKeys": ["value"],
|
|
122
122
|
"columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
|
|
@@ -125,41 +125,42 @@ When the user asks for a visual representation, comparison, distribution, breakd
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
3. DATA RULES
|
|
128
|
-
- Build the UI payload from retrieved context only. Do not invent products
|
|
129
|
-
-
|
|
130
|
-
- For
|
|
131
|
-
-
|
|
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.
|
|
128
|
+
- Build the UI payload from retrieved context only. Do not invent products or values.
|
|
129
|
+
- For CAROUSEL: "data" must be a list of product objects (id, name, brand, price, image, link).
|
|
130
|
+
- For CHART: "data" must be aggregated counts or sums. Never put raw product lists in a chart view.
|
|
131
|
+
- If the user asks for "products in beauty", do NOT return a pie chart of categories; return a carousel of the beauty products themselves.
|
|
136
132
|
|
|
137
133
|
4. FORMAT RULES
|
|
138
|
-
- Never use ASCII art, markdown tables as the primary answer, or text-only fake charts when a UI block is requested.
|
|
139
|
-
- Put any short natural-language explanation before or after the \`\`\`ui\`\`\` block, but keep it concise.
|
|
140
134
|
- Return valid JSON inside the \`\`\`ui\`\`\` block.
|
|
141
|
-
-
|
|
142
|
-
- Do not prefix the JSON with labels like "Table View", "Alternative", or explanatory text inside the code block.
|
|
135
|
+
- Put natural language explanation before or after the block, not inside.
|
|
143
136
|
|
|
144
|
-
5.
|
|
145
|
-
User: "
|
|
137
|
+
5. EXAMPLES
|
|
138
|
+
User: "show me beauty products"
|
|
139
|
+
Assistant:
|
|
140
|
+
\`\`\`ui
|
|
141
|
+
{
|
|
142
|
+
"view": "carousel",
|
|
143
|
+
"title": "Beauty Products",
|
|
144
|
+
"description": "Browse our latest skincare and makeup collection.",
|
|
145
|
+
"data": [
|
|
146
|
+
{ "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
|
|
147
|
+
{ "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
\`\`\`
|
|
151
|
+
|
|
152
|
+
User: "breakdown of products by category"
|
|
146
153
|
Assistant:
|
|
147
154
|
\`\`\`ui
|
|
148
155
|
{
|
|
149
156
|
"view": "chart",
|
|
150
|
-
"title": "
|
|
151
|
-
"description": "Pie chart showing product count by category with stock signals preserved per slice.",
|
|
157
|
+
"title": "Category Breakdown",
|
|
152
158
|
"chartType": "pie",
|
|
153
159
|
"xAxisKey": "label",
|
|
154
160
|
"dataKeys": ["value"],
|
|
155
|
-
"insights": [
|
|
156
|
-
"Beauty has the highest product count.",
|
|
157
|
-
"Electronics has the largest number of in-stock products."
|
|
158
|
-
],
|
|
159
161
|
"data": [
|
|
160
|
-
{ "label": "Beauty", "value":
|
|
161
|
-
{ "label": "
|
|
162
|
-
{ "label": "Home", "value": 5, "inStockCount": 2, "outOfStockCount": 3 }
|
|
162
|
+
{ "label": "Beauty", "value": 15 },
|
|
163
|
+
{ "label": "Fragrances", "value": 8 }
|
|
163
164
|
]
|
|
164
165
|
}
|
|
165
166
|
\`\`\``;
|