@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
@@ -0,0 +1,308 @@
1
+ /**
2
+ * @fileoverview Classify · Content Item Grid (Phase 4 audit/analytics).
3
+ *
4
+ * Thin, reusable AG Grid listing of content items in one of two modes:
5
+ * - scoped to a single pipeline run via `[RunID]` (items processed in that run), or
6
+ * - all items (when `[RunID]` is null).
7
+ *
8
+ * Read-only: uses `ResultType:'simple'` + an explicit narrow `Fields` list, and a
9
+ * load-more / page-size widening approach mirroring the host dashboard. Selecting a
10
+ * row bubbles `(ItemSelected)` UP — this component owns no navigation (the host owns
11
+ * NavigationService and opens the drilldown).
12
+ */
13
+ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject, } from '@angular/core';
14
+ import { RunView } from '@memberjunction/core';
15
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
16
+ import { ModuleRegistry, AllCommunityModule, themeAlpine, } from 'ag-grid-community';
17
+ import { deriveDisplayName, formatDate } from '../shared/classify.format';
18
+ import * as i0 from "@angular/core";
19
+ import * as i1 from "ag-grid-angular";
20
+ import * as i2 from "@memberjunction/ng-shared-generic";
21
+ import * as i3 from "@memberjunction/ng-ui-components";
22
+ function ClassifyItemGridComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
23
+ i0.ɵɵelement(0, "mj-loading", 1);
24
+ } }
25
+ function ClassifyItemGridComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
26
+ i0.ɵɵelementStart(0, "div", 2);
27
+ i0.ɵɵelement(1, "i", 3);
28
+ i0.ɵɵelementStart(2, "p");
29
+ i0.ɵɵtext(3, "No content items found.");
30
+ i0.ɵɵelementEnd()();
31
+ } }
32
+ function ClassifyItemGridComponent_Conditional_3_Conditional_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
33
+ i0.ɵɵelement(0, "i", 9);
34
+ i0.ɵɵtext(1, " Loading\u2026 ");
35
+ } }
36
+ function ClassifyItemGridComponent_Conditional_3_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
37
+ i0.ɵɵelement(0, "i", 10);
38
+ i0.ɵɵtext(1, " Load more ");
39
+ } }
40
+ function ClassifyItemGridComponent_Conditional_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
41
+ const _r3 = i0.ɵɵgetCurrentView();
42
+ i0.ɵɵelementStart(0, "button", 8);
43
+ i0.ɵɵlistener("click", function ClassifyItemGridComponent_Conditional_3_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.LoadMore()); });
44
+ i0.ɵɵconditionalCreate(1, ClassifyItemGridComponent_Conditional_3_Conditional_4_Conditional_1_Template, 2, 0)(2, ClassifyItemGridComponent_Conditional_3_Conditional_4_Conditional_2_Template, 2, 0);
45
+ i0.ɵɵelementEnd();
46
+ } if (rf & 2) {
47
+ const ctx_r1 = i0.ɵɵnextContext(2);
48
+ i0.ɵɵproperty("disabled", ctx_r1.IsLoadingMore);
49
+ i0.ɵɵadvance();
50
+ i0.ɵɵconditional(ctx_r1.IsLoadingMore ? 1 : 2);
51
+ } }
52
+ function ClassifyItemGridComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
53
+ const _r1 = i0.ɵɵgetCurrentView();
54
+ i0.ɵɵelementStart(0, "ag-grid-angular", 4);
55
+ i0.ɵɵlistener("gridReady", function ClassifyItemGridComponent_Conditional_3_Template_ag_grid_angular_gridReady_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnGridReady($event)); })("rowClicked", function ClassifyItemGridComponent_Conditional_3_Template_ag_grid_angular_rowClicked_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnRowClicked($event)); });
56
+ i0.ɵɵelementEnd();
57
+ i0.ɵɵelementStart(1, "div", 5)(2, "span", 6);
58
+ i0.ɵɵtext(3);
59
+ i0.ɵɵelementEnd();
60
+ i0.ɵɵconditionalCreate(4, ClassifyItemGridComponent_Conditional_3_Conditional_4_Template, 3, 2, "button", 7);
61
+ i0.ɵɵelementEnd();
62
+ } if (rf & 2) {
63
+ const ctx_r1 = i0.ɵɵnextContext();
64
+ i0.ɵɵproperty("theme", ctx_r1.Theme)("columnDefs", ctx_r1.ColumnDefs)("defaultColDef", ctx_r1.DefaultColDef)("gridOptions", ctx_r1.GridOptions)("rowData", ctx_r1.Rows);
65
+ i0.ɵɵadvance(3);
66
+ i0.ɵɵtextInterpolate2("Showing ", ctx_r1.Rows.length, " of ", ctx_r1.TotalCount, " items");
67
+ i0.ɵɵadvance();
68
+ i0.ɵɵconditional(ctx_r1.HasMore ? 4 : -1);
69
+ } }
70
+ // Register AG Grid community modules once (idempotent across grids in the bundle).
71
+ ModuleRegistry.registerModules([AllCommunityModule]);
72
+ export class ClassifyItemGridComponent extends BaseAngularComponent {
73
+ cdr = inject(ChangeDetectorRef);
74
+ /** One page worth of additional rows fetched when "Load more" is clicked. */
75
+ static PAGE_SIZE = 100;
76
+ /**
77
+ * When set, scope the grid to items processed by this pipeline run. When null,
78
+ * show all content items. Re-fetches on change (setter pattern, not ngOnChanges).
79
+ */
80
+ _runID = null;
81
+ set RunID(value) {
82
+ if (value === this._runID)
83
+ return;
84
+ this._runID = value;
85
+ this._pageSize = ClassifyItemGridComponent.PAGE_SIZE;
86
+ void this.loadItems();
87
+ }
88
+ get RunID() {
89
+ return this._runID;
90
+ }
91
+ /** When true, the grid loads its data on init. Lets a parent defer the query until the section is shown. */
92
+ _autoLoad = true;
93
+ set AutoLoad(value) {
94
+ const was = this._autoLoad;
95
+ this._autoLoad = value;
96
+ if (value && !was && !this._loaded)
97
+ void this.loadItems();
98
+ }
99
+ get AutoLoad() {
100
+ return this._autoLoad;
101
+ }
102
+ /** Emits the selected content item's ID when a row is clicked. */
103
+ ItemSelected = new EventEmitter();
104
+ Rows = [];
105
+ IsLoading = false;
106
+ IsLoadingMore = false;
107
+ TotalCount = 0;
108
+ _loaded = false;
109
+ _pageSize = ClassifyItemGridComponent.PAGE_SIZE;
110
+ gridApi = null;
111
+ get HasMore() {
112
+ return this.TotalCount > this.Rows.length;
113
+ }
114
+ // ── AG Grid configuration ──
115
+ Theme = themeAlpine.withParams({
116
+ backgroundColor: 'var(--mj-bg-surface)',
117
+ foregroundColor: 'var(--mj-text-primary)',
118
+ textColor: 'var(--mj-text-primary)',
119
+ borderColor: 'var(--mj-border-default)',
120
+ chromeBackgroundColor: 'var(--mj-bg-surface-card)',
121
+ headerBackgroundColor: 'var(--mj-bg-surface-card)',
122
+ headerTextColor: 'var(--mj-text-secondary)',
123
+ cellTextColor: 'var(--mj-text-primary)',
124
+ subtleTextColor: 'var(--mj-text-muted)',
125
+ dataBackgroundColor: 'var(--mj-bg-surface)',
126
+ oddRowBackgroundColor: 'var(--mj-bg-surface-card)',
127
+ rowHoverColor: 'var(--mj-bg-surface-hover, color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface)))',
128
+ selectedRowBackgroundColor: 'color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface))',
129
+ accentColor: 'var(--mj-brand-primary)',
130
+ borderRadius: 'var(--mj-radius-sm)',
131
+ browserColorScheme: 'inherit',
132
+ });
133
+ GridOptions = {
134
+ animateRows: true,
135
+ rowHeight: 36,
136
+ headerHeight: 40,
137
+ suppressCellFocus: true,
138
+ enableCellTextSelection: true,
139
+ suppressNoRowsOverlay: true,
140
+ };
141
+ DefaultColDef = {
142
+ sortable: true,
143
+ resizable: true,
144
+ minWidth: 80,
145
+ };
146
+ ColumnDefs = [
147
+ { field: 'DisplayName', headerName: 'Item', flex: 2, minWidth: 220, tooltipField: 'DisplayName' },
148
+ { field: 'SourceName', headerName: 'Source', flex: 1, minWidth: 140 },
149
+ { field: 'TagCount', headerName: 'Tags', width: 90, type: 'numericColumn' },
150
+ { field: 'TaggingStatus', headerName: 'Status', width: 130 },
151
+ { field: 'UpdatedAt', headerName: 'Updated', width: 160, sort: 'desc',
152
+ comparator: (_a, _b, nodeA, nodeB) => (nodeA.data?.UpdatedAtRaw ?? '').localeCompare(nodeB.data?.UpdatedAtRaw ?? '') },
153
+ ];
154
+ OnGridReady(event) {
155
+ this.gridApi = event.api;
156
+ this.gridApi.setGridOption('rowData', this.Rows);
157
+ }
158
+ OnRowClicked(event) {
159
+ if (event.data)
160
+ this.ItemSelected.emit(event.data.ID);
161
+ }
162
+ /** Public refresh entry point (parent toolbar / load-more handler). */
163
+ async Reload() {
164
+ this._pageSize = ClassifyItemGridComponent.PAGE_SIZE;
165
+ await this.loadItems();
166
+ }
167
+ async LoadMore() {
168
+ if (this.IsLoadingMore || !this.HasMore)
169
+ return;
170
+ this.IsLoadingMore = true;
171
+ this.cdr.detectChanges();
172
+ this._pageSize += ClassifyItemGridComponent.PAGE_SIZE;
173
+ await this.loadItems();
174
+ this.IsLoadingMore = false;
175
+ this.cdr.detectChanges();
176
+ }
177
+ async loadItems() {
178
+ if (!this._autoLoad)
179
+ return;
180
+ this.IsLoading = true;
181
+ this.cdr.detectChanges();
182
+ // When scoped to a run, resolve the run's source + time window so we can
183
+ // constrain the item query. There is no per-item run join table in the
184
+ // schema, so we scope by ContentSource + the run's processing window
185
+ // (same time-correlation basis the rest of the pipeline uses).
186
+ let extraFilter = '';
187
+ if (this._runID) {
188
+ const scope = await this.resolveRunScope(this._runID);
189
+ if (scope === null) {
190
+ this.applyRows([], 0);
191
+ return;
192
+ }
193
+ extraFilter = scope;
194
+ }
195
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
196
+ const result = await rv.RunView({
197
+ EntityName: 'MJ: Content Items',
198
+ ExtraFilter: extraFilter,
199
+ OrderBy: '__mj_UpdatedAt DESC',
200
+ MaxRows: this._pageSize,
201
+ StartRow: 0,
202
+ ResultType: 'simple',
203
+ Fields: ['ID', 'Name', 'Description', 'ContentSource', 'TaggingStatus', 'EmbeddingStatus', '__mj_UpdatedAt'],
204
+ }, this.ProviderToUse.CurrentUser);
205
+ if (!result.Success) {
206
+ this.applyRows([], 0);
207
+ return;
208
+ }
209
+ const tagCounts = await this.loadTagCounts(result.Results.map(r => r['ID']));
210
+ const rows = result.Results.map(r => this.toRow(r, tagCounts));
211
+ this.applyRows(rows, result.TotalRowCount);
212
+ }
213
+ /**
214
+ * Build the item-level filter that scopes to a pipeline run: the run's
215
+ * ContentSource, optionally bounded by the run's processing window. Returns
216
+ * null on a hard failure (so the caller short-circuits to an empty grid).
217
+ */
218
+ async resolveRunScope(runID) {
219
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
220
+ const result = await rv.RunView({
221
+ EntityName: 'MJ: Content Process Runs',
222
+ ExtraFilter: `ID='${runID}'`,
223
+ Fields: ['SourceID', 'StartTime', 'EndTime'],
224
+ ResultType: 'simple',
225
+ }, this.ProviderToUse.CurrentUser);
226
+ if (!result.Success || result.Results.length === 0)
227
+ return null;
228
+ const run = result.Results[0];
229
+ const fragments = [`ContentSourceID='${run.SourceID.replace(/'/g, '')}'`];
230
+ if (run.StartTime)
231
+ fragments.push(`__mj_UpdatedAt >= '${this.toSqlDate(run.StartTime)}'`);
232
+ if (run.EndTime)
233
+ fragments.push(`__mj_UpdatedAt <= '${this.toSqlDate(run.EndTime)}'`);
234
+ return fragments.join(' AND ');
235
+ }
236
+ /** Format an ISO date value for a SQL string literal (UTC, no tz suffix surprises). */
237
+ toSqlDate(value) {
238
+ const d = new Date(value);
239
+ return isNaN(d.getTime()) ? value.replace(/'/g, '') : d.toISOString().replace('T', ' ').replace('Z', '');
240
+ }
241
+ /** Count tags per item for the visible page (one RunView, aggregated client-side). */
242
+ async loadTagCounts(itemIDs) {
243
+ const counts = new Map();
244
+ if (itemIDs.length === 0)
245
+ return counts;
246
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
247
+ const result = await rv.RunView({
248
+ EntityName: 'MJ: Content Item Tags',
249
+ ExtraFilter: this.buildItemIDInFilter(itemIDs, 'ItemID'),
250
+ Fields: ['ItemID'],
251
+ ResultType: 'simple',
252
+ }, this.ProviderToUse.CurrentUser);
253
+ if (result.Success) {
254
+ for (const r of result.Results) {
255
+ counts.set(r.ItemID, (counts.get(r.ItemID) ?? 0) + 1);
256
+ }
257
+ }
258
+ return counts;
259
+ }
260
+ toRow(r, tagCounts) {
261
+ const id = r['ID'];
262
+ const updatedAt = r['__mj_UpdatedAt'] ?? '';
263
+ return {
264
+ ID: id,
265
+ DisplayName: deriveDisplayName({ Name: r['Name'], Description: r['Description'] }),
266
+ SourceName: r['ContentSource'] ?? 'Unknown',
267
+ TagCount: tagCounts.get(id) ?? 0,
268
+ EmbeddingStatus: r['EmbeddingStatus'] ?? '',
269
+ TaggingStatus: r['TaggingStatus'] ?? '',
270
+ UpdatedAt: updatedAt ? formatDate(updatedAt) : '—',
271
+ UpdatedAtRaw: updatedAt,
272
+ };
273
+ }
274
+ applyRows(rows, total) {
275
+ this.Rows = rows;
276
+ this.TotalCount = total;
277
+ this.IsLoading = false;
278
+ this._loaded = true;
279
+ this.gridApi?.setGridOption('rowData', rows);
280
+ this.cdr.detectChanges();
281
+ }
282
+ /** Build an `IN (...)` filter from item IDs, defensively stripping quotes. */
283
+ buildItemIDInFilter(ids, column = 'ID') {
284
+ const quoted = ids.map(id => `'${id.replace(/'/g, '')}'`).join(',');
285
+ return `${column} IN (${quoted})`;
286
+ }
287
+ static ɵfac = /*@__PURE__*/ (() => { let ɵClassifyItemGridComponent_BaseFactory; return function ClassifyItemGridComponent_Factory(__ngFactoryType__) { return (ɵClassifyItemGridComponent_BaseFactory || (ɵClassifyItemGridComponent_BaseFactory = i0.ɵɵgetInheritedFactory(ClassifyItemGridComponent)))(__ngFactoryType__ || ClassifyItemGridComponent); }; })();
288
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ClassifyItemGridComponent, selectors: [["classify-item-grid"]], inputs: { RunID: "RunID", AutoLoad: "AutoLoad" }, outputs: { ItemSelected: "ItemSelected" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [[1, "cig-wrap"], ["text", "Loading items...", "size", "small"], [1, "cig-empty"], [1, "fa-solid", "fa-inbox"], [1, "cig-grid", 3, "gridReady", "rowClicked", "theme", "columnDefs", "defaultColDef", "gridOptions", "rowData"], [1, "cig-footer"], [1, "cig-count"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "disabled"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-chevron-down"]], template: function ClassifyItemGridComponent_Template(rf, ctx) { if (rf & 1) {
289
+ i0.ɵɵelementStart(0, "div", 0);
290
+ i0.ɵɵconditionalCreate(1, ClassifyItemGridComponent_Conditional_1_Template, 1, 0, "mj-loading", 1)(2, ClassifyItemGridComponent_Conditional_2_Template, 4, 0, "div", 2)(3, ClassifyItemGridComponent_Conditional_3_Template, 5, 8);
291
+ i0.ɵɵelementEnd();
292
+ } if (rf & 2) {
293
+ i0.ɵɵadvance();
294
+ i0.ɵɵconditional(ctx.IsLoading && ctx.Rows.length === 0 ? 1 : ctx.Rows.length === 0 ? 2 : 3);
295
+ } }, dependencies: [i1.AgGridAngular, i2.LoadingComponent, i3.MJButtonDirective], styles: [".cig-wrap[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n}\n\n.cig-grid[_ngcontent-%COMP%] {\n width: 100%;\n height: 360px;\n}\n\n.cig-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 2px;\n}\n\n.cig-count[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n}\n\n.cig-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 32px 16px;\n color: var(--mj-text-muted);\n}\n\n.cig-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.6rem;\n color: var(--mj-text-disabled);\n}\n\n.cig-empty[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.85rem;\n}"] });
296
+ }
297
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClassifyItemGridComponent, [{
298
+ type: Component,
299
+ args: [{ standalone: false, selector: 'classify-item-grid', template: "<div class=\"cig-wrap\">\n @if (IsLoading && Rows.length === 0) {\n <mj-loading text=\"Loading items...\" size=\"small\"></mj-loading>\n } @else if (Rows.length === 0) {\n <div class=\"cig-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No content items found.</p>\n </div>\n } @else {\n <ag-grid-angular\n class=\"cig-grid\"\n [theme]=\"Theme\"\n [columnDefs]=\"ColumnDefs\"\n [defaultColDef]=\"DefaultColDef\"\n [gridOptions]=\"GridOptions\"\n [rowData]=\"Rows\"\n (gridReady)=\"OnGridReady($event)\"\n (rowClicked)=\"OnRowClicked($event)\">\n </ag-grid-angular>\n\n <div class=\"cig-footer\">\n <span class=\"cig-count\">Showing {{ Rows.length }} of {{ TotalCount }} items</span>\n @if (HasMore) {\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"LoadMore()\" [disabled]=\"IsLoadingMore\">\n @if (IsLoadingMore) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Loading\u2026\n } @else {\n <i class=\"fa-solid fa-chevron-down\"></i> Load more\n }\n </button>\n }\n </div>\n }\n</div>\n", styles: [".cig-wrap {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n}\n\n.cig-grid {\n width: 100%;\n height: 360px;\n}\n\n.cig-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 2px;\n}\n\n.cig-count {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n}\n\n.cig-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 32px 16px;\n color: var(--mj-text-muted);\n}\n\n.cig-empty i {\n font-size: 1.6rem;\n color: var(--mj-text-disabled);\n}\n\n.cig-empty p {\n margin: 0;\n font-size: 0.85rem;\n}\n"] }]
300
+ }], null, { RunID: [{
301
+ type: Input
302
+ }], AutoLoad: [{
303
+ type: Input
304
+ }], ItemSelected: [{
305
+ type: Output
306
+ }] }); })();
307
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ClassifyItemGridComponent, { className: "ClassifyItemGridComponent", filePath: "src/AI/components/autotagging/components/classify-item-grid.component.ts", lineNumber: 46 }); })();
308
+ //# sourceMappingURL=classify-item-grid.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-item-grid.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/autotagging/components/classify-item-grid.component.ts","../../../../../src/AI/components/autotagging/components/classify-item-grid.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Classify · Content Item Grid (Phase 4 audit/analytics).\n *\n * Thin, reusable AG Grid listing of content items in one of two modes:\n * - scoped to a single pipeline run via `[RunID]` (items processed in that run), or\n * - all items (when `[RunID]` is null).\n *\n * Read-only: uses `ResultType:'simple'` + an explicit narrow `Fields` list, and a\n * load-more / page-size widening approach mirroring the host dashboard. Selecting a\n * row bubbles `(ItemSelected)` UP — this component owns no navigation (the host owns\n * NavigationService and opens the drilldown).\n */\nimport {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectorRef,\n inject,\n} from '@angular/core';\nimport { RunView } from '@memberjunction/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport {\n ColDef,\n GridApi,\n GridOptions,\n GridReadyEvent,\n ModuleRegistry,\n AllCommunityModule,\n Theme,\n themeAlpine,\n RowClickedEvent,\n} from 'ag-grid-community';\nimport { ClassifyItemGridRow } from '../shared/classify.types';\nimport { deriveDisplayName, formatDate } from '../shared/classify.format';\n\n// Register AG Grid community modules once (idempotent across grids in the bundle).\nModuleRegistry.registerModules([AllCommunityModule]);\n\n@Component({\n standalone: false,\n selector: 'classify-item-grid',\n templateUrl: './classify-item-grid.component.html',\n styleUrls: ['./classify-item-grid.component.css'],\n})\nexport class ClassifyItemGridComponent extends BaseAngularComponent {\n private cdr = inject(ChangeDetectorRef);\n\n /** One page worth of additional rows fetched when \"Load more\" is clicked. */\n private static readonly PAGE_SIZE = 100;\n\n /**\n * When set, scope the grid to items processed by this pipeline run. When null,\n * show all content items. Re-fetches on change (setter pattern, not ngOnChanges).\n */\n private _runID: string | null = null;\n @Input()\n set RunID(value: string | null) {\n if (value === this._runID) return;\n this._runID = value;\n this._pageSize = ClassifyItemGridComponent.PAGE_SIZE;\n void this.loadItems();\n }\n get RunID(): string | null {\n return this._runID;\n }\n\n /** When true, the grid loads its data on init. Lets a parent defer the query until the section is shown. */\n private _autoLoad = true;\n @Input()\n set AutoLoad(value: boolean) {\n const was = this._autoLoad;\n this._autoLoad = value;\n if (value && !was && !this._loaded) void this.loadItems();\n }\n get AutoLoad(): boolean {\n return this._autoLoad;\n }\n\n /** Emits the selected content item's ID when a row is clicked. */\n @Output() ItemSelected = new EventEmitter<string>();\n\n public Rows: ClassifyItemGridRow[] = [];\n public IsLoading = false;\n public IsLoadingMore = false;\n public TotalCount = 0;\n\n private _loaded = false;\n private _pageSize = ClassifyItemGridComponent.PAGE_SIZE;\n private gridApi: GridApi | null = null;\n\n public get HasMore(): boolean {\n return this.TotalCount > this.Rows.length;\n }\n\n // ── AG Grid configuration ──\n\n public Theme: Theme = themeAlpine.withParams({\n backgroundColor: 'var(--mj-bg-surface)',\n foregroundColor: 'var(--mj-text-primary)',\n textColor: 'var(--mj-text-primary)',\n borderColor: 'var(--mj-border-default)',\n chromeBackgroundColor: 'var(--mj-bg-surface-card)',\n headerBackgroundColor: 'var(--mj-bg-surface-card)',\n headerTextColor: 'var(--mj-text-secondary)',\n cellTextColor: 'var(--mj-text-primary)',\n subtleTextColor: 'var(--mj-text-muted)',\n dataBackgroundColor: 'var(--mj-bg-surface)',\n oddRowBackgroundColor: 'var(--mj-bg-surface-card)',\n rowHoverColor: 'var(--mj-bg-surface-hover, color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface)))',\n selectedRowBackgroundColor: 'color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface))',\n accentColor: 'var(--mj-brand-primary)',\n borderRadius: 'var(--mj-radius-sm)',\n browserColorScheme: 'inherit',\n });\n\n public GridOptions: GridOptions<ClassifyItemGridRow> = {\n animateRows: true,\n rowHeight: 36,\n headerHeight: 40,\n suppressCellFocus: true,\n enableCellTextSelection: true,\n suppressNoRowsOverlay: true,\n };\n\n public DefaultColDef: ColDef = {\n sortable: true,\n resizable: true,\n minWidth: 80,\n };\n\n public ColumnDefs: ColDef<ClassifyItemGridRow>[] = [\n { field: 'DisplayName', headerName: 'Item', flex: 2, minWidth: 220, tooltipField: 'DisplayName' },\n { field: 'SourceName', headerName: 'Source', flex: 1, minWidth: 140 },\n { field: 'TagCount', headerName: 'Tags', width: 90, type: 'numericColumn' },\n { field: 'TaggingStatus', headerName: 'Status', width: 130 },\n { field: 'UpdatedAt', headerName: 'Updated', width: 160, sort: 'desc',\n comparator: (_a, _b, nodeA, nodeB) =>\n (nodeA.data?.UpdatedAtRaw ?? '').localeCompare(nodeB.data?.UpdatedAtRaw ?? '') },\n ];\n\n public OnGridReady(event: GridReadyEvent<ClassifyItemGridRow>): void {\n this.gridApi = event.api;\n this.gridApi.setGridOption('rowData', this.Rows);\n }\n\n public OnRowClicked(event: RowClickedEvent<ClassifyItemGridRow>): void {\n if (event.data) this.ItemSelected.emit(event.data.ID);\n }\n\n /** Public refresh entry point (parent toolbar / load-more handler). */\n public async Reload(): Promise<void> {\n this._pageSize = ClassifyItemGridComponent.PAGE_SIZE;\n await this.loadItems();\n }\n\n public async LoadMore(): Promise<void> {\n if (this.IsLoadingMore || !this.HasMore) return;\n this.IsLoadingMore = true;\n this.cdr.detectChanges();\n this._pageSize += ClassifyItemGridComponent.PAGE_SIZE;\n await this.loadItems();\n this.IsLoadingMore = false;\n this.cdr.detectChanges();\n }\n\n private async loadItems(): Promise<void> {\n if (!this._autoLoad) return;\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n // When scoped to a run, resolve the run's source + time window so we can\n // constrain the item query. There is no per-item run join table in the\n // schema, so we scope by ContentSource + the run's processing window\n // (same time-correlation basis the rest of the pipeline uses).\n let extraFilter = '';\n if (this._runID) {\n const scope = await this.resolveRunScope(this._runID);\n if (scope === null) {\n this.applyRows([], 0);\n return;\n }\n extraFilter = scope;\n }\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<Record<string, unknown>>({\n EntityName: 'MJ: Content Items',\n ExtraFilter: extraFilter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: this._pageSize,\n StartRow: 0,\n ResultType: 'simple',\n Fields: ['ID', 'Name', 'Description', 'ContentSource', 'TaggingStatus', 'EmbeddingStatus', '__mj_UpdatedAt'],\n }, this.ProviderToUse.CurrentUser);\n\n if (!result.Success) {\n this.applyRows([], 0);\n return;\n }\n\n const tagCounts = await this.loadTagCounts(result.Results.map(r => r['ID'] as string));\n const rows = result.Results.map(r => this.toRow(r, tagCounts));\n this.applyRows(rows, result.TotalRowCount);\n }\n\n /**\n * Build the item-level filter that scopes to a pipeline run: the run's\n * ContentSource, optionally bounded by the run's processing window. Returns\n * null on a hard failure (so the caller short-circuits to an empty grid).\n */\n private async resolveRunScope(runID: string): Promise<string | null> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<{ SourceID: string; StartTime: string | null; EndTime: string | null }>({\n EntityName: 'MJ: Content Process Runs',\n ExtraFilter: `ID='${runID}'`,\n Fields: ['SourceID', 'StartTime', 'EndTime'],\n ResultType: 'simple',\n }, this.ProviderToUse.CurrentUser);\n if (!result.Success || result.Results.length === 0) return null;\n\n const run = result.Results[0];\n const fragments = [`ContentSourceID='${run.SourceID.replace(/'/g, '')}'`];\n if (run.StartTime) fragments.push(`__mj_UpdatedAt >= '${this.toSqlDate(run.StartTime)}'`);\n if (run.EndTime) fragments.push(`__mj_UpdatedAt <= '${this.toSqlDate(run.EndTime)}'`);\n return fragments.join(' AND ');\n }\n\n /** Format an ISO date value for a SQL string literal (UTC, no tz suffix surprises). */\n private toSqlDate(value: string): string {\n const d = new Date(value);\n return isNaN(d.getTime()) ? value.replace(/'/g, '') : d.toISOString().replace('T', ' ').replace('Z', '');\n }\n\n /** Count tags per item for the visible page (one RunView, aggregated client-side). */\n private async loadTagCounts(itemIDs: string[]): Promise<Map<string, number>> {\n const counts = new Map<string, number>();\n if (itemIDs.length === 0) return counts;\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<{ ItemID: string }>({\n EntityName: 'MJ: Content Item Tags',\n ExtraFilter: this.buildItemIDInFilter(itemIDs, 'ItemID'),\n Fields: ['ItemID'],\n ResultType: 'simple',\n }, this.ProviderToUse.CurrentUser);\n if (result.Success) {\n for (const r of result.Results) {\n counts.set(r.ItemID, (counts.get(r.ItemID) ?? 0) + 1);\n }\n }\n return counts;\n }\n\n private toRow(r: Record<string, unknown>, tagCounts: Map<string, number>): ClassifyItemGridRow {\n const id = r['ID'] as string;\n const updatedAt = (r['__mj_UpdatedAt'] as string) ?? '';\n return {\n ID: id,\n DisplayName: deriveDisplayName({ Name: r['Name'] as string | null, Description: r['Description'] as string | null }),\n SourceName: (r['ContentSource'] as string) ?? 'Unknown',\n TagCount: tagCounts.get(id) ?? 0,\n EmbeddingStatus: (r['EmbeddingStatus'] as string) ?? '',\n TaggingStatus: (r['TaggingStatus'] as string) ?? '',\n UpdatedAt: updatedAt ? formatDate(updatedAt) : '—',\n UpdatedAtRaw: updatedAt,\n };\n }\n\n private applyRows(rows: ClassifyItemGridRow[], total: number): void {\n this.Rows = rows;\n this.TotalCount = total;\n this.IsLoading = false;\n this._loaded = true;\n this.gridApi?.setGridOption('rowData', rows);\n this.cdr.detectChanges();\n }\n\n /** Build an `IN (...)` filter from item IDs, defensively stripping quotes. */\n private buildItemIDInFilter(ids: string[], column = 'ID'): string {\n const quoted = ids.map(id => `'${id.replace(/'/g, '')}'`).join(',');\n return `${column} IN (${quoted})`;\n }\n}\n","<div class=\"cig-wrap\">\n @if (IsLoading && Rows.length === 0) {\n <mj-loading text=\"Loading items...\" size=\"small\"></mj-loading>\n } @else if (Rows.length === 0) {\n <div class=\"cig-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No content items found.</p>\n </div>\n } @else {\n <ag-grid-angular\n class=\"cig-grid\"\n [theme]=\"Theme\"\n [columnDefs]=\"ColumnDefs\"\n [defaultColDef]=\"DefaultColDef\"\n [gridOptions]=\"GridOptions\"\n [rowData]=\"Rows\"\n (gridReady)=\"OnGridReady($event)\"\n (rowClicked)=\"OnRowClicked($event)\">\n </ag-grid-angular>\n\n <div class=\"cig-footer\">\n <span class=\"cig-count\">Showing {{ Rows.length }} of {{ TotalCount }} items</span>\n @if (HasMore) {\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"LoadMore()\" [disabled]=\"IsLoadingMore\">\n @if (IsLoadingMore) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Loading…\n } @else {\n <i class=\"fa-solid fa-chevron-down\"></i> Load more\n }\n </button>\n }\n </div>\n }\n</div>\n"]}
@@ -0,0 +1,29 @@
1
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
2
+ import * as i0 from "@angular/core";
3
+ export declare class ClassifyOrgContextEditorComponent extends BaseAngularComponent {
4
+ private cdr;
5
+ /** Whether the editor body is expanded. */
6
+ Expanded: boolean;
7
+ /** The org-context text bound to the textarea. */
8
+ ContextText: string;
9
+ /** The value last loaded/saved, used to detect unsaved edits. */
10
+ private savedText;
11
+ /** Loading spinner while reading the setting. */
12
+ IsLoading: boolean;
13
+ /** Saving spinner. */
14
+ IsSaving: boolean;
15
+ /** Whether the initial load has run. */
16
+ private loaded;
17
+ private khApplicationID;
18
+ /** True when the textarea differs from the persisted value. */
19
+ get HasUnsavedChanges(): boolean;
20
+ /** Toggle the editor open/closed, lazy-loading the value the first time. */
21
+ Toggle(): Promise<void>;
22
+ private resolveKnowledgeHubApplicationID;
23
+ private load;
24
+ /** Persist the org-level context to the Knowledge Hub application scope. */
25
+ Save(): Promise<void>;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<ClassifyOrgContextEditorComponent, never>;
27
+ static ɵcmp: i0.ɵɵComponentDeclaration<ClassifyOrgContextEditorComponent, "classify-org-context-editor", never, {}, {}, never, never, false, never>;
28
+ }
29
+ //# sourceMappingURL=classify-org-context-editor.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-org-context-editor.component.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/autotagging/components/classify-org-context-editor.component.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAOrE,qBAMa,iCAAkC,SAAQ,oBAAoB;IACvE,OAAO,CAAC,GAAG,CAA6B;IAExC,2CAA2C;IACpC,QAAQ,UAAS;IACxB,kDAAkD;IAC3C,WAAW,SAAM;IACxB,iEAAiE;IACjE,OAAO,CAAC,SAAS,CAAM;IACvB,iDAAiD;IAC1C,SAAS,UAAS;IACzB,sBAAsB;IACf,QAAQ,UAAS;IACxB,wCAAwC;IACxC,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,eAAe,CAAwC;IAE/D,+DAA+D;IAC/D,IAAW,iBAAiB,IAAI,OAAO,CAEtC;IAED,4EAA4E;IAC/D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAQtB,gCAAgC;YAchC,IAAI;IAuBlB,4EAA4E;IAC/D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;yCAtEzB,iCAAiC;2CAAjC,iCAAiC;CAiG7C"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @fileoverview Classify · Org-level domain-context editor.
3
+ *
4
+ * Reads and writes the `classify.org.context` `MJ: Application Settings` record
5
+ * scoped to the Knowledge Hub application. This is the ORG scope of the
6
+ * three-tier classification context (org → content-type → source) consumed by
7
+ * the server-side `ClassificationContextResolver` during autotagging.
8
+ *
9
+ * Persists via `ApplicationSettingEngine.SetSetting`, which creates the record
10
+ * if absent (or updates it) and checks the Save() result. The Knowledge Hub
11
+ * application ID is resolved by name via the threaded provider.
12
+ */
13
+ import { Component, ChangeDetectorRef, inject } from '@angular/core';
14
+ import { RunView } from '@memberjunction/core';
15
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
16
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
17
+ import { ApplicationSettingEngine } from '@memberjunction/core-entities';
18
+ import * as i0 from "@angular/core";
19
+ import * as i1 from "@angular/forms";
20
+ import * as i2 from "@memberjunction/ng-shared-generic";
21
+ import * as i3 from "@memberjunction/ng-ui-components";
22
+ function ClassifyOrgContextEditorComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
23
+ i0.ɵɵelementStart(0, "span", 4);
24
+ i0.ɵɵtext(1, "Unsaved");
25
+ i0.ɵɵelementEnd();
26
+ } }
27
+ function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
28
+ i0.ɵɵelement(0, "mj-loading", 7);
29
+ } }
30
+ function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
31
+ i0.ɵɵelement(0, "i", 12);
32
+ i0.ɵɵtext(1, " Saving\u2026 ");
33
+ } }
34
+ function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Conditional_6_Template(rf, ctx) { if (rf & 1) {
35
+ i0.ɵɵelement(0, "i", 13);
36
+ i0.ɵɵtext(1, " Save ");
37
+ } }
38
+ function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Template(rf, ctx) { if (rf & 1) {
39
+ const _r1 = i0.ɵɵgetCurrentView();
40
+ i0.ɵɵelementStart(0, "p", 8);
41
+ i0.ɵɵtext(1, " Applies to every source as the broadest classification-context scope. Source and content-type guidance combine with this at run time. ");
42
+ i0.ɵɵelementEnd();
43
+ i0.ɵɵelementStart(2, "textarea", 9);
44
+ i0.ɵɵtwoWayListener("ngModelChange", function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Template_textarea_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.ContextText, $event) || (ctx_r1.ContextText = $event); return i0.ɵɵresetView($event); });
45
+ i0.ɵɵelementEnd();
46
+ i0.ɵɵelementStart(3, "div", 10)(4, "button", 11);
47
+ i0.ɵɵlistener("click", function ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.Save()); });
48
+ i0.ɵɵconditionalCreate(5, ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Conditional_5_Template, 2, 0)(6, ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Conditional_6_Template, 2, 0);
49
+ i0.ɵɵelementEnd()();
50
+ } if (rf & 2) {
51
+ const ctx_r1 = i0.ɵɵnextContext(2);
52
+ i0.ɵɵadvance(2);
53
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.ContextText);
54
+ i0.ɵɵadvance(2);
55
+ i0.ɵɵproperty("disabled", ctx_r1.IsSaving || !ctx_r1.HasUnsavedChanges);
56
+ i0.ɵɵadvance();
57
+ i0.ɵɵconditional(ctx_r1.IsSaving ? 5 : 6);
58
+ } }
59
+ function ClassifyOrgContextEditorComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
60
+ i0.ɵɵelementStart(0, "div", 6);
61
+ i0.ɵɵconditionalCreate(1, ClassifyOrgContextEditorComponent_Conditional_7_Conditional_1_Template, 1, 0, "mj-loading", 7)(2, ClassifyOrgContextEditorComponent_Conditional_7_Conditional_2_Template, 7, 3);
62
+ i0.ɵɵelementEnd();
63
+ } if (rf & 2) {
64
+ const ctx_r1 = i0.ɵɵnextContext();
65
+ i0.ɵɵadvance();
66
+ i0.ɵɵconditional(ctx_r1.IsLoading ? 1 : 2);
67
+ } }
68
+ const KNOWLEDGE_HUB_APPLICATION_NAME = 'Knowledge Hub';
69
+ const CLASSIFY_ORG_CONTEXT_SETTING_KEY = 'classify.org.context';
70
+ export class ClassifyOrgContextEditorComponent extends BaseAngularComponent {
71
+ cdr = inject(ChangeDetectorRef);
72
+ /** Whether the editor body is expanded. */
73
+ Expanded = false;
74
+ /** The org-context text bound to the textarea. */
75
+ ContextText = '';
76
+ /** The value last loaded/saved, used to detect unsaved edits. */
77
+ savedText = '';
78
+ /** Loading spinner while reading the setting. */
79
+ IsLoading = false;
80
+ /** Saving spinner. */
81
+ IsSaving = false;
82
+ /** Whether the initial load has run. */
83
+ loaded = false;
84
+ khApplicationID = undefined;
85
+ /** True when the textarea differs from the persisted value. */
86
+ get HasUnsavedChanges() {
87
+ return this.ContextText !== this.savedText;
88
+ }
89
+ /** Toggle the editor open/closed, lazy-loading the value the first time. */
90
+ async Toggle() {
91
+ this.Expanded = !this.Expanded;
92
+ if (this.Expanded && !this.loaded) {
93
+ await this.load();
94
+ }
95
+ this.cdr.detectChanges();
96
+ }
97
+ async resolveKnowledgeHubApplicationID() {
98
+ if (this.khApplicationID !== undefined)
99
+ return this.khApplicationID;
100
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
101
+ const result = await rv.RunView({
102
+ EntityName: 'MJ: Applications',
103
+ ExtraFilter: `Name = '${KNOWLEDGE_HUB_APPLICATION_NAME.replace(/'/g, "''")}'`,
104
+ Fields: ['ID'],
105
+ MaxRows: 1,
106
+ ResultType: 'simple',
107
+ });
108
+ this.khApplicationID = result.Success && result.Results.length > 0 ? result.Results[0].ID : null;
109
+ return this.khApplicationID;
110
+ }
111
+ async load() {
112
+ this.IsLoading = true;
113
+ this.cdr.detectChanges();
114
+ try {
115
+ const p = this.ProviderToUse;
116
+ await ApplicationSettingEngine.Instance.Config(false, p.CurrentUser, p);
117
+ const appID = await this.resolveKnowledgeHubApplicationID();
118
+ const raw = ApplicationSettingEngine.Instance.GetSetting(CLASSIFY_ORG_CONTEXT_SETTING_KEY, appID ?? undefined);
119
+ this.ContextText = raw ?? '';
120
+ this.savedText = this.ContextText;
121
+ this.loaded = true;
122
+ }
123
+ catch (error) {
124
+ const msg = error instanceof Error ? error.message : String(error);
125
+ MJNotificationService.Instance.CreateSimpleNotification(`Failed to load org context: ${msg}`, 'error', 4000);
126
+ }
127
+ finally {
128
+ this.IsLoading = false;
129
+ this.cdr.detectChanges();
130
+ }
131
+ }
132
+ /** Persist the org-level context to the Knowledge Hub application scope. */
133
+ async Save() {
134
+ if (this.IsSaving)
135
+ return;
136
+ this.IsSaving = true;
137
+ this.cdr.detectChanges();
138
+ try {
139
+ const p = this.ProviderToUse;
140
+ const appID = await this.resolveKnowledgeHubApplicationID();
141
+ const ok = await ApplicationSettingEngine.Instance.SetSetting(CLASSIFY_ORG_CONTEXT_SETTING_KEY, this.ContextText, appID ?? undefined, p.CurrentUser);
142
+ if (ok) {
143
+ this.savedText = this.ContextText;
144
+ MJNotificationService.Instance.CreateSimpleNotification('Org context saved', 'success', 2500);
145
+ }
146
+ else {
147
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to save org context', 'error', 4000);
148
+ }
149
+ }
150
+ catch (error) {
151
+ const msg = error instanceof Error ? error.message : String(error);
152
+ MJNotificationService.Instance.CreateSimpleNotification(`Error: ${msg}`, 'error', 4000);
153
+ }
154
+ finally {
155
+ this.IsSaving = false;
156
+ this.cdr.detectChanges();
157
+ }
158
+ }
159
+ static ɵfac = /*@__PURE__*/ (() => { let ɵClassifyOrgContextEditorComponent_BaseFactory; return function ClassifyOrgContextEditorComponent_Factory(__ngFactoryType__) { return (ɵClassifyOrgContextEditorComponent_BaseFactory || (ɵClassifyOrgContextEditorComponent_BaseFactory = i0.ɵɵgetInheritedFactory(ClassifyOrgContextEditorComponent)))(__ngFactoryType__ || ClassifyOrgContextEditorComponent); }; })();
160
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ClassifyOrgContextEditorComponent, selectors: [["classify-org-context-editor"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 8, vars: 6, consts: [[1, "cls-org-ctx"], [1, "cls-org-ctx-head", 3, "click"], [1, "fa-solid", "fa-building"], [1, "cls-org-ctx-title"], [1, "cls-org-ctx-dirty"], [1, "fa-solid"], [1, "cls-org-ctx-body"], ["text", "Loading org context\u2026", "size", "small"], [1, "cls-org-ctx-help"], ["rows", "5", "placeholder", "Org-wide guidance for the classifier \u2014 your organization's domain, audience, taxonomy conventions\u2026", 1, "mj-textarea", 3, "ngModelChange", "ngModel"], [1, "cls-org-ctx-actions"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-check"]], template: function ClassifyOrgContextEditorComponent_Template(rf, ctx) { if (rf & 1) {
161
+ i0.ɵɵelementStart(0, "div", 0)(1, "button", 1);
162
+ i0.ɵɵlistener("click", function ClassifyOrgContextEditorComponent_Template_button_click_1_listener() { return ctx.Toggle(); });
163
+ i0.ɵɵelement(2, "i", 2);
164
+ i0.ɵɵelementStart(3, "span", 3);
165
+ i0.ɵɵtext(4, "Organization domain context");
166
+ i0.ɵɵelementEnd();
167
+ i0.ɵɵconditionalCreate(5, ClassifyOrgContextEditorComponent_Conditional_5_Template, 2, 0, "span", 4);
168
+ i0.ɵɵelement(6, "i", 5);
169
+ i0.ɵɵelementEnd();
170
+ i0.ɵɵconditionalCreate(7, ClassifyOrgContextEditorComponent_Conditional_7_Template, 3, 1, "div", 6);
171
+ i0.ɵɵelementEnd();
172
+ } if (rf & 2) {
173
+ i0.ɵɵadvance(5);
174
+ i0.ɵɵconditional(ctx.HasUnsavedChanges ? 5 : -1);
175
+ i0.ɵɵadvance();
176
+ i0.ɵɵclassProp("fa-chevron-down", !ctx.Expanded)("fa-chevron-up", ctx.Expanded);
177
+ i0.ɵɵadvance();
178
+ i0.ɵɵconditional(ctx.Expanded ? 7 : -1);
179
+ } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.LoadingComponent, i3.MJButtonDirective], styles: [".cls-org-ctx[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.cls-org-ctx-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 12px 14px;\n background: var(--mj-bg-surface-card);\n border: none;\n cursor: pointer;\n font-size: 0.88rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: left;\n}\n.cls-org-ctx-head[_ngcontent-%COMP%] > i[_ngcontent-%COMP%]:first-child { color: var(--mj-brand-primary); }\n.cls-org-ctx-title[_ngcontent-%COMP%] { flex: 1; }\n.cls-org-ctx-dirty[_ngcontent-%COMP%] {\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-status-warning-text);\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-warning-border, var(--mj-border-default));\n border-radius: 999px;\n padding: 2px 8px;\n}\n.cls-org-ctx-body[_ngcontent-%COMP%] {\n padding: 14px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n.cls-org-ctx-help[_ngcontent-%COMP%] {\n margin: 0 0 10px;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.45;\n}\n.cls-org-ctx-actions[_ngcontent-%COMP%] {\n margin-top: 10px;\n display: flex;\n gap: 8px;\n}"] });
180
+ }
181
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClassifyOrgContextEditorComponent, [{
182
+ type: Component,
183
+ args: [{ standalone: false, selector: 'classify-org-context-editor', template: "<div class=\"cls-org-ctx\">\n <button class=\"cls-org-ctx-head\" (click)=\"Toggle()\">\n <i class=\"fa-solid fa-building\"></i>\n <span class=\"cls-org-ctx-title\">Organization domain context</span>\n @if (HasUnsavedChanges) {\n <span class=\"cls-org-ctx-dirty\">Unsaved</span>\n }\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!Expanded\" [class.fa-chevron-up]=\"Expanded\"></i>\n </button>\n\n @if (Expanded) {\n <div class=\"cls-org-ctx-body\">\n @if (IsLoading) {\n <mj-loading text=\"Loading org context\u2026\" size=\"small\"></mj-loading>\n } @else {\n <p class=\"cls-org-ctx-help\">\n Applies to every source as the broadest classification-context scope. Source and\n content-type guidance combine with this at run time.\n </p>\n <textarea class=\"mj-textarea\" rows=\"5\"\n [(ngModel)]=\"ContextText\"\n placeholder=\"Org-wide guidance for the classifier \u2014 your organization's domain, audience, taxonomy conventions\u2026\"></textarea>\n <div class=\"cls-org-ctx-actions\">\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"Save()\"\n [disabled]=\"IsSaving || !HasUnsavedChanges\">\n @if (IsSaving) { <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving\u2026 }\n @else { <i class=\"fa-solid fa-check\"></i> Save }\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".cls-org-ctx {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.cls-org-ctx-head {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 12px 14px;\n background: var(--mj-bg-surface-card);\n border: none;\n cursor: pointer;\n font-size: 0.88rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: left;\n}\n.cls-org-ctx-head > i:first-child { color: var(--mj-brand-primary); }\n.cls-org-ctx-title { flex: 1; }\n.cls-org-ctx-dirty {\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-status-warning-text);\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-warning-border, var(--mj-border-default));\n border-radius: 999px;\n padding: 2px 8px;\n}\n.cls-org-ctx-body {\n padding: 14px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n.cls-org-ctx-help {\n margin: 0 0 10px;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.45;\n}\n.cls-org-ctx-actions {\n margin-top: 10px;\n display: flex;\n gap: 8px;\n}\n"] }]
184
+ }], null, null); })();
185
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ClassifyOrgContextEditorComponent, { className: "ClassifyOrgContextEditorComponent", filePath: "src/AI/components/autotagging/components/classify-org-context-editor.component.ts", lineNumber: 28 }); })();
186
+ //# sourceMappingURL=classify-org-context-editor.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-org-context-editor.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/autotagging/components/classify-org-context-editor.component.ts","../../../../../src/AI/components/autotagging/components/classify-org-context-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Classify · Org-level domain-context editor.\n *\n * Reads and writes the `classify.org.context` `MJ: Application Settings` record\n * scoped to the Knowledge Hub application. This is the ORG scope of the\n * three-tier classification context (org → content-type → source) consumed by\n * the server-side `ClassificationContextResolver` during autotagging.\n *\n * Persists via `ApplicationSettingEngine.SetSetting`, which creates the record\n * if absent (or updates it) and checks the Save() result. The Knowledge Hub\n * application ID is resolved by name via the threaded provider.\n */\nimport { Component, ChangeDetectorRef, inject } from '@angular/core';\nimport { RunView } from '@memberjunction/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { ApplicationSettingEngine } from '@memberjunction/core-entities';\n\nconst KNOWLEDGE_HUB_APPLICATION_NAME = 'Knowledge Hub';\nconst CLASSIFY_ORG_CONTEXT_SETTING_KEY = 'classify.org.context';\n\n@Component({\n standalone: false,\n selector: 'classify-org-context-editor',\n templateUrl: './classify-org-context-editor.component.html',\n styleUrls: ['./classify-org-context-editor.component.css']\n})\nexport class ClassifyOrgContextEditorComponent extends BaseAngularComponent {\n private cdr = inject(ChangeDetectorRef);\n\n /** Whether the editor body is expanded. */\n public Expanded = false;\n /** The org-context text bound to the textarea. */\n public ContextText = '';\n /** The value last loaded/saved, used to detect unsaved edits. */\n private savedText = '';\n /** Loading spinner while reading the setting. */\n public IsLoading = false;\n /** Saving spinner. */\n public IsSaving = false;\n /** Whether the initial load has run. */\n private loaded = false;\n\n private khApplicationID: string | null | undefined = undefined;\n\n /** True when the textarea differs from the persisted value. */\n public get HasUnsavedChanges(): boolean {\n return this.ContextText !== this.savedText;\n }\n\n /** Toggle the editor open/closed, lazy-loading the value the first time. */\n public async Toggle(): Promise<void> {\n this.Expanded = !this.Expanded;\n if (this.Expanded && !this.loaded) {\n await this.load();\n }\n this.cdr.detectChanges();\n }\n\n private async resolveKnowledgeHubApplicationID(): Promise<string | null> {\n if (this.khApplicationID !== undefined) return this.khApplicationID;\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<{ ID: string }>({\n EntityName: 'MJ: Applications',\n ExtraFilter: `Name = '${KNOWLEDGE_HUB_APPLICATION_NAME.replace(/'/g, \"''\")}'`,\n Fields: ['ID'],\n MaxRows: 1,\n ResultType: 'simple',\n });\n this.khApplicationID = result.Success && result.Results.length > 0 ? result.Results[0].ID : null;\n return this.khApplicationID;\n }\n\n private async load(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n try {\n const p = this.ProviderToUse;\n await ApplicationSettingEngine.Instance.Config(false, p.CurrentUser, p);\n const appID = await this.resolveKnowledgeHubApplicationID();\n const raw = ApplicationSettingEngine.Instance.GetSetting(\n CLASSIFY_ORG_CONTEXT_SETTING_KEY,\n appID ?? undefined,\n );\n this.ContextText = raw ?? '';\n this.savedText = this.ContextText;\n this.loaded = true;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n MJNotificationService.Instance.CreateSimpleNotification(`Failed to load org context: ${msg}`, 'error', 4000);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n /** Persist the org-level context to the Knowledge Hub application scope. */\n public async Save(): Promise<void> {\n if (this.IsSaving) return;\n this.IsSaving = true;\n this.cdr.detectChanges();\n try {\n const p = this.ProviderToUse;\n const appID = await this.resolveKnowledgeHubApplicationID();\n const ok = await ApplicationSettingEngine.Instance.SetSetting(\n CLASSIFY_ORG_CONTEXT_SETTING_KEY,\n this.ContextText,\n appID ?? undefined,\n p.CurrentUser,\n );\n if (ok) {\n this.savedText = this.ContextText;\n MJNotificationService.Instance.CreateSimpleNotification('Org context saved', 'success', 2500);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to save org context', 'error', 4000);\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n MJNotificationService.Instance.CreateSimpleNotification(`Error: ${msg}`, 'error', 4000);\n } finally {\n this.IsSaving = false;\n this.cdr.detectChanges();\n }\n }\n}\n","<div class=\"cls-org-ctx\">\n <button class=\"cls-org-ctx-head\" (click)=\"Toggle()\">\n <i class=\"fa-solid fa-building\"></i>\n <span class=\"cls-org-ctx-title\">Organization domain context</span>\n @if (HasUnsavedChanges) {\n <span class=\"cls-org-ctx-dirty\">Unsaved</span>\n }\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!Expanded\" [class.fa-chevron-up]=\"Expanded\"></i>\n </button>\n\n @if (Expanded) {\n <div class=\"cls-org-ctx-body\">\n @if (IsLoading) {\n <mj-loading text=\"Loading org context…\" size=\"small\"></mj-loading>\n } @else {\n <p class=\"cls-org-ctx-help\">\n Applies to every source as the broadest classification-context scope. Source and\n content-type guidance combine with this at run time.\n </p>\n <textarea class=\"mj-textarea\" rows=\"5\"\n [(ngModel)]=\"ContextText\"\n placeholder=\"Org-wide guidance for the classifier — your organization's domain, audience, taxonomy conventions…\"></textarea>\n <div class=\"cls-org-ctx-actions\">\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"Save()\"\n [disabled]=\"IsSaving || !HasUnsavedChanges\">\n @if (IsSaving) { <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving… }\n @else { <i class=\"fa-solid fa-check\"></i> Save }\n </button>\n </div>\n }\n </div>\n }\n</div>\n"]}