@retrivora-ai/rag-engine 1.6.8 → 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,17 +2259,18 @@ var Pipeline = class {
2259
2259
  async initialize() {
2260
2260
  var _a, _b;
2261
2261
  if (this.initialised) return;
2262
- const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
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 "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.
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
  {
@@ -2277,52 +2278,31 @@ When the user asks for a visual representation, comparison, distribution, breakd
2277
2278
  "title": "Short heading",
2278
2279
  "description": "One sentence describing the view",
2279
2280
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
2280
- "xAxisKey": "label",
2281
- "dataKeys": ["value"],
2282
- "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
2283
- "insights": ["Short takeaway 1", "Short takeaway 2"],
2284
- "data": []
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 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.
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 }.
2292
2288
 
2293
2289
  4. FORMAT RULES
2294
- - Return valid JSON inside the \`\`\`ui\`\`\` block.
2295
- - Put natural language explanation before or after the block, not inside.
2290
+ - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
2291
+ - Place natural language text OUTSIDE the block.
2296
2292
 
2297
- 5. EXAMPLES
2298
- User: "show me beauty products"
2299
- Assistant:
2293
+ 5. EXAMPLE
2294
+ User: "find me beauty products"
2295
+ Assistant: "I found several beauty products in our catalog:"
2300
2296
  \`\`\`ui
2301
2297
  {
2302
2298
  "view": "carousel",
2303
2299
  "title": "Beauty Products",
2304
- "description": "Browse our latest skincare and makeup collection.",
2300
+ "description": "Latest skincare and makeup items.",
2305
2301
  "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": "..." }
2302
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
2308
2303
  ]
2309
2304
  }
2310
2305
  \`\`\`
2311
-
2312
- User: "breakdown of products by category"
2313
- Assistant:
2314
- \`\`\`ui
2315
- {
2316
- "view": "chart",
2317
- "title": "Category Breakdown",
2318
- "chartType": "pie",
2319
- "xAxisKey": "label",
2320
- "dataKeys": ["value"],
2321
- "data": [
2322
- { "label": "Beauty", "value": 15 },
2323
- { "label": "Fragrances", "value": 8 }
2324
- ]
2325
- }
2326
2306
  \`\`\``;
2327
2307
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
2328
2308
  this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
@@ -3822,17 +3822,18 @@ var Pipeline = class {
3822
3822
  async initialize() {
3823
3823
  var _a, _b;
3824
3824
  if (this.initialised) return;
3825
- const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
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 "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.
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
  {
@@ -3840,52 +3841,31 @@ When the user asks for a visual representation, comparison, distribution, breakd
3840
3841
  "title": "Short heading",
3841
3842
  "description": "One sentence describing the view",
3842
3843
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
3843
- "xAxisKey": "label",
3844
- "dataKeys": ["value"],
3845
- "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3846
- "insights": ["Short takeaway 1", "Short takeaway 2"],
3847
- "data": []
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 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.
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 }.
3855
3851
 
3856
3852
  4. FORMAT RULES
3857
- - Return valid JSON inside the \`\`\`ui\`\`\` block.
3858
- - Put natural language explanation before or after the block, not inside.
3853
+ - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
3854
+ - Place natural language text OUTSIDE the block.
3859
3855
 
3860
- 5. EXAMPLES
3861
- User: "show me beauty products"
3862
- Assistant:
3856
+ 5. EXAMPLE
3857
+ User: "find me beauty products"
3858
+ Assistant: "I found several beauty products in our catalog:"
3863
3859
  \`\`\`ui
3864
3860
  {
3865
3861
  "view": "carousel",
3866
3862
  "title": "Beauty Products",
3867
- "description": "Browse our latest skincare and makeup collection.",
3863
+ "description": "Latest skincare and makeup items.",
3868
3864
  "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": "..." }
3865
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
3871
3866
  ]
3872
3867
  }
3873
3868
  \`\`\`
3874
-
3875
- User: "breakdown of products by category"
3876
- Assistant:
3877
- \`\`\`ui
3878
- {
3879
- "view": "chart",
3880
- "title": "Category Breakdown",
3881
- "chartType": "pie",
3882
- "xAxisKey": "label",
3883
- "dataKeys": ["value"],
3884
- "data": [
3885
- { "label": "Beauty", "value": 15 },
3886
- { "label": "Fragrances", "value": 8 }
3887
- ]
3888
- }
3889
3869
  \`\`\``;
3890
3870
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
3891
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-J7BCIPO2.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(
@@ -841,15 +841,16 @@ function MessageBubble({
841
841
  let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
842
842
  const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
843
843
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
844
- if (!isStreaming) {
845
- 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) => {
846
846
  if (match.includes("```")) return match;
847
- if (!looksLikeStructuredPayload(match)) return match;
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";
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";
849
850
  return `
850
851
 
851
852
  \`\`\`${type}
852
- ${match.trim()}
853
+ ${trimmedBody}
853
854
  \`\`\`
854
855
 
855
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(
@@ -804,15 +804,16 @@ function MessageBubble({
804
804
  let raw = structuredContent.payload ? structuredContent.text : cleanContent || message.content;
805
805
  const hasStructuredUiBlock = Boolean(structuredContent.payload) || /```(?:ui|json|chart)\s*[\s\S]*?"(?:view|chartType|type|data|items|rows|products|results)"\s*:/i.test(raw);
806
806
  raw = raw.replace(/([^\n])\n\|/g, "$1\n\n|").replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, "$1\n");
807
- if (!isStreaming) {
808
- 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) => {
809
809
  if (match.includes("```")) return match;
810
- if (!looksLikeStructuredPayload(match)) return match;
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";
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";
812
813
  return `
813
814
 
814
815
  \`\`\`${type}
815
- ${match.trim()}
816
+ ${trimmedBody}
816
817
  \`\`\`
817
818
 
818
819
  `;
package/dist/server.js CHANGED
@@ -3913,17 +3913,18 @@ var Pipeline = class {
3913
3913
  async initialize() {
3914
3914
  var _a, _b;
3915
3915
  if (this.initialised) return;
3916
- const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
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 "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.
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
  {
@@ -3931,52 +3932,31 @@ When the user asks for a visual representation, comparison, distribution, breakd
3931
3932
  "title": "Short heading",
3932
3933
  "description": "One sentence describing the view",
3933
3934
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
3934
- "xAxisKey": "label",
3935
- "dataKeys": ["value"],
3936
- "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3937
- "insights": ["Short takeaway 1", "Short takeaway 2"],
3938
- "data": []
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 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.
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 }.
3946
3942
 
3947
3943
  4. FORMAT RULES
3948
- - Return valid JSON inside the \`\`\`ui\`\`\` block.
3949
- - Put natural language explanation before or after the block, not inside.
3944
+ - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
3945
+ - Place natural language text OUTSIDE the block.
3950
3946
 
3951
- 5. EXAMPLES
3952
- User: "show me beauty products"
3953
- Assistant:
3947
+ 5. EXAMPLE
3948
+ User: "find me beauty products"
3949
+ Assistant: "I found several beauty products in our catalog:"
3954
3950
  \`\`\`ui
3955
3951
  {
3956
3952
  "view": "carousel",
3957
3953
  "title": "Beauty Products",
3958
- "description": "Browse our latest skincare and makeup collection.",
3954
+ "description": "Latest skincare and makeup items.",
3959
3955
  "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": "..." }
3956
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
3962
3957
  ]
3963
3958
  }
3964
3959
  \`\`\`
3965
-
3966
- User: "breakdown of products by category"
3967
- Assistant:
3968
- \`\`\`ui
3969
- {
3970
- "view": "chart",
3971
- "title": "Category Breakdown",
3972
- "chartType": "pie",
3973
- "xAxisKey": "label",
3974
- "dataKeys": ["value"],
3975
- "data": [
3976
- { "label": "Beauty", "value": 15 },
3977
- { "label": "Fragrances", "value": 8 }
3978
- ]
3979
- }
3980
3960
  \`\`\``;
3981
3961
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
3982
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-J7BCIPO2.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.8",
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 {
@@ -718,17 +718,23 @@ export function MessageBubble({
718
718
  .replace(/(\|[^\n]*\|)\n\n+(?=\|)/g, '$1\n'); // Remove blank lines between rows
719
719
 
720
720
  // 2. HEALING: Detect naked JSON charts/products and wrap them if markers are missing
721
- // This handles models (like Llama 3.2) that often omit the ```chart markers.
722
- if (!isStreaming) {
721
+ // This handles models (like Llama 3.2) that often omit the ```ui markers.
722
+ if (!isStreaming && !hasStructuredUiBlock) {
723
723
  // Look for objects that look like chart or product data
724
- raw = raw.replace(/(?:^|\n)\s*\{[\s\S]*\}\s*(?:\n|$)/g, (match) => {
724
+ raw = raw.replace(/(?:^|\n)\s*(\{[\s\S]+?\})(?:\s*\n|$)/g, (match, body) => {
725
725
  // Only wrap if not already in a code block
726
726
  if (match.includes('```')) return match;
727
- if (!looksLikeStructuredPayload(match)) return match;
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"')
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"')
729
734
  ? 'ui'
730
735
  : 'json';
731
- return `\n\n\`\`\`${type}\n${match.trim()}\n\`\`\`\n\n`;
736
+
737
+ return `\n\n\`\`\`${type}\n${trimmedBody}\n\`\`\`\n\n`;
732
738
  });
733
739
  }
734
740
 
@@ -101,15 +101,16 @@ 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_V6 -->';
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 "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.
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
  {
@@ -117,52 +118,31 @@ When the user asks for a visual representation, comparison, distribution, breakd
117
118
  "title": "Short heading",
118
119
  "description": "One sentence describing the view",
119
120
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
120
- "xAxisKey": "label",
121
- "dataKeys": ["value"],
122
- "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
123
- "insights": ["Short takeaway 1", "Short takeaway 2"],
124
- "data": []
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 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.
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 }.
132
128
 
133
129
  4. FORMAT RULES
134
- - Return valid JSON inside the \`\`\`ui\`\`\` block.
135
- - Put natural language explanation before or after the block, not inside.
130
+ - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
131
+ - Place natural language text OUTSIDE the block.
136
132
 
137
- 5. EXAMPLES
138
- User: "show me beauty products"
139
- Assistant:
133
+ 5. EXAMPLE
134
+ User: "find me beauty products"
135
+ Assistant: "I found several beauty products in our catalog:"
140
136
  \`\`\`ui
141
137
  {
142
138
  "view": "carousel",
143
139
  "title": "Beauty Products",
144
- "description": "Browse our latest skincare and makeup collection.",
140
+ "description": "Latest skincare and makeup items.",
145
141
  "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": "..." }
142
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." }
148
143
  ]
149
144
  }
150
145
  \`\`\`
151
-
152
- User: "breakdown of products by category"
153
- Assistant:
154
- \`\`\`ui
155
- {
156
- "view": "chart",
157
- "title": "Category Breakdown",
158
- "chartType": "pie",
159
- "xAxisKey": "label",
160
- "dataKeys": ["value"],
161
- "data": [
162
- { "label": "Beauty", "value": 15 },
163
- { "label": "Fragrances", "value": 8 }
164
- ]
165
- }
166
146
  \`\`\``;
167
147
 
168
148
  if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {