@retrivora-ai/rag-engine 1.8.3 → 1.8.5

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.
@@ -120,11 +120,14 @@ export class QdrantProvider extends BaseVectorProvider {
120
120
  const opts = this.config.options as Record<string, unknown>;
121
121
  const dimensionsForCreate = (opts.dimensions as number) || 1536;
122
122
 
123
+ const metric = this.config.distanceMetric || 'cosine';
124
+ const distance = metric === 'euclidean' ? 'Euclid' : metric === 'dotproduct' ? 'Dot' : 'Cosine';
125
+
123
126
  console.log(`[QdrantProvider] ⏳ Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
124
127
  await this.http.put(`/collections/${this.indexName}`, {
125
128
  vectors: {
126
129
  size: dimensionsForCreate,
127
- distance: 'Cosine',
130
+ distance: distance,
128
131
  },
129
132
  });
130
133
  console.log(`[QdrantProvider] ✅ Created collection "${this.indexName}"`);
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss";
2
+ @source "./**/*.{ts,tsx,js,jsx}";
@@ -29,7 +29,8 @@ export class UITransformer {
29
29
  ? retrievedData.filter(item => this.determineStockStatus(item))
30
30
  : retrievedData;
31
31
 
32
- const categories = this.detectCategories(filteredData);
32
+ const groupByField = this.determineGroupByField(userQuery, filteredData);
33
+ const categories = this.detectCategories(filteredData, groupByField);
33
34
  const hasProducts = filteredData.some(item => this.isProductData(item));
34
35
  const isTimeSeries = filteredData.some(item => this.isTimeSeriesData(item));
35
36
  const isTrendQuery = this.isTrendQuery(userQuery);
@@ -39,14 +40,16 @@ export class UITransformer {
39
40
  return this.transformToLineChart(filteredData);
40
41
  }
41
42
 
43
+ const explicitChartRequest = this.isExplicitChartQuery(userQuery);
44
+
42
45
  // 2. High Priority: Products (Always prefer carousel for products unless it's a specific breakdown request)
43
- if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
46
+ if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
44
47
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
45
48
  }
46
49
 
47
50
  // 3. Category breakdowns
48
- if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
49
- return this.transformToPieChart(filteredData);
51
+ if (explicitChartRequest || (categories.length > 1 && this.shouldShowCategoryChart(userQuery))) {
52
+ return this.transformToPieChart(filteredData, groupByField);
50
53
  }
51
54
 
52
55
  // 4. Secondary: Products as carousel if not already handled
@@ -87,13 +90,16 @@ export class UITransformer {
87
90
  * Transform data to pie chart format
88
91
  */
89
92
  private static transformToPieChart(
90
- data: VectorMatch[]
93
+ data: VectorMatch[],
94
+ groupByField: string
91
95
  ): UITransformationResponse {
92
- const categories = this.detectCategories(data);
93
- const categoryData = this.aggregateByCategory(data, categories);
96
+ let categories = this.detectCategories(data, groupByField);
97
+ if (categories.length === 0) categories = ['All Data'];
98
+
99
+ const categoryData = this.aggregateByCategory(data, categories, groupByField);
94
100
 
95
101
  const pieData: PieChartData[] = Object.entries(categoryData).map(([label, count]) => {
96
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
102
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
97
103
  return {
98
104
  label,
99
105
  value: count as number,
@@ -104,7 +110,7 @@ export class UITransformer {
104
110
 
105
111
  return {
106
112
  type: 'pie_chart',
107
- title: 'Distribution by Category',
113
+ title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
108
114
  description: `Showing breakdown across ${categories.length} categories`,
109
115
  data: pieData,
110
116
  };
@@ -371,11 +377,16 @@ export class UITransformer {
371
377
  return hasTimeKeyword || hasValidDateValue;
372
378
  }
373
379
 
374
- private static shouldShowCategoryChart(query: string, categories: string[]): boolean {
375
- if (categories.length < 2) {
376
- return false;
377
- }
380
+ private static isExplicitChartQuery(query: string): boolean {
381
+ const normalized = query.toLowerCase();
382
+ return normalized.includes('pie chart') ||
383
+ normalized.includes('piechart') ||
384
+ normalized.includes('bar chart') ||
385
+ normalized.includes('barchart') ||
386
+ normalized.includes('radar chart');
387
+ }
378
388
 
389
+ private static shouldShowCategoryChart(query: string): boolean {
379
390
  const normalized = query.toLowerCase();
380
391
  const chartKeywords = [
381
392
  'distribution',
@@ -389,6 +400,10 @@ export class UITransformer {
389
400
  'split',
390
401
  'category breakdown',
391
402
  'category distribution',
403
+ 'pie chart',
404
+ 'piechart',
405
+ 'bar chart',
406
+ 'barchart'
392
407
  ];
393
408
 
394
409
  return chartKeywords.some(keyword => normalized.includes(keyword));
@@ -501,38 +516,57 @@ export class UITransformer {
501
516
  }
502
517
 
503
518
  /**
504
- * Helper: Detect categories in data
519
+ * Helper: Dynamically determine what field to group by based on user query
520
+ */
521
+ private static determineGroupByField(query: string, data: VectorMatch[]): string {
522
+ const normalized = query.toLowerCase();
523
+ const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
524
+
525
+ let targetField = 'category';
526
+ if (match && match[1]) {
527
+ let field = match[1].toLowerCase();
528
+ // Un-pluralize common words to match schema keys
529
+ if (field.endsWith('ies')) field = field.replace(/ies$/, 'y');
530
+ else if (field.endsWith('s') && field !== 'status') field = field.slice(0, -1);
531
+
532
+ const stopWords = ['the', 'all', 'different', 'various', 'some', 'these', 'those'];
533
+ if (!stopWords.includes(field)) {
534
+ targetField = field;
535
+ }
536
+ }
537
+
538
+ // Verify field exists, or fallback
539
+ const hasTargetField = data.some(d => d.metadata && d.metadata[targetField] !== undefined);
540
+ if (!hasTargetField && targetField === 'category') {
541
+ if (data.some(d => d.metadata && d.metadata['type'] !== undefined)) return 'type';
542
+ if (data.some(d => d.metadata && d.metadata['brand'] !== undefined)) return 'brand';
543
+ }
544
+
545
+ return targetField;
546
+ }
547
+
548
+ /**
549
+ * Helper: Detect grouping values dynamically
505
550
  */
506
- private static detectCategories(data: VectorMatch[]): string[] {
551
+ private static detectCategories(data: VectorMatch[], groupByField: string): string[] {
507
552
  const categories = new Set<string>();
508
553
 
509
554
  data.forEach(item => {
510
555
  const meta = item.metadata || {};
511
556
 
512
- // Check for category field
513
- if (meta.category) {
514
- categories.add(String(meta.category));
515
- }
516
- if (meta.type) {
517
- categories.add(String(meta.type));
518
- }
519
- if (meta.tag) {
520
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
521
- tags.forEach(t => categories.add(String(t)));
522
- }
523
-
524
- // Parse category strings from content lines like "Shoes & Footwear: • Product"
525
- const contentCategories = Array.from(new Set(
526
- Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm))
527
- .map(match => match[1].trim())
528
- .filter(Boolean)
529
- ));
530
- contentCategories.forEach((category) => categories.add(category));
531
-
532
- // Parse from structured category hints in content
533
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
534
- if (categoryMatch) {
535
- categories.add(categoryMatch[1].trim());
557
+ if (meta[groupByField] !== undefined) {
558
+ const val = meta[groupByField];
559
+ if (Array.isArray(val)) val.forEach(v => categories.add(String(v)));
560
+ else categories.add(String(val));
561
+ } else if (groupByField === 'category') {
562
+ // Fallbacks for legacy 'category' extraction
563
+ if (meta.type) categories.add(String(meta.type));
564
+ if (meta.tag) {
565
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
566
+ tags.forEach(t => categories.add(String(t)));
567
+ }
568
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
569
+ if (categoryMatch) categories.add(categoryMatch[1].trim());
536
570
  }
537
571
  });
538
572
 
@@ -540,11 +574,12 @@ export class UITransformer {
540
574
  }
541
575
 
542
576
  /**
543
- * Helper: Aggregate data by category
577
+ * Helper: Aggregate data dynamically
544
578
  */
545
579
  private static aggregateByCategory(
546
580
  data: VectorMatch[],
547
- categories: string[]
581
+ categories: string[],
582
+ groupByField: string
548
583
  ): Record<string, number> {
549
584
  const result: Record<string, number> = {};
550
585
 
@@ -554,7 +589,13 @@ export class UITransformer {
554
589
 
555
590
  data.forEach(item => {
556
591
  const meta = item.metadata || {};
557
- const itemCategory = (meta.category || meta.type || 'Other') as string;
592
+ let itemCategory = 'Other';
593
+
594
+ if (meta[groupByField] !== undefined) {
595
+ itemCategory = String(meta[groupByField]);
596
+ } else if (groupByField === 'category' && meta.type) {
597
+ itemCategory = String(meta.type);
598
+ }
558
599
 
559
600
  if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
560
601
  result[itemCategory]++;
@@ -615,15 +656,21 @@ export class UITransformer {
615
656
  }
616
657
 
617
658
  /**
618
- * Helper: Calculate stock counts by category
659
+ * Helper: Calculate stock counts dynamically
619
660
  */
620
- private static calculateStockCounts(category: string, data: VectorMatch[]): { inStockCount: number; outOfStockCount: number } {
661
+ private static calculateStockCounts(category: string, data: VectorMatch[], groupByField: string): { inStockCount: number; outOfStockCount: number } {
621
662
  let inStock = 0;
622
663
  let outOfStock = 0;
623
664
 
624
665
  data.forEach(d => {
625
666
  const meta = d.metadata || {};
626
- const itemCategory = (meta.category || meta.type || 'Other') as string;
667
+ let itemCategory = 'Other';
668
+ if (meta[groupByField] !== undefined) {
669
+ itemCategory = String(meta[groupByField]);
670
+ } else if (groupByField === 'category' && meta.type) {
671
+ itemCategory = String(meta.type);
672
+ }
673
+
627
674
  if (itemCategory === category) {
628
675
  if (this.determineStockStatus(d)) {
629
676
  inStock++;
@@ -772,12 +819,13 @@ DATA SHAPE per type:
772
819
  - text: { "content": "<prose answer>" }
773
820
 
774
821
  DECISION RULES (follow strictly):
775
- 1. bar_chart → comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
776
- 2. line_chart trends or changes over time (dates, months, years, sequential events)
777
- 3. pie_chart proportional breakdown or percentage distribution
778
- 4. radar_chart comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
779
- 5. table multi-field structured records where each item has ≥ 3 attributes
780
- 6. text conversational or free-form answers where no chart adds value
822
+ 1. IF the user explicitly asks for a "pie chart", "bar chart", or "table", you MUST output that exact type. Do not deviate from their request.
823
+ 2. bar_chart comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
824
+ 3. line_chart trends or changes over time (dates, months, years, sequential events)
825
+ 4. pie_chart proportional breakdown or percentage distribution
826
+ 5. radar_chart comparing 2 or more products across MULTIPLE attributes.
827
+ 6. table multi-field structured records where each item has ≥ 3 attributes
828
+ 7. text → conversational or free-form answers where no chart adds value
781
829
 
782
830
  IMPORTANT:
783
831
  - Aggregate numeric values from the raw data — do not pass raw object arrays as data.