@memberjunction/ng-dashboards 5.39.0 → 5.40.1

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
@@ -0,0 +1,362 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var VisualizeResourceComponent_1;
8
+ /**
9
+ * @fileoverview Knowledge Hub "Visualize" Resource (host surface)
10
+ *
11
+ * Phase 5 host surface that replaces the standalone "Clusters" tab. It hosts two
12
+ * visualization modes behind an internal segmented control:
13
+ * - "Clusters" — the existing cluster scatter visualization (embedded child).
14
+ * - "Tag Cloud" — a weighted tag cloud driven by TagCloudEngine.
15
+ *
16
+ * Both modes feed ONE shared record-drilldown panel:
17
+ * - cluster point selection → a single record opened/listed.
18
+ * - tag-cloud word selection → the records carrying that tag (via RunView).
19
+ *
20
+ * The host owns the resource lifecycle (NotifyLoadComplete), agent context
21
+ * (ActiveVisualizationMode), and all navigation — the embedded children emit
22
+ * open-record intents up to here.
23
+ */
24
+ import { Component, ChangeDetectorRef, inject, ViewChild } from '@angular/core';
25
+ import { Subject } from 'rxjs';
26
+ import { CompositeKey, RunView } from '@memberjunction/core';
27
+ import { UserInfoEngine } from '@memberjunction/core-entities';
28
+ import { RegisterClass } from '@memberjunction/global';
29
+ import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
30
+ import * as i0 from "@angular/core";
31
+ import * as i1 from "@memberjunction/ng-ui-components";
32
+ import * as i2 from "../clusters/cluster-visualization-resource.component";
33
+ import * as i3 from "./tag-cloud/tag-cloud.component";
34
+ import * as i4 from "./record-drilldown/record-drilldown.component";
35
+ const _c0 = ["tagCloud"];
36
+ function VisualizeResourceComponent_For_5_Template(rf, ctx) { if (rf & 1) {
37
+ const _r1 = i0.ɵɵgetCurrentView();
38
+ i0.ɵɵelementStart(0, "button", 11);
39
+ i0.ɵɵlistener("click", function VisualizeResourceComponent_For_5_Template_button_click_0_listener() { const mode_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.SelectMode(mode_r2.ID)); });
40
+ i0.ɵɵelement(1, "i");
41
+ i0.ɵɵelementStart(2, "span");
42
+ i0.ɵɵtext(3);
43
+ i0.ɵɵelementEnd()();
44
+ } if (rf & 2) {
45
+ const mode_r2 = ctx.$implicit;
46
+ const ctx_r2 = i0.ɵɵnextContext();
47
+ i0.ɵɵclassProp("active", ctx_r2.IsActiveMode(mode_r2.ID));
48
+ i0.ɵɵattribute("aria-selected", ctx_r2.IsActiveMode(mode_r2.ID));
49
+ i0.ɵɵadvance();
50
+ i0.ɵɵclassMap(mode_r2.Icon);
51
+ i0.ɵɵadvance(2);
52
+ i0.ɵɵtextInterpolate(mode_r2.Label);
53
+ } }
54
+ function VisualizeResourceComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
55
+ const _r4 = i0.ɵɵgetCurrentView();
56
+ i0.ɵɵelementStart(0, "app-cluster-visualization-resource", 12);
57
+ i0.ɵɵlistener("OpenRecordRequested", function VisualizeResourceComponent_Conditional_9_Template_app_cluster_visualization_resource_OpenRecordRequested_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnClusterOpenRecord($event)); });
58
+ i0.ɵɵelementEnd();
59
+ } if (rf & 2) {
60
+ i0.ɵɵproperty("Embedded", true);
61
+ } }
62
+ function VisualizeResourceComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
63
+ const _r5 = i0.ɵɵgetCurrentView();
64
+ i0.ɵɵelementStart(0, "app-kh-tag-cloud", 13, 0);
65
+ i0.ɵɵlistener("TagSelected", function VisualizeResourceComponent_Conditional_10_Template_app_kh_tag_cloud_TagSelected_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnTagSelected($event)); });
66
+ i0.ɵɵelementEnd();
67
+ } if (rf & 2) {
68
+ const ctx_r2 = i0.ɵɵnextContext();
69
+ i0.ɵɵproperty("Provider", ctx_r2.Provider);
70
+ } }
71
+ let VisualizeResourceComponent = class VisualizeResourceComponent extends BaseResourceComponent {
72
+ static { VisualizeResourceComponent_1 = this; }
73
+ tagCloud;
74
+ cdr = inject(ChangeDetectorRef);
75
+ navigationService = inject(NavigationService);
76
+ destroy$ = new Subject();
77
+ // ================================================================
78
+ // Resource overrides
79
+ // ================================================================
80
+ async GetResourceDisplayName(_data) {
81
+ return 'Visualize';
82
+ }
83
+ async GetResourceIconClass(_data) {
84
+ return 'fa-solid fa-circle-nodes';
85
+ }
86
+ // ================================================================
87
+ // Mode switch
88
+ // ================================================================
89
+ Modes = [
90
+ { ID: 'clusters', Label: 'Clusters', Icon: 'fa-solid fa-circle-nodes' },
91
+ { ID: 'tagcloud', Label: 'Tag Cloud', Icon: 'fa-solid fa-cloud' },
92
+ ];
93
+ ActiveMode = 'clusters';
94
+ // ================================================================
95
+ // Shared drilldown state
96
+ // ================================================================
97
+ DrilldownVisible = false;
98
+ DrilldownTitle = '';
99
+ DrilldownSubtitle = '';
100
+ DrilldownIcon = 'fa-solid fa-list';
101
+ DrilldownLoading = false;
102
+ DrilldownRecords = [];
103
+ // ================================================================
104
+ // Preferences
105
+ // ================================================================
106
+ /** Current preference key for the active mode. */
107
+ static PREFS_KEY = 'KH_Visualize_Mode';
108
+ /** Legacy key (pre-rename) — read for back-compat only. */
109
+ static LEGACY_PREFS_KEY = 'KH_Clusters_ActiveTab';
110
+ // ================================================================
111
+ // Lifecycle
112
+ // ================================================================
113
+ async ngAfterViewInit() {
114
+ await UserInfoEngine.Instance.Config(false);
115
+ this.loadModePreference();
116
+ this.emitAgentContext();
117
+ this.registerAgentTools();
118
+ // loadModePreference() may flip ActiveMode AFTER the view's first CD pass
119
+ // (it runs post-`await`), which swaps the `@if (IsActiveMode('clusters'))`
120
+ // branch and the embedded child's count bindings mid-cycle — surfacing an
121
+ // NG0100. Flush once here so the mode is settled before the next checked render.
122
+ this.cdr.detectChanges();
123
+ this.NotifyLoadComplete();
124
+ }
125
+ ngOnDestroy() {
126
+ super.ngOnDestroy();
127
+ this.destroy$.next();
128
+ this.destroy$.complete();
129
+ }
130
+ // ================================================================
131
+ // Mode switching
132
+ // ================================================================
133
+ SelectMode(mode) {
134
+ if (this.ActiveMode === mode) {
135
+ return;
136
+ }
137
+ this.ActiveMode = mode;
138
+ this.CloseDrilldown();
139
+ this.persistModePreference();
140
+ this.emitAgentContext();
141
+ this.cdr.detectChanges();
142
+ }
143
+ IsActiveMode(mode) {
144
+ return this.ActiveMode === mode;
145
+ }
146
+ TrackByMode(_index, mode) {
147
+ return mode.ID;
148
+ }
149
+ // ================================================================
150
+ // Drilldown — shared by both modes
151
+ // ================================================================
152
+ /** Cluster mode: open the underlying entity record directly. */
153
+ OnClusterOpenRecord(request) {
154
+ this.openEntityRecord(request.EntityName, request.RecordID);
155
+ }
156
+ /** Tag-cloud mode: list the records carrying the clicked tag in the drilldown. */
157
+ async OnTagSelected(selection) {
158
+ this.DrilldownVisible = true;
159
+ this.DrilldownLoading = true;
160
+ this.DrilldownIcon = 'fa-solid fa-tag';
161
+ this.DrilldownTitle = selection.Tag;
162
+ this.DrilldownSubtitle = `${selection.Count} tagged ${selection.Count === 1 ? 'record' : 'records'}`;
163
+ this.DrilldownRecords = [];
164
+ this.cdr.detectChanges();
165
+ try {
166
+ this.DrilldownRecords = await this.loadRecordsForTag(selection.Tag, selection.Scope);
167
+ }
168
+ catch (error) {
169
+ console.error('[Visualize] Error loading tag records:', error);
170
+ this.DrilldownRecords = [];
171
+ }
172
+ finally {
173
+ this.DrilldownLoading = false;
174
+ this.cdr.detectChanges();
175
+ }
176
+ }
177
+ /** Drilldown open-record intent (from the shared panel). */
178
+ OnDrilldownOpenRecord(request) {
179
+ this.openEntityRecord(request.EntityName, request.RecordID);
180
+ }
181
+ CloseDrilldown() {
182
+ this.DrilldownVisible = false;
183
+ this.DrilldownRecords = [];
184
+ this.DrilldownLoading = false;
185
+ this.cdr.detectChanges();
186
+ }
187
+ // ================================================================
188
+ // Private helpers
189
+ // ================================================================
190
+ /**
191
+ * Load the content-item records carrying a given tag, scoped to the same
192
+ * content-source / content-type the cloud was built with. Each returned row
193
+ * is a `MJ: Content Items` record the user can open.
194
+ */
195
+ async loadRecordsForTag(tag, scope) {
196
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
197
+ const user = this.ProviderToUse.CurrentUser;
198
+ // 1) Tag rows for this tag text → ItemID + Weight.
199
+ const safeTag = tag.replace(/'/g, "''");
200
+ const tagResult = await rv.RunView({
201
+ EntityName: 'MJ: Content Item Tags',
202
+ ExtraFilter: `Tag = '${safeTag}'`,
203
+ Fields: ['ItemID', 'Item', 'Weight'],
204
+ MaxRows: 200,
205
+ ResultType: 'simple',
206
+ }, user);
207
+ if (!tagResult.Success || tagResult.Results.length === 0) {
208
+ return [];
209
+ }
210
+ // De-dupe by ItemID, keep the highest weight seen.
211
+ const byItem = new Map();
212
+ for (const row of tagResult.Results) {
213
+ const existing = byItem.get(row.ItemID);
214
+ if (!existing || row.Weight > existing.weight) {
215
+ byItem.set(row.ItemID, { item: row.Item, weight: row.Weight });
216
+ }
217
+ }
218
+ const itemIDs = Array.from(byItem.keys());
219
+ // 2) Resolve content-item details, narrowed by scope when present.
220
+ const itemFilter = this.buildItemFilter(itemIDs, scope);
221
+ const itemResult = await rv.RunView({
222
+ EntityName: 'MJ: Content Items',
223
+ ExtraFilter: itemFilter,
224
+ Fields: ['ID', 'Name', 'ContentSource', 'ContentType'],
225
+ MaxRows: 200,
226
+ ResultType: 'simple',
227
+ }, user);
228
+ if (!itemResult.Success) {
229
+ return [];
230
+ }
231
+ return itemResult.Results.map(item => {
232
+ const tagged = byItem.get(item.ID);
233
+ const subtitleParts = [item.ContentType, item.ContentSource].filter(p => !!p);
234
+ return {
235
+ EntityName: 'MJ: Content Items',
236
+ RecordID: item.ID,
237
+ Title: item.Name || tagged?.item || 'Untitled',
238
+ Subtitle: subtitleParts.join(' · '),
239
+ Weight: tagged?.weight,
240
+ };
241
+ }).sort((a, b) => (b.Weight ?? 0) - (a.Weight ?? 0));
242
+ }
243
+ /** Build the `MJ: Content Items` filter: ID IN (...) plus optional scope. */
244
+ buildItemFilter(itemIDs, scope) {
245
+ const fragments = [];
246
+ if (itemIDs.length > 0) {
247
+ const quoted = itemIDs.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
248
+ fragments.push(`ID IN (${quoted})`);
249
+ }
250
+ if (scope.ContentSourceIDs && scope.ContentSourceIDs.length > 0) {
251
+ const quoted = scope.ContentSourceIDs.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
252
+ fragments.push(`ContentSourceID IN (${quoted})`);
253
+ }
254
+ if (scope.ContentTypeIDs && scope.ContentTypeIDs.length > 0) {
255
+ const quoted = scope.ContentTypeIDs.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
256
+ fragments.push(`ContentTypeID IN (${quoted})`);
257
+ }
258
+ return fragments.join(' AND ');
259
+ }
260
+ /** Open an entity record via NavigationService (single navigation path). */
261
+ openEntityRecord(entityName, recordID) {
262
+ if (!entityName || !recordID) {
263
+ return;
264
+ }
265
+ const md = this.ProviderToUse;
266
+ const entityInfo = md.EntityByName(entityName);
267
+ const pkey = new CompositeKey();
268
+ if (entityInfo) {
269
+ pkey.LoadFromURLSegment(entityInfo, recordID);
270
+ }
271
+ else {
272
+ pkey.KeyValuePairs = [{ FieldName: 'ID', Value: recordID }];
273
+ }
274
+ this.navigationService.OpenEntityRecord(entityName, pkey);
275
+ }
276
+ // ================================================================
277
+ // Agent context + tools
278
+ // ================================================================
279
+ emitAgentContext() {
280
+ this.navigationService.SetAgentContext(this, {
281
+ ActiveVisualizationMode: this.ActiveMode,
282
+ });
283
+ }
284
+ registerAgentTools() {
285
+ this.navigationService.SetAgentClientTools(this, [
286
+ {
287
+ Name: 'SwitchVisualizationMode',
288
+ Description: 'Switch the Visualize surface mode (clusters or tagcloud)',
289
+ ParameterSchema: {
290
+ type: 'object',
291
+ properties: { mode: { type: 'string', enum: ['clusters', 'tagcloud'] } },
292
+ required: ['mode'],
293
+ },
294
+ Handler: async (params) => {
295
+ const mode = params['mode'];
296
+ if (mode === 'clusters' || mode === 'tagcloud') {
297
+ this.SelectMode(mode);
298
+ return { Success: true };
299
+ }
300
+ return { Success: false };
301
+ },
302
+ },
303
+ ]);
304
+ }
305
+ // ================================================================
306
+ // Preferences (with legacy back-compat)
307
+ // ================================================================
308
+ persistModePreference() {
309
+ UserInfoEngine.Instance.SetSettingDebounced(VisualizeResourceComponent_1.PREFS_KEY, this.ActiveMode);
310
+ }
311
+ loadModePreference() {
312
+ // Prefer the new key; fall back to the legacy "Clusters" tab key.
313
+ const raw = UserInfoEngine.Instance.GetSetting(VisualizeResourceComponent_1.PREFS_KEY)
314
+ ?? UserInfoEngine.Instance.GetSetting(VisualizeResourceComponent_1.LEGACY_PREFS_KEY);
315
+ if (raw === 'clusters' || raw === 'tagcloud') {
316
+ this.ActiveMode = raw;
317
+ }
318
+ }
319
+ static ɵfac = /*@__PURE__*/ (() => { let ɵVisualizeResourceComponent_BaseFactory; return function VisualizeResourceComponent_Factory(__ngFactoryType__) { return (ɵVisualizeResourceComponent_BaseFactory || (ɵVisualizeResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(VisualizeResourceComponent)))(__ngFactoryType__ || VisualizeResourceComponent); }; })();
320
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: VisualizeResourceComponent, selectors: [["app-visualize-resource"]], viewQuery: function VisualizeResourceComponent_Query(rf, ctx) { if (rf & 1) {
321
+ i0.ɵɵviewQuery(_c0, 5);
322
+ } if (rf & 2) {
323
+ let _t;
324
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.tagCloud = _t.first);
325
+ } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 12, vars: 9, consts: [["tagCloud", ""], ["Title", "Visualize", "Icon", "fa-solid fa-circle-nodes", "Subtitle", "Explore your knowledge base as clusters or a tag cloud"], ["toolbar", ""], ["role", "tablist", 1, "mode-switch"], ["type", "button", "role", "tab", 1, "mode-switch-btn", 3, "active"], [3, "Flex", "Padding"], [1, "visualize-container"], [1, "visualize-main"], [3, "Embedded"], [3, "Provider"], [3, "Closed", "OpenRecord", "Visible", "IsLoading", "Title", "Subtitle", "Icon", "Records"], ["type", "button", "role", "tab", 1, "mode-switch-btn", 3, "click"], [3, "OpenRecordRequested", "Embedded"], [3, "TagSelected", "Provider"]], template: function VisualizeResourceComponent_Template(rf, ctx) { if (rf & 1) {
326
+ i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 1)(2, "div", 2)(3, "div", 3);
327
+ i0.ɵɵrepeaterCreate(4, VisualizeResourceComponent_For_5_Template, 4, 6, "button", 4, ctx.TrackByMode, true);
328
+ i0.ɵɵelementEnd()()();
329
+ i0.ɵɵelementStart(6, "mj-page-body", 5)(7, "div", 6)(8, "div", 7);
330
+ i0.ɵɵconditionalCreate(9, VisualizeResourceComponent_Conditional_9_Template, 1, 1, "app-cluster-visualization-resource", 8)(10, VisualizeResourceComponent_Conditional_10_Template, 2, 1, "app-kh-tag-cloud", 9);
331
+ i0.ɵɵelementEnd();
332
+ i0.ɵɵelementStart(11, "app-kh-record-drilldown", 10);
333
+ i0.ɵɵlistener("Closed", function VisualizeResourceComponent_Template_app_kh_record_drilldown_Closed_11_listener() { return ctx.CloseDrilldown(); })("OpenRecord", function VisualizeResourceComponent_Template_app_kh_record_drilldown_OpenRecord_11_listener($event) { return ctx.OnDrilldownOpenRecord($event); });
334
+ i0.ɵɵelementEnd()()()();
335
+ } if (rf & 2) {
336
+ i0.ɵɵadvance(4);
337
+ i0.ɵɵrepeater(ctx.Modes);
338
+ i0.ɵɵadvance(2);
339
+ i0.ɵɵproperty("Flex", true)("Padding", false);
340
+ i0.ɵɵadvance(3);
341
+ i0.ɵɵconditional(ctx.IsActiveMode("clusters") ? 9 : 10);
342
+ i0.ɵɵadvance(2);
343
+ i0.ɵɵproperty("Visible", ctx.DrilldownVisible)("IsLoading", ctx.DrilldownLoading)("Title", ctx.DrilldownTitle)("Subtitle", ctx.DrilldownSubtitle)("Icon", ctx.DrilldownIcon)("Records", ctx.DrilldownRecords);
344
+ } }, dependencies: [i1.MJPageHeaderComponent, i1.MJPageLayoutComponent, i1.MJPageBodyComponent, i2.ClusterVisualizationResourceComponent, i3.TagCloudComponent, i4.RecordDrilldownComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n}\n\n\n\n.mode-switch[_ngcontent-%COMP%] {\n display: inline-flex;\n gap: 2px;\n padding: 3px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.mode-switch-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 600;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.mode-switch-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.mode-switch-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n\n\n.visualize-container[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n min-height: 0;\n width: 100%;\n}\n\n.visualize-main[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.visualize-main[_ngcontent-%COMP%] > app-cluster-visualization-resource[_ngcontent-%COMP%], \n.visualize-main[_ngcontent-%COMP%] > app-kh-tag-cloud[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n display: block;\n}"] });
345
+ };
346
+ VisualizeResourceComponent = VisualizeResourceComponent_1 = __decorate([
347
+ RegisterClass(BaseResourceComponent, 'VisualizationResource')
348
+ ], VisualizeResourceComponent);
349
+ export { VisualizeResourceComponent };
350
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(VisualizeResourceComponent, [{
351
+ type: Component,
352
+ args: [{ standalone: false, selector: 'app-visualize-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Visualize\"\n Icon=\"fa-solid fa-circle-nodes\"\n Subtitle=\"Explore your knowledge base as clusters or a tag cloud\">\n <div toolbar>\n <div class=\"mode-switch\" role=\"tablist\">\n @for (mode of Modes; track TrackByMode($index, mode)) {\n <button\n type=\"button\"\n class=\"mode-switch-btn\"\n role=\"tab\"\n [class.active]=\"IsActiveMode(mode.ID)\"\n [attr.aria-selected]=\"IsActiveMode(mode.ID)\"\n (click)=\"SelectMode(mode.ID)\">\n <i [class]=\"mode.Icon\"></i>\n <span>{{ mode.Label }}</span>\n </button>\n }\n </div>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n <div class=\"visualize-container\">\n <div class=\"visualize-main\">\n @if (IsActiveMode('clusters')) {\n <app-cluster-visualization-resource\n [Embedded]=\"true\"\n (OpenRecordRequested)=\"OnClusterOpenRecord($event)\">\n </app-cluster-visualization-resource>\n } @else {\n <app-kh-tag-cloud\n #tagCloud\n [Provider]=\"Provider\"\n (TagSelected)=\"OnTagSelected($event)\">\n </app-kh-tag-cloud>\n }\n </div>\n\n <app-kh-record-drilldown\n [Visible]=\"DrilldownVisible\"\n [IsLoading]=\"DrilldownLoading\"\n [Title]=\"DrilldownTitle\"\n [Subtitle]=\"DrilldownSubtitle\"\n [Icon]=\"DrilldownIcon\"\n [Records]=\"DrilldownRecords\"\n (Closed)=\"CloseDrilldown()\"\n (OpenRecord)=\"OnDrilldownOpenRecord($event)\">\n </app-kh-record-drilldown>\n </div>\n </mj-page-body>\n</mj-page-layout>\n", styles: [":host {\n display: block;\n height: 100%;\n}\n\n/* Segmented mode switch in the header toolbar */\n.mode-switch {\n display: inline-flex;\n gap: 2px;\n padding: 3px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.mode-switch-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 13px;\n font-weight: 600;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.mode-switch-btn:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.mode-switch-btn.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n/* Body layout: main visualization + drilldown side panel */\n.visualize-container {\n display: flex;\n height: 100%;\n min-height: 0;\n width: 100%;\n}\n\n.visualize-main {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.visualize-main > app-cluster-visualization-resource,\n.visualize-main > app-kh-tag-cloud {\n flex: 1;\n min-height: 0;\n display: block;\n}\n"] }]
353
+ }], null, { tagCloud: [{
354
+ type: ViewChild,
355
+ args: ['tagCloud']
356
+ }] }); })();
357
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(VisualizeResourceComponent, { className: "VisualizeResourceComponent", filePath: "src/KnowledgeHub/components/visualize/visualize-resource.component.ts", lineNumber: 44 }); })();
358
+ /** Tree-shaking prevention */
359
+ export function LoadVisualizeResource() {
360
+ // Prevents tree-shaking of the component
361
+ }
362
+ //# sourceMappingURL=visualize-resource.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visualize-resource.component.js","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/visualize/visualize-resource.component.ts","../../../../src/KnowledgeHub/components/visualize/visualize-resource.component.html"],"names":[],"mappings":";;;;;;;AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAA4B,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1G,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAgB,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;;;;;;;;;ICd3E,kCAMgC;IAA9B,gNAAS,6BAAmB,KAAC;IAC7B,oBAA2B;IAC3B,4BAAM;IAAA,YAAgB;IACxB,AADwB,iBAAO,EACtB;;;;IALP,yDAAsC;;IAGnC,cAAmB;IAAnB,2BAAmB;IAChB,eAAgB;IAAhB,mCAAgB;;;;IAWxB,8DAEsD;IAApD,4PAAuB,kCAA2B,KAAC;IACrD,iBAAqC;;IAFnC,+BAAiB;;;;IAInB,+CAGwC;IAAtC,2NAAe,4BAAqB,KAAC;IACvC,iBAAmB;;;IAFjB,0CAAqB;;ADS1B,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,qBAAqB;;IAC1C,QAAQ,CAAqB;IAE5C,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACrB,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9C,QAAQ,GAAG,IAAI,OAAO,EAAQ,CAAC;IAElD,mEAAmE;IACnE,qBAAqB;IACrB,mEAAmE;IAEnE,KAAK,CAAC,sBAAsB,CAAC,KAAmB;QAC5C,OAAO,WAAW,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,KAAmB;QAC1C,OAAO,0BAA0B,CAAC;IACtC,CAAC;IAED,mEAAmE;IACnE,cAAc;IACd,mEAAmE;IAE5D,KAAK,GAAiB;QACzB,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,0BAA0B,EAAE;QACvE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE;KACpE,CAAC;IAEK,UAAU,GAAsB,UAAU,CAAC;IAElD,mEAAmE;IACnE,yBAAyB;IACzB,mEAAmE;IAE5D,gBAAgB,GAAG,KAAK,CAAC;IACzB,cAAc,GAAG,EAAE,CAAC;IACpB,iBAAiB,GAAG,EAAE,CAAC;IACvB,aAAa,GAAG,kBAAkB,CAAC;IACnC,gBAAgB,GAAG,KAAK,CAAC;IACzB,gBAAgB,GAAsB,EAAE,CAAC;IAEhD,mEAAmE;IACnE,cAAc;IACd,mEAAmE;IAEnE,kDAAkD;IAC1C,MAAM,CAAU,SAAS,GAAG,mBAAmB,CAAC;IACxD,2DAA2D;IACnD,MAAM,CAAU,gBAAgB,GAAG,uBAAuB,CAAC;IAEnE,mEAAmE;IACnE,YAAY;IACZ,mEAAmE;IAEnE,KAAK,CAAC,eAAe;QACjB,MAAM,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,0EAA0E;QAC1E,2EAA2E;QAC3E,0EAA0E;QAC1E,iFAAiF;QACjF,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAED,WAAW;QACP,KAAK,CAAC,WAAW,EAAE,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,mEAAmE;IACnE,iBAAiB;IACjB,mEAAmE;IAE5D,UAAU,CAAC,IAAuB;QACrC,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC3B,OAAO;QACX,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC7B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC7B,CAAC;IAEM,YAAY,CAAC,IAAuB;QACvC,OAAO,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC;IACpC,CAAC;IAEM,WAAW,CAAC,MAAc,EAAE,IAAgB;QAC/C,OAAO,IAAI,CAAC,EAAE,CAAC;IACnB,CAAC;IAED,mEAAmE;IACnE,mCAAmC;IACnC,mEAAmE;IAEnE,gEAAgE;IACzD,mBAAmB,CAAC,OAAiD;QACxE,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,CAAC;IAED,kFAAkF;IAC3E,KAAK,CAAC,aAAa,CAAC,SAA4B;QACnD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,iBAAiB,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC,GAAG,CAAC;QACpC,IAAI,CAAC,iBAAiB,GAAG,GAAG,SAAS,CAAC,KAAK,WAAW,SAAS,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACrG,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACD,IAAI,CAAC,gBAAgB,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACzF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,4DAA4D;IACrD,qBAAqB,CAAC,OAA6B;QACtD,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,CAAC;IAEM,cAAc;QACjB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAC9B,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC7B,CAAC;IAED,mEAAmE;IACnE,kBAAkB;IAClB,mEAAmE;IAEnE;;;;OAIG;IACK,KAAK,CAAC,iBAAiB,CAAC,GAAW,EAAE,KAAoB;QAC7D,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC;QAE5C,mDAAmD;QACnD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAA0D;YACxF,UAAU,EAAE,uBAAuB;YACnC,WAAW,EAAE,UAAU,OAAO,GAAG;YACjC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACpC,OAAO,EAAE,GAAG;YACZ,UAAU,EAAE,QAAQ;SACvB,EAAE,IAAI,CAAC,CAAC;QAET,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvD,OAAO,EAAE,CAAC;QACd,CAAC;QAED,mDAAmD;QACnD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmD,CAAC;QAC1E,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAC5C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACnE,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAE1C,mEAAmE;QACnE,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAEhC;YACC,UAAU,EAAE,mBAAmB;YAC/B,WAAW,EAAE,UAAU;YACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,CAAC;YACtD,OAAO,EAAE,GAAG;YACZ,UAAU,EAAE,QAAQ;SACvB,EAAE,IAAI,CAAC,CAAC;QAET,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACd,CAAC;QAED,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACjC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnC,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9E,OAAO;gBACH,UAAU,EAAE,mBAAmB;gBAC/B,QAAQ,EAAE,IAAI,CAAC,EAAE;gBACjB,KAAK,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM,EAAE,IAAI,IAAI,UAAU;gBAC9C,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC;gBACnC,MAAM,EAAE,MAAM,EAAE,MAAM;aACN,CAAC;QACzB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,6EAA6E;IACrE,eAAe,CAAC,OAAiB,EAAE,KAAoB;QAC3D,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3E,SAAS,CAAC,IAAI,CAAC,UAAU,MAAM,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1F,SAAS,CAAC,IAAI,CAAC,uBAAuB,MAAM,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,KAAK,CAAC,cAAc,IAAI,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxF,SAAS,CAAC,IAAI,CAAC,qBAAqB,MAAM,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,4EAA4E;IACpE,gBAAgB,CAAC,UAAkB,EAAE,QAAgB;QACzD,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,OAAO;QACX,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QAC9B,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,aAAa,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED,mEAAmE;IACnE,wBAAwB;IACxB,mEAAmE;IAE3D,gBAAgB;QACpB,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,IAAI,EAAE;YACzC,uBAAuB,EAAE,IAAI,CAAC,UAAU;SAC3C,CAAC,CAAC;IACP,CAAC;IAEO,kBAAkB;QACtB,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,IAAI,EAAE;YAC7C;gBACI,IAAI,EAAE,yBAAyB;gBAC/B,WAAW,EAAE,0DAA0D;gBACvE,eAAe,EAAE;oBACb,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE;oBACxE,QAAQ,EAAE,CAAC,MAAM,CAAC;iBACrB;gBACD,OAAO,EAAE,KAAK,EAAE,MAA+B,EAAE,EAAE;oBAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAsB,CAAC;oBACjD,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;wBAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;wBACtB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;oBAC7B,CAAC;oBACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;gBAC9B,CAAC;aACJ;SACJ,CAAC,CAAC;IACP,CAAC;IAED,mEAAmE;IACnE,wCAAwC;IACxC,mEAAmE;IAE3D,qBAAqB;QACzB,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CAAC,4BAA0B,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACvG,CAAC;IAEO,kBAAkB;QACtB,kEAAkE;QAClE,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,4BAA0B,CAAC,SAAS,CAAC;eAC7E,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,4BAA0B,CAAC,gBAAgB,CAAC,CAAC;QACvF,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YAC3C,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,CAAC;IACL,CAAC;qRA7RQ,0BAA0B,yBAA1B,0BAA0B;6DAA1B,0BAA0B;;;;;;YCrCjC,AADF,AAJF,AADF,sCAAgB,wBAIsD,aACrD,aAC6B;YACtC,2GAWC;YAGP,AADE,AADE,iBAAM,EACF,EACS;YAIb,AADF,AADF,uCAA8C,aACX,aACH;YAMxB,AALF,2HAAgC,qFAKvB;YAOX,iBAAM;YAEN,oDAQ+C;YAA7C,AADA,2HAAU,oBAAgB,IAAC,4HACb,iCAA6B,IAAC;YAIpD,AADE,AADE,AADE,iBAA0B,EACtB,EACO,EACA;;YA7CT,eAWC;YAXD,wBAWC;YAKO,eAAa;YAAC,AAAd,2BAAa,kBAAkB;YAGvC,eAWC;YAXD,uDAWC;YAID,eAA4B;YAK5B,AADA,AADA,AADA,AADA,AADA,8CAA4B,mCACE,6BACN,mCACM,2BACR,iCACM;;;ADHvB,0BAA0B;IAPtC,aAAa,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;GAOjD,0BAA0B,CA8RtC;;iFA9RY,0BAA0B;cANtC,SAAS;6BACM,KAAK,YACP,wBAAwB;;kBAKjC,SAAS;mBAAC,UAAU;;kFADZ,0BAA0B;AAgSvC,8BAA8B;AAC9B,MAAM,UAAU,qBAAqB;IACjC,yCAAyC;AAC7C,CAAC","sourcesContent":["/**\n * @fileoverview Knowledge Hub \"Visualize\" Resource (host surface)\n *\n * Phase 5 host surface that replaces the standalone \"Clusters\" tab. It hosts two\n * visualization modes behind an internal segmented control:\n * - \"Clusters\" — the existing cluster scatter visualization (embedded child).\n * - \"Tag Cloud\" — a weighted tag cloud driven by TagCloudEngine.\n *\n * Both modes feed ONE shared record-drilldown panel:\n * - cluster point selection → a single record opened/listed.\n * - tag-cloud word selection → the records carrying that tag (via RunView).\n *\n * The host owns the resource lifecycle (NotifyLoadComplete), agent context\n * (ActiveVisualizationMode), and all navigation — the embedded children emit\n * open-record intents up to here.\n */\n\nimport { Component, ChangeDetectorRef, OnDestroy, AfterViewInit, inject, ViewChild } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { CompositeKey, RunView } from '@memberjunction/core';\nimport { ResourceData, UserInfoEngine } from '@memberjunction/core-entities';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';\nimport { TagCloudScope } from '@memberjunction/tag-engine-base';\nimport { TagCloudComponent, TagCloudSelection } from './tag-cloud/tag-cloud.component';\nimport { DrilldownRecord, DrilldownOpenRequest } from './record-drilldown/record-drilldown.component';\n\n/** Visualization modes hosted by this surface. */\nexport type VisualizationMode = 'clusters' | 'tagcloud';\n\ninterface ModeOption {\n ID: VisualizationMode;\n Label: string;\n Icon: string;\n}\n\n@RegisterClass(BaseResourceComponent, 'VisualizationResource')\n@Component({\n standalone: false,\n selector: 'app-visualize-resource',\n templateUrl: './visualize-resource.component.html',\n styleUrls: ['./visualize-resource.component.css'],\n})\nexport class VisualizeResourceComponent extends BaseResourceComponent implements AfterViewInit, OnDestroy {\n @ViewChild('tagCloud') tagCloud?: TagCloudComponent;\n\n private cdr = inject(ChangeDetectorRef);\n protected override navigationService = inject(NavigationService);\n protected override destroy$ = new Subject<void>();\n\n // ================================================================\n // Resource overrides\n // ================================================================\n\n async GetResourceDisplayName(_data: ResourceData): Promise<string> {\n return 'Visualize';\n }\n\n async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-circle-nodes';\n }\n\n // ================================================================\n // Mode switch\n // ================================================================\n\n public Modes: ModeOption[] = [\n { ID: 'clusters', Label: 'Clusters', Icon: 'fa-solid fa-circle-nodes' },\n { ID: 'tagcloud', Label: 'Tag Cloud', Icon: 'fa-solid fa-cloud' },\n ];\n\n public ActiveMode: VisualizationMode = 'clusters';\n\n // ================================================================\n // Shared drilldown state\n // ================================================================\n\n public DrilldownVisible = false;\n public DrilldownTitle = '';\n public DrilldownSubtitle = '';\n public DrilldownIcon = 'fa-solid fa-list';\n public DrilldownLoading = false;\n public DrilldownRecords: DrilldownRecord[] = [];\n\n // ================================================================\n // Preferences\n // ================================================================\n\n /** Current preference key for the active mode. */\n private static readonly PREFS_KEY = 'KH_Visualize_Mode';\n /** Legacy key (pre-rename) — read for back-compat only. */\n private static readonly LEGACY_PREFS_KEY = 'KH_Clusters_ActiveTab';\n\n // ================================================================\n // Lifecycle\n // ================================================================\n\n async ngAfterViewInit(): Promise<void> {\n await UserInfoEngine.Instance.Config(false);\n this.loadModePreference();\n this.emitAgentContext();\n this.registerAgentTools();\n // loadModePreference() may flip ActiveMode AFTER the view's first CD pass\n // (it runs post-`await`), which swaps the `@if (IsActiveMode('clusters'))`\n // branch and the embedded child's count bindings mid-cycle — surfacing an\n // NG0100. Flush once here so the mode is settled before the next checked render.\n this.cdr.detectChanges();\n this.NotifyLoadComplete();\n }\n\n ngOnDestroy(): void {\n super.ngOnDestroy();\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ================================================================\n // Mode switching\n // ================================================================\n\n public SelectMode(mode: VisualizationMode): void {\n if (this.ActiveMode === mode) {\n return;\n }\n this.ActiveMode = mode;\n this.CloseDrilldown();\n this.persistModePreference();\n this.emitAgentContext();\n this.cdr.detectChanges();\n }\n\n public IsActiveMode(mode: VisualizationMode): boolean {\n return this.ActiveMode === mode;\n }\n\n public TrackByMode(_index: number, mode: ModeOption): string {\n return mode.ID;\n }\n\n // ================================================================\n // Drilldown — shared by both modes\n // ================================================================\n\n /** Cluster mode: open the underlying entity record directly. */\n public OnClusterOpenRecord(request: { EntityName: string; RecordID: string }): void {\n this.openEntityRecord(request.EntityName, request.RecordID);\n }\n\n /** Tag-cloud mode: list the records carrying the clicked tag in the drilldown. */\n public async OnTagSelected(selection: TagCloudSelection): Promise<void> {\n this.DrilldownVisible = true;\n this.DrilldownLoading = true;\n this.DrilldownIcon = 'fa-solid fa-tag';\n this.DrilldownTitle = selection.Tag;\n this.DrilldownSubtitle = `${selection.Count} tagged ${selection.Count === 1 ? 'record' : 'records'}`;\n this.DrilldownRecords = [];\n this.cdr.detectChanges();\n\n try {\n this.DrilldownRecords = await this.loadRecordsForTag(selection.Tag, selection.Scope);\n } catch (error) {\n console.error('[Visualize] Error loading tag records:', error);\n this.DrilldownRecords = [];\n } finally {\n this.DrilldownLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n /** Drilldown open-record intent (from the shared panel). */\n public OnDrilldownOpenRecord(request: DrilldownOpenRequest): void {\n this.openEntityRecord(request.EntityName, request.RecordID);\n }\n\n public CloseDrilldown(): void {\n this.DrilldownVisible = false;\n this.DrilldownRecords = [];\n this.DrilldownLoading = false;\n this.cdr.detectChanges();\n }\n\n // ================================================================\n // Private helpers\n // ================================================================\n\n /**\n * Load the content-item records carrying a given tag, scoped to the same\n * content-source / content-type the cloud was built with. Each returned row\n * is a `MJ: Content Items` record the user can open.\n */\n private async loadRecordsForTag(tag: string, scope: TagCloudScope): Promise<DrilldownRecord[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const user = this.ProviderToUse.CurrentUser;\n\n // 1) Tag rows for this tag text → ItemID + Weight.\n const safeTag = tag.replace(/'/g, \"''\");\n const tagResult = await rv.RunView<{ ItemID: string; Item: string | null; Weight: number }>({\n EntityName: 'MJ: Content Item Tags',\n ExtraFilter: `Tag = '${safeTag}'`,\n Fields: ['ItemID', 'Item', 'Weight'],\n MaxRows: 200,\n ResultType: 'simple',\n }, user);\n\n if (!tagResult.Success || tagResult.Results.length === 0) {\n return [];\n }\n\n // De-dupe by ItemID, keep the highest weight seen.\n const byItem = new Map<string, { item: string | null; weight: number }>();\n for (const row of tagResult.Results) {\n const existing = byItem.get(row.ItemID);\n if (!existing || row.Weight > existing.weight) {\n byItem.set(row.ItemID, { item: row.Item, weight: row.Weight });\n }\n }\n const itemIDs = Array.from(byItem.keys());\n\n // 2) Resolve content-item details, narrowed by scope when present.\n const itemFilter = this.buildItemFilter(itemIDs, scope);\n const itemResult = await rv.RunView<{\n ID: string; Name: string | null; ContentSource: string | null; ContentType: string;\n }>({\n EntityName: 'MJ: Content Items',\n ExtraFilter: itemFilter,\n Fields: ['ID', 'Name', 'ContentSource', 'ContentType'],\n MaxRows: 200,\n ResultType: 'simple',\n }, user);\n\n if (!itemResult.Success) {\n return [];\n }\n\n return itemResult.Results.map(item => {\n const tagged = byItem.get(item.ID);\n const subtitleParts = [item.ContentType, item.ContentSource].filter(p => !!p);\n return {\n EntityName: 'MJ: Content Items',\n RecordID: item.ID,\n Title: item.Name || tagged?.item || 'Untitled',\n Subtitle: subtitleParts.join(' · '),\n Weight: tagged?.weight,\n } as DrilldownRecord;\n }).sort((a, b) => (b.Weight ?? 0) - (a.Weight ?? 0));\n }\n\n /** Build the `MJ: Content Items` filter: ID IN (...) plus optional scope. */\n private buildItemFilter(itemIDs: string[], scope: TagCloudScope): string {\n const fragments: string[] = [];\n if (itemIDs.length > 0) {\n const quoted = itemIDs.map(id => `'${id.replace(/'/g, \"''\")}'`).join(', ');\n fragments.push(`ID IN (${quoted})`);\n }\n if (scope.ContentSourceIDs && scope.ContentSourceIDs.length > 0) {\n const quoted = scope.ContentSourceIDs.map(id => `'${id.replace(/'/g, \"''\")}'`).join(', ');\n fragments.push(`ContentSourceID IN (${quoted})`);\n }\n if (scope.ContentTypeIDs && scope.ContentTypeIDs.length > 0) {\n const quoted = scope.ContentTypeIDs.map(id => `'${id.replace(/'/g, \"''\")}'`).join(', ');\n fragments.push(`ContentTypeID IN (${quoted})`);\n }\n return fragments.join(' AND ');\n }\n\n /** Open an entity record via NavigationService (single navigation path). */\n private openEntityRecord(entityName: string, recordID: string): void {\n if (!entityName || !recordID) {\n return;\n }\n const md = this.ProviderToUse;\n const entityInfo = md.EntityByName(entityName);\n const pkey = new CompositeKey();\n if (entityInfo) {\n pkey.LoadFromURLSegment(entityInfo, recordID);\n } else {\n pkey.KeyValuePairs = [{ FieldName: 'ID', Value: recordID }];\n }\n this.navigationService.OpenEntityRecord(entityName, pkey);\n }\n\n // ================================================================\n // Agent context + tools\n // ================================================================\n\n private emitAgentContext(): void {\n this.navigationService.SetAgentContext(this, {\n ActiveVisualizationMode: this.ActiveMode,\n });\n }\n\n private registerAgentTools(): void {\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'SwitchVisualizationMode',\n Description: 'Switch the Visualize surface mode (clusters or tagcloud)',\n ParameterSchema: {\n type: 'object',\n properties: { mode: { type: 'string', enum: ['clusters', 'tagcloud'] } },\n required: ['mode'],\n },\n Handler: async (params: Record<string, unknown>) => {\n const mode = params['mode'] as VisualizationMode;\n if (mode === 'clusters' || mode === 'tagcloud') {\n this.SelectMode(mode);\n return { Success: true };\n }\n return { Success: false };\n },\n },\n ]);\n }\n\n // ================================================================\n // Preferences (with legacy back-compat)\n // ================================================================\n\n private persistModePreference(): void {\n UserInfoEngine.Instance.SetSettingDebounced(VisualizeResourceComponent.PREFS_KEY, this.ActiveMode);\n }\n\n private loadModePreference(): void {\n // Prefer the new key; fall back to the legacy \"Clusters\" tab key.\n const raw = UserInfoEngine.Instance.GetSetting(VisualizeResourceComponent.PREFS_KEY)\n ?? UserInfoEngine.Instance.GetSetting(VisualizeResourceComponent.LEGACY_PREFS_KEY);\n if (raw === 'clusters' || raw === 'tagcloud') {\n this.ActiveMode = raw;\n }\n }\n}\n\n/** Tree-shaking prevention */\nexport function LoadVisualizeResource(): void {\n // Prevents tree-shaking of the component\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Visualize\"\n Icon=\"fa-solid fa-circle-nodes\"\n Subtitle=\"Explore your knowledge base as clusters or a tag cloud\">\n <div toolbar>\n <div class=\"mode-switch\" role=\"tablist\">\n @for (mode of Modes; track TrackByMode($index, mode)) {\n <button\n type=\"button\"\n class=\"mode-switch-btn\"\n role=\"tab\"\n [class.active]=\"IsActiveMode(mode.ID)\"\n [attr.aria-selected]=\"IsActiveMode(mode.ID)\"\n (click)=\"SelectMode(mode.ID)\">\n <i [class]=\"mode.Icon\"></i>\n <span>{{ mode.Label }}</span>\n </button>\n }\n </div>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n <div class=\"visualize-container\">\n <div class=\"visualize-main\">\n @if (IsActiveMode('clusters')) {\n <app-cluster-visualization-resource\n [Embedded]=\"true\"\n (OpenRecordRequested)=\"OnClusterOpenRecord($event)\">\n </app-cluster-visualization-resource>\n } @else {\n <app-kh-tag-cloud\n #tagCloud\n [Provider]=\"Provider\"\n (TagSelected)=\"OnTagSelected($event)\">\n </app-kh-tag-cloud>\n }\n </div>\n\n <app-kh-record-drilldown\n [Visible]=\"DrilldownVisible\"\n [IsLoading]=\"DrilldownLoading\"\n [Title]=\"DrilldownTitle\"\n [Subtitle]=\"DrilldownSubtitle\"\n [Icon]=\"DrilldownIcon\"\n [Records]=\"DrilldownRecords\"\n (Closed)=\"CloseDrilldown()\"\n (OpenRecord)=\"OnDrilldownOpenRecord($event)\">\n </app-kh-record-drilldown>\n </div>\n </mj-page-body>\n</mj-page-layout>\n"]}
@@ -1,6 +1,9 @@
1
1
  export * from './components/config/knowledge-config-resource.component';
