@memberjunction/ng-dashboards 5.39.0 → 5.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +128 -4
  2. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
  3. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +548 -145
  4. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
  5. package/dist/AI/components/autotagging/components/classify-item-drilldown.component.d.ts +56 -0
  6. package/dist/AI/components/autotagging/components/classify-item-drilldown.component.d.ts.map +1 -0
  7. package/dist/AI/components/autotagging/components/classify-item-drilldown.component.js +423 -0
  8. package/dist/AI/components/autotagging/components/classify-item-drilldown.component.js.map +1 -0
  9. package/dist/AI/components/autotagging/components/classify-item-grid.component.d.ts +70 -0
  10. package/dist/AI/components/autotagging/components/classify-item-grid.component.d.ts.map +1 -0
  11. package/dist/AI/components/autotagging/components/classify-item-grid.component.js +308 -0
  12. package/dist/AI/components/autotagging/components/classify-item-grid.component.js.map +1 -0
  13. package/dist/AI/components/autotagging/components/classify-org-context-editor.component.d.ts +29 -0
  14. package/dist/AI/components/autotagging/components/classify-org-context-editor.component.d.ts.map +1 -0
  15. package/dist/AI/components/autotagging/components/classify-org-context-editor.component.js +186 -0
  16. package/dist/AI/components/autotagging/components/classify-org-context-editor.component.js.map +1 -0
  17. package/dist/AI/components/autotagging/components/classify-overview-analytics.component.d.ts +69 -0
  18. package/dist/AI/components/autotagging/components/classify-overview-analytics.component.d.ts.map +1 -0
  19. package/dist/AI/components/autotagging/components/classify-overview-analytics.component.js +278 -0
  20. package/dist/AI/components/autotagging/components/classify-overview-analytics.component.js.map +1 -0
  21. package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.d.ts +73 -0
  22. package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.d.ts.map +1 -0
  23. package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.js +393 -0
  24. package/dist/AI/components/autotagging/components/classify-seed-taxonomy.component.js.map +1 -0
  25. package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.d.ts +122 -0
  26. package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.d.ts.map +1 -0
  27. package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.js +908 -0
  28. package/dist/AI/components/autotagging/dialogs/classify-setup-wizard.component.js.map +1 -0
  29. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.d.ts +100 -2
  30. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.d.ts.map +1 -1
  31. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js +603 -213
  32. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js.map +1 -1
  33. package/dist/AI/components/autotagging/shared/classify.format.d.ts +15 -0
  34. package/dist/AI/components/autotagging/shared/classify.format.d.ts.map +1 -1
  35. package/dist/AI/components/autotagging/shared/classify.format.js +51 -0
  36. package/dist/AI/components/autotagging/shared/classify.format.js.map +1 -1
  37. package/dist/AI/components/autotagging/shared/classify.types.d.ts +43 -0
  38. package/dist/AI/components/autotagging/shared/classify.types.d.ts.map +1 -1
  39. package/dist/AI/components/autotagging/shared/classify.types.js.map +1 -1
  40. package/dist/AI/components/autotagging/tabs/history-tab.component.d.ts +38 -1
  41. package/dist/AI/components/autotagging/tabs/history-tab.component.d.ts.map +1 -1
  42. package/dist/AI/components/autotagging/tabs/history-tab.component.js +185 -68
  43. package/dist/AI/components/autotagging/tabs/history-tab.component.js.map +1 -1
  44. package/dist/AI/components/autotagging/tabs/pipeline-tab.component.d.ts +10 -1
  45. package/dist/AI/components/autotagging/tabs/pipeline-tab.component.d.ts.map +1 -1
  46. package/dist/AI/components/autotagging/tabs/pipeline-tab.component.js +249 -188
  47. package/dist/AI/components/autotagging/tabs/pipeline-tab.component.js.map +1 -1
  48. package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts +12 -1
  49. package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts.map +1 -1
  50. package/dist/AI/components/autotagging/tabs/sources-tab.component.js +377 -296
  51. package/dist/AI/components/autotagging/tabs/sources-tab.component.js.map +1 -1
  52. package/dist/AI/components/autotagging/tabs/tags-tab.component.d.ts +8 -0
  53. package/dist/AI/components/autotagging/tabs/tags-tab.component.d.ts.map +1 -1
  54. package/dist/AI/components/autotagging/tabs/tags-tab.component.js +112 -68
  55. package/dist/AI/components/autotagging/tabs/tags-tab.component.js.map +1 -1
  56. package/dist/AI/components/autotagging/tabs/types-tab.component.d.ts +9 -0
  57. package/dist/AI/components/autotagging/tabs/types-tab.component.d.ts.map +1 -1
  58. package/dist/AI/components/autotagging/tabs/types-tab.component.js +87 -36
  59. package/dist/AI/components/autotagging/tabs/types-tab.component.js.map +1 -1
  60. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +3 -0
  61. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  62. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +15 -3
  63. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  64. package/dist/AI/components/execution-monitoring.component.js +1 -1
  65. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  66. package/dist/AI/components/tags/tags-resource.component.d.ts +1 -0
  67. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  68. package/dist/AI/components/tags/tags-resource.component.js +28 -6
  69. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  70. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +3 -0
  71. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  72. package/dist/AI/components/vectors/vector-management-resource.component.js +330 -302
  73. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  74. package/dist/APIKeys/api-applications-panel.component.js +2 -2
  75. package/dist/APIKeys/api-key-create-dialog.component.js +2 -2
  76. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +31 -340
  77. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  78. package/dist/DataExplorer/data-explorer-dashboard.component.js +468 -1958
  79. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  80. package/dist/DataExplorer/data-explorer-resource.component.d.ts.map +1 -1
  81. package/dist/DataExplorer/data-explorer-resource.component.js +10 -0
  82. package/dist/DataExplorer/data-explorer-resource.component.js.map +1 -1
  83. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts.map +1 -1
  84. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js +12 -9
  85. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js.map +1 -1
  86. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts +27 -2
  87. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts.map +1 -1
  88. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js +244 -120
  89. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js.map +1 -1
  90. package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.d.ts +65 -0
  91. package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.d.ts.map +1 -0
  92. package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.js +176 -0
  93. package/dist/KnowledgeHub/components/visualize/record-drilldown/record-drilldown.component.js.map +1 -0
  94. package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.d.ts +81 -0
  95. package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.d.ts.map +1 -0
  96. package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.js +308 -0
  97. package/dist/KnowledgeHub/components/visualize/tag-cloud/tag-cloud.component.js.map +1 -0
  98. package/dist/KnowledgeHub/components/visualize/visualize-resource.component.d.ts +85 -0
  99. package/dist/KnowledgeHub/components/visualize/visualize-resource.component.d.ts.map +1 -0
  100. package/dist/KnowledgeHub/components/visualize/visualize-resource.component.js +362 -0
  101. package/dist/KnowledgeHub/components/visualize/visualize-resource.component.js.map +1 -0
  102. package/dist/KnowledgeHub/index.d.ts +3 -0
  103. package/dist/KnowledgeHub/index.d.ts.map +1 -1
  104. package/dist/KnowledgeHub/index.js +3 -0
  105. package/dist/KnowledgeHub/index.js.map +1 -1
  106. package/dist/MCP/components/mcp-server-dialog.component.js +2 -2
  107. package/dist/QueryBrowser/query-browser-resource.component.js +1 -1
  108. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  109. package/dist/ai-dashboards.module.d.ts +48 -38
  110. package/dist/ai-dashboards.module.d.ts.map +1 -1
  111. package/dist/ai-dashboards.module.js +41 -1
  112. package/dist/ai-dashboards.module.js.map +1 -1
  113. package/dist/data-explorer-dashboards.module.d.ts +12 -14
  114. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  115. package/dist/data-explorer-dashboards.module.js +5 -14
  116. package/dist/data-explorer-dashboards.module.js.map +1 -1
  117. package/dist/public-api.d.ts +3 -0
  118. package/dist/public-api.d.ts.map +1 -1
  119. package/dist/public-api.js +3 -0
  120. package/dist/public-api.js.map +1 -1
  121. package/package.json +57 -55
  122. package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.d.ts +0 -79
  123. package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.d.ts.map +0 -1
  124. package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.js +0 -195
  125. package/dist/DataExplorer/components/filter-dialog/filter-dialog.component.js.map +0 -1
  126. package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts +0 -226
  127. package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts.map +0 -1
  128. package/dist/DataExplorer/components/view-selector/view-selector.component.js +0 -861
  129. package/dist/DataExplorer/components/view-selector/view-selector.component.js.map +0 -1
