@retrivora-ai/rag-engine 1.7.0 → 1.7.2

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/server.js CHANGED
@@ -3913,18 +3913,17 @@ var Pipeline = class {
3913
3913
  async initialize() {
3914
3914
  var _a, _b;
3915
3915
  if (this.initialised) return;
3916
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
3916
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
3917
3917
  const chartInstruction = `
3918
3918
 
3919
3919
  ${CHART_MARKER}
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.
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.
3923
3922
 
3924
3923
  1. VIEW SELECTION
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.
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.
3928
3927
 
3929
3928
  2. REQUIRED JSON SHAPE
3930
3929
  {
@@ -3932,31 +3931,52 @@ NEVER output naked JSON. If you provide a visualization, do not also provide a m
3932
3931
  "title": "Short heading",
3933
3932
  "description": "One sentence describing the view",
3934
3933
  "chartType": "pie" | "bar" | "line", // Required if view is "chart"
3935
- "data": [] // Array of objects
3934
+ "xAxisKey": "label",
3935
+ "dataKeys": ["value"],
3936
+ "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3937
+ "insights": ["Short takeaway 1", "Short takeaway 2"],
3938
+ "data": []
3936
3939
  }
3937
3940
 
3938
3941
  3. DATA RULES
3939
- - Build the UI payload from retrieved context and metadata.
3940
- - For CAROUSEL: "data" items MUST have { id, name, brand, price, image, link }.
3941
- - IMPORTANT: Extract "image" and "price" from Source metadata. Image URLs are often in fields like "images", "thumbnail", or "img".
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.
3942
3946
 
3943
3947
  4. FORMAT RULES
3944
- - Wrap the JSON in \`\`\`ui ... \`\`\` blocks.
3945
- - Place natural language text OUTSIDE the block.
3948
+ - Return valid JSON inside the \`\`\`ui\`\`\` block.
3949
+ - Put natural language explanation before or after the block, not inside.
3946
3950
 
3947
- 5. EXAMPLE
3948
- User: "find me beauty products"
3949
- Assistant: "I found several beauty products in our catalog:"
3951
+ 5. EXAMPLES
3952
+ User: "show me beauty products"
3953
+ Assistant:
3950
3954
  \`\`\`ui
3951
3955
  {
3952
3956
  "view": "carousel",
3953
3957
  "title": "Beauty Products",
3954
- "description": "Latest skincare and makeup items.",
3958
+ "description": "Browse our latest skincare and makeup collection.",
3955
3959
  "data": [
3956
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "https://placehold.co/600x400?text=Product", "link": "https://example.com/product" }
3960
+ { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
3961
+ { "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
3957
3962
  ]
3958
3963
  }
3959
3964
  \`\`\`
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
+ }
3960
3980
  \`\`\``;
3961
3981
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
3962
3982
  this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
@@ -4165,8 +4185,7 @@ Assistant: "I found several beauty products in our catalog:"
4165
4185
  sources = sources.slice(0, topK);
4166
4186
  }
4167
4187
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
4168
- Content: ${m.content}
4169
- Metadata: ${JSON.stringify(m.metadata)}`).join("\n\n---\n\n") : "No relevant context found.";
4188
+ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
4170
4189
  if (graphData && graphData.nodes.length > 0) {
4171
4190
  const graphContext = graphData.nodes.map(
4172
4191
  (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
@@ -4903,6 +4922,353 @@ init_UniversalVectorProvider();
4903
4922
 
4904
4923
  // src/handlers/index.ts
4905
4924
  var import_server = require("next/server");
4925
+
4926
+ // src/utils/UITransformer.ts
4927
+ var UITransformer = class {
4928
+ /**
4929
+ * Main transformation method
4930
+ * Analyzes user query and retrieved data to determine the best visualization format
4931
+ *
4932
+ * @param userQuery - The original user question/query
4933
+ * @param retrievedData - Vector database retrieval results
4934
+ * @returns Structured JSON response for UI rendering
4935
+ */
4936
+ static transform(userQuery, retrievedData) {
4937
+ if (!retrievedData || retrievedData.length === 0) {
4938
+ return this.createTextResponse("No data available", "No relevant data found for your query.");
4939
+ }
4940
+ const analysis = this.analyzeData(userQuery, retrievedData);
4941
+ switch (analysis.suggestedType) {
4942
+ case "product_carousel":
4943
+ return this.transformToProductCarousel(retrievedData);
4944
+ case "pie_chart":
4945
+ return this.transformToPieChart(retrievedData);
4946
+ case "bar_chart":
4947
+ return this.transformToBarChart(retrievedData);
4948
+ case "line_chart":
4949
+ return this.transformToLineChart(retrievedData);
4950
+ case "table":
4951
+ return this.transformToTable(retrievedData);
4952
+ default:
4953
+ return this.transformToText(retrievedData);
4954
+ }
4955
+ }
4956
+ /**
4957
+ * Analyzes the data and query to suggest the best visualization type
4958
+ */
4959
+ static analyzeData(query, data) {
4960
+ const queryLower = query.toLowerCase();
4961
+ const hasProducts = data.some(
4962
+ (item) => this.isProductData(item)
4963
+ );
4964
+ const hasTimeSeries = data.some(
4965
+ (item) => this.isTimeSeriesData(item)
4966
+ );
4967
+ const hasCategories = this.detectCategories(data).length > 0;
4968
+ const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
4969
+ const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
4970
+ const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
4971
+ if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
4972
+ return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
4973
+ }
4974
+ if (isTrendQuery && hasTimeSeries) {
4975
+ return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
4976
+ }
4977
+ if (isDistributionQuery && hasCategories) {
4978
+ return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
4979
+ }
4980
+ if (isComparisonQuery && hasCategories && data.length > 2) {
4981
+ return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
4982
+ }
4983
+ if (data.length > 5 && this.hasMultipleFields(data)) {
4984
+ return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
4985
+ }
4986
+ return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
4987
+ }
4988
+ /**
4989
+ * Transform data to product carousel format
4990
+ */
4991
+ static transformToProductCarousel(data) {
4992
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
4993
+ return {
4994
+ type: "product_carousel",
4995
+ title: "Recommended Products",
4996
+ description: `Found ${products.length} relevant products`,
4997
+ data: products
4998
+ };
4999
+ }
5000
+ /**
5001
+ * Transform data to pie chart format
5002
+ */
5003
+ static transformToPieChart(data) {
5004
+ const categories = this.detectCategories(data);
5005
+ const categoryData = this.aggregateByCategory(data, categories);
5006
+ const pieData = Object.entries(categoryData).map(([label, count]) => ({
5007
+ label,
5008
+ value: count,
5009
+ inStock: this.checkStockStatus(label, data)
5010
+ }));
5011
+ return {
5012
+ type: "pie_chart",
5013
+ title: "Distribution by Category",
5014
+ description: `Showing breakdown across ${categories.length} categories`,
5015
+ data: pieData
5016
+ };
5017
+ }
5018
+ /**
5019
+ * Transform data to bar chart format
5020
+ */
5021
+ static transformToBarChart(data) {
5022
+ const categories = this.detectCategories(data);
5023
+ const categoryData = this.aggregateByCategory(data, categories);
5024
+ const barData = Object.entries(categoryData).map(([category, value]) => ({
5025
+ category,
5026
+ value,
5027
+ inStock: this.checkStockStatus(category, data)
5028
+ }));
5029
+ return {
5030
+ type: "bar_chart",
5031
+ title: "Comparison by Category",
5032
+ description: `Comparing ${categories.length} categories`,
5033
+ data: barData
5034
+ };
5035
+ }
5036
+ /**
5037
+ * Transform data to line chart format
5038
+ */
5039
+ static transformToLineChart(data) {
5040
+ const timePoints = this.extractTimeSeriesData(data);
5041
+ const lineData = timePoints.map((point) => ({
5042
+ timestamp: point.timestamp,
5043
+ value: point.value,
5044
+ label: point.label
5045
+ }));
5046
+ return {
5047
+ type: "line_chart",
5048
+ title: "Trend Over Time",
5049
+ description: `Showing ${lineData.length} data points`,
5050
+ data: lineData
5051
+ };
5052
+ }
5053
+ /**
5054
+ * Transform data to table format
5055
+ */
5056
+ static transformToTable(data) {
5057
+ const columns = this.extractTableColumns(data);
5058
+ const rows = data.map((item) => this.extractTableRow(item, columns));
5059
+ const tableData = {
5060
+ columns,
5061
+ rows
5062
+ };
5063
+ return {
5064
+ type: "table",
5065
+ title: "Detailed Results",
5066
+ description: `Showing ${data.length} results`,
5067
+ data: tableData
5068
+ };
5069
+ }
5070
+ /**
5071
+ * Transform data to text format (fallback)
5072
+ */
5073
+ static transformToText(data) {
5074
+ const textContent = data.map((item) => item.content).join("\n\n");
5075
+ return this.createTextResponse(
5076
+ "Search Results",
5077
+ textContent,
5078
+ `Found ${data.length} relevant results`
5079
+ );
5080
+ }
5081
+ /**
5082
+ * Helper: Create text response
5083
+ */
5084
+ static createTextResponse(title, content, description) {
5085
+ return {
5086
+ type: "text",
5087
+ title,
5088
+ description,
5089
+ data: { content }
5090
+ };
5091
+ }
5092
+ /**
5093
+ * Helper: Check if data item is product-related
5094
+ */
5095
+ static isProductData(item) {
5096
+ const content = (item.content || "").toLowerCase();
5097
+ const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
5098
+ return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
5099
+ (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
5100
+ );
5101
+ }
5102
+ /**
5103
+ * Helper: Check if data contains time series
5104
+ */
5105
+ static isTimeSeriesData(item) {
5106
+ const content = (item.content || "").toLowerCase();
5107
+ const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
5108
+ return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
5109
+ (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
5110
+ );
5111
+ }
5112
+ /**
5113
+ * Helper: Extract product information from vector match
5114
+ */
5115
+ static extractProductInfo(item) {
5116
+ const meta = item.metadata || {};
5117
+ if (meta.name || meta.product) {
5118
+ return {
5119
+ id: item.id,
5120
+ name: meta.name || meta.product,
5121
+ price: meta.price,
5122
+ image: meta.image || meta.imageUrl,
5123
+ brand: meta.brand,
5124
+ description: item.content,
5125
+ inStock: this.determineStockStatus(item)
5126
+ };
5127
+ }
5128
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
5129
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
5130
+ if (nameMatch) {
5131
+ return {
5132
+ id: item.id,
5133
+ name: nameMatch[1],
5134
+ price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
5135
+ description: item.content,
5136
+ inStock: this.determineStockStatus(item)
5137
+ };
5138
+ }
5139
+ return null;
5140
+ }
5141
+ /**
5142
+ * Helper: Detect categories in data
5143
+ */
5144
+ static detectCategories(data) {
5145
+ const categories = /* @__PURE__ */ new Set();
5146
+ data.forEach((item) => {
5147
+ const meta = item.metadata || {};
5148
+ if (meta.category) {
5149
+ categories.add(String(meta.category));
5150
+ }
5151
+ if (meta.type) {
5152
+ categories.add(String(meta.type));
5153
+ }
5154
+ if (meta.tag) {
5155
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
5156
+ tags.forEach((t) => categories.add(String(t)));
5157
+ }
5158
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
5159
+ if (categoryMatch) {
5160
+ categories.add(categoryMatch[1].trim());
5161
+ }
5162
+ });
5163
+ return Array.from(categories);
5164
+ }
5165
+ /**
5166
+ * Helper: Aggregate data by category
5167
+ */
5168
+ static aggregateByCategory(data, categories) {
5169
+ const result = {};
5170
+ categories.forEach((cat) => {
5171
+ result[cat] = 0;
5172
+ });
5173
+ data.forEach((item) => {
5174
+ const meta = item.metadata || {};
5175
+ const itemCategory = meta.category || meta.type || "Other";
5176
+ if (result.hasOwnProperty(itemCategory)) {
5177
+ result[itemCategory]++;
5178
+ } else {
5179
+ result["Other"] = (result["Other"] || 0) + 1;
5180
+ }
5181
+ });
5182
+ return result;
5183
+ }
5184
+ /**
5185
+ * Helper: Extract time series data
5186
+ */
5187
+ static extractTimeSeriesData(data) {
5188
+ return data.map((item) => {
5189
+ const meta = item.metadata || {};
5190
+ return {
5191
+ timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
5192
+ value: meta.value || item.score || 0,
5193
+ label: meta.label || item.content.substring(0, 50)
5194
+ };
5195
+ });
5196
+ }
5197
+ /**
5198
+ * Helper: Extract table columns
5199
+ */
5200
+ static extractTableColumns(data) {
5201
+ const columnSet = /* @__PURE__ */ new Set();
5202
+ columnSet.add("Content");
5203
+ data.forEach((item) => {
5204
+ Object.keys(item.metadata || {}).forEach((key) => {
5205
+ columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
5206
+ });
5207
+ });
5208
+ return Array.from(columnSet);
5209
+ }
5210
+ /**
5211
+ * Helper: Extract table row
5212
+ */
5213
+ static extractTableRow(item, columns) {
5214
+ const meta = item.metadata || {};
5215
+ return columns.map((col) => {
5216
+ if (col === "Content") {
5217
+ return item.content.substring(0, 100);
5218
+ }
5219
+ const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
5220
+ const value = meta[metaKey];
5221
+ return value !== void 0 ? String(value) : "";
5222
+ });
5223
+ }
5224
+ /**
5225
+ * Helper: Check stock status
5226
+ */
5227
+ static checkStockStatus(category, data) {
5228
+ const item = data.find((d) => {
5229
+ const meta = d.metadata || {};
5230
+ return meta.category === category || meta.type === category;
5231
+ });
5232
+ return this.determineStockStatus(item || {});
5233
+ }
5234
+ /**
5235
+ * Helper: Determine if item is in stock
5236
+ */
5237
+ static determineStockStatus(item) {
5238
+ const meta = item.metadata || {};
5239
+ if (meta.inStock !== void 0) {
5240
+ return Boolean(meta.inStock);
5241
+ }
5242
+ if (meta.stock !== void 0) {
5243
+ return Boolean(meta.stock);
5244
+ }
5245
+ if (meta.available !== void 0) {
5246
+ return Boolean(meta.available);
5247
+ }
5248
+ const content = (item.content || "").toLowerCase();
5249
+ if (content.includes("out of stock") || content.includes("unavailable")) {
5250
+ return false;
5251
+ }
5252
+ if (content.includes("in stock") || content.includes("available")) {
5253
+ return true;
5254
+ }
5255
+ return true;
5256
+ }
5257
+ /**
5258
+ * Helper: Check if data has multiple fields
5259
+ */
5260
+ static hasMultipleFields(data) {
5261
+ const fieldCount = /* @__PURE__ */ new Set();
5262
+ data.forEach((item) => {
5263
+ Object.keys(item.metadata || {}).forEach((key) => {
5264
+ fieldCount.add(key);
5265
+ });
5266
+ });
5267
+ return fieldCount.size > 2;
5268
+ }
5269
+ };
5270
+
5271
+ // src/handlers/index.ts
4906
5272
  function sseFrame(payload) {
4907
5273
  return `data: ${JSON.stringify(payload)}
4908
5274
 
@@ -4916,6 +5282,11 @@ function sseTextFrame(text) {
4916
5282
  function sseMetaFrame(meta) {
4917
5283
  return `data: ${JSON.stringify(__spreadValues({ type: "metadata" }, meta != null ? meta : {}))}
4918
5284
 
5285
+ `;
5286
+ }
5287
+ function sseUIFrame(uiTransformation) {
5288
+ return `data: ${JSON.stringify(__spreadValues({ type: "ui_transformation" }, uiTransformation != null ? uiTransformation : {}))}
5289
+
4919
5290
  `;
4920
5291
  }
4921
5292
  function sseErrorFrame(message) {
@@ -4979,6 +5350,15 @@ function createStreamHandler(configOrPlugin) {
4979
5350
  enqueue(sseTextFrame(chunk));
4980
5351
  } else {
4981
5352
  enqueue(sseMetaFrame(chunk));
5353
+ const sources = (chunk == null ? void 0 : chunk.sources) || [];
5354
+ if (sources && sources.length > 0) {
5355
+ try {
5356
+ const uiTransformation = UITransformer.transform(message, sources);
5357
+ enqueue(sseUIFrame(uiTransformation));
5358
+ } catch (transformError) {
5359
+ console.warn("[createStreamHandler] UI transformation warning:", transformError);
5360
+ }
5361
+ }
4982
5362
  }
4983
5363
  }
4984
5364
  } catch (temp) {
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-OHXRQTQH.mjs";
42
+ } from "./chunk-M32ZXLTT.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.7.0",
3
+ "version": "1.7.2",
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",