2
2
  export * from './components/results-detail/search-result-detail.component';
3
3
  export * from './components/clusters/cluster-visualization-resource.component';
4
+ export * from './components/visualize/visualize-resource.component';
5
+ export * from './components/visualize/tag-cloud/tag-cloud.component';
6
+ export * from './components/visualize/record-drilldown/record-drilldown.component';
4
7
  export * from './components/scheduling/scheduling-resource.component';
5
8
  export * from './components/analytics/analytics-resource.component';
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/KnowledgeHub/index.ts"],"names":[],"mappings":"AACA,cAAc,yDAAyD,CAAC;AACxE,cAAc,4DAA4D,CAAC;AAC3E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/KnowledgeHub/index.ts"],"names":[],"mappings":"AACA,cAAc,yDAAyD,CAAC;AACxE,cAAc,4DAA4D,CAAC;AAC3E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,qDAAqD,CAAC;AACpE,cAAc,sDAAsD,CAAC;AACrE,cAAc,oEAAoE,CAAC;AACnF,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC"}
@@ -2,6 +2,9 @@
2
2
  export * from './components/config/knowledge-config-resource.component';
3
3
  export * from './components/results-detail/search-result-detail.component';
4
4
  export * from './components/clusters/cluster-visualization-resource.component';
5
+ export * from './components/visualize/visualize-resource.component';
6
+ export * from './components/visualize/tag-cloud/tag-cloud.component';
7
+ export * from './components/visualize/record-drilldown/record-drilldown.component';
5
8
  export * from './components/scheduling/scheduling-resource.component';
