@retrivora-ai/rag-engine 1.8.9 → 1.9.0

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.
@@ -99,8 +99,6 @@ interface VectorDBConfig {
99
99
  * - searchFields?: string[] | string // optional override for which columns receive exact-match boosts
100
100
  */
101
101
  options: Record<string, unknown>;
102
- /** Optional distance metric (defaults to cosine if not specified and supported by provider) */
103
- distanceMetric?: 'cosine' | 'euclidean' | 'dotproduct';
104
102
  }
105
103
  type GraphDBProvider = typeof GRAPH_DB_PROVIDERS[number];
106
104
  interface GraphDBConfig {
@@ -99,8 +99,6 @@ interface VectorDBConfig {
99
99
  * - searchFields?: string[] | string // optional override for which columns receive exact-match boosts
100
100
  */
101
101
  options: Record<string, unknown>;
102
- /** Optional distance metric (defaults to cosine if not specified and supported by provider) */
103
- distanceMetric?: 'cosine' | 'euclidean' | 'dotproduct';
104
102
  }
105
103
  type GraphDBProvider = typeof GRAPH_DB_PROVIDERS[number];
106
104
  interface GraphDBConfig {
@@ -1,3 +1,3 @@
1
- import '../ILLMProvider-CbUtvWAW.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, l as sseObservabilityFrame, j as sseTextFrame, m as sseUIFrame } from '../index-c_y_5qdw.mjs';
1
+ import '../ILLMProvider-BOJFz3Na.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, l as sseObservabilityFrame, j as sseTextFrame, m as sseUIFrame } from '../index-D3V9Et2M.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
- import '../ILLMProvider-CbUtvWAW.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, l as sseObservabilityFrame, j as sseTextFrame, m as sseUIFrame } from '../index-DgzcnqZs.js';
1
+ import '../ILLMProvider-BOJFz3Na.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, l as sseObservabilityFrame, j as sseTextFrame, m as sseUIFrame } from '../index-BwpcaziY.js';
3
3
  import 'next/server';
@@ -374,12 +374,10 @@ var init_MultiTablePostgresProvider = __esm({
374
374
  embedding VECTOR(${this.dimensions})
375
375
  )
376
376
  `);
377
- const metric = this.config.distanceMetric || "cosine";
378
- const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
379
377
  await client.query(`
380
378
  CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
381
379
  ON ${this.uploadTable}
382
- USING hnsw (embedding ${indexOps})
380
+ USING hnsw (embedding vector_cosine_ops)
383
381
  `);
384
382
  const res = await client.query(`
385
383
  SELECT table_name
@@ -441,12 +439,10 @@ var init_MultiTablePostgresProvider = __esm({
441
439
  )
442
440
  `;
443
441
  await client.query(createTableSql);
444
- const metric = this.config.distanceMetric || "cosine";
445
- const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
446
442
  await client.query(`
447
443
  CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
448
444
  ON "${tableName}"
449
- USING hnsw (embedding ${indexOps})
445
+ USING hnsw (embedding vector_cosine_ops)
450
446
  `);
451
447
  if (!this.tables.includes(tableName)) {
452
448
  this.tables.push(tableName);
@@ -528,8 +524,6 @@ var init_MultiTablePostgresProvider = __esm({
528
524
  try {
529
525
  let sqlQuery = "";
530
526
  let params = [];
531
- const metric = this.config.distanceMetric || "cosine";
532
- const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
533
527
  if (queryText) {
534
528
  const hasEntityHints = entityHints.length > 0;
535
529
  const exactNameScoreExpr = hasEntityHints ? `+ (
@@ -542,9 +536,9 @@ var init_MultiTablePostgresProvider = __esm({
542
536
  ) * 3.0` : "";
543
537
  sqlQuery = `
544
538
  SELECT *,
545
- (${scoreExpr}) AS vector_score,
539
+ (1 - (embedding <=> $1::vector)) AS vector_score,
546
540
  COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
547
- ((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
541
+ ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
548
542
  FROM "${table}" t
549
543
  ORDER BY hybrid_score DESC
550
544
  LIMIT 50
@@ -553,7 +547,7 @@ var init_MultiTablePostgresProvider = __esm({
553
547
  } else {
554
548
  sqlQuery = `
555
549
  SELECT *,
556
- (${scoreExpr}) AS hybrid_score
550
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
557
551
  FROM "${table}" t
558
552
  ORDER BY hybrid_score DESC
559
553
  LIMIT 50
@@ -1027,13 +1021,11 @@ var init_QdrantProvider = __esm({
1027
1021
  if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
1028
1022
  const opts = this.config.options;
1029
1023
  const dimensionsForCreate = opts.dimensions || 1536;
1030
- const metric = this.config.distanceMetric || "cosine";
1031
- const distance = metric === "euclidean" ? "Euclid" : metric === "dotproduct" ? "Dot" : "Cosine";
1032
1024
  console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
1033
1025
  await this.http.put(`/collections/${this.indexName}`, {
1034
1026
  vectors: {
1035
1027
  size: dimensionsForCreate,
1036
- distance
1028
+ distance: "Cosine"
1037
1029
  }
1038
1030
  });
1039
1031
  console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
@@ -1211,12 +1203,9 @@ var init_ChromaDBProvider = __esm({
1211
1203
  console.log(`[ChromaDBProvider] \u2705 Collection "${this.indexName}" found (id: ${this.collectionId})`);
1212
1204
  } catch (err) {
1213
1205
  if (import_axios5.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
1214
- const metric = this.config.distanceMetric || "cosine";
1215
- const space = metric === "euclidean" ? "l2" : metric === "dotproduct" ? "ip" : "cosine";
1216
1206
  console.log(`[ChromaDBProvider] \u23F3 Collection "${this.indexName}" not found. Creating...`);
1217
1207
  const { data } = await this.http.post("/api/v1/collections", {
1218
- name: this.indexName,
1219
- metadata: { "hnsw:space": space }
1208
+ name: this.indexName
1220
1209
  });
1221
1210
  this.collectionId = data.id;
1222
1211
  console.log(`[ChromaDBProvider] \u2705 Created collection "${this.indexName}" (id: ${this.collectionId})`);
@@ -4176,20 +4165,18 @@ var UITransformer = class {
4176
4165
  }
4177
4166
  const isStockRequest = this.isStockQuery(userQuery);
4178
4167
  const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
4179
- const groupByField = this.determineGroupByField(userQuery, filteredData);
4180
- const categories = this.detectCategories(filteredData, groupByField);
4168
+ const categories = this.detectCategories(filteredData);
4181
4169
  const hasProducts = filteredData.some((item) => this.isProductData(item));
4182
4170
  const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
4183
4171
  const isTrendQuery = this.isTrendQuery(userQuery);
4184
4172
  if (isTrendQuery && isTimeSeries) {
4185
4173
  return this.transformToLineChart(filteredData);
4186
4174
  }
4187
- const explicitChartRequest = this.isExplicitChartQuery(userQuery);
4188
- if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
4175
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
4189
4176
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
4190
4177
  }
4191
- if (explicitChartRequest || categories.length > 1 && this.shouldShowCategoryChart(userQuery)) {
4192
- return this.transformToPieChart(filteredData, groupByField);
4178
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
4179
+ return this.transformToPieChart(filteredData);
4193
4180
  }
4194
4181
  if (hasProducts) {
4195
4182
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
@@ -4214,12 +4201,11 @@ var UITransformer = class {
4214
4201
  /**
4215
4202
  * Transform data to pie chart format
4216
4203
  */
4217
- static transformToPieChart(data, groupByField) {
4218
- let categories = this.detectCategories(data, groupByField);
4219
- if (categories.length === 0) categories = ["All Data"];
4220
- const categoryData = this.aggregateByCategory(data, categories, groupByField);
4204
+ static transformToPieChart(data) {
4205
+ const categories = this.detectCategories(data);
4206
+ const categoryData = this.aggregateByCategory(data, categories);
4221
4207
  const pieData = Object.entries(categoryData).map(([label, count]) => {
4222
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
4208
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
4223
4209
  return {
4224
4210
  label,
4225
4211
  value: count,
@@ -4229,7 +4215,7 @@ var UITransformer = class {
4229
4215
  });
4230
4216
  return {
4231
4217
  type: "pie_chart",
4232
- title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
4218
+ title: "Distribution by Category",
4233
4219
  description: `Showing breakdown across ${categories.length} categories`,
4234
4220
  data: pieData
4235
4221
  };
@@ -4452,11 +4438,10 @@ var UITransformer = class {
4452
4438
  });
4453
4439
  return hasTimeKeyword || hasValidDateValue;
4454
4440
  }
4455
- static isExplicitChartQuery(query) {
4456
- const normalized = query.toLowerCase();
4457
- return normalized.includes("pie chart") || normalized.includes("piechart") || normalized.includes("bar chart") || normalized.includes("barchart") || normalized.includes("radar chart");
4458
- }
4459
- static shouldShowCategoryChart(query) {
4441
+ static shouldShowCategoryChart(query, categories) {
4442
+ if (categories.length < 2) {
4443
+ return false;
4444
+ }
4460
4445
  const normalized = query.toLowerCase();
4461
4446
  const chartKeywords = [
4462
4447
  "distribution",
@@ -4469,11 +4454,7 @@ var UITransformer = class {
4469
4454
  "segmentation",
4470
4455
  "split",
4471
4456
  "category breakdown",
4472
- "category distribution",
4473
- "pie chart",
4474
- "piechart",
4475
- "bar chart",
4476
- "barchart"
4457
+ "category distribution"
4477
4458
  ];
4478
4459
  return chartKeywords.some((keyword) => normalized.includes(keyword));
4479
4460
  }
@@ -4549,67 +4530,44 @@ var UITransformer = class {
4549
4530
  return null;
4550
4531
  }
4551
4532
  /**
4552
- * Helper: Dynamically determine what field to group by based on user query
4553
- */
4554
- static determineGroupByField(query, data) {
4555
- const normalized = query.toLowerCase();
4556
- const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
4557
- let targetField = "category";
4558
- if (match && match[1]) {
4559
- let field = match[1].toLowerCase();
4560
- if (field.endsWith("ies")) field = field.replace(/ies$/, "y");
4561
- else if (field.endsWith("s") && field !== "status") field = field.slice(0, -1);
4562
- const stopWords = ["the", "all", "different", "various", "some", "these", "those"];
4563
- if (!stopWords.includes(field)) {
4564
- targetField = field;
4565
- }
4566
- }
4567
- const hasTargetField = data.some((d) => d.metadata && d.metadata[targetField] !== void 0);
4568
- if (!hasTargetField && targetField === "category") {
4569
- if (data.some((d) => d.metadata && d.metadata["type"] !== void 0)) return "type";
4570
- if (data.some((d) => d.metadata && d.metadata["brand"] !== void 0)) return "brand";
4571
- }
4572
- return targetField;
4573
- }
4574
- /**
4575
- * Helper: Detect grouping values dynamically
4533
+ * Helper: Detect categories in data
4576
4534
  */
4577
- static detectCategories(data, groupByField) {
4535
+ static detectCategories(data) {
4578
4536
  const categories = /* @__PURE__ */ new Set();
4579
4537
  data.forEach((item) => {
4580
4538
  const meta = item.metadata || {};
4581
- if (meta[groupByField] !== void 0) {
4582
- const val = meta[groupByField];
4583
- if (Array.isArray(val)) val.forEach((v) => categories.add(String(v)));
4584
- else categories.add(String(val));
4585
- } else if (groupByField === "category") {
4586
- if (meta.type) categories.add(String(meta.type));
4587
- if (meta.tag) {
4588
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4589
- tags.forEach((t) => categories.add(String(t)));
4590
- }
4591
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4592
- if (categoryMatch) categories.add(categoryMatch[1].trim());
4539
+ if (meta.category) {
4540
+ categories.add(String(meta.category));
4541
+ }
4542
+ if (meta.type) {
4543
+ categories.add(String(meta.type));
4544
+ }
4545
+ if (meta.tag) {
4546
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4547
+ tags.forEach((t) => categories.add(String(t)));
4548
+ }
4549
+ const contentCategories = Array.from(new Set(
4550
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
4551
+ ));
4552
+ contentCategories.forEach((category) => categories.add(category));
4553
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4554
+ if (categoryMatch) {
4555
+ categories.add(categoryMatch[1].trim());
4593
4556
  }
4594
4557
  });
4595
4558
  return Array.from(categories);
4596
4559
  }
4597
4560
  /**
4598
- * Helper: Aggregate data dynamically
4561
+ * Helper: Aggregate data by category
4599
4562
  */
4600
- static aggregateByCategory(data, categories, groupByField) {
4563
+ static aggregateByCategory(data, categories) {
4601
4564
  const result = {};
4602
4565
  categories.forEach((cat) => {
4603
4566
  result[cat] = 0;
4604
4567
  });
4605
4568
  data.forEach((item) => {
4606
4569
  const meta = item.metadata || {};
4607
- let itemCategory = "Other";
4608
- if (meta[groupByField] !== void 0) {
4609
- itemCategory = String(meta[groupByField]);
4610
- } else if (groupByField === "category" && meta.type) {
4611
- itemCategory = String(meta.type);
4612
- }
4570
+ const itemCategory = meta.category || meta.type || "Other";
4613
4571
  if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
4614
4572
  result[itemCategory]++;
4615
4573
  } else {
@@ -4659,19 +4617,14 @@ var UITransformer = class {
4659
4617
  });
4660
4618
  }
4661
4619
  /**
4662
- * Helper: Calculate stock counts dynamically
4620
+ * Helper: Calculate stock counts by category
4663
4621
  */
4664
- static calculateStockCounts(category, data, groupByField) {
4622
+ static calculateStockCounts(category, data) {
4665
4623
  let inStock = 0;
4666
4624
  let outOfStock = 0;
4667
4625
  data.forEach((d) => {
4668
4626
  const meta = d.metadata || {};
4669
- let itemCategory = "Other";
4670
- if (meta[groupByField] !== void 0) {
4671
- itemCategory = String(meta[groupByField]);
4672
- } else if (groupByField === "category" && meta.type) {
4673
- itemCategory = String(meta.type);
4674
- }
4627
+ const itemCategory = meta.category || meta.type || "Other";
4675
4628
  if (itemCategory === category) {
4676
4629
  if (this.determineStockStatus(d)) {
4677
4630
  inStock++;
@@ -4796,13 +4749,12 @@ DATA SHAPE per type:
4796
4749
  - text: { "content": "<prose answer>" }
4797
4750
 
4798
4751
  DECISION RULES (follow strictly):
4799
- 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.
4800
- 2. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
4801
- 3. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4802
- 4. pie_chart \u2192 proportional breakdown or percentage distribution
4803
- 5. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes.
4804
- 6. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4805
- 7. text \u2192 conversational or free-form answers where no chart adds value
4752
+ 1. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
4753
+ 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4754
+ 3. pie_chart \u2192 proportional breakdown or percentage distribution
4755
+ 4. radar_chart \u2192 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.
4756
+ 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4757
+ 6. text \u2192 conversational or free-form answers where no chart adds value
4806
4758
 
4807
4759
  IMPORTANT:
4808
4760
  - Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
@@ -353,12 +353,10 @@ var init_MultiTablePostgresProvider = __esm({
353
353
  embedding VECTOR(${this.dimensions})
354
354
  )
355
355
  `);
356
- const metric = this.config.distanceMetric || "cosine";
357
- const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
358
356
  await client.query(`
359
357
  CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
360
358
  ON ${this.uploadTable}
361
- USING hnsw (embedding ${indexOps})
359
+ USING hnsw (embedding vector_cosine_ops)
362
360
  `);
363
361
  const res = await client.query(`
364
362
  SELECT table_name
@@ -420,12 +418,10 @@ var init_MultiTablePostgresProvider = __esm({
420
418
  )
421
419
  `;
422
420
  await client.query(createTableSql);
423
- const metric = this.config.distanceMetric || "cosine";
424
- const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
425
421
  await client.query(`
426
422
  CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
427
423
  ON "${tableName}"
428
- USING hnsw (embedding ${indexOps})
424
+ USING hnsw (embedding vector_cosine_ops)
429
425
  `);
430
426
  if (!this.tables.includes(tableName)) {
431
427
  this.tables.push(tableName);
@@ -507,8 +503,6 @@ var init_MultiTablePostgresProvider = __esm({
507
503
  try {
508
504
  let sqlQuery = "";
509
505
  let params = [];
510
- const metric = this.config.distanceMetric || "cosine";
511
- const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
512
506
  if (queryText) {
513
507
  const hasEntityHints = entityHints.length > 0;
514
508
  const exactNameScoreExpr = hasEntityHints ? `+ (
@@ -521,9 +515,9 @@ var init_MultiTablePostgresProvider = __esm({
521
515
  ) * 3.0` : "";
522
516
  sqlQuery = `
523
517
  SELECT *,
524
- (${scoreExpr}) AS vector_score,
518
+ (1 - (embedding <=> $1::vector)) AS vector_score,
525
519
  COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
526
- ((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
520
+ ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
527
521
  FROM "${table}" t
528
522
  ORDER BY hybrid_score DESC
529
523
  LIMIT 50
@@ -532,7 +526,7 @@ var init_MultiTablePostgresProvider = __esm({
532
526
  } else {
533
527
  sqlQuery = `
534
528
  SELECT *,
535
- (${scoreExpr}) AS hybrid_score
529
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
536
530
  FROM "${table}" t
537
531
  ORDER BY hybrid_score DESC
538
532
  LIMIT 50
@@ -1006,13 +1000,11 @@ var init_QdrantProvider = __esm({
1006
1000
  if (axios4.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
1007
1001
  const opts = this.config.options;
1008
1002
  const dimensionsForCreate = opts.dimensions || 1536;
1009
- const metric = this.config.distanceMetric || "cosine";
1010
- const distance = metric === "euclidean" ? "Euclid" : metric === "dotproduct" ? "Dot" : "Cosine";
1011
1003
  console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
1012
1004
  await this.http.put(`/collections/${this.indexName}`, {
1013
1005
  vectors: {
1014
1006
  size: dimensionsForCreate,
1015
- distance
1007
+ distance: "Cosine"
1016
1008
  }
1017
1009
  });
1018
1010
  console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
@@ -1190,12 +1182,9 @@ var init_ChromaDBProvider = __esm({
1190
1182
  console.log(`[ChromaDBProvider] \u2705 Collection "${this.indexName}" found (id: ${this.collectionId})`);
1191
1183
  } catch (err) {
1192
1184
  if (axios5.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
1193
- const metric = this.config.distanceMetric || "cosine";
1194
- const space = metric === "euclidean" ? "l2" : metric === "dotproduct" ? "ip" : "cosine";
1195
1185
  console.log(`[ChromaDBProvider] \u23F3 Collection "${this.indexName}" not found. Creating...`);
1196
1186
  const { data } = await this.http.post("/api/v1/collections", {
1197
- name: this.indexName,
1198
- metadata: { "hnsw:space": space }
1187
+ name: this.indexName
1199
1188
  });
1200
1189
  this.collectionId = data.id;
1201
1190
  console.log(`[ChromaDBProvider] \u2705 Created collection "${this.indexName}" (id: ${this.collectionId})`);
@@ -4139,20 +4128,18 @@ var UITransformer = class {
4139
4128
  }
4140
4129
  const isStockRequest = this.isStockQuery(userQuery);
4141
4130
  const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
4142
- const groupByField = this.determineGroupByField(userQuery, filteredData);
4143
- const categories = this.detectCategories(filteredData, groupByField);
4131
+ const categories = this.detectCategories(filteredData);
4144
4132
  const hasProducts = filteredData.some((item) => this.isProductData(item));
4145
4133
  const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
4146
4134
  const isTrendQuery = this.isTrendQuery(userQuery);
4147
4135
  if (isTrendQuery && isTimeSeries) {
4148
4136
  return this.transformToLineChart(filteredData);
4149
4137
  }
4150
- const explicitChartRequest = this.isExplicitChartQuery(userQuery);
4151
- if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
4138
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
4152
4139
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
4153
4140
  }
4154
- if (explicitChartRequest || categories.length > 1 && this.shouldShowCategoryChart(userQuery)) {
4155
- return this.transformToPieChart(filteredData, groupByField);
4141
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
4142
+ return this.transformToPieChart(filteredData);
4156
4143
  }
4157
4144
  if (hasProducts) {
4158
4145
  return this.transformToProductCarousel(filteredData, config, trainedSchema);
@@ -4177,12 +4164,11 @@ var UITransformer = class {
4177
4164
  /**
4178
4165
  * Transform data to pie chart format
4179
4166
  */
4180
- static transformToPieChart(data, groupByField) {
4181
- let categories = this.detectCategories(data, groupByField);
4182
- if (categories.length === 0) categories = ["All Data"];
4183
- const categoryData = this.aggregateByCategory(data, categories, groupByField);
4167
+ static transformToPieChart(data) {
4168
+ const categories = this.detectCategories(data);
4169
+ const categoryData = this.aggregateByCategory(data, categories);
4184
4170
  const pieData = Object.entries(categoryData).map(([label, count]) => {
4185
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
4171
+ const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
4186
4172
  return {
4187
4173
  label,
4188
4174
  value: count,
@@ -4192,7 +4178,7 @@ var UITransformer = class {
4192
4178
  });
4193
4179
  return {
4194
4180
  type: "pie_chart",
4195
- title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
4181
+ title: "Distribution by Category",
4196
4182
  description: `Showing breakdown across ${categories.length} categories`,
4197
4183
  data: pieData
4198
4184
  };
@@ -4415,11 +4401,10 @@ var UITransformer = class {
4415
4401
  });
4416
4402
  return hasTimeKeyword || hasValidDateValue;
4417
4403
  }
4418
- static isExplicitChartQuery(query) {
4419
- const normalized = query.toLowerCase();
4420
- return normalized.includes("pie chart") || normalized.includes("piechart") || normalized.includes("bar chart") || normalized.includes("barchart") || normalized.includes("radar chart");
4421
- }
4422
- static shouldShowCategoryChart(query) {
4404
+ static shouldShowCategoryChart(query, categories) {
4405
+ if (categories.length < 2) {
4406
+ return false;
4407
+ }
4423
4408
  const normalized = query.toLowerCase();
4424
4409
  const chartKeywords = [
4425
4410
  "distribution",
@@ -4432,11 +4417,7 @@ var UITransformer = class {
4432
4417
  "segmentation",
4433
4418
  "split",
4434
4419
  "category breakdown",
4435
- "category distribution",
4436
- "pie chart",
4437
- "piechart",
4438
- "bar chart",
4439
- "barchart"
4420
+ "category distribution"
4440
4421
  ];
4441
4422
  return chartKeywords.some((keyword) => normalized.includes(keyword));
4442
4423
  }
@@ -4512,67 +4493,44 @@ var UITransformer = class {
4512
4493
  return null;
4513
4494
  }
4514
4495
  /**
4515
- * Helper: Dynamically determine what field to group by based on user query
4516
- */
4517
- static determineGroupByField(query, data) {
4518
- const normalized = query.toLowerCase();
4519
- const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
4520
- let targetField = "category";
4521
- if (match && match[1]) {
4522
- let field = match[1].toLowerCase();
4523
- if (field.endsWith("ies")) field = field.replace(/ies$/, "y");
4524
- else if (field.endsWith("s") && field !== "status") field = field.slice(0, -1);
4525
- const stopWords = ["the", "all", "different", "various", "some", "these", "those"];
4526
- if (!stopWords.includes(field)) {
4527
- targetField = field;
4528
- }
4529
- }
4530
- const hasTargetField = data.some((d) => d.metadata && d.metadata[targetField] !== void 0);
4531
- if (!hasTargetField && targetField === "category") {
4532
- if (data.some((d) => d.metadata && d.metadata["type"] !== void 0)) return "type";
4533
- if (data.some((d) => d.metadata && d.metadata["brand"] !== void 0)) return "brand";
4534
- }
4535
- return targetField;
4536
- }
4537
- /**
4538
- * Helper: Detect grouping values dynamically
4496
+ * Helper: Detect categories in data
4539
4497
  */
4540
- static detectCategories(data, groupByField) {
4498
+ static detectCategories(data) {
4541
4499
  const categories = /* @__PURE__ */ new Set();
4542
4500
  data.forEach((item) => {
4543
4501
  const meta = item.metadata || {};
4544
- if (meta[groupByField] !== void 0) {
4545
- const val = meta[groupByField];
4546
- if (Array.isArray(val)) val.forEach((v) => categories.add(String(v)));
4547
- else categories.add(String(val));
4548
- } else if (groupByField === "category") {
4549
- if (meta.type) categories.add(String(meta.type));
4550
- if (meta.tag) {
4551
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4552
- tags.forEach((t) => categories.add(String(t)));
4553
- }
4554
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4555
- if (categoryMatch) categories.add(categoryMatch[1].trim());
4502
+ if (meta.category) {
4503
+ categories.add(String(meta.category));
4504
+ }
4505
+ if (meta.type) {
4506
+ categories.add(String(meta.type));
4507
+ }
4508
+ if (meta.tag) {
4509
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4510
+ tags.forEach((t) => categories.add(String(t)));
4511
+ }
4512
+ const contentCategories = Array.from(new Set(
4513
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
4514
+ ));
4515
+ contentCategories.forEach((category) => categories.add(category));
4516
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4517
+ if (categoryMatch) {
4518
+ categories.add(categoryMatch[1].trim());
4556
4519
  }
4557
4520
  });
4558
4521
  return Array.from(categories);
4559
4522
  }
4560
4523
  /**
4561
- * Helper: Aggregate data dynamically
4524
+ * Helper: Aggregate data by category
4562
4525
  */
4563
- static aggregateByCategory(data, categories, groupByField) {
4526
+ static aggregateByCategory(data, categories) {
4564
4527
  const result = {};
4565
4528
  categories.forEach((cat) => {
4566
4529
  result[cat] = 0;
4567
4530
  });
4568
4531
  data.forEach((item) => {
4569
4532
  const meta = item.metadata || {};
4570
- let itemCategory = "Other";
4571
- if (meta[groupByField] !== void 0) {
4572
- itemCategory = String(meta[groupByField]);
4573
- } else if (groupByField === "category" && meta.type) {
4574
- itemCategory = String(meta.type);
4575
- }
4533
+ const itemCategory = meta.category || meta.type || "Other";
4576
4534
  if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
4577
4535
  result[itemCategory]++;
4578
4536
  } else {
@@ -4622,19 +4580,14 @@ var UITransformer = class {
4622
4580
  });
4623
4581
  }
4624
4582
  /**
4625
- * Helper: Calculate stock counts dynamically
4583
+ * Helper: Calculate stock counts by category
4626
4584
  */
4627
- static calculateStockCounts(category, data, groupByField) {
4585
+ static calculateStockCounts(category, data) {
4628
4586
  let inStock = 0;
4629
4587
  let outOfStock = 0;
4630
4588
  data.forEach((d) => {
4631
4589
  const meta = d.metadata || {};
4632
- let itemCategory = "Other";
4633
- if (meta[groupByField] !== void 0) {
4634
- itemCategory = String(meta[groupByField]);
4635
- } else if (groupByField === "category" && meta.type) {
4636
- itemCategory = String(meta.type);
4637
- }
4590
+ const itemCategory = meta.category || meta.type || "Other";
4638
4591
  if (itemCategory === category) {
4639
4592
  if (this.determineStockStatus(d)) {
4640
4593
  inStock++;
@@ -4759,13 +4712,12 @@ DATA SHAPE per type:
4759
4712
  - text: { "content": "<prose answer>" }
4760
4713
 
4761
4714
  DECISION RULES (follow strictly):
4762
- 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.
4763
- 2. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
4764
- 3. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4765
- 4. pie_chart \u2192 proportional breakdown or percentage distribution
4766
- 5. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes.
4767
- 6. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4768
- 7. text \u2192 conversational or free-form answers where no chart adds value
4715
+ 1. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
4716
+ 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4717
+ 3. pie_chart \u2192 proportional breakdown or percentage distribution
4718
+ 4. radar_chart \u2192 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.
4719
+ 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4720
+ 6. text \u2192 conversational or free-form answers where no chart adds value
4769
4721
 
4770
4722
  IMPORTANT:
4771
4723
  - Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
@@ -1,4 +1,4 @@
1
- import { k as RagConfig, I as ILLMProvider, C as ChatMessage, d as ChatResponse, g as IngestDocument } from './ILLMProvider-CbUtvWAW.js';
1
+ import { k as RagConfig, I as ILLMProvider, C as ChatMessage, d as ChatResponse, g as IngestDocument } from './ILLMProvider-BOJFz3Na.js';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
@@ -1,4 +1,4 @@
1
- import { k as RagConfig, I as ILLMProvider, C as ChatMessage, d as ChatResponse, g as IngestDocument } from './ILLMProvider-CbUtvWAW.mjs';
1
+ import { k as RagConfig, I as ILLMProvider, C as ChatMessage, d as ChatResponse, g as IngestDocument } from './ILLMProvider-BOJFz3Na.mjs';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {