@retrivora-ai/rag-engine 2.3.2 → 2.3.4

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/index.mjs CHANGED
@@ -3049,7 +3049,7 @@ function useRagChat(projectId, options = {}) {
3049
3049
  // package.json
3050
3050
  var package_default = {
3051
3051
  name: "@retrivora-ai/rag-engine",
3052
- version: "2.3.2",
3052
+ version: "2.3.4",
3053
3053
  description: "Retrivora AI is a plug-and-play AI engine for RAG chat experiences \u2014 generic vector DB + LLM provider, embeddable or standalone.",
3054
3054
  author: "Abhinav Alkuchi",
3055
3055
  license: "UNLICENSED",
@@ -3438,14 +3438,15 @@ LicenseVerifier.CACHE_TTL_MS = 60 * 1e3;
3438
3438
  // Cache verified license for 60 seconds
3439
3439
  // Retrivora's Public Key used to verify the license key signature.
3440
3440
  // In production, this matches the private key held by Retrivora SaaS.
3441
+ // Rotate this key only when the signing private key is replaced.
3441
3442
  LicenseVerifier.PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
3442
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArMieCkCBtTfByRNOserm
3443
- XM4T0Am29JyNzB4XQPe+WJJ56CN73H1HLXx9n1ByFcxkEJ9po+SURj5DnvUXwEy+
3444
- xstx33wDPpVmcMQe33j5CRYS0S4gICiNqIRN7XkTlJtrcXqrmh1/hdOAwfRbjO1T
3445
- NVtORHwUwWfI/lBLyxUPGtKPed+0fQ7NtcW4o00JXxs3EjCB5BV9CbnXoTjQb/4h
3446
- iwZof4l8rb7oaNzOsVg0RSyxsETX7Ex5sI0CjBz1p2mYp4oVhw/A/h8Ls7H0XzjC
3447
- +Eb6BLtsytt5JHoSp0jExLlCpDmpys2L+kETcBVx+IqiXtdN1n6KbkksvEDUVzLI
3448
- MwIDAQAB
3443
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOQlmDSKfCZaRy06rxwa
3444
+ chuDimlF134xTCitKDTPTPizF23oSn34pGDJakm/0T09JVUX8L/cvhGPe/3lf/g9
3445
+ 4Y1q+Gb2KVM17KNGeT1Aj9Ua3BKz/yao/oFhi/r3Zt3cWY13B/Rd6clqY9WshIAV
3446
+ NqZNM8ilTl777cMbyym+6D+mzGXgmnJAO5brddIAI9FPLW0T3Znt8vEqDOIljEkC
3447
+ HCGHeirKABLbjKSJ4hljCa3vVitWqndhiq8CBSJG6HatiSGYotgMWGRY+0EbdlEv
3448
+ OJP4SqafQ3aD6YnL9q1n15QKwxu/uMK+JGM2VxxuyUi9GANtMIkCYy9xya73P4Qc
3449
+ gQIDAQAB
3449
3450
  -----END PUBLIC KEY-----`;
3450
3451
 
3451
3452
  // src/core/LicenseValidator.ts
@@ -4405,1958 +4406,6 @@ function CodeViewer({ snippet }) {
4405
4406
  ] });
4406
4407
  }
4407
4408
 
4408
- // src/rendering/IntentClassifier.ts
4409
- var IntentClassifier = class {
4410
- /**
4411
- * Fast, zero-latency classifier that maps a user query and optional data context
4412
- * into a high-level IntentCategory.
4413
- */
4414
- static classify(context) {
4415
- const q = (context.userQuery || "").toLowerCase().trim();
4416
- const signals = this.extractDataSignals(context);
4417
- if (this.isInformationLookup(q)) {
4418
- return { intent: "information_lookup", signals };
4419
- }
4420
- if (this.isComparisonQuery(q)) {
4421
- return { intent: "comparison", signals };
4422
- }
4423
- if (this.isProductQuery(q) || signals.isProductLike && this.isRecommendationQuery(q)) {
4424
- return { intent: "product_search", signals };
4425
- }
4426
- if (this.isTrendQuery(q) || signals.hasDateFields && signals.hasNumericFields && /\b(growth|trend|over time|monthly|yearly|weekly)\b/.test(q)) {
4427
- return { intent: "trend_analysis", signals };
4428
- }
4429
- if (this.isRankingQuery(q)) {
4430
- return { intent: "ranking", signals };
4431
- }
4432
- if (this.isAnalyticsQuery(q) || signals.hasNumericFields && signals.hasCategoricalFields && /\b(sales|revenue|performance|count|total|average|breakdown|share|percentage|correlation)\b/.test(q)) {
4433
- return { intent: "analytics", signals };
4434
- }
4435
- if (this.isRecommendationQuery(q)) {
4436
- return { intent: "recommendation", signals };
4437
- }
4438
- return { intent: "information_lookup", signals };
4439
- }
4440
- /**
4441
- * Extract key data structure signals from retrieved documents.
4442
- */
4443
- static extractDataSignals(context) {
4444
- const docs = context.retrievedDocuments || [];
4445
- const rowCount = docs.length;
4446
- let hasNumericFields = false;
4447
- let hasDateFields = false;
4448
- let hasCategoricalFields = false;
4449
- let isProductLike = false;
4450
- let numericFieldCount = 0;
4451
- let categoricalFieldCount = 0;
4452
- if (docs.length > 0) {
4453
- const keys = /* @__PURE__ */ new Set();
4454
- let numericKeys = /* @__PURE__ */ new Set();
4455
- let dateKeys = /* @__PURE__ */ new Set();
4456
- let catKeys = /* @__PURE__ */ new Set();
4457
- let productKeyMatches = 0;
4458
- docs.forEach((doc) => {
4459
- const meta = doc.metadata || {};
4460
- Object.entries(meta).forEach(([key, val]) => {
4461
- keys.add(key);
4462
- const kLower = key.toLowerCase();
4463
- if (["price", "image", "brand", "in_stock", "rating", "sku", "product"].some((p) => kLower.includes(p))) {
4464
- productKeyMatches++;
4465
- }
4466
- if (typeof val === "number") {
4467
- numericKeys.add(key);
4468
- } else if (typeof val === "string") {
4469
- if (/^\d{4}-\d{2}-\d{2}/.test(val) || !isNaN(Date.parse(val)) && val.length > 5) {
4470
- dateKeys.add(key);
4471
- } else if (val.length < 50 && !val.includes("\n")) {
4472
- catKeys.add(key);
4473
- }
4474
- }
4475
- });
4476
- });
4477
- hasNumericFields = numericKeys.size > 0;
4478
- hasDateFields = dateKeys.size > 0;
4479
- hasCategoricalFields = catKeys.size > 0;
4480
- numericFieldCount = numericKeys.size;
4481
- categoricalFieldCount = catKeys.size;
4482
- if (productKeyMatches >= 2 || docs.some((d) => {
4483
- var _a, _b;
4484
- return ((_a = d == null ? void 0 : d.content) != null ? _a : "").toLowerCase().includes("price") || ((_b = d == null ? void 0 : d.content) != null ? _b : "").toLowerCase().includes("brand");
4485
- })) {
4486
- isProductLike = true;
4487
- }
4488
- }
4489
- return {
4490
- rowCount,
4491
- hasNumericFields,
4492
- hasDateFields,
4493
- hasCategoricalFields,
4494
- isProductLike,
4495
- numericFieldCount,
4496
- categoricalFieldCount
4497
- };
4498
- }
4499
- // ─── Intent Heuristic Checkers ─────────────────────────────────────────────
4500
- static isInformationLookup(query) {
4501
- if (/^(what|who|explain|explore|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(query)) {
4502
- if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
4503
- return true;
4504
- }
4505
- }
4506
- if (/^(what is the )?(price|cost|fee|rate) of\b/i.test(query)) {
4507
- return true;
4508
- }
4509
- return false;
4510
- }
4511
- static isComparisonQuery(query) {
4512
- return /\b(compare|comparison|vs\.?|versus|difference between|differ|contrast|against)\b/i.test(query);
4513
- }
4514
- static isProductQuery(query) {
4515
- 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);
4516
- }
4517
- static isRecommendationQuery(query) {
4518
- return /\b(recommend|suggest|what should i (buy|get|choose)|best options|top picks)\b/i.test(query);
4519
- }
4520
- static isTrendQuery(query) {
4521
- return /\b(trend|trends|growth|over time|historical|timeline|monthly|yearly|weekly|daily|revenue growth|sales growth)\b/i.test(query);
4522
- }
4523
- static isRankingQuery(query) {
4524
- return /\b(highest|lowest|top\s*\d+|bottom\s*\d+|rank|ranking|top performing|best performing)\b/i.test(query);
4525
- }
4526
- static isAnalyticsQuery(query) {
4527
- return /\b(sales|revenue|analytics|insights|distribution|percentage|share|breakdown|breakup|scatter|correlation|metrics|performance|by region|by category)\b/i.test(query);
4528
- }
4529
- };
4530
-
4531
- // src/rendering/rules/Rule1SpecificInfoRule.ts
4532
- var Rule1SpecificInfoRule = class {
4533
- constructor() {
4534
- this.name = "Rule1SpecificInfo";
4535
- this.priority = 100;
4536
- }
4537
- evaluate(context, intent) {
4538
- const q = (context.userQuery || "").toLowerCase().trim();
4539
- const isFactQuery = intent === "information_lookup" || /^(what|who|explain|explore|define|where|when|why|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
4540
- const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
4541
- if (isFactQuery && isNonVisual) {
4542
- return {
4543
- renderType: "text",
4544
- confidence: 0.95,
4545
- reason: "Rule 1: Specific information request (fact, explanation, or definition) requires plain text rendering."
4546
- };
4547
- }
4548
- return null;
4549
- }
4550
- };
4551
-
4552
- // src/rendering/rules/Rule2ComparisonRule.ts
4553
- var Rule2ComparisonRule = class {
4554
- constructor() {
4555
- this.name = "Rule2Comparison";
4556
- this.priority = 90;
4557
- }
4558
- evaluate(context, intent) {
4559
- const q = (context.userQuery || "").toLowerCase().trim();
4560
- const isComparison = intent === "comparison" || /\b(compare|comparison|vs\.?|versus|difference between|differ|contrast|against)\b/i.test(q);
4561
- if (isComparison) {
4562
- return {
4563
- renderType: "table",
4564
- confidence: 0.9,
4565
- reason: "Rule 2: Comparison query contrasting multiple entities requires structured table rendering."
4566
- };
4567
- }
4568
- return null;
4569
- }
4570
- };
4571
-
4572
- // src/rendering/rules/Rule3ProductDiscoveryRule.ts
4573
- var Rule3ProductDiscoveryRule = class {
4574
- constructor() {
4575
- this.name = "Rule3ProductDiscovery";
4576
- this.priority = 85;
4577
- }
4578
- evaluate(context, intent) {
4579
- const q = (context.userQuery || "").toLowerCase().trim();
4580
- 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);
4581
- const isNotChart = !/\b(chart|sales|revenue|monthly|growth|trend|over time)\b/i.test(q);
4582
- const isAnalyticalRanking = /\b(top performing|top selling|best performing|performing|sales|revenue|ranking)\b/i.test(q);
4583
- if (isDiscovery && isNotChart && !isAnalyticalRanking) {
4584
- return {
4585
- renderType: "carousel",
4586
- confidence: 0.9,
4587
- reason: "Rule 3: Product discovery/recommendation query requires product carousel rendering."
4588
- };
4589
- }
4590
- return null;
4591
- }
4592
- };
4593
-
4594
- // src/rendering/rules/Rule4AnalyticalRule.ts
4595
- var Rule4AnalyticalRule = class {
4596
- constructor() {
4597
- this.name = "Rule4Analytical";
4598
- this.priority = 80;
4599
- }
4600
- evaluate(context, intent) {
4601
- const q = (context.userQuery || "").toLowerCase().trim();
4602
- 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);
4603
- if (!isAnalytical) return null;
4604
- let chartType = "bar";
4605
- if (intent === "trend_analysis" || /\b(monthly|yearly|weekly|growth|over time|trend|timeline|historical)\b/i.test(q)) {
4606
- chartType = "line";
4607
- } else if (/\b(share|percentage|percent|breakup|breakdown|composition|pie|donut|proportion)\b/i.test(q)) {
4608
- chartType = "pie";
4609
- } else if (/\b(correlation|scatter|relationship|relation|impact of|depends on)\b/i.test(q)) {
4610
- chartType = "scatter";
4611
- } else {
4612
- chartType = "bar";
4613
- }
4614
- return {
4615
- renderType: "chart",
4616
- chartType,
4617
- confidence: 0.85,
4618
- reason: `Rule 4: Analytical query with numerical insights selects ${chartType} chart.`
4619
- };
4620
- }
4621
- };
4622
-
4623
- // src/rendering/rules/Rule5MixedResponseRule.ts
4624
- var Rule5MixedResponseRule = class {
4625
- constructor() {
4626
- this.name = "Rule5MixedResponse";
4627
- this.priority = 95;
4628
- }
4629
- evaluate(context, _intent) {
4630
- const q = (context.userQuery || "").toLowerCase().trim();
4631
- const isTopSellingThisMonth = /\btop\s*(selling|performing)?\s*products?\s*(this|last)?\s*(month|year|week)\b/i.test(q);
4632
- 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);
4633
- if (isTopSellingThisMonth || hasMultipleSectionsRequest) {
4634
- return {
4635
- renderType: "mixed",
4636
- confidence: 0.95,
4637
- reason: "Rule 5: Mixed response query requires multi-section composition (text, carousel, chart).",
4638
- sections: [
4639
- { type: "text", title: "Summary" },
4640
- { type: "carousel", title: "Top Products" },
4641
- { type: "chart", chartType: "bar", title: "Sales Performance" }
4642
- ]
4643
- };
4644
- }
4645
- return null;
4646
- }
4647
- };
4648
-
4649
- // src/rendering/rules/Rule6SmallResultSetRule.ts
4650
- var Rule6SmallResultSetRule = class {
4651
- constructor() {
4652
- this.name = "Rule6SmallResultSet";
4653
- this.priority = 110;
4654
- }
4655
- evaluate(context, _intent) {
4656
- const docs = context.retrievedDocuments || [];
4657
- if (docs.length > 0 && docs.length < 3) {
4658
- const q = (context.userQuery || "").toLowerCase().trim();
4659
- const isExplicitComparison = /\b(compare|versus|vs\.?)\b/i.test(q);
4660
- return {
4661
- renderType: isExplicitComparison ? "table" : "text",
4662
- confidence: 0.98,
4663
- reason: `Rule 6: Small result set (${docs.length} rows < 3). Charts avoided to prevent sparse or uninformative visualizations.`
4664
- };
4665
- }
4666
- return null;
4667
- }
4668
- };
4669
-
4670
- // src/rendering/rules/Rule7LargeTableRule.ts
4671
- var Rule7LargeTableRule = class {
4672
- constructor() {
4673
- this.name = "Rule7LargeTable";
4674
- this.priority = 75;
4675
- }
4676
- evaluate(context, intent) {
4677
- const docs = context.retrievedDocuments || [];
4678
- if (docs.length > 5) {
4679
- const q = (context.userQuery || "").toLowerCase().trim();
4680
- const hasNumericAgg = intent === "analytics" || intent === "ranking" || /\b(sales|revenue|growth|count|total|average)\b/i.test(q);
4681
- if (hasNumericAgg) {
4682
- return {
4683
- renderType: "mixed",
4684
- confidence: 0.88,
4685
- reason: `Rule 7: Large result set (${docs.length} rows > 5) with numeric aggregation rendered as Table + Chart.`,
4686
- sections: [
4687
- { type: "table", title: "Detailed Data Table" },
4688
- { type: "chart", chartType: "bar", title: "Numerical Breakdown" }
4689
- ]
4690
- };
4691
- }
4692
- return {
4693
- renderType: "table",
4694
- confidence: 0.85,
4695
- reason: `Rule 7: Large result set (${docs.length} rows > 5) rendered as structured table for readability.`
4696
- };
4697
- }
4698
- return null;
4699
- }
4700
- };
4701
-
4702
- // src/rendering/RuleEngine.ts
4703
- var RuleEngine = class {
4704
- constructor() {
4705
- this.rules = [];
4706
- this.registerDefaultRules();
4707
- }
4708
- /**
4709
- * Register standard rules in priority order.
4710
- */
4711
- registerDefaultRules() {
4712
- this.registerRule(new Rule6SmallResultSetRule());
4713
- this.registerRule(new Rule1SpecificInfoRule());
4714
- this.registerRule(new Rule5MixedResponseRule());
4715
- this.registerRule(new Rule2ComparisonRule());
4716
- this.registerRule(new Rule3ProductDiscoveryRule());
4717
- this.registerRule(new Rule4AnalyticalRule());
4718
- this.registerRule(new Rule7LargeTableRule());
4719
- }
4720
- /**
4721
- * Add or replace a rule in the engine.
4722
- * Automatically keeps rules sorted by descending priority.
4723
- */
4724
- registerRule(rule) {
4725
- this.rules = this.rules.filter((r) => r.name !== rule.name);
4726
- this.rules.push(rule);
4727
- this.rules.sort((a, b) => b.priority - a.priority);
4728
- }
4729
- /**
4730
- * Evaluate context against rules in descending priority order.
4731
- * Returns the first non-null RenderDecision.
4732
- */
4733
- evaluate(context, intent) {
4734
- for (const rule of this.rules) {
4735
- const decision = rule.evaluate(context, intent);
4736
- if (decision) {
4737
- return decision;
4738
- }
4739
- }
4740
- return {
4741
- renderType: "text",
4742
- confidence: 0.7,
4743
- reason: "Default fallback: No specific visualization rule matched context."
4744
- };
4745
- }
4746
- /**
4747
- * List all registered rules.
4748
- */
4749
- getRegisteredRules() {
4750
- return this.rules.map((r) => ({ name: r.name, priority: r.priority }));
4751
- }
4752
- };
4753
-
4754
- // src/rendering/VisualizationDecisionEngine.ts
4755
- var LRUDecisionCache = class {
4756
- constructor(maxSize = 200) {
4757
- this.cache = /* @__PURE__ */ new Map();
4758
- this.maxSize = maxSize;
4759
- }
4760
- generateKey(context) {
4761
- var _a, _b;
4762
- const q = (context.userQuery || "").toLowerCase().trim();
4763
- const docCount = (_b = (_a = context.retrievedDocuments) == null ? void 0 : _a.length) != null ? _b : 0;
4764
- return `${q}::docs:${docCount}`;
4765
- }
4766
- get(context) {
4767
- const key = this.generateKey(context);
4768
- const item = this.cache.get(key);
4769
- if (item) {
4770
- this.cache.delete(key);
4771
- this.cache.set(key, item);
4772
- return item.decision;
4773
- }
4774
- return void 0;
4775
- }
4776
- set(context, decision) {
4777
- const key = this.generateKey(context);
4778
- if (this.cache.has(key)) {
4779
- this.cache.delete(key);
4780
- } else if (this.cache.size >= this.maxSize) {
4781
- const oldest = this.cache.keys().next().value;
4782
- if (oldest !== void 0) this.cache.delete(oldest);
4783
- }
4784
- this.cache.set(key, { decision, timestamp: Date.now() });
4785
- }
4786
- clear() {
4787
- this.cache.clear();
4788
- }
4789
- get size() {
4790
- return this.cache.size;
4791
- }
4792
- };
4793
- var VisualizationDecisionEngine = class {
4794
- /**
4795
- * Evaluate user query, documents, and context to produce a RenderDecision.
4796
- * Execution time is < 1ms for cached or rule-matched queries.
4797
- */
4798
- static decideVisualization(userQuery, retrievedDocuments, llmResponse, metadata) {
4799
- const context = {
4800
- userQuery,
4801
- retrievedDocuments,
4802
- llmResponse,
4803
- metadata
4804
- };
4805
- const cached = this.cache.get(context);
4806
- if (cached) {
4807
- return cached;
4808
- }
4809
- const { intent } = IntentClassifier.classify(context);
4810
- const decision = this.ruleEngine.evaluate(context, intent);
4811
- this.cache.set(context, decision);
4812
- return decision;
4813
- }
4814
- /**
4815
- * Convenience helper to decide AND render the data into a UITransformationResponse.
4816
- */
4817
- static render(userQuery, retrievedDocuments, llmResponse, metadata) {
4818
- const decision = this.decideVisualization(userQuery, retrievedDocuments, llmResponse, metadata);
4819
- const strategy = RendererRegistry.getStrategy(decision.renderType);
4820
- return strategy.render(retrievedDocuments || [], __spreadValues({
4821
- query: userQuery,
4822
- chartType: decision.chartType,
4823
- sections: decision.sections
4824
- }, metadata));
4825
- }
4826
- /**
4827
- * Register a custom rule in the rule engine.
4828
- */
4829
- static registerRule(rule) {
4830
- this.ruleEngine.registerRule(rule);
4831
- this.cache.clear();
4832
- }
4833
- /**
4834
- * Register a custom renderer strategy.
4835
- */
4836
- static registerRendererStrategy(strategy) {
4837
- RendererRegistry.registerStrategy(strategy);
4838
- }
4839
- /**
4840
- * Clear the decision cache.
4841
- */
4842
- static clearCache() {
4843
- this.cache.clear();
4844
- }
4845
- /**
4846
- * Get size of current decision cache.
4847
- */
4848
- static getCacheSize() {
4849
- return this.cache.size;
4850
- }
4851
- };
4852
- VisualizationDecisionEngine.ruleEngine = new RuleEngine();
4853
- VisualizationDecisionEngine.cache = new LRUDecisionCache(200);
4854
- function decideVisualization(userQuery, retrievedDocuments, llmResponse, metadata) {
4855
- return VisualizationDecisionEngine.decideVisualization(
4856
- userQuery,
4857
- retrievedDocuments,
4858
- llmResponse,
4859
- metadata
4860
- );
4861
- }
4862
-
4863
- // src/utils/UITransformer.ts
4864
- var UITransformer = class _UITransformer {
4865
- // ─── Public Entry Points ─────────────────────────────────────────────────
4866
- /**
4867
- * Heuristic-only transform (no LLM required).
4868
- * Uses the lightweight heuristic intent detector as a fallback.
4869
- * Prefer `analyzeAndDecide()` in production.
4870
- */
4871
- static transform(userQuery, retrievedData, config, trainedSchema, intent) {
4872
- var _a, _b, _c;
4873
- if (!retrievedData || retrievedData.length === 0) {
4874
- return this.createTextResponse("No data available", "No relevant data found for your query.");
4875
- }
4876
- const resolvedIntent = intent != null ? intent : this.detectIntentHeuristic(userQuery);
4877
- const filteredData = resolvedIntent.filterInStockOnly ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
4878
- const profile = this.profileData(filteredData);
4879
- const hasProducts = filteredData.some((item) => this.isProductData(item));
4880
- const wantsPieLikeChart = ["pie_chart", "donut_chart"].includes(resolvedIntent.recommendedChart);
4881
- if (resolvedIntent.visualizationHint === "trend" && profile.dateFields.length > 0 && profile.numericFields.length > 0) {
4882
- return this.transformToLineChart(profile);
4883
- }
4884
- if (wantsPieLikeChart || ["composition", "category_breakdown"].includes(resolvedIntent.visualizationHint)) {
4885
- const pieChart = this.transformToPieChart(filteredData, profile, userQuery);
4886
- if (pieChart) return pieChart;
4887
- }
4888
- if (["comparison", "ranking"].includes(resolvedIntent.visualizationHint)) {
4889
- return this.transformToBarChart(filteredData, profile, userQuery, resolvedIntent.visualizationHint === "ranking");
4890
- }
4891
- if (resolvedIntent.visualizationHint === "distribution") {
4892
- return (_a = this.transformToHistogram(profile, userQuery)) != null ? _a : this.transformToBarChart(filteredData, profile, userQuery);
4893
- }
4894
- if (resolvedIntent.visualizationHint === "correlation") {
4895
- return (_b = this.transformToScatterPlot(profile, userQuery)) != null ? _b : this.transformToBarChart(filteredData, profile, userQuery);
4896
- }
4897
- if (resolvedIntent.visualizationHint === "geographic") {
4898
- return this.transformToBarChart(filteredData, profile, userQuery);
4899
- }
4900
- if (this.isStructuredListQuery(userQuery)) {
4901
- return this.hasMultipleFields(filteredData) ? this.transformToTable(filteredData, userQuery) : this.transformToText(filteredData);
4902
- }
4903
- if (resolvedIntent.visualizationHint === "kpi") {
4904
- return (_c = this.transformToMetricCard(profile, userQuery)) != null ? _c : this.transformToText(filteredData);
4905
- }
4906
- if (["tabular", "table"].includes(resolvedIntent.visualizationHint) || resolvedIntent.wantsExplicitTable) {
4907
- return this.hasMultipleFields(filteredData) ? this.transformToTable(filteredData, userQuery) : this.transformToText(filteredData);
4908
- }
4909
- if ((hasProducts || this.isProductQuery(userQuery)) && resolvedIntent.visualizationHint === "product_browse") {
4910
- if (!this.isProductQuery(userQuery)) {
4911
- return this.transformToText(filteredData);
4912
- }
4913
- return this.transformToProductCarousel(filteredData, config, trainedSchema);
4914
- }
4915
- const automatic = this.chooseAutomaticVisualization(filteredData, profile, userQuery);
4916
- if (automatic) return automatic;
4917
- return this.transformToText(filteredData);
4918
- }
4919
- /**
4920
- * LLM-driven entry point (recommended for production).
4921
- *
4922
- * Step 1 — Detect intent via a dedicated, lightweight LLM call.
4923
- * Step 2 — Pass the intent + data to the visualization-selection prompt.
4924
- * Step 3 — Fall back to the heuristic `transform()` if either LLM call fails.
4925
- */
4926
- static async analyzeAndDecide(query, sources, llm) {
4927
- const decision = VisualizationDecisionEngine.decideVisualization(query, sources);
4928
- if (decision.renderType === "text") {
4929
- console.debug("[UITransformer] Decision Engine selected plain text rendering \u2014 skipping visual LLM calls.");
4930
- return this.transformToText(sources);
4931
- }
4932
- let intent;
4933
- try {
4934
- intent = await this.detectIntent(query, llm, sources);
4935
- console.debug("[UITransformer] Detected intent:", intent);
4936
- } catch (err) {
4937
- console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
4938
- intent = this.detectIntentHeuristic(query);
4939
- }
4940
- if (this.isProductQuery(query) && ["text", "product_browse"].includes(intent.visualizationHint)) {
4941
- return this.transform(
4942
- query,
4943
- sources,
4944
- void 0,
4945
- void 0,
4946
- __spreadProps(__spreadValues({}, intent), { visualizationHint: "product_browse", recommendedChart: "text" })
4947
- );
4948
- }
4949
- try {
4950
- const context = this.buildContextSummary(sources);
4951
- const systemPrompt = this.buildVisualizationSystemPrompt();
4952
- const profile = this.profileData(sources);
4953
- const schemaContext = `
4954
- RETRIEVED DATA SCHEMA:
4955
- - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4956
- - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4957
- - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4958
- - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4959
- - Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
4960
- `;
4961
- const userPrompt = [
4962
- `USER QUESTION: ${query}`,
4963
- "",
4964
- `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4965
- "",
4966
- `RETRIEVED DATA SCHEMA:`,
4967
- schemaContext,
4968
- "",
4969
- "RETRIEVED DATA (JSON):",
4970
- context
4971
- ].join("\n");
4972
- const rawResponse = await llm.chat(
4973
- [{ role: "user", content: userPrompt }],
4974
- "",
4975
- { systemPrompt, temperature: 0 }
4976
- );
4977
- const parsed = this.parseTransformationResponse(rawResponse);
4978
- if (parsed) {
4979
- let isCompatible = true;
4980
- if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
4981
- isCompatible = false;
4982
- } else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
4983
- isCompatible = false;
4984
- } else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
4985
- isCompatible = false;
4986
- } else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
4987
- isCompatible = false;
4988
- }
4989
- if (!isCompatible) {
4990
- console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
4991
- return this.transform(query, sources, void 0, void 0, intent);
4992
- }
4993
- const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4994
- const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4995
- if (parsed.type === "table" && !intentAllowsTable) {
4996
- console.debug("[UITransformer] LLM chose table but intent says no. Falling back to text.");
4997
- return this.transform(query, sources, void 0, void 0, intent);
4998
- }
4999
- if (intentWantsPieLikeChart && parsed.type !== "pie_chart" && this.detectCategories(sources).length > 1) {
5000
- console.debug("[UITransformer] LLM ignored pie/composition intent. Using deterministic pie chart.");
5001
- return this.transform(query, sources, void 0, void 0, intent);
5002
- }
5003
- console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
5004
- return parsed;
5005
- }
5006
- console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
5007
- } catch (err) {
5008
- console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
5009
- }
5010
- return this.transform(query, sources, void 0, void 0, intent);
5011
- }
5012
- // ─── Dynamic Intent Detection ─────────────────────────────────────────────
5013
- /**
5014
- * Calls the LLM with a compact, focused prompt to extract a structured
5015
- * `QueryIntent` from the user's query.
5016
- *
5017
- * Keeping this as a *separate* call from visualization selection means:
5018
- * - The prompt is shorter and more reliable.
5019
- * - The intent object can be reused across both the heuristic and LLM paths.
5020
- * - It is easy to unit-test intent detection in isolation.
5021
- */
5022
- static async detectIntent(query, llm, sources) {
5023
- let schemaProfileText = "";
5024
- if (sources && sources.length > 0) {
5025
- const profile = this.profileData(sources);
5026
- const hasProducts = sources.some((item) => this.isProductData(item));
5027
- schemaProfileText = `
5028
- RETRIEVED DATA SCHEMA:
5029
- - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5030
- - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5031
- - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5032
- - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5033
- - Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
5034
-
5035
- Please choose visualizationHint and recommendedChart dynamically based on the available schema.
5036
- - Do NOT recommend "trend" or "line_chart" if there are no Date fields.
5037
- - Do NOT recommend numeric-dependent visualizations (like bar_chart, line_chart, pie_chart, scatter_plot, histogram, metric_card) if there are no Numeric fields. Fall back to tabular/table or text.
5038
- - If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
5039
- ${hasProducts ? `- Note: The retrieved data matches product/catalog structure. If the user query is looking for, exploring, searching, browsing, or listing products/items, you MUST recommend visualizationHint: "product_browse" and recommendedChart: "text".` : ""}
5040
- `;
5041
- }
5042
- const systemPrompt = `You are an intent classifier for a product-search RAG system.
5043
- Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
5044
-
5045
- {
5046
- "visualizationHint": "trend" | "comparison" | "distribution" | "composition" | "correlation" | "ranking" | "kpi" | "tabular" | "geographic" | "product_browse" | "table" | "text",
5047
- "recommendedChart": "line_chart" | "bar_chart" | "histogram" | "pie_chart" | "donut_chart" | "scatter_plot" | "horizontal_bar" | "metric_card" | "table" | "geo_map" | "text",
5048
- "filterInStockOnly": boolean,
5049
- "wantsExplicitTable": boolean,
5050
- "isTemporal": boolean,
5051
- "isComparison": boolean,
5052
- "language": "<BCP-47 language tag, e.g. en, de, hi, ja>",
5053
- "reasoning": "<one short sentence \u2014 why you chose these values>"
5054
- }
5055
-
5056
- RULES:
5057
- - visualizationHint and recommendedChart
5058
- "trend" \u2192 keywords: trend, growth, over time; recommendedChart "line_chart"
5059
- "comparison" \u2192 keywords: compare, versus, top; recommendedChart "bar_chart"
5060
- "distribution" \u2192 keywords: distribution, spread; recommendedChart "histogram"
5061
- "composition" \u2192 keywords: share, percentage, breakup, breakdown; recommendedChart "pie_chart" or "donut_chart"
5062
- "correlation" \u2192 keywords: relation, correlation; recommendedChart "scatter_plot"
5063
- "ranking" \u2192 keywords: highest, lowest, top 10, ranking; recommendedChart "horizontal_bar"
5064
- "kpi" \u2192 keywords: total, average, count; recommendedChart "metric_card"
5065
- "tabular" \u2192 keywords: detailed records, table, grid, spreadsheet; recommendedChart "table"
5066
- "geographic" \u2192 keywords: region, country, map; recommendedChart "geo_map"
5067
- "product_browse" \u2192 user is browsing, searching, describing, viewing details for, or asking about one or more products without analytical visualization intent
5068
- "table" \u2192 legacy alias for "tabular"; use only if the query literally says table
5069
- "text" \u2192 conversational, factual, or other intent
5070
- - If the user explicitly asks for a chart type, honor that chart type in recommendedChart.
5071
- - If a query says "distribution ... in a pie chart", use visualizationHint "composition" and recommendedChart "pie_chart".
5072
- - filterInStockOnly: true only if user mentions stock, availability, in stock, etc.
5073
- - wantsExplicitTable: true if the user asks for a table, list, grid, spreadsheet, or detailed records.
5074
- - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
5075
- - isComparison: true if user compares, ranks, or contrasts entities or categories.
5076
- - language: detect from the query text itself; default "en" if uncertain.`;
5077
- const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5078
-
5079
- ${schemaProfileText}` : ""}`;
5080
- const rawResponse = await llm.chat(
5081
- [{ role: "user", content: userPrompt }],
5082
- "",
5083
- { systemPrompt, temperature: 0 }
5084
- );
5085
- const parsed = this.parseIntentResponse(rawResponse);
5086
- if (!parsed) {
5087
- throw new Error(`Could not parse intent JSON from LLM response: ${rawResponse}`);
5088
- }
5089
- return parsed;
5090
- }
5091
- /**
5092
- * Parse and validate the raw LLM response into a `QueryIntent`.
5093
- */
5094
- static parseIntentResponse(raw) {
5095
- const jsonStr = this.extractJsonCandidate(raw);
5096
- if (!jsonStr) return null;
5097
- try {
5098
- const obj = JSON.parse(jsonStr);
5099
- const validHints = [
5100
- "trend",
5101
- "comparison",
5102
- "distribution",
5103
- "composition",
5104
- "correlation",
5105
- "ranking",
5106
- "kpi",
5107
- "tabular",
5108
- "geographic",
5109
- "category_breakdown",
5110
- "product_browse",
5111
- "table",
5112
- "text"
5113
- ];
5114
- const validCharts = [
5115
- "line_chart",
5116
- "bar_chart",
5117
- "histogram",
5118
- "pie_chart",
5119
- "donut_chart",
5120
- "scatter_plot",
5121
- "horizontal_bar",
5122
- "metric_card",
5123
- "table",
5124
- "geo_map",
5125
- "text"
5126
- ];
5127
- const hint = obj.visualizationHint;
5128
- if (!hint || !validHints.includes(hint)) return null;
5129
- const normalizedHint = hint === "category_breakdown" ? "composition" : hint;
5130
- const recommendedChart = typeof obj.recommendedChart === "string" && validCharts.includes(obj.recommendedChart) ? obj.recommendedChart : this.getRecommendedChartForIntent(normalizedHint);
5131
- return {
5132
- visualizationHint: normalizedHint,
5133
- recommendedChart,
5134
- filterInStockOnly: Boolean(obj.filterInStockOnly),
5135
- wantsExplicitTable: Boolean(obj.wantsExplicitTable),
5136
- isTemporal: Boolean(obj.isTemporal),
5137
- isComparison: Boolean(obj.isComparison),
5138
- language: typeof obj.language === "string" && obj.language ? obj.language : "en",
5139
- reasoning: typeof obj.reasoning === "string" ? obj.reasoning : void 0
5140
- };
5141
- } catch (e) {
5142
- return null;
5143
- }
5144
- }
5145
- /**
5146
- * Heuristic intent detector — used when the LLM is unavailable.
5147
- * Intentionally minimal: it should only catch the most obvious signals.
5148
- * The LLM path handles everything subtle.
5149
- */
5150
- static detectIntentHeuristic(query) {
5151
- const q = query.toLowerCase();
5152
- const isTemporal = /\b(trend|trends|over time|historical|history|growth|decline|monthly|yearly|weekly|daily|last (year|month|week)|timeline|forecast)\b/.test(q);
5153
- const isRanking = /\b(highest|lowest|top\s*\d+|bottom\s*\d+|rank(?:ing)?|best|worst|leading)\b/.test(q);
5154
- const isComparison = /\b(compare|comparison|vs\.?|versus|against|differ|difference|contrast|top)\b/.test(q) || isRanking;
5155
- const isDistribution = /\b(distribution|spread|histogram|frequency|variance|range)\b/.test(q);
5156
- const wantsPieLikeChart = /\b(pie|donut|doughnut)(?:\s+chart)?\b/.test(q);
5157
- const isComposition = wantsPieLikeChart || /\b(share|percentage|percent|breakup|breakdown|composition|split|segmentation|by category|by type|proportion)\b/.test(q);
5158
- const isCorrelation = /\b(relation|relationship|correlation|correlate|scatter|association|impact of|depend(?:s|ence)? on)\b/.test(q);
5159
- const isKpi = /\b(total|average|avg|count|sum|median|minimum|maximum|metric|kpi|how many|number of)\b/.test(q);
5160
- const isGeographic = /\b(region|country|countries|state|city|location|map|geo|geographic|territory)\b/.test(q);
5161
- const filterInStockOnly = /\b(in[- ]?stock|available|availability|inventory|stock status)\b/.test(q);
5162
- const wantsExplicitTable = /\b(table|spreadsheet|grid|detailed records|record details|list all|compare all)\b/.test(q);
5163
- let visualizationHint = "text";
5164
- if (wantsExplicitTable) visualizationHint = "tabular";
5165
- else if (isTemporal) visualizationHint = "trend";
5166
- else if (wantsPieLikeChart) visualizationHint = "composition";
5167
- else if (isRanking) visualizationHint = "ranking";
5168
- else if (isComparison) visualizationHint = "comparison";
5169
- else if (isDistribution) visualizationHint = "distribution";
5170
- else if (isComposition) visualizationHint = "composition";
5171
- else if (isCorrelation) visualizationHint = "correlation";
5172
- else if (isGeographic) visualizationHint = "geographic";
5173
- else if (isKpi) visualizationHint = "kpi";
5174
- else if (this.isProductQuery(query)) visualizationHint = "product_browse";
5175
- return {
5176
- visualizationHint,
5177
- recommendedChart: this.getRecommendedChartForIntent(visualizationHint),
5178
- filterInStockOnly,
5179
- wantsExplicitTable,
5180
- isTemporal,
5181
- isComparison,
5182
- language: "en"
5183
- // heuristic cannot reliably detect language
5184
- };
5185
- }
5186
- static getRecommendedChartForIntent(intent) {
5187
- switch (intent) {
5188
- case "trend":
5189
- return "line_chart";
5190
- case "comparison":
5191
- return "bar_chart";
5192
- case "distribution":
5193
- return "histogram";
5194
- case "composition":
5195
- case "category_breakdown":
5196
- return "pie_chart";
5197
- case "correlation":
5198
- return "scatter_plot";
5199
- case "ranking":
5200
- return "horizontal_bar";
5201
- case "kpi":
5202
- return "metric_card";
5203
- case "tabular":
5204
- case "table":
5205
- return "table";
5206
- case "geographic":
5207
- return "geo_map";
5208
- case "product_browse":
5209
- case "text":
5210
- default:
5211
- return "text";
5212
- }
5213
- }
5214
- // ─── Transform Helpers ────────────────────────────────────────────────────
5215
- static transformToProductCarousel(data, config, trainedSchema) {
5216
- const products = data.map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
5217
- return {
5218
- type: "product_carousel",
5219
- title: "Recommended Products",
5220
- description: `Found ${products.length} relevant products`,
5221
- data: products
5222
- };
5223
- }
5224
- static transformToPieChart(data, profile, query = "") {
5225
- var _a;
5226
- const dimension = profile ? this.selectDimensionField(profile, query) : void 0;
5227
- const categories = dimension ? Array.from(new Set(profile.records.map((record) => {
5228
- var _a2;
5229
- return String((_a2 = record.fields[dimension.key]) != null ? _a2 : "");
5230
- }).filter(Boolean))) : this.detectCategories(data);
5231
- if (categories.length === 0) return null;
5232
- const categoryData = dimension && profile ? this.aggregateProfileByDimension(profile, dimension.key) : this.aggregateByCategory(data, categories);
5233
- const pieData = Object.entries(categoryData).map(([label, count]) => {
5234
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
5235
- return { label, value: count, inStockCount, outOfStockCount };
5236
- });
5237
- return {
5238
- type: "pie_chart",
5239
- title: `Distribution by ${(_a = dimension == null ? void 0 : dimension.label) != null ? _a : "Category"}`,
5240
- description: `Showing breakdown across ${pieData.length} categories`,
5241
- data: pieData
5242
- };
5243
- }
5244
- static transformToLineChart(profile) {
5245
- const dateField = profile.dateFields[0];
5246
- const valueField = profile.numericFields[0];
5247
- const buckets = /* @__PURE__ */ new Map();
5248
- profile.records.forEach((record) => {
5249
- var _a, _b, _c;
5250
- const timestamp = String((_a = record.fields[dateField.key]) != null ? _a : "");
5251
- const value = (_b = this.toFiniteNumber(record.fields[valueField.key])) != null ? _b : 0;
5252
- buckets.set(timestamp, ((_c = buckets.get(timestamp)) != null ? _c : 0) + value);
5253
- });
5254
- const lineData = Array.from(buckets.entries()).sort(([a], [b]) => Date.parse(a) - Date.parse(b)).slice(0, 24).map(([timestamp, value]) => ({ timestamp, value, label: timestamp }));
5255
- return {
5256
- type: "line_chart",
5257
- title: `${valueField.label} Over Time`,
5258
- description: `Showing ${lineData.length} data points`,
5259
- data: lineData
5260
- };
5261
- }
5262
- static transformToBarChart(data, profile, query = "", horizontal = false) {
5263
- var _a;
5264
- const dimension = profile ? this.selectDimensionField(profile, query) : void 0;
5265
- const measure = profile ? this.selectNumericField(profile, query) : void 0;
5266
- const aggregate = dimension && profile ? this.aggregateProfileByDimension(profile, dimension.key, measure == null ? void 0 : measure.key) : this.aggregateByCategory(data, this.detectCategories(data));
5267
- const barData = Object.entries(aggregate).map(([category, value]) => ({ category, value: Number(value) })).sort((a, b) => horizontal ? b.value - a.value : 0).slice(0, 12);
5268
- const fallbackData = barData.length > 0 ? barData : data.slice(0, 12).map((item, index) => {
5269
- var _a2, _b, _c, _d, _e;
5270
- const meta = item.metadata || {};
5271
- const label = String(
5272
- (_c = (_b = (_a2 = this.getDynamicVal(meta, "name")) != null ? _a2 : meta.label) != null ? _b : meta.title) != null ? _c : `Result ${index + 1}`
5273
- );
5274
- const value = (_e = (_d = this.extractNumericValue(meta)) != null ? _d : item.score) != null ? _e : 0;
5275
- return { category: label, value: Number(value) };
5276
- });
5277
- return {
5278
- type: horizontal ? "horizontal_bar" : "bar_chart",
5279
- title: dimension ? `${(_a = measure == null ? void 0 : measure.label) != null ? _a : "Count"} by ${dimension.label}` : "Comparison",
5280
- description: `Showing ${fallbackData.length} comparable values`,
5281
- data: fallbackData
5282
- };
5283
- }
5284
- static transformToHistogram(profile, query = "") {
5285
- const field = this.selectNumericField(profile, query);
5286
- if (!field) return null;
5287
- const values = profile.records.map((record) => this.toFiniteNumber(record.fields[field.key])).filter((value) => value !== null).sort((a, b) => a - b);
5288
- if (values.length === 0) return null;
5289
- const min = values[0];
5290
- const max = values[values.length - 1];
5291
- const bucketCount = Math.min(10, Math.max(3, Math.ceil(Math.sqrt(values.length))));
5292
- const width = max === min ? 1 : (max - min) / bucketCount;
5293
- const buckets = Array.from({ length: bucketCount }, (_, index) => {
5294
- const start = min + index * width;
5295
- const end = index === bucketCount - 1 ? max : start + width;
5296
- return { category: `${this.formatNumber(start)}-${this.formatNumber(end)}`, value: 0 };
5297
- });
5298
- values.forEach((value) => {
5299
- const bucketIndex = max === min ? 0 : Math.min(bucketCount - 1, Math.floor((value - min) / width));
5300
- buckets[bucketIndex].value += 1;
5301
- });
5302
- return {
5303
- type: "histogram",
5304
- title: `${field.label} Distribution`,
5305
- description: `Showing ${values.length} values across ${bucketCount} buckets`,
5306
- data: buckets
5307
- };
5308
- }
5309
- static transformToScatterPlot(profile, query = "") {
5310
- const fields = this.rankFieldsByQuery(profile.numericFields, query).slice(0, 2);
5311
- if (fields.length < 2) return null;
5312
- const [xField, yField] = fields;
5313
- const points = profile.records.map((record) => {
5314
- var _a;
5315
- const x = this.toFiniteNumber(record.fields[xField.key]);
5316
- const y = this.toFiniteNumber(record.fields[yField.key]);
5317
- if (x === null || y === null) return null;
5318
- return { x, y, label: String((_a = this.getRecordLabel(record)) != null ? _a : record.id) };
5319
- }).filter((point) => point !== null).slice(0, 100);
5320
- if (points.length === 0) return null;
5321
- return {
5322
- type: "scatter_plot",
5323
- title: `${xField.label} vs ${yField.label}`,
5324
- description: `Showing ${points.length} paired values`,
5325
- data: points
5326
- };
5327
- }
5328
- static transformToMetricCard(profile, query = "") {
5329
- const operation = this.detectAggregationOperation(query);
5330
- const numericField = this.selectNumericField(profile, query);
5331
- const values = numericField ? profile.records.map((record) => this.toFiniteNumber(record.fields[numericField.key])).filter((value2) => value2 !== null) : [];
5332
- const value = operation === "count" || values.length === 0 ? profile.records.length : this.calculateAggregate(values, operation);
5333
- const metric = {
5334
- label: numericField ? `${operation} ${numericField.label}` : "Count",
5335
- value,
5336
- operation
5337
- };
5338
- return {
5339
- type: "metric_card",
5340
- title: metric.label,
5341
- description: `Calculated from ${profile.records.length} result${profile.records.length === 1 ? "" : "s"}`,
5342
- data: metric
5343
- };
5344
- }
5345
- static transformToRadarChart(data) {
5346
- const attributeMap = {};
5347
- data.forEach((item) => {
5348
- var _a, _b, _c;
5349
- const meta = item.metadata || {};
5350
- const seriesName = String((_c = (_b = (_a = meta.name) != null ? _a : meta.product) != null ? _b : item.id) != null ? _c : "Item");
5351
- Object.entries(meta).forEach(([key, val]) => {
5352
- if (typeof val === "number" && !["price", "id"].includes(key.toLowerCase())) {
5353
- if (!attributeMap[key]) attributeMap[key] = {};
5354
- attributeMap[key][seriesName] = val;
5355
- }
5356
- });
5357
- });
5358
- const radarData = Object.entries(attributeMap).map(([attribute, series]) => __spreadValues({
5359
- attribute
5360
- }, series));
5361
- return {
5362
- type: "radar_chart",
5363
- title: "Product Comparison",
5364
- description: `Comparing ${data.length} items across ${radarData.length} attributes`,
5365
- data: radarData.length > 0 ? radarData : data.map((d) => {
5366
- var _a;
5367
- return { attribute: ((_a = d == null ? void 0 : d.content) != null ? _a : "").substring(0, 40) };
5368
- })
5369
- };
5370
- }
5371
- static transformToTable(data, query = "") {
5372
- const columns = this.extractTableColumns(data, query);
5373
- const rows = data.map((item) => this.extractTableRow(item, columns));
5374
- const tableData = { columns, rows };
5375
- return {
5376
- type: "table",
5377
- title: "Detailed Results",
5378
- description: `Showing ${data.length} results`,
5379
- data: tableData
5380
- };
5381
- }
5382
- static transformToText(data) {
5383
- return this.createTextResponse(
5384
- "Retrieved Context",
5385
- data.map((item) => {
5386
- var _a;
5387
- return (_a = item == null ? void 0 : item.content) != null ? _a : "";
5388
- }).join("\n\n"),
5389
- `Found ${data.length} relevant results`
5390
- );
5391
- }
5392
- static createTextResponse(title, content, description) {
5393
- return { type: "text", title, description, data: { content } };
5394
- }
5395
- // ─── LLM Response Parsing ─────────────────────────────────────────────────
5396
- static parseTransformationResponse(raw) {
5397
- const payloadText = this.extractJsonCandidate(raw);
5398
- if (!payloadText) return null;
5399
- try {
5400
- const parsed = JSON.parse(payloadText);
5401
- return this.normalizeTransformation(parsed);
5402
- } catch (e) {
5403
- return null;
5404
- }
5405
- }
5406
- static extractJsonCandidate(raw) {
5407
- if (!raw) return null;
5408
- const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
5409
- const start = cleaned.indexOf("{");
5410
- if (start === -1) return null;
5411
- let depth = 0;
5412
- let inString = false;
5413
- let escape = false;
5414
- for (let i = start; i < cleaned.length; i += 1) {
5415
- const char = cleaned[i];
5416
- if (escape) {
5417
- escape = false;
5418
- continue;
5419
- }
5420
- if (char === "\\") {
5421
- escape = true;
5422
- continue;
5423
- }
5424
- if (char === '"') {
5425
- inString = !inString;
5426
- continue;
5427
- }
5428
- if (inString) continue;
5429
- if (char === "{") depth += 1;
5430
- if (char === "}") {
5431
- depth -= 1;
5432
- if (depth === 0) return cleaned.slice(start, i + 1);
5433
- }
5434
- }
5435
- return null;
5436
- }
5437
- static normalizeTransformation(payload) {
5438
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
5439
- if (!payload || typeof payload !== "object") return null;
5440
- const p = payload;
5441
- const type = this.normalizeVisualizationType(
5442
- String((_c = (_b = (_a = p.type) != null ? _a : p.view) != null ? _b : p.chartType) != null ? _c : "")
5443
- );
5444
- if (!type) return null;
5445
- const title = String((_e = (_d = p.title) != null ? _d : p.heading) != null ? _e : "Visualization");
5446
- const description = p.description ? String(p.description) : void 0;
5447
- const rawData = (_j = (_i = (_h = (_g = (_f = p.data) != null ? _f : p.table) != null ? _g : p.rows) != null ? _h : p.items) != null ? _i : p.content) != null ? _j : null;
5448
- const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
5449
- const transformation = { type, title, description, data };
5450
- return this.validateTransformation(transformation) ? transformation : null;
5451
- }
5452
- static normalizeVisualizationType(type) {
5453
- var _a;
5454
- const mapping = {
5455
- pie: "pie_chart",
5456
- pie_chart: "pie_chart",
5457
- bar: "bar_chart",
5458
- bar_chart: "bar_chart",
5459
- histogram: "histogram",
5460
- horizontal_bar: "horizontal_bar",
5461
- horizontalbar: "horizontal_bar",
5462
- line: "line_chart",
5463
- line_chart: "line_chart",
5464
- scatter: "scatter_plot",
5465
- scatter_plot: "scatter_plot",
5466
- scatterplot: "scatter_plot",
5467
- radar: "radar_chart",
5468
- radar_chart: "radar_chart",
5469
- metric: "metric_card",
5470
- metric_card: "metric_card",
5471
- card: "metric_card",
5472
- kpi: "metric_card",
5473
- geo: "geo_map",
5474
- geo_map: "geo_map",
5475
- map: "geo_map",
5476
- table: "table",
5477
- text: "text",
5478
- product_carousel: "product_carousel",
5479
- carousel: "carousel"
5480
- };
5481
- return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
5482
- }
5483
- static validateTransformation(t) {
5484
- const { type, data } = t;
5485
- switch (type) {
5486
- case "pie_chart":
5487
- case "bar_chart":
5488
- case "histogram":
5489
- case "horizontal_bar":
5490
- return Array.isArray(data) && data.every(
5491
- (i) => i !== null && typeof i === "object" && (typeof i.value === "number" || !Number.isNaN(Number(i.value)))
5492
- );
5493
- case "scatter_plot":
5494
- return Array.isArray(data) && data.every(
5495
- (i) => i !== null && typeof i === "object" && (typeof i.x === "number" || !Number.isNaN(Number(i.x))) && (typeof i.y === "number" || !Number.isNaN(Number(i.y)))
5496
- );
5497
- case "line_chart":
5498
- return Array.isArray(data) && data.every(
5499
- (i) => i !== null && typeof i === "object" && "timestamp" in i && (typeof i.value === "number" || !Number.isNaN(Number(i.value)))
5500
- );
5501
- case "metric_card":
5502
- return typeof data === "object" && data !== null && (typeof data.value === "number" || !Number.isNaN(Number(data.value)));
5503
- case "geo_map":
5504
- return Array.isArray(data) || typeof data === "object" && data !== null;
5505
- case "radar_chart":
5506
- return Array.isArray(data) && data.every(
5507
- (i) => i !== null && typeof i === "object" && "attribute" in i
5508
- );
5509
- case "table":
5510
- return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
5511
- case "text":
5512
- return typeof data === "object" && data !== null && typeof data.content === "string";
5513
- case "product_carousel":
5514
- case "carousel":
5515
- return Array.isArray(data) && data.every(
5516
- (i) => i !== null && typeof i === "object" && ("id" in i || "name" in i)
5517
- );
5518
- default:
5519
- return false;
5520
- }
5521
- }
5522
- // ─── Data Inspection Helpers ──────────────────────────────────────────────
5523
- static isStructuredListQuery(query) {
5524
- const q = query.toLowerCase();
5525
- const asksForRecords = /\b(list|provide|show|give|get|find|which|whose|records?|details?)\b/.test(q);
5526
- const hasNumericPredicate = /\b(greater than|more than|above|over|less than|below|under|at least|at most|minimum|maximum|>=|<=|>|<|equals?|equal to)\b\s*\$?\d/i.test(q) || /\b\d+\b\s*(?:or more|or less|and above|and below)\b/i.test(q);
5527
- const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5528
- return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5529
- }
5530
- /** @internal kept private for transform() internal logic */
5531
- static isProductQuery(query) {
5532
- return _UITransformer._productQueryTest(query);
5533
- }
5534
- /**
5535
- * Public entry-point for external callers (e.g. Pipeline)
5536
- * to check whether a query maps to product_browse without triggering an LLM call.
5537
- */
5538
- static isProductQueryPublic(query) {
5539
- return _UITransformer._productQueryTest(query);
5540
- }
5541
- static _productQueryTest(query) {
5542
- const q = query.toLowerCase();
5543
- const productTerms = /\b(product|products|item|items|sku|catalog|catalogue|price|prices|brand|model|stock|inventory|in stock|out of stock|buy|shop)\b/.test(q);
5544
- const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
5545
- const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5546
- return productTerms && productAction && !nonProductEntityTerms;
5547
- }
5548
- static isProductData(item) {
5549
- if (!item) return false;
5550
- const content = (item.content || "").toLowerCase();
5551
- const productKeywords = [
5552
- "product",
5553
- "price",
5554
- "stock",
5555
- "sku",
5556
- "brand",
5557
- "msrp",
5558
- "inventory",
5559
- "buy",
5560
- "shop",
5561
- "beauty",
5562
- "cosmetic",
5563
- "facial",
5564
- "cream",
5565
- "serum",
5566
- "mask",
5567
- "makeup",
5568
- "fragrance"
5569
- ];
5570
- const hasKeywords = productKeywords.some((kw) => content.includes(kw));
5571
- const metadata = item.metadata || {};
5572
- const hasMetadataKey = Object.keys(metadata).some((k) => {
5573
- const val = metadata[k];
5574
- return ["price", "product", "sku", "brand", "cost"].includes(k.toLowerCase()) && val !== null && val !== void 0 && val !== "";
5575
- });
5576
- const hasPricePattern = /\$\s*\d+\.\d{2}/.test(content);
5577
- return hasKeywords || hasMetadataKey || hasPricePattern;
5578
- }
5579
- static isTimeSeriesData(item) {
5580
- const metadata = item.metadata || {};
5581
- const maybeDateKeys = Object.keys(metadata).filter(
5582
- (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
5583
- );
5584
- return maybeDateKeys.some((key) => {
5585
- const value = metadata[key];
5586
- if (typeof value === "string") return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
5587
- return typeof value === "number";
5588
- });
5589
- }
5590
- static hasMultipleFields(data) {
5591
- const fieldCount = /* @__PURE__ */ new Set();
5592
- data.forEach((item) => Object.keys(item.metadata || {}).forEach((k) => fieldCount.add(k)));
5593
- return fieldCount.size > 2;
5594
- }
5595
- static profileData(data) {
5596
- const records = (data || []).filter((item) => Boolean(item && typeof item === "object")).map((item) => {
5597
- var _a, _b;
5598
- const fields2 = {};
5599
- Object.entries(item.metadata || {}).forEach(([key, value]) => {
5600
- const primitive = this.toPrimitive(value);
5601
- if (primitive !== null) fields2[key] = primitive;
5602
- });
5603
- if (!fields2.content && item.content) fields2.content = item.content.substring(0, 500);
5604
- return {
5605
- id: item.id,
5606
- content: (_a = item.content) != null ? _a : "",
5607
- score: (_b = item.score) != null ? _b : 0,
5608
- fields: fields2,
5609
- source: item
5610
- };
5611
- });
5612
- const keys = Array.from(new Set(records.flatMap((record) => Object.keys(record.fields))));
5613
- const fields = keys.map((key) => {
5614
- const values = records.map((record) => record.fields[key]).filter((value) => value !== void 0 && value !== null && String(value).trim() !== "");
5615
- const uniqueCount = new Set(values.map((value) => String(value).toLowerCase())).size;
5616
- return {
5617
- key,
5618
- label: this.humanizeFieldName(key),
5619
- kind: this.inferFieldKind(key, values, records.length, uniqueCount),
5620
- values,
5621
- uniqueCount
5622
- };
5623
- });
5624
- return {
5625
- records,
5626
- fields,
5627
- numericFields: fields.filter((field) => field.kind === "number"),
5628
- dateFields: fields.filter((field) => field.kind === "date"),
5629
- categoricalFields: fields.filter((field) => field.kind === "category"),
5630
- booleanFields: fields.filter((field) => field.kind === "boolean")
5631
- };
5632
- }
5633
- static toPrimitive(value) {
5634
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
5635
- if (value === null || value === void 0) return null;
5636
- if (value instanceof Date) return value.toISOString();
5637
- return null;
5638
- }
5639
- static inferFieldKind(key, values, rowCount, uniqueCount) {
5640
- const lowerKey = key.toLowerCase();
5641
- if (values.length === 0) return "text";
5642
- if (values.every((value) => typeof value === "boolean" || /^(true|false|yes|no|in_stock|out_of_stock|available|unavailable)$/i.test(String(value)))) {
5643
- return "boolean";
5644
- }
5645
- if (/(date|time|timestamp|created|updated|month|year|period)/i.test(lowerKey) && values.some((value) => this.isDateLike(value))) {
5646
- return "date";
5647
- }
5648
- const numericCount = values.filter((value) => this.toFiniteNumber(value) !== null).length;
5649
- if (numericCount / values.length >= 0.85 && !/(id|sku|ean|upc|zip|postal|phone|code)/i.test(lowerKey)) {
5650
- return "number";
5651
- }
5652
- if (uniqueCount <= Math.max(20, Math.ceil(rowCount * 0.7)) && !/(id|sku|ean|upc|description|content|url|image|thumbnail)/i.test(lowerKey)) {
5653
- return "category";
5654
- }
5655
- return "text";
5656
- }
5657
- static isDateLike(value) {
5658
- if (typeof value === "number") return value > 1900 && value < 3e3;
5659
- const text = String(value).trim();
5660
- return text.length > 3 && !Number.isNaN(Date.parse(text));
5661
- }
5662
- static chooseAutomaticVisualization(data, profile, query) {
5663
- if (profile.records.length === 0) return null;
5664
- const q = query.toLowerCase().trim();
5665
- const isExplicitVisualOrAnalytic = /\b(chart|graph|plot|visualize|trend|monthly|revenue|top\s*\d+|breakdown|analytics|compare|versus|vs\.?|histogram|pie|bar|line|distribution|percentage|share|summary of metrics)\b/i.test(q);
5666
- if (!isExplicitVisualOrAnalytic) {
5667
- return null;
5668
- }
5669
- if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
5670
- return this.transformToLineChart(profile);
5671
- }
5672
- if (profile.categoricalFields.length > 0 && profile.numericFields.length > 0) {
5673
- return this.transformToBarChart(data, profile, query);
5674
- }
5675
- if (profile.categoricalFields.length > 0) {
5676
- return this.transformToPieChart(data, profile, query);
5677
- }
5678
- if (profile.numericFields.length >= 2) {
5679
- return this.transformToScatterPlot(profile, query);
5680
- }
5681
- if (profile.numericFields.length === 1) {
5682
- return this.transformToMetricCard(profile, query);
5683
- }
5684
- return null;
5685
- }
5686
- static selectDimensionField(profile, query) {
5687
- var _a, _b;
5688
- const productCategory = profile.categoricalFields.find(
5689
- (field) => /category|department|collection|type|group|segment|region|country|state|city/i.test(field.key)
5690
- );
5691
- const ranked = this.rankFieldsByQuery(profile.categoricalFields, query);
5692
- return (_b = (_a = ranked[0]) != null ? _a : productCategory) != null ? _b : profile.categoricalFields[0];
5693
- }
5694
- static selectNumericField(profile, query) {
5695
- var _a;
5696
- return (_a = this.rankFieldsByQuery(profile.numericFields, query)[0]) != null ? _a : profile.numericFields[0];
5697
- }
5698
- static rankFieldsByQuery(fields, query) {
5699
- const q = query.toLowerCase();
5700
- return [...fields].sort((a, b) => this.fieldScore(b, q) - this.fieldScore(a, q));
5701
- }
5702
- static fieldScore(field, query) {
5703
- const key = field.key.toLowerCase();
5704
- const label = field.label.toLowerCase();
5705
- let score = 0;
5706
- if (query.includes(key)) score += 4;
5707
- if (query.includes(label)) score += 4;
5708
- key.split(/[_\s-]+/).forEach((part) => {
5709
- if (part && query.includes(part)) score += 1;
5710
- });
5711
- if (/category|department|collection|type|group|segment/.test(key)) score += 2;
5712
- if (/count|total|quantity|stock|inventory|sales|revenue|amount|price|value|score/.test(key)) score += 2;
5713
- if (/id|sku|ean|upc|code/.test(key)) score -= 5;
5714
- return score;
5715
- }
5716
- static normalizeComparableField(field) {
5717
- return field.toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]+/g, "").trim();
5718
- }
5719
- static fieldTokens(field) {
5720
- return field.toLowerCase().replace(/[_-]+/g, " ").split(/\s+/).map((token) => token.replace(/[^a-z0-9]/g, "")).filter(Boolean);
5721
- }
5722
- static aggregateProfileByDimension(profile, dimensionKey, measureKey) {
5723
- const result = {};
5724
- profile.records.forEach((record) => {
5725
- var _a, _b, _c;
5726
- const category = String((_a = record.fields[dimensionKey]) != null ? _a : "Other").trim() || "Other";
5727
- const value = measureKey ? (_b = this.toFiniteNumber(record.fields[measureKey])) != null ? _b : 0 : 1;
5728
- result[category] = ((_c = result[category]) != null ? _c : 0) + value;
5729
- });
5730
- return result;
5731
- }
5732
- static getRecordLabel(record) {
5733
- const labelKeys = ["name", "title", "label", "product", "item", "brand"];
5734
- const key = Object.keys(record.fields).find(
5735
- (fieldKey) => labelKeys.some((labelKey) => fieldKey.toLowerCase().includes(labelKey))
5736
- );
5737
- return key ? record.fields[key] : void 0;
5738
- }
5739
- static detectAggregationOperation(query) {
5740
- const q = query.toLowerCase();
5741
- if (/\b(avg|average|mean)\b/.test(q)) return "average";
5742
- if (/\b(count|how many|number of)\b/.test(q)) return "count";
5743
- if (/\b(min|minimum|lowest)\b/.test(q)) return "min";
5744
- if (/\b(max|maximum|highest)\b/.test(q)) return "max";
5745
- if (/\bmedian\b/.test(q)) return "median";
5746
- return "sum";
5747
- }
5748
- static calculateAggregate(values, operation) {
5749
- if (values.length === 0) return 0;
5750
- switch (operation) {
5751
- case "average":
5752
- return values.reduce((sum, value) => sum + value, 0) / values.length;
5753
- case "count":
5754
- return values.length;
5755
- case "min":
5756
- return Math.min(...values);
5757
- case "max":
5758
- return Math.max(...values);
5759
- case "median": {
5760
- const sorted = [...values].sort((a, b) => a - b);
5761
- const middle = Math.floor(sorted.length / 2);
5762
- return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
5763
- }
5764
- case "sum":
5765
- default:
5766
- return values.reduce((sum, value) => sum + value, 0);
5767
- }
5768
- }
5769
- static humanizeFieldName(key) {
5770
- return key.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
5771
- }
5772
- static formatNumber(value) {
5773
- return Number.isInteger(value) ? String(value) : value.toFixed(1);
5774
- }
5775
- static detectCategories(data) {
5776
- const categories = /* @__PURE__ */ new Set();
5777
- data.forEach((item) => {
5778
- const category = this.getProductCategory(item);
5779
- if (category) categories.add(category);
5780
- });
5781
- return Array.from(categories);
5782
- }
5783
- static getProductCategory(item) {
5784
- if (!item) return null;
5785
- const meta = item.metadata || {};
5786
- const metadataCategory = resolveMetadataValue(meta, "category");
5787
- if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
5788
- const content = item.content || "";
5789
- const explicitCategoryMatch = content.match(
5790
- /(?:^|\n)\s*(?:product\s*)?(?:category|department|collection)\s*[:\-–]\s*([^\n]+)/i
5791
- );
5792
- if (explicitCategoryMatch && this.isUsableCategory(explicitCategoryMatch[1])) {
5793
- return explicitCategoryMatch[1].trim();
5794
- }
5795
- return null;
5796
- }
5797
- static isUsableCategory(value) {
5798
- if (value === null || value === void 0) return false;
5799
- const category = String(value).trim();
5800
- if (!category) return false;
5801
- return !/^(category|type|tag|price|cost|stock|availability|description|product|item|name|brand|sku|id|ean|currency|color|size|index|internal id|other|\[?type\]?|\[?products?_?\d*\]?)$/i.test(category);
5802
- }
5803
- static aggregateByCategory(data, categories) {
5804
- const result = Object.fromEntries(categories.map((c) => [c, 0]));
5805
- data.forEach((item) => {
5806
- var _a;
5807
- const cat = (_a = this.getProductCategory(item)) != null ? _a : "Other";
5808
- if (Object.prototype.hasOwnProperty.call(result, cat)) result[cat]++;
5809
- else result["Other"] = (result["Other"] || 0) + 1;
5810
- });
5811
- return result;
5812
- }
5813
- static extractTimeSeriesData(data) {
5814
- return data.map((item) => {
5815
- var _a, _b, _c, _d, _e, _f;
5816
- const meta = item.metadata || {};
5817
- return {
5818
- timestamp: (_b = (_a = meta.timestamp) != null ? _a : meta.date) != null ? _b : (/* @__PURE__ */ new Date()).toISOString(),
5819
- value: (_d = (_c = meta.value) != null ? _c : item.score) != null ? _d : 0,
5820
- label: (_f = meta.label) != null ? _f : ((_e = item == null ? void 0 : item.content) != null ? _e : "").substring(0, 50)
5821
- };
5822
- });
5823
- }
5824
- static extractNumericValue(meta) {
5825
- var _a;
5826
- const preferredKeys = [
5827
- "value",
5828
- "count",
5829
- "total",
5830
- "average",
5831
- "avg",
5832
- "amount",
5833
- "sales",
5834
- "revenue",
5835
- "score",
5836
- "quantity",
5837
- "price"
5838
- ];
5839
- for (const key of preferredKeys) {
5840
- const raw = (_a = resolveMetadataValue(meta, key)) != null ? _a : meta[key];
5841
- const value = typeof raw === "number" ? raw : Number(String(raw != null ? raw : "").replace(/[$,% ,]/g, ""));
5842
- if (Number.isFinite(value)) return value;
5843
- }
5844
- for (const value of Object.values(meta)) {
5845
- const numeric = typeof value === "number" ? value : Number(String(value != null ? value : "").replace(/[$,% ,]/g, ""));
5846
- if (Number.isFinite(numeric)) return numeric;
5847
- }
5848
- return null;
5849
- }
5850
- static extractTableColumns(data, query = "") {
5851
- const q = query.toLowerCase();
5852
- const availableFields = this.extractAvailableTableFields(data);
5853
- if (/\b(organization|organisations?|organizations?|company|companies)\b/.test(q) && /\b(employee|employees|staff|headcount|workforce)\b/.test(q)) {
5854
- const nameField = this.pickTableField(availableFields, [
5855
- "company name",
5856
- "organization name",
5857
- "organisation name",
5858
- "name",
5859
- "company",
5860
- "organization",
5861
- "organisation"
5862
- ]);
5863
- const employeeField = this.pickTableField(availableFields, [
5864
- "number of employees",
5865
- "employee count",
5866
- "employees",
5867
- "employee_count",
5868
- "number_employees",
5869
- "num employees",
5870
- "staff count",
5871
- "headcount",
5872
- "workforce"
5873
- ]);
5874
- const columns = [nameField, employeeField].filter((field) => Boolean(field));
5875
- if (columns.length > 0) return columns;
5876
- }
5877
- const requestedFields = availableFields.map((field) => ({
5878
- field,
5879
- score: this.tableFieldQueryScore(field, q)
5880
- })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).map((item) => item.field);
5881
- if (requestedFields.length > 0) {
5882
- return requestedFields.slice(0, Math.min(6, requestedFields.length));
5883
- }
5884
- const metadataFields = Array.from(new Set(
5885
- data.flatMap((item) => Object.keys(item.metadata || {}).map((k) => this.humanizeFieldName(k)))
5886
- ));
5887
- return metadataFields.length > 0 ? metadataFields : ["Content"];
5888
- }
5889
- static extractTableRow(item, columns) {
5890
- return columns.map((col) => this.resolveTableCellValue(item, col));
5891
- }
5892
- static extractAvailableTableFields(data) {
5893
- const fields = /* @__PURE__ */ new Map();
5894
- const addField = (field) => {
5895
- const clean = this.humanizeFieldName(field);
5896
- if (!clean || /^(content|\[?type\]?)$/i.test(clean)) return;
5897
- const normalized = this.normalizeComparableField(clean);
5898
- if (!fields.has(normalized)) fields.set(normalized, clean);
5899
- };
5900
- data.forEach((item) => {
5901
- if (!item) return;
5902
- Object.keys(item.metadata || {}).forEach(addField);
5903
- for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
5904
- addField(match[1]);
5905
- }
5906
- });
5907
- return Array.from(fields.values());
5908
- }
5909
- static pickTableField(fields, aliases) {
5910
- const normalizedAliases = aliases.map((alias) => this.normalizeComparableField(alias));
5911
- for (const alias of normalizedAliases) {
5912
- const exact = fields.find((field) => this.normalizeComparableField(field) === alias);
5913
- if (exact) return exact;
5914
- }
5915
- for (const alias of normalizedAliases) {
5916
- const fuzzy = fields.find((field) => {
5917
- const normalizedField = this.normalizeComparableField(field);
5918
- if (/(id|code|uuid)$/.test(normalizedField) && !/(id|code|uuid)$/.test(alias)) return false;
5919
- return normalizedField.includes(alias) || alias.includes(normalizedField);
5920
- });
5921
- if (fuzzy) return fuzzy;
5922
- }
5923
- return void 0;
5924
- }
5925
- static tableFieldQueryScore(field, query) {
5926
- const normalizedField = this.normalizeComparableField(field);
5927
- if (!normalizedField) return 0;
5928
- const fieldTokens = this.fieldTokens(field);
5929
- return fieldTokens.reduce((score, token) => {
5930
- if (token.length < 3) return score;
5931
- return query.includes(token) ? score + 1 : score;
5932
- }, query.includes(normalizedField) ? 3 : 0);
5933
- }
5934
- static resolveTableCellValue(item, column) {
5935
- var _a, _b, _c;
5936
- if (column === "Content") return ((_a = item == null ? void 0 : item.content) != null ? _a : "").substring(0, 100);
5937
- const meta = item.metadata || {};
5938
- const normalizedColumn = this.normalizeComparableField(column);
5939
- const exactMetadata = Object.entries(meta).find(
5940
- ([key]) => this.normalizeComparableField(key) === normalizedColumn || this.normalizeComparableField(this.humanizeFieldName(key)) === normalizedColumn
5941
- );
5942
- if (exactMetadata && exactMetadata[1] !== void 0 && exactMetadata[1] !== null) {
5943
- return this.toDisplayValue(exactMetadata[1]);
5944
- }
5945
- const aliasValue = this.resolveAliasedTableCell(meta, column);
5946
- if (aliasValue !== void 0) return this.toDisplayValue(aliasValue);
5947
- const contentValue = this.extractContentFieldValue((_b = item == null ? void 0 : item.content) != null ? _b : "", column);
5948
- if (contentValue !== null) return contentValue;
5949
- const aliasContentValue = this.extractAliasedContentFieldValue((_c = item == null ? void 0 : item.content) != null ? _c : "", column);
5950
- return aliasContentValue != null ? aliasContentValue : "";
5951
- }
5952
- static resolveAliasedTableCell(meta, column) {
5953
- const normalizedColumn = this.normalizeComparableField(column);
5954
- const aliases = normalizedColumn.includes("employee") ? ["number of employees", "employee count", "employees", "headcount", "staff count", "workforce"] : normalizedColumn.includes("name") ? ["company name", "organization name", "organisation name", "name", "company", "organization", "organisation"] : [];
5955
- for (const alias of aliases) {
5956
- const normalizedAlias = this.normalizeComparableField(alias);
5957
- const match = Object.entries(meta).find(([key]) => {
5958
- const normalizedKey = this.normalizeComparableField(key);
5959
- return normalizedKey === normalizedAlias || normalizedKey.includes(normalizedAlias) || normalizedAlias.includes(normalizedKey);
5960
- });
5961
- if (match && match[1] !== void 0 && match[1] !== null) return match[1];
5962
- }
5963
- return void 0;
5964
- }
5965
- static extractContentFieldValue(content, column) {
5966
- const escaped = column.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "[\\s_ -]+");
5967
- const pattern = new RegExp(`(?:^|\\n)\\s*${escaped}\\s*[:\\-\u2013]\\s*([^\\n]+)`, "i");
5968
- const match = content.match(pattern);
5969
- return match ? match[1].trim() : null;
5970
- }
5971
- static extractAliasedContentFieldValue(content, column) {
5972
- const normalizedColumn = this.normalizeComparableField(column);
5973
- const aliases = normalizedColumn.includes("employee") ? ["Number Of Employees", "Number of Employees", "Employee Count", "Employees", "Headcount", "Staff Count", "Workforce"] : normalizedColumn.includes("name") ? ["Company Name", "Organization Name", "Organisation Name", "Name", "Company", "Organization", "Organisation"] : [];
5974
- for (const alias of aliases) {
5975
- const value = this.extractContentFieldValue(content, alias);
5976
- if (value !== null) return value;
5977
- }
5978
- return null;
5979
- }
5980
- static toDisplayValue(value) {
5981
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
5982
- return String(value != null ? value : "");
5983
- }
5984
- static calculateStockCounts(category, data) {
5985
- let inStock = 0;
5986
- let outOfStock = 0;
5987
- data.forEach((d) => {
5988
- var _a, _b;
5989
- const cat = (_a = this.getProductCategory(d)) != null ? _a : "Other";
5990
- if (cat === category) {
5991
- const quantity = (_b = this.extractStockQuantity(d)) != null ? _b : 1;
5992
- if (this.determineStockStatus(d)) inStock += quantity;
5993
- else outOfStock += quantity;
5994
- }
5995
- });
5996
- return { inStockCount: inStock, outOfStockCount: outOfStock };
5997
- }
5998
- static determineStockStatus(item) {
5999
- if (!item) return true;
6000
- const meta = item.metadata || {};
6001
- const stockValue = resolveMetadataValue(meta, "stock");
6002
- if (stockValue !== void 0) {
6003
- const normalized = String(stockValue).toLowerCase();
6004
- if (/out[_\s-]?of[_\s-]?stock|unavailable|false|no|sold out|0/.test(normalized)) return false;
6005
- if (/in[_\s-]?stock|available|true|yes/.test(normalized)) return true;
6006
- const numeric = this.toFiniteNumber(stockValue);
6007
- if (numeric !== null) return numeric > 0;
6008
- }
6009
- if (meta.inStock !== void 0) return Boolean(meta.inStock);
6010
- if (meta.stock !== void 0) return Boolean(meta.stock);
6011
- if (meta.available !== void 0) return Boolean(meta.available);
6012
- const content = (item.content || "").toLowerCase();
6013
- if (/out[_\s-]?of[_\s-]?stock|unavailable|sold out/.test(content)) return false;
6014
- if (/in[_\s-]?stock|available/.test(content)) return true;
6015
- return true;
6016
- }
6017
- static extractStockQuantity(item) {
6018
- if (!item) return null;
6019
- const meta = item.metadata || {};
6020
- const stockValue = resolveMetadataValue(meta, "stock");
6021
- const numericStock = this.toFiniteNumber(stockValue);
6022
- if (numericStock !== null) return numericStock;
6023
- const content = item.content || "";
6024
- const stockMatch = content.match(/\b(?:stock|inventory|quantity|count)\s*[:\-–]?\s*([\d,]+)/i);
6025
- if (!stockMatch) return null;
6026
- return this.toFiniteNumber(stockMatch[1]);
6027
- }
6028
- static toFiniteNumber(value) {
6029
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
6030
- const numeric = Number(String(value != null ? value : "").replace(/[$,% ,]/g, ""));
6031
- return Number.isFinite(numeric) ? numeric : null;
6032
- }
6033
- // ─── Product Extraction ───────────────────────────────────────────────────
6034
- static getDynamicVal(meta, uiKey, config, trainedSchema) {
6035
- var _a;
6036
- if (!meta) return void 0;
6037
- const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
6038
- if ((mapping == null ? void 0 : mapping[uiKey]) && meta[mapping[uiKey]] !== void 0) return meta[mapping[uiKey]];
6039
- if (trainedSchema && typeof trainedSchema === "object") {
6040
- const trainedKey = trainedSchema[uiKey];
6041
- if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
6042
- }
6043
- return resolveMetadataValue(meta, uiKey);
6044
- }
6045
- static extractProductInfo(item, config, trainedSchema) {
6046
- var _a, _b;
6047
- if (!item) return null;
6048
- const meta = item.metadata || {};
6049
- const content = item.content || "";
6050
- const name = this.getDynamicVal(meta, "name", config, trainedSchema);
6051
- const price = this.getDynamicVal(meta, "price", config, trainedSchema);
6052
- const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
6053
- const description = this.cleanProductDescription(
6054
- (_a = this.extractProductDescriptionFromContent(content)) != null ? _a : this.getProductDescriptionValue(meta, config, trainedSchema)
6055
- );
6056
- if (name || this.isProductData(item)) {
6057
- let finalName = name ? String(name) : void 0;
6058
- if (!finalName && content) {
6059
- const nameMatch = content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
6060
- finalName = nameMatch ? nameMatch[1].trim() : (_b = content.split("\n")[0]) == null ? void 0 : _b.substring(0, 60);
6061
- }
6062
- if (!finalName) {
6063
- console.warn(
6064
- `[UITransformer] Unable to read product name from metadata or content for item ${item.id}. Defaulting to 'Unnamed Product'.`,
6065
- { metadataKeys: Object.keys(meta), contentLength: content.length }
6066
- );
6067
- }
6068
- let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
6069
- if (!finalPrice && content) {
6070
- const priceMatch = content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || content.match(/\$\s*([\d,.]+)/);
6071
- if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
6072
- }
6073
- if (finalPrice === void 0) {
6074
- console.warn(
6075
- `[UITransformer] Unable to read product price from metadata or content for item ${item.id}.`,
6076
- { metadataKeys: Object.keys(meta), contentSnippet: content.substring(0, 100) }
6077
- );
6078
- }
6079
- const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
6080
- if (!imageValue) {
6081
- console.debug(
6082
- `[UITransformer] Unable to read product image URL from metadata for item ${item.id}.`
6083
- );
6084
- }
6085
- if (!description) {
6086
- console.debug(
6087
- `[UITransformer] Unable to read product description from metadata or content for item ${item.id}.`
6088
- );
6089
- }
6090
- return {
6091
- id: item.id,
6092
- name: finalName || "Unnamed Product",
6093
- price: finalPrice,
6094
- image: typeof imageValue === "string" ? imageValue : void 0,
6095
- brand: brand ? String(brand) : void 0,
6096
- description,
6097
- inStock: this.determineStockStatus(item)
6098
- };
6099
- } else {
6100
- console.warn(
6101
- `[UITransformer] Item ${item.id} skipped: unable to read name or identify as product data from metadata/content.`,
6102
- { metadataKeys: Object.keys(meta), contentSnippet: content.substring(0, 100) }
6103
- );
6104
- }
6105
- return null;
6106
- }
6107
- static extractProductDescriptionFromContent(content) {
6108
- const bodyMatch = content.match(
6109
- /\bBody\s*\(HTML\)\s*:\s*([\s\S]*?)(?=\s*[,.]?\s+\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\b\s*:|$)/i
6110
- );
6111
- if (bodyMatch == null ? void 0 : bodyMatch[1]) return bodyMatch[1];
6112
- const descriptionMatch = content.match(
6113
- /\b(?:Description|Summary|Details|Body|Content)\s*:\s*([\s\S]*?)(?=\s*[,.]?\s+\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\b\s*:|$)/i
6114
- );
6115
- if (descriptionMatch == null ? void 0 : descriptionMatch[1]) return descriptionMatch[1];
6116
- return null;
6117
- }
6118
- static getProductDescriptionValue(meta, config, trainedSchema) {
6119
- const mapped = this.getDynamicVal(meta, "description", config, trainedSchema);
6120
- if (mapped !== void 0 && mapped !== meta.content && mapped !== meta.text) return mapped;
6121
- const preferredKeys = [
6122
- "body_html",
6123
- "body html",
6124
- "bodyHtml",
6125
- "description",
6126
- "summary",
6127
- "details",
6128
- "body"
6129
- ];
6130
- for (const key of preferredKeys) {
6131
- const match = Object.keys(meta).find(
6132
- (candidate) => this.normalizeComparableField(candidate) === this.normalizeComparableField(key)
6133
- );
6134
- if (match && meta[match] !== void 0 && meta[match] !== null) return meta[match];
6135
- }
6136
- return void 0;
6137
- }
6138
- static cleanProductDescription(raw) {
6139
- if (raw === null || raw === void 0) return void 0;
6140
- const extracted = this.extractProductDescriptionFromContent(String(raw));
6141
- if (extracted && extracted !== String(raw)) return this.cleanProductDescription(extracted);
6142
- const text = String(raw).replace(/<[^>]*>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/\[[^\]]*TYPE[^\]]*\]/gi, " ").replace(/\b(?:Handle|Title|Vendor|Type|Tags|Published|Option\d*\s+Name|Option\d*\s+Value|Option|Variant|Image|SKU|Price|Status|Category)\s*:\s*[^,.\n:]+[,.]?/gi, " ").replace(/\bBody\s*\(HTML\)\s*:\s*/gi, " ").replace(/\b(?:Description|Summary|Details|Body|Content)\s*:\s*/gi, " ").replace(/\s+/g, " ").trim();
6143
- return text || void 0;
6144
- }
6145
- // ─── Visualization Prompt ─────────────────────────────────────────────────
6146
- static buildVisualizationSystemPrompt() {
6147
- return `You are a data visualization expert embedded in a RAG chat system.
6148
- You will receive a user question, a pre-computed INTENT object, and structured data from a vector database.
6149
- Your ONLY job is to return a single JSON object describing how to visualize the data.
6150
- The INTENT object is authoritative \u2014 honor it. For example:
6151
- - If intent.wantsExplicitTable is false, never return type "table".
6152
- - If intent.isTemporal is true, prefer line_chart.
6153
- - If intent.visualizationHint is "comparison" or "ranking", prefer bar_chart.
6154
- - If intent.visualizationHint is "composition", prefer pie_chart.
6155
- - If intent.recommendedChart is unsupported by the response schema, use the nearest supported type.
6156
- - Always respond in the language specified by intent.language.
6157
-
6158
- Return ONLY a valid JSON object \u2014 no markdown fences, no prose.
6159
-
6160
- {
6161
- "type": "bar_chart" | "horizontal_bar" | "line_chart" | "pie_chart" | "histogram" | "scatter_plot" | "radar_chart" | "metric_card" | "geo_map" | "table" | "text",
6162
- "title": "concise descriptive title",
6163
- "description": "one sentence describing the visualization",
6164
- "data": <structured data per type below>
6165
- }
6166
-
6167
- DATA SHAPES:
6168
- - bar_chart: [{ "category": string, "value": number }]
6169
- - horizontal_bar: [{ "category": string, "value": number }]
6170
- - histogram: [{ "category": string, "value": number }]
6171
- - line_chart: [{ "timestamp": string, "value": number, "label": string }]
6172
- - pie_chart: [{ "label": string, "value": number }]
6173
- - scatter_plot:[{ "x": number, "y": number, "label": string }]
6174
- - radar_chart: [{ "attribute": string, "<series1>": number, "<series2>": number, ... }]
6175
- - metric_card: { "label": string, "value": number, "operation": "sum"|"average"|"count"|"min"|"max"|"median" }
6176
- - geo_map: [{ "category": string, "value": number }]
6177
- - table: { "columns": string[], "rows": (string|number)[][] }
6178
- - text: { "content": "A concise plain-language answer." }
6179
-
6180
- RULES:
6181
- 1. Aggregate values \u2014 never pass raw nested objects in chart arrays.
6182
- 2. All "value" fields must be numbers.
6183
- 3. Cap bar/line/pie at 12 data points.
6184
- 4. Use dollar signs only for monetary prices, not for stock quantities or years.
6185
- 5. If no relevant data is found, return text: "I cannot answer this question based on the available information."`;
6186
- }
6187
- static buildContextSummary(sources, maxChars = 6e3) {
6188
- const items = (sources || []).filter(Boolean).map((s, i) => {
6189
- var _a, _b, _c, _d;
6190
- return {
6191
- index: i + 1,
6192
- content: (_b = (_a = s == null ? void 0 : s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
6193
- metadata: (_c = s == null ? void 0 : s.metadata) != null ? _c : {},
6194
- score: (_d = s == null ? void 0 : s.score) != null ? _d : 0
6195
- };
6196
- });
6197
- const full = JSON.stringify(items, null, 2);
6198
- if (full.length <= maxChars) return full;
6199
- const partial = [];
6200
- let chars = 2;
6201
- for (const item of items) {
6202
- const chunk = JSON.stringify(item);
6203
- if (chars + chunk.length + 2 > maxChars) break;
6204
- partial.push(item);
6205
- chars += chunk.length + 2;
6206
- }
6207
- return JSON.stringify(partial, null, 2) + "\n// ... truncated";
6208
- }
6209
- };
6210
-
6211
- // src/rendering/RendererRegistry.ts
6212
- var TextRendererStrategy = class {
6213
- constructor() {
6214
- this.type = "text";
6215
- }
6216
- render(data, options) {
6217
- const title = (options == null ? void 0 : options.title) || "Information";
6218
- if (typeof data === "string") {
6219
- return { type: "text", title, data: { content: data } };
6220
- }
6221
- const docs = Array.isArray(data) ? data : [];
6222
- const text = docs.map((d) => {
6223
- var _a;
6224
- return (_a = d == null ? void 0 : d.content) != null ? _a : "";
6225
- }).join("\n\n");
6226
- return { type: "text", title, data: { content: text || "No detailed content available." } };
6227
- }
6228
- };
6229
- var TableRendererStrategy = class {
6230
- constructor() {
6231
- this.type = "table";
6232
- }
6233
- render(data, options) {
6234
- const docs = Array.isArray(data) ? data : [];
6235
- const query = (options == null ? void 0 : options.query) || "";
6236
- return UITransformer.transform(query, docs, void 0, void 0, {
6237
- visualizationHint: "table",
6238
- recommendedChart: "table",
6239
- filterInStockOnly: false,
6240
- wantsExplicitTable: true,
6241
- isTemporal: false,
6242
- isComparison: true,
6243
- language: "en"
6244
- });
6245
- }
6246
- };
6247
- var CarouselRendererStrategy = class {
6248
- constructor() {
6249
- this.type = "carousel";
6250
- }
6251
- render(data, options) {
6252
- const docs = Array.isArray(data) ? data : [];
6253
- const query = (options == null ? void 0 : options.query) || "";
6254
- return UITransformer.transform(query, docs, void 0, void 0, {
6255
- visualizationHint: "product_browse",
6256
- recommendedChart: "text",
6257
- filterInStockOnly: false,
6258
- wantsExplicitTable: false,
6259
- isTemporal: false,
6260
- isComparison: false,
6261
- language: "en"
6262
- });
6263
- }
6264
- };
6265
- var ChartRendererStrategy = class {
6266
- constructor() {
6267
- this.type = "chart";
6268
- }
6269
- render(data, options) {
6270
- const docs = Array.isArray(data) ? data : [];
6271
- const query = (options == null ? void 0 : options.query) || "";
6272
- const chartType = (options == null ? void 0 : options.chartType) || "bar";
6273
- let hint = "comparison";
6274
- let rec = "bar_chart";
6275
- if (chartType === "line") {
6276
- hint = "trend";
6277
- rec = "line_chart";
6278
- } else if (chartType === "pie") {
6279
- hint = "composition";
6280
- rec = "pie_chart";
6281
- } else if (chartType === "scatter") {
6282
- hint = "correlation";
6283
- rec = "scatter_plot";
6284
- }
6285
- return UITransformer.transform(query, docs, void 0, void 0, {
6286
- visualizationHint: hint,
6287
- recommendedChart: rec,
6288
- filterInStockOnly: false,
6289
- wantsExplicitTable: false,
6290
- isTemporal: chartType === "line",
6291
- isComparison: chartType === "bar",
6292
- language: "en"
6293
- });
6294
- }
6295
- };
6296
- var MixedRendererStrategy = class {
6297
- constructor() {
6298
- this.type = "mixed";
6299
- }
6300
- render(data, options) {
6301
- const docs = Array.isArray(data) ? data : [];
6302
- const sections = (options == null ? void 0 : options.sections) || [];
6303
- const sectionPayloads = sections.map((s) => {
6304
- const strategy = RendererRegistry.getStrategy(s.type);
6305
- return {
6306
- type: s.type,
6307
- title: s.title,
6308
- payload: strategy.render(data, __spreadProps(__spreadValues({}, options), { chartType: s.chartType, title: s.title }))
6309
- };
6310
- });
6311
- return {
6312
- type: "table",
6313
- // Fallback container type for compatibility
6314
- title: (options == null ? void 0 : options.title) || "Composite Response",
6315
- description: `Mixed payload containing ${sectionPayloads.length} visual sections`,
6316
- data: { sections: sectionPayloads }
6317
- };
6318
- }
6319
- };
6320
- var _RendererRegistry = class _RendererRegistry {
6321
- /**
6322
- * Register a new renderer strategy.
6323
- * Enables seamless addition of custom visualizers (Timeline, Maps, Flow Diagrams, etc.).
6324
- */
6325
- static registerStrategy(strategy) {
6326
- this.strategies.set(String(strategy.type).toLowerCase(), strategy);
6327
- }
6328
- /**
6329
- * Retrieve a registered renderer strategy by type string.
6330
- */
6331
- static getStrategy(type) {
6332
- const key = type.toLowerCase();
6333
- const strategy = this.strategies.get(key);
6334
- if (!strategy) {
6335
- return this.strategies.get("text");
6336
- }
6337
- return strategy;
6338
- }
6339
- /**
6340
- * Check if a renderer strategy is registered.
6341
- */
6342
- static hasStrategy(type) {
6343
- return this.strategies.has(type.toLowerCase());
6344
- }
6345
- /**
6346
- * List all registered strategy types.
6347
- */
6348
- static getRegisteredTypes() {
6349
- return Array.from(this.strategies.keys());
6350
- }
6351
- };
6352
- _RendererRegistry.strategies = /* @__PURE__ */ new Map();
6353
- _RendererRegistry.registerStrategy(new TextRendererStrategy());
6354
- _RendererRegistry.registerStrategy(new TableRendererStrategy());
6355
- _RendererRegistry.registerStrategy(new CarouselRendererStrategy());
6356
- _RendererRegistry.registerStrategy(new ChartRendererStrategy());
6357
- _RendererRegistry.registerStrategy(new MixedRendererStrategy());
6358
- var RendererRegistry = _RendererRegistry;
6359
-
6360
4409
  // src/core/CircuitBreaker.ts
6361
4410
  var CircuitBreaker = class {
6362
4411
  constructor(options = {}) {
@@ -6718,7 +4767,6 @@ export {
6718
4767
  ConfigurationException,
6719
4768
  DocumentUpload,
6720
4769
  EmbeddingFailedException,
6721
- IntentClassifier,
6722
4770
  LicenseValidationError,
6723
4771
  LicenseValidator,
6724
4772
  MessageBubble,
@@ -6727,16 +4775,12 @@ export {
6727
4775
  ProductCarousel,
6728
4776
  ProviderNotFoundException,
6729
4777
  RateLimitException,
6730
- RendererRegistry,
6731
4778
  RetrievalException,
6732
4779
  RetrivoraError,
6733
- RuleEngine,
6734
4780
  SDKVersionUnsupportedError,
6735
4781
  SDK_VERSION,
6736
4782
  SourceCard,
6737
- VisualizationDecisionEngine,
6738
4783
  addSynonyms,
6739
- decideVisualization,
6740
4784
  isTransientError,
6741
4785
  useConfig,
6742
4786
  useRagChat