@memberjunction/ng-dashboards 5.33.0 → 5.34.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.
- package/dist/AI/components/agents/agent-editor.component.js +2 -2
- package/dist/AI/components/agents/agent-editor.component.js.map +1 -1
- package/dist/ComponentStudio/components/workspace/component-preview.component.js +1 -1
- package/dist/ComponentStudio/components/workspace/component-preview.component.js.map +1 -1
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts +28 -8
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts.map +1 -1
- package/dist/ComponentStudio/services/component-studio-state.service.js +45 -27
- package/dist/ComponentStudio/services/component-studio-state.service.js.map +1 -1
- package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.d.ts +18 -3
- package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.d.ts.map +1 -1
- package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.js +51 -11
- package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +2 -2
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/Integration/components/widgets/integration-card.component.js +2 -2
- package/dist/Integration/components/widgets/integration-card.component.js.map +1 -1
- package/dist/Integration/components/widgets/run-history-panel.component.js +2 -2
- package/dist/Integration/components/widgets/run-history-panel.component.js.map +1 -1
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts +134 -1
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +1227 -24
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
- package/dist/SystemDiagnostics/system-diagnostics.component.d.ts +4 -0
- package/dist/SystemDiagnostics/system-diagnostics.component.d.ts.map +1 -1
- package/dist/SystemDiagnostics/system-diagnostics.component.js +106 -68
- package/dist/SystemDiagnostics/system-diagnostics.component.js.map +1 -1
- package/dist/Testing/components/testing-runs.component.js +3 -3
- package/dist/Testing/components/testing-runs.component.js.map +1 -1
- package/package.json +52 -52
|
@@ -13,7 +13,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
13
13
|
*/
|
|
14
14
|
import { Component, ChangeDetectorRef, inject } from '@angular/core';
|
|
15
15
|
import { Subject } from 'rxjs';
|
|
16
|
-
import { RunView } from '@memberjunction/core';
|
|
16
|
+
import { CompositeKey, RunView, LogError } from '@memberjunction/core';
|
|
17
17
|
import { KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
|
|
18
18
|
import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
|
|
19
19
|
import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
|
|
@@ -22,9 +22,14 @@ import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
|
22
22
|
import * as i0 from "@angular/core";
|
|
23
23
|
import * as i1 from "@angular/forms";
|
|
24
24
|
import * as i2 from "@memberjunction/ng-shared-generic";
|
|
25
|
-
import * as i3 from "
|
|
25
|
+
import * as i3 from "@memberjunction/ng-search";
|
|
26
|
+
import * as i4 from "../scheduling/scheduling-resource.component";
|
|
27
|
+
import * as i5 from "@angular/common";
|
|
26
28
|
const _forTrack0 = ($index, $item) => $item.ID;
|
|
27
29
|
const _forTrack1 = ($index, $item) => $item.EntityName;
|
|
30
|
+
const _forTrack2 = ($index, $item) => $item.ScopeID;
|
|
31
|
+
const _forTrack3 = ($index, $item) => $item.Reranker;
|
|
32
|
+
const _forTrack4 = ($index, $item) => $item.Reason;
|
|
28
33
|
function KnowledgeConfigResourceComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
29
34
|
i0.ɵɵelementStart(0, "div", 0);
|
|
30
35
|
i0.ɵɵelement(1, "mj-loading", 2);
|
|
@@ -706,37 +711,761 @@ function KnowledgeConfigResourceComponent_Conditional_1_Conditional_13_Template(
|
|
|
706
711
|
i0.ɵɵadvance();
|
|
707
712
|
i0.ɵɵtwoWayProperty("ngModel", ctx_r2.ThresholdSettings.AutotagConfidence);
|
|
708
713
|
} }
|
|
714
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
715
|
+
i0.ɵɵelementStart(0, "div", 115);
|
|
716
|
+
i0.ɵɵtext(1, "Loading\u2026");
|
|
717
|
+
i0.ɵɵelementEnd();
|
|
718
|
+
} }
|
|
719
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_15_Template(rf, ctx) { if (rf & 1) {
|
|
720
|
+
i0.ɵɵelementStart(0, "div", 116);
|
|
721
|
+
i0.ɵɵtext(1, " No scopes defined yet. Click ");
|
|
722
|
+
i0.ɵɵelementStart(2, "strong");
|
|
723
|
+
i0.ɵɵtext(3, "New");
|
|
724
|
+
i0.ɵɵelementEnd();
|
|
725
|
+
i0.ɵɵtext(4, " to create one. ");
|
|
726
|
+
i0.ɵɵelementEnd();
|
|
727
|
+
} }
|
|
728
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
729
|
+
i0.ɵɵelementStart(0, "span", 123);
|
|
730
|
+
i0.ɵɵtext(1, "Global");
|
|
731
|
+
i0.ɵɵelementEnd();
|
|
732
|
+
} }
|
|
733
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
734
|
+
i0.ɵɵelementStart(0, "span", 123);
|
|
735
|
+
i0.ɵɵtext(1, "Default");
|
|
736
|
+
i0.ɵɵelementEnd();
|
|
737
|
+
} }
|
|
738
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
739
|
+
const _r22 = i0.ɵɵgetCurrentView();
|
|
740
|
+
i0.ɵɵelementStart(0, "button", 120);
|
|
741
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Template_button_click_0_listener() { const s_r23 = i0.ɵɵrestoreView(_r22).$implicit; const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.SelectScope(s_r23.ID)); });
|
|
742
|
+
i0.ɵɵelement(1, "i", 121);
|
|
743
|
+
i0.ɵɵelementStart(2, "span", 122);
|
|
744
|
+
i0.ɵɵtext(3);
|
|
745
|
+
i0.ɵɵelementEnd();
|
|
746
|
+
i0.ɵɵconditionalCreate(4, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Conditional_4_Template, 2, 0, "span", 123)(5, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Conditional_5_Template, 2, 0, "span", 123);
|
|
747
|
+
i0.ɵɵelementEnd();
|
|
748
|
+
} if (rf & 2) {
|
|
749
|
+
const s_r23 = ctx.$implicit;
|
|
750
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
751
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeID === s_r23.ID);
|
|
752
|
+
i0.ɵɵadvance();
|
|
753
|
+
i0.ɵɵclassMap(s_r23.Icon || "fa-solid fa-filter");
|
|
754
|
+
i0.ɵɵadvance(2);
|
|
755
|
+
i0.ɵɵtextInterpolate(s_r23.Name);
|
|
756
|
+
i0.ɵɵadvance();
|
|
757
|
+
i0.ɵɵconditional(s_r23.IsGlobal ? 4 : s_r23.IsDefault ? 5 : -1);
|
|
758
|
+
} }
|
|
759
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_Template(rf, ctx) { if (rf & 1) {
|
|
760
|
+
i0.ɵɵrepeaterCreate(0, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_For_1_Template, 6, 6, "button", 119, _forTrack0);
|
|
761
|
+
} if (rf & 2) {
|
|
762
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
763
|
+
i0.ɵɵrepeater(ctx_r2.SearchScopes);
|
|
764
|
+
} }
|
|
765
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
766
|
+
const _r25 = i0.ɵɵgetCurrentView();
|
|
767
|
+
i0.ɵɵelementStart(0, "button", 136);
|
|
768
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_14_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r25); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.OpenActiveScopeFullForm()); });
|
|
769
|
+
i0.ɵɵelement(1, "i", 137);
|
|
770
|
+
i0.ɵɵtext(2, " Open Full Form ");
|
|
771
|
+
i0.ɵɵelementEnd();
|
|
772
|
+
} }
|
|
773
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_18_Template(rf, ctx) { if (rf & 1) {
|
|
774
|
+
const _r26 = i0.ɵɵgetCurrentView();
|
|
775
|
+
i0.ɵɵelementStart(0, "button", 138);
|
|
776
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_18_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r26); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.DeleteActiveScope()); });
|
|
777
|
+
i0.ɵɵelement(1, "i", 139);
|
|
778
|
+
i0.ɵɵelementEnd();
|
|
779
|
+
} }
|
|
780
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template(rf, ctx) { if (rf & 1) {
|
|
781
|
+
const _r27 = i0.ɵɵgetCurrentView();
|
|
782
|
+
i0.ɵɵelementStart(0, "div", 131)(1, "label")(2, "span");
|
|
783
|
+
i0.ɵɵtext(3, "Name");
|
|
784
|
+
i0.ɵɵelementEnd();
|
|
785
|
+
i0.ɵɵelementStart(4, "input", 140);
|
|
786
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_input_change_4_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.Name = $event.target.value); });
|
|
787
|
+
i0.ɵɵelementEnd()();
|
|
788
|
+
i0.ɵɵelementStart(5, "label")(6, "span");
|
|
789
|
+
i0.ɵɵtext(7, "Icon (Font Awesome class)");
|
|
790
|
+
i0.ɵɵelementEnd();
|
|
791
|
+
i0.ɵɵelementStart(8, "input", 140);
|
|
792
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_input_change_8_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.Icon = $event.target.value); });
|
|
793
|
+
i0.ɵɵelementEnd()();
|
|
794
|
+
i0.ɵɵelementStart(9, "label", 141)(10, "span");
|
|
795
|
+
i0.ɵɵtext(11, "Description");
|
|
796
|
+
i0.ɵɵelementEnd();
|
|
797
|
+
i0.ɵɵelementStart(12, "textarea", 142);
|
|
798
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_textarea_change_12_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.Description = $event.target.value); });
|
|
799
|
+
i0.ɵɵelementEnd()();
|
|
800
|
+
i0.ɵɵelementStart(13, "label")(14, "span");
|
|
801
|
+
i0.ɵɵtext(15, "Status");
|
|
802
|
+
i0.ɵɵelementEnd();
|
|
803
|
+
i0.ɵɵelementStart(16, "select", 143);
|
|
804
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_select_change_16_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.Status = $event.target.value); });
|
|
805
|
+
i0.ɵɵelementStart(17, "option", 144);
|
|
806
|
+
i0.ɵɵtext(18, "Active");
|
|
807
|
+
i0.ɵɵelementEnd();
|
|
808
|
+
i0.ɵɵelementStart(19, "option", 145);
|
|
809
|
+
i0.ɵɵtext(20, "Inactive");
|
|
810
|
+
i0.ɵɵelementEnd()()();
|
|
811
|
+
i0.ɵɵelementStart(21, "label")(22, "span");
|
|
812
|
+
i0.ɵɵtext(23, "Start at");
|
|
813
|
+
i0.ɵɵelementEnd();
|
|
814
|
+
i0.ɵɵelementStart(24, "input", 146);
|
|
815
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_input_change_24_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.SetScopeDate(ctx_r2.ActiveScope, "StartAt", $event.target.value)); });
|
|
816
|
+
i0.ɵɵelementEnd()();
|
|
817
|
+
i0.ɵɵelementStart(25, "label")(26, "span");
|
|
818
|
+
i0.ɵɵtext(27, "End at");
|
|
819
|
+
i0.ɵɵelementEnd();
|
|
820
|
+
i0.ɵɵelementStart(28, "input", 146);
|
|
821
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_input_change_28_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.SetScopeDate(ctx_r2.ActiveScope, "EndAt", $event.target.value)); });
|
|
822
|
+
i0.ɵɵelementEnd()();
|
|
823
|
+
i0.ɵɵelementStart(29, "label", 147)(30, "input", 148);
|
|
824
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_input_change_30_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.IsDefault = $event.target.checked); });
|
|
825
|
+
i0.ɵɵelementEnd();
|
|
826
|
+
i0.ɵɵelementStart(31, "span");
|
|
827
|
+
i0.ɵɵtext(32, "Default scope \u2014 picked when users/agents don't specify one");
|
|
828
|
+
i0.ɵɵelementEnd()();
|
|
829
|
+
i0.ɵɵelementStart(33, "label", 147);
|
|
830
|
+
i0.ɵɵelement(34, "input", 149);
|
|
831
|
+
i0.ɵɵelementStart(35, "span");
|
|
832
|
+
i0.ɵɵtext(36, "Global \u2014 reserved for the built-in Global scope (read-only)");
|
|
833
|
+
i0.ɵɵelementEnd()();
|
|
834
|
+
i0.ɵɵelementStart(37, "label", 141)(38, "span");
|
|
835
|
+
i0.ɵɵtext(39, "Scope Config (JSON)");
|
|
836
|
+
i0.ɵɵelementEnd();
|
|
837
|
+
i0.ɵɵelementStart(40, "textarea", 150);
|
|
838
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_textarea_change_40_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.ScopeConfig = $event.target.value); });
|
|
839
|
+
i0.ɵɵelementEnd()();
|
|
840
|
+
i0.ɵɵelementStart(41, "label", 141)(42, "span");
|
|
841
|
+
i0.ɵɵtext(43, "Search Context Config (JSON)");
|
|
842
|
+
i0.ɵɵelementEnd();
|
|
843
|
+
i0.ɵɵelementStart(44, "textarea", 150);
|
|
844
|
+
i0.ɵɵlistener("change", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template_textarea_change_44_listener($event) { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ActiveScope.SearchContextConfig = $event.target.value); });
|
|
845
|
+
i0.ɵɵelementEnd()()();
|
|
846
|
+
} if (rf & 2) {
|
|
847
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
848
|
+
i0.ɵɵadvance(4);
|
|
849
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.Name);
|
|
850
|
+
i0.ɵɵadvance(4);
|
|
851
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.Icon || "");
|
|
852
|
+
i0.ɵɵadvance(4);
|
|
853
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.Description || "");
|
|
854
|
+
i0.ɵɵadvance(4);
|
|
855
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.Status);
|
|
856
|
+
i0.ɵɵadvance(8);
|
|
857
|
+
i0.ɵɵproperty("value", ctx_r2.FormatScopeDate(ctx_r2.ActiveScope.StartAt));
|
|
858
|
+
i0.ɵɵadvance(4);
|
|
859
|
+
i0.ɵɵproperty("value", ctx_r2.FormatScopeDate(ctx_r2.ActiveScope.EndAt));
|
|
860
|
+
i0.ɵɵadvance(2);
|
|
861
|
+
i0.ɵɵproperty("checked", ctx_r2.ActiveScope.IsDefault)("disabled", ctx_r2.ActiveScope.IsGlobal);
|
|
862
|
+
i0.ɵɵadvance(4);
|
|
863
|
+
i0.ɵɵproperty("checked", ctx_r2.ActiveScope.IsGlobal);
|
|
864
|
+
i0.ɵɵadvance(6);
|
|
865
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.ScopeConfig || "");
|
|
866
|
+
i0.ɵɵadvance(4);
|
|
867
|
+
i0.ɵɵproperty("value", ctx_r2.ActiveScope.SearchContextConfig || "");
|
|
868
|
+
} }
|
|
869
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_20_Template(rf, ctx) { if (rf & 1) {
|
|
870
|
+
i0.ɵɵelement(0, "mj-search-scope-child-grid", 132);
|
|
871
|
+
} if (rf & 2) {
|
|
872
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
873
|
+
i0.ɵɵproperty("ParentID", ctx_r2.ActiveScopeID)("Columns", ctx_r2.ScopeProviderColumns);
|
|
874
|
+
} }
|
|
875
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_21_Template(rf, ctx) { if (rf & 1) {
|
|
876
|
+
i0.ɵɵelement(0, "mj-search-scope-child-grid", 133);
|
|
877
|
+
} if (rf & 2) {
|
|
878
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
879
|
+
i0.ɵɵproperty("ParentID", ctx_r2.ActiveScopeID)("Columns", ctx_r2.ScopeExternalIndexColumns);
|
|
880
|
+
} }
|
|
881
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_22_Template(rf, ctx) { if (rf & 1) {
|
|
882
|
+
i0.ɵɵelement(0, "mj-search-scope-child-grid", 134);
|
|
883
|
+
} if (rf & 2) {
|
|
884
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
885
|
+
i0.ɵɵproperty("ParentID", ctx_r2.ActiveScopeID)("Columns", ctx_r2.ScopeEntityColumns);
|
|
886
|
+
} }
|
|
887
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_23_Template(rf, ctx) { if (rf & 1) {
|
|
888
|
+
i0.ɵɵelement(0, "mj-search-scope-child-grid", 135);
|
|
889
|
+
} if (rf & 2) {
|
|
890
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
891
|
+
i0.ɵɵproperty("ParentID", ctx_r2.ActiveScopeID)("Columns", ctx_r2.ScopeStorageColumns);
|
|
892
|
+
} }
|
|
893
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_24_Template(rf, ctx) { if (rf & 1) {
|
|
894
|
+
i0.ɵɵelementStart(0, "div", 151)(1, "p", 152);
|
|
895
|
+
i0.ɵɵelement(2, "i", 153);
|
|
896
|
+
i0.ɵɵelementStart(3, "strong");
|
|
897
|
+
i0.ɵɵtext(4, "Per-user / per-role grants on this scope.");
|
|
898
|
+
i0.ɵɵelementEnd();
|
|
899
|
+
i0.ɵɵtext(5, " Each row binds ");
|
|
900
|
+
i0.ɵɵelementStart(6, "em");
|
|
901
|
+
i0.ɵɵtext(7, "either");
|
|
902
|
+
i0.ɵɵelementEnd();
|
|
903
|
+
i0.ɵɵtext(8, " a User ");
|
|
904
|
+
i0.ɵɵelementStart(9, "em");
|
|
905
|
+
i0.ɵɵtext(10, "or");
|
|
906
|
+
i0.ɵɵelementEnd();
|
|
907
|
+
i0.ɵɵtext(11, " a Role (not both). Levels: ");
|
|
908
|
+
i0.ɵɵelementStart(12, "code");
|
|
909
|
+
i0.ɵɵtext(13, "None");
|
|
910
|
+
i0.ɵɵelementEnd();
|
|
911
|
+
i0.ɵɵtext(14, " = explicit deny that overrides role grants, ");
|
|
912
|
+
i0.ɵɵelementStart(15, "code");
|
|
913
|
+
i0.ɵɵtext(16, "Read");
|
|
914
|
+
i0.ɵɵelementEnd();
|
|
915
|
+
i0.ɵɵtext(17, " = view scope metadata, ");
|
|
916
|
+
i0.ɵɵelementStart(18, "code");
|
|
917
|
+
i0.ɵɵtext(19, "Search");
|
|
918
|
+
i0.ɵɵelementEnd();
|
|
919
|
+
i0.ɵɵtext(20, " = invoke ScopedSearchAction, ");
|
|
920
|
+
i0.ɵɵelementStart(21, "code");
|
|
921
|
+
i0.ɵɵtext(22, "Manage");
|
|
922
|
+
i0.ɵɵelementEnd();
|
|
923
|
+
i0.ɵɵtext(23, " = full edit including authoring permissions. Combined with ");
|
|
924
|
+
i0.ɵɵelementStart(24, "code");
|
|
925
|
+
i0.ɵɵtext(25, "AIAgent.SearchScopeAccess");
|
|
926
|
+
i0.ɵɵelementEnd();
|
|
927
|
+
i0.ɵɵtext(26, " via the resolver \u2014 see the spec for the full resolution order. ");
|
|
928
|
+
i0.ɵɵelementEnd()();
|
|
929
|
+
i0.ɵɵelement(27, "mj-search-scope-child-grid", 154);
|
|
930
|
+
} if (rf & 2) {
|
|
931
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
932
|
+
i0.ɵɵadvance(27);
|
|
933
|
+
i0.ɵɵproperty("ParentID", ctx_r2.ActiveScopeID)("Columns", ctx_r2.ScopePermissionColumns);
|
|
934
|
+
} }
|
|
935
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template(rf, ctx) { if (rf & 1) {
|
|
936
|
+
const _r24 = i0.ɵɵgetCurrentView();
|
|
937
|
+
i0.ɵɵelementStart(0, "div", 124)(1, "button", 125);
|
|
938
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("definition")); });
|
|
939
|
+
i0.ɵɵtext(2, "Definition");
|
|
940
|
+
i0.ɵɵelementEnd();
|
|
941
|
+
i0.ɵɵelementStart(3, "button", 125);
|
|
942
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("providers")); });
|
|
943
|
+
i0.ɵɵtext(4, "Providers");
|
|
944
|
+
i0.ɵɵelementEnd();
|
|
945
|
+
i0.ɵɵelementStart(5, "button", 125);
|
|
946
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("indexes")); });
|
|
947
|
+
i0.ɵɵtext(6, "External Indexes");
|
|
948
|
+
i0.ɵɵelementEnd();
|
|
949
|
+
i0.ɵɵelementStart(7, "button", 125);
|
|
950
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("entities")); });
|
|
951
|
+
i0.ɵɵtext(8, "Entities");
|
|
952
|
+
i0.ɵɵelementEnd();
|
|
953
|
+
i0.ɵɵelementStart(9, "button", 125);
|
|
954
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("storage")); });
|
|
955
|
+
i0.ɵɵtext(10, "Storage");
|
|
956
|
+
i0.ɵɵelementEnd();
|
|
957
|
+
i0.ɵɵelementStart(11, "button", 125);
|
|
958
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SelectScopeTab("permissions")); });
|
|
959
|
+
i0.ɵɵtext(12, "Permissions");
|
|
960
|
+
i0.ɵɵelementEnd();
|
|
961
|
+
i0.ɵɵelement(13, "div", 126);
|
|
962
|
+
i0.ɵɵconditionalCreate(14, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_14_Template, 3, 0, "button", 127);
|
|
963
|
+
i0.ɵɵelementStart(15, "button", 128);
|
|
964
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template_button_click_15_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.SaveActiveScope()); });
|
|
965
|
+
i0.ɵɵelement(16, "i", 129);
|
|
966
|
+
i0.ɵɵtext(17, " Save ");
|
|
967
|
+
i0.ɵɵelementEnd();
|
|
968
|
+
i0.ɵɵconditionalCreate(18, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_18_Template, 2, 0, "button", 130);
|
|
969
|
+
i0.ɵɵelementEnd();
|
|
970
|
+
i0.ɵɵconditionalCreate(19, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_19_Template, 45, 11, "div", 131);
|
|
971
|
+
i0.ɵɵconditionalCreate(20, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_20_Template, 1, 2, "mj-search-scope-child-grid", 132);
|
|
972
|
+
i0.ɵɵconditionalCreate(21, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_21_Template, 1, 2, "mj-search-scope-child-grid", 133);
|
|
973
|
+
i0.ɵɵconditionalCreate(22, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_22_Template, 1, 2, "mj-search-scope-child-grid", 134);
|
|
974
|
+
i0.ɵɵconditionalCreate(23, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_23_Template, 1, 2, "mj-search-scope-child-grid", 135);
|
|
975
|
+
i0.ɵɵconditionalCreate(24, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Conditional_24_Template, 28, 2);
|
|
976
|
+
} if (rf & 2) {
|
|
977
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
978
|
+
i0.ɵɵadvance();
|
|
979
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "definition");
|
|
980
|
+
i0.ɵɵadvance(2);
|
|
981
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "providers");
|
|
982
|
+
i0.ɵɵadvance(2);
|
|
983
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "indexes");
|
|
984
|
+
i0.ɵɵadvance(2);
|
|
985
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "entities");
|
|
986
|
+
i0.ɵɵadvance(2);
|
|
987
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "storage");
|
|
988
|
+
i0.ɵɵadvance(2);
|
|
989
|
+
i0.ɵɵclassProp("active", ctx_r2.ActiveScopeTab === "permissions");
|
|
990
|
+
i0.ɵɵadvance(3);
|
|
991
|
+
i0.ɵɵconditional(ctx_r2.ActiveScope.ID ? 14 : -1);
|
|
992
|
+
i0.ɵɵadvance(4);
|
|
993
|
+
i0.ɵɵconditional(!ctx_r2.ActiveScope.IsGlobal ? 18 : -1);
|
|
994
|
+
i0.ɵɵadvance();
|
|
995
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "definition" ? 19 : -1);
|
|
996
|
+
i0.ɵɵadvance();
|
|
997
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "providers" ? 20 : -1);
|
|
998
|
+
i0.ɵɵadvance();
|
|
999
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "indexes" ? 21 : -1);
|
|
1000
|
+
i0.ɵɵadvance();
|
|
1001
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "entities" ? 22 : -1);
|
|
1002
|
+
i0.ɵɵadvance();
|
|
1003
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "storage" ? 23 : -1);
|
|
1004
|
+
i0.ɵɵadvance();
|
|
1005
|
+
i0.ɵɵconditional(ctx_r2.ActiveScopeTab === "permissions" ? 24 : -1);
|
|
1006
|
+
} }
|
|
1007
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_19_Template(rf, ctx) { if (rf & 1) {
|
|
1008
|
+
i0.ɵɵelementStart(0, "div", 118);
|
|
1009
|
+
i0.ɵɵtext(1, " Select a scope on the left, or click ");
|
|
1010
|
+
i0.ɵɵelementStart(2, "strong");
|
|
1011
|
+
i0.ɵɵtext(3, "New");
|
|
1012
|
+
i0.ɵɵelementEnd();
|
|
1013
|
+
i0.ɵɵtext(4, " to create one. ");
|
|
1014
|
+
i0.ɵɵelementEnd();
|
|
1015
|
+
} }
|
|
709
1016
|
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
710
|
-
i0.ɵɵ
|
|
1017
|
+
const _r21 = i0.ɵɵgetCurrentView();
|
|
1018
|
+
i0.ɵɵelementStart(0, "div", 8)(1, "h2", 12);
|
|
711
1019
|
i0.ɵɵelement(2, "i", 110);
|
|
1020
|
+
i0.ɵɵtext(3, " Search Scopes");
|
|
1021
|
+
i0.ɵɵelementEnd();
|
|
1022
|
+
i0.ɵɵelementStart(4, "p", 13);
|
|
1023
|
+
i0.ɵɵtext(5, " Define reusable search scopes that filter which providers, entities, external indexes, and storage accounts participate in a scoped search. Scopes can be assigned to AI agents for pre-execution RAG or agent-invoked search. ");
|
|
1024
|
+
i0.ɵɵelementEnd();
|
|
1025
|
+
i0.ɵɵelementStart(6, "div", 111)(7, "aside", 112)(8, "div", 113)(9, "span");
|
|
1026
|
+
i0.ɵɵtext(10);
|
|
1027
|
+
i0.ɵɵelementEnd();
|
|
1028
|
+
i0.ɵɵelementStart(11, "button", 114);
|
|
1029
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r21); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.CreateNewScope()); });
|
|
1030
|
+
i0.ɵɵelement(12, "i", 58);
|
|
1031
|
+
i0.ɵɵtext(13, " New ");
|
|
1032
|
+
i0.ɵɵelementEnd()();
|
|
1033
|
+
i0.ɵɵconditionalCreate(14, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_14_Template, 2, 0, "div", 115)(15, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_15_Template, 5, 0, "div", 116)(16, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_16_Template, 2, 0);
|
|
1034
|
+
i0.ɵɵelementEnd();
|
|
1035
|
+
i0.ɵɵelementStart(17, "section", 117);
|
|
1036
|
+
i0.ɵɵconditionalCreate(18, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_18_Template, 25, 20)(19, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Conditional_19_Template, 5, 0, "div", 118);
|
|
1037
|
+
i0.ɵɵelementEnd()()();
|
|
1038
|
+
} if (rf & 2) {
|
|
1039
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
1040
|
+
i0.ɵɵadvance(10);
|
|
1041
|
+
i0.ɵɵtextInterpolate1("Scopes (", ctx_r2.SearchScopes.length, ")");
|
|
1042
|
+
i0.ɵɵadvance(4);
|
|
1043
|
+
i0.ɵɵconditional(ctx_r2.IsLoadingScopes ? 14 : ctx_r2.SearchScopes.length === 0 ? 15 : 16);
|
|
1044
|
+
i0.ɵɵadvance(4);
|
|
1045
|
+
i0.ɵɵconditional(ctx_r2.ActiveScope ? 18 : 19);
|
|
1046
|
+
} }
|
|
1047
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
1048
|
+
i0.ɵɵelement(0, "mj-loading", 156);
|
|
1049
|
+
} }
|
|
1050
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
1051
|
+
const _r28 = i0.ɵɵgetCurrentView();
|
|
1052
|
+
i0.ɵɵelementStart(0, "button", 158);
|
|
1053
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r28); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.LoadSearchAnalytics()); });
|
|
1054
|
+
i0.ɵɵelement(1, "i", 159);
|
|
1055
|
+
i0.ɵɵtext(2, " Load analytics ");
|
|
1056
|
+
i0.ɵɵelementEnd();
|
|
1057
|
+
} }
|
|
1058
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_37_Template(rf, ctx) { if (rf & 1) {
|
|
1059
|
+
i0.ɵɵelementStart(0, "p", 166);
|
|
1060
|
+
i0.ɵɵtext(1, "No runs in the window.");
|
|
1061
|
+
i0.ɵɵelementEnd();
|
|
1062
|
+
} }
|
|
1063
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_38_For_11_Template(rf, ctx) { if (rf & 1) {
|
|
1064
|
+
i0.ɵɵelementStart(0, "tr")(1, "td");
|
|
1065
|
+
i0.ɵɵtext(2);
|
|
1066
|
+
i0.ɵɵelementEnd();
|
|
1067
|
+
i0.ɵɵelementStart(3, "td");
|
|
1068
|
+
i0.ɵɵtext(4);
|
|
1069
|
+
i0.ɵɵpipe(5, "number");
|
|
1070
|
+
i0.ɵɵelementEnd();
|
|
1071
|
+
i0.ɵɵelementStart(6, "td");
|
|
1072
|
+
i0.ɵɵtext(7);
|
|
1073
|
+
i0.ɵɵelementEnd()();
|
|
1074
|
+
} if (rf & 2) {
|
|
1075
|
+
const s_r30 = ctx.$implicit;
|
|
1076
|
+
i0.ɵɵadvance(2);
|
|
1077
|
+
i0.ɵɵtextInterpolate(s_r30.Name);
|
|
1078
|
+
i0.ɵɵadvance(2);
|
|
1079
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(5, 3, s_r30.Count));
|
|
1080
|
+
i0.ɵɵadvance(3);
|
|
1081
|
+
i0.ɵɵtextInterpolate1("", s_r30.AvgLatencyMs, "ms");
|
|
1082
|
+
} }
|
|
1083
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_38_Template(rf, ctx) { if (rf & 1) {
|
|
1084
|
+
i0.ɵɵelementStart(0, "table", 167)(1, "thead")(2, "tr")(3, "th");
|
|
1085
|
+
i0.ɵɵtext(4, "Scope");
|
|
1086
|
+
i0.ɵɵelementEnd();
|
|
1087
|
+
i0.ɵɵelementStart(5, "th");
|
|
1088
|
+
i0.ɵɵtext(6, "Runs");
|
|
1089
|
+
i0.ɵɵelementEnd();
|
|
1090
|
+
i0.ɵɵelementStart(7, "th");
|
|
1091
|
+
i0.ɵɵtext(8, "Avg latency");
|
|
1092
|
+
i0.ɵɵelementEnd()()();
|
|
1093
|
+
i0.ɵɵelementStart(9, "tbody");
|
|
1094
|
+
i0.ɵɵrepeaterCreate(10, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_38_For_11_Template, 8, 5, "tr", null, _forTrack2);
|
|
1095
|
+
i0.ɵɵelementEnd()();
|
|
1096
|
+
} if (rf & 2) {
|
|
1097
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
1098
|
+
i0.ɵɵadvance(10);
|
|
1099
|
+
i0.ɵɵrepeater(ctx_r2.AnalyticsTopScopes);
|
|
1100
|
+
} }
|
|
1101
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_41_Template(rf, ctx) { if (rf & 1) {
|
|
1102
|
+
i0.ɵɵelementStart(0, "p", 166);
|
|
1103
|
+
i0.ɵɵtext(1, "No rerank invocations in the window.");
|
|
1104
|
+
i0.ɵɵelementEnd();
|
|
1105
|
+
} }
|
|
1106
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_42_For_11_Template(rf, ctx) { if (rf & 1) {
|
|
1107
|
+
i0.ɵɵelementStart(0, "tr")(1, "td");
|
|
1108
|
+
i0.ɵɵtext(2);
|
|
1109
|
+
i0.ɵɵelementEnd();
|
|
1110
|
+
i0.ɵɵelementStart(3, "td");
|
|
1111
|
+
i0.ɵɵtext(4);
|
|
1112
|
+
i0.ɵɵpipe(5, "number");
|
|
1113
|
+
i0.ɵɵelementEnd();
|
|
1114
|
+
i0.ɵɵelementStart(6, "td");
|
|
1115
|
+
i0.ɵɵtext(7);
|
|
1116
|
+
i0.ɵɵpipe(8, "number");
|
|
1117
|
+
i0.ɵɵelementEnd()();
|
|
1118
|
+
} if (rf & 2) {
|
|
1119
|
+
const r_r31 = ctx.$implicit;
|
|
1120
|
+
i0.ɵɵadvance(2);
|
|
1121
|
+
i0.ɵɵtextInterpolate(r_r31.Reranker);
|
|
1122
|
+
i0.ɵɵadvance(2);
|
|
1123
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(5, 3, r_r31.Count));
|
|
1124
|
+
i0.ɵɵadvance(3);
|
|
1125
|
+
i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(8, 5, r_r31.TotalCents, "1.0-2"), "\u00A2");
|
|
1126
|
+
} }
|
|
1127
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_42_Template(rf, ctx) { if (rf & 1) {
|
|
1128
|
+
i0.ɵɵelementStart(0, "table", 167)(1, "thead")(2, "tr")(3, "th");
|
|
1129
|
+
i0.ɵɵtext(4, "Reranker");
|
|
1130
|
+
i0.ɵɵelementEnd();
|
|
1131
|
+
i0.ɵɵelementStart(5, "th");
|
|
1132
|
+
i0.ɵɵtext(6, "Calls");
|
|
1133
|
+
i0.ɵɵelementEnd();
|
|
1134
|
+
i0.ɵɵelementStart(7, "th");
|
|
1135
|
+
i0.ɵɵtext(8, "Total cents");
|
|
1136
|
+
i0.ɵɵelementEnd()()();
|
|
1137
|
+
i0.ɵɵelementStart(9, "tbody");
|
|
1138
|
+
i0.ɵɵrepeaterCreate(10, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_42_For_11_Template, 9, 8, "tr", null, _forTrack3);
|
|
1139
|
+
i0.ɵɵelementEnd()();
|
|
1140
|
+
} if (rf & 2) {
|
|
1141
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
1142
|
+
i0.ɵɵadvance(10);
|
|
1143
|
+
i0.ɵɵrepeater(ctx_r2.AnalyticsRerankerSpend);
|
|
1144
|
+
} }
|
|
1145
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_45_Template(rf, ctx) { if (rf & 1) {
|
|
1146
|
+
i0.ɵɵelementStart(0, "p", 166);
|
|
1147
|
+
i0.ɵɵtext(1, "No failures in the window.");
|
|
1148
|
+
i0.ɵɵelementEnd();
|
|
1149
|
+
} }
|
|
1150
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_46_For_9_Template(rf, ctx) { if (rf & 1) {
|
|
1151
|
+
i0.ɵɵelementStart(0, "tr")(1, "td");
|
|
1152
|
+
i0.ɵɵtext(2);
|
|
1153
|
+
i0.ɵɵelementEnd();
|
|
1154
|
+
i0.ɵɵelementStart(3, "td");
|
|
1155
|
+
i0.ɵɵtext(4);
|
|
1156
|
+
i0.ɵɵpipe(5, "number");
|
|
1157
|
+
i0.ɵɵelementEnd()();
|
|
1158
|
+
} if (rf & 2) {
|
|
1159
|
+
const f_r32 = ctx.$implicit;
|
|
1160
|
+
i0.ɵɵadvance(2);
|
|
1161
|
+
i0.ɵɵtextInterpolate(f_r32.Reason);
|
|
1162
|
+
i0.ɵɵadvance(2);
|
|
1163
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(5, 2, f_r32.Count));
|
|
1164
|
+
} }
|
|
1165
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_46_Template(rf, ctx) { if (rf & 1) {
|
|
1166
|
+
i0.ɵɵelementStart(0, "table", 167)(1, "thead")(2, "tr")(3, "th");
|
|
1167
|
+
i0.ɵɵtext(4, "Reason");
|
|
1168
|
+
i0.ɵɵelementEnd();
|
|
1169
|
+
i0.ɵɵelementStart(5, "th");
|
|
1170
|
+
i0.ɵɵtext(6, "Count");
|
|
1171
|
+
i0.ɵɵelementEnd()()();
|
|
1172
|
+
i0.ɵɵelementStart(7, "tbody");
|
|
1173
|
+
i0.ɵɵrepeaterCreate(8, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_46_For_9_Template, 6, 4, "tr", null, _forTrack4);
|
|
1174
|
+
i0.ɵɵelementEnd()();
|
|
1175
|
+
} if (rf & 2) {
|
|
1176
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
1177
|
+
i0.ɵɵadvance(8);
|
|
1178
|
+
i0.ɵɵrepeater(ctx_r2.AnalyticsTopFailures);
|
|
1179
|
+
} }
|
|
1180
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
1181
|
+
const _r29 = i0.ɵɵgetCurrentView();
|
|
1182
|
+
i0.ɵɵelementStart(0, "div", 160)(1, "div", 161)(2, "span", 162);
|
|
1183
|
+
i0.ɵɵtext(3, "Total runs");
|
|
1184
|
+
i0.ɵɵelementEnd();
|
|
1185
|
+
i0.ɵɵelementStart(4, "span", 163);
|
|
1186
|
+
i0.ɵɵtext(5);
|
|
1187
|
+
i0.ɵɵpipe(6, "number");
|
|
1188
|
+
i0.ɵɵelementEnd()();
|
|
1189
|
+
i0.ɵɵelementStart(7, "div", 161)(8, "span", 162);
|
|
1190
|
+
i0.ɵɵtext(9, "Success rate");
|
|
1191
|
+
i0.ɵɵelementEnd();
|
|
1192
|
+
i0.ɵɵelementStart(10, "span", 163);
|
|
1193
|
+
i0.ɵɵtext(11);
|
|
1194
|
+
i0.ɵɵelementEnd()();
|
|
1195
|
+
i0.ɵɵelementStart(12, "div", 161)(13, "span", 162);
|
|
1196
|
+
i0.ɵɵtext(14, "Hit rate");
|
|
1197
|
+
i0.ɵɵelementEnd();
|
|
1198
|
+
i0.ɵɵelementStart(15, "span", 163);
|
|
1199
|
+
i0.ɵɵtext(16);
|
|
1200
|
+
i0.ɵɵelementEnd();
|
|
1201
|
+
i0.ɵɵelementStart(17, "span", 164);
|
|
1202
|
+
i0.ɵɵtext(18, "% of successful runs that returned \u22651 result");
|
|
1203
|
+
i0.ɵɵelementEnd()();
|
|
1204
|
+
i0.ɵɵelementStart(19, "div", 161)(20, "span", 162);
|
|
1205
|
+
i0.ɵɵtext(21, "Avg latency");
|
|
1206
|
+
i0.ɵɵelementEnd();
|
|
1207
|
+
i0.ɵɵelementStart(22, "span", 163);
|
|
1208
|
+
i0.ɵɵtext(23);
|
|
1209
|
+
i0.ɵɵelementEnd()();
|
|
1210
|
+
i0.ɵɵelementStart(24, "div", 161)(25, "span", 162);
|
|
1211
|
+
i0.ɵɵtext(26, "P95 latency");
|
|
1212
|
+
i0.ɵɵelementEnd();
|
|
1213
|
+
i0.ɵɵelementStart(27, "span", 163);
|
|
1214
|
+
i0.ɵɵtext(28);
|
|
1215
|
+
i0.ɵɵelementEnd()();
|
|
1216
|
+
i0.ɵɵelementStart(29, "div", 161)(30, "span", 162);
|
|
1217
|
+
i0.ɵɵtext(31, "Reranker spend");
|
|
1218
|
+
i0.ɵɵelementEnd();
|
|
1219
|
+
i0.ɵɵelementStart(32, "span", 163);
|
|
1220
|
+
i0.ɵɵtext(33);
|
|
1221
|
+
i0.ɵɵpipe(34, "number");
|
|
1222
|
+
i0.ɵɵelementEnd()()();
|
|
1223
|
+
i0.ɵɵelementStart(35, "h3", 165);
|
|
1224
|
+
i0.ɵɵtext(36, "Top scopes by volume");
|
|
1225
|
+
i0.ɵɵelementEnd();
|
|
1226
|
+
i0.ɵɵconditionalCreate(37, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_37_Template, 2, 0, "p", 166)(38, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_38_Template, 12, 0, "table", 167);
|
|
1227
|
+
i0.ɵɵelementStart(39, "h3", 165);
|
|
1228
|
+
i0.ɵɵtext(40, "Reranker spend by driver");
|
|
1229
|
+
i0.ɵɵelementEnd();
|
|
1230
|
+
i0.ɵɵconditionalCreate(41, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_41_Template, 2, 0, "p", 166)(42, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_42_Template, 12, 0, "table", 167);
|
|
1231
|
+
i0.ɵɵelementStart(43, "h3", 165);
|
|
1232
|
+
i0.ɵɵtext(44, "Top failure reasons");
|
|
1233
|
+
i0.ɵɵelementEnd();
|
|
1234
|
+
i0.ɵɵconditionalCreate(45, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_45_Template, 2, 0, "p", 166)(46, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Conditional_46_Template, 10, 0, "table", 167);
|
|
1235
|
+
i0.ɵɵelementStart(47, "button", 168);
|
|
1236
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Template_button_click_47_listener() { i0.ɵɵrestoreView(_r29); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.LoadSearchAnalytics()); });
|
|
1237
|
+
i0.ɵɵelement(48, "i", 159);
|
|
1238
|
+
i0.ɵɵtext(49, " Refresh ");
|
|
1239
|
+
i0.ɵɵelementEnd();
|
|
1240
|
+
} if (rf & 2) {
|
|
1241
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
1242
|
+
i0.ɵɵadvance(5);
|
|
1243
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(6, 9, ctx_r2.AnalyticsTotalRuns));
|
|
1244
|
+
i0.ɵɵadvance(6);
|
|
1245
|
+
i0.ɵɵtextInterpolate1("", ctx_r2.AnalyticsSuccessRate, "%");
|
|
1246
|
+
i0.ɵɵadvance(5);
|
|
1247
|
+
i0.ɵɵtextInterpolate1("", ctx_r2.AnalyticsHitRate, "%");
|
|
1248
|
+
i0.ɵɵadvance(7);
|
|
1249
|
+
i0.ɵɵtextInterpolate1("", ctx_r2.AnalyticsAvgLatencyMs, "ms");
|
|
1250
|
+
i0.ɵɵadvance(5);
|
|
1251
|
+
i0.ɵɵtextInterpolate1("", ctx_r2.AnalyticsP95LatencyMs, "ms");
|
|
1252
|
+
i0.ɵɵadvance(5);
|
|
1253
|
+
i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(34, 11, ctx_r2.AnalyticsTotalRerankerCostCents, "1.0-2"), "\u00A2");
|
|
1254
|
+
i0.ɵɵadvance(4);
|
|
1255
|
+
i0.ɵɵconditional(ctx_r2.AnalyticsTopScopes.length === 0 ? 37 : 38);
|
|
1256
|
+
i0.ɵɵadvance(4);
|
|
1257
|
+
i0.ɵɵconditional(ctx_r2.AnalyticsRerankerSpend.length === 0 ? 41 : 42);
|
|
1258
|
+
i0.ɵɵadvance(4);
|
|
1259
|
+
i0.ɵɵconditional(ctx_r2.AnalyticsTopFailures.length === 0 ? 45 : 46);
|
|
1260
|
+
} }
|
|
1261
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Template(rf, ctx) { if (rf & 1) {
|
|
1262
|
+
i0.ɵɵelementStart(0, "div", 9)(1, "h2", 12);
|
|
1263
|
+
i0.ɵɵelement(2, "i", 155);
|
|
1264
|
+
i0.ɵɵtext(3, " Search Analytics");
|
|
1265
|
+
i0.ɵɵelementEnd();
|
|
1266
|
+
i0.ɵɵelementStart(4, "p", 13);
|
|
1267
|
+
i0.ɵɵtext(5, "Aggregated metrics over the last 5,000 search invocations from ");
|
|
1268
|
+
i0.ɵɵelementStart(6, "code");
|
|
1269
|
+
i0.ɵɵtext(7, "MJ: Search Execution Logs");
|
|
1270
|
+
i0.ɵɵelementEnd();
|
|
1271
|
+
i0.ɵɵtext(8, ". Use this to spot slow scopes, high-failure queries, or runaway reranker spend.");
|
|
1272
|
+
i0.ɵɵelementEnd();
|
|
1273
|
+
i0.ɵɵconditionalCreate(9, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_9_Template, 1, 0, "mj-loading", 156)(10, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_10_Template, 3, 0, "button", 157)(11, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Conditional_11_Template, 50, 14);
|
|
1274
|
+
i0.ɵɵelementEnd();
|
|
1275
|
+
} if (rf & 2) {
|
|
1276
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
1277
|
+
i0.ɵɵadvance(9);
|
|
1278
|
+
i0.ɵɵconditional(ctx_r2.AnalyticsLoading ? 9 : !ctx_r2.AnalyticsLoaded ? 10 : 11);
|
|
1279
|
+
} }
|
|
1280
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
1281
|
+
i0.ɵɵelementStart(0, "div", 170);
|
|
1282
|
+
i0.ɵɵelement(1, "i", 80);
|
|
1283
|
+
i0.ɵɵtext(2, " Loading permission rows... ");
|
|
1284
|
+
i0.ɵɵelementEnd();
|
|
1285
|
+
} }
|
|
1286
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_19_Template(rf, ctx) { if (rf & 1) {
|
|
1287
|
+
i0.ɵɵelementStart(0, "div", 181);
|
|
1288
|
+
i0.ɵɵtext(1, " No permission rows match your filters. ");
|
|
1289
|
+
i0.ɵɵelementEnd();
|
|
1290
|
+
} }
|
|
1291
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
1292
|
+
i0.ɵɵtext(0);
|
|
1293
|
+
i0.ɵɵelementStart(1, "span", 184);
|
|
1294
|
+
i0.ɵɵtext(2);
|
|
1295
|
+
i0.ɵɵelementEnd();
|
|
1296
|
+
} if (rf & 2) {
|
|
1297
|
+
const row_r34 = i0.ɵɵnextContext().$implicit;
|
|
1298
|
+
i0.ɵɵtextInterpolate1(" ", row_r34.UserName, " ");
|
|
1299
|
+
i0.ɵɵadvance(2);
|
|
1300
|
+
i0.ɵɵtextInterpolate1("(", row_r34.UserEmail, ")");
|
|
1301
|
+
} }
|
|
1302
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
1303
|
+
i0.ɵɵtext(0);
|
|
1304
|
+
} if (rf & 2) {
|
|
1305
|
+
const row_r34 = i0.ɵɵnextContext().$implicit;
|
|
1306
|
+
i0.ɵɵtextInterpolate1(" ", row_r34.RoleName, " ");
|
|
1307
|
+
} }
|
|
1308
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
1309
|
+
i0.ɵɵelementStart(0, "span", 183);
|
|
1310
|
+
i0.ɵɵtext(1, "User");
|
|
1311
|
+
i0.ɵɵelementEnd();
|
|
1312
|
+
} }
|
|
1313
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
1314
|
+
i0.ɵɵelementStart(0, "span", 183);
|
|
1315
|
+
i0.ɵɵtext(1, "Role");
|
|
1316
|
+
i0.ɵɵelementEnd();
|
|
1317
|
+
} }
|
|
1318
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Template(rf, ctx) { if (rf & 1) {
|
|
1319
|
+
i0.ɵɵelementStart(0, "tr")(1, "td");
|
|
1320
|
+
i0.ɵɵtext(2);
|
|
1321
|
+
i0.ɵɵelementEnd();
|
|
1322
|
+
i0.ɵɵelementStart(3, "td");
|
|
1323
|
+
i0.ɵɵconditionalCreate(4, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_4_Template, 3, 2)(5, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_5_Template, 1, 1);
|
|
1324
|
+
i0.ɵɵelementEnd();
|
|
1325
|
+
i0.ɵɵelementStart(6, "td");
|
|
1326
|
+
i0.ɵɵconditionalCreate(7, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_7_Template, 2, 0, "span", 183);
|
|
1327
|
+
i0.ɵɵconditionalCreate(8, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Conditional_8_Template, 2, 0, "span", 183);
|
|
1328
|
+
i0.ɵɵelementEnd();
|
|
1329
|
+
i0.ɵɵelementStart(9, "td")(10, "span");
|
|
1330
|
+
i0.ɵɵpipe(11, "lowercase");
|
|
1331
|
+
i0.ɵɵtext(12);
|
|
1332
|
+
i0.ɵɵelementEnd()()();
|
|
1333
|
+
} if (rf & 2) {
|
|
1334
|
+
const row_r34 = ctx.$implicit;
|
|
1335
|
+
i0.ɵɵadvance(2);
|
|
1336
|
+
i0.ɵɵtextInterpolate(row_r34.SearchScopeName);
|
|
1337
|
+
i0.ɵɵadvance(2);
|
|
1338
|
+
i0.ɵɵconditional(row_r34.UserID ? 4 : 5);
|
|
1339
|
+
i0.ɵɵadvance(3);
|
|
1340
|
+
i0.ɵɵconditional(row_r34.UserID ? 7 : -1);
|
|
1341
|
+
i0.ɵɵadvance();
|
|
1342
|
+
i0.ɵɵconditional(row_r34.RoleID ? 8 : -1);
|
|
1343
|
+
i0.ɵɵadvance(2);
|
|
1344
|
+
i0.ɵɵclassMap(i0.ɵɵinterpolate1("search-permissions-level search-permissions-level-", i0.ɵɵpipeBind1(11, 8, row_r34.PermissionLevel)));
|
|
1345
|
+
i0.ɵɵadvance(2);
|
|
1346
|
+
i0.ɵɵtextInterpolate(row_r34.PermissionLevel);
|
|
1347
|
+
} }
|
|
1348
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_Template(rf, ctx) { if (rf & 1) {
|
|
1349
|
+
i0.ɵɵelementStart(0, "table", 182)(1, "thead")(2, "tr")(3, "th");
|
|
1350
|
+
i0.ɵɵtext(4, "Scope");
|
|
1351
|
+
i0.ɵɵelementEnd();
|
|
1352
|
+
i0.ɵɵelementStart(5, "th");
|
|
1353
|
+
i0.ɵɵtext(6, "Principal");
|
|
1354
|
+
i0.ɵɵelementEnd();
|
|
1355
|
+
i0.ɵɵelementStart(7, "th");
|
|
1356
|
+
i0.ɵɵtext(8, "Type");
|
|
1357
|
+
i0.ɵɵelementEnd();
|
|
1358
|
+
i0.ɵɵelementStart(9, "th");
|
|
1359
|
+
i0.ɵɵtext(10, "Permission Level");
|
|
1360
|
+
i0.ɵɵelementEnd()()();
|
|
1361
|
+
i0.ɵɵelementStart(11, "tbody");
|
|
1362
|
+
i0.ɵɵrepeaterCreate(12, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_For_13_Template, 13, 10, "tr", null, _forTrack0);
|
|
1363
|
+
i0.ɵɵelementEnd()();
|
|
1364
|
+
} if (rf & 2) {
|
|
1365
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
1366
|
+
i0.ɵɵadvance(12);
|
|
1367
|
+
i0.ɵɵrepeater(ctx_r2.FilteredPermissionsRows);
|
|
1368
|
+
} }
|
|
1369
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
1370
|
+
const _r33 = i0.ɵɵgetCurrentView();
|
|
1371
|
+
i0.ɵɵelementStart(0, "div", 171)(1, "input", 172);
|
|
1372
|
+
i0.ɵɵtwoWayListener("ngModelChange", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template_input_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r33); const ctx_r2 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r2.PermissionsFilterScope, $event) || (ctx_r2.PermissionsFilterScope = $event); return i0.ɵɵresetView($event); });
|
|
1373
|
+
i0.ɵɵelementEnd();
|
|
1374
|
+
i0.ɵɵelementStart(2, "input", 173);
|
|
1375
|
+
i0.ɵɵtwoWayListener("ngModelChange", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template_input_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r33); const ctx_r2 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r2.PermissionsFilterPrincipal, $event) || (ctx_r2.PermissionsFilterPrincipal = $event); return i0.ɵɵresetView($event); });
|
|
1376
|
+
i0.ɵɵelementEnd();
|
|
1377
|
+
i0.ɵɵelementStart(3, "select", 174);
|
|
1378
|
+
i0.ɵɵtwoWayListener("ngModelChange", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template_select_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r33); const ctx_r2 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r2.PermissionsFilterLevel, $event) || (ctx_r2.PermissionsFilterLevel = $event); return i0.ɵɵresetView($event); });
|
|
1379
|
+
i0.ɵɵelementStart(4, "option", 175);
|
|
1380
|
+
i0.ɵɵtext(5, "All levels");
|
|
1381
|
+
i0.ɵɵelementEnd();
|
|
1382
|
+
i0.ɵɵelementStart(6, "option", 176);
|
|
1383
|
+
i0.ɵɵtext(7, "None");
|
|
1384
|
+
i0.ɵɵelementEnd();
|
|
1385
|
+
i0.ɵɵelementStart(8, "option", 177);
|
|
1386
|
+
i0.ɵɵtext(9, "Read");
|
|
1387
|
+
i0.ɵɵelementEnd();
|
|
1388
|
+
i0.ɵɵelementStart(10, "option", 178);
|
|
1389
|
+
i0.ɵɵtext(11, "Search");
|
|
1390
|
+
i0.ɵɵelementEnd();
|
|
1391
|
+
i0.ɵɵelementStart(12, "option", 179);
|
|
1392
|
+
i0.ɵɵtext(13, "Manage");
|
|
1393
|
+
i0.ɵɵelementEnd()();
|
|
1394
|
+
i0.ɵɵelementStart(14, "button", 158);
|
|
1395
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r33); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.RefreshPermissionsAudit()); });
|
|
1396
|
+
i0.ɵɵelement(15, "i", 159);
|
|
1397
|
+
i0.ɵɵtext(16, " Refresh ");
|
|
1398
|
+
i0.ɵɵelementEnd()();
|
|
1399
|
+
i0.ɵɵelementStart(17, "div", 180);
|
|
1400
|
+
i0.ɵɵtext(18);
|
|
1401
|
+
i0.ɵɵelementEnd();
|
|
1402
|
+
i0.ɵɵconditionalCreate(19, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_19_Template, 2, 0, "div", 181)(20, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Conditional_20_Template, 14, 0, "table", 182);
|
|
1403
|
+
} if (rf & 2) {
|
|
1404
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
1405
|
+
i0.ɵɵadvance();
|
|
1406
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r2.PermissionsFilterScope);
|
|
1407
|
+
i0.ɵɵadvance();
|
|
1408
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r2.PermissionsFilterPrincipal);
|
|
1409
|
+
i0.ɵɵadvance();
|
|
1410
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r2.PermissionsFilterLevel);
|
|
1411
|
+
i0.ɵɵadvance(15);
|
|
1412
|
+
i0.ɵɵtextInterpolate2(" Showing ", ctx_r2.FilteredPermissionsRows.length, " of ", ctx_r2.PermissionsRows.length, " rows ");
|
|
1413
|
+
i0.ɵɵadvance();
|
|
1414
|
+
i0.ɵɵconditional(ctx_r2.FilteredPermissionsRows.length === 0 ? 19 : 20);
|
|
1415
|
+
} }
|
|
1416
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Template(rf, ctx) { if (rf & 1) {
|
|
1417
|
+
i0.ɵɵelementStart(0, "div", 9)(1, "h2", 12);
|
|
1418
|
+
i0.ɵɵelement(2, "i", 169);
|
|
1419
|
+
i0.ɵɵtext(3, " Permissions");
|
|
1420
|
+
i0.ɵɵelementEnd();
|
|
1421
|
+
i0.ɵɵelementStart(4, "p", 13);
|
|
1422
|
+
i0.ɵɵtext(5, " Cross-scope view of every ");
|
|
1423
|
+
i0.ɵɵelementStart(6, "code");
|
|
1424
|
+
i0.ɵɵtext(7, "SearchScopePermission");
|
|
1425
|
+
i0.ɵɵelementEnd();
|
|
1426
|
+
i0.ɵɵtext(8, " row. Filter by scope, by user/role, or by permission level to answer \"who has access to what\". Read-only \u2014 to add or edit a grant, open the SearchScope's full form and use its Permissions panel. ");
|
|
1427
|
+
i0.ɵɵelementEnd();
|
|
1428
|
+
i0.ɵɵconditionalCreate(9, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_9_Template, 3, 0, "div", 170);
|
|
1429
|
+
i0.ɵɵconditionalCreate(10, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Conditional_10_Template, 21, 6);
|
|
1430
|
+
i0.ɵɵelementEnd();
|
|
1431
|
+
} if (rf & 2) {
|
|
1432
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
1433
|
+
i0.ɵɵadvance(9);
|
|
1434
|
+
i0.ɵɵconditional(ctx_r2.PermissionsLoading ? 9 : -1);
|
|
1435
|
+
i0.ɵɵadvance();
|
|
1436
|
+
i0.ɵɵconditional(ctx_r2.PermissionsLoaded && !ctx_r2.PermissionsLoading ? 10 : -1);
|
|
1437
|
+
} }
|
|
1438
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_17_Template(rf, ctx) { if (rf & 1) {
|
|
1439
|
+
i0.ɵɵelementStart(0, "div", 9)(1, "h2", 12);
|
|
1440
|
+
i0.ɵɵelement(2, "i", 185);
|
|
712
1441
|
i0.ɵɵtext(3, " Scheduling");
|
|
713
1442
|
i0.ɵɵelementEnd();
|
|
714
1443
|
i0.ɵɵelementStart(4, "p", 13);
|
|
715
1444
|
i0.ɵɵtext(5, "Manage automated pipeline schedules for content classification and vector sync. Create schedules here or manage all schedules in the dedicated Scheduling app.");
|
|
716
1445
|
i0.ɵɵelementEnd();
|
|
717
|
-
i0.ɵɵelementStart(6, "div",
|
|
1446
|
+
i0.ɵɵelementStart(6, "div", 186);
|
|
718
1447
|
i0.ɵɵelement(7, "app-scheduling-resource");
|
|
719
1448
|
i0.ɵɵelementEnd()();
|
|
720
1449
|
} }
|
|
721
|
-
function
|
|
1450
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
722
1451
|
i0.ɵɵelement(0, "i", 80);
|
|
723
1452
|
i0.ɵɵtext(1, " Saving... ");
|
|
724
1453
|
} }
|
|
725
|
-
function
|
|
726
|
-
i0.ɵɵelement(0, "i",
|
|
1454
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
1455
|
+
i0.ɵɵelement(0, "i", 129);
|
|
727
1456
|
i0.ɵɵtext(1, " Save Changes ");
|
|
728
1457
|
} }
|
|
729
|
-
function
|
|
730
|
-
const
|
|
731
|
-
i0.ɵɵelementStart(0, "div", 10)(1, "span",
|
|
1458
|
+
function KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Template(rf, ctx) { if (rf & 1) {
|
|
1459
|
+
const _r35 = i0.ɵɵgetCurrentView();
|
|
1460
|
+
i0.ɵɵelementStart(0, "div", 10)(1, "span", 187);
|
|
732
1461
|
i0.ɵɵtext(2, "You have unsaved changes");
|
|
733
1462
|
i0.ɵɵelementEnd();
|
|
734
|
-
i0.ɵɵelementStart(3, "button",
|
|
735
|
-
i0.ɵɵlistener("click", function
|
|
736
|
-
i0.ɵɵconditionalCreate(4,
|
|
1463
|
+
i0.ɵɵelementStart(3, "button", 188);
|
|
1464
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r35); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.SaveConfiguration()); });
|
|
1465
|
+
i0.ɵɵconditionalCreate(4, KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Conditional_4_Template, 2, 0)(5, KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Conditional_5_Template, 2, 0);
|
|
737
1466
|
i0.ɵɵelementEnd();
|
|
738
|
-
i0.ɵɵelementStart(6, "button",
|
|
739
|
-
i0.ɵɵlistener("click", function
|
|
1467
|
+
i0.ɵɵelementStart(6, "button", 189);
|
|
1468
|
+
i0.ɵɵlistener("click", function KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r35); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ResetConfiguration()); });
|
|
740
1469
|
i0.ɵɵtext(7, " Reset ");
|
|
741
1470
|
i0.ɵɵelementEnd()();
|
|
742
1471
|
} if (rf & 2) {
|
|
@@ -762,8 +1491,11 @@ function KnowledgeConfigResourceComponent_Conditional_1_Template(rf, ctx) { if (
|
|
|
762
1491
|
i0.ɵɵconditionalCreate(11, KnowledgeConfigResourceComponent_Conditional_1_Conditional_11_Template, 8, 1, "div", 8);
|
|
763
1492
|
i0.ɵɵconditionalCreate(12, KnowledgeConfigResourceComponent_Conditional_1_Conditional_12_Template, 7, 1, "div", 8);
|
|
764
1493
|
i0.ɵɵconditionalCreate(13, KnowledgeConfigResourceComponent_Conditional_1_Conditional_13_Template, 34, 8, "div", 8);
|
|
765
|
-
i0.ɵɵconditionalCreate(14, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Template,
|
|
766
|
-
i0.ɵɵconditionalCreate(15, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Template,
|
|
1494
|
+
i0.ɵɵconditionalCreate(14, KnowledgeConfigResourceComponent_Conditional_1_Conditional_14_Template, 20, 3, "div", 8);
|
|
1495
|
+
i0.ɵɵconditionalCreate(15, KnowledgeConfigResourceComponent_Conditional_1_Conditional_15_Template, 12, 1, "div", 9);
|
|
1496
|
+
i0.ɵɵconditionalCreate(16, KnowledgeConfigResourceComponent_Conditional_1_Conditional_16_Template, 11, 2, "div", 9);
|
|
1497
|
+
i0.ɵɵconditionalCreate(17, KnowledgeConfigResourceComponent_Conditional_1_Conditional_17_Template, 8, 0, "div", 9);
|
|
1498
|
+
i0.ɵɵconditionalCreate(18, KnowledgeConfigResourceComponent_Conditional_1_Conditional_18_Template, 8, 3, "div", 10);
|
|
767
1499
|
i0.ɵɵelementEnd()();
|
|
768
1500
|
} if (rf & 2) {
|
|
769
1501
|
const ctx_r2 = i0.ɵɵnextContext();
|
|
@@ -780,9 +1512,15 @@ function KnowledgeConfigResourceComponent_Conditional_1_Template(rf, ctx) { if (
|
|
|
780
1512
|
i0.ɵɵadvance();
|
|
781
1513
|
i0.ɵɵconditional(ctx_r2.ActiveSection === "thresholds" ? 13 : -1);
|
|
782
1514
|
i0.ɵɵadvance();
|
|
783
|
-
i0.ɵɵconditional(ctx_r2.ActiveSection === "
|
|
1515
|
+
i0.ɵɵconditional(ctx_r2.ActiveSection === "search-scopes" ? 14 : -1);
|
|
1516
|
+
i0.ɵɵadvance();
|
|
1517
|
+
i0.ɵɵconditional(ctx_r2.ActiveSection === "search-analytics" ? 15 : -1);
|
|
784
1518
|
i0.ɵɵadvance();
|
|
785
|
-
i0.ɵɵconditional(ctx_r2.
|
|
1519
|
+
i0.ɵɵconditional(ctx_r2.ActiveSection === "search-permissions" ? 16 : -1);
|
|
1520
|
+
i0.ɵɵadvance();
|
|
1521
|
+
i0.ɵɵconditional(ctx_r2.ActiveSection === "scheduling" ? 17 : -1);
|
|
1522
|
+
i0.ɵɵadvance();
|
|
1523
|
+
i0.ɵɵconditional(ctx_r2.HasUnsavedChanges ? 18 : -1);
|
|
786
1524
|
} }
|
|
787
1525
|
let KnowledgeConfigResourceComponent = class KnowledgeConfigResourceComponent extends BaseResourceComponent {
|
|
788
1526
|
cdr = inject(ChangeDetectorRef);
|
|
@@ -801,6 +1539,9 @@ let KnowledgeConfigResourceComponent = class KnowledgeConfigResourceComponent ex
|
|
|
801
1539
|
{ ID: 'fulltext', Label: 'Full-Text Indexes', Icon: 'fa-solid fa-text-width', Description: 'Configure SQL full-text search indexes' },
|
|
802
1540
|
{ ID: 'embedding', Label: 'Embedding Models', Icon: 'fa-solid fa-microchip', Description: 'Select and configure embedding models' },
|
|
803
1541
|
{ ID: 'thresholds', Label: 'Thresholds', Icon: 'fa-solid fa-sliders', Description: 'Set scoring thresholds for search and deduplication' },
|
|
1542
|
+
{ ID: 'search-scopes', Label: 'Search Scopes', Icon: 'fa-solid fa-compass-drafting', Description: 'Define reusable search scopes — which providers, entities, external indexes, and storage accounts participate in scoped search' },
|
|
1543
|
+
{ ID: 'search-analytics', Label: 'Search Analytics', Icon: 'fa-solid fa-chart-line', Description: 'Per-scope query volume, p50/p95 latency, hit rate, top failures, and reranker spend (driven by SearchExecutionLog)' },
|
|
1544
|
+
{ ID: 'search-permissions', Label: 'Permissions', Icon: 'fa-solid fa-shield-halved', Description: 'Cross-scope view of every SearchScopePermission row, filterable by scope, user, or role (P2A.7)' },
|
|
804
1545
|
{ ID: 'scheduling', Label: 'Scheduling', Icon: 'fa-solid fa-clock', Description: 'Manage automated pipeline schedules' },
|
|
805
1546
|
];
|
|
806
1547
|
ActiveSection = 'pipeline';
|
|
@@ -869,6 +1610,63 @@ let KnowledgeConfigResourceComponent = class KnowledgeConfigResourceComponent ex
|
|
|
869
1610
|
NewIndexName = '';
|
|
870
1611
|
NewIndexVectorDBID = '';
|
|
871
1612
|
NewIndexEmbeddingModelID = '';
|
|
1613
|
+
// --- Search Scopes ---
|
|
1614
|
+
/** All SearchScope rows the current user can manage. */
|
|
1615
|
+
SearchScopes = [];
|
|
1616
|
+
/** Currently-selected scope ID — drives child-grid loading. */
|
|
1617
|
+
ActiveScopeID = null;
|
|
1618
|
+
IsLoadingScopes = false;
|
|
1619
|
+
/** Which sub-tab of the selected scope is open. */
|
|
1620
|
+
ActiveScopeTab = 'definition';
|
|
1621
|
+
/** Column spec for the Providers child grid. */
|
|
1622
|
+
ScopeProviderColumns = [
|
|
1623
|
+
{ Field: 'SearchProviderID', Label: 'Provider', Type: 'lookup', LookupEntityName: 'MJ: Search Providers', LookupFilter: "Status='Active'", Width: '200px' },
|
|
1624
|
+
{ Field: 'Enabled', Label: 'Enabled', Type: 'checkbox', Width: '80px' },
|
|
1625
|
+
{ Field: 'MaxResults', Label: 'Max Results', Type: 'number', Placeholder: 'e.g. 20', Width: '110px' },
|
|
1626
|
+
{ Field: 'QueryTransformTemplateID', Label: 'Query Transform', Type: 'lookup', LookupEntityName: 'Templates', Width: '180px' },
|
|
1627
|
+
{ Field: 'ProviderConfigOverride', Label: 'Config Override', Type: 'code', Placeholder: 'JSON override (optional)' },
|
|
1628
|
+
];
|
|
1629
|
+
/** Column spec for the External Indexes child grid. */
|
|
1630
|
+
ScopeExternalIndexColumns = [
|
|
1631
|
+
{ Field: 'IndexType', Label: 'Type', Type: 'select', Options: [
|
|
1632
|
+
{ Label: 'Vector', Value: 'Vector' },
|
|
1633
|
+
{ Label: 'Elasticsearch', Value: 'Elasticsearch' },
|
|
1634
|
+
{ Label: 'OpenSearch', Value: 'OpenSearch' },
|
|
1635
|
+
{ Label: 'Typesense', Value: 'Typesense' },
|
|
1636
|
+
{ Label: 'Azure AI Search', Value: 'AzureAISearch' },
|
|
1637
|
+
], Width: '140px' },
|
|
1638
|
+
{ Field: 'IndexName', Label: 'Index Name', Type: 'text', Placeholder: 'hr-policies-v2', Width: '200px' },
|
|
1639
|
+
{ Field: 'MetadataFilterTemplate', Label: 'Metadata Filter (Nunjucks)', Type: 'code', Placeholder: '{ "tenantId": "{{ context.PrimaryScopeRecordID }}" }' },
|
|
1640
|
+
{ Field: 'ExternalIndexConfig', Label: 'Config Override', Type: 'code', Placeholder: 'JSON override (optional)' },
|
|
1641
|
+
];
|
|
1642
|
+
/** Column spec for the Entities child grid. */
|
|
1643
|
+
ScopeEntityColumns = [
|
|
1644
|
+
{ Field: 'EntityID', Label: 'Entity', Type: 'lookup', LookupEntityName: 'Entities', Width: '220px' },
|
|
1645
|
+
{ Field: 'ExtraFilter', Label: 'Extra Filter (SQL + Nunjucks)', Type: 'code', Placeholder: "CategoryID = '<uuid>' AND OrganizationID = '{{ context.PrimaryScopeRecordID }}'" },
|
|
1646
|
+
{ Field: 'UserSearchStringOverride', Label: 'Query Rewrite', Type: 'text', Placeholder: '— use raw query —' },
|
|
1647
|
+
];
|
|
1648
|
+
/** Column spec for the Storage Accounts child grid. */
|
|
1649
|
+
ScopeStorageColumns = [
|
|
1650
|
+
{ Field: 'StorageAccountID', Label: 'Storage Account', Type: 'lookup', LookupEntityName: 'Storage Providers', Width: '220px' },
|
|
1651
|
+
{ Field: 'FolderPath', Label: 'Folder Path (Nunjucks)', Type: 'code', Placeholder: '/tenants/{{ context.PrimaryScopeRecordID }}/hr/policies/' },
|
|
1652
|
+
];
|
|
1653
|
+
/**
|
|
1654
|
+
* Column spec for the SearchScopePermission child grid (Phase 2A).
|
|
1655
|
+
* Editable surface — admins author per-user / per-role grants here.
|
|
1656
|
+
* Each row binds either a User or a Role (XOR enforced by the DB CHECK
|
|
1657
|
+
* constraint, surfaced as a save-time error for now). Resolution order
|
|
1658
|
+
* lives in SearchScopePermissionResolver.
|
|
1659
|
+
*/
|
|
1660
|
+
ScopePermissionColumns = [
|
|
1661
|
+
{ Field: 'UserID', Label: 'User', Type: 'lookup', LookupEntityName: 'Users', LookupFilter: "IsActive=1", Width: '240px' },
|
|
1662
|
+
{ Field: 'RoleID', Label: 'Role', Type: 'lookup', LookupEntityName: 'Roles', Width: '200px' },
|
|
1663
|
+
{ Field: 'PermissionLevel', Label: 'Level', Type: 'select', Options: [
|
|
1664
|
+
{ Label: 'None (explicit deny)', Value: 'None' },
|
|
1665
|
+
{ Label: 'Read (view only)', Value: 'Read' },
|
|
1666
|
+
{ Label: 'Search (invoke)', Value: 'Search' },
|
|
1667
|
+
{ Label: 'Manage (edit + grant)', Value: 'Manage' },
|
|
1668
|
+
], Width: '200px' },
|
|
1669
|
+
];
|
|
872
1670
|
ngAfterViewInit() {
|
|
873
1671
|
this.loadConfiguration();
|
|
874
1672
|
this.navigationService.SetAgentContext(this, {
|
|
@@ -886,6 +1684,411 @@ let KnowledgeConfigResourceComponent = class KnowledgeConfigResourceComponent ex
|
|
|
886
1684
|
// ================================================================
|
|
887
1685
|
SelectSection(sectionId) {
|
|
888
1686
|
this.ActiveSection = sectionId;
|
|
1687
|
+
if (sectionId === 'search-scopes' && this.SearchScopes.length === 0) {
|
|
1688
|
+
void this.LoadSearchScopes();
|
|
1689
|
+
}
|
|
1690
|
+
if (sectionId === 'search-analytics' && !this.AnalyticsLoaded && !this.AnalyticsLoading) {
|
|
1691
|
+
void this.LoadSearchAnalytics();
|
|
1692
|
+
}
|
|
1693
|
+
if (sectionId === 'search-permissions' && !this.PermissionsLoaded && !this.PermissionsLoading) {
|
|
1694
|
+
void this.LoadPermissionsAudit();
|
|
1695
|
+
}
|
|
1696
|
+
this.cdr.detectChanges();
|
|
1697
|
+
}
|
|
1698
|
+
// ─── Search Analytics (P3.3) ──────────────────────────────────────────────
|
|
1699
|
+
AnalyticsLoaded = false;
|
|
1700
|
+
AnalyticsLoading = false;
|
|
1701
|
+
AnalyticsTotalRuns = 0;
|
|
1702
|
+
AnalyticsSuccessRate = 0;
|
|
1703
|
+
AnalyticsAvgLatencyMs = 0;
|
|
1704
|
+
AnalyticsP95LatencyMs = 0;
|
|
1705
|
+
AnalyticsTotalRerankerCostCents = 0;
|
|
1706
|
+
AnalyticsHitRate = 0;
|
|
1707
|
+
AnalyticsTopScopes = [];
|
|
1708
|
+
AnalyticsTopFailures = [];
|
|
1709
|
+
AnalyticsRerankerSpend = [];
|
|
1710
|
+
async LoadSearchAnalytics() {
|
|
1711
|
+
this.AnalyticsLoading = true;
|
|
1712
|
+
this.cdr.detectChanges();
|
|
1713
|
+
try {
|
|
1714
|
+
const rows = await this.fetchSearchExecutionRows();
|
|
1715
|
+
if (rows === null)
|
|
1716
|
+
return;
|
|
1717
|
+
this.computeAnalyticsKpis(rows);
|
|
1718
|
+
await this.computeAnalyticsTopScopes(rows);
|
|
1719
|
+
this.computeAnalyticsTopFailures(rows);
|
|
1720
|
+
this.computeAnalyticsRerankerSpend(rows);
|
|
1721
|
+
this.AnalyticsLoaded = true;
|
|
1722
|
+
}
|
|
1723
|
+
catch (err) {
|
|
1724
|
+
LogError(`KnowledgeConfig: LoadSearchAnalytics threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
1725
|
+
}
|
|
1726
|
+
finally {
|
|
1727
|
+
this.AnalyticsLoading = false;
|
|
1728
|
+
this.cdr.detectChanges();
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
/** Fetch the raw SearchExecutionLog rows. Returns null on RunView failure (already logged). */
|
|
1732
|
+
async fetchSearchExecutionRows() {
|
|
1733
|
+
const rv = new RunView();
|
|
1734
|
+
const result = await rv.RunView({
|
|
1735
|
+
EntityName: 'MJ: Search Execution Logs',
|
|
1736
|
+
Fields: ['ID', 'SearchScopeID', 'Status', 'ResultCount', 'TotalDurationMs', 'RerankerName', 'RerankerCostCents', 'FailureReason'],
|
|
1737
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
1738
|
+
MaxRows: 5000,
|
|
1739
|
+
ResultType: 'simple',
|
|
1740
|
+
});
|
|
1741
|
+
if (!result.Success) {
|
|
1742
|
+
LogError(`KnowledgeConfig: LoadSearchAnalytics failed: ${result.ErrorMessage}`);
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
return result.Results ?? [];
|
|
1746
|
+
}
|
|
1747
|
+
/** Total runs, success rate, hit rate, avg + p95 latency, total reranker spend. */
|
|
1748
|
+
computeAnalyticsKpis(rows) {
|
|
1749
|
+
this.AnalyticsTotalRuns = rows.length;
|
|
1750
|
+
const successRows = rows.filter(r => r.Status === 'Success');
|
|
1751
|
+
this.AnalyticsSuccessRate = rows.length > 0 ? Math.round((successRows.length / rows.length) * 100) : 0;
|
|
1752
|
+
this.AnalyticsHitRate = successRows.length > 0
|
|
1753
|
+
? Math.round((successRows.filter(r => (r.ResultCount ?? 0) > 0).length / successRows.length) * 100)
|
|
1754
|
+
: 0;
|
|
1755
|
+
const latencies = successRows.map(r => r.TotalDurationMs ?? 0).sort((a, b) => a - b);
|
|
1756
|
+
this.AnalyticsAvgLatencyMs = latencies.length > 0
|
|
1757
|
+
? Math.round(latencies.reduce((s, x) => s + x, 0) / latencies.length)
|
|
1758
|
+
: 0;
|
|
1759
|
+
this.AnalyticsP95LatencyMs = latencies.length > 0
|
|
1760
|
+
? latencies[Math.min(latencies.length - 1, Math.floor(latencies.length * 0.95))]
|
|
1761
|
+
: 0;
|
|
1762
|
+
this.AnalyticsTotalRerankerCostCents = rows.reduce((s, r) => s + (r.RerankerCostCents ?? 0), 0);
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Per-scope volume rollup → top 10 by Count. Fetches scope names from
|
|
1766
|
+
* `this.SearchScopes` when already loaded, otherwise loads them on demand
|
|
1767
|
+
* so the unscoped audit-only path still gets human-readable labels.
|
|
1768
|
+
*/
|
|
1769
|
+
async computeAnalyticsTopScopes(rows) {
|
|
1770
|
+
const byScope = new Map();
|
|
1771
|
+
for (const r of rows) {
|
|
1772
|
+
const id = r.SearchScopeID ?? '__unscoped__';
|
|
1773
|
+
const cur = byScope.get(id) ?? { Count: 0, LatencySum: 0, ScopeID: id };
|
|
1774
|
+
cur.Count++;
|
|
1775
|
+
cur.LatencySum += r.TotalDurationMs ?? 0;
|
|
1776
|
+
byScope.set(id, cur);
|
|
1777
|
+
}
|
|
1778
|
+
const scopeNameMap = new Map(this.SearchScopes.map(s => [s.ID, s.Name]));
|
|
1779
|
+
if (this.SearchScopes.length === 0 && byScope.size > 0) {
|
|
1780
|
+
await this.LoadSearchScopes();
|
|
1781
|
+
this.SearchScopes.forEach(s => scopeNameMap.set(s.ID, s.Name));
|
|
1782
|
+
}
|
|
1783
|
+
this.AnalyticsTopScopes = Array.from(byScope.values())
|
|
1784
|
+
.map(g => ({
|
|
1785
|
+
ScopeID: g.ScopeID,
|
|
1786
|
+
Name: scopeNameMap.get(g.ScopeID) ?? (g.ScopeID === '__unscoped__' ? '— unscoped —' : `(${g.ScopeID.slice(0, 8)}…)`),
|
|
1787
|
+
Count: g.Count,
|
|
1788
|
+
AvgLatencyMs: Math.round(g.LatencySum / g.Count),
|
|
1789
|
+
}))
|
|
1790
|
+
.sort((a, b) => b.Count - a.Count)
|
|
1791
|
+
.slice(0, 10);
|
|
1792
|
+
}
|
|
1793
|
+
/** Top 5 failure reasons across non-Success rows. */
|
|
1794
|
+
computeAnalyticsTopFailures(rows) {
|
|
1795
|
+
const byFailure = new Map();
|
|
1796
|
+
for (const r of rows) {
|
|
1797
|
+
if (r.Status !== 'Success' && r.FailureReason) {
|
|
1798
|
+
byFailure.set(r.FailureReason, (byFailure.get(r.FailureReason) ?? 0) + 1);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
this.AnalyticsTopFailures = Array.from(byFailure.entries())
|
|
1802
|
+
.map(([Reason, Count]) => ({ Reason, Count }))
|
|
1803
|
+
.sort((a, b) => b.Count - a.Count)
|
|
1804
|
+
.slice(0, 5);
|
|
1805
|
+
}
|
|
1806
|
+
/** Per-reranker spend rollup, sorted descending by total cost. */
|
|
1807
|
+
computeAnalyticsRerankerSpend(rows) {
|
|
1808
|
+
const byReranker = new Map();
|
|
1809
|
+
for (const r of rows) {
|
|
1810
|
+
if (!r.RerankerName)
|
|
1811
|
+
continue;
|
|
1812
|
+
const cur = byReranker.get(r.RerankerName) ?? { Count: 0, TotalCents: 0 };
|
|
1813
|
+
cur.Count++;
|
|
1814
|
+
cur.TotalCents += r.RerankerCostCents ?? 0;
|
|
1815
|
+
byReranker.set(r.RerankerName, cur);
|
|
1816
|
+
}
|
|
1817
|
+
this.AnalyticsRerankerSpend = Array.from(byReranker.entries())
|
|
1818
|
+
.map(([Reranker, v]) => ({ Reranker, Count: v.Count, TotalCents: +v.TotalCents.toFixed(4) }))
|
|
1819
|
+
.sort((a, b) => b.TotalCents - a.TotalCents);
|
|
1820
|
+
}
|
|
1821
|
+
// ─── Permissions Audit (P2A.7) ────────────────────────────────────────────
|
|
1822
|
+
//
|
|
1823
|
+
// Cross-scope view of every SearchScopePermission row. Renders a flat,
|
|
1824
|
+
// filterable list so an admin can answer "who has access to which scopes"
|
|
1825
|
+
// in one place. Editing happens on the SearchScope full form's
|
|
1826
|
+
// Permissions panel — this dashboard is read-only.
|
|
1827
|
+
PermissionsLoaded = false;
|
|
1828
|
+
PermissionsLoading = false;
|
|
1829
|
+
PermissionsRows = [];
|
|
1830
|
+
PermissionsFilterScope = '';
|
|
1831
|
+
PermissionsFilterPrincipal = '';
|
|
1832
|
+
PermissionsFilterLevel = '';
|
|
1833
|
+
async LoadPermissionsAudit() {
|
|
1834
|
+
this.PermissionsLoading = true;
|
|
1835
|
+
this.cdr.detectChanges();
|
|
1836
|
+
try {
|
|
1837
|
+
const fetched = await this.fetchPermissionsAuditData();
|
|
1838
|
+
if (!fetched) {
|
|
1839
|
+
this.PermissionsRows = [];
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
this.PermissionsRows = this.shapePermissionsAuditRows(fetched);
|
|
1843
|
+
this.PermissionsLoaded = true;
|
|
1844
|
+
}
|
|
1845
|
+
catch (err) {
|
|
1846
|
+
LogError(`KnowledgeConfig: LoadPermissionsAudit threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
1847
|
+
}
|
|
1848
|
+
finally {
|
|
1849
|
+
this.PermissionsLoading = false;
|
|
1850
|
+
this.cdr.detectChanges();
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
get FilteredPermissionsRows() {
|
|
1854
|
+
const scope = (this.PermissionsFilterScope || '').toLowerCase();
|
|
1855
|
+
const principal = (this.PermissionsFilterPrincipal || '').toLowerCase();
|
|
1856
|
+
const level = this.PermissionsFilterLevel;
|
|
1857
|
+
return this.PermissionsRows.filter(r => {
|
|
1858
|
+
if (scope && !r.SearchScopeName.toLowerCase().includes(scope))
|
|
1859
|
+
return false;
|
|
1860
|
+
if (principal) {
|
|
1861
|
+
const candidates = [r.UserName, r.UserEmail, r.RoleName].filter(Boolean);
|
|
1862
|
+
if (!candidates.some(c => c.toLowerCase().includes(principal)))
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
if (level && r.PermissionLevel !== level)
|
|
1866
|
+
return false;
|
|
1867
|
+
return true;
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
RefreshPermissionsAudit() {
|
|
1871
|
+
this.PermissionsLoaded = false;
|
|
1872
|
+
void this.LoadPermissionsAudit();
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Pull permissions, scopes, users, roles in one batched call.
|
|
1876
|
+
* `BypassCache: true` on every view — this is an audit dashboard, so we
|
|
1877
|
+
* want SQL-truth even when permission/scope rows have been written
|
|
1878
|
+
* outside the normal `BaseEntity.Save()` path (test harnesses,
|
|
1879
|
+
* direct-SQL maintenance, recovery scripts). Without this, scope names
|
|
1880
|
+
* can render as "(unknown)" if the cache was populated before the
|
|
1881
|
+
* underlying row was inserted.
|
|
1882
|
+
*
|
|
1883
|
+
* Returns null on RunView failure (already logged).
|
|
1884
|
+
*/
|
|
1885
|
+
async fetchPermissionsAuditData() {
|
|
1886
|
+
const rv = new RunView();
|
|
1887
|
+
const result = await rv.RunViews([
|
|
1888
|
+
{ EntityName: 'MJ: Search Scope Permissions', Fields: ['ID', 'SearchScopeID', 'UserID', 'RoleID', 'PermissionLevel'], OrderBy: '__mj_CreatedAt DESC', MaxRows: 5000, ResultType: 'simple', BypassCache: true },
|
|
1889
|
+
{ EntityName: 'MJ: Search Scopes', Fields: ['ID', 'Name'], ResultType: 'simple', BypassCache: true },
|
|
1890
|
+
{ EntityName: 'MJ: Users', Fields: ['ID', 'Name', 'Email'], ResultType: 'simple', BypassCache: true },
|
|
1891
|
+
{ EntityName: 'MJ: Roles', Fields: ['ID', 'Name'], ResultType: 'simple', BypassCache: true },
|
|
1892
|
+
]);
|
|
1893
|
+
if (!result?.[0]?.Success) {
|
|
1894
|
+
LogError(`KnowledgeConfig: LoadPermissionsAudit failed: ${result?.[0]?.ErrorMessage}`);
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
perms: (result[0].Results ?? []),
|
|
1899
|
+
scopes: (result[1].Results ?? []),
|
|
1900
|
+
users: (result[2].Results ?? []),
|
|
1901
|
+
roles: (result[3].Results ?? []),
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Shape the raw fetched data into the audit-table rows. Drops
|
|
1906
|
+
* permission rows whose SearchScope is not visible to the current user
|
|
1907
|
+
* (MJ's row-level filtering hides scopes from non-Owner callers).
|
|
1908
|
+
* Surfacing the permission row with "(unknown)" leaks the row's
|
|
1909
|
+
* existence without giving the auditor anything useful — the underlying
|
|
1910
|
+
* API already enforces visibility, so this is a UX call, not a security
|
|
1911
|
+
* one.
|
|
1912
|
+
*/
|
|
1913
|
+
shapePermissionsAuditRows(data) {
|
|
1914
|
+
const scopeName = new Map(data.scopes.map(s => [s.ID, s.Name]));
|
|
1915
|
+
const userByID = new Map(data.users.map(u => [u.ID, u]));
|
|
1916
|
+
const roleName = new Map(data.roles.map(r => [r.ID, r.Name]));
|
|
1917
|
+
return data.perms
|
|
1918
|
+
.filter(p => scopeName.has(p.SearchScopeID))
|
|
1919
|
+
.map(p => {
|
|
1920
|
+
const u = p.UserID ? userByID.get(p.UserID) : null;
|
|
1921
|
+
return {
|
|
1922
|
+
ID: p.ID,
|
|
1923
|
+
SearchScopeID: p.SearchScopeID,
|
|
1924
|
+
SearchScopeName: scopeName.get(p.SearchScopeID) ?? '(unknown)',
|
|
1925
|
+
UserID: p.UserID,
|
|
1926
|
+
UserName: u?.Name ?? null,
|
|
1927
|
+
UserEmail: u?.Email ?? null,
|
|
1928
|
+
RoleID: p.RoleID,
|
|
1929
|
+
RoleName: p.RoleID ? (roleName.get(p.RoleID) ?? '(unknown)') : null,
|
|
1930
|
+
PermissionLevel: p.PermissionLevel,
|
|
1931
|
+
};
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
// ─── Search Scopes ────────────────────────────────────────────────────────
|
|
1935
|
+
async LoadSearchScopes() {
|
|
1936
|
+
this.IsLoadingScopes = true;
|
|
1937
|
+
this.cdr.detectChanges();
|
|
1938
|
+
try {
|
|
1939
|
+
const rv = new RunView();
|
|
1940
|
+
const result = await rv.RunView({
|
|
1941
|
+
EntityName: 'MJ: Search Scopes',
|
|
1942
|
+
OrderBy: 'IsGlobal DESC, IsDefault DESC, Name ASC',
|
|
1943
|
+
ResultType: 'entity_object'
|
|
1944
|
+
});
|
|
1945
|
+
if (!result.Success) {
|
|
1946
|
+
LogError(`KnowledgeConfig: LoadSearchScopes failed: ${result.ErrorMessage}`);
|
|
1947
|
+
this.SearchScopes = [];
|
|
1948
|
+
}
|
|
1949
|
+
else {
|
|
1950
|
+
// Collapse by ID — defensive against any caller (or interleaved
|
|
1951
|
+
// reactive cycle) that could ever put the same scope in twice.
|
|
1952
|
+
const byID = new Map();
|
|
1953
|
+
for (const s of result.Results ?? []) {
|
|
1954
|
+
byID.set(s.ID, s);
|
|
1955
|
+
}
|
|
1956
|
+
this.SearchScopes = Array.from(byID.values());
|
|
1957
|
+
if (!this.ActiveScopeID && this.SearchScopes.length > 0) {
|
|
1958
|
+
this.ActiveScopeID = this.SearchScopes[0].ID;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
finally {
|
|
1963
|
+
this.IsLoadingScopes = false;
|
|
1964
|
+
this.cdr.detectChanges();
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
SelectScope(scopeID) {
|
|
1968
|
+
this.ActiveScopeID = scopeID;
|
|
1969
|
+
this.ActiveScopeTab = 'definition';
|
|
1970
|
+
this.cdr.detectChanges();
|
|
1971
|
+
}
|
|
1972
|
+
SelectScopeTab(tab) {
|
|
1973
|
+
this.ActiveScopeTab = tab;
|
|
1974
|
+
this.cdr.detectChanges();
|
|
1975
|
+
}
|
|
1976
|
+
get ActiveScope() {
|
|
1977
|
+
return this.SearchScopes.find(s => UUIDsEqual(s.ID, this.ActiveScopeID ?? '')) ?? null;
|
|
1978
|
+
}
|
|
1979
|
+
async CreateNewScope() {
|
|
1980
|
+
try {
|
|
1981
|
+
const md = this.ProviderToUse;
|
|
1982
|
+
const scope = await md.GetEntityObject('MJ: Search Scopes');
|
|
1983
|
+
scope.Name = this.pickUniqueNewScopeName();
|
|
1984
|
+
scope.Description = 'New scope — configure providers, entities, or storage below.';
|
|
1985
|
+
scope.Icon = 'fa-solid fa-filter';
|
|
1986
|
+
scope.Status = 'Active';
|
|
1987
|
+
scope.IsGlobal = false;
|
|
1988
|
+
scope.IsDefault = false;
|
|
1989
|
+
const ok = await scope.Save();
|
|
1990
|
+
if (!ok) {
|
|
1991
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Create scope failed: ${scope.LatestResult?.CompleteMessage ?? 'unknown error'}`, 'error', 5000);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
// Append synchronously so a rapid second + New click picks a fresh
|
|
1995
|
+
// unique-suffix Name. An async refetch here would leave a window
|
|
1996
|
+
// where pickUniqueNewScopeName() sees a stale list and re-picks
|
|
1997
|
+
// the same suffix, producing UQ_SearchScope_Name violations.
|
|
1998
|
+
this.SearchScopes = [...this.SearchScopes, scope];
|
|
1999
|
+
this.ActiveScopeID = scope.ID;
|
|
2000
|
+
this.ActiveScopeTab = 'definition';
|
|
2001
|
+
this.cdr.detectChanges();
|
|
2002
|
+
}
|
|
2003
|
+
catch (err) {
|
|
2004
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2005
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Error creating scope: ${msg}`, 'error', 5000);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Pick a placeholder Name that doesn't collide with an existing scope.
|
|
2010
|
+
* `__mj.SearchScope.Name` is UNIQUE, so reusing the literal string
|
|
2011
|
+
* "New Search Scope" twice in a row throws SQL Server's UQ violation.
|
|
2012
|
+
* Walk an incrementing suffix until we find one the in-memory list
|
|
2013
|
+
* doesn't already use ("New Search Scope", "New Search Scope 2",
|
|
2014
|
+
* "New Search Scope 3", ...). Existing scopes table is small and
|
|
2015
|
+
* already loaded, so the linear scan is trivially cheap.
|
|
2016
|
+
*/
|
|
2017
|
+
pickUniqueNewScopeName() {
|
|
2018
|
+
const base = 'New Search Scope';
|
|
2019
|
+
const existing = new Set(this.SearchScopes.map(s => (s.Name ?? '').toLowerCase()));
|
|
2020
|
+
if (!existing.has(base.toLowerCase()))
|
|
2021
|
+
return base;
|
|
2022
|
+
for (let i = 2; i < 1000; i++) {
|
|
2023
|
+
const candidate = `${base} ${i}`;
|
|
2024
|
+
if (!existing.has(candidate.toLowerCase()))
|
|
2025
|
+
return candidate;
|
|
2026
|
+
}
|
|
2027
|
+
// Fallback: degrade to a timestamp suffix so we never throw.
|
|
2028
|
+
return `${base} ${Date.now()}`;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Open the active scope in its full custom form (a new MJExplorer tab).
|
|
2032
|
+
* The dashboard view exposes a quick-edit subset; the full form has the
|
|
2033
|
+
* Phase 2D / Phase 4 surfaces (Fusion Weights sliders, Reranker dropdown,
|
|
2034
|
+
* Reranker Budget Cents, Live Preview, Search Scope Test Queries panel,
|
|
2035
|
+
* Search Execution Logs panel, etc.). No-ops for the built-in Global
|
|
2036
|
+
* scope (no detail to author) and any scope without an ID yet.
|
|
2037
|
+
*/
|
|
2038
|
+
OpenActiveScopeFullForm() {
|
|
2039
|
+
const scope = this.ActiveScope;
|
|
2040
|
+
if (!scope?.ID)
|
|
2041
|
+
return;
|
|
2042
|
+
const pkey = new CompositeKey([{ FieldName: 'ID', Value: scope.ID }]);
|
|
2043
|
+
this.navigationService.OpenEntityRecord('MJ: Search Scopes', pkey);
|
|
2044
|
+
}
|
|
2045
|
+
async SaveActiveScope() {
|
|
2046
|
+
const scope = this.ActiveScope;
|
|
2047
|
+
if (!scope)
|
|
2048
|
+
return;
|
|
2049
|
+
const ok = await scope.Save();
|
|
2050
|
+
if (ok) {
|
|
2051
|
+
MJNotificationService.Instance.CreateSimpleNotification('Scope saved', 'success', 2000);
|
|
2052
|
+
}
|
|
2053
|
+
else {
|
|
2054
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Save failed: ${scope.LatestResult?.CompleteMessage ?? 'unknown error'}`, 'error', 5000);
|
|
2055
|
+
}
|
|
2056
|
+
this.cdr.detectChanges();
|
|
2057
|
+
}
|
|
2058
|
+
/** Format a scope Date field as the string expected by <input type="datetime-local">. */
|
|
2059
|
+
FormatScopeDate(value) {
|
|
2060
|
+
if (!value)
|
|
2061
|
+
return '';
|
|
2062
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
2063
|
+
if (isNaN(d.getTime()))
|
|
2064
|
+
return '';
|
|
2065
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
2066
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
2067
|
+
}
|
|
2068
|
+
/** Write a datetime-local input value back to the scope entity. Empty clears to null. */
|
|
2069
|
+
SetScopeDate(scope, field, value) {
|
|
2070
|
+
if (!value) {
|
|
2071
|
+
scope[field] = null;
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const parsed = new Date(value);
|
|
2075
|
+
scope[field] = isNaN(parsed.getTime()) ? null : parsed;
|
|
2076
|
+
}
|
|
2077
|
+
async DeleteActiveScope() {
|
|
2078
|
+
const scope = this.ActiveScope;
|
|
2079
|
+
if (!scope)
|
|
2080
|
+
return;
|
|
2081
|
+
if (scope.IsGlobal) {
|
|
2082
|
+
MJNotificationService.Instance.CreateSimpleNotification('The built-in Global scope cannot be deleted.', 'warning', 3000);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const ok = await scope.Delete();
|
|
2086
|
+
if (!ok) {
|
|
2087
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Delete failed: ${scope.LatestResult?.CompleteMessage ?? 'unknown error'}`, 'error', 5000);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
this.SearchScopes = this.SearchScopes.filter(s => !UUIDsEqual(s.ID, scope.ID));
|
|
2091
|
+
this.ActiveScopeID = this.SearchScopes.length > 0 ? this.SearchScopes[0].ID : null;
|
|
889
2092
|
this.cdr.detectChanges();
|
|
890
2093
|
}
|
|
891
2094
|
OnSettingChanged() {
|
|
@@ -1169,11 +2372,11 @@ let KnowledgeConfigResourceComponent = class KnowledgeConfigResourceComponent ex
|
|
|
1169
2372
|
}));
|
|
1170
2373
|
}
|
|
1171
2374
|
static ɵfac = /*@__PURE__*/ (() => { let ɵKnowledgeConfigResourceComponent_BaseFactory; return function KnowledgeConfigResourceComponent_Factory(__ngFactoryType__) { return (ɵKnowledgeConfigResourceComponent_BaseFactory || (ɵKnowledgeConfigResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(KnowledgeConfigResourceComponent)))(__ngFactoryType__ || KnowledgeConfigResourceComponent); }; })();
|
|
1172
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KnowledgeConfigResourceComponent, selectors: [["app-knowledge-config-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "config-loading"], [1, "config-layout"], ["text", "Loading configuration...", "size", "medium"], [1, "config-nav"], [1, "config-nav-header"], [1, "fa-solid", "fa-cogs"], [1, "config-nav-item", 3, "config-nav-item-active"], [1, "config-content"], [1, "config-section"], [1, "config-section-content"], [1, "config-save-bar"], [1, "config-nav-item", 3, "click"], [1, "config-section-title"], [1, "config-section-desc"], [1, "config-group"], [1, "config-toggle-row"], [1, "config-toggle-info"], [1, "config-label"], [1, "config-hint"], ["type", "checkbox", 1, "config-checkbox", 3, "ngModelChange", "change", "ngModel"], [1, "config-field-row"], [1, "config-field-info"], ["type", "number", "min", "10", "max", "1000", 1, "config-input", "config-input-number", 3, "ngModelChange", "input", "ngModel"], ["type", "number", "min", "1", "max", "10", 1, "config-input", "config-input-number", 3, "ngModelChange", "input", "ngModel"], [1, "setup-progress"], [1, "setup-progress-header"], [1, "setup-progress-label"], [1, "setup-progress-count"], [1, "setup-progress-bar"], [1, "setup-progress-fill"], [1, "setup-step"], [1, "setup-step-header"], [1, "setup-step-indicator"], [1, "fa-solid", "fa-circle-check"], [1, "setup-step-number"], [1, "setup-step-info"], [1, "setup-step-title"], [1, "setup-step-status"], [1, "setup-step-detail"], [1, "setup-step-action"], [1, "create-index-form"], [1, "provider-card"], [1, "provider-icon"], [1, "fa-solid", "fa-database"], [1, "provider-info"], [1, "provider-name"], [1, "provider-class"], [1, "config-status-badge", "config-status-active"], [1, "provider-credential-row"], [1, "provider-credential-label"], [1, "fa-solid", "fa-key"], [1, "provider-credential-picker"], [1, "provider-credential-select", 3, "ngModelChange", "change", "ngModel", "disabled"], [3, "ngValue"], [1, "fa-solid", "fa-spinner", "fa-spin", "provider-credential-spinner"], [1, "config-tag-list"], [1, "config-tag"], [1, "setup-step-action", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "index-card"], [1, "index-icon"], [1, "fa-solid", "fa-cubes"], [1, "index-info"], [1, "index-name"], [1, "index-meta"], [1, "fa-solid", "fa-microchip"], [1, "index-actions"], ["title", "Delete index", 1, "index-delete-btn", 3, "click"], [1, "fa-solid", "fa-trash-can"], [1, "create-index-title"], [1, "fa-solid", "fa-plus-circle"], [1, "create-index-fields"], [1, "create-index-field"], [1, "create-index-label"], ["type", "text", "placeholder", "e.g., mj-knowledge-index", 1, "config-input", 3, "ngModelChange", "ngModel"], [1, "config-select", 3, "ngModelChange", "ngModel"], [3, "value"], [1, "create-index-actions"], [1, "create-index-submit", 3, "click", "disabled"], [1, "create-index-cancel", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["text", "Discovering searchable entities...", "size", "medium"], [1, "config-empty-state"], [1, "fa-solid", "fa-text-width", "config-empty-icon"], [1, "config-empty-title"], [1, "config-empty-text"], [1, "fts-entity-controls"], [1, "fts-summary"], [1, "fts-summary-count"], ["type", "text", "placeholder", "Filter entities...", 1, "config-input", "fts-filter-input", 3, "ngModelChange", "ngModel"], [1, "fts-entity-list"], [1, "fts-entity-card", 3, "fts-entity-enabled"], [1, "fts-entity-card"], [1, "fts-entity-toggle"], [1, "fts-entity-info"], [1, "fts-entity-name"], [1, "fts-entity-fields"], [1, "fts-field-tag"], [1, "fts-entity-meta"], ["title", "Title field", 1, "fts-entity-title-field"], [1, "fa-solid", "fa-heading"], ["title", "Snippet field", 1, "fts-entity-snippet-field"], [1, "fa-solid", "fa-align-left"], [1, "config-value-display"], [1, "config-section-note"], [1, "fa-solid", "fa-info-circle"], [1, "fa-solid", "fa-microchip", "config-empty-icon"], ["type", "range", "min", "0.5", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], ["type", "range", "min", "0.3", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], ["type", "range", "min", "0", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], [1, "fa-solid", "fa-
|
|
1173
|
-
i0.ɵɵconditionalCreate(0, KnowledgeConfigResourceComponent_Conditional_0_Template, 2, 0, "div", 0)(1, KnowledgeConfigResourceComponent_Conditional_1_Template,
|
|
2375
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KnowledgeConfigResourceComponent, selectors: [["app-knowledge-config-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "config-loading"], [1, "config-layout"], ["text", "Loading configuration...", "size", "medium"], [1, "config-nav"], [1, "config-nav-header"], [1, "fa-solid", "fa-cogs"], [1, "config-nav-item", 3, "config-nav-item-active"], [1, "config-content"], [1, "config-section"], [1, "config-section-content"], [1, "config-save-bar"], [1, "config-nav-item", 3, "click"], [1, "config-section-title"], [1, "config-section-desc"], [1, "config-group"], [1, "config-toggle-row"], [1, "config-toggle-info"], [1, "config-label"], [1, "config-hint"], ["type", "checkbox", 1, "config-checkbox", 3, "ngModelChange", "change", "ngModel"], [1, "config-field-row"], [1, "config-field-info"], ["type", "number", "min", "10", "max", "1000", 1, "config-input", "config-input-number", 3, "ngModelChange", "input", "ngModel"], ["type", "number", "min", "1", "max", "10", 1, "config-input", "config-input-number", 3, "ngModelChange", "input", "ngModel"], [1, "setup-progress"], [1, "setup-progress-header"], [1, "setup-progress-label"], [1, "setup-progress-count"], [1, "setup-progress-bar"], [1, "setup-progress-fill"], [1, "setup-step"], [1, "setup-step-header"], [1, "setup-step-indicator"], [1, "fa-solid", "fa-circle-check"], [1, "setup-step-number"], [1, "setup-step-info"], [1, "setup-step-title"], [1, "setup-step-status"], [1, "setup-step-detail"], [1, "setup-step-action"], [1, "create-index-form"], [1, "provider-card"], [1, "provider-icon"], [1, "fa-solid", "fa-database"], [1, "provider-info"], [1, "provider-name"], [1, "provider-class"], [1, "config-status-badge", "config-status-active"], [1, "provider-credential-row"], [1, "provider-credential-label"], [1, "fa-solid", "fa-key"], [1, "provider-credential-picker"], [1, "provider-credential-select", 3, "ngModelChange", "change", "ngModel", "disabled"], [3, "ngValue"], [1, "fa-solid", "fa-spinner", "fa-spin", "provider-credential-spinner"], [1, "config-tag-list"], [1, "config-tag"], [1, "setup-step-action", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "index-card"], [1, "index-icon"], [1, "fa-solid", "fa-cubes"], [1, "index-info"], [1, "index-name"], [1, "index-meta"], [1, "fa-solid", "fa-microchip"], [1, "index-actions"], ["title", "Delete index", 1, "index-delete-btn", 3, "click"], [1, "fa-solid", "fa-trash-can"], [1, "create-index-title"], [1, "fa-solid", "fa-plus-circle"], [1, "create-index-fields"], [1, "create-index-field"], [1, "create-index-label"], ["type", "text", "placeholder", "e.g., mj-knowledge-index", 1, "config-input", 3, "ngModelChange", "ngModel"], [1, "config-select", 3, "ngModelChange", "ngModel"], [3, "value"], [1, "create-index-actions"], [1, "create-index-submit", 3, "click", "disabled"], [1, "create-index-cancel", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["text", "Discovering searchable entities...", "size", "medium"], [1, "config-empty-state"], [1, "fa-solid", "fa-text-width", "config-empty-icon"], [1, "config-empty-title"], [1, "config-empty-text"], [1, "fts-entity-controls"], [1, "fts-summary"], [1, "fts-summary-count"], ["type", "text", "placeholder", "Filter entities...", 1, "config-input", "fts-filter-input", 3, "ngModelChange", "ngModel"], [1, "fts-entity-list"], [1, "fts-entity-card", 3, "fts-entity-enabled"], [1, "fts-entity-card"], [1, "fts-entity-toggle"], [1, "fts-entity-info"], [1, "fts-entity-name"], [1, "fts-entity-fields"], [1, "fts-field-tag"], [1, "fts-entity-meta"], ["title", "Title field", 1, "fts-entity-title-field"], [1, "fa-solid", "fa-heading"], ["title", "Snippet field", 1, "fts-entity-snippet-field"], [1, "fa-solid", "fa-align-left"], [1, "config-value-display"], [1, "config-section-note"], [1, "fa-solid", "fa-info-circle"], [1, "fa-solid", "fa-microchip", "config-empty-icon"], ["type", "range", "min", "0.5", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], ["type", "range", "min", "0.3", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], ["type", "range", "min", "0", "max", "1", "step", "0.01", 1, "config-slider", 3, "ngModelChange", "input", "ngModel"], [1, "fa-solid", "fa-compass-drafting"], [1, "scope-manager"], [1, "scope-manager-list"], [1, "scope-manager-list-header"], ["type", "button", 1, "scope-manager-new-btn", 3, "click"], [1, "scope-manager-loading"], [1, "scope-manager-empty"], [1, "scope-manager-detail"], [1, "scope-manager-empty-detail"], ["type", "button", 1, "scope-manager-list-item", 3, "active"], ["type", "button", 1, "scope-manager-list-item", 3, "click"], ["aria-hidden", "true"], [1, "scope-manager-list-name"], [1, "scope-manager-badge"], [1, "scope-manager-tabs"], ["type", "button", 1, "scope-manager-tab", 3, "click"], [1, "scope-manager-tab-spacer"], ["type", "button", "title", "Open the full custom form for this scope (Live Preview, Fusion Weights, Reranker, Test Queries, Execution Logs)", 1, "scope-manager-open-btn"], ["type", "button", 1, "scope-manager-save-btn", 3, "click"], [1, "fa-solid", "fa-save"], ["type", "button", "title", "Delete scope", 1, "scope-manager-delete-btn"], [1, "scope-definition-grid"], ["ChildEntityName", "MJ: Search Scope Providers", "ParentFieldName", "SearchScopeID", "AddButtonLabel", "+ Add provider", "EmptyMessage", "No providers assigned. Without any providers, this scope searches nothing.", 3, "ParentID", "Columns"], ["ChildEntityName", "MJ: Search Scope External Indexes", "ParentFieldName", "SearchScopeID", "AddButtonLabel", "+ Add external index", "EmptyMessage", "No external indexes configured for this scope.", 3, "ParentID", "Columns"], ["ChildEntityName", "MJ: Search Scope Entities", "ParentFieldName", "SearchScopeID", "AddButtonLabel", "+ Add entity", "EmptyMessage", "No entities configured. Scoped entity search will return no rows.", 3, "ParentID", "Columns"], ["ChildEntityName", "MJ: Search Scope Storage Accounts", "ParentFieldName", "SearchScopeID", "AddButtonLabel", "+ Add storage folder", "EmptyMessage", "No storage accounts linked to this scope.", 3, "ParentID", "Columns"], ["type", "button", "title", "Open the full custom form for this scope (Live Preview, Fusion Weights, Reranker, Test Queries, Execution Logs)", 1, "scope-manager-open-btn", 3, "click"], [1, "fa-solid", "fa-up-right-from-square"], ["type", "button", "title", "Delete scope", 1, "scope-manager-delete-btn", 3, "click"], [1, "fa-solid", "fa-trash"], ["type", "text", 1, "mj-input", 3, "change", "value"], [1, "scope-definition-full"], ["rows", "2", 1, "mj-textarea", 3, "change", "value"], [1, "mj-input", 3, "change", "value"], ["value", "Active"], ["value", "Inactive"], ["type", "datetime-local", 1, "mj-input", 3, "change", "value"], [1, "scope-definition-toggle"], ["type", "checkbox", 3, "change", "checked", "disabled"], ["type", "checkbox", "disabled", "", 3, "checked"], ["rows", "4", "spellcheck", "false", 1, "mj-textarea", "scope-code-block", 3, "change", "value"], [1, "scope-permissions-help", 2, "margin-bottom", "12px", "padding", "8px 12px", "background", "var(--mj-bg-surface-card)", "border-left", "3px solid var(--mj-brand-primary)", "border-radius", "4px"], [2, "margin", "0", "font-size", "0.95em"], [1, "fa-solid", "fa-shield-halved", 2, "color", "var(--mj-brand-primary)"], ["ChildEntityName", "MJ: Search Scope Permissions", "ParentFieldName", "SearchScopeID", "AddButtonLabel", "+ Grant access", "EmptyMessage", "No explicit grants. Access falls back to agent SearchScopeAccess only.", 3, "ParentID", "Columns"], [1, "fa-solid", "fa-chart-line"], ["text", "Loading analytics\u2026", "size", "medium"], ["type", "button", 1, "mj-input"], ["type", "button", 1, "mj-input", 3, "click"], [1, "fa-solid", "fa-arrows-rotate"], [1, "search-analytics-kpi-grid"], [1, "search-analytics-kpi"], [1, "search-analytics-kpi-label"], [1, "search-analytics-kpi-value"], [1, "search-analytics-kpi-hint"], [1, "search-analytics-h3"], [1, "search-analytics-empty"], [1, "search-analytics-table"], ["type", "button", 1, "mj-input", "search-analytics-refresh", 3, "click"], [1, "fa-solid", "fa-shield-halved"], [1, "search-permissions-loading"], [1, "search-permissions-filters"], ["type", "text", "placeholder", "Filter by scope name...", 1, "mj-input", 3, "ngModelChange", "ngModel"], ["type", "text", "placeholder", "Filter by user/role (name or email)...", 1, "mj-input", 3, "ngModelChange", "ngModel"], [1, "mj-input", 3, "ngModelChange", "ngModel"], ["value", ""], ["value", "None"], ["value", "Read"], ["value", "Search"], ["value", "Manage"], [1, "search-permissions-summary"], [1, "search-permissions-empty"], [1, "search-permissions-table"], [1, "search-permissions-tag"], [1, "search-permissions-secondary"], [1, "fa-solid", "fa-clock"], [2, "margin-top", "16px"], [1, "config-save-text"], [1, "config-save-btn", 3, "click", "disabled"], [1, "config-reset-btn", 3, "click", "disabled"]], template: function KnowledgeConfigResourceComponent_Template(rf, ctx) { if (rf & 1) {
|
|
2376
|
+
i0.ɵɵconditionalCreate(0, KnowledgeConfigResourceComponent_Conditional_0_Template, 2, 0, "div", 0)(1, KnowledgeConfigResourceComponent_Conditional_1_Template, 19, 10, "div", 1);
|
|
1174
2377
|
} if (rf & 2) {
|
|
1175
2378
|
i0.ɵɵconditional(ctx.IsLoading ? 0 : 1);
|
|
1176
|
-
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.RangeValueAccessor, i1.CheckboxControlValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel, i2.LoadingComponent, i3.SchedulingResourceComponent], styles: ["\n\n\n.config-loading[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 400px;\n}\n\n.config-layout[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n min-height: 500px;\n}\n\n\n\n.config-nav[_ngcontent-%COMP%] {\n width: 240px;\n border-right: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0;\n}\n\n.config-nav-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n font-weight: 600;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n margin-bottom: 0.5rem;\n}\n\n.config-nav-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.55rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n text-align: left;\n transition: all 0.15s ease;\n width: 100%;\n}\n\n.config-nav-item[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.config-nav-item-active[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n\n\n.config-content[_ngcontent-%COMP%] {\n flex: 1;\n padding: 1.5rem 2rem;\n overflow-y: auto;\n position: relative;\n}\n\n.config-section[_ngcontent-%COMP%] {\n max-width: 700px;\n}\n\n.config-section-title[_ngcontent-%COMP%] {\n font-size: 1.3rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem;\n}\n\n.config-section-desc[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n}\n\n\n\n.config-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.config-toggle-row[_ngcontent-%COMP%], \n.config-field-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-radius: 8px;\n transition: background 0.1s ease;\n gap: 1rem;\n}\n\n.config-toggle-row[_ngcontent-%COMP%]:hover, \n.config-field-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.config-toggle-info[_ngcontent-%COMP%], \n.config-field-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n flex: 1;\n}\n\n.config-label[_ngcontent-%COMP%] {\n font-weight: 500;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n\n.config-hint[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.config-checkbox[_ngcontent-%COMP%] {\n width: 1.1rem;\n height: 1.1rem;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.config-input[_ngcontent-%COMP%] {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n font-family: inherit;\n}\n\n.config-input-number[_ngcontent-%COMP%] {\n width: 80px;\n text-align: center;\n}\n\n.config-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.config-slider[_ngcontent-%COMP%] {\n width: 200px;\n accent-color: var(--mj-brand-primary);\n cursor: pointer;\n}\n\n.config-value-display[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n color: var(--mj-text-secondary);\n padding: 0.3rem 0.6rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: monospace;\n}\n\n\n\n.config-placeholder[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 1.5rem;\n text-align: center;\n gap: 1rem;\n color: var(--mj-text-muted);\n}\n\n.config-placeholder-icon[_ngcontent-%COMP%] {\n font-size: 2.5rem;\n}\n\n\n\n\n.setup-progress[_ngcontent-%COMP%] {\n margin-bottom: 1.5rem;\n padding: 1rem 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n}\n\n.setup-progress-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.5rem;\n}\n\n.setup-progress-label[_ngcontent-%COMP%] {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.setup-progress-count[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.setup-progress-bar[_ngcontent-%COMP%] {\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.setup-progress-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.4s ease;\n}\n\n\n\n\n.setup-step[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n margin-bottom: 0.75rem;\n transition: border-color 0.2s;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] {\n border-color: var(--mj-status-success-border);\n background: color-mix(in srgb, var(--mj-status-success) 3%, var(--mj-bg-surface));\n}\n\n.setup-step-pending[_ngcontent-%COMP%] {\n border-color: var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.setup-step-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 0.85rem;\n}\n\n.setup-step-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n margin-top: 1px;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] .setup-step-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.25rem;\n color: var(--mj-status-success);\n}\n\n.setup-step-number[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n font-weight: 700;\n}\n\n.setup-step-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n flex: 1;\n}\n\n.setup-step-title[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.setup-step-status[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] .setup-step-status[_ngcontent-%COMP%] {\n color: var(--mj-status-success-text);\n}\n\n\n\n\n.config-group-title[_ngcontent-%COMP%] {\n margin: 0 0 0.75rem 0;\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n\n\n\n.config-status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 0.75rem;\n font-weight: 600;\n}\n\n.config-status-active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n\n\n\n.setup-step-detail[_ngcontent-%COMP%] {\n margin-top: 0.75rem;\n padding-left: 2.85rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.setup-step-action[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.setup-step-action[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n\n\n\n.provider-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n}\n\n.provider-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.provider-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.provider-name[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.provider-class[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n font-family: monospace;\n}\n\n\n\n\n.provider-credential-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem 0.75rem;\n margin-top: -0.25rem;\n}\n\n.provider-credential-label[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.provider-credential-label[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.provider-credential-picker[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-width: 0;\n}\n\n.provider-credential-select[_ngcontent-%COMP%] {\n flex: 1;\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.8rem;\n}\n\n.provider-credential-select[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.provider-credential-spinner[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n\n\n\n.index-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n transition: border-color 0.15s;\n}\n\n.index-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-default);\n}\n\n.index-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.index-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.index-name[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.index-meta[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n}\n\n.index-meta[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.65rem;\n}\n\n.index-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n\n.index-delete-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.index-delete-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error);\n}\n\n\n\n\n.create-index-form[_ngcontent-%COMP%] {\n margin-top: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-brand-primary);\n border-radius: 10px;\n margin-left: 2.85rem;\n}\n\n.create-index-title[_ngcontent-%COMP%] {\n margin: 0 0 1rem 0;\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.create-index-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.create-index-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n.create-index-field[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.create-index-label[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.config-select[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n}\n\n.config-select[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.create-index-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: 1rem;\n}\n\n.create-index-submit[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n\n.create-index-submit[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.create-index-submit[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.create-index-cancel[_ngcontent-%COMP%] {\n padding: 8px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.create-index-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n\n\n.config-empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 2rem;\n text-align: center;\n gap: 0.75rem;\n border: 2px dashed var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.config-empty-icon[_ngcontent-%COMP%] {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n}\n\n.config-empty-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.config-empty-text[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.85rem;\n color: var(--mj-text-muted);\n max-width: 480px;\n line-height: 1.5;\n}\n\n\n\n.config-tag-list[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.config-tag[_ngcontent-%COMP%] {\n padding: 3px 10px;\n border-radius: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n\n\n.config-section-note[_ngcontent-%COMP%] {\n margin-top: 1rem;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.config-section-note[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-info);\n}\n\n\n\n.config-save-bar[_ngcontent-%COMP%] {\n position: sticky;\n bottom: 0;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n margin-top: 2rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.config-save-text[_ngcontent-%COMP%] {\n flex: 1;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n}\n\n.config-save-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.config-save-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.config-save-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.config-reset-btn[_ngcontent-%COMP%] {\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.config-reset-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n\n\n@media (max-width: 768px) {\n .config-layout[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .config-nav[_ngcontent-%COMP%] {\n width: 100%;\n flex-direction: row;\n overflow-x: auto;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 0;\n }\n\n .config-nav-header[_ngcontent-%COMP%] {\n display: none;\n }\n\n .config-nav-item[_ngcontent-%COMP%] {\n white-space: nowrap;\n padding: 0.6rem 1rem;\n }\n\n .config-nav-item-active[_ngcontent-%COMP%] {\n border-left: none;\n border-bottom: 3px solid var(--mj-brand-primary);\n }\n\n .config-content[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n}\n\n\n\n.fts-entity-controls[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n}\n\n.fts-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.fts-summary-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.fts-filter-input[_ngcontent-%COMP%] {\n max-width: 240px;\n}\n\n.fts-entity-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.fts-entity-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n transition: border-color 0.15s, background 0.15s;\n}\n\n.fts-entity-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n}\n\n.fts-entity-card.fts-entity-enabled[_ngcontent-%COMP%] {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n}\n\n.fts-entity-toggle[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n.fts-entity-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.fts-entity-name[_ngcontent-%COMP%] {\n display: block;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n}\n\n.fts-entity-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.fts-field-tag[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 1px 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.fts-entity-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n flex-shrink: 0;\n}\n\n.fts-entity-title-field[_ngcontent-%COMP%], \n.fts-entity-snippet-field[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.fts-entity-title-field[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n.fts-entity-snippet-field[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 4px;\n font-size: 10px;\n}"] });
|
|
2379
|
+
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.RangeValueAccessor, i1.CheckboxControlValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel, i2.LoadingComponent, i3.SearchScopeChildGridComponent, i4.SchedulingResourceComponent, i5.LowerCasePipe, i5.DecimalPipe], styles: ["\n\n\n.config-loading[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 400px;\n}\n\n.config-layout[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n min-height: 500px;\n}\n\n\n\n.config-nav[_ngcontent-%COMP%] {\n width: 240px;\n border-right: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0;\n}\n\n.config-nav-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n font-weight: 600;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n margin-bottom: 0.5rem;\n}\n\n.config-nav-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.55rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n text-align: left;\n transition: all 0.15s ease;\n width: 100%;\n}\n\n.config-nav-item[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.config-nav-item-active[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n\n\n.config-content[_ngcontent-%COMP%] {\n flex: 1;\n padding: 1.5rem 2rem;\n overflow-y: auto;\n position: relative;\n}\n\n.config-section[_ngcontent-%COMP%] {\n max-width: 700px;\n}\n\n.config-section-title[_ngcontent-%COMP%] {\n font-size: 1.3rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem;\n}\n\n.config-section-desc[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n}\n\n\n\n.config-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.config-toggle-row[_ngcontent-%COMP%], \n.config-field-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-radius: 8px;\n transition: background 0.1s ease;\n gap: 1rem;\n}\n\n.config-toggle-row[_ngcontent-%COMP%]:hover, \n.config-field-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.config-toggle-info[_ngcontent-%COMP%], \n.config-field-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n flex: 1;\n}\n\n.config-label[_ngcontent-%COMP%] {\n font-weight: 500;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n\n.config-hint[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.config-checkbox[_ngcontent-%COMP%] {\n width: 1.1rem;\n height: 1.1rem;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.config-input[_ngcontent-%COMP%] {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n font-family: inherit;\n}\n\n.config-input-number[_ngcontent-%COMP%] {\n width: 80px;\n text-align: center;\n}\n\n.config-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.config-slider[_ngcontent-%COMP%] {\n width: 200px;\n accent-color: var(--mj-brand-primary);\n cursor: pointer;\n}\n\n.config-value-display[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n color: var(--mj-text-secondary);\n padding: 0.3rem 0.6rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: monospace;\n}\n\n\n\n.config-placeholder[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 1.5rem;\n text-align: center;\n gap: 1rem;\n color: var(--mj-text-muted);\n}\n\n.config-placeholder-icon[_ngcontent-%COMP%] {\n font-size: 2.5rem;\n}\n\n\n\n\n.setup-progress[_ngcontent-%COMP%] {\n margin-bottom: 1.5rem;\n padding: 1rem 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n}\n\n.setup-progress-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.5rem;\n}\n\n.setup-progress-label[_ngcontent-%COMP%] {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.setup-progress-count[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.setup-progress-bar[_ngcontent-%COMP%] {\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.setup-progress-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.4s ease;\n}\n\n\n\n\n.setup-step[_ngcontent-%COMP%] {\n padding: 1rem 1.25rem;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n margin-bottom: 0.75rem;\n transition: border-color 0.2s;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] {\n border-color: var(--mj-status-success-border);\n background: color-mix(in srgb, var(--mj-status-success) 3%, var(--mj-bg-surface));\n}\n\n.setup-step-pending[_ngcontent-%COMP%] {\n border-color: var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.setup-step-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 0.85rem;\n}\n\n.setup-step-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n margin-top: 1px;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] .setup-step-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.25rem;\n color: var(--mj-status-success);\n}\n\n.setup-step-number[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n font-weight: 700;\n}\n\n.setup-step-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n flex: 1;\n}\n\n.setup-step-title[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.setup-step-status[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n.setup-step-complete[_ngcontent-%COMP%] .setup-step-status[_ngcontent-%COMP%] {\n color: var(--mj-status-success-text);\n}\n\n\n\n\n.config-group-title[_ngcontent-%COMP%] {\n margin: 0 0 0.75rem 0;\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n\n\n\n.config-status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 0.75rem;\n font-weight: 600;\n}\n\n.config-status-active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n\n\n\n.setup-step-detail[_ngcontent-%COMP%] {\n margin-top: 0.75rem;\n padding-left: 2.85rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.setup-step-action[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.setup-step-action[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n\n\n\n.provider-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n}\n\n.provider-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.provider-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.provider-name[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.provider-class[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n font-family: monospace;\n}\n\n\n\n\n.provider-credential-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem 0.75rem;\n margin-top: -0.25rem;\n}\n\n.provider-credential-label[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.provider-credential-label[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.provider-credential-picker[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-width: 0;\n}\n\n.provider-credential-select[_ngcontent-%COMP%] {\n flex: 1;\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.8rem;\n}\n\n.provider-credential-select[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.provider-credential-spinner[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n\n\n\n.index-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n transition: border-color 0.15s;\n}\n\n.index-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-default);\n}\n\n.index-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.index-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.index-name[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.index-meta[_ngcontent-%COMP%] {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n}\n\n.index-meta[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.65rem;\n}\n\n.index-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n\n.index-delete-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.index-delete-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error);\n}\n\n\n\n\n.create-index-form[_ngcontent-%COMP%] {\n margin-top: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-brand-primary);\n border-radius: 10px;\n margin-left: 2.85rem;\n}\n\n.create-index-title[_ngcontent-%COMP%] {\n margin: 0 0 1rem 0;\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.create-index-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.create-index-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n.create-index-field[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.create-index-label[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.config-select[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n}\n\n.config-select[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.create-index-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: 1rem;\n}\n\n.create-index-submit[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n\n.create-index-submit[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.create-index-submit[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.create-index-cancel[_ngcontent-%COMP%] {\n padding: 8px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.create-index-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n\n\n.config-empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 2rem;\n text-align: center;\n gap: 0.75rem;\n border: 2px dashed var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.config-empty-icon[_ngcontent-%COMP%] {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n}\n\n.config-empty-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.config-empty-text[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.85rem;\n color: var(--mj-text-muted);\n max-width: 480px;\n line-height: 1.5;\n}\n\n\n\n.config-tag-list[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.config-tag[_ngcontent-%COMP%] {\n padding: 3px 10px;\n border-radius: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n\n\n.config-section-note[_ngcontent-%COMP%] {\n margin-top: 1rem;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.config-section-note[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-info);\n}\n\n\n\n.config-save-bar[_ngcontent-%COMP%] {\n position: sticky;\n bottom: 0;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n margin-top: 2rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.config-save-text[_ngcontent-%COMP%] {\n flex: 1;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n}\n\n.config-save-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.config-save-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.config-save-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.config-reset-btn[_ngcontent-%COMP%] {\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.config-reset-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n\n\n@media (max-width: 768px) {\n .config-layout[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .config-nav[_ngcontent-%COMP%] {\n width: 100%;\n flex-direction: row;\n overflow-x: auto;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 0;\n }\n\n .config-nav-header[_ngcontent-%COMP%] {\n display: none;\n }\n\n .config-nav-item[_ngcontent-%COMP%] {\n white-space: nowrap;\n padding: 0.6rem 1rem;\n }\n\n .config-nav-item-active[_ngcontent-%COMP%] {\n border-left: none;\n border-bottom: 3px solid var(--mj-brand-primary);\n }\n\n .config-content[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n}\n\n\n\n.fts-entity-controls[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n}\n\n.fts-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.fts-summary-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.fts-filter-input[_ngcontent-%COMP%] {\n max-width: 240px;\n}\n\n.fts-entity-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.fts-entity-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n transition: border-color 0.15s, background 0.15s;\n}\n\n.fts-entity-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n}\n\n.fts-entity-card.fts-entity-enabled[_ngcontent-%COMP%] {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n}\n\n.fts-entity-toggle[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n.fts-entity-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.fts-entity-name[_ngcontent-%COMP%] {\n display: block;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n}\n\n.fts-entity-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.fts-field-tag[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 1px 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.fts-entity-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n flex-shrink: 0;\n}\n\n.fts-entity-title-field[_ngcontent-%COMP%], \n.fts-entity-snippet-field[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.fts-entity-title-field[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n.fts-entity-snippet-field[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 4px;\n font-size: 10px;\n}\n\n\n\n\n.scope-manager[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 260px 1fr;\n gap: 16px;\n min-height: 420px;\n}\n\n.scope-manager-list[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.scope-manager-list-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 6px 8px;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.scope-manager-new-btn[_ngcontent-%COMP%] {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-link);\n padding: 3px 8px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.7rem;\n font-weight: 500;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.scope-manager-new-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-brand-primary);\n}\n\n.scope-manager-loading[_ngcontent-%COMP%], \n.scope-manager-empty[_ngcontent-%COMP%], \n.scope-manager-empty-detail[_ngcontent-%COMP%] {\n padding: 16px;\n color: var(--mj-text-muted);\n text-align: center;\n font-size: 0.85rem;\n}\n\n.scope-manager-empty-detail[_ngcontent-%COMP%] {\n padding: 40px 24px;\n background: var(--mj-bg-surface-card);\n border: 1px dashed var(--mj-border-default);\n border-radius: 8px;\n}\n\n.scope-manager-list-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: none;\n background: transparent;\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n cursor: pointer;\n border-radius: 6px;\n text-align: left;\n transition: background 120ms ease;\n}\n\n.scope-manager-list-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.scope-manager-list-item.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.scope-manager-list-name[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.scope-manager-badge[_ngcontent-%COMP%] {\n font-size: 0.625rem;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.scope-manager-detail[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n}\n\n.scope-manager-tabs[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--mj-border-subtle);\n padding-bottom: 8px;\n flex-wrap: wrap;\n}\n\n.scope-manager-tab[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n padding: 6px 12px;\n color: var(--mj-text-secondary);\n font-size: 0.875rem;\n cursor: pointer;\n border-radius: 4px;\n transition: background 120ms ease, color 120ms ease;\n}\n\n.scope-manager-tab[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.scope-manager-tab.active[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n border-bottom: 2px solid var(--mj-brand-primary);\n border-radius: 0;\n font-weight: 500;\n}\n\n.scope-manager-tab-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.scope-manager-save-btn[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.8125rem;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.scope-manager-save-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.scope-manager-delete-btn[_ngcontent-%COMP%] {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-status-error-text);\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.scope-manager-delete-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error-border);\n}\n\n.scope-manager-open-btn[_ngcontent-%COMP%] {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-primary);\n padding: 6px 12px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.8125rem;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.scope-manager-open-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.scope-definition-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px 16px;\n}\n\n.scope-definition-grid[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.scope-definition-grid[_ngcontent-%COMP%] label[_ngcontent-%COMP%] > span[_ngcontent-%COMP%] {\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.scope-definition-full[_ngcontent-%COMP%] {\n grid-column: 1 / -1;\n}\n\n.scope-definition-toggle[_ngcontent-%COMP%] {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n\n.scope-definition-toggle[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n accent-color: var(--mj-brand-primary);\n}\n\n.scope-code-block[_ngcontent-%COMP%] {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n padding: 8px;\n}\n\n\n\n\n\n.search-analytics-kpi-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n}\n\n.search-analytics-kpi[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 14px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n}\n\n.search-analytics-kpi-label[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.search-analytics-kpi-value[_ngcontent-%COMP%] {\n font-size: 1.6rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n font-variant-numeric: tabular-nums;\n}\n\n.search-analytics-kpi-hint[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.72rem;\n}\n\n.search-analytics-h3[_ngcontent-%COMP%] {\n margin: 22px 0 8px;\n font-size: 1rem;\n color: var(--mj-text-primary);\n}\n\n.search-analytics-empty[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n margin: 4px 0 14px;\n}\n\n.search-analytics-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.86rem;\n}\n\n.search-analytics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 6px 10px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n.search-analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 6px 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-variant-numeric: tabular-nums;\n}\n\n.search-analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:last-child td[_ngcontent-%COMP%] {\n border-bottom: none;\n}\n\n.search-analytics-refresh[_ngcontent-%COMP%] {\n margin-top: 16px;\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.4rem 0.9rem;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.82rem;\n}\n\n.search-analytics-refresh[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n\n\n\n.search-permissions-loading[_ngcontent-%COMP%] {\n padding: 1.2rem;\n color: var(--mj-text-secondary);\n font-style: italic;\n}\n\n.search-permissions-filters[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr 180px 130px;\n gap: 0.5rem;\n margin-bottom: 0.8rem;\n}\n\n.search-permissions-filters[_ngcontent-%COMP%] .mj-input[_ngcontent-%COMP%] {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n font-size: 0.85rem;\n}\n\n.search-permissions-summary[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n margin-bottom: 0.6rem;\n}\n\n.search-permissions-empty[_ngcontent-%COMP%] {\n padding: 1.2rem;\n color: var(--mj-text-muted);\n text-align: center;\n background: var(--mj-bg-surface-card);\n border-radius: 4px;\n}\n\n.search-permissions-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n}\n\n.search-permissions-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n.search-permissions-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 0.5rem 0.7rem;\n border-bottom: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.search-permissions-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n}\n\n.search-permissions-secondary[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.78rem;\n}\n\n.search-permissions-tag[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 0.1rem 0.5rem;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n border-radius: 3px;\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.search-permissions-level[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 0.15rem 0.55rem;\n border-radius: 3px;\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n.search-permissions-level-none[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface));\n color: var(--mj-status-error-text);\n}\n\n.search-permissions-level-read[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n color: var(--mj-status-info-text);\n}\n\n.search-permissions-level-search[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n.search-permissions-level-manage[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}"] });
|
|
1177
2380
|
};
|
|
1178
2381
|
KnowledgeConfigResourceComponent = __decorate([
|
|
1179
2382
|
RegisterClass(BaseResourceComponent, 'KnowledgeConfigResource')
|
|
@@ -1181,9 +2384,9 @@ KnowledgeConfigResourceComponent = __decorate([
|
|
|
1181
2384
|
export { KnowledgeConfigResourceComponent };
|
|
1182
2385
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KnowledgeConfigResourceComponent, [{
|
|
1183
2386
|
type: Component,
|
|
1184
|
-
args: [{ standalone: false, selector: 'app-knowledge-config-resource', template: "@if (IsLoading) {\n <div class=\"config-loading\">\n <mj-loading text=\"Loading configuration...\" size=\"medium\"></mj-loading>\n </div>\n} @else {\n <div class=\"config-layout\">\n <!-- Left Navigation -->\n <nav class=\"config-nav\">\n <div class=\"config-nav-header\">\n <i class=\"fa-solid fa-cogs\"></i>\n <span>Configuration</span>\n </div>\n @for (section of Sections; track section.ID) {\n <button\n class=\"config-nav-item\"\n [class.config-nav-item-active]=\"ActiveSection === section.ID\"\n (click)=\"SelectSection(section.ID)\"\n >\n <i [class]=\"section.Icon\"></i>\n <span>{{ section.Label }}</span>\n </button>\n }\n </nav>\n\n <!-- Content Area -->\n <div class=\"config-content\">\n <!-- Pipeline Settings -->\n @if (ActiveSection === 'pipeline') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Pipeline Settings</h2>\n <p class=\"config-section-desc\">Configure how the Knowledge Pipeline processes incoming content.</p>\n\n <div class=\"config-group\">\n <label class=\"config-toggle-row\">\n <div class=\"config-toggle-info\">\n <span class=\"config-label\">Auto-tag on Ingest</span>\n <span class=\"config-hint\">Automatically run autotagging when new content is ingested</span>\n </div>\n <input type=\"checkbox\" [(ngModel)]=\"PipelineSettings.AutotagOnIngest\" (change)=\"OnSettingChanged()\" class=\"config-checkbox\" />\n </label>\n\n <label class=\"config-toggle-row\">\n <div class=\"config-toggle-info\">\n <span class=\"config-label\">Vectorize on Ingest</span>\n <span class=\"config-hint\">Automatically create embeddings for new content</span>\n </div>\n <input type=\"checkbox\" [(ngModel)]=\"PipelineSettings.VectorizeOnIngest\" (change)=\"OnSettingChanged()\" class=\"config-checkbox\" />\n </label>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Default Batch Size</span>\n <span class=\"config-hint\">Number of items processed per batch</span>\n </div>\n <input type=\"number\" [(ngModel)]=\"PipelineSettings.DefaultBatchSize\" (input)=\"OnSettingChanged()\" class=\"config-input config-input-number\" min=\"10\" max=\"1000\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Max Concurrent Jobs</span>\n <span class=\"config-hint\">Maximum number of pipeline jobs running at once</span>\n </div>\n <input type=\"number\" [(ngModel)]=\"PipelineSettings.MaxConcurrentJobs\" (input)=\"OnSettingChanged()\" class=\"config-input config-input-number\" min=\"1\" max=\"10\" />\n </div>\n </div>\n </div>\n }\n\n <!-- Vector Database Settings -->\n @if (ActiveSection === 'vectordb') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Vector Database</h2>\n <p class=\"config-section-desc\">Manage the shared vector index and database connection.</p>\n\n <!-- Setup Progress -->\n <div class=\"setup-progress\">\n <div class=\"setup-progress-header\">\n <span class=\"setup-progress-label\">Setup Progress</span>\n <span class=\"setup-progress-count\">{{ SetupStepsCompleted }} of 3 complete</span>\n </div>\n <div class=\"setup-progress-bar\">\n <div class=\"setup-progress-fill\" [style.width.%]=\"(SetupStepsCompleted / 3) * 100\"></div>\n </div>\n </div>\n\n <!-- Step 1: Providers -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasVectorDBProvider\" [class.setup-step-pending]=\"!HasVectorDBProvider\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasVectorDBProvider) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">1</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Vector Database Providers</span>\n @if (HasVectorDBProvider) {\n <span class=\"setup-step-status\">{{ VectorDBProviders.length }} provider(s) registered</span>\n } @else {\n <span class=\"setup-step-status\">No providers registered. Add a vector database provider (e.g., Pinecone, Weaviate) via the admin console.</span>\n }\n </div>\n </div>\n @if (HasVectorDBProvider) {\n <div class=\"setup-step-detail\">\n @for (provider of VectorDBProviders; track provider.ID) {\n <div class=\"provider-card\">\n <div class=\"provider-icon\">\n <i class=\"fa-solid fa-database\"></i>\n </div>\n <div class=\"provider-info\">\n <span class=\"provider-name\">{{ provider.Name }}</span>\n <span class=\"provider-class\">{{ provider.ClassKey }}</span>\n </div>\n <span class=\"config-status-badge config-status-active\">\n <i class=\"fa-solid fa-circle-check\"></i> Active\n </span>\n </div>\n <div class=\"provider-credential-row\">\n <label class=\"provider-credential-label\">\n <i class=\"fa-solid fa-key\"></i> API Credential\n </label>\n <div class=\"provider-credential-picker\">\n <select class=\"provider-credential-select\"\n [(ngModel)]=\"provider.CredentialID\"\n (change)=\"SaveProviderCredential(provider)\"\n [disabled]=\"IsSavingCredential\">\n <option [ngValue]=\"null\">None (use environment variable)</option>\n @for (cred of AvailableCredentials; track cred.ID) {\n <option [ngValue]=\"cred.ID\">{{ cred.Name }}</option>\n }\n </select>\n @if (IsSavingCredential) {\n <i class=\"fa-solid fa-spinner fa-spin provider-credential-spinner\"></i>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Step 2: Embedding Model -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasEmbeddingModel\" [class.setup-step-pending]=\"!HasEmbeddingModel\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasEmbeddingModel) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">2</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Embedding Models</span>\n @if (HasEmbeddingModel) {\n <span class=\"setup-step-status\">{{ EmbeddingModels.length }} model(s) available</span>\n } @else {\n <span class=\"setup-step-status\">No embedding models found. Configure at least one in the AI app > Models tab.</span>\n }\n </div>\n </div>\n @if (HasEmbeddingModel) {\n <div class=\"setup-step-detail\">\n <div class=\"config-tag-list\">\n @for (model of EmbeddingModels; track model.ID) {\n <span class=\"config-tag\">{{ model.Name }}</span>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Step 3: Vector Indexes -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasVectorIndex\" [class.setup-step-pending]=\"!HasVectorIndex\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasVectorIndex) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">3</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Vector Indexes</span>\n @if (HasVectorIndex) {\n <span class=\"setup-step-status\">{{ VectorIndexes.length }} index(es) configured</span>\n } @else if (HasVectorDBProvider && HasEmbeddingModel) {\n <span class=\"setup-step-status\">No indexes yet \u2014 create one below.</span>\n } @else {\n <span class=\"setup-step-status\">Complete steps 1 and 2 first.</span>\n }\n </div>\n @if (HasVectorDBProvider && HasEmbeddingModel && !ShowCreateIndexForm) {\n <button class=\"setup-step-action\" (click)=\"OpenCreateIndexForm()\">\n <i class=\"fa-solid fa-plus\"></i> Create Index\n </button>\n }\n </div>\n\n <!-- Existing Indexes -->\n @if (HasVectorIndex) {\n <div class=\"setup-step-detail\">\n @for (idx of VectorIndexes; track idx.ID) {\n <div class=\"index-card\">\n <div class=\"index-icon\">\n <i class=\"fa-solid fa-cubes\"></i>\n </div>\n <div class=\"index-info\">\n <span class=\"index-name\">{{ idx.Name }}</span>\n <span class=\"index-meta\">\n <i class=\"fa-solid fa-database\"></i> {{ idx.VectorDatabase }}\n \u00B7 \n <i class=\"fa-solid fa-microchip\"></i> {{ idx.EmbeddingModel }}\n </span>\n </div>\n <div class=\"index-actions\">\n <span class=\"config-status-badge config-status-active\">\n <i class=\"fa-solid fa-circle-check\"></i> Active\n </span>\n <button class=\"index-delete-btn\" (click)=\"DeleteIndex(idx.ID)\" title=\"Delete index\">\n <i class=\"fa-solid fa-trash-can\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Create Index Form -->\n @if (ShowCreateIndexForm) {\n <div class=\"create-index-form\">\n <h4 class=\"create-index-title\">\n <i class=\"fa-solid fa-plus-circle\"></i> Create New Vector Index\n </h4>\n <div class=\"create-index-fields\">\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Index Name</label>\n <input type=\"text\"\n class=\"config-input\"\n [(ngModel)]=\"NewIndexName\"\n placeholder=\"e.g., mj-knowledge-index\" />\n </div>\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Vector Database</label>\n <select class=\"config-select\" [(ngModel)]=\"NewIndexVectorDBID\">\n @for (db of VectorDBProviders; track db.ID) {\n <option [value]=\"db.ID\">{{ db.Name }}</option>\n }\n </select>\n </div>\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Embedding Model</label>\n <select class=\"config-select\" [(ngModel)]=\"NewIndexEmbeddingModelID\">\n @for (model of EmbeddingModels; track model.ID) {\n <option [value]=\"model.ID\">{{ model.Name }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"create-index-actions\">\n <button class=\"create-index-submit\" (click)=\"CreateIndex()\" [disabled]=\"IsCreatingIndex\">\n @if (IsCreatingIndex) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Creating...\n } @else {\n <i class=\"fa-solid fa-plus\"></i> Create Index\n }\n </button>\n <button class=\"create-index-cancel\" (click)=\"CancelCreateIndex()\" [disabled]=\"IsCreatingIndex\">\n Cancel\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Full-Text Indexes -->\n @if (ActiveSection === 'fulltext') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Full-Text Search Entities</h2>\n <p class=\"config-section-desc\">Configure which entities are included in full-text search. Entities with text fields (Name, Description, etc.) are automatically discovered.</p>\n\n @if (IsLoadingFTSEntities) {\n <mj-loading text=\"Discovering searchable entities...\" size=\"medium\"></mj-loading>\n } @else if (FTSEntities.length === 0) {\n <div class=\"config-empty-state\">\n <i class=\"fa-solid fa-text-width config-empty-icon\"></i>\n <h3 class=\"config-empty-title\">No Searchable Entities Found</h3>\n <p class=\"config-empty-text\">No entities with text fields were discovered. Ensure your database schema includes entities with varchar/nvarchar columns.</p>\n </div>\n } @else {\n <div class=\"fts-entity-controls\">\n <div class=\"fts-summary\">\n <span class=\"fts-summary-count\">{{ EnabledFTSCount }} of {{ FTSEntities.length }} entities enabled for search</span>\n <input type=\"text\" class=\"config-input fts-filter-input\" placeholder=\"Filter entities...\" [(ngModel)]=\"FTSFilterText\" />\n </div>\n </div>\n\n <div class=\"fts-entity-list\">\n @for (entity of FilteredFTSEntities; track entity.EntityName) {\n <div class=\"fts-entity-card\" [class.fts-entity-enabled]=\"entity.Enabled\">\n <div class=\"fts-entity-toggle\">\n <input type=\"checkbox\" [(ngModel)]=\"entity.Enabled\" (change)=\"OnFTSEntityToggled(entity)\" class=\"config-checkbox\" />\n </div>\n <div class=\"fts-entity-info\">\n <span class=\"fts-entity-name\">{{ entity.EntityName }}</span>\n <div class=\"fts-entity-fields\">\n @for (field of entity.IndexedFields; track field) {\n <span class=\"fts-field-tag\">{{ field }}</span>\n }\n </div>\n </div>\n <div class=\"fts-entity-meta\">\n <span class=\"fts-entity-title-field\" title=\"Title field\">\n <i class=\"fa-solid fa-heading\"></i> {{ entity.TitleField }}\n </span>\n @if (entity.SnippetField !== entity.TitleField) {\n <span class=\"fts-entity-snippet-field\" title=\"Snippet field\">\n <i class=\"fa-solid fa-align-left\"></i> {{ entity.SnippetField }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Embedding Models -->\n @if (ActiveSection === 'embedding') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Embedding Models</h2>\n <p class=\"config-section-desc\">AI models used for generating vector embeddings from text.</p>\n\n @if (HasEmbeddingModel) {\n <div class=\"config-group\">\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Active Model</span>\n <span class=\"config-hint\">Currently selected embedding model</span>\n </div>\n <span class=\"config-value-display\">{{ EmbeddingModelName }}</span>\n </div>\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Available Models</span>\n <span class=\"config-hint\">{{ EmbeddingModels.length }} embedding model(s) configured</span>\n </div>\n <div class=\"config-tag-list\">\n @for (model of EmbeddingModels; track model.ID) {\n <span class=\"config-tag\">{{ model.Name }}</span>\n }\n </div>\n </div>\n </div>\n <p class=\"config-section-note\">\n <i class=\"fa-solid fa-info-circle\"></i>\n Manage embedding models in the AI Dashboard > Models tab.\n </p>\n } @else {\n <div class=\"config-empty-state\">\n <i class=\"fa-solid fa-microchip config-empty-icon\"></i>\n <h3 class=\"config-empty-title\">No Embedding Models Found</h3>\n <p class=\"config-empty-text\">\n Embedding models are required to convert text into vectors for semantic search and duplicate detection.\n Configure at least one embedding model (e.g., text-embedding-3-small) in the AI Dashboard > Models tab.\n </p>\n </div>\n }\n </div>\n }\n\n <!-- Thresholds -->\n @if (ActiveSection === 'thresholds') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Scoring Thresholds</h2>\n <p class=\"config-section-desc\">Set the scoring thresholds used by search, duplicate detection, and autotagging.</p>\n\n <div class=\"config-group\">\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Duplicate Absolute Match</span>\n <span class=\"config-hint\">Score above which duplicates are auto-confirmed ({{ FormatThreshold(ThresholdSettings.DuplicateAbsolute) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.DuplicateAbsolute\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.5\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Duplicate Potential Match</span>\n <span class=\"config-hint\">Score above which duplicates are flagged for review ({{ FormatThreshold(ThresholdSettings.DuplicatePotential) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.DuplicatePotential\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.3\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Search Relevance</span>\n <span class=\"config-hint\">Minimum score for search results ({{ FormatThreshold(ThresholdSettings.SearchRelevance) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.SearchRelevance\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Autotag Confidence</span>\n <span class=\"config-hint\">Minimum confidence for accepting auto-generated tags ({{ FormatThreshold(ThresholdSettings.AutotagConfidence) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.AutotagConfidence\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.3\" max=\"1\" step=\"0.01\" />\n </div>\n </div>\n </div>\n }\n\n <!-- Scheduling Section -->\n @if (ActiveSection === 'scheduling') {\n <div class=\"config-section-content\">\n <h2 class=\"config-section-title\"><i class=\"fa-solid fa-clock\"></i> Scheduling</h2>\n <p class=\"config-section-desc\">Manage automated pipeline schedules for content classification and vector sync. Create schedules here or manage all schedules in the dedicated Scheduling app.</p>\n <div style=\"margin-top: 16px;\">\n <app-scheduling-resource></app-scheduling-resource>\n </div>\n </div>\n }\n\n <!-- Save Bar -->\n @if (HasUnsavedChanges) {\n <div class=\"config-save-bar\">\n <span class=\"config-save-text\">You have unsaved changes</span>\n <button class=\"config-save-btn\" (click)=\"SaveConfiguration()\" [disabled]=\"IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Changes\n }\n </button>\n <button class=\"config-reset-btn\" (click)=\"ResetConfiguration()\" [disabled]=\"IsSaving\">\n Reset\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: ["/* Knowledge Configuration - Settings Page with Left Nav */\n\n.config-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 400px;\n}\n\n.config-layout {\n display: flex;\n height: 100%;\n min-height: 500px;\n}\n\n/* Left Navigation */\n.config-nav {\n width: 240px;\n border-right: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0;\n}\n\n.config-nav-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n font-weight: 600;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n margin-bottom: 0.5rem;\n}\n\n.config-nav-item {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.55rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n text-align: left;\n transition: all 0.15s ease;\n width: 100%;\n}\n\n.config-nav-item:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.config-nav-item-active {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n/* Content Area */\n.config-content {\n flex: 1;\n padding: 1.5rem 2rem;\n overflow-y: auto;\n position: relative;\n}\n\n.config-section {\n max-width: 700px;\n}\n\n.config-section-title {\n font-size: 1.3rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem;\n}\n\n.config-section-desc {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n}\n\n/* Config Groups */\n.config-group {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.config-toggle-row,\n.config-field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-radius: 8px;\n transition: background 0.1s ease;\n gap: 1rem;\n}\n\n.config-toggle-row:hover,\n.config-field-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.config-toggle-info,\n.config-field-info {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n flex: 1;\n}\n\n.config-label {\n font-weight: 500;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n\n.config-hint {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.config-checkbox {\n width: 1.1rem;\n height: 1.1rem;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.config-input {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n font-family: inherit;\n}\n\n.config-input-number {\n width: 80px;\n text-align: center;\n}\n\n.config-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.config-slider {\n width: 200px;\n accent-color: var(--mj-brand-primary);\n cursor: pointer;\n}\n\n.config-value-display {\n font-size: 0.85rem;\n color: var(--mj-text-secondary);\n padding: 0.3rem 0.6rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: monospace;\n}\n\n/* Placeholder */\n.config-placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 1.5rem;\n text-align: center;\n gap: 1rem;\n color: var(--mj-text-muted);\n}\n\n.config-placeholder-icon {\n font-size: 2.5rem;\n}\n\n/* ---- Setup Progress Bar ---- */\n\n.setup-progress {\n margin-bottom: 1.5rem;\n padding: 1rem 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n}\n\n.setup-progress-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.5rem;\n}\n\n.setup-progress-label {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.setup-progress-count {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.setup-progress-bar {\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.setup-progress-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.4s ease;\n}\n\n/* ---- Setup Steps ---- */\n\n.setup-step {\n padding: 1rem 1.25rem;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n margin-bottom: 0.75rem;\n transition: border-color 0.2s;\n}\n\n.setup-step-complete {\n border-color: var(--mj-status-success-border);\n background: color-mix(in srgb, var(--mj-status-success) 3%, var(--mj-bg-surface));\n}\n\n.setup-step-pending {\n border-color: var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.setup-step-header {\n display: flex;\n align-items: flex-start;\n gap: 0.85rem;\n}\n\n.setup-step-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n margin-top: 1px;\n}\n\n.setup-step-complete .setup-step-indicator i {\n font-size: 1.25rem;\n color: var(--mj-status-success);\n}\n\n.setup-step-number {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n font-weight: 700;\n}\n\n.setup-step-info {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n flex: 1;\n}\n\n.setup-step-title {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.setup-step-status {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n.setup-step-complete .setup-step-status {\n color: var(--mj-status-success-text);\n}\n\n/* ---- Config Group Title ---- */\n\n.config-group-title {\n margin: 0 0 0.75rem 0;\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n/* ---- Status Badge ---- */\n\n.config-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 0.75rem;\n font-weight: 600;\n}\n\n.config-status-active {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n/* ---- Step Detail Area ---- */\n\n.setup-step-detail {\n margin-top: 0.75rem;\n padding-left: 2.85rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.setup-step-action {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.setup-step-action:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* ---- Provider Cards ---- */\n\n.provider-card {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n}\n\n.provider-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.provider-info {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.provider-name {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.provider-class {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n font-family: monospace;\n}\n\n/* ---- Provider Credential Row ---- */\n\n.provider-credential-row {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem 0.75rem;\n margin-top: -0.25rem;\n}\n\n.provider-credential-label {\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.provider-credential-label i {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.provider-credential-picker {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-width: 0;\n}\n\n.provider-credential-select {\n flex: 1;\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.8rem;\n}\n\n.provider-credential-select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.provider-credential-spinner {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n/* ---- Index Cards ---- */\n\n.index-card {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n transition: border-color 0.15s;\n}\n\n.index-card:hover {\n border-color: var(--mj-border-default);\n}\n\n.index-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.index-info {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.index-name {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.index-meta {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n}\n\n.index-meta i {\n font-size: 0.65rem;\n}\n\n.index-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n\n.index-delete-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.index-delete-btn:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error);\n}\n\n/* ---- Create Index Form ---- */\n\n.create-index-form {\n margin-top: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-brand-primary);\n border-radius: 10px;\n margin-left: 2.85rem;\n}\n\n.create-index-title {\n margin: 0 0 1rem 0;\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.create-index-title i {\n color: var(--mj-brand-primary);\n}\n\n.create-index-fields {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n.create-index-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.create-index-label {\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.config-select {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n}\n\n.config-select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.create-index-actions {\n display: flex;\n gap: 8px;\n margin-top: 1rem;\n}\n\n.create-index-submit {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n\n.create-index-submit:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.create-index-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.create-index-cancel {\n padding: 8px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.create-index-cancel:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Empty State for unconfigured sections */\n.config-empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 2rem;\n text-align: center;\n gap: 0.75rem;\n border: 2px dashed var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.config-empty-icon {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n}\n\n.config-empty-title {\n margin: 0;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.config-empty-text {\n margin: 0;\n font-size: 0.85rem;\n color: var(--mj-text-muted);\n max-width: 480px;\n line-height: 1.5;\n}\n\n/* Tag list for showing multiple models */\n.config-tag-list {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.config-tag {\n padding: 3px 10px;\n border-radius: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n/* Section note */\n.config-section-note {\n margin-top: 1rem;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.config-section-note i {\n color: var(--mj-status-info);\n}\n\n/* Save Bar */\n.config-save-bar {\n position: sticky;\n bottom: 0;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n margin-top: 2rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.config-save-text {\n flex: 1;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n}\n\n.config-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.config-save-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.config-save-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.config-reset-btn {\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.config-reset-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile responsive */\n@media (max-width: 768px) {\n .config-layout {\n flex-direction: column;\n }\n\n .config-nav {\n width: 100%;\n flex-direction: row;\n overflow-x: auto;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 0;\n }\n\n .config-nav-header {\n display: none;\n }\n\n .config-nav-item {\n white-space: nowrap;\n padding: 0.6rem 1rem;\n }\n\n .config-nav-item-active {\n border-left: none;\n border-bottom: 3px solid var(--mj-brand-primary);\n }\n\n .config-content {\n padding: 1rem;\n }\n}\n\n/* Full-Text Search Entity Management */\n.fts-entity-controls {\n margin-bottom: 16px;\n}\n\n.fts-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.fts-summary-count {\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.fts-filter-input {\n max-width: 240px;\n}\n\n.fts-entity-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.fts-entity-card {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n transition: border-color 0.15s, background 0.15s;\n}\n\n.fts-entity-card:hover {\n border-color: var(--mj-border-strong);\n}\n\n.fts-entity-card.fts-entity-enabled {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n}\n\n.fts-entity-toggle {\n flex-shrink: 0;\n}\n\n.fts-entity-info {\n flex: 1;\n min-width: 0;\n}\n\n.fts-entity-name {\n display: block;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n}\n\n.fts-entity-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.fts-field-tag {\n display: inline-block;\n padding: 1px 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.fts-entity-meta {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n flex-shrink: 0;\n}\n\n.fts-entity-title-field,\n.fts-entity-snippet-field {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.fts-entity-title-field i,\n.fts-entity-snippet-field i {\n margin-right: 4px;\n font-size: 10px;\n}\n"] }]
|
|
2387
|
+
args: [{ standalone: false, selector: 'app-knowledge-config-resource', template: "@if (IsLoading) {\n <div class=\"config-loading\">\n <mj-loading text=\"Loading configuration...\" size=\"medium\"></mj-loading>\n </div>\n} @else {\n <div class=\"config-layout\">\n <!-- Left Navigation -->\n <nav class=\"config-nav\">\n <div class=\"config-nav-header\">\n <i class=\"fa-solid fa-cogs\"></i>\n <span>Configuration</span>\n </div>\n @for (section of Sections; track section.ID) {\n <button\n class=\"config-nav-item\"\n [class.config-nav-item-active]=\"ActiveSection === section.ID\"\n (click)=\"SelectSection(section.ID)\"\n >\n <i [class]=\"section.Icon\"></i>\n <span>{{ section.Label }}</span>\n </button>\n }\n </nav>\n\n <!-- Content Area -->\n <div class=\"config-content\">\n <!-- Pipeline Settings -->\n @if (ActiveSection === 'pipeline') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Pipeline Settings</h2>\n <p class=\"config-section-desc\">Configure how the Knowledge Pipeline processes incoming content.</p>\n\n <div class=\"config-group\">\n <label class=\"config-toggle-row\">\n <div class=\"config-toggle-info\">\n <span class=\"config-label\">Auto-tag on Ingest</span>\n <span class=\"config-hint\">Automatically run autotagging when new content is ingested</span>\n </div>\n <input type=\"checkbox\" [(ngModel)]=\"PipelineSettings.AutotagOnIngest\" (change)=\"OnSettingChanged()\" class=\"config-checkbox\" />\n </label>\n\n <label class=\"config-toggle-row\">\n <div class=\"config-toggle-info\">\n <span class=\"config-label\">Vectorize on Ingest</span>\n <span class=\"config-hint\">Automatically create embeddings for new content</span>\n </div>\n <input type=\"checkbox\" [(ngModel)]=\"PipelineSettings.VectorizeOnIngest\" (change)=\"OnSettingChanged()\" class=\"config-checkbox\" />\n </label>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Default Batch Size</span>\n <span class=\"config-hint\">Number of items processed per batch</span>\n </div>\n <input type=\"number\" [(ngModel)]=\"PipelineSettings.DefaultBatchSize\" (input)=\"OnSettingChanged()\" class=\"config-input config-input-number\" min=\"10\" max=\"1000\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Max Concurrent Jobs</span>\n <span class=\"config-hint\">Maximum number of pipeline jobs running at once</span>\n </div>\n <input type=\"number\" [(ngModel)]=\"PipelineSettings.MaxConcurrentJobs\" (input)=\"OnSettingChanged()\" class=\"config-input config-input-number\" min=\"1\" max=\"10\" />\n </div>\n </div>\n </div>\n }\n\n <!-- Vector Database Settings -->\n @if (ActiveSection === 'vectordb') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Vector Database</h2>\n <p class=\"config-section-desc\">Manage the shared vector index and database connection.</p>\n\n <!-- Setup Progress -->\n <div class=\"setup-progress\">\n <div class=\"setup-progress-header\">\n <span class=\"setup-progress-label\">Setup Progress</span>\n <span class=\"setup-progress-count\">{{ SetupStepsCompleted }} of 3 complete</span>\n </div>\n <div class=\"setup-progress-bar\">\n <div class=\"setup-progress-fill\" [style.width.%]=\"(SetupStepsCompleted / 3) * 100\"></div>\n </div>\n </div>\n\n <!-- Step 1: Providers -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasVectorDBProvider\" [class.setup-step-pending]=\"!HasVectorDBProvider\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasVectorDBProvider) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">1</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Vector Database Providers</span>\n @if (HasVectorDBProvider) {\n <span class=\"setup-step-status\">{{ VectorDBProviders.length }} provider(s) registered</span>\n } @else {\n <span class=\"setup-step-status\">No providers registered. Add a vector database provider (e.g., Pinecone, Weaviate) via the admin console.</span>\n }\n </div>\n </div>\n @if (HasVectorDBProvider) {\n <div class=\"setup-step-detail\">\n @for (provider of VectorDBProviders; track provider.ID) {\n <div class=\"provider-card\">\n <div class=\"provider-icon\">\n <i class=\"fa-solid fa-database\"></i>\n </div>\n <div class=\"provider-info\">\n <span class=\"provider-name\">{{ provider.Name }}</span>\n <span class=\"provider-class\">{{ provider.ClassKey }}</span>\n </div>\n <span class=\"config-status-badge config-status-active\">\n <i class=\"fa-solid fa-circle-check\"></i> Active\n </span>\n </div>\n <div class=\"provider-credential-row\">\n <label class=\"provider-credential-label\">\n <i class=\"fa-solid fa-key\"></i> API Credential\n </label>\n <div class=\"provider-credential-picker\">\n <select class=\"provider-credential-select\"\n [(ngModel)]=\"provider.CredentialID\"\n (change)=\"SaveProviderCredential(provider)\"\n [disabled]=\"IsSavingCredential\">\n <option [ngValue]=\"null\">None (use environment variable)</option>\n @for (cred of AvailableCredentials; track cred.ID) {\n <option [ngValue]=\"cred.ID\">{{ cred.Name }}</option>\n }\n </select>\n @if (IsSavingCredential) {\n <i class=\"fa-solid fa-spinner fa-spin provider-credential-spinner\"></i>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Step 2: Embedding Model -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasEmbeddingModel\" [class.setup-step-pending]=\"!HasEmbeddingModel\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasEmbeddingModel) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">2</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Embedding Models</span>\n @if (HasEmbeddingModel) {\n <span class=\"setup-step-status\">{{ EmbeddingModels.length }} model(s) available</span>\n } @else {\n <span class=\"setup-step-status\">No embedding models found. Configure at least one in the AI app > Models tab.</span>\n }\n </div>\n </div>\n @if (HasEmbeddingModel) {\n <div class=\"setup-step-detail\">\n <div class=\"config-tag-list\">\n @for (model of EmbeddingModels; track model.ID) {\n <span class=\"config-tag\">{{ model.Name }}</span>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Step 3: Vector Indexes -->\n <div class=\"setup-step\" [class.setup-step-complete]=\"HasVectorIndex\" [class.setup-step-pending]=\"!HasVectorIndex\">\n <div class=\"setup-step-header\">\n <div class=\"setup-step-indicator\">\n @if (HasVectorIndex) {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <span class=\"setup-step-number\">3</span>\n }\n </div>\n <div class=\"setup-step-info\">\n <span class=\"setup-step-title\">Vector Indexes</span>\n @if (HasVectorIndex) {\n <span class=\"setup-step-status\">{{ VectorIndexes.length }} index(es) configured</span>\n } @else if (HasVectorDBProvider && HasEmbeddingModel) {\n <span class=\"setup-step-status\">No indexes yet \u2014 create one below.</span>\n } @else {\n <span class=\"setup-step-status\">Complete steps 1 and 2 first.</span>\n }\n </div>\n @if (HasVectorDBProvider && HasEmbeddingModel && !ShowCreateIndexForm) {\n <button class=\"setup-step-action\" (click)=\"OpenCreateIndexForm()\">\n <i class=\"fa-solid fa-plus\"></i> Create Index\n </button>\n }\n </div>\n\n <!-- Existing Indexes -->\n @if (HasVectorIndex) {\n <div class=\"setup-step-detail\">\n @for (idx of VectorIndexes; track idx.ID) {\n <div class=\"index-card\">\n <div class=\"index-icon\">\n <i class=\"fa-solid fa-cubes\"></i>\n </div>\n <div class=\"index-info\">\n <span class=\"index-name\">{{ idx.Name }}</span>\n <span class=\"index-meta\">\n <i class=\"fa-solid fa-database\"></i> {{ idx.VectorDatabase }}\n \u00B7 \n <i class=\"fa-solid fa-microchip\"></i> {{ idx.EmbeddingModel }}\n </span>\n </div>\n <div class=\"index-actions\">\n <span class=\"config-status-badge config-status-active\">\n <i class=\"fa-solid fa-circle-check\"></i> Active\n </span>\n <button class=\"index-delete-btn\" (click)=\"DeleteIndex(idx.ID)\" title=\"Delete index\">\n <i class=\"fa-solid fa-trash-can\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Create Index Form -->\n @if (ShowCreateIndexForm) {\n <div class=\"create-index-form\">\n <h4 class=\"create-index-title\">\n <i class=\"fa-solid fa-plus-circle\"></i> Create New Vector Index\n </h4>\n <div class=\"create-index-fields\">\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Index Name</label>\n <input type=\"text\"\n class=\"config-input\"\n [(ngModel)]=\"NewIndexName\"\n placeholder=\"e.g., mj-knowledge-index\" />\n </div>\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Vector Database</label>\n <select class=\"config-select\" [(ngModel)]=\"NewIndexVectorDBID\">\n @for (db of VectorDBProviders; track db.ID) {\n <option [value]=\"db.ID\">{{ db.Name }}</option>\n }\n </select>\n </div>\n <div class=\"create-index-field\">\n <label class=\"create-index-label\">Embedding Model</label>\n <select class=\"config-select\" [(ngModel)]=\"NewIndexEmbeddingModelID\">\n @for (model of EmbeddingModels; track model.ID) {\n <option [value]=\"model.ID\">{{ model.Name }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"create-index-actions\">\n <button class=\"create-index-submit\" (click)=\"CreateIndex()\" [disabled]=\"IsCreatingIndex\">\n @if (IsCreatingIndex) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Creating...\n } @else {\n <i class=\"fa-solid fa-plus\"></i> Create Index\n }\n </button>\n <button class=\"create-index-cancel\" (click)=\"CancelCreateIndex()\" [disabled]=\"IsCreatingIndex\">\n Cancel\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Full-Text Indexes -->\n @if (ActiveSection === 'fulltext') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Full-Text Search Entities</h2>\n <p class=\"config-section-desc\">Configure which entities are included in full-text search. Entities with text fields (Name, Description, etc.) are automatically discovered.</p>\n\n @if (IsLoadingFTSEntities) {\n <mj-loading text=\"Discovering searchable entities...\" size=\"medium\"></mj-loading>\n } @else if (FTSEntities.length === 0) {\n <div class=\"config-empty-state\">\n <i class=\"fa-solid fa-text-width config-empty-icon\"></i>\n <h3 class=\"config-empty-title\">No Searchable Entities Found</h3>\n <p class=\"config-empty-text\">No entities with text fields were discovered. Ensure your database schema includes entities with varchar/nvarchar columns.</p>\n </div>\n } @else {\n <div class=\"fts-entity-controls\">\n <div class=\"fts-summary\">\n <span class=\"fts-summary-count\">{{ EnabledFTSCount }} of {{ FTSEntities.length }} entities enabled for search</span>\n <input type=\"text\" class=\"config-input fts-filter-input\" placeholder=\"Filter entities...\" [(ngModel)]=\"FTSFilterText\" />\n </div>\n </div>\n\n <div class=\"fts-entity-list\">\n @for (entity of FilteredFTSEntities; track entity.EntityName) {\n <div class=\"fts-entity-card\" [class.fts-entity-enabled]=\"entity.Enabled\">\n <div class=\"fts-entity-toggle\">\n <input type=\"checkbox\" [(ngModel)]=\"entity.Enabled\" (change)=\"OnFTSEntityToggled(entity)\" class=\"config-checkbox\" />\n </div>\n <div class=\"fts-entity-info\">\n <span class=\"fts-entity-name\">{{ entity.EntityName }}</span>\n <div class=\"fts-entity-fields\">\n @for (field of entity.IndexedFields; track field) {\n <span class=\"fts-field-tag\">{{ field }}</span>\n }\n </div>\n </div>\n <div class=\"fts-entity-meta\">\n <span class=\"fts-entity-title-field\" title=\"Title field\">\n <i class=\"fa-solid fa-heading\"></i> {{ entity.TitleField }}\n </span>\n @if (entity.SnippetField !== entity.TitleField) {\n <span class=\"fts-entity-snippet-field\" title=\"Snippet field\">\n <i class=\"fa-solid fa-align-left\"></i> {{ entity.SnippetField }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Embedding Models -->\n @if (ActiveSection === 'embedding') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Embedding Models</h2>\n <p class=\"config-section-desc\">AI models used for generating vector embeddings from text.</p>\n\n @if (HasEmbeddingModel) {\n <div class=\"config-group\">\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Active Model</span>\n <span class=\"config-hint\">Currently selected embedding model</span>\n </div>\n <span class=\"config-value-display\">{{ EmbeddingModelName }}</span>\n </div>\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Available Models</span>\n <span class=\"config-hint\">{{ EmbeddingModels.length }} embedding model(s) configured</span>\n </div>\n <div class=\"config-tag-list\">\n @for (model of EmbeddingModels; track model.ID) {\n <span class=\"config-tag\">{{ model.Name }}</span>\n }\n </div>\n </div>\n </div>\n <p class=\"config-section-note\">\n <i class=\"fa-solid fa-info-circle\"></i>\n Manage embedding models in the AI Dashboard > Models tab.\n </p>\n } @else {\n <div class=\"config-empty-state\">\n <i class=\"fa-solid fa-microchip config-empty-icon\"></i>\n <h3 class=\"config-empty-title\">No Embedding Models Found</h3>\n <p class=\"config-empty-text\">\n Embedding models are required to convert text into vectors for semantic search and duplicate detection.\n Configure at least one embedding model (e.g., text-embedding-3-small) in the AI Dashboard > Models tab.\n </p>\n </div>\n }\n </div>\n }\n\n <!-- Thresholds -->\n @if (ActiveSection === 'thresholds') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\">Scoring Thresholds</h2>\n <p class=\"config-section-desc\">Set the scoring thresholds used by search, duplicate detection, and autotagging.</p>\n\n <div class=\"config-group\">\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Duplicate Absolute Match</span>\n <span class=\"config-hint\">Score above which duplicates are auto-confirmed ({{ FormatThreshold(ThresholdSettings.DuplicateAbsolute) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.DuplicateAbsolute\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.5\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Duplicate Potential Match</span>\n <span class=\"config-hint\">Score above which duplicates are flagged for review ({{ FormatThreshold(ThresholdSettings.DuplicatePotential) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.DuplicatePotential\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.3\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Search Relevance</span>\n <span class=\"config-hint\">Minimum score for search results ({{ FormatThreshold(ThresholdSettings.SearchRelevance) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.SearchRelevance\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0\" max=\"1\" step=\"0.01\" />\n </div>\n\n <div class=\"config-field-row\">\n <div class=\"config-field-info\">\n <span class=\"config-label\">Autotag Confidence</span>\n <span class=\"config-hint\">Minimum confidence for accepting auto-generated tags ({{ FormatThreshold(ThresholdSettings.AutotagConfidence) }})</span>\n </div>\n <input type=\"range\" [(ngModel)]=\"ThresholdSettings.AutotagConfidence\" (input)=\"OnSettingChanged()\" class=\"config-slider\" min=\"0.3\" max=\"1\" step=\"0.01\" />\n </div>\n </div>\n </div>\n }\n\n <!-- Search Scopes Section -->\n @if (ActiveSection === 'search-scopes') {\n <div class=\"config-section\">\n <h2 class=\"config-section-title\"><i class=\"fa-solid fa-compass-drafting\"></i> Search Scopes</h2>\n <p class=\"config-section-desc\">\n Define reusable search scopes that filter which providers, entities, external indexes, and storage accounts participate in a scoped search.\n Scopes can be assigned to AI agents for pre-execution RAG or agent-invoked search.\n </p>\n\n <div class=\"scope-manager\">\n <!-- Scope list -->\n <aside class=\"scope-manager-list\">\n <div class=\"scope-manager-list-header\">\n <span>Scopes ({{ SearchScopes.length }})</span>\n <button type=\"button\" class=\"scope-manager-new-btn\" (click)=\"CreateNewScope()\">\n <i class=\"fa-solid fa-plus\"></i> New\n </button>\n </div>\n @if (IsLoadingScopes) {\n <div class=\"scope-manager-loading\">Loading…</div>\n } @else if (SearchScopes.length === 0) {\n <div class=\"scope-manager-empty\">\n No scopes defined yet. Click <strong>New</strong> to create one.\n </div>\n } @else {\n @for (s of SearchScopes; track s.ID) {\n <button\n type=\"button\"\n class=\"scope-manager-list-item\"\n [class.active]=\"ActiveScopeID === s.ID\"\n (click)=\"SelectScope(s.ID)\">\n <i [class]=\"s.Icon || 'fa-solid fa-filter'\" aria-hidden=\"true\"></i>\n <span class=\"scope-manager-list-name\">{{ s.Name }}</span>\n @if (s.IsGlobal) {\n <span class=\"scope-manager-badge\">Global</span>\n } @else if (s.IsDefault) {\n <span class=\"scope-manager-badge\">Default</span>\n }\n </button>\n }\n }\n </aside>\n\n <!-- Scope detail -->\n <section class=\"scope-manager-detail\">\n @if (ActiveScope) {\n <!-- Tab strip -->\n <div class=\"scope-manager-tabs\">\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'definition'\" (click)=\"SelectScopeTab('definition')\">Definition</button>\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'providers'\" (click)=\"SelectScopeTab('providers')\">Providers</button>\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'indexes'\" (click)=\"SelectScopeTab('indexes')\">External Indexes</button>\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'entities'\" (click)=\"SelectScopeTab('entities')\">Entities</button>\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'storage'\" (click)=\"SelectScopeTab('storage')\">Storage</button>\n <button type=\"button\" class=\"scope-manager-tab\" [class.active]=\"ActiveScopeTab === 'permissions'\" (click)=\"SelectScopeTab('permissions')\">Permissions</button>\n <div class=\"scope-manager-tab-spacer\"></div>\n @if (ActiveScope.ID) {\n <button type=\"button\" class=\"scope-manager-open-btn\"\n (click)=\"OpenActiveScopeFullForm()\"\n title=\"Open the full custom form for this scope (Live Preview, Fusion Weights, Reranker, Test Queries, Execution Logs)\">\n <i class=\"fa-solid fa-up-right-from-square\"></i> Open Full Form\n </button>\n }\n <button type=\"button\" class=\"scope-manager-save-btn\" (click)=\"SaveActiveScope()\">\n <i class=\"fa-solid fa-save\"></i> Save\n </button>\n @if (!ActiveScope.IsGlobal) {\n <button type=\"button\" class=\"scope-manager-delete-btn\" (click)=\"DeleteActiveScope()\" title=\"Delete scope\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n }\n </div>\n\n <!-- Definition tab -->\n @if (ActiveScopeTab === 'definition') {\n <div class=\"scope-definition-grid\">\n <label>\n <span>Name</span>\n <input type=\"text\" class=\"mj-input\" [value]=\"ActiveScope.Name\" (change)=\"ActiveScope.Name = $any($event.target).value\">\n </label>\n <label>\n <span>Icon (Font Awesome class)</span>\n <input type=\"text\" class=\"mj-input\" [value]=\"ActiveScope.Icon || ''\" (change)=\"ActiveScope.Icon = $any($event.target).value\">\n </label>\n <label class=\"scope-definition-full\">\n <span>Description</span>\n <textarea rows=\"2\" class=\"mj-textarea\" [value]=\"ActiveScope.Description || ''\" (change)=\"ActiveScope.Description = $any($event.target).value\"></textarea>\n </label>\n <label>\n <span>Status</span>\n <select class=\"mj-input\" [value]=\"ActiveScope.Status\" (change)=\"ActiveScope.Status = $any($event.target).value\">\n <option value=\"Active\">Active</option>\n <option value=\"Inactive\">Inactive</option>\n </select>\n </label>\n <label>\n <span>Start at</span>\n <input type=\"datetime-local\" class=\"mj-input\" [value]=\"FormatScopeDate(ActiveScope.StartAt)\" (change)=\"SetScopeDate(ActiveScope, 'StartAt', $any($event.target).value)\">\n </label>\n <label>\n <span>End at</span>\n <input type=\"datetime-local\" class=\"mj-input\" [value]=\"FormatScopeDate(ActiveScope.EndAt)\" (change)=\"SetScopeDate(ActiveScope, 'EndAt', $any($event.target).value)\">\n </label>\n <label class=\"scope-definition-toggle\">\n <input type=\"checkbox\" [checked]=\"ActiveScope.IsDefault\" (change)=\"ActiveScope.IsDefault = $any($event.target).checked\" [disabled]=\"ActiveScope.IsGlobal\">\n <span>Default scope \u2014 picked when users/agents don't specify one</span>\n </label>\n <label class=\"scope-definition-toggle\">\n <input type=\"checkbox\" [checked]=\"ActiveScope.IsGlobal\" disabled>\n <span>Global \u2014 reserved for the built-in Global scope (read-only)</span>\n </label>\n <label class=\"scope-definition-full\">\n <span>Scope Config (JSON)</span>\n <textarea rows=\"4\" class=\"mj-textarea scope-code-block\" spellcheck=\"false\" [value]=\"ActiveScope.ScopeConfig || ''\" (change)=\"ActiveScope.ScopeConfig = $any($event.target).value\"></textarea>\n </label>\n <label class=\"scope-definition-full\">\n <span>Search Context Config (JSON)</span>\n <textarea rows=\"4\" class=\"mj-textarea scope-code-block\" spellcheck=\"false\" [value]=\"ActiveScope.SearchContextConfig || ''\" (change)=\"ActiveScope.SearchContextConfig = $any($event.target).value\"></textarea>\n </label>\n </div>\n }\n\n @if (ActiveScopeTab === 'providers') {\n <mj-search-scope-child-grid\n [ParentID]=\"ActiveScopeID\"\n ChildEntityName=\"MJ: Search Scope Providers\"\n ParentFieldName=\"SearchScopeID\"\n [Columns]=\"ScopeProviderColumns\"\n AddButtonLabel=\"+ Add provider\"\n EmptyMessage=\"No providers assigned. Without any providers, this scope searches nothing.\">\n </mj-search-scope-child-grid>\n }\n\n @if (ActiveScopeTab === 'indexes') {\n <mj-search-scope-child-grid\n [ParentID]=\"ActiveScopeID\"\n ChildEntityName=\"MJ: Search Scope External Indexes\"\n ParentFieldName=\"SearchScopeID\"\n [Columns]=\"ScopeExternalIndexColumns\"\n AddButtonLabel=\"+ Add external index\"\n EmptyMessage=\"No external indexes configured for this scope.\">\n </mj-search-scope-child-grid>\n }\n\n @if (ActiveScopeTab === 'entities') {\n <mj-search-scope-child-grid\n [ParentID]=\"ActiveScopeID\"\n ChildEntityName=\"MJ: Search Scope Entities\"\n ParentFieldName=\"SearchScopeID\"\n [Columns]=\"ScopeEntityColumns\"\n AddButtonLabel=\"+ Add entity\"\n EmptyMessage=\"No entities configured. Scoped entity search will return no rows.\">\n </mj-search-scope-child-grid>\n }\n\n @if (ActiveScopeTab === 'storage') {\n <mj-search-scope-child-grid\n [ParentID]=\"ActiveScopeID\"\n ChildEntityName=\"MJ: Search Scope Storage Accounts\"\n ParentFieldName=\"SearchScopeID\"\n [Columns]=\"ScopeStorageColumns\"\n AddButtonLabel=\"+ Add storage folder\"\n EmptyMessage=\"No storage accounts linked to this scope.\">\n </mj-search-scope-child-grid>\n }\n\n @if (ActiveScopeTab === 'permissions') {\n <div class=\"scope-permissions-help\" style=\"margin-bottom: 12px; padding: 8px 12px; background: var(--mj-bg-surface-card); border-left: 3px solid var(--mj-brand-primary); border-radius: 4px;\">\n <p style=\"margin: 0; font-size: 0.95em;\">\n <i class=\"fa-solid fa-shield-halved\" style=\"color: var(--mj-brand-primary);\"></i>\n <strong>Per-user / per-role grants on this scope.</strong>\n Each row binds <em>either</em> a User <em>or</em> a Role (not both). Levels:\n <code>None</code> = explicit deny that overrides role grants,\n <code>Read</code> = view scope metadata,\n <code>Search</code> = invoke ScopedSearchAction,\n <code>Manage</code> = full edit including authoring permissions.\n Combined with <code>AIAgent.SearchScopeAccess</code> via the resolver \u2014 see the spec for the full resolution order.\n </p>\n </div>\n <mj-search-scope-child-grid\n [ParentID]=\"ActiveScopeID\"\n ChildEntityName=\"MJ: Search Scope Permissions\"\n ParentFieldName=\"SearchScopeID\"\n [Columns]=\"ScopePermissionColumns\"\n AddButtonLabel=\"+ Grant access\"\n EmptyMessage=\"No explicit grants. Access falls back to agent SearchScopeAccess only.\">\n </mj-search-scope-child-grid>\n }\n } @else {\n <div class=\"scope-manager-empty-detail\">\n Select a scope on the left, or click <strong>New</strong> to create one.\n </div>\n }\n </section>\n </div>\n </div>\n }\n\n <!-- Search Analytics Section (P3.3) \u2014 driven by SearchExecutionLog -->\n @if (ActiveSection === 'search-analytics') {\n <div class=\"config-section-content\">\n <h2 class=\"config-section-title\"><i class=\"fa-solid fa-chart-line\"></i> Search Analytics</h2>\n <p class=\"config-section-desc\">Aggregated metrics over the last 5,000 search invocations from <code>MJ: Search Execution Logs</code>. Use this to spot slow scopes, high-failure queries, or runaway reranker spend.</p>\n\n @if (AnalyticsLoading) {\n <mj-loading text=\"Loading analytics\u2026\" size=\"medium\"></mj-loading>\n } @else if (!AnalyticsLoaded) {\n <button class=\"mj-input\" type=\"button\" (click)=\"LoadSearchAnalytics()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Load analytics\n </button>\n } @else {\n <div class=\"search-analytics-kpi-grid\">\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">Total runs</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsTotalRuns | number }}</span>\n </div>\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">Success rate</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsSuccessRate }}%</span>\n </div>\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">Hit rate</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsHitRate }}%</span>\n <span class=\"search-analytics-kpi-hint\">% of successful runs that returned \u22651 result</span>\n </div>\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">Avg latency</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsAvgLatencyMs }}ms</span>\n </div>\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">P95 latency</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsP95LatencyMs }}ms</span>\n </div>\n <div class=\"search-analytics-kpi\">\n <span class=\"search-analytics-kpi-label\">Reranker spend</span>\n <span class=\"search-analytics-kpi-value\">{{ AnalyticsTotalRerankerCostCents | number:'1.0-2' }}\u00A2</span>\n </div>\n </div>\n\n <h3 class=\"search-analytics-h3\">Top scopes by volume</h3>\n @if (AnalyticsTopScopes.length === 0) {\n <p class=\"search-analytics-empty\">No runs in the window.</p>\n } @else {\n <table class=\"search-analytics-table\">\n <thead>\n <tr><th>Scope</th><th>Runs</th><th>Avg latency</th></tr>\n </thead>\n <tbody>\n @for (s of AnalyticsTopScopes; track s.ScopeID) {\n <tr>\n <td>{{ s.Name }}</td>\n <td>{{ s.Count | number }}</td>\n <td>{{ s.AvgLatencyMs }}ms</td>\n </tr>\n }\n </tbody>\n </table>\n }\n\n <h3 class=\"search-analytics-h3\">Reranker spend by driver</h3>\n @if (AnalyticsRerankerSpend.length === 0) {\n <p class=\"search-analytics-empty\">No rerank invocations in the window.</p>\n } @else {\n <table class=\"search-analytics-table\">\n <thead>\n <tr><th>Reranker</th><th>Calls</th><th>Total cents</th></tr>\n </thead>\n <tbody>\n @for (r of AnalyticsRerankerSpend; track r.Reranker) {\n <tr>\n <td>{{ r.Reranker }}</td>\n <td>{{ r.Count | number }}</td>\n <td>{{ r.TotalCents | number:'1.0-2' }}\u00A2</td>\n </tr>\n }\n </tbody>\n </table>\n }\n\n <h3 class=\"search-analytics-h3\">Top failure reasons</h3>\n @if (AnalyticsTopFailures.length === 0) {\n <p class=\"search-analytics-empty\">No failures in the window.</p>\n } @else {\n <table class=\"search-analytics-table\">\n <thead>\n <tr><th>Reason</th><th>Count</th></tr>\n </thead>\n <tbody>\n @for (f of AnalyticsTopFailures; track f.Reason) {\n <tr>\n <td>{{ f.Reason }}</td>\n <td>{{ f.Count | number }}</td>\n </tr>\n }\n </tbody>\n </table>\n }\n\n <button class=\"mj-input search-analytics-refresh\" type=\"button\" (click)=\"LoadSearchAnalytics()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n }\n </div>\n }\n\n <!-- Permissions Audit Section (P2A.7) -->\n @if (ActiveSection === 'search-permissions') {\n <div class=\"config-section-content\">\n <h2 class=\"config-section-title\"><i class=\"fa-solid fa-shield-halved\"></i> Permissions</h2>\n <p class=\"config-section-desc\">\n Cross-scope view of every <code>SearchScopePermission</code> row. Filter by scope, by user/role, or by permission level to answer \"who has access to what\". Read-only \u2014 to add or edit a grant, open the SearchScope's full form and use its Permissions panel.\n </p>\n\n @if (PermissionsLoading) {\n <div class=\"search-permissions-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Loading permission rows...\n </div>\n }\n\n @if (PermissionsLoaded && !PermissionsLoading) {\n <div class=\"search-permissions-filters\">\n <input class=\"mj-input\" type=\"text\" placeholder=\"Filter by scope name...\" [(ngModel)]=\"PermissionsFilterScope\" />\n <input class=\"mj-input\" type=\"text\" placeholder=\"Filter by user/role (name or email)...\" [(ngModel)]=\"PermissionsFilterPrincipal\" />\n <select class=\"mj-input\" [(ngModel)]=\"PermissionsFilterLevel\">\n <option value=\"\">All levels</option>\n <option value=\"None\">None</option>\n <option value=\"Read\">Read</option>\n <option value=\"Search\">Search</option>\n <option value=\"Manage\">Manage</option>\n </select>\n <button class=\"mj-input\" type=\"button\" (click)=\"RefreshPermissionsAudit()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n </div>\n\n <div class=\"search-permissions-summary\">\n Showing {{ FilteredPermissionsRows.length }} of {{ PermissionsRows.length }} rows\n </div>\n\n @if (FilteredPermissionsRows.length === 0) {\n <div class=\"search-permissions-empty\">\n No permission rows match your filters.\n </div>\n } @else {\n <table class=\"search-permissions-table\">\n <thead>\n <tr>\n <th>Scope</th>\n <th>Principal</th>\n <th>Type</th>\n <th>Permission Level</th>\n </tr>\n </thead>\n <tbody>\n @for (row of FilteredPermissionsRows; track row.ID) {\n <tr>\n <td>{{ row.SearchScopeName }}</td>\n <td>\n @if (row.UserID) {\n {{ row.UserName }} <span class=\"search-permissions-secondary\">({{ row.UserEmail }})</span>\n } @else {\n {{ row.RoleName }}\n }\n </td>\n <td>\n @if (row.UserID) { <span class=\"search-permissions-tag\">User</span> }\n @if (row.RoleID) { <span class=\"search-permissions-tag\">Role</span> }\n </td>\n <td>\n <span class=\"search-permissions-level search-permissions-level-{{ row.PermissionLevel | lowercase }}\">{{ row.PermissionLevel }}</span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n }\n }\n </div>\n }\n\n <!-- Scheduling Section -->\n @if (ActiveSection === 'scheduling') {\n <div class=\"config-section-content\">\n <h2 class=\"config-section-title\"><i class=\"fa-solid fa-clock\"></i> Scheduling</h2>\n <p class=\"config-section-desc\">Manage automated pipeline schedules for content classification and vector sync. Create schedules here or manage all schedules in the dedicated Scheduling app.</p>\n <div style=\"margin-top: 16px;\">\n <app-scheduling-resource></app-scheduling-resource>\n </div>\n </div>\n }\n\n <!-- Save Bar -->\n @if (HasUnsavedChanges) {\n <div class=\"config-save-bar\">\n <span class=\"config-save-text\">You have unsaved changes</span>\n <button class=\"config-save-btn\" (click)=\"SaveConfiguration()\" [disabled]=\"IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Saving...\n } @else {\n <i class=\"fa-solid fa-save\"></i> Save Changes\n }\n </button>\n <button class=\"config-reset-btn\" (click)=\"ResetConfiguration()\" [disabled]=\"IsSaving\">\n Reset\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: ["/* Knowledge Configuration - Settings Page with Left Nav */\n\n.config-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 400px;\n}\n\n.config-layout {\n display: flex;\n height: 100%;\n min-height: 500px;\n}\n\n/* Left Navigation */\n.config-nav {\n width: 240px;\n border-right: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0;\n}\n\n.config-nav-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n font-weight: 600;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n margin-bottom: 0.5rem;\n}\n\n.config-nav-item {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.55rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n text-align: left;\n transition: all 0.15s ease;\n width: 100%;\n}\n\n.config-nav-item:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n.config-nav-item-active {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n/* Content Area */\n.config-content {\n flex: 1;\n padding: 1.5rem 2rem;\n overflow-y: auto;\n position: relative;\n}\n\n.config-section {\n max-width: 700px;\n}\n\n.config-section-title {\n font-size: 1.3rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 0.5rem;\n}\n\n.config-section-desc {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n}\n\n/* Config Groups */\n.config-group {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.config-toggle-row,\n.config-field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-radius: 8px;\n transition: background 0.1s ease;\n gap: 1rem;\n}\n\n.config-toggle-row:hover,\n.config-field-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.config-toggle-info,\n.config-field-info {\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n flex: 1;\n}\n\n.config-label {\n font-weight: 500;\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n\n.config-hint {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.config-checkbox {\n width: 1.1rem;\n height: 1.1rem;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.config-input {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n font-family: inherit;\n}\n\n.config-input-number {\n width: 80px;\n text-align: center;\n}\n\n.config-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.config-slider {\n width: 200px;\n accent-color: var(--mj-brand-primary);\n cursor: pointer;\n}\n\n.config-value-display {\n font-size: 0.85rem;\n color: var(--mj-text-secondary);\n padding: 0.3rem 0.6rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-family: monospace;\n}\n\n/* Placeholder */\n.config-placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 1.5rem;\n text-align: center;\n gap: 1rem;\n color: var(--mj-text-muted);\n}\n\n.config-placeholder-icon {\n font-size: 2.5rem;\n}\n\n/* ---- Setup Progress Bar ---- */\n\n.setup-progress {\n margin-bottom: 1.5rem;\n padding: 1rem 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n}\n\n.setup-progress-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.5rem;\n}\n\n.setup-progress-label {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.setup-progress-count {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.setup-progress-bar {\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n}\n\n.setup-progress-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.4s ease;\n}\n\n/* ---- Setup Steps ---- */\n\n.setup-step {\n padding: 1rem 1.25rem;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n margin-bottom: 0.75rem;\n transition: border-color 0.2s;\n}\n\n.setup-step-complete {\n border-color: var(--mj-status-success-border);\n background: color-mix(in srgb, var(--mj-status-success) 3%, var(--mj-bg-surface));\n}\n\n.setup-step-pending {\n border-color: var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n\n.setup-step-header {\n display: flex;\n align-items: flex-start;\n gap: 0.85rem;\n}\n\n.setup-step-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n margin-top: 1px;\n}\n\n.setup-step-complete .setup-step-indicator i {\n font-size: 1.25rem;\n color: var(--mj-status-success);\n}\n\n.setup-step-number {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n font-weight: 700;\n}\n\n.setup-step-info {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n flex: 1;\n}\n\n.setup-step-title {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.setup-step-status {\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n.setup-step-complete .setup-step-status {\n color: var(--mj-status-success-text);\n}\n\n/* ---- Config Group Title ---- */\n\n.config-group-title {\n margin: 0 0 0.75rem 0;\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n/* ---- Status Badge ---- */\n\n.config-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 0.75rem;\n font-weight: 600;\n}\n\n.config-status-active {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n/* ---- Step Detail Area ---- */\n\n.setup-step-detail {\n margin-top: 0.75rem;\n padding-left: 2.85rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.setup-step-action {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s;\n flex-shrink: 0;\n}\n\n.setup-step-action:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* ---- Provider Cards ---- */\n\n.provider-card {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n}\n\n.provider-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.provider-info {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.provider-name {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.provider-class {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n font-family: monospace;\n}\n\n/* ---- Provider Credential Row ---- */\n\n.provider-credential-row {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem 0.75rem;\n margin-top: -0.25rem;\n}\n\n.provider-credential-label {\n font-size: 0.8rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.provider-credential-label i {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.provider-credential-picker {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-width: 0;\n}\n\n.provider-credential-select {\n flex: 1;\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.8rem;\n}\n\n.provider-credential-select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.provider-credential-spinner {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n/* ---- Index Cards ---- */\n\n.index-card {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n transition: border-color 0.15s;\n}\n\n.index-card:hover {\n border-color: var(--mj-border-default);\n}\n\n.index-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.index-info {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n}\n\n.index-name {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.index-meta {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n}\n\n.index-meta i {\n font-size: 0.65rem;\n}\n\n.index-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n\n.index-delete-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.index-delete-btn:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error);\n}\n\n/* ---- Create Index Form ---- */\n\n.create-index-form {\n margin-top: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-brand-primary);\n border-radius: 10px;\n margin-left: 2.85rem;\n}\n\n.create-index-title {\n margin: 0 0 1rem 0;\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.create-index-title i {\n color: var(--mj-brand-primary);\n}\n\n.create-index-fields {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n.create-index-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.create-index-label {\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.config-select {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.85rem;\n}\n\n.config-select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.create-index-actions {\n display: flex;\n gap: 8px;\n margin-top: 1rem;\n}\n\n.create-index-submit {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n}\n\n.create-index-submit:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.create-index-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.create-index-cancel {\n padding: 8px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.create-index-cancel:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Empty State for unconfigured sections */\n.config-empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 3rem 2rem;\n text-align: center;\n gap: 0.75rem;\n border: 2px dashed var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.config-empty-icon {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n}\n\n.config-empty-title {\n margin: 0;\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.config-empty-text {\n margin: 0;\n font-size: 0.85rem;\n color: var(--mj-text-muted);\n max-width: 480px;\n line-height: 1.5;\n}\n\n/* Tag list for showing multiple models */\n.config-tag-list {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.config-tag {\n padding: 3px 10px;\n border-radius: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n/* Section note */\n.config-section-note {\n margin-top: 1rem;\n font-size: 0.8rem;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.config-section-note i {\n color: var(--mj-status-info);\n}\n\n/* Save Bar */\n.config-save-bar {\n position: sticky;\n bottom: 0;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.75rem 1rem;\n margin-top: 2rem;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.config-save-text {\n flex: 1;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n}\n\n.config-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n}\n\n.config-save-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.config-save-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.config-reset-btn {\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.config-reset-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile responsive */\n@media (max-width: 768px) {\n .config-layout {\n flex-direction: column;\n }\n\n .config-nav {\n width: 100%;\n flex-direction: row;\n overflow-x: auto;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 0;\n }\n\n .config-nav-header {\n display: none;\n }\n\n .config-nav-item {\n white-space: nowrap;\n padding: 0.6rem 1rem;\n }\n\n .config-nav-item-active {\n border-left: none;\n border-bottom: 3px solid var(--mj-brand-primary);\n }\n\n .config-content {\n padding: 1rem;\n }\n}\n\n/* Full-Text Search Entity Management */\n.fts-entity-controls {\n margin-bottom: 16px;\n}\n\n.fts-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.fts-summary-count {\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.fts-filter-input {\n max-width: 240px;\n}\n\n.fts-entity-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.fts-entity-card {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n transition: border-color 0.15s, background 0.15s;\n}\n\n.fts-entity-card:hover {\n border-color: var(--mj-border-strong);\n}\n\n.fts-entity-card.fts-entity-enabled {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n}\n\n.fts-entity-toggle {\n flex-shrink: 0;\n}\n\n.fts-entity-info {\n flex: 1;\n min-width: 0;\n}\n\n.fts-entity-name {\n display: block;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n}\n\n.fts-entity-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.fts-field-tag {\n display: inline-block;\n padding: 1px 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.fts-entity-meta {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 2px;\n flex-shrink: 0;\n}\n\n.fts-entity-title-field,\n.fts-entity-snippet-field {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n}\n\n.fts-entity-title-field i,\n.fts-entity-snippet-field i {\n margin-right: 4px;\n font-size: 10px;\n}\n\n/* ===== Search Scopes Manager ===== */\n\n.scope-manager {\n display: grid;\n grid-template-columns: 260px 1fr;\n gap: 16px;\n min-height: 420px;\n}\n\n.scope-manager-list {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.scope-manager-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 6px 8px;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.scope-manager-new-btn {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-link);\n padding: 3px 8px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.7rem;\n font-weight: 500;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.scope-manager-new-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-brand-primary);\n}\n\n.scope-manager-loading,\n.scope-manager-empty,\n.scope-manager-empty-detail {\n padding: 16px;\n color: var(--mj-text-muted);\n text-align: center;\n font-size: 0.85rem;\n}\n\n.scope-manager-empty-detail {\n padding: 40px 24px;\n background: var(--mj-bg-surface-card);\n border: 1px dashed var(--mj-border-default);\n border-radius: 8px;\n}\n\n.scope-manager-list-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: none;\n background: transparent;\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n cursor: pointer;\n border-radius: 6px;\n text-align: left;\n transition: background 120ms ease;\n}\n\n.scope-manager-list-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.scope-manager-list-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.scope-manager-list-name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.scope-manager-badge {\n font-size: 0.625rem;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.scope-manager-detail {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n}\n\n.scope-manager-tabs {\n display: flex;\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--mj-border-subtle);\n padding-bottom: 8px;\n flex-wrap: wrap;\n}\n\n.scope-manager-tab {\n background: transparent;\n border: none;\n padding: 6px 12px;\n color: var(--mj-text-secondary);\n font-size: 0.875rem;\n cursor: pointer;\n border-radius: 4px;\n transition: background 120ms ease, color 120ms ease;\n}\n\n.scope-manager-tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.scope-manager-tab.active {\n color: var(--mj-brand-primary);\n border-bottom: 2px solid var(--mj-brand-primary);\n border-radius: 0;\n font-weight: 500;\n}\n\n.scope-manager-tab-spacer {\n flex: 1;\n}\n\n.scope-manager-save-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.8125rem;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.scope-manager-save-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.scope-manager-delete-btn {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-status-error-text);\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.scope-manager-delete-btn:hover {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error-border);\n}\n\n.scope-manager-open-btn {\n background: transparent;\n border: 1px solid var(--mj-border-default);\n color: var(--mj-text-primary);\n padding: 6px 12px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.8125rem;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.scope-manager-open-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.scope-definition-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px 16px;\n}\n\n.scope-definition-grid label {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n}\n\n.scope-definition-grid label > span {\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.scope-definition-full {\n grid-column: 1 / -1;\n}\n\n.scope-definition-toggle {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n\n.scope-definition-toggle input[type=\"checkbox\"] {\n accent-color: var(--mj-brand-primary);\n}\n\n.scope-code-block {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n padding: 8px;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Search Analytics section (P3.3)\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.search-analytics-kpi-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n}\n\n.search-analytics-kpi {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 14px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n}\n\n.search-analytics-kpi-label {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.search-analytics-kpi-value {\n font-size: 1.6rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n font-variant-numeric: tabular-nums;\n}\n\n.search-analytics-kpi-hint {\n color: var(--mj-text-muted);\n font-size: 0.72rem;\n}\n\n.search-analytics-h3 {\n margin: 22px 0 8px;\n font-size: 1rem;\n color: var(--mj-text-primary);\n}\n\n.search-analytics-empty {\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n margin: 4px 0 14px;\n}\n\n.search-analytics-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.86rem;\n}\n\n.search-analytics-table thead th {\n text-align: left;\n border-bottom: 1px solid var(--mj-border-default);\n padding: 6px 10px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n.search-analytics-table tbody td {\n padding: 6px 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-variant-numeric: tabular-nums;\n}\n\n.search-analytics-table tbody tr:last-child td {\n border-bottom: none;\n}\n\n.search-analytics-refresh {\n margin-top: 16px;\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.4rem 0.9rem;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.82rem;\n}\n\n.search-analytics-refresh:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n/* \u2500\u2500\u2500 Permissions Audit (P2A.7) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.search-permissions-loading {\n padding: 1.2rem;\n color: var(--mj-text-secondary);\n font-style: italic;\n}\n\n.search-permissions-filters {\n display: grid;\n grid-template-columns: 1fr 1fr 180px 130px;\n gap: 0.5rem;\n margin-bottom: 0.8rem;\n}\n\n.search-permissions-filters .mj-input {\n padding: 0.4rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n font-size: 0.85rem;\n}\n\n.search-permissions-summary {\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n margin-bottom: 0.6rem;\n}\n\n.search-permissions-empty {\n padding: 1.2rem;\n color: var(--mj-text-muted);\n text-align: center;\n background: var(--mj-bg-surface-card);\n border-radius: 4px;\n}\n\n.search-permissions-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n}\n\n.search-permissions-table th,\n.search-permissions-table td {\n padding: 0.5rem 0.7rem;\n border-bottom: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.search-permissions-table th {\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n}\n\n.search-permissions-secondary {\n color: var(--mj-text-muted);\n font-size: 0.78rem;\n}\n\n.search-permissions-tag {\n display: inline-block;\n padding: 0.1rem 0.5rem;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n border-radius: 3px;\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.search-permissions-level {\n display: inline-block;\n padding: 0.15rem 0.55rem;\n border-radius: 3px;\n font-size: 0.78rem;\n font-weight: 500;\n}\n\n.search-permissions-level-none {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface));\n color: var(--mj-status-error-text);\n}\n\n.search-permissions-level-read {\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n color: var(--mj-status-info-text);\n}\n\n.search-permissions-level-search {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n}\n\n.search-permissions-level-manage {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n"] }]
|
|
1185
2388
|
}], null, null); })();
|
|
1186
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KnowledgeConfigResourceComponent, { className: "KnowledgeConfigResourceComponent", filePath: "src/KnowledgeHub/components/config/knowledge-config-resource.component.ts", lineNumber:
|
|
2389
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KnowledgeConfigResourceComponent, { className: "KnowledgeConfigResourceComponent", filePath: "src/KnowledgeHub/components/config/knowledge-config-resource.component.ts", lineNumber: 99 }); })();
|
|
1187
2390
|
/** Tree-shaking prevention */
|
|
1188
2391
|
export function LoadKnowledgeConfigResource() {
|
|
1189
2392
|
// Prevents tree-shaking
|