6
9
  export * from './components/analytics/analytics-resource.component';
7
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/KnowledgeHub/index.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,cAAc,yDAAyD,CAAC;AACxE,cAAc,4DAA4D,CAAC;AAC3E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC","sourcesContent":["// Knowledge Hub components\nexport * from './components/config/knowledge-config-resource.component';\nexport * from './components/results-detail/search-result-detail.component';\nexport * from './components/clusters/cluster-visualization-resource.component';\nexport * from './components/scheduling/scheduling-resource.component';\nexport * from './components/analytics/analytics-resource.component';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/KnowledgeHub/index.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,cAAc,yDAAyD,CAAC;AACxE,cAAc,4DAA4D,CAAC;AAC3E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,qDAAqD,CAAC;AACpE,cAAc,sDAAsD,CAAC;AACrE,cAAc,oEAAoE,CAAC;AACnF,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC","sourcesContent":["// Knowledge Hub components\nexport * from './components/config/knowledge-config-resource.component';\nexport * from './components/results-detail/search-result-detail.component';\nexport * from './components/clusters/cluster-visualization-resource.component';\nexport * from './components/visualize/visualize-resource.component';\nexport * from './components/visualize/tag-cloud/tag-cloud.component';\nexport * from './components/visualize/record-drilldown/record-drilldown.component';\nexport * from './components/scheduling/scheduling-resource.component';\nexport * from './components/analytics/analytics-resource.component';\n"]}
@@ -545,11 +545,11 @@ export class MCPServerDialogComponent extends BaseAngularComponent {
545
545
  i0.ɵɵconditionalCreate(0, MCPServerDialogComponent_Conditional_0_Template, 72, 42, "mj-dialog", 1);
546
546
  } if (rf & 2) {
547
547
  i0.ɵɵconditional(ctx.visible ? 0 : -1);
548
- } }, dependencies: [i1.ɵNgNoValidate, i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgControlStatusGroup, i1.NgModel, i1.FormGroupDirective, i1.FormControlName, i2.MJButtonDirective, i2.MJDialogComponent, i2.MJDialogActionsComponent, i2.MJDropdownComponent, i2.MJNumericInputComponent], styles: [".server-form[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.error-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error);\n border-radius: 6px;\n color: var(--mj-status-error);\n font-size: 14px;\n}\n\n.form-section[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.form-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.form-group[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n}\n\n.form-group[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 6px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] .required[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.form-group[_ngcontent-%COMP%] input[_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.form-group[_ngcontent-%COMP%] .hint[_ngcontent-%COMP%] {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n.form-group[_ngcontent-%COMP%] .error-text[_ngcontent-%COMP%] {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n.form-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n}\n\n.form-group.half[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.transport-option[_ngcontent-%COMP%], \n.auth-option[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n padding: 4px 0;\n}\n\n.transport-label[_ngcontent-%COMP%], \n.auth-label[_ngcontent-%COMP%] {\n font-weight: 500;\n font-size: 14px;\n}\n\n.transport-desc[_ngcontent-%COMP%], \n.auth-desc[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n\n\n.oauth-section[_ngcontent-%COMP%] {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 4%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.oauth-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.oauth-client-section[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px dashed var(--mj-border-strong);\n}\n\n.oauth-client-section[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.section-hint[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 12px;\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n\n\n[_nghost-%COMP%] .k-dialog-actions {\n padding: 16px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n[_nghost-%COMP%] .k-dialog-content {\n padding: 20px;\n max-height: 70vh;\n overflow-y: auto;\n}\n\n[_nghost-%COMP%] .k-textbox, \n[_nghost-%COMP%] .k-textarea, \n[_nghost-%COMP%] .k-numerictextbox, \n[_nghost-%COMP%] .k-dropdownlist {\n width: 100%;\n}"] });
548
+ } }, dependencies: [i1.ɵNgNoValidate, i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgControlStatusGroup, i1.NgModel, i1.FormGroupDirective, i1.FormControlName, i2.MJButtonDirective, i2.MJDialogComponent, i2.MJDialogActionsComponent, i2.MJDropdownComponent, i2.MJNumericInputComponent], styles: [".server-form[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.error-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error);\n border-radius: 6px;\n color: var(--mj-status-error);\n font-size: 14px;\n}\n\n.form-section[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.form-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.form-group[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n}\n\n.form-group[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 6px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] .required[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.form-group[_ngcontent-%COMP%] input[_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.form-group[_ngcontent-%COMP%] .hint[_ngcontent-%COMP%] {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n.form-group[_ngcontent-%COMP%] .error-text[_ngcontent-%COMP%] {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n.form-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n}\n\n.form-group.half[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.transport-option[_ngcontent-%COMP%], \n.auth-option[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n padding: 4px 0;\n}\n\n.transport-label[_ngcontent-%COMP%], \n.auth-label[_ngcontent-%COMP%] {\n font-weight: 500;\n font-size: 14px;\n}\n\n.transport-desc[_ngcontent-%COMP%], \n.auth-desc[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n\n\n.oauth-section[_ngcontent-%COMP%] {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 4%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.oauth-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.oauth-client-section[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px dashed var(--mj-border-strong);\n}\n\n.oauth-client-section[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.section-hint[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 12px;\n color: var(--mj-text-muted);\n font-style: italic;\n}"] });
549
549
  }
