@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.
- package/LICENSE.txt +21 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.mts → ILLMProvider-CbUtvWAW.d.mts} +2 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.ts → ILLMProvider-CbUtvWAW.d.ts} +2 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +100 -52
- package/dist/handlers/index.mjs +100 -52
- package/dist/{index-BwpcaziY.d.ts → index-DgzcnqZs.d.ts} +1 -1
- package/dist/{index-D3V9Et2M.d.mts → index-c_y_5qdw.d.mts} +1 -1
- package/dist/index.css +3189 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -4
- package/dist/index.mjs +18 -4
- package/dist/server.d.mts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +108 -55
- package/dist/server.mjs +108 -55
- package/package.json +4 -2
- package/src/app/globals.css +97 -0
- package/src/components/ChatWidget.tsx +23 -10
- package/src/config/RagConfig.ts +2 -0
- package/src/providers/vectordb/ChromaDBProvider.ts +4 -0
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +18 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +14 -3
- package/src/providers/vectordb/QdrantProvider.ts +4 -1
- package/src/tailwind.css +2 -0
- package/src/utils/UITransformer.ts +99 -51
package/dist/server.mjs
CHANGED
|
@@ -353,10 +353,12 @@ 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";
|
|
356
358
|
await client.query(`
|
|
357
359
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
358
360
|
ON ${this.uploadTable}
|
|
359
|
-
USING hnsw (embedding
|
|
361
|
+
USING hnsw (embedding ${indexOps})
|
|
360
362
|
`);
|
|
361
363
|
const res = await client.query(`
|
|
362
364
|
SELECT table_name
|
|
@@ -418,10 +420,12 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
418
420
|
)
|
|
419
421
|
`;
|
|
420
422
|
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";
|
|
421
425
|
await client.query(`
|
|
422
426
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
423
427
|
ON "${tableName}"
|
|
424
|
-
USING hnsw (embedding
|
|
428
|
+
USING hnsw (embedding ${indexOps})
|
|
425
429
|
`);
|
|
426
430
|
if (!this.tables.includes(tableName)) {
|
|
427
431
|
this.tables.push(tableName);
|
|
@@ -503,6 +507,8 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
503
507
|
try {
|
|
504
508
|
let sqlQuery = "";
|
|
505
509
|
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)`;
|
|
506
512
|
if (queryText) {
|
|
507
513
|
const hasEntityHints = entityHints.length > 0;
|
|
508
514
|
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
@@ -515,9 +521,9 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
515
521
|
) * 3.0` : "";
|
|
516
522
|
sqlQuery = `
|
|
517
523
|
SELECT *,
|
|
518
|
-
(
|
|
524
|
+
(${scoreExpr}) AS vector_score,
|
|
519
525
|
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
520
|
-
((
|
|
526
|
+
((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
521
527
|
FROM "${table}" t
|
|
522
528
|
ORDER BY hybrid_score DESC
|
|
523
529
|
LIMIT 50
|
|
@@ -526,7 +532,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
526
532
|
} else {
|
|
527
533
|
sqlQuery = `
|
|
528
534
|
SELECT *,
|
|
529
|
-
(
|
|
535
|
+
(${scoreExpr}) AS hybrid_score
|
|
530
536
|
FROM "${table}" t
|
|
531
537
|
ORDER BY hybrid_score DESC
|
|
532
538
|
LIMIT 50
|
|
@@ -1000,11 +1006,13 @@ var init_QdrantProvider = __esm({
|
|
|
1000
1006
|
if (axios4.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
1001
1007
|
const opts = this.config.options;
|
|
1002
1008
|
const dimensionsForCreate = opts.dimensions || 1536;
|
|
1009
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
1010
|
+
const distance = metric === "euclidean" ? "Euclid" : metric === "dotproduct" ? "Dot" : "Cosine";
|
|
1003
1011
|
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
|
|
1004
1012
|
await this.http.put(`/collections/${this.indexName}`, {
|
|
1005
1013
|
vectors: {
|
|
1006
1014
|
size: dimensionsForCreate,
|
|
1007
|
-
distance
|
|
1015
|
+
distance
|
|
1008
1016
|
}
|
|
1009
1017
|
});
|
|
1010
1018
|
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
@@ -1182,9 +1190,12 @@ var init_ChromaDBProvider = __esm({
|
|
|
1182
1190
|
console.log(`[ChromaDBProvider] \u2705 Collection "${this.indexName}" found (id: ${this.collectionId})`);
|
|
1183
1191
|
} catch (err) {
|
|
1184
1192
|
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";
|
|
1185
1195
|
console.log(`[ChromaDBProvider] \u23F3 Collection "${this.indexName}" not found. Creating...`);
|
|
1186
1196
|
const { data } = await this.http.post("/api/v1/collections", {
|
|
1187
|
-
name: this.indexName
|
|
1197
|
+
name: this.indexName,
|
|
1198
|
+
metadata: { "hnsw:space": space }
|
|
1188
1199
|
});
|
|
1189
1200
|
this.collectionId = data.id;
|
|
1190
1201
|
console.log(`[ChromaDBProvider] \u2705 Created collection "${this.indexName}" (id: ${this.collectionId})`);
|
|
@@ -4173,18 +4184,20 @@ var UITransformer = class {
|
|
|
4173
4184
|
}
|
|
4174
4185
|
const isStockRequest = this.isStockQuery(userQuery);
|
|
4175
4186
|
const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
|
|
4176
|
-
const
|
|
4187
|
+
const groupByField = this.determineGroupByField(userQuery, filteredData);
|
|
4188
|
+
const categories = this.detectCategories(filteredData, groupByField);
|
|
4177
4189
|
const hasProducts = filteredData.some((item) => this.isProductData(item));
|
|
4178
4190
|
const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
|
|
4179
4191
|
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
4180
4192
|
if (isTrendQuery && isTimeSeries) {
|
|
4181
4193
|
return this.transformToLineChart(filteredData);
|
|
4182
4194
|
}
|
|
4183
|
-
|
|
4195
|
+
const explicitChartRequest = this.isExplicitChartQuery(userQuery);
|
|
4196
|
+
if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
|
|
4184
4197
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
4185
4198
|
}
|
|
4186
|
-
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery
|
|
4187
|
-
return this.transformToPieChart(filteredData);
|
|
4199
|
+
if (explicitChartRequest || categories.length > 1 && this.shouldShowCategoryChart(userQuery)) {
|
|
4200
|
+
return this.transformToPieChart(filteredData, groupByField);
|
|
4188
4201
|
}
|
|
4189
4202
|
if (hasProducts) {
|
|
4190
4203
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
@@ -4209,11 +4222,12 @@ var UITransformer = class {
|
|
|
4209
4222
|
/**
|
|
4210
4223
|
* Transform data to pie chart format
|
|
4211
4224
|
*/
|
|
4212
|
-
static transformToPieChart(data) {
|
|
4213
|
-
|
|
4214
|
-
|
|
4225
|
+
static transformToPieChart(data, groupByField) {
|
|
4226
|
+
let categories = this.detectCategories(data, groupByField);
|
|
4227
|
+
if (categories.length === 0) categories = ["All Data"];
|
|
4228
|
+
const categoryData = this.aggregateByCategory(data, categories, groupByField);
|
|
4215
4229
|
const pieData = Object.entries(categoryData).map(([label, count]) => {
|
|
4216
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
4230
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
|
|
4217
4231
|
return {
|
|
4218
4232
|
label,
|
|
4219
4233
|
value: count,
|
|
@@ -4223,7 +4237,7 @@ var UITransformer = class {
|
|
|
4223
4237
|
});
|
|
4224
4238
|
return {
|
|
4225
4239
|
type: "pie_chart",
|
|
4226
|
-
title:
|
|
4240
|
+
title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
|
|
4227
4241
|
description: `Showing breakdown across ${categories.length} categories`,
|
|
4228
4242
|
data: pieData
|
|
4229
4243
|
};
|
|
@@ -4446,10 +4460,11 @@ var UITransformer = class {
|
|
|
4446
4460
|
});
|
|
4447
4461
|
return hasTimeKeyword || hasValidDateValue;
|
|
4448
4462
|
}
|
|
4449
|
-
static
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4463
|
+
static isExplicitChartQuery(query) {
|
|
4464
|
+
const normalized = query.toLowerCase();
|
|
4465
|
+
return normalized.includes("pie chart") || normalized.includes("piechart") || normalized.includes("bar chart") || normalized.includes("barchart") || normalized.includes("radar chart");
|
|
4466
|
+
}
|
|
4467
|
+
static shouldShowCategoryChart(query) {
|
|
4453
4468
|
const normalized = query.toLowerCase();
|
|
4454
4469
|
const chartKeywords = [
|
|
4455
4470
|
"distribution",
|
|
@@ -4462,7 +4477,11 @@ var UITransformer = class {
|
|
|
4462
4477
|
"segmentation",
|
|
4463
4478
|
"split",
|
|
4464
4479
|
"category breakdown",
|
|
4465
|
-
"category distribution"
|
|
4480
|
+
"category distribution",
|
|
4481
|
+
"pie chart",
|
|
4482
|
+
"piechart",
|
|
4483
|
+
"bar chart",
|
|
4484
|
+
"barchart"
|
|
4466
4485
|
];
|
|
4467
4486
|
return chartKeywords.some((keyword) => normalized.includes(keyword));
|
|
4468
4487
|
}
|
|
@@ -4538,44 +4557,67 @@ var UITransformer = class {
|
|
|
4538
4557
|
return null;
|
|
4539
4558
|
}
|
|
4540
4559
|
/**
|
|
4541
|
-
* Helper:
|
|
4560
|
+
* Helper: Dynamically determine what field to group by based on user query
|
|
4561
|
+
*/
|
|
4562
|
+
static determineGroupByField(query, data) {
|
|
4563
|
+
const normalized = query.toLowerCase();
|
|
4564
|
+
const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
|
|
4565
|
+
let targetField = "category";
|
|
4566
|
+
if (match && match[1]) {
|
|
4567
|
+
let field = match[1].toLowerCase();
|
|
4568
|
+
if (field.endsWith("ies")) field = field.replace(/ies$/, "y");
|
|
4569
|
+
else if (field.endsWith("s") && field !== "status") field = field.slice(0, -1);
|
|
4570
|
+
const stopWords = ["the", "all", "different", "various", "some", "these", "those"];
|
|
4571
|
+
if (!stopWords.includes(field)) {
|
|
4572
|
+
targetField = field;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
const hasTargetField = data.some((d) => d.metadata && d.metadata[targetField] !== void 0);
|
|
4576
|
+
if (!hasTargetField && targetField === "category") {
|
|
4577
|
+
if (data.some((d) => d.metadata && d.metadata["type"] !== void 0)) return "type";
|
|
4578
|
+
if (data.some((d) => d.metadata && d.metadata["brand"] !== void 0)) return "brand";
|
|
4579
|
+
}
|
|
4580
|
+
return targetField;
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Helper: Detect grouping values dynamically
|
|
4542
4584
|
*/
|
|
4543
|
-
static detectCategories(data) {
|
|
4585
|
+
static detectCategories(data, groupByField) {
|
|
4544
4586
|
const categories = /* @__PURE__ */ new Set();
|
|
4545
4587
|
data.forEach((item) => {
|
|
4546
4588
|
const meta = item.metadata || {};
|
|
4547
|
-
if (meta
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
));
|
|
4560
|
-
contentCategories.forEach((category) => categories.add(category));
|
|
4561
|
-
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4562
|
-
if (categoryMatch) {
|
|
4563
|
-
categories.add(categoryMatch[1].trim());
|
|
4589
|
+
if (meta[groupByField] !== void 0) {
|
|
4590
|
+
const val = meta[groupByField];
|
|
4591
|
+
if (Array.isArray(val)) val.forEach((v) => categories.add(String(v)));
|
|
4592
|
+
else categories.add(String(val));
|
|
4593
|
+
} else if (groupByField === "category") {
|
|
4594
|
+
if (meta.type) categories.add(String(meta.type));
|
|
4595
|
+
if (meta.tag) {
|
|
4596
|
+
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
4597
|
+
tags.forEach((t) => categories.add(String(t)));
|
|
4598
|
+
}
|
|
4599
|
+
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4600
|
+
if (categoryMatch) categories.add(categoryMatch[1].trim());
|
|
4564
4601
|
}
|
|
4565
4602
|
});
|
|
4566
4603
|
return Array.from(categories);
|
|
4567
4604
|
}
|
|
4568
4605
|
/**
|
|
4569
|
-
* Helper: Aggregate data
|
|
4606
|
+
* Helper: Aggregate data dynamically
|
|
4570
4607
|
*/
|
|
4571
|
-
static aggregateByCategory(data, categories) {
|
|
4608
|
+
static aggregateByCategory(data, categories, groupByField) {
|
|
4572
4609
|
const result = {};
|
|
4573
4610
|
categories.forEach((cat) => {
|
|
4574
4611
|
result[cat] = 0;
|
|
4575
4612
|
});
|
|
4576
4613
|
data.forEach((item) => {
|
|
4577
4614
|
const meta = item.metadata || {};
|
|
4578
|
-
|
|
4615
|
+
let itemCategory = "Other";
|
|
4616
|
+
if (meta[groupByField] !== void 0) {
|
|
4617
|
+
itemCategory = String(meta[groupByField]);
|
|
4618
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4619
|
+
itemCategory = String(meta.type);
|
|
4620
|
+
}
|
|
4579
4621
|
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
4580
4622
|
result[itemCategory]++;
|
|
4581
4623
|
} else {
|
|
@@ -4625,14 +4667,19 @@ var UITransformer = class {
|
|
|
4625
4667
|
});
|
|
4626
4668
|
}
|
|
4627
4669
|
/**
|
|
4628
|
-
* Helper: Calculate stock counts
|
|
4670
|
+
* Helper: Calculate stock counts dynamically
|
|
4629
4671
|
*/
|
|
4630
|
-
static calculateStockCounts(category, data) {
|
|
4672
|
+
static calculateStockCounts(category, data, groupByField) {
|
|
4631
4673
|
let inStock = 0;
|
|
4632
4674
|
let outOfStock = 0;
|
|
4633
4675
|
data.forEach((d) => {
|
|
4634
4676
|
const meta = d.metadata || {};
|
|
4635
|
-
|
|
4677
|
+
let itemCategory = "Other";
|
|
4678
|
+
if (meta[groupByField] !== void 0) {
|
|
4679
|
+
itemCategory = String(meta[groupByField]);
|
|
4680
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4681
|
+
itemCategory = String(meta.type);
|
|
4682
|
+
}
|
|
4636
4683
|
if (itemCategory === category) {
|
|
4637
4684
|
if (this.determineStockStatus(d)) {
|
|
4638
4685
|
inStock++;
|
|
@@ -4757,12 +4804,13 @@ DATA SHAPE per type:
|
|
|
4757
4804
|
- text: { "content": "<prose answer>" }
|
|
4758
4805
|
|
|
4759
4806
|
DECISION RULES (follow strictly):
|
|
4760
|
-
1.
|
|
4761
|
-
2.
|
|
4762
|
-
3.
|
|
4763
|
-
4.
|
|
4764
|
-
5.
|
|
4765
|
-
6.
|
|
4807
|
+
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.
|
|
4808
|
+
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.
|
|
4809
|
+
3. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
|
|
4810
|
+
4. pie_chart \u2192 proportional breakdown or percentage distribution
|
|
4811
|
+
5. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes.
|
|
4812
|
+
6. table \u2192 multi-field structured records where each item has \u2265 3 attributes
|
|
4813
|
+
7. text \u2192 conversational or free-form answers where no chart adds value
|
|
4766
4814
|
|
|
4767
4815
|
IMPORTANT:
|
|
4768
4816
|
- Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
|
|
@@ -5920,10 +5968,12 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
5920
5968
|
embedding VECTOR(${this.dimensions})
|
|
5921
5969
|
)
|
|
5922
5970
|
`);
|
|
5971
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
5972
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
5923
5973
|
await client.query(`
|
|
5924
5974
|
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
5925
5975
|
ON ${this.tableName}
|
|
5926
|
-
USING hnsw (embedding
|
|
5976
|
+
USING hnsw (embedding ${indexOps})
|
|
5927
5977
|
`);
|
|
5928
5978
|
} finally {
|
|
5929
5979
|
client.release();
|
|
@@ -5995,11 +6045,14 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
5995
6045
|
try {
|
|
5996
6046
|
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
5997
6047
|
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
6048
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
6049
|
+
const queryOp = metric === "euclidean" ? "<->" : metric === "dotproduct" ? "<#>" : "<=>";
|
|
6050
|
+
const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
|
|
5998
6051
|
const result = await client.query(
|
|
5999
|
-
`SELECT id, content, metadata,
|
|
6052
|
+
`SELECT id, content, metadata, ${scoreExpr} AS score
|
|
6000
6053
|
FROM ${this.tableName}
|
|
6001
6054
|
${whereClause}
|
|
6002
|
-
ORDER BY embedding
|
|
6055
|
+
ORDER BY embedding ${queryOp} $1::vector
|
|
6003
6056
|
LIMIT $2`,
|
|
6004
6057
|
params
|
|
6005
6058
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
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",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"require": "./dist/index.js",
|
|
37
37
|
"import": "./dist/index.mjs"
|
|
38
38
|
},
|
|
39
|
+
"./style.css": "./dist/index.css",
|
|
39
40
|
"./handlers": {
|
|
40
41
|
"types": "./dist/handlers/index.d.ts",
|
|
41
42
|
"require": "./dist/handlers/index.js",
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
"scripts": {
|
|
74
75
|
"dev": "next dev",
|
|
75
76
|
"build": "next build",
|
|
76
|
-
"build:pkg": "tsup src/index.ts src/handlers/index.ts src/server.ts --format cjs,esm --dts --clean --no-splitting --tsconfig tsconfig.build.json --external react,react-dom,next,mongodb,pg,openai,@anthropic-ai/sdk,@pinecone-database/pinecone,axios,lucide-react,mammoth,pdf-parse,react-markdown,remark-gfm,next-themes,langchain,@langchain/core,@langchain/openai,llamaindex",
|
|
77
|
+
"build:pkg": "tsup src/index.ts src/handlers/index.ts src/server.ts --format cjs,esm --dts --clean --no-splitting --tsconfig tsconfig.build.json --external react,react-dom,next,mongodb,pg,openai,@anthropic-ai/sdk,@pinecone-database/pinecone,axios,lucide-react,mammoth,pdf-parse,react-markdown,remark-gfm,next-themes,langchain,@langchain/core,@langchain/openai,llamaindex && npx @tailwindcss/cli -i src/tailwind.css -o dist/index.css",
|
|
77
78
|
"start": "next start",
|
|
78
79
|
"lint": "eslint",
|
|
79
80
|
"prepublishOnly": "npm run build:pkg"
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
"pg": "^8.20.0"
|
|
113
114
|
},
|
|
114
115
|
"devDependencies": {
|
|
116
|
+
"@tailwindcss/cli": "^4",
|
|
115
117
|
"@tailwindcss/postcss": "^4",
|
|
116
118
|
"@types/estree": "^1.0.9",
|
|
117
119
|
"@types/node": "^20",
|
package/src/app/globals.css
CHANGED
|
@@ -2,7 +2,104 @@
|
|
|
2
2
|
|
|
3
3
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
4
4
|
|
|
5
|
+
@theme {
|
|
6
|
+
--animate-shapeshift: shapeshift 5s ease-in-out infinite forwards;
|
|
7
|
+
--animate-gradientMove: gradientMove 3.5s ease-in-out infinite alternate;
|
|
8
|
+
|
|
9
|
+
@keyframes shapeshift {
|
|
10
|
+
0% { clip-path: var(--circle); rotate: 0turn; }
|
|
11
|
+
25% { clip-path: var(--flower); }
|
|
12
|
+
50% { clip-path: var(--cylinder); }
|
|
13
|
+
75% { clip-path: var(--hexagon); }
|
|
14
|
+
100% { clip-path: var(--circle); rotate: 1turn; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes gradientMove {
|
|
18
|
+
0% { translate: 0 0; }
|
|
19
|
+
100% { translate: -75% -75%; }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
|
|
23
|
+
@layer base {
|
|
24
|
+
:root {
|
|
25
|
+
--star: shape(
|
|
26
|
+
evenodd from 50% 24.787%,
|
|
27
|
+
curve by 7.143% 18.016% with 0% 0% / 2.9725% 13.814%,
|
|
28
|
+
curve by 17.882% 7.197% with 4.171% 4.2025% / 17.882% 7.197%,
|
|
29
|
+
curve by -17.882% 8.6765% with 0% 0% / -13.711% 4.474%,
|
|
30
|
+
curve by -7.143% 16.5365% with -4.1705% 4.202% / -7.143% 16.5365%,
|
|
31
|
+
curve by -8.6115% -16.5365% with 0% 0% / -4.441% -12.3345%,
|
|
32
|
+
curve by -16.4135% -8.6765% with -4.171% -4.2025% / -16.4135% -8.6765%,
|
|
33
|
+
curve by 16.4135% -7.197% with 0% 0% / 12.2425% -2.9945%,
|
|
34
|
+
curve by 8.6115% -18.016% with 4.1705% -4.202% / 8.6115% -18.016%,
|
|
35
|
+
close
|
|
36
|
+
);
|
|
37
|
+
--flower: shape(
|
|
38
|
+
evenodd from 17.9665% 82.0335%,
|
|
39
|
+
curve by -12.349% -32.0335% with -13.239% -5.129% / -18.021% -15.402%,
|
|
40
|
+
curve by -0.0275% -22.203% with -3.1825% -9.331% / -3.074% -16.6605%,
|
|
41
|
+
curve by 12.3765% -9.8305% with 2.3835% -4.3365% / 6.565% -7.579%,
|
|
42
|
+
curve by 32.0335% -12.349% with 5.129% -13.239% / 15.402% -18.021%,
|
|
43
|
+
curve by 20.4535% -0.8665% with 8.3805% -2.858% / 15.1465% -3.062%,
|
|
44
|
+
curve by 11.58% 13.2155% with 5.225% 2.161% / 9.0355% 6.6475%,
|
|
45
|
+
curve by 12.349% 32.0335% with 13.239% 5.129% / 18.021% 15.402%,
|
|
46
|
+
curve by 0.5715% 21.1275% with 2.9805% 8.7395% / 3.0745% 15.723%,
|
|
47
|
+
curve by -12.9205% 10.906% with -2.26% 4.88% / -6.638% 8.472%,
|
|
48
|
+
curve by -32.0335% 12.349% with -5.129% 13.239% / -15.402% 18.021%,
|
|
49
|
+
curve by -21.1215% 0.5745% with -8.736% 2.9795% / -15.718% 3.0745%,
|
|
50
|
+
curve by -10.912% -12.9235% with -4.883% -2.2595% / -8.477% -6.6385%,
|
|
51
|
+
close
|
|
52
|
+
);
|
|
53
|
+
--hexagon: shape(
|
|
54
|
+
evenodd from 6.47% 67.001%,
|
|
55
|
+
curve by 0% -34.002% with -1.1735% -7.7% / -1.1735% -26.302%,
|
|
56
|
+
curve by 7.0415% -12.1965% with 0.7075% -4.641% / 3.3765% -9.2635%,
|
|
57
|
+
curve by 29.447% -17.001% with 6.0815% -4.8665% / 22.192% -14.1675%,
|
|
58
|
+
curve by 14.083% 0% with 4.3725% -1.708% / 9.7105% -1.708%,
|
|
59
|
+
curve by 29.447% 17.001% with 7.255% 2.8335% / 23.3655% 12.1345%,
|
|
60
|
+
curve by 7.0415% 12.1965% with 3.665% 2.933% / 6.334% 7.5555%,
|
|
61
|
+
curve by 0% 34.002% with 1.1735% 7.7% / 1.1735% 26.302%,
|
|
62
|
+
curve by -7.0415% 12.1965% with -0.7075% 4.641% / -3.3765% 9.2635%,
|
|
63
|
+
curve by -29.447% 17.001% with -6.0815% 4.8665% / -22.192% 14.1675%,
|
|
64
|
+
curve by -14.083% 0% with -4.3725% 1.708% / -9.7105% 1.708%,
|
|
65
|
+
curve by -29.447% -17.001% with -7.255% -2.8335% / -23.3655% -12.1345%,
|
|
66
|
+
curve by -7.0415% -12.1965% with -3.665% -2.933% / -6.334% -7.5555%,
|
|
67
|
+
close
|
|
68
|
+
);
|
|
69
|
+
--cylinder: shape(
|
|
70
|
+
evenodd from 10.5845% 59.7305%,
|
|
71
|
+
curve by 0% -19.461% with -0.113% -1.7525% / -0.11% -18.14%,
|
|
72
|
+
curve by 10.098% -26.213% with 0.837% -10.0375% / 3.821% -19.2625%,
|
|
73
|
+
curve by 29.3175% -13.0215% with 7.2175% -7.992% / 17.682% -13.0215%,
|
|
74
|
+
curve by 19.5845% 5.185% with 7.1265% 0% / 13.8135% 1.887%,
|
|
75
|
+
curve by 9.8595% 7.9775% with 3.7065% 2.1185% / 7.035% 4.8195%,
|
|
76
|
+
curve by 9.9715% 26.072% with 6.2015% 6.933% / 9.4345% 16.082%,
|
|
77
|
+
curve by 0% 19.461% with 0.074% 1.384% / 0.0745% 17.7715%,
|
|
78
|
+
curve by -13.0065% 29.1155% with -0.511% 11.5345% / -5.021% 21.933%,
|
|
79
|
+
curve by -26.409% 10.119% with -6.991% 6.288% / -16.254% 10.119%,
|
|
80
|
+
curve by -20.945% -5.9995% with -7.6935% 0% / -14.8755% -2.199%,
|
|
81
|
+
curve by -8.713% -7.404% with -3.255% -2.0385% / -6.1905% -4.537%,
|
|
82
|
+
curve by -9.7575% -25.831% with -6.074% -6.9035% / -9.1205% -15.963%,
|
|
83
|
+
close
|
|
84
|
+
);
|
|
85
|
+
--circle: shape(
|
|
86
|
+
evenodd from 13.482% 79.505%,
|
|
87
|
+
curve by -7.1945% -12.47% with -1.4985% -1.8575% / -6.328% -10.225%,
|
|
88
|
+
curve by 0.0985% -33.8965% with -4.1645% -10.7945% / -4.1685% -23.0235%,
|
|
89
|
+
curve by 6.9955% -12.101% with 1.72% -4.3825% / 4.0845% -8.458%,
|
|
90
|
+
curve by 30.125% -17.119% with 7.339% -9.1825% / 18.4775% -15.5135%,
|
|
91
|
+
curve by 13.4165% 0.095% with 4.432% -0.6105% / 8.9505% -0.5855%,
|
|
92
|
+
curve by 29.364% 16.9% with 11.6215% 1.77% / 22.102% 7.9015%,
|
|
93
|
+
curve by 7.176% 12.4145% with 3.002% 3.7195% / 5.453% 7.968%,
|
|
94
|
+
curve by -0.0475% 33.8925% with 4.168% 10.756% / 4.2305% 22.942%,
|
|
95
|
+
curve by -7.1135% 12.2825% with -1.74% 4.4535% / -4.1455% 8.592%,
|
|
96
|
+
curve by -29.404% 16.9075% with -7.202% 8.954% / -18.019% 15.137%,
|
|
97
|
+
curve by -14.19% -0.018% with -4.6635% 0.7255% / -9.4575% 0.7205%,
|
|
98
|
+
curve by -29.226% -16.8875% with -11.573% -1.8065% / -21.9955% -7.9235%,
|
|
99
|
+
close
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
6
103
|
|
|
7
104
|
/* ─── Design Tokens ─────────────────────────────────────────── */
|
|
8
105
|
:root {
|
|
@@ -119,25 +119,38 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart }: ChatWidge
|
|
|
119
119
|
{/* ── Floating Action Button ── */}
|
|
120
120
|
<button
|
|
121
121
|
onClick={isOpen ? () => setIsOpen(false) : handleOpen}
|
|
122
|
-
className={`fixed z-[9999] w-14 h-14 shadow-2xl flex items-center justify-center transition-all duration-300 cursor-pointer hover:scale-110 active:scale-95 ${
|
|
123
|
-
ui.borderRadius === 'full' ? 'rounded-full' : 'rounded-2xl'
|
|
124
|
-
} ${positionClass}`}
|
|
125
|
-
style={{ background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` }}
|
|
122
|
+
className={`group fixed z-[9999] w-14 h-14 drop-shadow-2xl flex items-center justify-center transition-all duration-300 cursor-pointer hover:scale-110 active:scale-95 ${positionClass}`}
|
|
126
123
|
aria-label="Open chat"
|
|
127
124
|
>
|
|
125
|
+
{/* Animated Background */}
|
|
126
|
+
<div
|
|
127
|
+
className={`absolute inset-0 transition-all duration-300 overflow-hidden ${
|
|
128
|
+
ui.borderRadius === 'full' ? 'rounded-full' : 'rounded-2xl'
|
|
129
|
+
} group-hover:rounded-none group-hover:animate-shapeshift`}
|
|
130
|
+
>
|
|
131
|
+
<div
|
|
132
|
+
className="absolute top-0 left-0 w-[400%] h-[400%] transition-transform duration-300 group-hover:animate-gradientMove"
|
|
133
|
+
style={{
|
|
134
|
+
background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor}, ${ui.primaryColor}, ${ui.accentColor})`
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
128
139
|
{/* Ripple ring */}
|
|
129
140
|
<span
|
|
130
|
-
className="absolute inset-0 rounded-full animate-ping opacity-20"
|
|
141
|
+
className="absolute inset-0 rounded-full animate-ping opacity-20 pointer-events-none"
|
|
131
142
|
style={{ background: ui.primaryColor }}
|
|
132
143
|
/>
|
|
133
144
|
|
|
145
|
+
{/* Icon Layer */}
|
|
134
146
|
<span
|
|
135
|
-
className={`transition-transform duration-300 ${isOpen ? 'rotate-90 scale-90' : 'rotate-0 scale-100'}`}
|
|
147
|
+
className={`relative z-10 transition-transform duration-300 ${isOpen ? 'rotate-90 scale-90' : 'rotate-0 scale-100'}`}
|
|
136
148
|
>
|
|
137
|
-
{isOpen
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
{isOpen ? (
|
|
150
|
+
<X className="w-6 h-6 text-white" />
|
|
151
|
+
) : (
|
|
152
|
+
<MessageSquare className="w-6 h-6 text-white" />
|
|
153
|
+
)}
|
|
141
154
|
</span>
|
|
142
155
|
|
|
143
156
|
{/* Unread badge */}
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface VectorDBConfig {
|
|
|
44
44
|
* - searchFields?: string[] | string // optional override for which columns receive exact-match boosts
|
|
45
45
|
*/
|
|
46
46
|
options: Record<string, unknown>;
|
|
47
|
+
/** Optional distance metric (defaults to cosine if not specified and supported by provider) */
|
|
48
|
+
distanceMetric?: 'cosine' | 'euclidean' | 'dotproduct';
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
@@ -41,9 +41,13 @@ export class ChromaDBProvider extends BaseVectorProvider {
|
|
|
41
41
|
} catch (err) {
|
|
42
42
|
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
|
43
43
|
// Create the collection if it doesn't exist
|
|
44
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
45
|
+
const space = metric === 'euclidean' ? 'l2' : metric === 'dotproduct' ? 'ip' : 'cosine';
|
|
46
|
+
|
|
44
47
|
console.log(`[ChromaDBProvider] ⏳ Collection "${this.indexName}" not found. Creating...`);
|
|
45
48
|
const { data } = await this.http.post('/api/v1/collections', {
|
|
46
49
|
name: this.indexName,
|
|
50
|
+
metadata: { "hnsw:space": space }
|
|
47
51
|
});
|
|
48
52
|
this.collectionId = data.id;
|
|
49
53
|
console.log(`[ChromaDBProvider] ✅ Created collection "${this.indexName}" (id: ${this.collectionId})`);
|
|
@@ -66,10 +66,13 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
66
66
|
embedding VECTOR(${this.dimensions})
|
|
67
67
|
)
|
|
68
68
|
`);
|
|
69
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
70
|
+
const indexOps = metric === 'euclidean' ? 'vector_l2_ops' : metric === 'dotproduct' ? 'vector_ip_ops' : 'vector_cosine_ops';
|
|
71
|
+
|
|
69
72
|
await client.query(`
|
|
70
73
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
71
74
|
ON ${this.uploadTable}
|
|
72
|
-
USING hnsw (embedding
|
|
75
|
+
USING hnsw (embedding ${indexOps})
|
|
73
76
|
`);
|
|
74
77
|
|
|
75
78
|
const res = await client.query(`
|
|
@@ -147,10 +150,13 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
147
150
|
await client.query(createTableSql);
|
|
148
151
|
|
|
149
152
|
// Create Index
|
|
153
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
154
|
+
const indexOps = metric === 'euclidean' ? 'vector_l2_ops' : metric === 'dotproduct' ? 'vector_ip_ops' : 'vector_cosine_ops';
|
|
155
|
+
|
|
150
156
|
await client.query(`
|
|
151
157
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
152
158
|
ON "${tableName}"
|
|
153
|
-
USING hnsw (embedding
|
|
159
|
+
USING hnsw (embedding ${indexOps})
|
|
154
160
|
`);
|
|
155
161
|
|
|
156
162
|
// Ensure this new table is added to our search scope
|
|
@@ -274,6 +280,13 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
274
280
|
let sqlQuery = '';
|
|
275
281
|
let params: unknown[] = [];
|
|
276
282
|
|
|
283
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
284
|
+
const scoreExpr = metric === 'euclidean'
|
|
285
|
+
? `1 / (1 + (embedding <-> $1::vector))`
|
|
286
|
+
: metric === 'dotproduct'
|
|
287
|
+
? `(embedding <#> $1::vector) * -1`
|
|
288
|
+
: `1 - (embedding <=> $1::vector)`;
|
|
289
|
+
|
|
277
290
|
if (queryText) {
|
|
278
291
|
const hasEntityHints = entityHints.length > 0;
|
|
279
292
|
const exactNameScoreExpr = hasEntityHints
|
|
@@ -290,9 +303,9 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
290
303
|
// Actual Hybrid Search: Semantic Vector Score + Full-Text Keyword Rank
|
|
291
304
|
sqlQuery = `
|
|
292
305
|
SELECT *,
|
|
293
|
-
(
|
|
306
|
+
(${scoreExpr}) AS vector_score,
|
|
294
307
|
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
295
|
-
((
|
|
308
|
+
((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
296
309
|
FROM "${table}" t
|
|
297
310
|
ORDER BY hybrid_score DESC
|
|
298
311
|
LIMIT 50
|
|
@@ -302,7 +315,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
302
315
|
// Fallback to Vector-only Search
|
|
303
316
|
sqlQuery = `
|
|
304
317
|
SELECT *,
|
|
305
|
-
(
|
|
318
|
+
(${scoreExpr}) AS hybrid_score
|
|
306
319
|
FROM "${table}" t
|
|
307
320
|
ORDER BY hybrid_score DESC
|
|
308
321
|
LIMIT 50
|
|
@@ -97,10 +97,13 @@ export class PostgreSQLProvider extends BaseVectorProvider {
|
|
|
97
97
|
embedding VECTOR(${this.dimensions})
|
|
98
98
|
)
|
|
99
99
|
`);
|
|
100
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
101
|
+
const indexOps = metric === 'euclidean' ? 'vector_l2_ops' : metric === 'dotproduct' ? 'vector_ip_ops' : 'vector_cosine_ops';
|
|
102
|
+
|
|
100
103
|
await client.query(`
|
|
101
104
|
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
102
105
|
ON ${this.tableName}
|
|
103
|
-
USING hnsw (embedding
|
|
106
|
+
USING hnsw (embedding ${indexOps})
|
|
104
107
|
`);
|
|
105
108
|
} finally {
|
|
106
109
|
client.release();
|
|
@@ -182,11 +185,19 @@ export class PostgreSQLProvider extends BaseVectorProvider {
|
|
|
182
185
|
const efSearch = (this.config.options.efSearch as number) || Math.max(topK * 10, 40);
|
|
183
186
|
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
184
187
|
|
|
188
|
+
const metric = this.config.distanceMetric || 'cosine';
|
|
189
|
+
const queryOp = metric === 'euclidean' ? '<->' : metric === 'dotproduct' ? '<#>' : '<=>';
|
|
190
|
+
const scoreExpr = metric === 'euclidean'
|
|
191
|
+
? `1 / (1 + (embedding <-> $1::vector))`
|
|
192
|
+
: metric === 'dotproduct'
|
|
193
|
+
? `(embedding <#> $1::vector) * -1`
|
|
194
|
+
: `1 - (embedding <=> $1::vector)`;
|
|
195
|
+
|
|
185
196
|
const result = await client.query(
|
|
186
|
-
`SELECT id, content, metadata,
|
|
197
|
+
`SELECT id, content, metadata, ${scoreExpr} AS score
|
|
187
198
|
FROM ${this.tableName}
|
|
188
199
|
${whereClause}
|
|
189
|
-
ORDER BY embedding
|
|
200
|
+
ORDER BY embedding ${queryOp} $1::vector
|
|
190
201
|
LIMIT $2`,
|
|
191
202
|
params
|
|
192
203
|
);
|