@@ -15,93 +15,176 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
15
15
  *
16
16
  * Registered as BaseResourceComponent for the Knowledge Hub application.
17
17
  */
18
- import { Component, ChangeDetectorRef, inject, ViewChild } from '@angular/core';
18
+ import { Component, ChangeDetectorRef, inject, ViewChild, Input, Output, EventEmitter } from '@angular/core';
19
19
  import { Subject } from 'rxjs';
20
20
  import { CompositeKey, Metadata } from '@memberjunction/core';
21
21
  import { UserInfoEngine, KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
22
22
  import { RegisterClass } from '@memberjunction/global';
23
- import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
23
+ import { BaseResourceComponent, NavigationService, ActivityService } from '@memberjunction/ng-shared';
24
24
  import { GraphQLAIClient } from '@memberjunction/graphql-dataprovider';
25
25
  import { AIEngineBase } from '@memberjunction/ai-engine-base';
26
26
  import { DefaultClusterConfig, } from '@memberjunction/ng-clustering';
27
27
  import { ClusteringService } from '@memberjunction/ng-clustering';
28
28
  import * as i0 from "@angular/core";
29
- import * as i1 from "@memberjunction/ng-ui-components";
30
- import * as i2 from "@memberjunction/ng-clustering";
29
+ import * as i1 from "@angular/common";
30
+ import * as i2 from "@memberjunction/ng-ui-components";
31
+ import * as i3 from "@memberjunction/ng-clustering";
31
32
  const _c0 = ["scatterPlot"];
32
33
  const _c1 = () => [];
33
- function ClusterVisualizationResourceComponent_For_16_Template(rf, ctx) { if (rf & 1) {
34
+ function ClusterVisualizationResourceComponent_Conditional_0_ng_container_0_Template(rf, ctx) { if (rf & 1) {
35
+ i0.ɵɵelementContainer(0);
36
+ } }
37
+ function ClusterVisualizationResourceComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
38
+ i0.ɵɵtemplate(0, ClusterVisualizationResourceComponent_Conditional_0_ng_container_0_Template, 1, 0, "ng-container", 2);
39
+ } if (rf & 2) {
40
+ i0.ɵɵnextContext();
41
+ const clusterBody_r1 = i0.ɵɵreference(3);
42
+ i0.ɵɵproperty("ngTemplateOutlet", clusterBody_r1);
43
+ } }
44
+ function ClusterVisualizationResourceComponent_Conditional_1_ng_container_8_Template(rf, ctx) { if (rf & 1) {
45
+ i0.ɵɵelementContainer(0);
46
+ } }
47
+ function ClusterVisualizationResourceComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
34
48
  const _r2 = i0.ɵɵgetCurrentView();
35
- i0.ɵɵelementStart(0, "div", 20);
36
- i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_For_16_Template_div_click_0_listener() { const saved_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.OnSelectSaved(saved_r3)); });
37
- i0.ɵɵelementStart(1, "div", 21);
49
+ i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 3)(2, "div", 4)(3, "button", 5);
50
+ i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_Conditional_1_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnNewAnalysis()); });
51
+ i0.ɵɵelement(4, "i", 6);
52
+ i0.ɵɵelementStart(5, "span", 7);
53
+ i0.ɵɵtext(6, "New Analysis");
54
+ i0.ɵɵelementEnd()()()();
55
+ i0.ɵɵelementStart(7, "mj-page-body", 8);
56
+ i0.ɵɵtemplate(8, ClusterVisualizationResourceComponent_Conditional_1_ng_container_8_Template, 1, 0, "ng-container", 2);
57
+ i0.ɵɵelementEnd()();
58
+ } if (rf & 2) {
59
+ i0.ɵɵnextContext();
60
+ const clusterBody_r1 = i0.ɵɵreference(3);
61
+ i0.ɵɵadvance(7);
62
+ i0.ɵɵproperty("Flex", true)("Padding", false);
63
+ i0.ɵɵadvance();
64
+ i0.ɵɵproperty("ngTemplateOutlet", clusterBody_r1);
65
+ } }
66
+ function ClusterVisualizationResourceComponent_ng_template_2_For_8_Template(rf, ctx) { if (rf & 1) {
67
+ const _r5 = i0.ɵɵgetCurrentView();
68
+ i0.ɵɵelementStart(0, "div", 23);
69
+ i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_ng_template_2_For_8_Template_div_click_0_listener() { const saved_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnSelectSaved(saved_r6)); });
70
+ i0.ɵɵelementStart(1, "div", 24);
38
71
  i0.ɵɵtext(2);
39
72
  i0.ɵɵelementEnd();
40
- i0.ɵɵelementStart(3, "div", 22)(4, "span");
73
+ i0.ɵɵelementStart(3, "div", 25)(4, "span");
41
74
  i0.ɵɵtext(5);
42
75
  i0.ɵɵelementEnd();
43
76
  i0.ɵɵelementStart(6, "span");
44
77
  i0.ɵɵtext(7);
45
78
  i0.ɵɵelementEnd()();
46
- i0.ɵɵelementStart(8, "button", 23);
47
- i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_For_16_Template_button_click_8_listener($event) { const saved_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.OnDeleteSaved(saved_r3, $event)); });
48
- i0.ɵɵelement(9, "i", 24);
79
+ i0.ɵɵelementStart(8, "button", 26);
80
+ i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_ng_template_2_For_8_Template_button_click_8_listener($event) { const saved_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnDeleteSaved(saved_r6, $event)); });
81
+ i0.ɵɵelement(9, "i", 27);
49
82
  i0.ɵɵelementEnd()();
50
83
  } if (rf & 2) {
51
- const saved_r3 = ctx.$implicit;
52
- const ctx_r3 = i0.ɵɵnextContext();
53
- i0.ɵɵclassProp("active", ctx_r3.IsActiveSaved(saved_r3));
84
+ const saved_r6 = ctx.$implicit;
85
+ const ctx_r2 = i0.ɵɵnextContext(2);
86
+ i0.ɵɵclassProp("active", ctx_r2.IsActiveSaved(saved_r6));
54
87
  i0.ɵɵadvance(2);
55
- i0.ɵɵtextInterpolate(saved_r3.Name);
88
+ i0.ɵɵtextInterpolate(saved_r6.Name);
56
89
  i0.ɵɵadvance(3);
57
- i0.ɵɵtextInterpolate(saved_r3.EntityName);
90
+ i0.ɵɵtextInterpolate(saved_r6.EntityName);
58
91
  i0.ɵɵadvance(2);
59
- i0.ɵɵtextInterpolate(ctx_r3.FormatDate(saved_r3.CreatedAt));
92
+ i0.ɵɵtextInterpolate(ctx_r2.FormatDate(saved_r6.CreatedAt));
60
93
  } }
