@retrivora-ai/rag-engine 1.7.1 → 1.7.3

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,18 +2259,17 @@ var Pipeline = class {
2259
2259
  async initialize() {
2260
2260
  var _a, _b;
2261
2261
  if (this.initialised) return;
2262
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
2262
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
2263
2263
  const chartInstruction = `
2264
2264
 
2265
2265
  ${CHART_MARKER}
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.
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.
2269
2268
 
2270
2269
  1. VIEW SELECTION
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.
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.
2274
2273
 
2275
2274
  2. REQUIRED JSON SHAPE
2276
2275
  {
@@ -2278,31 +2277,52 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
2278
2277
  "title": "Short heading",
2279
2278
  "description": "One sentence describing the view",
2280
2279
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
2281
- "data": [] // Array of objects
2280
+ "xAxisKey": "label",
2281
+ "dataKeys": ["value"],
2282
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
2283
+ "insights": ["Short takeaway 1", "Short takeaway 2"],
2284
+ "data": []
2282
2285
  }
2283
2286
 
2284
2287
  3. DATA RULES
2285
- - Build the UI payload from retrieved context and metadata.
2286
- - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
2287
- - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
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.
2288
2292
 
2289
2293
  4. FORMAT RULES
2290
- - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
2291
- - Place natural language text OUTSIDE the block.
2294
+ - Return valid JSON inside the \`\`\`ui\`\`\` block.
2295
+ - Put natural language explanation before or after the block, not inside.
2292
2296
 
2293
- 5. EXAMPLE
2294
- User: "find me beauty products"
2295
- Assistant: "I found several beauty products in our catalog:"
2297
+ 5. EXAMPLES
2298
+ User: "show me beauty products"
2299
+ Assistant:
2296
2300
  \`\`\`ui
2297
2301
  {
2298
2302
  "view": "carousel",
2299
2303
  "title": "Beauty Products",
2300
- "description": "Latest skincare and makeup items.",
2304
+ "description": "Browse our latest skincare and makeup collection.",
2301
2305
  "data": [
2302
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
2306
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
2307
+ { "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
2303
2308
  ]
2304
2309
  }
2305
2310
  \`\`\`
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
+ }
2306
2326
  \`\`\``;
2307
2327
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
2308
2328
  this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
@@ -2511,8 +2531,7 @@ Assistant: "I found several beauty products in our catalog:"
2511
2531
  sources = sources.slice(0, topK);
2512
2532
  }
2513
2533
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2514
- Content: ${m.content}
2515
- Metadata: ${JSON.stringify(m.metadata)}`).join("\n\n---\n\n") : "No relevant context found.";
2534
+ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
2516
2535
  if (graphData && graphData.nodes.length > 0) {
2517
2536
  const graphContext = graphData.nodes.map(
2518
2537
  (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
@@ -2864,6 +2883,351 @@ var DocumentParser = class {
2864
2883
  }
2865
2884
  };
2866
2885
 
2886
+ // src/utils/UITransformer.ts
2887
+ var UITransformer = class {
2888
+ /**
2889
+ * Main transformation method
2890
+ * Analyzes user query and retrieved data to determine the best visualization format
2891
+ *
2892
+ * @param userQuery - The original user question/query
2893
+ * @param retrievedData - Vector database retrieval results
2894
+ * @returns Structured JSON response for UI rendering
2895
+ */
2896
+ static transform(userQuery, retrievedData) {
2897
+ if (!retrievedData || retrievedData.length === 0) {
2898
+ return this.createTextResponse("No data available", "No relevant data found for your query.");
2899
+ }
2900
+ const analysis = this.analyzeData(userQuery, retrievedData);
2901
+ switch (analysis.suggestedType) {
2902
+ case "product_carousel":
2903
+ return this.transformToProductCarousel(retrievedData);
2904
+ case "pie_chart":
2905
+ return this.transformToPieChart(retrievedData);
2906
+ case "bar_chart":
2907
+ return this.transformToBarChart(retrievedData);
2908
+ case "line_chart":
2909
+ return this.transformToLineChart(retrievedData);
2910
+ case "table":
2911
+ return this.transformToTable(retrievedData);
2912
+ default:
2913
+ return this.transformToText(retrievedData);
2914
+ }
2915
+ }
2916
+ /**
2917
+ * Analyzes the data and query to suggest the best visualization type
2918
+ */
2919
+ static analyzeData(query, data) {
2920
+ const queryLower = query.toLowerCase();
2921
+ const hasProducts = data.some(
2922
+ (item) => this.isProductData(item)
2923
+ );
2924
+ const hasTimeSeries = data.some(
2925
+ (item) => this.isTimeSeriesData(item)
2926
+ );
2927
+ const hasCategories = this.detectCategories(data).length > 0;
2928
+ const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
2929
+ const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
2930
+ const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
2931
+ if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
2932
+ return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
2933
+ }
2934
+ if (isTrendQuery && hasTimeSeries) {
2935
+ return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
2936
+ }
2937
+ if (isDistributionQuery && hasCategories) {
2938
+ return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
2939
+ }
2940
+ if (isComparisonQuery && hasCategories && data.length > 2) {
2941
+ return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
2942
+ }
2943
+ if (data.length > 5 && this.hasMultipleFields(data)) {
2944
+ return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
2945
+ }
2946
+ return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
2947
+ }
2948
+ /**
2949
+ * Transform data to product carousel format
2950
+ */
2951
+ static transformToProductCarousel(data) {
2952
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
2953
+ return {
2954
+ type: "product_carousel",
2955
+ title: "Recommended Products",
2956
+ description: `Found ${products.length} relevant products`,
2957
+ data: products
2958
+ };
2959
+ }
2960
+ /**
2961
+ * Transform data to pie chart format
2962
+ */
2963
+ static transformToPieChart(data) {
2964
+ const categories = this.detectCategories(data);
2965
+ const categoryData = this.aggregateByCategory(data, categories);
2966
+ const pieData = Object.entries(categoryData).map(([label, count]) => ({
2967
+ label,
2968
+ value: count,
2969
+ inStock: this.checkStockStatus(label, data)
2970
+ }));
2971
+ return {
2972
+ type: "pie_chart",
2973
+ title: "Distribution by Category",
2974
+ description: `Showing breakdown across ${categories.length} categories`,
2975
+ data: pieData
2976
+ };
2977
+ }
2978
+ /**
2979
+ * Transform data to bar chart format
2980
+ */
2981
+ static transformToBarChart(data) {
2982
+ const categories = this.detectCategories(data);
2983
+ const categoryData = this.aggregateByCategory(data, categories);
2984
+ const barData = Object.entries(categoryData).map(([category, value]) => ({
2985
+ category,
2986
+ value,
2987
+ inStock: this.checkStockStatus(category, data)
2988
+ }));
2989
+ return {
2990
+ type: "bar_chart",
2991
+ title: "Comparison by Category",
2992
+ description: `Comparing ${categories.length} categories`,
2993
+ data: barData
2994
+ };
2995
+ }
2996
+ /**
2997
+ * Transform data to line chart format
2998
+ */
2999
+ static transformToLineChart(data) {
3000
+ const timePoints = this.extractTimeSeriesData(data);
3001
+ const lineData = timePoints.map((point) => ({
3002
+ timestamp: point.timestamp,
3003
+ value: point.value,
3004
+ label: point.label
3005
+ }));
3006
+ return {
3007
+ type: "line_chart",
3008
+ title: "Trend Over Time",
3009
+ description: `Showing ${lineData.length} data points`,
3010
+ data: lineData
3011
+ };
3012
+ }
3013
+ /**
3014
+ * Transform data to table format
3015
+ */
3016
+ static transformToTable(data) {
3017
+ const columns = this.extractTableColumns(data);
3018
+ const rows = data.map((item) => this.extractTableRow(item, columns));
3019
+ const tableData = {
3020
+ columns,
3021
+ rows
3022
+ };
3023
+ return {
3024
+ type: "table",
3025
+ title: "Detailed Results",
3026
+ description: `Showing ${data.length} results`,
3027
+ data: tableData
3028
+ };
3029
+ }
3030
+ /**
3031
+ * Transform data to text format (fallback)
3032
+ */
3033
+ static transformToText(data) {
3034
+ const textContent = data.map((item) => item.content).join("\n\n");
3035
+ return this.createTextResponse(
3036
+ "Search Results",
3037
+ textContent,
3038
+ `Found ${data.length} relevant results`
3039
+ );
3040
+ }
3041
+ /**
3042
+ * Helper: Create text response
3043
+ */
3044
+ static createTextResponse(title, content, description) {
3045
+ return {
3046
+ type: "text",
3047
+ title,
3048
+ description,
3049
+ data: { content }
3050
+ };
3051
+ }
3052
+ /**
3053
+ * Helper: Check if data item is product-related
3054
+ */
3055
+ static isProductData(item) {
3056
+ const content = (item.content || "").toLowerCase();
3057
+ const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
3058
+ return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3059
+ (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
3060
+ );
3061
+ }
3062
+ /**
3063
+ * Helper: Check if data contains time series
3064
+ */
3065
+ static isTimeSeriesData(item) {
3066
+ const content = (item.content || "").toLowerCase();
3067
+ const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
3068
+ return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3069
+ (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
3070
+ );
3071
+ }
3072
+ /**
3073
+ * Helper: Extract product information from vector match
3074
+ */
3075
+ static extractProductInfo(item) {
3076
+ const meta = item.metadata || {};
3077
+ if (meta.name || meta.product) {
3078
+ return {
3079
+ id: item.id,
3080
+ name: meta.name || meta.product,
3081
+ price: meta.price,
3082
+ image: meta.image || meta.imageUrl,
3083
+ brand: meta.brand,
3084
+ description: item.content,
3085
+ inStock: this.determineStockStatus(item)
3086
+ };
3087
+ }
3088
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
3089
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
3090
+ if (nameMatch) {
3091
+ return {
3092
+ id: item.id,
3093
+ name: nameMatch[1],
3094
+ price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
3095
+ description: item.content,
3096
+ inStock: this.determineStockStatus(item)
3097
+ };
3098
+ }
3099
+ return null;
3100
+ }
3101
+ /**
3102
+ * Helper: Detect categories in data
3103
+ */
3104
+ static detectCategories(data) {
3105
+ const categories = /* @__PURE__ */ new Set();
3106
+ data.forEach((item) => {
3107
+ const meta = item.metadata || {};
3108
+ if (meta.category) {
3109
+ categories.add(String(meta.category));
3110
+ }
3111
+ if (meta.type) {
3112
+ categories.add(String(meta.type));
3113
+ }
3114
+ if (meta.tag) {
3115
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
3116
+ tags.forEach((t) => categories.add(String(t)));
3117
+ }
3118
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
3119
+ if (categoryMatch) {
3120
+ categories.add(categoryMatch[1].trim());
3121
+ }
3122
+ });
3123
+ return Array.from(categories);
3124
+ }
3125
+ /**
3126
+ * Helper: Aggregate data by category
3127
+ */
3128
+ static aggregateByCategory(data, categories) {
3129
+ const result = {};
3130
+ categories.forEach((cat) => {
3131
+ result[cat] = 0;
3132
+ });
3133
+ data.forEach((item) => {
3134
+ const meta = item.metadata || {};
3135
+ const itemCategory = meta.category || meta.type || "Other";
3136
+ if (result.hasOwnProperty(itemCategory)) {
3137
+ result[itemCategory]++;
3138
+ } else {
3139
+ result["Other"] = (result["Other"] || 0) + 1;
3140
+ }
3141
+ });
3142
+ return result;
3143
+ }
3144
+ /**
3145
+ * Helper: Extract time series data
3146
+ */
3147
+ static extractTimeSeriesData(data) {
3148
+ return data.map((item) => {
3149
+ const meta = item.metadata || {};
3150
+ return {
3151
+ timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
3152
+ value: meta.value || item.score || 0,
3153
+ label: meta.label || item.content.substring(0, 50)
3154
+ };
3155
+ });
3156
+ }
3157
+ /**
3158
+ * Helper: Extract table columns
3159
+ */
3160
+ static extractTableColumns(data) {
3161
+ const columnSet = /* @__PURE__ */ new Set();
3162
+ columnSet.add("Content");
3163
+ data.forEach((item) => {
3164
+ Object.keys(item.metadata || {}).forEach((key) => {
3165
+ columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
3166
+ });
3167
+ });
3168
+ return Array.from(columnSet);
3169
+ }
3170
+ /**
3171
+ * Helper: Extract table row
3172
+ */
3173
+ static extractTableRow(item, columns) {
3174
+ const meta = item.metadata || {};
3175
+ return columns.map((col) => {
3176
+ if (col === "Content") {
3177
+ return item.content.substring(0, 100);
3178
+ }
3179
+ const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
3180
+ const value = meta[metaKey];
3181
+ return value !== void 0 ? String(value) : "";
3182
+ });
3183
+ }
3184
+ /**
3185
+ * Helper: Check stock status
3186
+ */
3187
+ static checkStockStatus(category, data) {
3188
+ const item = data.find((d) => {
3189
+ const meta = d.metadata || {};
3190
+ return meta.category === category || meta.type === category;
3191
+ });
3192
+ return this.determineStockStatus(item || {});
3193
+ }
3194
+ /**
3195
+ * Helper: Determine if item is in stock
3196
+ */
3197
+ static determineStockStatus(item) {
3198
+ const meta = item.metadata || {};
3199
+ if (meta.inStock !== void 0) {
3200
+ return Boolean(meta.inStock);
3201
+ }
3202
+ if (meta.stock !== void 0) {
3203
+ return Boolean(meta.stock);
3204
+ }
3205
+ if (meta.available !== void 0) {
3206
+ return Boolean(meta.available);
3207
+ }
3208
+ const content = (item.content || "").toLowerCase();
3209
+ if (content.includes("out of stock") || content.includes("unavailable")) {
3210
+ return false;
3211
+ }
3212
+ if (content.includes("in stock") || content.includes("available")) {
3213
+ return true;
3214
+ }
3215
+ return true;
3216
+ }
3217
+ /**
3218
+ * Helper: Check if data has multiple fields
3219
+ */
3220
+ static hasMultipleFields(data) {
3221
+ const fieldCount = /* @__PURE__ */ new Set();
3222
+ data.forEach((item) => {
3223
+ Object.keys(item.metadata || {}).forEach((key) => {
3224
+ fieldCount.add(key);
3225
+ });
3226
+ });
3227
+ return fieldCount.size > 2;
3228
+ }
3229
+ };
3230
+
2867
3231
  // src/handlers/index.ts
2868
3232
  function sseFrame(payload) {
2869
3233
  return `data: ${JSON.stringify(payload)}
@@ -2878,6 +3242,11 @@ function sseTextFrame(text) {
2878
3242
  function sseMetaFrame(meta) {
2879
3243
  return `data: ${JSON.stringify(__spreadValues({ type: "metadata" }, meta != null ? meta : {}))}
2880
3244
 
3245
+ `;
3246
+ }
3247
+ function sseUIFrame(uiTransformation) {
3248
+ return `data: ${JSON.stringify(__spreadValues({ type: "ui_transformation" }, uiTransformation != null ? uiTransformation : {}))}
3249
+
2881
3250
  `;
2882
3251
  }
2883
3252
  function sseErrorFrame(message) {
@@ -2941,6 +3310,15 @@ function createStreamHandler(configOrPlugin) {
2941
3310
  enqueue(sseTextFrame(chunk));
2942
3311
  } else {
2943
3312
  enqueue(sseMetaFrame(chunk));
3313
+ const sources = (chunk == null ? void 0 : chunk.sources) || [];
3314
+ if (sources && sources.length > 0) {
3315
+ try {
3316
+ const uiTransformation = UITransformer.transform(message, sources);
3317
+ enqueue(sseUIFrame(uiTransformation));
3318
+ } catch (transformError) {
3319
+ console.warn("[createStreamHandler] UI transformation warning:", transformError);
3320
+ }
3321
+ }
2944
3322
  }
2945
3323
  }
2946
3324
  } catch (temp) {
@@ -3073,6 +3451,7 @@ export {
3073
3451
  sseFrame,
3074
3452
  sseTextFrame,
3075
3453
  sseMetaFrame,
3454
+ sseUIFrame,
3076
3455
  sseErrorFrame,
3077
3456
  createChatHandler,
3078
3457
  createStreamHandler,
@@ -1,3 +1,3 @@
1
1
  import '../index-Cti1u0y1.mjs';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-kUXnRvuI.mjs';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame, l as sseUIFrame } from '../index-BejNscWZ.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
1
  import '../index-Cti1u0y1.js';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-B67KQ9NN.js';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, k as createSuggestionsHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame, l as sseUIFrame } from '../index-CbkMJvu5.js';
3
3
  import 'next/server';