@retrivora-ai/rag-engine 2.0.1 → 2.0.3
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/FREE_TIER_ARCHITECTURE.md +232 -0
- package/README.md +9 -0
- package/dist/{ILLMProvider-DMxLyTdq.d.mts → ILLMProvider-0rRBYbVW.d.mts} +133 -1
- package/dist/{ILLMProvider-DMxLyTdq.d.ts → ILLMProvider-0rRBYbVW.d.ts} +133 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +679 -15
- package/dist/handlers/index.mjs +679 -15
- package/dist/index-B1wGUlSL.d.mts +532 -0
- package/dist/{index-tzwc7UhY.d.ts → index-BIHHp_f6.d.ts} +1 -1
- package/dist/{index-DHsFLJXQ.d.mts → index-BvODr57d.d.mts} +1 -1
- package/dist/index-DcklhThn.d.ts +532 -0
- package/dist/index.css +51 -3
- package/dist/index.d.mts +6 -105
- package/dist/index.d.ts +6 -105
- package/dist/index.js +1918 -8
- package/dist/index.mjs +1913 -8
- package/dist/server.d.mts +51 -7
- package/dist/server.d.ts +51 -7
- package/dist/server.js +721 -15
- package/dist/server.mjs +704 -15
- package/package.json +9 -7
- package/src/app/page.tsx +54 -18
- package/src/components/ChatWindow.tsx +2 -2
- package/src/components/DocumentUpload.tsx +5 -4
- package/src/components/MessageBubble.tsx +2 -4
- package/src/core/Pipeline.ts +23 -5
- package/src/handlers/index.ts +48 -1
- package/src/index.ts +22 -4
- package/src/rendering/IntentClassifier.ts +167 -0
- package/src/rendering/RendererRegistry.ts +164 -0
- package/src/rendering/RuleEngine.ts +67 -0
- package/src/rendering/VisualizationDecisionEngine.ts +164 -0
- package/src/rendering/__tests__/VisualizationDecisionEngine.test.ts +237 -0
- package/src/rendering/index.ts +12 -0
- package/src/rendering/rules/Rule1SpecificInfoRule.ts +28 -0
- package/src/rendering/rules/Rule2ComparisonRule.ts +24 -0
- package/src/rendering/rules/Rule3ProductDiscoveryRule.ts +28 -0
- package/src/rendering/rules/Rule4AnalyticalRule.ts +38 -0
- package/src/rendering/rules/Rule5MixedResponseRule.ts +29 -0
- package/src/rendering/rules/Rule6SmallResultSetRule.ts +24 -0
- package/src/rendering/rules/Rule7LargeTableRule.ts +35 -0
- package/src/rendering/types.ts +96 -0
- package/src/server.ts +3 -2
- package/src/types/index.ts +48 -0
- package/src/utils/DocumentParser.ts +64 -28
- package/src/utils/UITransformer.ts +15 -4
- package/dist/index-CfkqZd2Y.d.ts +0 -197
- package/dist/index-xygonxpW.d.mts +0 -197
package/dist/handlers/index.js
CHANGED
|
@@ -5689,6 +5689,598 @@ var LLMRouter = class {
|
|
|
5689
5689
|
|
|
5690
5690
|
// src/utils/UITransformer.ts
|
|
5691
5691
|
init_synonyms();
|
|
5692
|
+
|
|
5693
|
+
// src/rendering/IntentClassifier.ts
|
|
5694
|
+
var IntentClassifier = class {
|
|
5695
|
+
/**
|
|
5696
|
+
* Fast, zero-latency classifier that maps a user query and optional data context
|
|
5697
|
+
* into a high-level IntentCategory.
|
|
5698
|
+
*/
|
|
5699
|
+
static classify(context) {
|
|
5700
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5701
|
+
const signals = this.extractDataSignals(context);
|
|
5702
|
+
if (this.isInformationLookup(q)) {
|
|
5703
|
+
return { intent: "information_lookup", signals };
|
|
5704
|
+
}
|
|
5705
|
+
if (this.isComparisonQuery(q)) {
|
|
5706
|
+
return { intent: "comparison", signals };
|
|
5707
|
+
}
|
|
5708
|
+
if (this.isProductQuery(q) || signals.isProductLike && this.isRecommendationQuery(q)) {
|
|
5709
|
+
return { intent: "product_search", signals };
|
|
5710
|
+
}
|
|
5711
|
+
if (this.isTrendQuery(q) || signals.hasDateFields && signals.hasNumericFields && /\b(growth|trend|over time|monthly|yearly|weekly)\b/.test(q)) {
|
|
5712
|
+
return { intent: "trend_analysis", signals };
|
|
5713
|
+
}
|
|
5714
|
+
if (this.isRankingQuery(q)) {
|
|
5715
|
+
return { intent: "ranking", signals };
|
|
5716
|
+
}
|
|
5717
|
+
if (this.isAnalyticsQuery(q) || signals.hasNumericFields && signals.hasCategoricalFields && /\b(sales|revenue|performance|count|total|average|breakdown|share|percentage|correlation)\b/.test(q)) {
|
|
5718
|
+
return { intent: "analytics", signals };
|
|
5719
|
+
}
|
|
5720
|
+
if (this.isRecommendationQuery(q)) {
|
|
5721
|
+
return { intent: "recommendation", signals };
|
|
5722
|
+
}
|
|
5723
|
+
return { intent: "information_lookup", signals };
|
|
5724
|
+
}
|
|
5725
|
+
/**
|
|
5726
|
+
* Extract key data structure signals from retrieved documents.
|
|
5727
|
+
*/
|
|
5728
|
+
static extractDataSignals(context) {
|
|
5729
|
+
const docs = context.retrievedDocuments || [];
|
|
5730
|
+
const rowCount = docs.length;
|
|
5731
|
+
let hasNumericFields = false;
|
|
5732
|
+
let hasDateFields = false;
|
|
5733
|
+
let hasCategoricalFields = false;
|
|
5734
|
+
let isProductLike = false;
|
|
5735
|
+
let numericFieldCount = 0;
|
|
5736
|
+
let categoricalFieldCount = 0;
|
|
5737
|
+
if (docs.length > 0) {
|
|
5738
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5739
|
+
let numericKeys = /* @__PURE__ */ new Set();
|
|
5740
|
+
let dateKeys = /* @__PURE__ */ new Set();
|
|
5741
|
+
let catKeys = /* @__PURE__ */ new Set();
|
|
5742
|
+
let productKeyMatches = 0;
|
|
5743
|
+
docs.forEach((doc) => {
|
|
5744
|
+
const meta = doc.metadata || {};
|
|
5745
|
+
Object.entries(meta).forEach(([key, val]) => {
|
|
5746
|
+
keys.add(key);
|
|
5747
|
+
const kLower = key.toLowerCase();
|
|
5748
|
+
if (["price", "image", "brand", "in_stock", "rating", "sku", "product"].some((p) => kLower.includes(p))) {
|
|
5749
|
+
productKeyMatches++;
|
|
5750
|
+
}
|
|
5751
|
+
if (typeof val === "number") {
|
|
5752
|
+
numericKeys.add(key);
|
|
5753
|
+
} else if (typeof val === "string") {
|
|
5754
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(val) || !isNaN(Date.parse(val)) && val.length > 5) {
|
|
5755
|
+
dateKeys.add(key);
|
|
5756
|
+
} else if (val.length < 50 && !val.includes("\n")) {
|
|
5757
|
+
catKeys.add(key);
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
});
|
|
5761
|
+
});
|
|
5762
|
+
hasNumericFields = numericKeys.size > 0;
|
|
5763
|
+
hasDateFields = dateKeys.size > 0;
|
|
5764
|
+
hasCategoricalFields = catKeys.size > 0;
|
|
5765
|
+
numericFieldCount = numericKeys.size;
|
|
5766
|
+
categoricalFieldCount = catKeys.size;
|
|
5767
|
+
if (productKeyMatches >= 2 || docs.some((d) => d.content.toLowerCase().includes("price") || d.content.toLowerCase().includes("brand"))) {
|
|
5768
|
+
isProductLike = true;
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
return {
|
|
5772
|
+
rowCount,
|
|
5773
|
+
hasNumericFields,
|
|
5774
|
+
hasDateFields,
|
|
5775
|
+
hasCategoricalFields,
|
|
5776
|
+
isProductLike,
|
|
5777
|
+
numericFieldCount,
|
|
5778
|
+
categoricalFieldCount
|
|
5779
|
+
};
|
|
5780
|
+
}
|
|
5781
|
+
// ─── Intent Heuristic Checkers ─────────────────────────────────────────────
|
|
5782
|
+
static isInformationLookup(query) {
|
|
5783
|
+
if (/^(what|who|explain|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of)\s+/i.test(query)) {
|
|
5784
|
+
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
|
|
5785
|
+
return true;
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
if (/^(what is the )?(price|cost|fee|rate) of\b/i.test(query)) {
|
|
5789
|
+
return true;
|
|
5790
|
+
}
|
|
5791
|
+
return false;
|
|
5792
|
+
}
|
|
5793
|
+
static isComparisonQuery(query) {
|
|
5794
|
+
return /\b(compare|comparison|vs\.?|versus|difference between|differ|contrast|against)\b/i.test(query);
|
|
5795
|
+
}
|
|
5796
|
+
static isProductQuery(query) {
|
|
5797
|
+
return /\b(best|recommend|top|buy|shopping|under \$|cheap|shoes|laptops|chairs|headphones|phone|sneakers|apparel|products)\b/i.test(query) && !/\b(compare|chart|sales|revenue|trend)\b/i.test(query);
|
|
5798
|
+
}
|
|
5799
|
+
static isRecommendationQuery(query) {
|
|
5800
|
+
return /\b(recommend|suggest|what should i (buy|get|choose)|best options|top picks)\b/i.test(query);
|
|
5801
|
+
}
|
|
5802
|
+
static isTrendQuery(query) {
|
|
5803
|
+
return /\b(trend|trends|growth|over time|historical|timeline|monthly|yearly|weekly|daily|revenue growth|sales growth)\b/i.test(query);
|
|
5804
|
+
}
|
|
5805
|
+
static isRankingQuery(query) {
|
|
5806
|
+
return /\b(highest|lowest|top\s*\d+|bottom\s*\d+|rank|ranking|top performing|best performing)\b/i.test(query);
|
|
5807
|
+
}
|
|
5808
|
+
static isAnalyticsQuery(query) {
|
|
5809
|
+
return /\b(sales|revenue|analytics|insights|distribution|percentage|share|breakdown|breakup|scatter|correlation|metrics|performance|by region|by category)\b/i.test(query);
|
|
5810
|
+
}
|
|
5811
|
+
};
|
|
5812
|
+
|
|
5813
|
+
// src/rendering/rules/Rule1SpecificInfoRule.ts
|
|
5814
|
+
var Rule1SpecificInfoRule = class {
|
|
5815
|
+
constructor() {
|
|
5816
|
+
this.name = "Rule1SpecificInfo";
|
|
5817
|
+
this.priority = 100;
|
|
5818
|
+
}
|
|
5819
|
+
evaluate(context, intent) {
|
|
5820
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5821
|
+
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|define|where|when|why|meaning of)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
5822
|
+
const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
|
|
5823
|
+
if (isFactQuery && isNonVisual) {
|
|
5824
|
+
return {
|
|
5825
|
+
renderType: "text",
|
|
5826
|
+
confidence: 0.95,
|
|
5827
|
+
reason: "Rule 1: Specific information request (fact, explanation, or definition) requires plain text rendering."
|
|
5828
|
+
};
|
|
5829
|
+
}
|
|
5830
|
+
return null;
|
|
5831
|
+
}
|
|
5832
|
+
};
|
|
5833
|
+
|
|
5834
|
+
// src/rendering/rules/Rule2ComparisonRule.ts
|
|
5835
|
+
var Rule2ComparisonRule = class {
|
|
5836
|
+
constructor() {
|
|
5837
|
+
this.name = "Rule2Comparison";
|
|
5838
|
+
this.priority = 90;
|
|
5839
|
+
}
|
|
5840
|
+
evaluate(context, intent) {
|
|
5841
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5842
|
+
const isComparison = intent === "comparison" || /\b(compare|comparison|vs\.?|versus|difference between|differ|contrast|against)\b/i.test(q);
|
|
5843
|
+
if (isComparison) {
|
|
5844
|
+
return {
|
|
5845
|
+
renderType: "table",
|
|
5846
|
+
confidence: 0.9,
|
|
5847
|
+
reason: "Rule 2: Comparison query contrasting multiple entities requires structured table rendering."
|
|
5848
|
+
};
|
|
5849
|
+
}
|
|
5850
|
+
return null;
|
|
5851
|
+
}
|
|
5852
|
+
};
|
|
5853
|
+
|
|
5854
|
+
// src/rendering/rules/Rule3ProductDiscoveryRule.ts
|
|
5855
|
+
var Rule3ProductDiscoveryRule = class {
|
|
5856
|
+
constructor() {
|
|
5857
|
+
this.name = "Rule3ProductDiscovery";
|
|
5858
|
+
this.priority = 85;
|
|
5859
|
+
}
|
|
5860
|
+
evaluate(context, intent) {
|
|
5861
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5862
|
+
const isDiscovery = intent === "product_search" || intent === "recommendation" || /\b(best|recommend|top|buy|shopping|under \$|cheap|shoes|laptops|chairs|headphones|sneakers|apparel|products|show available)\b/i.test(q);
|
|
5863
|
+
const isNotChart = !/\b(chart|sales|revenue|monthly|growth|trend|over time)\b/i.test(q);
|
|
5864
|
+
const isAnalyticalRanking = /\b(top performing|top selling|best performing|performing|sales|revenue|ranking)\b/i.test(q);
|
|
5865
|
+
if (isDiscovery && isNotChart && !isAnalyticalRanking) {
|
|
5866
|
+
return {
|
|
5867
|
+
renderType: "carousel",
|
|
5868
|
+
confidence: 0.9,
|
|
5869
|
+
reason: "Rule 3: Product discovery/recommendation query requires product carousel rendering."
|
|
5870
|
+
};
|
|
5871
|
+
}
|
|
5872
|
+
return null;
|
|
5873
|
+
}
|
|
5874
|
+
};
|
|
5875
|
+
|
|
5876
|
+
// src/rendering/rules/Rule4AnalyticalRule.ts
|
|
5877
|
+
var Rule4AnalyticalRule = class {
|
|
5878
|
+
constructor() {
|
|
5879
|
+
this.name = "Rule4Analytical";
|
|
5880
|
+
this.priority = 80;
|
|
5881
|
+
}
|
|
5882
|
+
evaluate(context, intent) {
|
|
5883
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5884
|
+
const isAnalytical = intent === "analytics" || intent === "trend_analysis" || intent === "ranking" || /\b(monthly sales|revenue growth|top performing|population trends|sales by|growth|distribution|share|percentage|breakdown|correlation|scatter|trend)\b/i.test(q);
|
|
5885
|
+
if (!isAnalytical) return null;
|
|
5886
|
+
let chartType = "bar";
|
|
5887
|
+
if (intent === "trend_analysis" || /\b(monthly|yearly|weekly|growth|over time|trend|timeline|historical)\b/i.test(q)) {
|
|
5888
|
+
chartType = "line";
|
|
5889
|
+
} else if (/\b(share|percentage|percent|breakup|breakdown|composition|pie|donut|proportion)\b/i.test(q)) {
|
|
5890
|
+
chartType = "pie";
|
|
5891
|
+
} else if (/\b(correlation|scatter|relationship|relation|impact of|depends on)\b/i.test(q)) {
|
|
5892
|
+
chartType = "scatter";
|
|
5893
|
+
} else {
|
|
5894
|
+
chartType = "bar";
|
|
5895
|
+
}
|
|
5896
|
+
return {
|
|
5897
|
+
renderType: "chart",
|
|
5898
|
+
chartType,
|
|
5899
|
+
confidence: 0.85,
|
|
5900
|
+
reason: `Rule 4: Analytical query with numerical insights selects ${chartType} chart.`
|
|
5901
|
+
};
|
|
5902
|
+
}
|
|
5903
|
+
};
|
|
5904
|
+
|
|
5905
|
+
// src/rendering/rules/Rule5MixedResponseRule.ts
|
|
5906
|
+
var Rule5MixedResponseRule = class {
|
|
5907
|
+
constructor() {
|
|
5908
|
+
this.name = "Rule5MixedResponse";
|
|
5909
|
+
this.priority = 95;
|
|
5910
|
+
}
|
|
5911
|
+
evaluate(context, _intent) {
|
|
5912
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5913
|
+
const isTopSellingThisMonth = /\btop\s*(selling|performing)?\s*products?\s*(this|last)?\s*(month|year|week)\b/i.test(q);
|
|
5914
|
+
const hasMultipleSectionsRequest = /\b(summary|overview)\b/i.test(q) && /\b(carousel|products?|items?)\b/i.test(q) && /\b(chart|sales|trend|revenue)\b/i.test(q);
|
|
5915
|
+
if (isTopSellingThisMonth || hasMultipleSectionsRequest) {
|
|
5916
|
+
return {
|
|
5917
|
+
renderType: "mixed",
|
|
5918
|
+
confidence: 0.95,
|
|
5919
|
+
reason: "Rule 5: Mixed response query requires multi-section composition (text, carousel, chart).",
|
|
5920
|
+
sections: [
|
|
5921
|
+
{ type: "text", title: "Summary" },
|
|
5922
|
+
{ type: "carousel", title: "Top Products" },
|
|
5923
|
+
{ type: "chart", chartType: "bar", title: "Sales Performance" }
|
|
5924
|
+
]
|
|
5925
|
+
};
|
|
5926
|
+
}
|
|
5927
|
+
return null;
|
|
5928
|
+
}
|
|
5929
|
+
};
|
|
5930
|
+
|
|
5931
|
+
// src/rendering/rules/Rule6SmallResultSetRule.ts
|
|
5932
|
+
var Rule6SmallResultSetRule = class {
|
|
5933
|
+
constructor() {
|
|
5934
|
+
this.name = "Rule6SmallResultSet";
|
|
5935
|
+
this.priority = 110;
|
|
5936
|
+
}
|
|
5937
|
+
evaluate(context, _intent) {
|
|
5938
|
+
const docs = context.retrievedDocuments || [];
|
|
5939
|
+
if (docs.length > 0 && docs.length < 3) {
|
|
5940
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5941
|
+
const isExplicitComparison = /\b(compare|versus|vs\.?)\b/i.test(q);
|
|
5942
|
+
return {
|
|
5943
|
+
renderType: isExplicitComparison ? "table" : "text",
|
|
5944
|
+
confidence: 0.98,
|
|
5945
|
+
reason: `Rule 6: Small result set (${docs.length} rows < 3). Charts avoided to prevent sparse or uninformative visualizations.`
|
|
5946
|
+
};
|
|
5947
|
+
}
|
|
5948
|
+
return null;
|
|
5949
|
+
}
|
|
5950
|
+
};
|
|
5951
|
+
|
|
5952
|
+
// src/rendering/rules/Rule7LargeTableRule.ts
|
|
5953
|
+
var Rule7LargeTableRule = class {
|
|
5954
|
+
constructor() {
|
|
5955
|
+
this.name = "Rule7LargeTable";
|
|
5956
|
+
this.priority = 75;
|
|
5957
|
+
}
|
|
5958
|
+
evaluate(context, intent) {
|
|
5959
|
+
const docs = context.retrievedDocuments || [];
|
|
5960
|
+
if (docs.length > 5) {
|
|
5961
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
5962
|
+
const hasNumericAgg = intent === "analytics" || intent === "ranking" || /\b(sales|revenue|growth|count|total|average)\b/i.test(q);
|
|
5963
|
+
if (hasNumericAgg) {
|
|
5964
|
+
return {
|
|
5965
|
+
renderType: "mixed",
|
|
5966
|
+
confidence: 0.88,
|
|
5967
|
+
reason: `Rule 7: Large result set (${docs.length} rows > 5) with numeric aggregation rendered as Table + Chart.`,
|
|
5968
|
+
sections: [
|
|
5969
|
+
{ type: "table", title: "Detailed Data Table" },
|
|
5970
|
+
{ type: "chart", chartType: "bar", title: "Numerical Breakdown" }
|
|
5971
|
+
]
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
return {
|
|
5975
|
+
renderType: "table",
|
|
5976
|
+
confidence: 0.85,
|
|
5977
|
+
reason: `Rule 7: Large result set (${docs.length} rows > 5) rendered as structured table for readability.`
|
|
5978
|
+
};
|
|
5979
|
+
}
|
|
5980
|
+
return null;
|
|
5981
|
+
}
|
|
5982
|
+
};
|
|
5983
|
+
|
|
5984
|
+
// src/rendering/RuleEngine.ts
|
|
5985
|
+
var RuleEngine = class {
|
|
5986
|
+
constructor() {
|
|
5987
|
+
this.rules = [];
|
|
5988
|
+
this.registerDefaultRules();
|
|
5989
|
+
}
|
|
5990
|
+
/**
|
|
5991
|
+
* Register standard rules in priority order.
|
|
5992
|
+
*/
|
|
5993
|
+
registerDefaultRules() {
|
|
5994
|
+
this.registerRule(new Rule6SmallResultSetRule());
|
|
5995
|
+
this.registerRule(new Rule1SpecificInfoRule());
|
|
5996
|
+
this.registerRule(new Rule5MixedResponseRule());
|
|
5997
|
+
this.registerRule(new Rule2ComparisonRule());
|
|
5998
|
+
this.registerRule(new Rule3ProductDiscoveryRule());
|
|
5999
|
+
this.registerRule(new Rule4AnalyticalRule());
|
|
6000
|
+
this.registerRule(new Rule7LargeTableRule());
|
|
6001
|
+
}
|
|
6002
|
+
/**
|
|
6003
|
+
* Add or replace a rule in the engine.
|
|
6004
|
+
* Automatically keeps rules sorted by descending priority.
|
|
6005
|
+
*/
|
|
6006
|
+
registerRule(rule) {
|
|
6007
|
+
this.rules = this.rules.filter((r) => r.name !== rule.name);
|
|
6008
|
+
this.rules.push(rule);
|
|
6009
|
+
this.rules.sort((a, b) => b.priority - a.priority);
|
|
6010
|
+
}
|
|
6011
|
+
/**
|
|
6012
|
+
* Evaluate context against rules in descending priority order.
|
|
6013
|
+
* Returns the first non-null RenderDecision.
|
|
6014
|
+
*/
|
|
6015
|
+
evaluate(context, intent) {
|
|
6016
|
+
for (const rule of this.rules) {
|
|
6017
|
+
const decision = rule.evaluate(context, intent);
|
|
6018
|
+
if (decision) {
|
|
6019
|
+
return decision;
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
return {
|
|
6023
|
+
renderType: "text",
|
|
6024
|
+
confidence: 0.7,
|
|
6025
|
+
reason: "Default fallback: No specific visualization rule matched context."
|
|
6026
|
+
};
|
|
6027
|
+
}
|
|
6028
|
+
/**
|
|
6029
|
+
* List all registered rules.
|
|
6030
|
+
*/
|
|
6031
|
+
getRegisteredRules() {
|
|
6032
|
+
return this.rules.map((r) => ({ name: r.name, priority: r.priority }));
|
|
6033
|
+
}
|
|
6034
|
+
};
|
|
6035
|
+
|
|
6036
|
+
// src/rendering/RendererRegistry.ts
|
|
6037
|
+
var TextRendererStrategy = class {
|
|
6038
|
+
constructor() {
|
|
6039
|
+
this.type = "text";
|
|
6040
|
+
}
|
|
6041
|
+
render(data, options) {
|
|
6042
|
+
const title = (options == null ? void 0 : options.title) || "Information";
|
|
6043
|
+
if (typeof data === "string") {
|
|
6044
|
+
return { type: "text", title, data: { content: data } };
|
|
6045
|
+
}
|
|
6046
|
+
const docs = Array.isArray(data) ? data : [];
|
|
6047
|
+
const text = docs.map((d) => d.content).join("\n\n");
|
|
6048
|
+
return { type: "text", title, data: { content: text || "No detailed content available." } };
|
|
6049
|
+
}
|
|
6050
|
+
};
|
|
6051
|
+
var TableRendererStrategy = class {
|
|
6052
|
+
constructor() {
|
|
6053
|
+
this.type = "table";
|
|
6054
|
+
}
|
|
6055
|
+
render(data, options) {
|
|
6056
|
+
const docs = Array.isArray(data) ? data : [];
|
|
6057
|
+
const query = (options == null ? void 0 : options.query) || "";
|
|
6058
|
+
return UITransformer.transform(query, docs, void 0, void 0, {
|
|
6059
|
+
visualizationHint: "table",
|
|
6060
|
+
recommendedChart: "table",
|
|
6061
|
+
filterInStockOnly: false,
|
|
6062
|
+
wantsExplicitTable: true,
|
|
6063
|
+
isTemporal: false,
|
|
6064
|
+
isComparison: true,
|
|
6065
|
+
language: "en"
|
|
6066
|
+
});
|
|
6067
|
+
}
|
|
6068
|
+
};
|
|
6069
|
+
var CarouselRendererStrategy = class {
|
|
6070
|
+
constructor() {
|
|
6071
|
+
this.type = "carousel";
|
|
6072
|
+
}
|
|
6073
|
+
render(data, options) {
|
|
6074
|
+
const docs = Array.isArray(data) ? data : [];
|
|
6075
|
+
const query = (options == null ? void 0 : options.query) || "";
|
|
6076
|
+
return UITransformer.transform(query, docs, void 0, void 0, {
|
|
6077
|
+
visualizationHint: "product_browse",
|
|
6078
|
+
recommendedChart: "text",
|
|
6079
|
+
filterInStockOnly: false,
|
|
6080
|
+
wantsExplicitTable: false,
|
|
6081
|
+
isTemporal: false,
|
|
6082
|
+
isComparison: false,
|
|
6083
|
+
language: "en"
|
|
6084
|
+
});
|
|
6085
|
+
}
|
|
6086
|
+
};
|
|
6087
|
+
var ChartRendererStrategy = class {
|
|
6088
|
+
constructor() {
|
|
6089
|
+
this.type = "chart";
|
|
6090
|
+
}
|
|
6091
|
+
render(data, options) {
|
|
6092
|
+
const docs = Array.isArray(data) ? data : [];
|
|
6093
|
+
const query = (options == null ? void 0 : options.query) || "";
|
|
6094
|
+
const chartType = (options == null ? void 0 : options.chartType) || "bar";
|
|
6095
|
+
let hint = "comparison";
|
|
6096
|
+
let rec = "bar_chart";
|
|
6097
|
+
if (chartType === "line") {
|
|
6098
|
+
hint = "trend";
|
|
6099
|
+
rec = "line_chart";
|
|
6100
|
+
} else if (chartType === "pie") {
|
|
6101
|
+
hint = "composition";
|
|
6102
|
+
rec = "pie_chart";
|
|
6103
|
+
} else if (chartType === "scatter") {
|
|
6104
|
+
hint = "correlation";
|
|
6105
|
+
rec = "scatter_plot";
|
|
6106
|
+
}
|
|
6107
|
+
return UITransformer.transform(query, docs, void 0, void 0, {
|
|
6108
|
+
visualizationHint: hint,
|
|
6109
|
+
recommendedChart: rec,
|
|
6110
|
+
filterInStockOnly: false,
|
|
6111
|
+
wantsExplicitTable: false,
|
|
6112
|
+
isTemporal: chartType === "line",
|
|
6113
|
+
isComparison: chartType === "bar",
|
|
6114
|
+
language: "en"
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
};
|
|
6118
|
+
var MixedRendererStrategy = class {
|
|
6119
|
+
constructor() {
|
|
6120
|
+
this.type = "mixed";
|
|
6121
|
+
}
|
|
6122
|
+
render(data, options) {
|
|
6123
|
+
const docs = Array.isArray(data) ? data : [];
|
|
6124
|
+
const sections = (options == null ? void 0 : options.sections) || [];
|
|
6125
|
+
const sectionPayloads = sections.map((s) => {
|
|
6126
|
+
const strategy = RendererRegistry.getStrategy(s.type);
|
|
6127
|
+
return {
|
|
6128
|
+
type: s.type,
|
|
6129
|
+
title: s.title,
|
|
6130
|
+
payload: strategy.render(data, __spreadProps(__spreadValues({}, options), { chartType: s.chartType, title: s.title }))
|
|
6131
|
+
};
|
|
6132
|
+
});
|
|
6133
|
+
return {
|
|
6134
|
+
type: "table",
|
|
6135
|
+
// Fallback container type for compatibility
|
|
6136
|
+
title: (options == null ? void 0 : options.title) || "Composite Response",
|
|
6137
|
+
description: `Mixed payload containing ${sectionPayloads.length} visual sections`,
|
|
6138
|
+
data: { sections: sectionPayloads }
|
|
6139
|
+
};
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
6142
|
+
var _RendererRegistry = class _RendererRegistry {
|
|
6143
|
+
/**
|
|
6144
|
+
* Register a new renderer strategy.
|
|
6145
|
+
* Enables seamless addition of custom visualizers (Timeline, Maps, Flow Diagrams, etc.).
|
|
6146
|
+
*/
|
|
6147
|
+
static registerStrategy(strategy) {
|
|
6148
|
+
this.strategies.set(String(strategy.type).toLowerCase(), strategy);
|
|
6149
|
+
}
|
|
6150
|
+
/**
|
|
6151
|
+
* Retrieve a registered renderer strategy by type string.
|
|
6152
|
+
*/
|
|
6153
|
+
static getStrategy(type) {
|
|
6154
|
+
const key = type.toLowerCase();
|
|
6155
|
+
const strategy = this.strategies.get(key);
|
|
6156
|
+
if (!strategy) {
|
|
6157
|
+
return this.strategies.get("text");
|
|
6158
|
+
}
|
|
6159
|
+
return strategy;
|
|
6160
|
+
}
|
|
6161
|
+
/**
|
|
6162
|
+
* Check if a renderer strategy is registered.
|
|
6163
|
+
*/
|
|
6164
|
+
static hasStrategy(type) {
|
|
6165
|
+
return this.strategies.has(type.toLowerCase());
|
|
6166
|
+
}
|
|
6167
|
+
/**
|
|
6168
|
+
* List all registered strategy types.
|
|
6169
|
+
*/
|
|
6170
|
+
static getRegisteredTypes() {
|
|
6171
|
+
return Array.from(this.strategies.keys());
|
|
6172
|
+
}
|
|
6173
|
+
};
|
|
6174
|
+
_RendererRegistry.strategies = /* @__PURE__ */ new Map();
|
|
6175
|
+
_RendererRegistry.registerStrategy(new TextRendererStrategy());
|
|
6176
|
+
_RendererRegistry.registerStrategy(new TableRendererStrategy());
|
|
6177
|
+
_RendererRegistry.registerStrategy(new CarouselRendererStrategy());
|
|
6178
|
+
_RendererRegistry.registerStrategy(new ChartRendererStrategy());
|
|
6179
|
+
_RendererRegistry.registerStrategy(new MixedRendererStrategy());
|
|
6180
|
+
var RendererRegistry = _RendererRegistry;
|
|
6181
|
+
|
|
6182
|
+
// src/rendering/VisualizationDecisionEngine.ts
|
|
6183
|
+
var LRUDecisionCache = class {
|
|
6184
|
+
constructor(maxSize = 200) {
|
|
6185
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
6186
|
+
this.maxSize = maxSize;
|
|
6187
|
+
}
|
|
6188
|
+
generateKey(context) {
|
|
6189
|
+
var _a2, _b;
|
|
6190
|
+
const q = (context.userQuery || "").toLowerCase().trim();
|
|
6191
|
+
const docCount = (_b = (_a2 = context.retrievedDocuments) == null ? void 0 : _a2.length) != null ? _b : 0;
|
|
6192
|
+
return `${q}::docs:${docCount}`;
|
|
6193
|
+
}
|
|
6194
|
+
get(context) {
|
|
6195
|
+
const key = this.generateKey(context);
|
|
6196
|
+
const item = this.cache.get(key);
|
|
6197
|
+
if (item) {
|
|
6198
|
+
this.cache.delete(key);
|
|
6199
|
+
this.cache.set(key, item);
|
|
6200
|
+
return item.decision;
|
|
6201
|
+
}
|
|
6202
|
+
return void 0;
|
|
6203
|
+
}
|
|
6204
|
+
set(context, decision) {
|
|
6205
|
+
const key = this.generateKey(context);
|
|
6206
|
+
if (this.cache.has(key)) {
|
|
6207
|
+
this.cache.delete(key);
|
|
6208
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
6209
|
+
const oldest = this.cache.keys().next().value;
|
|
6210
|
+
if (oldest !== void 0) this.cache.delete(oldest);
|
|
6211
|
+
}
|
|
6212
|
+
this.cache.set(key, { decision, timestamp: Date.now() });
|
|
6213
|
+
}
|
|
6214
|
+
clear() {
|
|
6215
|
+
this.cache.clear();
|
|
6216
|
+
}
|
|
6217
|
+
get size() {
|
|
6218
|
+
return this.cache.size;
|
|
6219
|
+
}
|
|
6220
|
+
};
|
|
6221
|
+
var VisualizationDecisionEngine = class {
|
|
6222
|
+
/**
|
|
6223
|
+
* Evaluate user query, documents, and context to produce a RenderDecision.
|
|
6224
|
+
* Execution time is < 1ms for cached or rule-matched queries.
|
|
6225
|
+
*/
|
|
6226
|
+
static decideVisualization(userQuery, retrievedDocuments, llmResponse, metadata) {
|
|
6227
|
+
const context = {
|
|
6228
|
+
userQuery,
|
|
6229
|
+
retrievedDocuments,
|
|
6230
|
+
llmResponse,
|
|
6231
|
+
metadata
|
|
6232
|
+
};
|
|
6233
|
+
const cached = this.cache.get(context);
|
|
6234
|
+
if (cached) {
|
|
6235
|
+
return cached;
|
|
6236
|
+
}
|
|
6237
|
+
const { intent } = IntentClassifier.classify(context);
|
|
6238
|
+
const decision = this.ruleEngine.evaluate(context, intent);
|
|
6239
|
+
this.cache.set(context, decision);
|
|
6240
|
+
return decision;
|
|
6241
|
+
}
|
|
6242
|
+
/**
|
|
6243
|
+
* Convenience helper to decide AND render the data into a UITransformationResponse.
|
|
6244
|
+
*/
|
|
6245
|
+
static render(userQuery, retrievedDocuments, llmResponse, metadata) {
|
|
6246
|
+
const decision = this.decideVisualization(userQuery, retrievedDocuments, llmResponse, metadata);
|
|
6247
|
+
const strategy = RendererRegistry.getStrategy(decision.renderType);
|
|
6248
|
+
return strategy.render(retrievedDocuments || [], __spreadValues({
|
|
6249
|
+
query: userQuery,
|
|
6250
|
+
chartType: decision.chartType,
|
|
6251
|
+
sections: decision.sections
|
|
6252
|
+
}, metadata));
|
|
6253
|
+
}
|
|
6254
|
+
/**
|
|
6255
|
+
* Register a custom rule in the rule engine.
|
|
6256
|
+
*/
|
|
6257
|
+
static registerRule(rule) {
|
|
6258
|
+
this.ruleEngine.registerRule(rule);
|
|
6259
|
+
this.cache.clear();
|
|
6260
|
+
}
|
|
6261
|
+
/**
|
|
6262
|
+
* Register a custom renderer strategy.
|
|
6263
|
+
*/
|
|
6264
|
+
static registerRendererStrategy(strategy) {
|
|
6265
|
+
RendererRegistry.registerStrategy(strategy);
|
|
6266
|
+
}
|
|
6267
|
+
/**
|
|
6268
|
+
* Clear the decision cache.
|
|
6269
|
+
*/
|
|
6270
|
+
static clearCache() {
|
|
6271
|
+
this.cache.clear();
|
|
6272
|
+
}
|
|
6273
|
+
/**
|
|
6274
|
+
* Get size of current decision cache.
|
|
6275
|
+
*/
|
|
6276
|
+
static getCacheSize() {
|
|
6277
|
+
return this.cache.size;
|
|
6278
|
+
}
|
|
6279
|
+
};
|
|
6280
|
+
VisualizationDecisionEngine.ruleEngine = new RuleEngine();
|
|
6281
|
+
VisualizationDecisionEngine.cache = new LRUDecisionCache(200);
|
|
6282
|
+
|
|
6283
|
+
// src/utils/UITransformer.ts
|
|
5692
6284
|
var UITransformer = class _UITransformer {
|
|
5693
6285
|
// ─── Public Entry Points ─────────────────────────────────────────────────
|
|
5694
6286
|
/**
|
|
@@ -5735,6 +6327,9 @@ var UITransformer = class _UITransformer {
|
|
|
5735
6327
|
return this.hasMultipleFields(filteredData) ? this.transformToTable(filteredData, userQuery) : this.transformToText(filteredData);
|
|
5736
6328
|
}
|
|
5737
6329
|
if ((hasProducts || this.isProductQuery(userQuery)) && resolvedIntent.visualizationHint === "product_browse") {
|
|
6330
|
+
if (!this.isProductQuery(userQuery)) {
|
|
6331
|
+
return this.transformToText(filteredData);
|
|
6332
|
+
}
|
|
5738
6333
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
5739
6334
|
}
|
|
5740
6335
|
const automatic = this.chooseAutomaticVisualization(filteredData, profile, userQuery);
|
|
@@ -5749,6 +6344,11 @@ var UITransformer = class _UITransformer {
|
|
|
5749
6344
|
* Step 3 — Fall back to the heuristic `transform()` if either LLM call fails.
|
|
5750
6345
|
*/
|
|
5751
6346
|
static async analyzeAndDecide(query, sources, llm) {
|
|
6347
|
+
const decision = VisualizationDecisionEngine.decideVisualization(query, sources);
|
|
6348
|
+
if (decision.renderType === "text") {
|
|
6349
|
+
console.debug("[UITransformer] Decision Engine selected plain text rendering \u2014 skipping visual LLM calls.");
|
|
6350
|
+
return this.transformToText(sources);
|
|
6351
|
+
}
|
|
5752
6352
|
let intent;
|
|
5753
6353
|
try {
|
|
5754
6354
|
intent = await this.detectIntent(query, llm, sources);
|
|
@@ -6198,7 +6798,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
6198
6798
|
}
|
|
6199
6799
|
static transformToText(data) {
|
|
6200
6800
|
return this.createTextResponse(
|
|
6201
|
-
"
|
|
6801
|
+
"Retrieved Context",
|
|
6202
6802
|
data.map((item) => item.content).join("\n\n"),
|
|
6203
6803
|
`Found ${data.length} relevant results`
|
|
6204
6804
|
);
|
|
@@ -6365,16 +6965,13 @@ ${schemaProfileText}` : ""}`;
|
|
|
6365
6965
|
"product",
|
|
6366
6966
|
"price",
|
|
6367
6967
|
"stock",
|
|
6368
|
-
"item",
|
|
6369
6968
|
"sku",
|
|
6370
6969
|
"brand",
|
|
6371
|
-
"model",
|
|
6372
6970
|
"msrp",
|
|
6373
6971
|
"inventory",
|
|
6374
6972
|
"buy",
|
|
6375
6973
|
"shop",
|
|
6376
6974
|
"beauty",
|
|
6377
|
-
"care",
|
|
6378
6975
|
"cosmetic",
|
|
6379
6976
|
"facial",
|
|
6380
6977
|
"cream",
|
|
@@ -6387,7 +6984,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
6387
6984
|
const metadata = item.metadata || {};
|
|
6388
6985
|
const hasMetadataKey = Object.keys(metadata).some((k) => {
|
|
6389
6986
|
const val = metadata[k];
|
|
6390
|
-
return ["price", "product", "sku", "brand", "
|
|
6987
|
+
return ["price", "product", "sku", "brand", "cost"].includes(k.toLowerCase()) && val !== null && val !== void 0 && val !== "";
|
|
6391
6988
|
});
|
|
6392
6989
|
const hasPricePattern = /\$\s*\d+\.\d{2}/.test(content);
|
|
6393
6990
|
return hasKeywords || hasMetadataKey || hasPricePattern;
|
|
@@ -7076,6 +7673,13 @@ SchemaMapper.TARGET_PROPERTIES = {
|
|
|
7076
7673
|
};
|
|
7077
7674
|
|
|
7078
7675
|
// src/core/Pipeline.ts
|
|
7676
|
+
function formatNamespace(raw) {
|
|
7677
|
+
if (!raw || !raw.trim()) return "retrivora-default";
|
|
7678
|
+
const trimmed = raw.trim();
|
|
7679
|
+
if (trimmed.startsWith("retrivora-")) return trimmed;
|
|
7680
|
+
const sanitized = trimmed.toLowerCase().replace(/[^a-z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
7681
|
+
return `retrivora-${sanitized}`;
|
|
7682
|
+
}
|
|
7079
7683
|
var LRUEmbeddingCache = class {
|
|
7080
7684
|
constructor(maxSize = 500) {
|
|
7081
7685
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -7267,7 +7871,7 @@ ${m.content}`).join("\n\n---\n\n");
|
|
|
7267
7871
|
*/
|
|
7268
7872
|
async ingest(documents, namespace) {
|
|
7269
7873
|
await this.initialize();
|
|
7270
|
-
const ns = namespace != null ? namespace : this.config.projectId;
|
|
7874
|
+
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
7271
7875
|
const results = [];
|
|
7272
7876
|
for (const doc of documents) {
|
|
7273
7877
|
try {
|
|
@@ -7486,7 +8090,7 @@ ${m.content}`).join("\n\n---\n\n");
|
|
|
7486
8090
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
7487
8091
|
var _a2, _b, _c, _d, _e, _f, _g2, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C;
|
|
7488
8092
|
yield new __await(this.initialize());
|
|
7489
|
-
const ns = namespace != null ? namespace : this.config.projectId;
|
|
8093
|
+
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
7490
8094
|
const topK = (_b = (_a2 = this.config.rag) == null ? void 0 : _a2.topK) != null ? _b : 5;
|
|
7491
8095
|
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
|
|
7492
8096
|
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7624,7 +8228,7 @@ ${context}`;
|
|
|
7624
8228
|
const isNativeThinking = isClaude37 && (((_p = this.config.llm.options) == null ? void 0 : _p.thinking) === true || ((_q = this.config.llm.options) == null ? void 0 : _q.thinking) === "enabled" || ((_r = this.config.llm.options) == null ? void 0 : _r.thinking) !== false);
|
|
7625
8229
|
const modelNameLower = this.config.llm.model.toLowerCase();
|
|
7626
8230
|
const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
|
|
7627
|
-
const isSimulatedThinking = !isNativeThinking && (((_s = this.config.llm.options) == null ? void 0 : _s.thinking) === true || ((_t = this.config.llm.options) == null ? void 0 : _t.thinking) === "enabled" ||
|
|
8231
|
+
const isSimulatedThinking = !isNativeThinking && (((_s = this.config.llm.options) == null ? void 0 : _s.thinking) === true || ((_t = this.config.llm.options) == null ? void 0 : _t.thinking) === "enabled" || ((_u = this.config.llm.options) == null ? void 0 : _u.thinking) !== false);
|
|
7628
8232
|
let finalRestrictionSuffix = restrictionSuffix;
|
|
7629
8233
|
if (isSimulatedThinking) {
|
|
7630
8234
|
finalRestrictionSuffix += "\n\n(IMPORTANT: You must think step-by-step before answering. Write your thinking process inside a `<think>...</think>` block. Write the `<think>` block first, then follow it with your final response. Keep the thinking process thorough and detailed. Example: `<think>Thinking details here...</think>Final answer text here.`)";
|
|
@@ -7950,7 +8554,7 @@ ${context}`;
|
|
|
7950
8554
|
}
|
|
7951
8555
|
async retrieve(query, options) {
|
|
7952
8556
|
var _a2, _b, _c, _d, _e, _f, _g2;
|
|
7953
|
-
const ns = (_a2 = options.namespace) != null ? _a2 : this.config.projectId;
|
|
8557
|
+
const ns = formatNamespace((_a2 = options.namespace) != null ? _a2 : this.config.projectId);
|
|
7954
8558
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
7955
8559
|
const cacheKey = `${ns}::${query}`;
|
|
7956
8560
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
@@ -8019,7 +8623,7 @@ Optimized Search Query:`;
|
|
|
8019
8623
|
async getSuggestions(query, namespace) {
|
|
8020
8624
|
if (!query || query.trim().length < 3) return [];
|
|
8021
8625
|
await this.initialize();
|
|
8022
|
-
const ns = namespace != null ? namespace : this.config.projectId;
|
|
8626
|
+
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
8023
8627
|
try {
|
|
8024
8628
|
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
8025
8629
|
if (sources.length === 0) return [];
|
|
@@ -8511,7 +9115,7 @@ var DocumentParser = class {
|
|
|
8511
9115
|
*/
|
|
8512
9116
|
static async parse(file, fileName, mimeType) {
|
|
8513
9117
|
var _a2;
|
|
8514
|
-
const extension = (_a2 = fileName.split(".").pop()) == null ? void 0 : _a2.toLowerCase();
|
|
9118
|
+
const extension = ((_a2 = fileName.split(".").pop()) == null ? void 0 : _a2.toLowerCase()) || "";
|
|
8515
9119
|
if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
|
|
8516
9120
|
return this.readAsText(file);
|
|
8517
9121
|
}
|
|
@@ -8527,6 +9131,29 @@ var DocumentParser = class {
|
|
|
8527
9131
|
if (extension === "csv" || mimeType === "text/csv") {
|
|
8528
9132
|
return this.readAsText(file);
|
|
8529
9133
|
}
|
|
9134
|
+
if (extension === "xlsx" || extension === "xls" || mimeType.includes("spreadsheet") || mimeType.includes("excel")) {
|
|
9135
|
+
try {
|
|
9136
|
+
const xlsx = await import("xlsx");
|
|
9137
|
+
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
9138
|
+
const workbook = xlsx.read(buffer, { type: "buffer" });
|
|
9139
|
+
const sheetsText = [];
|
|
9140
|
+
for (const sheetName of workbook.SheetNames) {
|
|
9141
|
+
const sheet = workbook.Sheets[sheetName];
|
|
9142
|
+
const csv = xlsx.utils.sheet_to_csv(sheet);
|
|
9143
|
+
if (csv && csv.trim()) {
|
|
9144
|
+
sheetsText.push(`--- Sheet: ${sheetName} ---
|
|
9145
|
+
${csv.trim()}`);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
return sheetsText.join("\n\n") || `[Empty Excel Workbook: ${fileName}]`;
|
|
9149
|
+
} catch (e) {
|
|
9150
|
+
try {
|
|
9151
|
+
return await this.readAsText(file);
|
|
9152
|
+
} catch (e2) {
|
|
9153
|
+
return `[Excel File Content: ${fileName}]`;
|
|
9154
|
+
}
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
8530
9157
|
if (extension === "pdf" || mimeType === "application/pdf") {
|
|
8531
9158
|
try {
|
|
8532
9159
|
const pdf = await import("pdf-parse");
|
|
@@ -8538,15 +9165,15 @@ var DocumentParser = class {
|
|
|
8538
9165
|
return `[PDF Parsing Error for ${fileName}]`;
|
|
8539
9166
|
}
|
|
8540
9167
|
}
|
|
8541
|
-
if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
|
|
9168
|
+
if (extension === "docx" || extension === "doc" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || mimeType === "application/msword") {
|
|
8542
9169
|
try {
|
|
8543
9170
|
const mammoth = await import("mammoth");
|
|
8544
9171
|
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
8545
9172
|
const result = await mammoth.extractRawText({ buffer });
|
|
8546
9173
|
return result.value;
|
|
8547
9174
|
} catch (err) {
|
|
8548
|
-
console.warn('[DocumentParser]
|
|
8549
|
-
return `[
|
|
9175
|
+
console.warn('[DocumentParser] Word parsing failed. Make sure "mammoth" is installed.', err);
|
|
9176
|
+
return `[Word Parsing Error for ${fileName}]`;
|
|
8550
9177
|
}
|
|
8551
9178
|
}
|
|
8552
9179
|
try {
|
|
@@ -9370,6 +9997,7 @@ function createUploadHandler(configOrPlugin, options) {
|
|
|
9370
9997
|
}
|
|
9371
9998
|
const documents = [];
|
|
9372
9999
|
for (const file of files) {
|
|
10000
|
+
const isExcel = file.name.toLowerCase().endsWith(".xlsx") || file.name.toLowerCase().endsWith(".xls") || file.type.includes("spreadsheet") || file.type.includes("excel");
|
|
9373
10001
|
if (file.name.toLowerCase().endsWith(".csv") || file.type === "text/csv") {
|
|
9374
10002
|
const text = await file.text();
|
|
9375
10003
|
const Papa = await import("papaparse");
|
|
@@ -9402,7 +10030,7 @@ function createUploadHandler(configOrPlugin, options) {
|
|
|
9402
10030
|
metadata: __spreadValues(__spreadProps(__spreadValues({
|
|
9403
10031
|
fileName: file.name,
|
|
9404
10032
|
fileSize: file.size,
|
|
9405
|
-
fileType: file.type,
|
|
10033
|
+
fileType: file.type || "text/csv",
|
|
9406
10034
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9407
10035
|
}, dimension ? { dimension } : {}), {
|
|
9408
10036
|
csvHeaders: csvCols
|
|
@@ -9410,6 +10038,42 @@ function createUploadHandler(configOrPlugin, options) {
|
|
|
9410
10038
|
});
|
|
9411
10039
|
}
|
|
9412
10040
|
}
|
|
10041
|
+
} else if (isExcel) {
|
|
10042
|
+
const xlsx = await import("xlsx");
|
|
10043
|
+
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
10044
|
+
const workbook = xlsx.read(buffer, { type: "buffer" });
|
|
10045
|
+
for (const sheetName of workbook.SheetNames) {
|
|
10046
|
+
const sheet = workbook.Sheets[sheetName];
|
|
10047
|
+
const jsonRows = xlsx.utils.sheet_to_json(sheet, { defval: "" });
|
|
10048
|
+
if (jsonRows && jsonRows.length > 0) {
|
|
10049
|
+
let i = 0;
|
|
10050
|
+
const headers = Object.keys(jsonRows[0] || {});
|
|
10051
|
+
for (const rowData of jsonRows) {
|
|
10052
|
+
i++;
|
|
10053
|
+
const contentParts = [];
|
|
10054
|
+
for (const [key, val] of Object.entries(rowData)) {
|
|
10055
|
+
if (key && val !== void 0 && val !== null && String(val).trim()) {
|
|
10056
|
+
contentParts.push(`${key}: ${val}`);
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
if (contentParts.length > 0) {
|
|
10060
|
+
documents.push({
|
|
10061
|
+
docId: `${file.name}-${sheetName}-row-${i}`,
|
|
10062
|
+
content: contentParts.join(", "),
|
|
10063
|
+
metadata: __spreadValues(__spreadProps(__spreadValues({
|
|
10064
|
+
fileName: file.name,
|
|
10065
|
+
sheetName,
|
|
10066
|
+
fileSize: file.size,
|
|
10067
|
+
fileType: file.type || "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
10068
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10069
|
+
}, dimension ? { dimension } : {}), {
|
|
10070
|
+
headers
|
|
10071
|
+
}), rowData)
|
|
10072
|
+
});
|
|
10073
|
+
}
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
}
|
|
9413
10077
|
} else {
|
|
9414
10078
|
const content = await DocumentParser.parse(file, file.name, file.type);
|
|
9415
10079
|
documents.push({
|