61
- function ClusterVisualizationResourceComponent_Conditional_17_Template(rf, ctx) { if (rf & 1) {
62
- i0.ɵɵelementStart(0, "div", 13);
63
- i0.ɵɵelement(1, "i", 25);
94
+ function ClusterVisualizationResourceComponent_ng_template_2_Conditional_9_Template(rf, ctx) { if (rf & 1) {
95
+ i0.ɵɵelementStart(0, "div", 15);
96
+ i0.ɵɵelement(1, "i", 28);
64
97
  i0.ɵɵelementStart(2, "p");
65
98
  i0.ɵɵtext(3, "No saved visualizations yet");
66
99
  i0.ɵɵelementEnd()();
67
100
  } }
68
- function ClusterVisualizationResourceComponent_Conditional_22_Template(rf, ctx) { if (rf & 1) {
69
- i0.ɵɵelementStart(0, "div", 26);
70
- i0.ɵɵelement(1, "i", 27);
101
+ function ClusterVisualizationResourceComponent_ng_template_2_Conditional_14_Template(rf, ctx) { if (rf & 1) {
102
+ i0.ɵɵelementStart(0, "div", 29);
103
+ i0.ɵɵelement(1, "i", 30);
71
104
  i0.ɵɵtext(2, " Records: ");
72
- i0.ɵɵelementStart(3, "span", 28);
105
+ i0.ɵɵelementStart(3, "span", 31);
73
106
  i0.ɵɵtext(4);
74
107
  i0.ɵɵelementEnd()();
75
- i0.ɵɵelementStart(5, "div", 26);
76
- i0.ɵɵelement(6, "i", 29);
108
+ i0.ɵɵelementStart(5, "div", 29);
109
+ i0.ɵɵelement(6, "i", 32);
77
110
  i0.ɵɵtext(7, " Clusters: ");
78
- i0.ɵɵelementStart(8, "span", 28);
111
+ i0.ɵɵelementStart(8, "span", 31);
79
112
  i0.ɵɵtext(9);
80
113
  i0.ɵɵelementEnd()();
81
- i0.ɵɵelementStart(10, "div", 26);
82
- i0.ɵɵelement(11, "i", 30);
114
+ i0.ɵɵelementStart(10, "div", 29);
115
+ i0.ɵɵelement(11, "i", 33);
83
116
  i0.ɵɵtext(12, " Silhouette: ");
84
- i0.ɵɵelementStart(13, "span", 28);
117
+ i0.ɵɵelementStart(13, "span", 31);
85
118
  i0.ɵɵtext(14);
86
119
  i0.ɵɵelementEnd()();
87
- i0.ɵɵelementStart(15, "div", 26);
88
- i0.ɵɵelement(16, "i", 31);
120
+ i0.ɵɵelementStart(15, "div", 29);
121
+ i0.ɵɵelement(16, "i", 34);
89
122
  i0.ɵɵtext(17, " Computed: ");
90
- i0.ɵɵelementStart(18, "span", 28);
123
+ i0.ɵɵelementStart(18, "span", 31);
91
124
  i0.ɵɵtext(19);
92
125
  i0.ɵɵelementEnd()();
93
126
  } if (rf & 2) {
94
- const ctx_r3 = i0.ɵɵnextContext();
127
+ const ctx_r2 = i0.ɵɵnextContext(2);
95
128
  i0.ɵɵadvance(4);
96
- i0.ɵɵtextInterpolate(ctx_r3.Result.Metrics.RecordCount);
129
+ i0.ɵɵtextInterpolate(ctx_r2.Result.Metrics.RecordCount);
97
130
  i0.ɵɵadvance(5);
98
- i0.ɵɵtextInterpolate(ctx_r3.Result.Metrics.ClusterCount);
131
+ i0.ɵɵtextInterpolate(ctx_r2.Result.Metrics.ClusterCount);
99
132
  i0.ɵɵadvance(4);
100
- i0.ɵɵclassProp("good", ctx_r3.IsSilhouetteGood);
133
+ i0.ɵɵclassProp("good", ctx_r2.IsSilhouetteGood);
101
134
  i0.ɵɵadvance();
102
- i0.ɵɵtextInterpolate1(" ", ctx_r3.SilhouetteScoreFormatted, " ");
135
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.SilhouetteScoreFormatted, " ");
103
136
  i0.ɵɵadvance(5);
104
- i0.ɵɵtextInterpolate(ctx_r3.ComputationTimeFormatted);
137
+ i0.ɵɵtextInterpolate(ctx_r2.ComputationTimeFormatted);
138
+ } }
139
+ function ClusterVisualizationResourceComponent_ng_template_2_Conditional_18_Template(rf, ctx) { if (rf & 1) {
140
+ i0.ɵɵelementStart(0, "div", 21);
141
+ i0.ɵɵelement(1, "i", 35);
142
+ i0.ɵɵelementStart(2, "span");
143
+ i0.ɵɵtext(3);
144
+ i0.ɵɵelementEnd()();
145
+ } if (rf & 2) {
146
+ const ctx_r2 = i0.ɵɵnextContext(2);
147
+ i0.ɵɵadvance(3);
148
+ i0.ɵɵtextInterpolate(ctx_r2.RunError);
149
+ } }
150
+ function ClusterVisualizationResourceComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) {
151
+ const _r4 = i0.ɵɵgetCurrentView();
152
+ i0.ɵɵelementStart(0, "div", 9)(1, "div", 10)(2, "div", 11)(3, "h2");
153
+ i0.ɵɵelement(4, "i", 12);
154
+ i0.ɵɵtext(5, " Saved Visualizations");
155
+ i0.ɵɵelementEnd()();
156
+ i0.ɵɵelementStart(6, "div", 13);
157
+ i0.ɵɵrepeaterCreate(7, ClusterVisualizationResourceComponent_ng_template_2_For_8_Template, 10, 5, "div", 14, i0.ɵɵcomponentInstance().TrackSavedBy, true);
158
+ i0.ɵɵconditionalCreate(9, ClusterVisualizationResourceComponent_ng_template_2_Conditional_9_Template, 4, 0, "div", 15);
159
+ i0.ɵɵelementEnd()();
160
+ i0.ɵɵelementStart(10, "div", 16)(11, "div", 17)(12, "div", 18);
161
+ i0.ɵɵtext(13);
162
+ i0.ɵɵelementEnd();
163
+ i0.ɵɵconditionalCreate(14, ClusterVisualizationResourceComponent_ng_template_2_Conditional_14_Template, 20, 6);
164
+ i0.ɵɵelementEnd();
165
+ i0.ɵɵelementStart(15, "div", 19)(16, "mj-cluster-scatter", 20, 1);
166
+ i0.ɵɵlistener("PointClicked", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_scatter_PointClicked_16_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnPointClicked($event)); })("PointHovered", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_scatter_PointHovered_16_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnPointHovered($event)); })("OpenRecordRequested", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_scatter_OpenRecordRequested_16_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnOpenRecord($event)); })("LabelEdited", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_scatter_LabelEdited_16_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnLabelEdited($event)); });
167
+ i0.ɵɵelementEnd();
168
+ i0.ɵɵconditionalCreate(18, ClusterVisualizationResourceComponent_ng_template_2_Conditional_18_Template, 4, 1, "div", 21);
169
+ i0.ɵɵelementStart(19, "mj-cluster-config-panel", 22);
170
+ i0.ɵɵlistener("RunClustering", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_config_panel_RunClustering_19_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnRunClustering($event)); })("DimensionsChanged", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_config_panel_DimensionsChanged_19_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnDimensionsChanged($event)); })("SaveVisualization", function ClusterVisualizationResourceComponent_ng_template_2_Template_mj_cluster_config_panel_SaveVisualization_19_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnSaveVisualization()); });
171
+ i0.ɵɵelementEnd()()()();
172
+ } if (rf & 2) {
173
+ const ctx_r2 = i0.ɵɵnextContext();
174
+ i0.ɵɵadvance(7);
175
+ i0.ɵɵrepeater(ctx_r2.SavedVisualizations);
176
+ i0.ɵɵadvance(2);
177
+ i0.ɵɵconditional(ctx_r2.SavedVisualizations.length === 0 ? 9 : -1);
178
+ i0.ɵɵadvance(4);
179
+ i0.ɵɵtextInterpolate(ctx_r2.VisualizationTitle);
180
+ i0.ɵɵadvance();
181
+ i0.ɵɵconditional(ctx_r2.HasResult ? 14 : -1);
182
+ i0.ɵɵadvance(2);
183
+ i0.ɵɵproperty("Points", (ctx_r2.Result == null ? null : ctx_r2.Result.Points) ?? i0.ɵɵpureFunction0(14, _c1))("Clusters", (ctx_r2.Result == null ? null : ctx_r2.Result.Clusters) ?? i0.ɵɵpureFunction0(15, _c1))("IsLoading", ctx_r2.IsRunning)("ColorBy", ctx_r2.ActiveConfig.ColorBy ?? "cluster")("EntityName", ctx_r2.ActiveConfig.EntityName);
184
+ i0.ɵɵadvance(2);
185
+ i0.ɵɵconditional(ctx_r2.RunError ? 18 : -1);
186
+ i0.ɵɵadvance();
187
+ i0.ɵɵproperty("IsRunning", ctx_r2.IsRunning)("Metrics", ctx_r2.Metrics)("EntityOptions", ctx_r2.EntityOptions)("EntityDocOptions", ctx_r2.EntityDocOptions)("AllEntityDocOptions", ctx_r2.AllEntityDocOptions);
105
188
  } }