550
550
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MCPServerDialogComponent, [{
551
551
  type: Component,
552
- args: [{ standalone: false, selector: 'mj-mcp-server-dialog', template: "@if (visible) {\n <mj-dialog\n [Visible]=\"true\"\n [Title]=\"DialogTitle\"\n (Close)=\"cancel()\"\n [Width]=\"600\">\n <form [formGroup]=\"serverForm\" class=\"server-form\">\n <!-- Error Message -->\n @if (ErrorMessage) {\n <div class=\"error-banner\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n <!-- Basic Info Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-info-circle\"></i> Basic Information</h3>\n <div class=\"form-group\">\n <label for=\"name\">Name <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"name\"\n formControlName=\"Name\"\n placeholder=\"e.g., GitHub MCP Server\" />\n @if (hasError('Name', 'required')) {\n <span class=\"error-text\">Name is required</span>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"description\">Description</label>\n <textarea class=\"mj-textarea\"\n id=\"description\"\n formControlName=\"Description\"\n placeholder=\"Optional description of this server\"\n [rows]=\"3\"></textarea>\n </div>\n <div class=\"form-group\">\n <label for=\"status\">Status</label>\n <mj-dropdown\n [Data]=\"['Active', 'Inactive']\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('Status')?.value\"\n (ValueChange)=\"serverForm.get('Status')?.setValue($any($event))\">\n </mj-dropdown>\n </div>\n </div>\n <!-- Transport Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-network-wired\"></i> Transport Configuration</h3>\n <div class=\"form-group\">\n <label for=\"transportType\">Transport Type <span class=\"required\">*</span></label>\n <mj-dropdown\n [Data]=\"transportTypes\"\n TextField=\"label\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('TransportType')?.value\"\n (ValueChange)=\"serverForm.get('TransportType')?.setValue($any($event)); onTransportTypeChange()\">\n <ng-template #mjDropdownItem let-dataItem>\n <div class=\"transport-option\">\n <span class=\"transport-label\">{{ dataItem.label }}</span>\n <span class=\"transport-desc\">{{ dataItem.description }}</span>\n </div>\n </ng-template>\n </mj-dropdown>\n </div>\n @if (RequiresURL) {\n <div class=\"form-group\">\n <label for=\"serverUrl\">Server URL <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"serverUrl\"\n formControlName=\"ServerURL\"\n placeholder=\"https://api.example.com/mcp\" />\n @if (hasError('ServerURL', 'required')) {\n <span class=\"error-text\">Server URL is required for this transport type</span>\n }\n </div>\n }\n @if (RequiresCommand) {\n <div class=\"form-group\">\n <label for=\"command\">Command <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"command\"\n formControlName=\"Command\"\n placeholder=\"e.g., npx or /usr/local/bin/mcp-server\" />\n @if (hasError('Command', 'required')) {\n <span class=\"error-text\">Command is required for Stdio transport</span>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"commandArgs\">Command Arguments (JSON array)</label>\n <input class=\"mj-input\"\n id=\"commandArgs\"\n formControlName=\"CommandArgs\"\n placeholder='[\"arg1\", \"arg2\"]' />\n <span class=\"hint\">JSON array of command line arguments</span>\n </div>\n }\n </div>\n <!-- Authentication Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-shield-halved\"></i> Authentication</h3>\n <div class=\"form-group\">\n <label for=\"authType\">Default Auth Type</label>\n <mj-dropdown\n [Data]=\"authTypes\"\n TextField=\"label\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('DefaultAuthType')?.value\"\n (ValueChange)=\"serverForm.get('DefaultAuthType')?.setValue($any($event)); onAuthTypeChange()\">\n <ng-template #mjDropdownItem let-dataItem>\n <div class=\"auth-option\">\n <span class=\"auth-label\">{{ dataItem.label }}</span>\n <span class=\"auth-desc\">{{ dataItem.description }}</span>\n </div>\n </ng-template>\n </mj-dropdown>\n </div>\n </div>\n <!-- OAuth 2.0 Configuration Section -->\n @if (IsOAuth2) {\n <div class=\"form-section oauth-section\">\n <h3><i class=\"fa-solid fa-key\"></i> OAuth 2.0 Configuration</h3>\n <div class=\"form-group\">\n <label for=\"oauthIssuerUrl\">OAuth Issuer URL <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"oauthIssuerUrl\"\n formControlName=\"OAuthIssuerURL\"\n placeholder=\"https://auth.example.com\" />\n @if (hasError('OAuthIssuerURL', 'required')) {\n <span class=\"error-text\">OAuth Issuer URL is required for OAuth 2.0 authentication</span>\n }\n <span class=\"hint\">The authorization server's issuer URL (e.g., https://login.microsoftonline.com/tenant-id or https://your-domain.auth0.com)</span>\n </div>\n <div class=\"form-group\">\n <label for=\"oauthScopes\">OAuth Scopes</label>\n <input class=\"mj-input\"\n id=\"oauthScopes\"\n formControlName=\"OAuthScopes\"\n placeholder=\"openid profile email\" />\n <span class=\"hint\">Space-separated list of OAuth scopes to request</span>\n </div>\n <div class=\"form-group\">\n <label for=\"oauthCacheTtl\">Metadata Cache TTL (minutes)</label>\n <mj-numeric-input\n [Min]=\"5\"\n Placeholder=\"1440\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('OAuthMetadataCacheTTLMinutes')?.value\"\n (ngModelChange)=\"serverForm.get('OAuthMetadataCacheTTLMinutes')?.setValue($event)\">\n </mj-numeric-input>\n <span class=\"hint\">How long to cache OAuth metadata (default: 1440 minutes = 24 hours)</span>\n </div>\n <div class=\"oauth-client-section\">\n <h4>Pre-configured Client (Optional)</h4>\n <p class=\"section-hint\">Leave blank to use Dynamic Client Registration (DCR). If the auth server doesn't support DCR, configure your client credentials here.</p>\n <div class=\"form-group\">\n <label for=\"oauthClientId\">Client ID</label>\n <input class=\"mj-input\"\n id=\"oauthClientId\"\n formControlName=\"OAuthClientID\"\n placeholder=\"your-client-id\" />\n </div>\n <div class=\"form-group\">\n <label for=\"oauthClientSecret\">Client Secret</label>\n <input class=\"mj-input\"\n id=\"oauthClientSecret\"\n formControlName=\"OAuthClientSecretEncrypted\"\n type=\"password\"\n placeholder=\"your-client-secret\" />\n <span class=\"hint\">The secret will be encrypted before storage</span>\n </div>\n </div>\n </div>\n }\n <!-- Rate Limiting Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-gauge-high\"></i> Rate Limiting</h3>\n <div class=\"form-row\">\n <div class=\"form-group half\">\n <label for=\"rateLimitMinute\">Requests per Minute</label>\n <mj-numeric-input\n [Min]=\"0\"\n Placeholder=\"No limit\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RateLimitPerMinute')?.value\"\n (ngModelChange)=\"serverForm.get('RateLimitPerMinute')?.setValue($event)\">\n </mj-numeric-input>\n </div>\n <div class=\"form-group half\">\n <label for=\"rateLimitHour\">Requests per Hour</label>\n <mj-numeric-input\n [Min]=\"0\"\n Placeholder=\"No limit\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RateLimitPerHour')?.value\"\n (ngModelChange)=\"serverForm.get('RateLimitPerHour')?.setValue($event)\">\n </mj-numeric-input>\n </div>\n </div>\n <div class=\"form-group\">\n <label for=\"requestTimeout\">Request Timeout (ms)</label>\n <mj-numeric-input\n [Min]=\"1000\"\n [Max]=\"600000\"\n [Step]=\"1000\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RequestTimeoutMs')?.value\"\n (ngModelChange)=\"serverForm.get('RequestTimeoutMs')?.setValue($event)\">\n </mj-numeric-input>\n <span class=\"hint\">Timeout for individual tool calls (1000-600000ms)</span>\n </div>\n </div>\n </form>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"save()\" [disabled]=\"IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsEditMode ? 'Update' : 'Create' }}\n </button>\n <button mjButton (click)=\"cancel()\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n", styles: [".server-form {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.error-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error);\n border-radius: 6px;\n color: var(--mj-status-error);\n font-size: 14px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.form-section h3 {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-section h3 i {\n color: var(--mj-brand-primary);\n}\n\n.form-group {\n margin-bottom: 16px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n margin-bottom: 6px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group label .required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea {\n width: 100%;\n}\n\n.form-group .hint {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n.form-group .error-text {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n.form-row {\n display: flex;\n gap: 16px;\n}\n\n.form-group.half {\n flex: 1;\n}\n\n.transport-option,\n.auth-option {\n display: flex;\n flex-direction: column;\n padding: 4px 0;\n}\n\n.transport-label,\n.auth-label {\n font-weight: 500;\n font-size: 14px;\n}\n\n.transport-desc,\n.auth-desc {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n/* OAuth Section Styles */\n.oauth-section {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 4%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.oauth-section h3 i {\n color: var(--mj-status-warning);\n}\n\n.oauth-client-section {\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px dashed var(--mj-border-strong);\n}\n\n.oauth-client-section h4 {\n margin: 0 0 8px 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.section-hint {\n margin: 0 0 16px 0;\n font-size: 12px;\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n/* Kendo overrides */\n:host ::ng-deep .k-dialog-actions {\n padding: 16px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n:host ::ng-deep .k-dialog-content {\n padding: 20px;\n max-height: 70vh;\n overflow-y: auto;\n}\n\n:host ::ng-deep .k-textbox,\n:host ::ng-deep .k-textarea,\n:host ::ng-deep .k-numerictextbox,\n:host ::ng-deep .k-dropdownlist {\n width: 100%;\n}\n"] }]
552
+ args: [{ standalone: false, selector: 'mj-mcp-server-dialog', template: "@if (visible) {\n <mj-dialog\n [Visible]=\"true\"\n [Title]=\"DialogTitle\"\n (Close)=\"cancel()\"\n [Width]=\"600\">\n <form [formGroup]=\"serverForm\" class=\"server-form\">\n <!-- Error Message -->\n @if (ErrorMessage) {\n <div class=\"error-banner\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n {{ ErrorMessage }}\n </div>\n }\n <!-- Basic Info Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-info-circle\"></i> Basic Information</h3>\n <div class=\"form-group\">\n <label for=\"name\">Name <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"name\"\n formControlName=\"Name\"\n placeholder=\"e.g., GitHub MCP Server\" />\n @if (hasError('Name', 'required')) {\n <span class=\"error-text\">Name is required</span>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"description\">Description</label>\n <textarea class=\"mj-textarea\"\n id=\"description\"\n formControlName=\"Description\"\n placeholder=\"Optional description of this server\"\n [rows]=\"3\"></textarea>\n </div>\n <div class=\"form-group\">\n <label for=\"status\">Status</label>\n <mj-dropdown\n [Data]=\"['Active', 'Inactive']\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('Status')?.value\"\n (ValueChange)=\"serverForm.get('Status')?.setValue($any($event))\">\n </mj-dropdown>\n </div>\n </div>\n <!-- Transport Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-network-wired\"></i> Transport Configuration</h3>\n <div class=\"form-group\">\n <label for=\"transportType\">Transport Type <span class=\"required\">*</span></label>\n <mj-dropdown\n [Data]=\"transportTypes\"\n TextField=\"label\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('TransportType')?.value\"\n (ValueChange)=\"serverForm.get('TransportType')?.setValue($any($event)); onTransportTypeChange()\">\n <ng-template #mjDropdownItem let-dataItem>\n <div class=\"transport-option\">\n <span class=\"transport-label\">{{ dataItem.label }}</span>\n <span class=\"transport-desc\">{{ dataItem.description }}</span>\n </div>\n </ng-template>\n </mj-dropdown>\n </div>\n @if (RequiresURL) {\n <div class=\"form-group\">\n <label for=\"serverUrl\">Server URL <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"serverUrl\"\n formControlName=\"ServerURL\"\n placeholder=\"https://api.example.com/mcp\" />\n @if (hasError('ServerURL', 'required')) {\n <span class=\"error-text\">Server URL is required for this transport type</span>\n }\n </div>\n }\n @if (RequiresCommand) {\n <div class=\"form-group\">\n <label for=\"command\">Command <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"command\"\n formControlName=\"Command\"\n placeholder=\"e.g., npx or /usr/local/bin/mcp-server\" />\n @if (hasError('Command', 'required')) {\n <span class=\"error-text\">Command is required for Stdio transport</span>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"commandArgs\">Command Arguments (JSON array)</label>\n <input class=\"mj-input\"\n id=\"commandArgs\"\n formControlName=\"CommandArgs\"\n placeholder='[\"arg1\", \"arg2\"]' />\n <span class=\"hint\">JSON array of command line arguments</span>\n </div>\n }\n </div>\n <!-- Authentication Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-shield-halved\"></i> Authentication</h3>\n <div class=\"form-group\">\n <label for=\"authType\">Default Auth Type</label>\n <mj-dropdown\n [Data]=\"authTypes\"\n TextField=\"label\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('DefaultAuthType')?.value\"\n (ValueChange)=\"serverForm.get('DefaultAuthType')?.setValue($any($event)); onAuthTypeChange()\">\n <ng-template #mjDropdownItem let-dataItem>\n <div class=\"auth-option\">\n <span class=\"auth-label\">{{ dataItem.label }}</span>\n <span class=\"auth-desc\">{{ dataItem.description }}</span>\n </div>\n </ng-template>\n </mj-dropdown>\n </div>\n </div>\n <!-- OAuth 2.0 Configuration Section -->\n @if (IsOAuth2) {\n <div class=\"form-section oauth-section\">\n <h3><i class=\"fa-solid fa-key\"></i> OAuth 2.0 Configuration</h3>\n <div class=\"form-group\">\n <label for=\"oauthIssuerUrl\">OAuth Issuer URL <span class=\"required\">*</span></label>\n <input class=\"mj-input\"\n id=\"oauthIssuerUrl\"\n formControlName=\"OAuthIssuerURL\"\n placeholder=\"https://auth.example.com\" />\n @if (hasError('OAuthIssuerURL', 'required')) {\n <span class=\"error-text\">OAuth Issuer URL is required for OAuth 2.0 authentication</span>\n }\n <span class=\"hint\">The authorization server's issuer URL (e.g., https://login.microsoftonline.com/tenant-id or https://your-domain.auth0.com)</span>\n </div>\n <div class=\"form-group\">\n <label for=\"oauthScopes\">OAuth Scopes</label>\n <input class=\"mj-input\"\n id=\"oauthScopes\"\n formControlName=\"OAuthScopes\"\n placeholder=\"openid profile email\" />\n <span class=\"hint\">Space-separated list of OAuth scopes to request</span>\n </div>\n <div class=\"form-group\">\n <label for=\"oauthCacheTtl\">Metadata Cache TTL (minutes)</label>\n <mj-numeric-input\n [Min]=\"5\"\n Placeholder=\"1440\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('OAuthMetadataCacheTTLMinutes')?.value\"\n (ngModelChange)=\"serverForm.get('OAuthMetadataCacheTTLMinutes')?.setValue($event)\">\n </mj-numeric-input>\n <span class=\"hint\">How long to cache OAuth metadata (default: 1440 minutes = 24 hours)</span>\n </div>\n <div class=\"oauth-client-section\">\n <h4>Pre-configured Client (Optional)</h4>\n <p class=\"section-hint\">Leave blank to use Dynamic Client Registration (DCR). If the auth server doesn't support DCR, configure your client credentials here.</p>\n <div class=\"form-group\">\n <label for=\"oauthClientId\">Client ID</label>\n <input class=\"mj-input\"\n id=\"oauthClientId\"\n formControlName=\"OAuthClientID\"\n placeholder=\"your-client-id\" />\n </div>\n <div class=\"form-group\">\n <label for=\"oauthClientSecret\">Client Secret</label>\n <input class=\"mj-input\"\n id=\"oauthClientSecret\"\n formControlName=\"OAuthClientSecretEncrypted\"\n type=\"password\"\n placeholder=\"your-client-secret\" />\n <span class=\"hint\">The secret will be encrypted before storage</span>\n </div>\n </div>\n </div>\n }\n <!-- Rate Limiting Section -->\n <div class=\"form-section\">\n <h3><i class=\"fa-solid fa-gauge-high\"></i> Rate Limiting</h3>\n <div class=\"form-row\">\n <div class=\"form-group half\">\n <label for=\"rateLimitMinute\">Requests per Minute</label>\n <mj-numeric-input\n [Min]=\"0\"\n Placeholder=\"No limit\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RateLimitPerMinute')?.value\"\n (ngModelChange)=\"serverForm.get('RateLimitPerMinute')?.setValue($event)\">\n </mj-numeric-input>\n </div>\n <div class=\"form-group half\">\n <label for=\"rateLimitHour\">Requests per Hour</label>\n <mj-numeric-input\n [Min]=\"0\"\n Placeholder=\"No limit\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RateLimitPerHour')?.value\"\n (ngModelChange)=\"serverForm.get('RateLimitPerHour')?.setValue($event)\">\n </mj-numeric-input>\n </div>\n </div>\n <div class=\"form-group\">\n <label for=\"requestTimeout\">Request Timeout (ms)</label>\n <mj-numeric-input\n [Min]=\"1000\"\n [Max]=\"600000\"\n [Step]=\"1000\"\n [ngModelOptions]=\"{standalone: true}\" [ngModel]=\"serverForm.get('RequestTimeoutMs')?.value\"\n (ngModelChange)=\"serverForm.get('RequestTimeoutMs')?.setValue($event)\">\n </mj-numeric-input>\n <span class=\"hint\">Timeout for individual tool calls (1000-600000ms)</span>\n </div>\n </div>\n </form>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"save()\" [disabled]=\"IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsEditMode ? 'Update' : 'Create' }}\n </button>\n <button mjButton (click)=\"cancel()\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n}\n", styles: [".server-form {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.error-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error);\n border-radius: 6px;\n color: var(--mj-status-error);\n font-size: 14px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.form-section h3 {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-section h3 i {\n color: var(--mj-brand-primary);\n}\n\n.form-group {\n margin-bottom: 16px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n margin-bottom: 6px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-group label .required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea {\n width: 100%;\n}\n\n.form-group .hint {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-text-disabled);\n}\n\n.form-group .error-text {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n.form-row {\n display: flex;\n gap: 16px;\n}\n\n.form-group.half {\n flex: 1;\n}\n\n.transport-option,\n.auth-option {\n display: flex;\n flex-direction: column;\n padding: 4px 0;\n}\n\n.transport-label,\n.auth-label {\n font-weight: 500;\n font-size: 14px;\n}\n\n.transport-desc,\n.auth-desc {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n/* OAuth Section Styles */\n.oauth-section {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 4%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.oauth-section h3 i {\n color: var(--mj-status-warning);\n}\n\n.oauth-client-section {\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px dashed var(--mj-border-strong);\n}\n\n.oauth-client-section h4 {\n margin: 0 0 8px 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.section-hint {\n margin: 0 0 16px 0;\n font-size: 12px;\n color: var(--mj-text-muted);\n font-style: italic;\n}\n"] }]
553
553
  }], () => [{ type: i1.FormBuilder }, { type: i0.ChangeDetectorRef }], { server: [{
554
554
  type: Input
555
555
  }], visible: [{
@@ -625,7 +625,7 @@ let QueryBrowserResourceComponent = class QueryBrowserResourceComponent extends
625
625
  // Load all queries from QueryEngine (event-driven cache, returns MJQueryEntityExtended[])
626
626
  const qe = QueryEngine.Instance;
627
627
  this.categories = qe.Categories || [];
628
- this.queries = (qe.Queries || []).filter(q => q.UserCanRun(this.metadata.CurrentUser));
628
+ this.queries = (qe.Queries || []).filter(q => q.UserCanRun(this.metadata.CurrentUser).canRun);
629
629
  this.applyFilters();
630
630
  this.buildCategoryTree();
631
631
  // Mark data as loaded