@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.
@@ -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 = "<!-- UI_PROTOCOL_V5 -->";
2262
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
2263
2263
  const chartInstruction = `
2264
2264
 
2265
2265
  ${CHART_MARKER}
2266
- ### UNIVERSAL UI PROTOCOL
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.
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 "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
2271
- - Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
2272
- - Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
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
- "xAxisKey": "label",
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. Do not invent products, categories, or stock values.
2289
- - Aggregate raw product rows when the user asks for a chart.
2290
- - For charts, every row in "data" must be flat JSON with primitive values only.
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
- - 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
- - 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: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
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": "chart",
2310
- "title": "Product Distribution Across Categories",
2311
- "description": "Pie chart showing product count by category with stock signals preserved per slice.",
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
- { "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
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;
@@ -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 = "<!-- UI_PROTOCOL_V5 -->";
3825
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
3826
3826
  const chartInstruction = `
3827
3827
 
3828
3828
  ${CHART_MARKER}
3829
- ### UNIVERSAL UI PROTOCOL
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.
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 "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
3834
- - Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
3835
- - Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
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
- "xAxisKey": "label",
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. Do not invent products, categories, or stock values.
3852
- - Aggregate raw product rows when the user asks for a chart.
3853
- - For charts, every row in "data" must be flat JSON with primitive values only.
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
- - 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
- - 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: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
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": "chart",
3873
- "title": "Product Distribution Across Categories",
3874
- "description": "Pie chart showing product count by category with stock signals preserved per slice.",
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
- { "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
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;
@@ -9,7 +9,7 @@ import {
9
9
  sseFrame,
10
10
  sseMetaFrame,
11
11
  sseTextFrame
12
- } from "../chunk-5K7EU4KW.mjs";
12
+ } from "../chunk-Q7FLITXB.mjs";
13
13
  import "../chunk-YLTMFW4M.mjs";
14
14
  import "../chunk-X4TOT24V.mjs";
15
15
  export {
package/dist/index.js CHANGED
@@ -405,7 +405,7 @@ function sanitizeJson(raw) {
405
405
  return `: ${result}`;
406
406
  }
407
407
  );
408
- s = s.replace(/\/\/[^\n]*/g, "");
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
- const resolvedRows2 = resolveStructuredRows(config2);
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
- config2.data = resolvedRows2;
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 = resolvedChartType;
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*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
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
- if (!looksLikeStructuredPayload(match)) return match;
846
- 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";
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
- ${match.trim()}
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(/\/\/[^\n]*/g, "");
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
- const resolvedRows2 = resolveStructuredRows(config2);
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
- config2.data = resolvedRows2;
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 = resolvedChartType;
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*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
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
- if (!looksLikeStructuredPayload(match)) return match;
809
- 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";
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
- ${match.trim()}
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 = "<!-- UI_PROTOCOL_V5 -->";
3916
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
3917
3917
  const chartInstruction = `
3918
3918
 
3919
3919
  ${CHART_MARKER}
3920
- ### UNIVERSAL UI PROTOCOL
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.
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 "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
3925
- - Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
3926
- - Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
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
- "xAxisKey": "label",
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. Do not invent products, categories, or stock values.
3943
- - Aggregate raw product rows when the user asks for a chart.
3944
- - For charts, every row in "data" must be flat JSON with primitive values only.
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
- - 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
- - 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: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
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": "chart",
3964
- "title": "Product Distribution Across Categories",
3965
- "description": "Pie chart showing product count by category with stock signals preserved per slice.",
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
- { "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
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
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-5K7EU4KW.mjs";
42
+ } from "./chunk-Q7FLITXB.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.6.7",
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(/\/\/[^\n]*/g, '');
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
- // Smart Auto-Detection for legacy or simpler formats
447
- if (!config.view) {
448
- const resolvedRows = resolveStructuredRows(config);
449
- const hasProductLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === 'object' && resolvedRows[0] !== null &&
450
- ('price' in resolvedRows[0] || 'brand' in resolvedRows[0] || 'image' in resolvedRows[0]);
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
- if (config.type === 'products' || Array.isArray(config.items) || (Array.isArray(config.data) && hasProductLikeData)) {
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 = resolvedChartType;
467
- config.data = resolvedRows;
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 ```chart markers.
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*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
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
- if (!looksLikeStructuredPayload(match)) return match;
717
- 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"')
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
- return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
736
+
737
+ return `\n\n\`\`\`${type}\n${trimmedBody}\n\`\`\`\n\n`;
721
738
  });
722
739
  }
723
740
 
@@ -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 = '<!-- UI_PROTOCOL_V5 -->';
104
+ const CHART_MARKER = '<!-- UI_PROTOCOL_V7 -->';
105
105
  const chartInstruction = `\n\n${CHART_MARKER}
106
- ### UNIVERSAL UI PROTOCOL
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.
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 "chart" for distributions, percentages, counts, comparisons, trends, or "show me in a chart".
111
- - Use "carousel" for browseable product recommendations or when the user asks to see products/items/cards.
112
- - Use "table" for tabular/raw detail, explicit table/list requests, or when there are many attributes to compare.
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
- "xAxisKey": "label",
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. Do not invent products, categories, or stock values.
129
- - Aggregate raw product rows when the user asks for a chart.
130
- - For charts, every row in "data" must be flat JSON with primitive values only.
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
- - 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
- - 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: "provide me the distribution of products across categories and highlight which ones are in stock in a pie chart"
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": "chart",
150
- "title": "Product Distribution Across Categories",
151
- "description": "Pie chart showing product count by category with stock signals preserved per slice.",
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
- { "label": "Beauty", "value": 12, "inStockCount": 9, "outOfStockCount": 3 },
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)) {