106
189
  /**
107
190
  * Build an environment-scoped storage key so cluster data does not bleed
@@ -126,10 +209,20 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
126
209
  scatterPlot;
127
210
  cdr = inject(ChangeDetectorRef);
128
211
  clusteringService = inject(ClusteringService);
212
+ activityService = inject(ActivityService);
129
213
  navigationService = inject(NavigationService);
130
214
  destroy$ = new Subject();
131
215
  /** LLM-generated cluster labels for the current result */
132
216
  ClusterLabels = [];
217
+ /**
218
+ * When true, this component is embedded inside the Visualize host surface.
219
+ * In that case the host owns the resource lifecycle (NotifyLoadComplete,
220
+ * agent context) and record navigation, so this component suppresses those
221
+ * and instead emits open-record intents via {@link OpenRecordRequested}.
222
+ */
223
+ Embedded = false;
224
+ /** Emitted (only when Embedded) to ask the host to open an entity record. */
225
+ OpenRecordRequested = new EventEmitter();
133
226
  // ================================================================
134
227
  // Resource overrides
135
228
  // ================================================================
@@ -154,6 +247,10 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
154
247
  EntityOptions = [];
155
248
  /** Entity document options for the selected entity (shown when 2+) */
156
249
  EntityDocOptions = [];
250
+ /** All entity documents across entities (for the multi-entity source selector) */
251
+ AllEntityDocOptions = [];
252
+ /** User-facing error from the last run (e.g. multi-entity embedding mismatch) */
253
+ RunError = null;
157
254
  /** Ordered field keys for prioritized display in scatter tooltip/detail */
158
255
  FieldPriority = [];
159
256
  /** Map of field names to human-readable display names */
@@ -170,13 +267,17 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
170
267
  await this.loadEntityOptions();
171
268
  this.loadSavedVisualizations();
172
269
  this.restoreLastSession();
173
- this.navigationService.SetAgentContext(this, {
174
- IsVisualizationLoaded: !!this.Result,
175
- VisualizationTitle: this.VisualizationTitle || null,
176
- ClusterCount: this.Result?.Clusters?.length ?? 0,
177
- TotalPoints: this.Result?.Points?.length ?? 0,
178
- });
179
- this.NotifyLoadComplete();
270
+ // When embedded in the Visualize host, the host owns agent context +
271
+ // the resource load lifecycle; skip them here to avoid double-reporting.
272
+ if (!this.Embedded) {
273
+ this.navigationService.SetAgentContext(this, {
274
+ IsVisualizationLoaded: !!this.Result,
275
+ VisualizationTitle: this.VisualizationTitle || null,
276
+ ClusterCount: this.Result?.Clusters?.length ?? 0,
277
+ TotalPoints: this.Result?.Points?.length ?? 0,
278
+ });
279
+ this.NotifyLoadComplete();
280
+ }
180
281
  }
