@retrivora-ai/rag-engine 1.9.1 → 1.9.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/{ILLMProvider-BQyKZLnd.d.mts → ILLMProvider-Bw2A28nU.d.mts} +2 -0
- package/dist/{ILLMProvider-BQyKZLnd.d.ts → ILLMProvider-Bw2A28nU.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 +183 -103
- package/dist/handlers/index.mjs +183 -103
- package/dist/{index-CDftK3qs.d.mts → index-B70ZLkfG.d.mts} +1 -1
- package/dist/{index-A0GqPsaG.d.ts → index-DVu-mkAM.d.ts} +1 -1
- package/dist/index.css +57 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +90 -36
- package/dist/index.mjs +91 -37
- package/dist/server.d.mts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +183 -103
- package/dist/server.mjs +183 -103
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +24 -14
- package/src/components/MessageBubble.tsx +41 -6
- package/src/components/ProductCard.tsx +4 -4
- package/src/core/Pipeline.ts +52 -25
- package/src/core/QueryProcessor.ts +10 -9
- package/src/handlers/index.ts +31 -8
- package/src/hooks/useRagChat.ts +24 -0
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +36 -4
- package/src/types/chat.ts +2 -0
- package/src/utils/SchemaMapper.ts +6 -4
- package/src/utils/UITransformer.ts +2 -1
- package/src/utils/synonyms.ts +5 -5
package/dist/server.mjs
CHANGED
|
@@ -519,6 +519,82 @@ var init_PostgreSQLProvider = __esm({
|
|
|
519
519
|
}
|
|
520
520
|
});
|
|
521
521
|
|
|
522
|
+
// src/utils/synonyms.ts
|
|
523
|
+
function resolveMetadataValue(meta, uiKey) {
|
|
524
|
+
var _a;
|
|
525
|
+
const synonyms = (_a = FIELD_SYNONYMS[uiKey]) != null ? _a : [];
|
|
526
|
+
const keys = Object.keys(meta);
|
|
527
|
+
const systemKeys = ["namespace", "filename", "filesize", "filetype", "docid", "uploadedat", "dimension", "chunkindex"];
|
|
528
|
+
let match = keys.find((k) => k.toLowerCase() === uiKey.toLowerCase());
|
|
529
|
+
if (match !== void 0) return meta[match];
|
|
530
|
+
match = keys.find((k) => synonyms.map((s) => s.toLowerCase()).includes(k.toLowerCase()));
|
|
531
|
+
if (match !== void 0) return meta[match];
|
|
532
|
+
const isBlacklisted = (kl) => {
|
|
533
|
+
return systemKeys.includes(kl) || kl.startsWith("option1") || kl.startsWith("option2") || kl.startsWith("option3");
|
|
534
|
+
};
|
|
535
|
+
match = keys.find((k) => {
|
|
536
|
+
const kl = k.toLowerCase();
|
|
537
|
+
if (isBlacklisted(kl)) return false;
|
|
538
|
+
return kl.includes(uiKey.toLowerCase());
|
|
539
|
+
});
|
|
540
|
+
if (match !== void 0) return meta[match];
|
|
541
|
+
match = keys.find((k) => {
|
|
542
|
+
const kl = k.toLowerCase();
|
|
543
|
+
if (isBlacklisted(kl)) return false;
|
|
544
|
+
return synonyms.some((sk) => kl.includes(sk.toLowerCase()) || sk.toLowerCase().includes(kl));
|
|
545
|
+
});
|
|
546
|
+
return match !== void 0 ? meta[match] : void 0;
|
|
547
|
+
}
|
|
548
|
+
var FIELD_SYNONYMS;
|
|
549
|
+
var init_synonyms = __esm({
|
|
550
|
+
"src/utils/synonyms.ts"() {
|
|
551
|
+
"use strict";
|
|
552
|
+
FIELD_SYNONYMS = {
|
|
553
|
+
name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
|
|
554
|
+
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd", "variant price"],
|
|
555
|
+
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
556
|
+
image: [
|
|
557
|
+
"imageUrl",
|
|
558
|
+
"thumbnail",
|
|
559
|
+
"img",
|
|
560
|
+
"url",
|
|
561
|
+
"photo",
|
|
562
|
+
"picture",
|
|
563
|
+
"media",
|
|
564
|
+
"image_url",
|
|
565
|
+
"main_image",
|
|
566
|
+
"product_image",
|
|
567
|
+
"thumb",
|
|
568
|
+
"image src",
|
|
569
|
+
"variant image"
|
|
570
|
+
],
|
|
571
|
+
stock: [
|
|
572
|
+
"inventory",
|
|
573
|
+
"quantity",
|
|
574
|
+
"count",
|
|
575
|
+
"availability",
|
|
576
|
+
"stock_level",
|
|
577
|
+
"inStock",
|
|
578
|
+
"is_available",
|
|
579
|
+
"in stock",
|
|
580
|
+
"status",
|
|
581
|
+
"variant inventory qty"
|
|
582
|
+
],
|
|
583
|
+
category: [
|
|
584
|
+
"product_category",
|
|
585
|
+
"product category",
|
|
586
|
+
"category_name",
|
|
587
|
+
"category name",
|
|
588
|
+
"department",
|
|
589
|
+
"collection",
|
|
590
|
+
"type"
|
|
591
|
+
],
|
|
592
|
+
description: ["summary", "content", "body", "text", "info", "details", "body (html)", "seo description"],
|
|
593
|
+
link: ["url", "href", "product_url", "page_url", "link"]
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
522
598
|
// src/providers/vectordb/MultiTablePostgresProvider.ts
|
|
523
599
|
var MultiTablePostgresProvider_exports = {};
|
|
524
600
|
__export(MultiTablePostgresProvider_exports, {
|
|
@@ -530,6 +606,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
530
606
|
"src/providers/vectordb/MultiTablePostgresProvider.ts"() {
|
|
531
607
|
"use strict";
|
|
532
608
|
init_BaseVectorProvider();
|
|
609
|
+
init_synonyms();
|
|
533
610
|
MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
534
611
|
constructor(config) {
|
|
535
612
|
var _a, _b, _c;
|
|
@@ -610,8 +687,13 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
610
687
|
}
|
|
611
688
|
tableName = tableName.replace(/[^a-z0-9_]/gi, "_").toLowerCase() || this.uploadTable;
|
|
612
689
|
const firstMeta = fileDocs[0].metadata || {};
|
|
613
|
-
|
|
614
|
-
|
|
690
|
+
let csvHeaders = [];
|
|
691
|
+
if (Array.isArray(firstMeta.csvHeaders)) {
|
|
692
|
+
csvHeaders = firstMeta.csvHeaders;
|
|
693
|
+
} else {
|
|
694
|
+
const systemKeys = ["fileName", "fileSize", "fileType", "uploadedAt", "dimension", "chunkIndex", "id", "namespace", "content", "metadata", "embedding", "docId", "docid", "chunkId", "chunkid", "csvHeaders", "csvheaders"];
|
|
695
|
+
csvHeaders = Object.keys(firstMeta).filter((k) => !systemKeys.includes(k) && !systemKeys.includes(k.toLowerCase()));
|
|
696
|
+
}
|
|
615
697
|
const columnDefs = csvHeaders.map((h) => `"${h}" TEXT`).join(",\n ");
|
|
616
698
|
const createTableSql = `
|
|
617
699
|
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
@@ -704,10 +786,26 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
704
786
|
};
|
|
705
787
|
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
706
788
|
const tableLimit = Math.max(topK, 50);
|
|
789
|
+
const metadataFilters = _filter == null ? void 0 : _filter.metadata;
|
|
707
790
|
const queryPromises = this.tables.map(async (table) => {
|
|
708
791
|
try {
|
|
709
792
|
let sqlQuery = "";
|
|
710
793
|
let params = [];
|
|
794
|
+
let whereClause = "";
|
|
795
|
+
const filterParams = [];
|
|
796
|
+
if (metadataFilters && Object.keys(metadataFilters).length > 0) {
|
|
797
|
+
const conditions = Object.entries(metadataFilters).map(([key, val]) => {
|
|
798
|
+
var _a3;
|
|
799
|
+
filterParams.push(String(val));
|
|
800
|
+
const baseOffset = queryText && dynamicKeywordQuery ? 2 : 1;
|
|
801
|
+
const paramIdx = baseOffset + filterParams.length;
|
|
802
|
+
const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
|
|
803
|
+
const keysToCheck = [key, ...synonyms];
|
|
804
|
+
const coalesceExprs = keysToCheck.map((k) => `LOWER(metadata->>'${k}')`);
|
|
805
|
+
return `COALESCE(${coalesceExprs.join(", ")}) = LOWER($${paramIdx})`;
|
|
806
|
+
});
|
|
807
|
+
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
808
|
+
}
|
|
711
809
|
if (queryText && dynamicKeywordQuery) {
|
|
712
810
|
const hasEntityHints = entityHints.length > 0;
|
|
713
811
|
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
@@ -724,19 +822,21 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
724
822
|
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
725
823
|
((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
|
|
726
824
|
FROM "${table}" t
|
|
825
|
+
${whereClause}
|
|
727
826
|
ORDER BY hybrid_score DESC
|
|
728
827
|
LIMIT ${tableLimit}
|
|
729
828
|
`;
|
|
730
|
-
params = [vectorLiteral, dynamicKeywordQuery];
|
|
829
|
+
params = [vectorLiteral, dynamicKeywordQuery, ...filterParams];
|
|
731
830
|
} else {
|
|
732
831
|
sqlQuery = `
|
|
733
832
|
SELECT *,
|
|
734
833
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
735
834
|
FROM "${table}" t
|
|
835
|
+
${whereClause}
|
|
736
836
|
ORDER BY hybrid_score DESC
|
|
737
837
|
LIMIT ${tableLimit}
|
|
738
838
|
`;
|
|
739
|
-
params = [vectorLiteral];
|
|
839
|
+
params = [vectorLiteral, ...filterParams];
|
|
740
840
|
}
|
|
741
841
|
const result = await this.pool.query(sqlQuery, params);
|
|
742
842
|
if (result.rowCount && result.rowCount > 0) {
|
|
@@ -4361,7 +4461,8 @@ var QueryProcessor = class {
|
|
|
4361
4461
|
const fieldValuePatterns = [
|
|
4362
4462
|
new RegExp(`\\b${fieldPattern}\\s*(?:=|:)\\s*["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
4363
4463
|
new RegExp(`\\b${fieldPattern}\\s+(?:is|are|was|were|equals?|equal to|named|called)\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
4364
|
-
new RegExp(`\\bwith\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi")
|
|
4464
|
+
new RegExp(`\\bwith\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
4465
|
+
new RegExp(`\\b(?:under|in|for|from|of)\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi")
|
|
4365
4466
|
];
|
|
4366
4467
|
for (const pattern of fieldValuePatterns) {
|
|
4367
4468
|
for (const match of question.matchAll(pattern)) {
|
|
@@ -4595,73 +4696,8 @@ var LLMRouter = class {
|
|
|
4595
4696
|
}
|
|
4596
4697
|
};
|
|
4597
4698
|
|
|
4598
|
-
// src/utils/synonyms.ts
|
|
4599
|
-
var FIELD_SYNONYMS = {
|
|
4600
|
-
name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
|
|
4601
|
-
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4602
|
-
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4603
|
-
image: [
|
|
4604
|
-
"imageUrl",
|
|
4605
|
-
"thumbnail",
|
|
4606
|
-
"img",
|
|
4607
|
-
"url",
|
|
4608
|
-
"photo",
|
|
4609
|
-
"picture",
|
|
4610
|
-
"media",
|
|
4611
|
-
"image_url",
|
|
4612
|
-
"main_image",
|
|
4613
|
-
"product_image",
|
|
4614
|
-
"thumb"
|
|
4615
|
-
],
|
|
4616
|
-
stock: [
|
|
4617
|
-
"inventory",
|
|
4618
|
-
"quantity",
|
|
4619
|
-
"count",
|
|
4620
|
-
"availability",
|
|
4621
|
-
"stock_level",
|
|
4622
|
-
"inStock",
|
|
4623
|
-
"is_available",
|
|
4624
|
-
"in stock",
|
|
4625
|
-
"status"
|
|
4626
|
-
],
|
|
4627
|
-
category: [
|
|
4628
|
-
"product_category",
|
|
4629
|
-
"product category",
|
|
4630
|
-
"category_name",
|
|
4631
|
-
"category name",
|
|
4632
|
-
"department",
|
|
4633
|
-
"collection"
|
|
4634
|
-
],
|
|
4635
|
-
description: ["summary", "content", "body", "text", "info", "details"],
|
|
4636
|
-
link: ["url", "href", "product_url", "page_url", "link"]
|
|
4637
|
-
};
|
|
4638
|
-
function resolveMetadataValue(meta, uiKey) {
|
|
4639
|
-
var _a;
|
|
4640
|
-
const synonyms = (_a = FIELD_SYNONYMS[uiKey]) != null ? _a : [];
|
|
4641
|
-
const keys = Object.keys(meta);
|
|
4642
|
-
const systemKeys = ["namespace", "filename", "filesize", "filetype", "docid", "uploadedat", "dimension", "chunkindex"];
|
|
4643
|
-
let match = keys.find((k) => k.toLowerCase() === uiKey.toLowerCase());
|
|
4644
|
-
if (match !== void 0) return meta[match];
|
|
4645
|
-
match = keys.find((k) => synonyms.map((s) => s.toLowerCase()).includes(k.toLowerCase()));
|
|
4646
|
-
if (match !== void 0) return meta[match];
|
|
4647
|
-
const isBlacklisted = (kl) => {
|
|
4648
|
-
return systemKeys.includes(kl) || kl.startsWith("option1") || kl.startsWith("option2") || kl.startsWith("option3");
|
|
4649
|
-
};
|
|
4650
|
-
match = keys.find((k) => {
|
|
4651
|
-
const kl = k.toLowerCase();
|
|
4652
|
-
if (isBlacklisted(kl)) return false;
|
|
4653
|
-
return kl.includes(uiKey.toLowerCase());
|
|
4654
|
-
});
|
|
4655
|
-
if (match !== void 0) return meta[match];
|
|
4656
|
-
match = keys.find((k) => {
|
|
4657
|
-
const kl = k.toLowerCase();
|
|
4658
|
-
if (isBlacklisted(kl)) return false;
|
|
4659
|
-
return synonyms.some((sk) => kl.includes(sk.toLowerCase()) || sk.toLowerCase().includes(kl));
|
|
4660
|
-
});
|
|
4661
|
-
return match !== void 0 ? meta[match] : void 0;
|
|
4662
|
-
}
|
|
4663
|
-
|
|
4664
4699
|
// src/utils/UITransformer.ts
|
|
4700
|
+
init_synonyms();
|
|
4665
4701
|
var UITransformer = class {
|
|
4666
4702
|
// ─── Public Entry Points ─────────────────────────────────────────────────
|
|
4667
4703
|
/**
|
|
@@ -4958,7 +4994,7 @@ RULES:
|
|
|
4958
4994
|
}
|
|
4959
4995
|
// ─── Transform Helpers ────────────────────────────────────────────────────
|
|
4960
4996
|
static transformToProductCarousel(data, config, trainedSchema) {
|
|
4961
|
-
const products = data.map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
|
|
4997
|
+
const products = data.map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null).slice(0, 15);
|
|
4962
4998
|
return {
|
|
4963
4999
|
type: "product_carousel",
|
|
4964
5000
|
title: "Recommended Products",
|
|
@@ -5917,14 +5953,16 @@ Given these metadata keys from a database: [${keys.join(", ")}]
|
|
|
5917
5953
|
Identify which keys best correspond to these standard UI properties:
|
|
5918
5954
|
${propertyList}
|
|
5919
5955
|
|
|
5920
|
-
Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
|
|
5956
|
+
Return ONLY a valid JSON object where the keys are the UI properties and the values are the EXACT matching database keys from the list above.
|
|
5921
5957
|
If no good match is found for a property, omit it.
|
|
5922
5958
|
|
|
5923
5959
|
Example:
|
|
5924
5960
|
{
|
|
5925
|
-
"name": "
|
|
5926
|
-
"price": "
|
|
5927
|
-
"brand": "
|
|
5961
|
+
"name": "Title",
|
|
5962
|
+
"price": "Variant Price",
|
|
5963
|
+
"brand": "Vendor",
|
|
5964
|
+
"image": "Image Src",
|
|
5965
|
+
"stock": "Variant Inventory Qty"
|
|
5928
5966
|
}
|
|
5929
5967
|
`;
|
|
5930
5968
|
try {
|
|
@@ -6215,7 +6253,10 @@ var Pipeline = class {
|
|
|
6215
6253
|
upsertBatchOptions
|
|
6216
6254
|
);
|
|
6217
6255
|
if (upsertResult.errors.length > 0) {
|
|
6218
|
-
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed
|
|
6256
|
+
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed. Error details:`);
|
|
6257
|
+
upsertResult.errors.forEach((err, idx) => {
|
|
6258
|
+
console.warn(` Batch ${idx + 1} Error: ${err.error.message || String(err.error)}`);
|
|
6259
|
+
});
|
|
6219
6260
|
}
|
|
6220
6261
|
return upsertResult.totalProcessed;
|
|
6221
6262
|
}
|
|
@@ -6330,8 +6371,9 @@ var Pipeline = class {
|
|
|
6330
6371
|
this.embeddingCache.set(cacheKey, queryVector);
|
|
6331
6372
|
}
|
|
6332
6373
|
const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((_j = this.config.rag) == null ? void 0 : _j.useGraphRetrieval) ? yield new __await(this.graphDB.query(searchQuery)) : void 0;
|
|
6374
|
+
const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
|
|
6333
6375
|
const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
|
|
6334
|
-
const wantsExhaustiveList = hasNumericPredicates || /\b(list|all|show|provide|give|get)\b/i.test(question);
|
|
6376
|
+
const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
|
|
6335
6377
|
const retrievalLimit = wantsExhaustiveList ? Math.max(topK * 20, 100) : topK * 2;
|
|
6336
6378
|
const rawSources = (strategyResult === "vector" || strategyResult === "both") && queryVector && queryVector.length > 0 ? yield new __await(this.vectorDB.query(queryVector, retrievalLimit, ns, filter)) : [];
|
|
6337
6379
|
const retrieveEnd = performance.now();
|
|
@@ -6339,15 +6381,27 @@ var Pipeline = class {
|
|
|
6339
6381
|
const retrieveMs = retrieveEnd - embedStart;
|
|
6340
6382
|
const rerankStart = performance.now();
|
|
6341
6383
|
const structuredSources = this.applyStructuredFilters(rawSources, filter);
|
|
6342
|
-
let
|
|
6384
|
+
let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
|
|
6385
|
+
const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
|
|
6343
6386
|
if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
|
|
6344
|
-
|
|
6387
|
+
fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
|
|
6345
6388
|
} else if (!wantsExhaustiveList) {
|
|
6346
|
-
|
|
6389
|
+
fullSources = fullSources.slice(0, topK);
|
|
6347
6390
|
}
|
|
6348
6391
|
const rerankMs = performance.now() - rerankStart;
|
|
6349
|
-
let context =
|
|
6392
|
+
let context = fullSources.length ? fullSources.map((m, i) => `[Source ${i + 1}]
|
|
6350
6393
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
6394
|
+
let displayCount = 15;
|
|
6395
|
+
if (hasMetadataFilter) {
|
|
6396
|
+
displayCount = fullSources.length;
|
|
6397
|
+
} else {
|
|
6398
|
+
const highlyRelevant = fullSources.filter((m) => m.score >= 0.4);
|
|
6399
|
+
displayCount = Math.max(highlyRelevant.length, topK);
|
|
6400
|
+
if (displayCount > 15) {
|
|
6401
|
+
displayCount = 15;
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
const sources = [...fullSources].sort((a, b) => b.score - a.score).slice(0, displayCount);
|
|
6351
6405
|
if (graphData && graphData.nodes.length > 0) {
|
|
6352
6406
|
const graphContext = graphData.nodes.map(
|
|
6353
6407
|
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
@@ -6360,7 +6414,18 @@ ${context}`;
|
|
|
6360
6414
|
}
|
|
6361
6415
|
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
6362
6416
|
const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
|
|
6363
|
-
const
|
|
6417
|
+
const uiTransformationPromise = trainedSchemaPromise.then(
|
|
6418
|
+
(trainedSchema) => this.generateUiTransformation(
|
|
6419
|
+
question,
|
|
6420
|
+
sources,
|
|
6421
|
+
trainedSchema,
|
|
6422
|
+
hasNumericPredicates
|
|
6423
|
+
)
|
|
6424
|
+
).catch((uiError) => {
|
|
6425
|
+
console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
|
|
6426
|
+
return UITransformer.transform(question, sources, this.config);
|
|
6427
|
+
});
|
|
6428
|
+
const restrictionSuffix = "\n\n(IMPORTANT: Format your response beautifully using rich Markdown! Use bolding for emphasis, headings (##) for structure, bullet points for lists, and proper line breaks between paragraphs to make it highly readable. However, NEVER generate Markdown tables, HTML figures, or text charts (the UI renders requested charts separately, so NEVER say you cannot create, display, draw, render, or provide a chart/visualization). If listing products, use simple markdown bullet points or comma-separated names. NEVER use plus signs (+) to separate product names, category names, or list items. For product description/detail questions, provide customer-facing prose only and do NOT list internal catalog fields such as Handle, Body (HTML), Vendor, Type, Tags, Published, Option, Variant, SKU, or raw metadata labels. You CAN use numbers for years/counts like 2006 or 5800, but NEVER put a dollar sign ($) before them.)";
|
|
6364
6429
|
const hardenedHistory = [...history];
|
|
6365
6430
|
const userQuestion = { role: "user", content: question + restrictionSuffix };
|
|
6366
6431
|
const messages = [...hardenedHistory, userQuestion];
|
|
@@ -6430,19 +6495,7 @@ ${context}`;
|
|
|
6430
6495
|
tokens,
|
|
6431
6496
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6432
6497
|
};
|
|
6433
|
-
|
|
6434
|
-
try {
|
|
6435
|
-
const trainedSchema = yield new __await(trainedSchemaPromise);
|
|
6436
|
-
uiTransformation = yield new __await(this.generateUiTransformation(
|
|
6437
|
-
question,
|
|
6438
|
-
sources,
|
|
6439
|
-
trainedSchema,
|
|
6440
|
-
hasNumericPredicates
|
|
6441
|
-
));
|
|
6442
|
-
} catch (uiError) {
|
|
6443
|
-
console.warn("[Pipeline] UI transformation failed before metadata yield:", uiError);
|
|
6444
|
-
uiTransformation = UITransformer.transform(question, sources, this.config);
|
|
6445
|
-
}
|
|
6498
|
+
const uiTransformation = yield new __await(uiTransformationPromise);
|
|
6446
6499
|
yield {
|
|
6447
6500
|
reply: "",
|
|
6448
6501
|
sources,
|
|
@@ -7176,15 +7229,24 @@ function createStreamHandler(configOrPlugin) {
|
|
|
7176
7229
|
});
|
|
7177
7230
|
}
|
|
7178
7231
|
const encoder = new TextEncoder();
|
|
7232
|
+
let isActive = true;
|
|
7179
7233
|
const stream = new ReadableStream({
|
|
7180
7234
|
async start(controller) {
|
|
7181
7235
|
var _a;
|
|
7182
|
-
const enqueue = (text) =>
|
|
7236
|
+
const enqueue = (text) => {
|
|
7237
|
+
if (!isActive) return;
|
|
7238
|
+
try {
|
|
7239
|
+
controller.enqueue(encoder.encode(text));
|
|
7240
|
+
} catch (err) {
|
|
7241
|
+
console.warn("[createStreamHandler] Failed to enqueue (stream already closed):", err);
|
|
7242
|
+
}
|
|
7243
|
+
};
|
|
7183
7244
|
try {
|
|
7184
7245
|
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
7185
7246
|
try {
|
|
7186
7247
|
for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
7187
7248
|
const chunk = temp.value;
|
|
7249
|
+
if (!isActive) break;
|
|
7188
7250
|
if (typeof chunk === "string") {
|
|
7189
7251
|
enqueue(sseTextFrame(chunk));
|
|
7190
7252
|
} else {
|
|
@@ -7222,12 +7284,27 @@ function createStreamHandler(configOrPlugin) {
|
|
|
7222
7284
|
}
|
|
7223
7285
|
}
|
|
7224
7286
|
} catch (streamError) {
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7287
|
+
if (isActive) {
|
|
7288
|
+
const errorMessage = streamError instanceof Error ? streamError.message : String(streamError);
|
|
7289
|
+
console.error("[createStreamHandler] Stream error:", streamError);
|
|
7290
|
+
try {
|
|
7291
|
+
enqueue(sseErrorFrame(errorMessage));
|
|
7292
|
+
} catch (e) {
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7228
7295
|
} finally {
|
|
7229
|
-
|
|
7296
|
+
if (isActive) {
|
|
7297
|
+
isActive = false;
|
|
7298
|
+
try {
|
|
7299
|
+
controller.close();
|
|
7300
|
+
} catch (e) {
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7230
7303
|
}
|
|
7304
|
+
},
|
|
7305
|
+
cancel(reason) {
|
|
7306
|
+
isActive = false;
|
|
7307
|
+
console.log("[createStreamHandler] Stream connection closed by client:", reason);
|
|
7231
7308
|
}
|
|
7232
7309
|
});
|
|
7233
7310
|
return new Response(stream, { headers: SSE_HEADERS });
|
|
@@ -7288,6 +7365,7 @@ function createUploadHandler(configOrPlugin) {
|
|
|
7288
7365
|
if (parsed.data && parsed.data.length > 0) {
|
|
7289
7366
|
let i = 0;
|
|
7290
7367
|
let lastRowData = null;
|
|
7368
|
+
const csvCols = Object.keys(parsed.data[0]);
|
|
7291
7369
|
for (const row of parsed.data) {
|
|
7292
7370
|
i++;
|
|
7293
7371
|
const rowData = row;
|
|
@@ -7309,12 +7387,14 @@ function createUploadHandler(configOrPlugin) {
|
|
|
7309
7387
|
documents.push({
|
|
7310
7388
|
docId: `${file.name}-row-${i}`,
|
|
7311
7389
|
content: contentParts.join(", "),
|
|
7312
|
-
metadata: __spreadValues(__spreadValues({
|
|
7390
|
+
metadata: __spreadValues(__spreadProps(__spreadValues({
|
|
7313
7391
|
fileName: file.name,
|
|
7314
7392
|
fileSize: file.size,
|
|
7315
7393
|
fileType: file.type,
|
|
7316
7394
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7317
|
-
}, dimension ? { dimension } : {}),
|
|
7395
|
+
}, dimension ? { dimension } : {}), {
|
|
7396
|
+
csvHeaders: csvCols
|
|
7397
|
+
}), rowData)
|
|
7318
7398
|
});
|
|
7319
7399
|
}
|
|
7320
7400
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.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",
|
|
@@ -71,7 +71,7 @@ export function ChatWindow({
|
|
|
71
71
|
const windowRef = useRef<HTMLDivElement>(null);
|
|
72
72
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
73
73
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
74
|
-
const { messages, send, clear, isLoading, error } = useRagChat(projectId, {
|
|
74
|
+
const { messages, send, clear, isLoading, error, stop } = useRagChat(projectId, {
|
|
75
75
|
namespace: projectId,
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -492,19 +492,29 @@ export function ChatWindow({
|
|
|
492
492
|
</button>
|
|
493
493
|
)}
|
|
494
494
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
495
|
+
{isLoading ? (
|
|
496
|
+
<button
|
|
497
|
+
onClick={stop}
|
|
498
|
+
className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 shadow-md bg-rose-500 hover:bg-rose-600 text-white cursor-pointer"
|
|
499
|
+
title="Stop generating"
|
|
500
|
+
>
|
|
501
|
+
<div className="w-3.5 h-3.5 bg-white rounded-[2px]" />
|
|
502
|
+
</button>
|
|
503
|
+
) : (
|
|
504
|
+
<button
|
|
505
|
+
onClick={sendMessage}
|
|
506
|
+
disabled={!input.trim()}
|
|
507
|
+
className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:scale-105 active:scale-95 shadow-md"
|
|
508
|
+
style={{
|
|
509
|
+
background:
|
|
510
|
+
input.trim()
|
|
511
|
+
? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})`
|
|
512
|
+
: 'rgba(148, 163, 184, 0.2)', // slate-400/20 for light mode disabled
|
|
513
|
+
}}
|
|
514
|
+
>
|
|
515
|
+
<ArrowUp className="w-4 h-4 text-white" />
|
|
516
|
+
</button>
|
|
517
|
+
)}
|
|
508
518
|
</div>
|
|
509
519
|
<p className="text-center text-[10px] text-slate-400 dark:text-white/20 mt-2">
|
|
510
520
|
Press Enter to send · Shift+Enter for new line
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { Bot, User, ChevronDown, ChevronRight, Activity } from 'lucide-react';
|
|
4
|
+
import { Bot, User, ChevronDown, ChevronRight, Activity, Copy, Check } from 'lucide-react';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import { MessageBubbleProps, Product, UITransformationResponse } from '../types';
|
|
7
7
|
import { SourceCard } from './SourceCard';
|
|
@@ -61,6 +61,17 @@ export function MessageBubble({
|
|
|
61
61
|
|
|
62
62
|
const [showSources, setShowSources] = React.useState(false);
|
|
63
63
|
const [showTrace, setShowTrace] = React.useState(false);
|
|
64
|
+
const [copied, setCopied] = React.useState(false);
|
|
65
|
+
|
|
66
|
+
const handleCopy = React.useCallback(async () => {
|
|
67
|
+
try {
|
|
68
|
+
await navigator.clipboard.writeText(message.content);
|
|
69
|
+
setCopied(true);
|
|
70
|
+
setTimeout(() => setCopied(false), 2000);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('Failed to copy text:', err);
|
|
73
|
+
}
|
|
74
|
+
}, [message.content]);
|
|
64
75
|
|
|
65
76
|
const structuredContent = React.useMemo(
|
|
66
77
|
() => isUser ? { payload: null, text: message.content } : extractStructuredPayload(message.content),
|
|
@@ -187,7 +198,7 @@ export function MessageBubble({
|
|
|
187
198
|
<div className={`flex flex-col gap-1 min-w-0 ${isCompact ? 'max-w-[94%]' : isMedium ? 'max-w-[92%]' : 'max-w-[90%]'} ${isUser ? 'items-end' : 'items-start'}`}>
|
|
188
199
|
{/* Bubble */}
|
|
189
200
|
<div
|
|
190
|
-
className={`relative ${isCompact ? 'px-3 py-2.5 text-[13px]' : 'px-4 py-3 text-sm'} rounded-2xl leading-relaxed shadow-sm dark:shadow-lg ${isUser
|
|
201
|
+
className={`relative group ${isCompact ? 'px-3 py-2.5 text-[13px]' : 'px-4 py-3 text-sm'} rounded-2xl leading-relaxed shadow-sm dark:shadow-lg ${isUser
|
|
191
202
|
? 'text-white rounded-tr-sm'
|
|
192
203
|
: 'bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm'
|
|
193
204
|
}`}
|
|
@@ -195,6 +206,20 @@ export function MessageBubble({
|
|
|
195
206
|
isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
196
207
|
}
|
|
197
208
|
>
|
|
209
|
+
{/* Floating Copy Button for completed assistant messages */}
|
|
210
|
+
{!isUser && !isStreaming && message.content && (
|
|
211
|
+
<button
|
|
212
|
+
onClick={handleCopy}
|
|
213
|
+
className={`absolute top-2 right-2 p-1.5 rounded-lg border transition-all duration-200 cursor-pointer ${
|
|
214
|
+
copied
|
|
215
|
+
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-500 dark:text-emerald-400'
|
|
216
|
+
: 'bg-slate-500/5 hover:bg-slate-500/10 border-slate-200 dark:border-white/10 text-slate-400 dark:text-white/30 hover:text-slate-600 dark:hover:text-white/60'
|
|
217
|
+
} opacity-0 group-hover:opacity-100 backdrop-blur-sm shadow-sm scale-95 group-hover:scale-100`}
|
|
218
|
+
title={copied ? "Copied!" : "Copy response"}
|
|
219
|
+
>
|
|
220
|
+
{copied ? <Check className="w-3.5 h-3.5" /> : <Copy className="w-3.5 h-3.5" />}
|
|
221
|
+
</button>
|
|
222
|
+
)}
|
|
198
223
|
{isStreaming && !message.content ? (
|
|
199
224
|
<span className="flex items-center gap-1 py-1">
|
|
200
225
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" />
|
|
@@ -240,11 +265,21 @@ export function MessageBubble({
|
|
|
240
265
|
if (isUser || structuredContent.payload || !message.uiTransformation) return null;
|
|
241
266
|
const ui = message.uiTransformation as UITransformationResponse;
|
|
242
267
|
const textContent = (ui.data as { content?: string })?.content ?? '';
|
|
268
|
+
|
|
269
|
+
const isNegativeOrFallbackText =
|
|
270
|
+
textContent.toLowerCase().includes('cannot answer') ||
|
|
271
|
+
textContent.toLowerCase().includes('no relevant data') ||
|
|
272
|
+
textContent.toLowerCase().includes('no data available') ||
|
|
273
|
+
(typeof ui.title === 'string' && ui.title.toLowerCase().includes('no data')) ||
|
|
274
|
+
(typeof ui.description === 'string' && ui.description.toLowerCase().includes('no relevant data'));
|
|
275
|
+
|
|
243
276
|
const shouldShow =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
277
|
+
!isNegativeOrFallbackText &&
|
|
278
|
+
(ui.type === 'table' ||
|
|
279
|
+
!['text', 'table'].includes(ui.type) ||
|
|
280
|
+
(ui.type === 'text' && !hasRichUI && allProducts.length === 0 && textContent &&
|
|
281
|
+
!processedMarkdown.toLowerCase().includes(textContent.trim().toLowerCase())));
|
|
282
|
+
|
|
248
283
|
if (!shouldShow) return null;
|
|
249
284
|
return (
|
|
250
285
|
<div className="w-full mt-3">
|
|
@@ -47,7 +47,7 @@ export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }:
|
|
|
47
47
|
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
|
48
48
|
/>
|
|
49
49
|
) : (
|
|
50
|
-
<div className="text-slate-400 dark:text-white/20
|
|
50
|
+
<div className="text-slate-400 dark:text-white/20">
|
|
51
51
|
<ShoppingCart className="w-12 h-12" />
|
|
52
52
|
</div>
|
|
53
53
|
)}
|
|
@@ -68,8 +68,8 @@ export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }:
|
|
|
68
68
|
</h3>
|
|
69
69
|
{product.price && (
|
|
70
70
|
<span className="text-sm font-bold" style={{ color: primaryColor }}>
|
|
71
|
-
{typeof product.price === 'number'
|
|
72
|
-
? `$${product.price}`
|
|
71
|
+
{typeof product.price === 'number'
|
|
72
|
+
? `$${product.price}`
|
|
73
73
|
: (String(product.price).match(/^[\d.]/) ? `$${product.price}` : product.price)}
|
|
74
74
|
</span>
|
|
75
75
|
)}
|
|
@@ -84,7 +84,7 @@ export function ProductCard({ product, primaryColor = '#6366f1', onAddToCart }:
|
|
|
84
84
|
<div className="mt-2 flex gap-2">
|
|
85
85
|
<button
|
|
86
86
|
onClick={() => onAddToCart?.(product)}
|
|
87
|
-
className="flex-1 py-2 px-3 rounded-lg text-[11px] font-medium text-white shadow-sm hover:opacity-90 transition-opacity flex items-center justify-center gap-1.5
|
|
87
|
+
className="flex-1 py-2 px-3 rounded-lg text-[11px] font-medium text-white shadow-sm hover:opacity-90 transition-opacity flex items-center justify-center gap-1.5"
|
|
88
88
|
style={{ background: primaryColor }}
|
|
89
89
|
>
|
|
90
90
|
<ShoppingCart className="w-3 h-3" />
|