181
282
  ngOnDestroy() {
182
283
  super.ngOnDestroy();
@@ -210,16 +311,22 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
210
311
  this.IsRunning = true;
211
312
  this.ActiveConfig = config;
212
313
  this.ClusterLabels = [];
314
+ this.RunError = null;
213
315
  // Auto-hide detail panel from previous visualization
214
316
  this.scatterPlot?.CloseDetailPanel();
215
317
  // Update entity doc options if entity changed
216
318
  this.updateEntityDocOptions(config.EntityName);
217
319
  this.cdr.detectChanges();
320
+ const activityID = this.activityService.Start('Cluster analysis', {
321
+ icon: 'fa-solid fa-circle-nodes',
322
+ detail: `${config.EntityName || 'Multiple sources'} · ${config.Algorithm === 'kmeans' ? 'K-Means' : 'DBSCAN'}`,
323
+ });
218
324
  try {
219
325
  // Fetch vectors from the vector database
220
326
  const vectors = await this.fetchVectorsForEntity(config);
221
327
  // Run clustering (client-side UMAP + K-Means/DBSCAN)
222
328
  this.Result = await this.clusteringService.RunClustering(vectors, config);
329
+ this.activityService.Complete(activityID, 'success', `${this.Result.Points.length} points · ${this.Result.Clusters.length} clusters`);
223
330
  this.VisualizationTitle = `${config.EntityName} — ${config.Algorithm === 'kmeans' ? 'K-Means' : 'DBSCAN'}`;
224
331
  this.FieldPriority = this.ComputeFieldPriority(config.EntityName);
225
332
  // Fire LLM cluster naming in the background (non-blocking).
@@ -231,12 +338,25 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
231
338
  catch (error) {
232
339
  console.error('[ClusterVisualization] Pipeline error:', error);
233
340
  this.Result = null;
341
+ this.RunError = error instanceof Error ? error.message : String(error);
342
+ this.activityService.Complete(activityID, 'error', this.RunError);
234
343
  }
235
344
  finally {
236
345
  this.IsRunning = false;
237
346
  this.cdr.detectChanges();
238
347
  }
239
348
  }
349
+ /**
350
+ * Re-run when the user flips 2D⇄3D so the projection updates immediately.
351
+ * A 3D layout needs a Z coordinate that only a fresh projection produces, so
352
+ * toggling alone wouldn't change the existing plot.
353
+ */
354
+ OnDimensionsChanged(dims) {
355
+ this.ActiveConfig = { ...this.ActiveConfig, Dimensions: dims };
356
+ if (this.Result && this.Result.Points.length > 0 && !this.IsRunning) {
357
+ void this.OnRunClustering(this.ActiveConfig);
358
+ }
359
+ }
240
360
  /** Handle point click — log for now */
241
361
  OnPointClicked(_point) {
242
362
  // Detail panel is handled by the scatter component internally
@@ -251,6 +371,11 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
251
371
  const recordID = point.Metadata?.['RecordID'];
252
372
  if (!entityName || !recordID)
253
373
  return;
374
+ // When embedded, defer navigation to the host (shared drilldown owner).
375
+ if (this.Embedded) {
376
+ this.OpenRecordRequested.emit({ EntityName: entityName, RecordID: recordID });
377
+ return;
378
+ }
254
379
  const compositeKey = new CompositeKey();
255
380
  compositeKey.SimpleLoadFromURLSegment(recordID);
256
381
  this.navigationService.OpenEntityRecord(entityName, compositeKey);
@@ -354,6 +479,16 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
354
479
  if (entityNames.length > 0) {
355
480
  this.EntityOptions = entityNames.map(name => ({ Name: name }));
356
481
  }
482
+ // Build the cross-entity document list that powers the multi-entity
483
+ // source selector (each option tagged with its owning entity).
484
+ const allDocs = [];
485
+ for (const name of entityNames) {
486
+ const docs = engine.GetEntityDocumentsForEntity(name).filter(d => d.Status === 'Active');
487
+ for (const d of docs) {
488
+ allDocs.push({ ID: d.ID, Name: d.Name, EntityName: name });
489
+ }
490
+ }
491
+ this.AllEntityDocOptions = allDocs;
357
492
  // Set default entity if config is blank
358
493
  if (this.EntityOptions.length > 0 && !this.ActiveConfig.EntityName) {
359
494
  this.ActiveConfig.EntityName = this.EntityOptions[0].Name;
@@ -390,44 +525,66 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
390
525
  * that match the requested entity.
391
526
  */
392
527
  async fetchVectorsForEntity(config) {
393
- // Use the selected entity document, or fall back to first active one
394
- let entityDocID = config.EntityDocumentID;
395
- if (!entityDocID) {
396
- const engine = KnowledgeHubMetadataEngine.Instance;
397
- const entityDocs = engine.GetEntityDocumentsForEntity(config.EntityName)
398
- .filter(d => d.Status === 'Active');
399
- if (entityDocs.length === 0) {
400
- return [];
401
- }
402
- entityDocID = entityDocs[0].ID;
528
+ const docIDs = this.resolveDocIDs(config);
529
+ if (docIDs.length === 0) {
530
+ return [];
403
531
  }
404
- // Fetch vectors + metadata directly from the vector database (Pinecone)
532
+ const isMulti = docIDs.length > 1;
533
+ const docEntityMap = new Map(this.AllEntityDocOptions.map(d => [d.ID, d.EntityName ?? '']));
405
534
  const provider = this.ProviderToUse;
406
535
  const aiClient = new GraphQLAIClient(provider);
407
- const result = await aiClient.FetchEntityVectors({
408
- entityDocumentID: entityDocID,
409
- maxRecords: config.MaxRecords,
410
- filter: config.Filter || undefined,
411
- });
412
- if (!result.Success || result.Results.length === 0) {
413
- return [];
414
- }
415
- // Convert vector DB results to ClusterInputVector format
416
536
  const vectors = [];
417
- for (const item of result.Results) {
418
- if (!item.Values || item.Values.length === 0)
419
- continue;
420
- const metadata = this.parseVectorMetadata(item.Metadata);
421
- const label = this.buildLabel(metadata);
422
- vectors.push({
423
- Key: item.ID,
424
- Label: label,
425
- Vector: item.Values,
426
- Metadata: metadata,
537
+ let expectedLen = -1;
538
+ for (const docID of docIDs) {
539
+ const result = await aiClient.FetchEntityVectors({
540
+ entityDocumentID: docID,
541
+ maxRecords: config.MaxRecords,
542
+ filter: config.Filter || undefined,
427
543
  });
544
+ if (!result.Success || result.Results.length === 0)
545
+ continue;
546
+ for (const item of result.Results) {
547
+ if (!item.Values || item.Values.length === 0)
548
+ continue;
549
+ // Hard-block multi-entity embedding mismatches: vectors of different
550
+ // dimensionalities live in different spaces and aren't co-clusterable.
551
+ if (isMulti) {
552
+ if (expectedLen === -1) {
553
+ expectedLen = item.Values.length;
554
+ }
555
+ else if (item.Values.length !== expectedLen) {
556
+ throw new Error('The selected documents use different embedding models (vector sizes differ), ' +
557
+ 'so their points are not comparable. Pick documents that share the same embedding model.');
558
+ }
559
+ }
560
+ const metadata = this.parseVectorMetadata(item.Metadata);
561
+ // Ensure each point knows its source entity for color-by-entity.
562
+ if (isMulti && !metadata['EntityName']) {
563
+ metadata['EntityName'] = docEntityMap.get(docID) ?? metadata['Entity'] ?? '';
564
+ }
565
+ const label = this.buildLabel(metadata);
566
+ vectors.push({
567
+ Key: item.ID,
568
+ Label: label,
569
+ Vector: item.Values,
570
+ Metadata: metadata,
571
+ });
572
+ }
428
573
  }
429
574
  return vectors;
430
575
  }
576
+ /** Resolve which entity-document IDs to source vectors from for a run. */
577
+ resolveDocIDs(config) {
578
+ if (config.EntityDocumentIDs && config.EntityDocumentIDs.length > 0) {
579
+ return config.EntityDocumentIDs;
580
+ }
581
+ if (config.EntityDocumentID) {
582
+ return [config.EntityDocumentID];
583
+ }
584
+ const engine = KnowledgeHubMetadataEngine.Instance;
585
+ const docs = engine.GetEntityDocumentsForEntity(config.EntityName).filter(d => d.Status === 'Active');
586
+ return docs.length > 0 ? [docs[0].ID] : [];
587
+ }
431
588
  /** Parse the JSON metadata string from the vector DB into a record */
432
589
  parseVectorMetadata(metadataJson) {
433
590
  try {
@@ -724,49 +881,12 @@ let ClusterVisualizationResourceComponent = class ClusterVisualizationResourceCo
724
881
  } if (rf & 2) {
725
882
  let _t;
726
883
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.scatterPlot = _t.first);
727
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 27, vars: 15, consts: [["scatterPlot", ""], ["Title", "Cluster Visualization", "Icon", "fa-solid fa-circle-nodes", "Subtitle", "Explore semantic clusters in your knowledge base"], ["actions", ""], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "action-btn-label"], [3, "Flex", "Padding"], [1, "cluster-viz-container"], [1, "sidebar"], [1, "sidebar-header"], [1, "fa-solid", "fa-circle-nodes"], [1, "saved-list"], [1, "saved-item", 3, "active"], [1, "no-saved"], [1, "main-area"], [1, "topbar"], [1, "topbar-title"], [1, "scatter-area"], [3, "PointClicked", "PointHovered", "OpenRecordRequested", "LabelEdited", "Points", "Clusters", "IsLoading", "EntityName"], [3, "RunClustering", "SaveVisualization", "IsRunning", "Metrics", "EntityOptions", "EntityDocOptions"], [1, "saved-item", 3, "click"], [1, "saved-name"], [1, "saved-meta"], ["title", "Delete", 1, "delete-btn", 3, "click"], [1, "fa-solid", "fa-times"], [1, "fa-solid", "fa-bookmark"], [1, "metric"], [1, "fa-solid", "fa-circle-dot"], [1, "metric-value"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-chart-line"], [1, "fa-solid", "fa-clock"]], template: function ClusterVisualizationResourceComponent_Template(rf, ctx) { if (rf & 1) {
728
- const _r1 = i0.ɵɵgetCurrentView();
729
- i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 1)(2, "div", 2)(3, "button", 3);
730
- i0.ɵɵlistener("click", function ClusterVisualizationResourceComponent_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnNewAnalysis()); });
731
- i0.ɵɵelement(4, "i", 4);
732
- i0.ɵɵelementStart(5, "span", 5);
733
- i0.ɵɵtext(6, "New Analysis");
734
- i0.ɵɵelementEnd()()()();
735
- i0.ɵɵelementStart(7, "mj-page-body", 6)(8, "div", 7)(9, "div", 8)(10, "div", 9)(11, "h2");
736
- i0.ɵɵelement(12, "i", 10);
737
- i0.ɵɵtext(13, " Saved Clusters");
738
- i0.ɵɵelementEnd()();
739
- i0.ɵɵelementStart(14, "div", 11);
740
- i0.ɵɵrepeaterCreate(15, ClusterVisualizationResourceComponent_For_16_Template, 10, 5, "div", 12, ctx.TrackSavedBy, true);
741
- i0.ɵɵconditionalCreate(17, ClusterVisualizationResourceComponent_Conditional_17_Template, 4, 0, "div", 13);
742
- i0.ɵɵelementEnd()();
743
- i0.ɵɵelementStart(18, "div", 14)(19, "div", 15)(20, "div", 16);
744
- i0.ɵɵtext(21);
745
- i0.ɵɵelementEnd();
746
- i0.ɵɵconditionalCreate(22, ClusterVisualizationResourceComponent_Conditional_22_Template, 20, 6);
747
- i0.ɵɵelementEnd();
748
- i0.ɵɵelementStart(23, "div", 17)(24, "mj-cluster-scatter", 18, 0);
749
- i0.ɵɵlistener("PointClicked", function ClusterVisualizationResourceComponent_Template_mj_cluster_scatter_PointClicked_24_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnPointClicked($event)); })("PointHovered", function ClusterVisualizationResourceComponent_Template_mj_cluster_scatter_PointHovered_24_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnPointHovered($event)); })("OpenRecordRequested", function ClusterVisualizationResourceComponent_Template_mj_cluster_scatter_OpenRecordRequested_24_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnOpenRecord($event)); })("LabelEdited", function ClusterVisualizationResourceComponent_Template_mj_cluster_scatter_LabelEdited_24_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnLabelEdited($event)); });
750
- i0.ɵɵelementEnd();
751
- i0.ɵɵelementStart(26, "mj-cluster-config-panel", 19);
752
- i0.ɵɵlistener("RunClustering", function ClusterVisualizationResourceComponent_Template_mj_cluster_config_panel_RunClustering_26_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnRunClustering($event)); })("SaveVisualization", function ClusterVisualizationResourceComponent_Template_mj_cluster_config_panel_SaveVisualization_26_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnSaveVisualization()); });
753
- i0.ɵɵelementEnd()()()()()();
884
+ } }, inputs: { Embedded: "Embedded" }, outputs: { OpenRecordRequested: "OpenRecordRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [["clusterBody", ""], ["scatterPlot", ""], [4, "ngTemplateOutlet"], ["Title", "Cluster Visualization", "Icon", "fa-solid fa-circle-nodes", "Subtitle", "Explore semantic clusters in your knowledge base"], ["actions", ""], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "action-btn-label"], [3, "Flex", "Padding"], [1, "cluster-viz-container"], [1, "sidebar"], [1, "sidebar-header"], [1, "fa-solid", "fa-circle-nodes"], [1, "saved-list"], [1, "saved-item", 3, "active"], [1, "no-saved"], [1, "main-area"], [1, "topbar"], [1, "topbar-title"], [1, "scatter-area"], [3, "PointClicked", "PointHovered", "OpenRecordRequested", "LabelEdited", "Points", "Clusters", "IsLoading", "ColorBy", "EntityName"], [1, "cluster-run-error"], [3, "RunClustering", "DimensionsChanged", "SaveVisualization", "IsRunning", "Metrics", "EntityOptions", "EntityDocOptions", "AllEntityDocOptions"], [1, "saved-item", 3, "click"], [1, "saved-name"], [1, "saved-meta"], ["title", "Delete", 1, "delete-btn", 3, "click"], [1, "fa-solid", "fa-times"], [1, "fa-solid", "fa-bookmark"], [1, "metric"], [1, "fa-solid", "fa-circle-dot"], [1, "metric-value"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-chart-line"], [1, "fa-solid", "fa-clock"], [1, "fa-solid", "fa-triangle-exclamation"]], template: function ClusterVisualizationResourceComponent_Template(rf, ctx) { if (rf & 1) {
885
+ i0.ɵɵconditionalCreate(0, ClusterVisualizationResourceComponent_Conditional_0_Template, 1, 1, "ng-container")(1, ClusterVisualizationResourceComponent_Conditional_1_Template, 9, 3, "mj-page-layout");
886
+ i0.ɵɵtemplate(2, ClusterVisualizationResourceComponent_ng_template_2_Template, 20, 16, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
754
887
  } if (rf & 2) {
755
- i0.ɵɵadvance(7);
756
- i0.ɵɵproperty("Flex", true)("Padding", false);
757
- i0.ɵɵadvance(8);
758
- i0.ɵɵrepeater(ctx.SavedVisualizations);
759
- i0.ɵɵadvance(2);
760
- i0.ɵɵconditional(ctx.SavedVisualizations.length === 0 ? 17 : -1);
761
- i0.ɵɵadvance(4);
762
- i0.ɵɵtextInterpolate(ctx.VisualizationTitle);
763
- i0.ɵɵadvance();
764
- i0.ɵɵconditional(ctx.HasResult ? 22 : -1);
765
- i0.ɵɵadvance(2);
766
- i0.ɵɵproperty("Points", (ctx.Result == null ? null : ctx.Result.Points) ?? i0.ɵɵpureFunction0(13, _c1))("Clusters", (ctx.Result == null ? null : ctx.Result.Clusters) ?? i0.ɵɵpureFunction0(14, _c1))("IsLoading", ctx.IsRunning)("EntityName", ctx.ActiveConfig.EntityName);
767
- i0.ɵɵadvance(2);
768
- i0.ɵɵproperty("IsRunning", ctx.IsRunning)("Metrics", ctx.Metrics)("EntityOptions", ctx.EntityOptions)("EntityDocOptions", ctx.EntityDocOptions);
769
- } }, dependencies: [i1.MJButtonDirective, i1.MJPageHeaderComponent, i1.MJPageLayoutComponent, i1.MJPageBodyComponent, i2.ClusterScatterComponent, i2.ClusterConfigPanelComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.cluster-viz-container[_ngcontent-%COMP%] {\n display: flex;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n\n\n.sidebar[_ngcontent-%COMP%] {\n width: 260px;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.sidebar-header[_ngcontent-%COMP%] {\n padding: 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.sidebar-header[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n font-size: 0.95rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n color: var(--mj-text-primary);\n}\n\n.sidebar-header[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.saved-list[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n\n.saved-item[_ngcontent-%COMP%] {\n padding: 12px 14px;\n border-radius: 8px;\n cursor: pointer;\n margin-bottom: 4px;\n transition: background 0.15s;\n position: relative;\n}\n\n.saved-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.saved-item.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.saved-name[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n font-weight: 500;\n margin-bottom: 3px;\n color: var(--mj-text-primary);\n padding-right: 20px;\n}\n\n.saved-meta[_ngcontent-%COMP%] {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n display: flex;\n gap: 10px;\n}\n\n.delete-btn[_ngcontent-%COMP%] {\n position: absolute;\n top: 10px;\n right: 10px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.7rem;\n padding: 2px 4px;\n opacity: 0;\n transition: opacity 0.15s, color 0.15s;\n}\n\n.saved-item[_ngcontent-%COMP%]:hover .delete-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.delete-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-status-error);\n}\n\n.no-saved[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\n\n.no-saved[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.5rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.no-saved[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n margin: 0;\n}\n\n.new-btn[_ngcontent-%COMP%] {\n margin: 12px;\n padding: 10px;\n border: 1px dashed var(--mj-border-strong);\n border-radius: 8px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.new-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n\n.main-area[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n\n\n.topbar[_ngcontent-%COMP%] {\n height: 48px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 20px;\n gap: 24px;\n flex-shrink: 0;\n}\n\n.topbar-title[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n margin-right: auto;\n color: var(--mj-text-primary);\n}\n\n.metric[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.metric[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.7rem;\n}\n\n.metric-value[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.metric-value.good[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n\n\n.scatter-area[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n overflow: hidden;\n}"] });
888
+ i0.ɵɵconditional(ctx.Embedded ? 0 : 1);
889
+ } }, dependencies: [i1.NgTemplateOutlet, i2.MJButtonDirective, i2.MJPageHeaderComponent, i2.MJPageLayoutComponent, i2.MJPageBodyComponent, i3.ClusterScatterComponent, i3.ClusterConfigPanelComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.cluster-viz-container[_ngcontent-%COMP%] {\n display: flex;\n flex: 1;\n \n\n\n\n\n\n height: 100%;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n\n\n.sidebar[_ngcontent-%COMP%] {\n width: 260px;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.sidebar-header[_ngcontent-%COMP%] {\n padding: 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.sidebar-header[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n font-size: 0.95rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n color: var(--mj-text-primary);\n}\n\n.sidebar-header[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.saved-list[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n\n.saved-item[_ngcontent-%COMP%] {\n padding: 12px 14px;\n border-radius: 8px;\n cursor: pointer;\n margin-bottom: 4px;\n transition: background 0.15s;\n position: relative;\n}\n\n.saved-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.saved-item.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.saved-name[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n font-weight: 500;\n margin-bottom: 3px;\n color: var(--mj-text-primary);\n padding-right: 20px;\n}\n\n.saved-meta[_ngcontent-%COMP%] {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n display: flex;\n gap: 10px;\n}\n\n.delete-btn[_ngcontent-%COMP%] {\n position: absolute;\n top: 10px;\n right: 10px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.7rem;\n padding: 2px 4px;\n opacity: 0;\n transition: opacity 0.15s, color 0.15s;\n}\n\n.saved-item[_ngcontent-%COMP%]:hover .delete-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.delete-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-status-error);\n}\n\n.no-saved[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\n\n.no-saved[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.5rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.no-saved[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n margin: 0;\n}\n\n.new-btn[_ngcontent-%COMP%] {\n margin: 12px;\n padding: 10px;\n border: 1px dashed var(--mj-border-strong);\n border-radius: 8px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.new-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n\n\n\n\n\n.main-area[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n\n\n.topbar[_ngcontent-%COMP%] {\n height: 48px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 20px;\n gap: 24px;\n flex-shrink: 0;\n}\n\n.topbar-title[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n margin-right: auto;\n color: var(--mj-text-primary);\n}\n\n.metric[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.metric[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.7rem;\n}\n\n.metric-value[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.metric-value.good[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n\n\n.cluster-run-error[_ngcontent-%COMP%] {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n max-width: 70%;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n color: var(--mj-status-error-text);\n border-radius: 8px;\n font-size: 0.82rem;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n\n\n.scatter-area[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n\n\n.scatter-area[_ngcontent-%COMP%] > mj-cluster-scatter[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n display: block;\n overflow: hidden;\n}\n\n\n\n\n\n\n.scatter-area[_ngcontent-%COMP%] > mj-cluster-config-panel[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 10;\n}\n.scatter-area[_ngcontent-%COMP%] .config-panel {\n pointer-events: auto;\n max-height: calc(100% - 24px);\n overflow-y: auto;\n overscroll-behavior: contain;\n}"] });
770
890
  };
771
891
  ClusterVisualizationResourceComponent = __decorate([
772
892
  RegisterClass(BaseResourceComponent, 'ClusterVisualizationResource')
@@ -774,10 +894,14 @@ ClusterVisualizationResourceComponent = __decorate([
774
894
  export { ClusterVisualizationResourceComponent };
775
895
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusterVisualizationResourceComponent, [{
776
896
  type: Component,
777
- args: [{ standalone: false, selector: 'app-cluster-visualization-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Cluster Visualization\"\n Icon=\"fa-solid fa-circle-nodes\"\n Subtitle=\"Explore semantic clusters in your knowledge base\">\n <div actions>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewAnalysis()\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"action-btn-label\">New Analysis</span>\n </button>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n<div class=\"cluster-viz-container\">\n <!-- Left Sidebar: Saved Visualizations -->\n <div class=\"sidebar\">\n <div class=\"sidebar-header\">\n <h2><i class=\"fa-solid fa-circle-nodes\"></i> Saved Clusters</h2>\n </div>\n <div class=\"saved-list\">\n @for (saved of SavedVisualizations; track TrackSavedBy($index, saved)) {\n <div class=\"saved-item\"\n [class.active]=\"IsActiveSaved(saved)\"\n (click)=\"OnSelectSaved(saved)\">\n <div class=\"saved-name\">{{ saved.Name }}</div>\n <div class=\"saved-meta\">\n <span>{{ saved.EntityName }}</span>\n <span>{{ FormatDate(saved.CreatedAt) }}</span>\n </div>\n <button class=\"delete-btn\"\n title=\"Delete\"\n (click)=\"OnDeleteSaved(saved, $event)\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n\n @if (SavedVisualizations.length === 0) {\n <div class=\"no-saved\">\n <i class=\"fa-solid fa-bookmark\"></i>\n <p>No saved visualizations yet</p>\n </div>\n }\n </div>\n </div>\n\n <!-- Main Area -->\n <div class=\"main-area\">\n <!-- Top metrics bar -->\n <div class=\"topbar\">\n <div class=\"topbar-title\">{{ VisualizationTitle }}</div>\n @if (HasResult) {\n <div class=\"metric\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n Records: <span class=\"metric-value\">{{ Result!.Metrics.RecordCount }}</span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-layer-group\"></i>\n Clusters: <span class=\"metric-value\">{{ Result!.Metrics.ClusterCount }}</span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-chart-line\"></i>\n Silhouette:\n <span class=\"metric-value\" [class.good]=\"IsSilhouetteGood\">\n {{ SilhouetteScoreFormatted }}\n </span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-clock\"></i>\n Computed: <span class=\"metric-value\">{{ ComputationTimeFormatted }}</span>\n </div>\n }\n </div>\n\n <!-- Scatter plot area -->\n <div class=\"scatter-area\">\n <mj-cluster-scatter\n #scatterPlot\n [Points]=\"Result?.Points ?? []\"\n [Clusters]=\"Result?.Clusters ?? []\"\n [IsLoading]=\"IsRunning\"\n [EntityName]=\"ActiveConfig.EntityName\"\n (PointClicked)=\"OnPointClicked($event)\"\n (PointHovered)=\"OnPointHovered($event)\"\n (OpenRecordRequested)=\"OnOpenRecord($event)\"\n (LabelEdited)=\"OnLabelEdited($event)\">\n </mj-cluster-scatter>\n\n <!-- Floating config panel -->\n <mj-cluster-config-panel\n [IsRunning]=\"IsRunning\"\n [Metrics]=\"Metrics\"\n [EntityOptions]=\"EntityOptions\"\n [EntityDocOptions]=\"EntityDocOptions\"\n (RunClustering)=\"OnRunClustering($event)\"\n (SaveVisualization)=\"OnSaveVisualization()\">\n </mj-cluster-config-panel>\n </div>\n </div>\n</div>\n </mj-page-body>\n</mj-page-layout>\n", styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.cluster-viz-container {\n display: flex;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ============================================================\n Left Sidebar\n ============================================================ */\n\n.sidebar {\n width: 260px;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.sidebar-header {\n padding: 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.sidebar-header h2 {\n font-size: 0.95rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n color: var(--mj-text-primary);\n}\n\n.sidebar-header h2 i {\n color: var(--mj-brand-primary);\n}\n\n.saved-list {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n\n.saved-item {\n padding: 12px 14px;\n border-radius: 8px;\n cursor: pointer;\n margin-bottom: 4px;\n transition: background 0.15s;\n position: relative;\n}\n\n.saved-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.saved-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.saved-name {\n font-size: 0.85rem;\n font-weight: 500;\n margin-bottom: 3px;\n color: var(--mj-text-primary);\n padding-right: 20px;\n}\n\n.saved-meta {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n display: flex;\n gap: 10px;\n}\n\n.delete-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.7rem;\n padding: 2px 4px;\n opacity: 0;\n transition: opacity 0.15s, color 0.15s;\n}\n\n.saved-item:hover .delete-btn {\n opacity: 1;\n}\n\n.delete-btn:hover {\n color: var(--mj-status-error);\n}\n\n.no-saved {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\n\n.no-saved i {\n font-size: 1.5rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.no-saved p {\n font-size: 0.8rem;\n margin: 0;\n}\n\n.new-btn {\n margin: 12px;\n padding: 10px;\n border: 1px dashed var(--mj-border-strong);\n border-radius: 8px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.new-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n/* ============================================================\n Main Area\n ============================================================ */\n\n.main-area {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n/* Top bar */\n.topbar {\n height: 48px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 20px;\n gap: 24px;\n flex-shrink: 0;\n}\n\n.topbar-title {\n font-size: 0.9rem;\n font-weight: 600;\n margin-right: auto;\n color: var(--mj-text-primary);\n}\n\n.metric {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.metric i {\n font-size: 0.7rem;\n}\n\n.metric-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.metric-value.good {\n color: var(--mj-status-success);\n}\n\n/* Scatter area */\n.scatter-area {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n"] }]
897
+ args: [{ standalone: false, selector: 'app-cluster-visualization-resource', template: "@if (Embedded) {\n <ng-container *ngTemplateOutlet=\"clusterBody\"></ng-container>\n} @else {\n<mj-page-layout>\n <mj-page-header\n Title=\"Cluster Visualization\"\n Icon=\"fa-solid fa-circle-nodes\"\n Subtitle=\"Explore semantic clusters in your knowledge base\">\n <div actions>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewAnalysis()\">\n <i class=\"fa-solid fa-plus\"></i> <span class=\"action-btn-label\">New Analysis</span>\n </button>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n <ng-container *ngTemplateOutlet=\"clusterBody\"></ng-container>\n </mj-page-body>\n</mj-page-layout>\n}\n\n<ng-template #clusterBody>\n<div class=\"cluster-viz-container\">\n <!-- Left Sidebar: Saved Visualizations -->\n <div class=\"sidebar\">\n <div class=\"sidebar-header\">\n <h2><i class=\"fa-solid fa-circle-nodes\"></i> Saved Visualizations</h2>\n </div>\n <div class=\"saved-list\">\n @for (saved of SavedVisualizations; track TrackSavedBy($index, saved)) {\n <div class=\"saved-item\"\n [class.active]=\"IsActiveSaved(saved)\"\n (click)=\"OnSelectSaved(saved)\">\n <div class=\"saved-name\">{{ saved.Name }}</div>\n <div class=\"saved-meta\">\n <span>{{ saved.EntityName }}</span>\n <span>{{ FormatDate(saved.CreatedAt) }}</span>\n </div>\n <button class=\"delete-btn\"\n title=\"Delete\"\n (click)=\"OnDeleteSaved(saved, $event)\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n\n @if (SavedVisualizations.length === 0) {\n <div class=\"no-saved\">\n <i class=\"fa-solid fa-bookmark\"></i>\n <p>No saved visualizations yet</p>\n </div>\n }\n </div>\n </div>\n\n <!-- Main Area -->\n <div class=\"main-area\">\n <!-- Top metrics bar -->\n <div class=\"topbar\">\n <div class=\"topbar-title\">{{ VisualizationTitle }}</div>\n @if (HasResult) {\n <div class=\"metric\">\n <i class=\"fa-solid fa-circle-dot\"></i>\n Records: <span class=\"metric-value\">{{ Result!.Metrics.RecordCount }}</span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-layer-group\"></i>\n Clusters: <span class=\"metric-value\">{{ Result!.Metrics.ClusterCount }}</span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-chart-line\"></i>\n Silhouette:\n <span class=\"metric-value\" [class.good]=\"IsSilhouetteGood\">\n {{ SilhouetteScoreFormatted }}\n </span>\n </div>\n <div class=\"metric\">\n <i class=\"fa-solid fa-clock\"></i>\n Computed: <span class=\"metric-value\">{{ ComputationTimeFormatted }}</span>\n </div>\n }\n </div>\n\n <!-- Scatter plot area -->\n <div class=\"scatter-area\">\n <mj-cluster-scatter\n #scatterPlot\n [Points]=\"Result?.Points ?? []\"\n [Clusters]=\"Result?.Clusters ?? []\"\n [IsLoading]=\"IsRunning\"\n [ColorBy]=\"ActiveConfig.ColorBy ?? 'cluster'\"\n [EntityName]=\"ActiveConfig.EntityName\"\n (PointClicked)=\"OnPointClicked($event)\"\n (PointHovered)=\"OnPointHovered($event)\"\n (OpenRecordRequested)=\"OnOpenRecord($event)\"\n (LabelEdited)=\"OnLabelEdited($event)\">\n </mj-cluster-scatter>\n\n <!-- Run error banner (e.g. multi-entity embedding mismatch) -->\n @if (RunError) {\n <div class=\"cluster-run-error\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ RunError }}</span>\n </div>\n }\n\n <!-- Floating config panel -->\n <mj-cluster-config-panel\n [IsRunning]=\"IsRunning\"\n [Metrics]=\"Metrics\"\n [EntityOptions]=\"EntityOptions\"\n [EntityDocOptions]=\"EntityDocOptions\"\n [AllEntityDocOptions]=\"AllEntityDocOptions\"\n (RunClustering)=\"OnRunClustering($event)\"\n (DimensionsChanged)=\"OnDimensionsChanged($event)\"\n (SaveVisualization)=\"OnSaveVisualization()\">\n </mj-cluster-config-panel>\n </div>\n </div>\n</div>\n</ng-template>\n", styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.cluster-viz-container {\n display: flex;\n flex: 1;\n /* height:100% so we fill the host whether the parent is a flex container\n (non-embedded, inside mj-page-body[Flex]) OR a plain block host\n (embedded inside Visualize, where :host is display:block and flex:1\n would otherwise be ignored, collapsing the area and clipping the\n floating config panel). */\n height: 100%;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ============================================================\n Left Sidebar\n ============================================================ */\n\n.sidebar {\n width: 260px;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.sidebar-header {\n padding: 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.sidebar-header h2 {\n font-size: 0.95rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n color: var(--mj-text-primary);\n}\n\n.sidebar-header h2 i {\n color: var(--mj-brand-primary);\n}\n\n.saved-list {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n\n.saved-item {\n padding: 12px 14px;\n border-radius: 8px;\n cursor: pointer;\n margin-bottom: 4px;\n transition: background 0.15s;\n position: relative;\n}\n\n.saved-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.saved-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.saved-name {\n font-size: 0.85rem;\n font-weight: 500;\n margin-bottom: 3px;\n color: var(--mj-text-primary);\n padding-right: 20px;\n}\n\n.saved-meta {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n display: flex;\n gap: 10px;\n}\n\n.delete-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.7rem;\n padding: 2px 4px;\n opacity: 0;\n transition: opacity 0.15s, color 0.15s;\n}\n\n.saved-item:hover .delete-btn {\n opacity: 1;\n}\n\n.delete-btn:hover {\n color: var(--mj-status-error);\n}\n\n.no-saved {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\n\n.no-saved i {\n font-size: 1.5rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.no-saved p {\n font-size: 0.8rem;\n margin: 0;\n}\n\n.new-btn {\n margin: 12px;\n padding: 10px;\n border: 1px dashed var(--mj-border-strong);\n border-radius: 8px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.new-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n/* ============================================================\n Main Area\n ============================================================ */\n\n.main-area {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n/* Top bar */\n.topbar {\n height: 48px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 20px;\n gap: 24px;\n flex-shrink: 0;\n}\n\n.topbar-title {\n font-size: 0.9rem;\n font-weight: 600;\n margin-right: auto;\n color: var(--mj-text-primary);\n}\n\n.metric {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.metric i {\n font-size: 0.7rem;\n}\n\n.metric-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.metric-value.good {\n color: var(--mj-status-success);\n}\n\n/* Run error banner */\n.cluster-run-error {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n max-width: 70%;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n color: var(--mj-status-error-text);\n border-radius: 8px;\n font-size: 0.82rem;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n/* Scatter area */\n.scatter-area {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n/* The scatter SVG / empty-state should fill and be clipped to the area \u2026 */\n.scatter-area > mj-cluster-scatter {\n position: absolute;\n inset: 0;\n display: block;\n overflow: hidden;\n}\n\n/* \u2026 but the floating Configuration panel must never be clipped by the area's\n overflow. Constrain its height to the visible area and let its own body scroll\n when the Filter (Optional) section makes it tall. (Panel lives in the generic\n ng-clustering package; we only adjust its containment from here.) */\n.scatter-area > mj-cluster-config-panel {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 10;\n}\n.scatter-area ::ng-deep .config-panel {\n pointer-events: auto;\n max-height: calc(100% - 24px);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n"] }]
778
898
  }], null, { scatterPlot: [{
779
899
  type: ViewChild,
780
900
  args: ['scatterPlot']
901
+ }], Embedded: [{
902
+ type: Input
903
+ }], OpenRecordRequested: [{
904
+ type: Output
781
905
  }] }); })();
782
906
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ClusterVisualizationResourceComponent, { className: "ClusterVisualizationResourceComponent", filePath: "src/KnowledgeHub/components/clusters/cluster-visualization-resource.component.ts", lineNumber: 63 }); })();
783
907
  /** Tree-shaking prevention */