@memberjunction/ng-explorer-core 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +5 -0
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +12 -2
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/guards/moved-nav-item-redirect.d.ts +15 -0
- package/dist/lib/guards/moved-nav-item-redirect.d.ts.map +1 -0
- package/dist/lib/guards/moved-nav-item-redirect.js +37 -0
- package/dist/lib/guards/moved-nav-item-redirect.js.map +1 -0
- package/dist/lib/omnibar/omnibar-dashboard-matches.d.ts +34 -0
- package/dist/lib/omnibar/omnibar-dashboard-matches.d.ts.map +1 -0
- package/dist/lib/omnibar/omnibar-dashboard-matches.js +70 -0
- package/dist/lib/omnibar/omnibar-dashboard-matches.js.map +1 -0
- package/dist/lib/omnibar/omnibar-palette.component.d.ts +27 -2
- package/dist/lib/omnibar/omnibar-palette.component.d.ts.map +1 -1
- package/dist/lib/omnibar/omnibar-palette.component.js +126 -65
- package/dist/lib/omnibar/omnibar-palette.component.js.map +1 -1
- package/dist/lib/omnibar/omnibar-provider.d.ts +4 -0
- package/dist/lib/omnibar/omnibar-provider.d.ts.map +1 -1
- package/dist/lib/omnibar/omnibar-provider.js.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-record.provider.d.ts +2 -2
- package/dist/lib/omnibar/providers/omnibar-record.provider.d.ts.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-record.provider.js +3 -7
- package/dist/lib/omnibar/providers/omnibar-record.provider.js.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-search.provider.d.ts +15 -2
- package/dist/lib/omnibar/providers/omnibar-search.provider.d.ts.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-search.provider.js +59 -7
- package/dist/lib/omnibar/providers/omnibar-search.provider.js.map +1 -1
- package/dist/lib/profile/forking-setting.d.ts +8 -0
- package/dist/lib/profile/forking-setting.d.ts.map +1 -0
- package/dist/lib/profile/forking-setting.js +18 -0
- package/dist/lib/profile/forking-setting.js.map +1 -0
- package/dist/lib/profile/profile-dialog.component.d.ts +8 -0
- package/dist/lib/profile/profile-dialog.component.d.ts.map +1 -1
- package/dist/lib/profile/profile-dialog.component.js +182 -92
- package/dist/lib/profile/profile-dialog.component.js.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +23 -4
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.js +36 -8
- package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +7 -18
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +20 -41
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-search-routing.d.ts +10 -3
- package/dist/lib/resource-wrappers/chat-search-routing.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-search-routing.js +11 -4
- package/dist/lib/resource-wrappers/chat-search-routing.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.d.ts +29 -0
- package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.js +141 -0
- package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-part-config.d.ts +28 -0
- package/dist/lib/resource-wrappers/dashboard-part-config.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-part-config.js +150 -0
- package/dist/lib/resource-wrappers/dashboard-part-config.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +361 -56
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +1312 -361
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-source-search.d.ts +86 -0
- package/dist/lib/resource-wrappers/dashboard-source-search.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-source-search.js +136 -0
- package/dist/lib/resource-wrappers/dashboard-source-search.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-studio-pane.d.ts +30 -0
- package/dist/lib/resource-wrappers/dashboard-studio-pane.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-studio-pane.js +55 -0
- package/dist/lib/resource-wrappers/dashboard-studio-pane.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-studio-tools.d.ts +135 -0
- package/dist/lib/resource-wrappers/dashboard-studio-tools.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-studio-tools.js +856 -0
- package/dist/lib/resource-wrappers/dashboard-studio-tools.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-tab-agent.d.ts +87 -0
- package/dist/lib/resource-wrappers/dashboard-tab-agent.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-tab-agent.js +125 -0
- package/dist/lib/resource-wrappers/dashboard-tab-agent.js.map +1 -0
- package/dist/lib/resource-wrappers/human-handoff-console-resource.component.d.ts +32 -0
- package/dist/lib/resource-wrappers/human-handoff-console-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/human-handoff-console-resource.component.js +114 -0
- package/dist/lib/resource-wrappers/human-handoff-console-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts +40 -6
- package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/livekit-room-resource.component.js +287 -147
- package/dist/lib/resource-wrappers/livekit-room-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/meetings-resource.component.d.ts +42 -0
- package/dist/lib/resource-wrappers/meetings-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/meetings-resource.component.js +225 -0
- package/dist/lib/resource-wrappers/meetings-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/record-resource.component.d.ts +27 -2
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +58 -6
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +5 -4
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +38 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +77 -4
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/dashboard-tab-for-url.d.ts +35 -0
- package/dist/lib/shell/dashboard-tab-for-url.d.ts.map +1 -0
- package/dist/lib/shell/dashboard-tab-for-url.js +39 -0
- package/dist/lib/shell/dashboard-tab-for-url.js.map +1 -0
- package/dist/lib/shell/shell.component.d.ts +31 -2
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +119 -42
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +2 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +6 -2
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +29 -3
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +44 -4
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/template-user/template-user-banner.component.d.ts +20 -0
- package/dist/lib/template-user/template-user-banner.component.d.ts.map +1 -0
- package/dist/lib/template-user/template-user-banner.component.js +67 -0
- package/dist/lib/template-user/template-user-banner.component.js.map +1 -0
- package/dist/lib/user-notifications/user-notifications.component.d.ts +13 -0
- package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +67 -0
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/module.d.ts +61 -56
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +32 -4
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +2 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +2 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +58 -52
|
@@ -4,18 +4,33 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { Component, ViewChild } from '@angular/core';
|
|
8
|
-
import { BaseResourceComponent, BaseDashboard } from '@memberjunction/ng-shared';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { Component, ViewChild, inject } from '@angular/core';
|
|
8
|
+
import { BaseResourceComponent, BaseDashboard, RecentAccessService, HomeAppPinService, SafeDetectChanges, CaptureElementJpeg, BuildDashboardPinInput, DASHBOARD_PIN_RESOURCE_TYPE } from '@memberjunction/ng-shared';
|
|
9
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
10
|
+
import { RealtimeSessionService, ArtifactPermissionService } from '@memberjunction/ng-conversations';
|
|
11
|
+
import { DashboardEngine, UserViewEngine, QueryEngine, ArtifactMetadataEngine, MJEnvironmentEntityExtended, UserInfoEngine } from '@memberjunction/core-entities';
|
|
12
|
+
import { RegisterClass, MJGlobal, SafeJSONParse, UUIDsEqual, EscapeSQLString, NormalizeUUID } from '@memberjunction/global';
|
|
13
|
+
import { CompositeKey, RunView, LogError } from '@memberjunction/core';
|
|
14
|
+
import { DefaultAgentResolver } from '@memberjunction/conversations-runtime';
|
|
15
|
+
import { filter, takeUntil } from 'rxjs';
|
|
16
|
+
import { ExtractPanelsFromLayout } from '@memberjunction/ng-dashboard-viewer';
|
|
17
|
+
import { BuildDashboardTabAgentContext, BuildDashboardTabAgentTools, SummarizeDashboardPanels } from './dashboard-tab-agent';
|
|
18
|
+
import { BuildDashboardStudioTools } from './dashboard-studio-tools';
|
|
19
|
+
import { SearchDashboardSources, SOURCE_KINDS } from './dashboard-source-search';
|
|
20
|
+
import { ParseStudioPanePrefs, PrefsFromSplitSizes, STUDIO_PANE_COPILOT_MAX_PCT, STUDIO_PANE_COPILOT_MIN_PCT, STUDIO_PANE_DEFAULTS, STUDIO_PANE_SETTING_KEY } from './dashboard-studio-pane';
|
|
13
21
|
import * as i0 from "@angular/core";
|
|
14
|
-
import * as i1 from "@
|
|
22
|
+
import * as i1 from "@memberjunction/ng-conversations";
|
|
15
23
|
import * as i2 from "@memberjunction/ng-dashboards/core-dashboards.module";
|
|
16
|
-
|
|
24
|
+
import * as i3 from "@memberjunction/ng-dashboard-viewer";
|
|
25
|
+
import * as i4 from "angular-split";
|
|
26
|
+
import * as i5 from "@memberjunction/ng-shared-generic";
|
|
27
|
+
import * as i6 from "@memberjunction/ng-ui-components";
|
|
28
|
+
import * as i7 from "./dashboard-add-to-menu.component";
|
|
29
|
+
const _c0 = ["editor"];
|
|
30
|
+
const _c1 = ["container"];
|
|
31
|
+
const _c2 = a0 => [a0];
|
|
17
32
|
function DashboardResource_Conditional_1_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
18
|
-
i0.ɵɵelementStart(0, "details",
|
|
33
|
+
i0.ɵɵelementStart(0, "details", 13)(1, "summary");
|
|
19
34
|
i0.ɵɵtext(2, "Technical Details");
|
|
20
35
|
i0.ɵɵelementEnd();
|
|
21
36
|
i0.ɵɵelementStart(3, "pre");
|
|
@@ -27,16 +42,16 @@ function DashboardResource_Conditional_1_Conditional_7_Template(rf, ctx) { if (r
|
|
|
27
42
|
i0.ɵɵtextInterpolate(ctx_r0.errorDetails);
|
|
28
43
|
} }
|
|
29
44
|
function DashboardResource_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
30
|
-
i0.ɵɵelementStart(0, "div",
|
|
31
|
-
i0.ɵɵelement(2, "i",
|
|
45
|
+
i0.ɵɵelementStart(0, "div", 3)(1, "div", 9);
|
|
46
|
+
i0.ɵɵelement(2, "i", 10);
|
|
32
47
|
i0.ɵɵelementEnd();
|
|
33
|
-
i0.ɵɵelementStart(3, "h2",
|
|
48
|
+
i0.ɵɵelementStart(3, "h2", 11);
|
|
34
49
|
i0.ɵɵtext(4, "Unable to Load Dashboard");
|
|
35
50
|
i0.ɵɵelementEnd();
|
|
36
|
-
i0.ɵɵelementStart(5, "p",
|
|
51
|
+
i0.ɵɵelementStart(5, "p", 12);
|
|
37
52
|
i0.ɵɵtext(6);
|
|
38
53
|
i0.ɵɵelementEnd();
|
|
39
|
-
i0.ɵɵconditionalCreate(7, DashboardResource_Conditional_1_Conditional_7_Template, 5, 1, "details",
|
|
54
|
+
i0.ɵɵconditionalCreate(7, DashboardResource_Conditional_1_Conditional_7_Template, 5, 1, "details", 13);
|
|
40
55
|
i0.ɵɵelementEnd();
|
|
41
56
|
} if (rf & 2) {
|
|
42
57
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
@@ -45,105 +60,194 @@ function DashboardResource_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
|
45
60
|
i0.ɵɵadvance();
|
|
46
61
|
i0.ɵɵconditional(ctx_r0.errorDetails ? 7 : -1);
|
|
47
62
|
} }
|
|
48
|
-
function
|
|
49
|
-
i0.ɵɵ
|
|
50
|
-
i0.ɵɵ
|
|
63
|
+
function DashboardResource_Conditional_2_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
64
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
65
|
+
i0.ɵɵelementStart(0, "button", 23);
|
|
66
|
+
i0.ɵɵlistener("click", function DashboardResource_Conditional_2_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OpenShareDialog()); });
|
|
67
|
+
i0.ɵɵelement(1, "i", 24);
|
|
51
68
|
i0.ɵɵelementEnd();
|
|
52
69
|
} }
|
|
53
|
-
function
|
|
54
|
-
const
|
|
55
|
-
i0.ɵɵelementStart(0, "
|
|
56
|
-
i0.ɵɵlistener("
|
|
57
|
-
i0.ɵɵelement(1, "i", 19);
|
|
70
|
+
function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
71
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
72
|
+
i0.ɵɵelementStart(0, "mj-conversation-chat-area", 38);
|
|
73
|
+
i0.ɵɵlistener("ConversationCreated", function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template_mj_conversation_chat_area_ConversationCreated_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.OnChatConversationCreated($event)); })("RealtimeConversationReady", function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template_mj_conversation_chat_area_RealtimeConversationReady_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.OnChatRealtimeConversationReady($event)); })("PendingMessageConsumed", function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template_mj_conversation_chat_area_PendingMessageConsumed_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.OnChatPendingMessageConsumed()); })("navigationRequest", function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template_mj_conversation_chat_area_navigationRequest_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.OnChatNavigationRequest($event)); })("OpenEntityRecord", function DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template_mj_conversation_chat_area_OpenEntityRecord_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.OnChatOpenEntityRecord($event)); });
|
|
58
74
|
i0.ɵɵelementEnd();
|
|
75
|
+
} if (rf & 2) {
|
|
76
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
77
|
+
i0.ɵɵproperty("Provider", ctx_r0.Provider)("EnvironmentId", ctx_r0.ChatEnvironmentId)("CurrentUser", ctx_r0.ProviderToUse.CurrentUser)("Conversation", ctx_r0.ChatConversation)("ConversationId", ctx_r0.ChatConversationId)("IsNewConversation", ctx_r0.ChatIsNewConversation)("PendingMessage", ctx_r0.ChatPendingMessage)("PendingAttachments", ctx_r0.ChatPendingAttachments)("SuppressNewConversationEmptyState", true)("AllowMentions", false)("OverlayMode", false)("ShowExportButton", false)("ShowShareButton", false)("ShowArtifactIndicator", false)("ShowAgentPicker", false)("ShowAgentModePicker", false)("DefaultAgentId", ctx_r0.ChatAgentId)("AllowedAgentIDs", i0.ɵɵpureFunction1(20, _c2, ctx_r0.ChatAgentId))("ApplicationId", ctx_r0.ChatApplicationId)("AppContext", ctx_r0.ChatAppContext);
|
|
59
78
|
} }
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
i0.ɵɵ
|
|
63
|
-
i0.ɵɵlistener("click", function DashboardResource_Conditional_2_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.toggleEditMode()); });
|
|
64
|
-
i0.ɵɵelement(1, "i", 22);
|
|
79
|
+
function DashboardResource_Conditional_2_Conditional_9_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
80
|
+
i0.ɵɵelementStart(0, "div", 36);
|
|
81
|
+
i0.ɵɵtext(1);
|
|
65
82
|
i0.ɵɵelementEnd();
|
|
83
|
+
} if (rf & 2) {
|
|
84
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
85
|
+
i0.ɵɵadvance();
|
|
86
|
+
i0.ɵɵtextInterpolate(ctx_r0.ChatAgentError);
|
|
66
87
|
} }
|
|
67
|
-
function
|
|
68
|
-
i0.ɵɵelementStart(0, "div",
|
|
69
|
-
i0.ɵɵelement(
|
|
70
|
-
i0.ɵɵtext(4);
|
|
88
|
+
function DashboardResource_Conditional_2_Conditional_9_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
89
|
+
i0.ɵɵelementStart(0, "div", 37);
|
|
90
|
+
i0.ɵɵelement(1, "mj-loading", 39);
|
|
71
91
|
i0.ɵɵelementEnd();
|
|
72
|
-
|
|
92
|
+
} }
|
|
93
|
+
function DashboardResource_Conditional_2_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
94
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
95
|
+
i0.ɵɵelementStart(0, "as-split-area", 22)(1, "aside", 25)(2, "div", 26)(3, "div", 27);
|
|
96
|
+
i0.ɵɵelement(4, "i", 28);
|
|
97
|
+
i0.ɵɵtext(5);
|
|
73
98
|
i0.ɵɵelementEnd();
|
|
74
|
-
i0.ɵɵelementStart(6, "div",
|
|
75
|
-
i0.ɵɵ
|
|
76
|
-
i0.ɵɵ
|
|
77
|
-
i0.ɵɵelementEnd()
|
|
99
|
+
i0.ɵɵelementStart(6, "div", 29)(7, "button", 30);
|
|
100
|
+
i0.ɵɵlistener("click", function DashboardResource_Conditional_2_Conditional_9_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.ToggleCopilotExpanded()); });
|
|
101
|
+
i0.ɵɵelement(8, "i", 31);
|
|
102
|
+
i0.ɵɵelementEnd();
|
|
103
|
+
i0.ɵɵelementStart(9, "button", 32);
|
|
104
|
+
i0.ɵɵlistener("click", function DashboardResource_Conditional_2_Conditional_9_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.CloseChat()); });
|
|
105
|
+
i0.ɵɵelement(10, "i", 33);
|
|
106
|
+
i0.ɵɵelementEnd()()();
|
|
107
|
+
i0.ɵɵelementStart(11, "div", 34);
|
|
108
|
+
i0.ɵɵconditionalCreate(12, DashboardResource_Conditional_2_Conditional_9_Conditional_12_Template, 1, 22, "mj-conversation-chat-area", 35)(13, DashboardResource_Conditional_2_Conditional_9_Conditional_13_Template, 2, 1, "div", 36)(14, DashboardResource_Conditional_2_Conditional_9_Conditional_14_Template, 2, 0, "div", 37);
|
|
109
|
+
i0.ɵɵelementEnd()()();
|
|
78
110
|
} if (rf & 2) {
|
|
79
|
-
const ctx_r0 = i0.ɵɵnextContext();
|
|
80
|
-
i0.ɵɵ
|
|
81
|
-
i0.ɵɵtextInterpolate1(" ", ctx_r0.configDashboard.Name, " ");
|
|
111
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
112
|
+
i0.ɵɵproperty("size", ctx_r0.CopilotExpanded ? ctx_r0.CopilotMaxPct : ctx_r0.CopilotSizePct)("minSize", ctx_r0.CopilotMinPct)("maxSize", ctx_r0.CopilotMaxPct);
|
|
82
113
|
i0.ɵɵadvance();
|
|
83
|
-
i0.ɵɵ
|
|
114
|
+
i0.ɵɵclassProp("expanded", ctx_r0.CopilotExpanded);
|
|
115
|
+
i0.ɵɵadvance(4);
|
|
116
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.ChatAgentName ?? "AI assistant");
|
|
84
117
|
i0.ɵɵadvance(2);
|
|
85
|
-
i0.ɵɵ
|
|
118
|
+
i0.ɵɵproperty("title", ctx_r0.CopilotExpanded ? "Collapse panel" : "Expand panel");
|
|
119
|
+
i0.ɵɵattribute("aria-label", ctx_r0.CopilotExpanded ? "Collapse panel" : "Expand panel");
|
|
86
120
|
i0.ɵɵadvance();
|
|
87
|
-
i0.ɵɵ
|
|
121
|
+
i0.ɵɵclassProp("fa-up-right-and-down-left-from-center", !ctx_r0.CopilotExpanded)("fa-down-left-and-up-right-to-center", ctx_r0.CopilotExpanded);
|
|
122
|
+
i0.ɵɵadvance(4);
|
|
123
|
+
i0.ɵɵconditional(ctx_r0.ChatAgentId && ctx_r0.ProviderToUse.CurrentUser ? 12 : ctx_r0.ChatAgentError ? 13 : 14);
|
|
88
124
|
} }
|
|
89
|
-
function
|
|
90
|
-
const
|
|
91
|
-
i0.ɵɵelementStart(0, "
|
|
92
|
-
i0.ɵɵlistener("
|
|
93
|
-
i0.ɵɵ
|
|
94
|
-
i0.ɵɵ
|
|
125
|
+
function DashboardResource_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
126
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
127
|
+
i0.ɵɵelementStart(0, "as-split", 14);
|
|
128
|
+
i0.ɵɵlistener("dragEnd", function DashboardResource_Conditional_2_Template_as_split_dragEnd_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnCopilotSplitDragEnd($event.sizes)); });
|
|
129
|
+
i0.ɵɵelementStart(1, "as-split-area", 15)(2, "mj-dashboard-editor", 16, 1);
|
|
130
|
+
i0.ɵɵlistener("Loaded", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_Loaded_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorLoaded($event)); })("LoadFailed", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_LoadFailed_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorLoadFailed($event)); })("EditingChange", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_EditingChange_2_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorEditingChange()); })("NameChanged", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_NameChanged_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorNameChanged($event)); })("ConfigChanged", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_ConfigChanged_2_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorConfigChanged()); })("ReloadedFromSaved", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_ReloadedFromSaved_2_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorReloaded()); })("FavoriteChange", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_FavoriteChange_2_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorFavoriteChange()); })("NavigationRequested", function DashboardResource_Conditional_2_Template_mj_dashboard_editor_NavigationRequested_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEditorNavigationRequested($event)); });
|
|
131
|
+
i0.ɵɵelementStart(4, "button", 17);
|
|
132
|
+
i0.ɵɵlistener("click", function DashboardResource_Conditional_2_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.ToggleChat()); });
|
|
133
|
+
i0.ɵɵelement(5, "i", 18);
|
|
95
134
|
i0.ɵɵelementEnd();
|
|
96
|
-
i0.ɵɵ
|
|
97
|
-
i0.ɵɵ
|
|
98
|
-
i0.ɵɵtwoWayListener("ngModelChange", function DashboardResource_Conditional_3_Template_input_ngModelChange_7_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r0.editingName, $event) || (ctx_r0.editingName = $event); return i0.ɵɵresetView($event); });
|
|
135
|
+
i0.ɵɵelementStart(6, "span", 19)(7, "mj-dashboard-add-to-menu", 20);
|
|
136
|
+
i0.ɵɵlistener("PinToHome", function DashboardResource_Conditional_2_Template_mj_dashboard_add_to_menu_PinToHome_7_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.PinToHome()); })("Share", function DashboardResource_Conditional_2_Template_mj_dashboard_add_to_menu_Share_7_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OpenShareDialog()); });
|
|
99
137
|
i0.ɵɵelementEnd();
|
|
100
|
-
i0.ɵɵ
|
|
101
|
-
i0.ɵɵtwoWayListener("ngModelChange", function DashboardResource_Conditional_3_Template_input_ngModelChange_8_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r0.editingDescription, $event) || (ctx_r0.editingDescription = $event); return i0.ɵɵresetView($event); });
|
|
138
|
+
i0.ɵɵconditionalCreate(8, DashboardResource_Conditional_2_Conditional_8_Template, 2, 0, "button", 21);
|
|
102
139
|
i0.ɵɵelementEnd()()();
|
|
103
|
-
i0.ɵɵ
|
|
104
|
-
i0.ɵɵlistener("click", function DashboardResource_Conditional_3_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.saveDashboard()); });
|
|
105
|
-
i0.ɵɵelement(11, "i", 32);
|
|
106
|
-
i0.ɵɵtext(12, " Save ");
|
|
140
|
+
i0.ɵɵconditionalCreate(9, DashboardResource_Conditional_2_Conditional_9_Template, 15, 13, "as-split-area", 22);
|
|
107
141
|
i0.ɵɵelementEnd();
|
|
108
|
-
i0.ɵɵelementStart(13, "button", 33);
|
|
109
|
-
i0.ɵɵlistener("click", function DashboardResource_Conditional_3_Template_button_click_13_listener() { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.cancelEdit()); });
|
|
110
|
-
i0.ɵɵtext(14, " Cancel ");
|
|
111
|
-
i0.ɵɵelementEnd()()();
|
|
112
142
|
} if (rf & 2) {
|
|
113
143
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
114
|
-
i0.ɵɵ
|
|
115
|
-
i0.ɵɵ
|
|
144
|
+
i0.ɵɵproperty("gutterSize", ctx_r0.ChatOpen ? 6 : 0);
|
|
145
|
+
i0.ɵɵadvance();
|
|
146
|
+
i0.ɵɵproperty("size", ctx_r0.ChatOpen ? ctx_r0.CopilotExpanded ? 100 - ctx_r0.CopilotMaxPct : ctx_r0.MainSizePct : 100)("minSize", 100 - ctx_r0.CopilotMaxPct);
|
|
147
|
+
i0.ɵɵadvance();
|
|
148
|
+
i0.ɵɵproperty("Provider", ctx_r0.Provider)("DashboardId", ctx_r0.ConfigDashboardId);
|
|
149
|
+
i0.ɵɵadvance(2);
|
|
150
|
+
i0.ɵɵclassProp("is-active", ctx_r0.ChatOpen);
|
|
151
|
+
i0.ɵɵattribute("aria-expanded", ctx_r0.ChatOpen ? "true" : "false");
|
|
152
|
+
i0.ɵɵadvance(3);
|
|
153
|
+
i0.ɵɵproperty("IsPinned", ctx_r0.IsPinnedToHome)("CanShare", ctx_r0.CanShare);
|
|
154
|
+
i0.ɵɵadvance();
|
|
155
|
+
i0.ɵɵconditional(ctx_r0.CanShare ? 8 : -1);
|
|
116
156
|
i0.ɵɵadvance();
|
|
117
|
-
i0.ɵɵ
|
|
157
|
+
i0.ɵɵconditional(ctx_r0.ChatOpen ? 9 : -1);
|
|
158
|
+
} }
|
|
159
|
+
function DashboardResource_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
160
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
161
|
+
i0.ɵɵelementStart(0, "mj-dashboard-share-dialog", 40);
|
|
162
|
+
i0.ɵɵlistener("Result", function DashboardResource_Conditional_5_Template_mj_dashboard_share_dialog_Result_0_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnShareDialogResult($event)); });
|
|
163
|
+
i0.ɵɵelementEnd();
|
|
164
|
+
} if (rf & 2) {
|
|
165
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
166
|
+
i0.ɵɵproperty("Visible", ctx_r0.ShowShareDialog)("Dashboard", ctx_r0.ConfigDashboard);
|
|
118
167
|
} }
|
|
119
168
|
function DashboardResource_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
120
|
-
const
|
|
121
|
-
i0.ɵɵelementStart(0, "mj-
|
|
122
|
-
i0.ɵɵlistener("
|
|
169
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
170
|
+
i0.ɵɵelementStart(0, "mj-confirm-dialog", 41);
|
|
171
|
+
i0.ɵɵlistener("Confirmed", function DashboardResource_Conditional_6_Template_mj_confirm_dialog_Confirmed_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnAgentConfirm(true)); })("Cancelled", function DashboardResource_Conditional_6_Template_mj_confirm_dialog_Cancelled_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnAgentConfirm(false)); });
|
|
123
172
|
i0.ɵɵelementEnd();
|
|
124
173
|
} if (rf & 2) {
|
|
125
174
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
126
|
-
i0.ɵɵproperty("Visible", ctx_r0.
|
|
175
|
+
i0.ɵɵproperty("Visible", true)("Title", ctx_r0.AgentConfirm.Title)("Message", ctx_r0.AgentConfirm.Message)("ConfirmText", ctx_r0.AgentConfirm.ConfirmText);
|
|
127
176
|
} }
|
|
177
|
+
function DashboardResource_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
178
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
179
|
+
i0.ɵɵelementStart(0, "mj-confirm-dialog", 42);
|
|
180
|
+
i0.ɵɵlistener("Confirmed", function DashboardResource_Conditional_7_Template_mj_confirm_dialog_Confirmed_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnCloseChatConfirmed()); })("Cancelled", function DashboardResource_Conditional_7_Template_mj_confirm_dialog_Cancelled_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnCloseChatCancelled()); });
|
|
181
|
+
i0.ɵɵelementEnd();
|
|
182
|
+
} if (rf & 2) {
|
|
183
|
+
i0.ɵɵproperty("Visible", true);
|
|
184
|
+
} }
|
|
185
|
+
/** The permissions the tab reports while its editor shows no dashboard: nothing is allowed. */
|
|
186
|
+
const NO_DASHBOARD_PERMISSIONS = {
|
|
187
|
+
DashboardID: '', CanRead: false, CanEdit: false, CanDelete: false, CanShare: false, IsOwner: false, PermissionSource: 'none',
|
|
188
|
+
};
|
|
189
|
+
/** Why an agent's request or edit fails while the tab shows no Config dashboard. */
|
|
190
|
+
const NO_CONFIG_DASHBOARD = 'No Config dashboard is open in this tab.';
|
|
191
|
+
/** How long, in milliseconds, an agent's confirm dialog waits for the user's answer before it closes. */
|
|
192
|
+
const AGENT_CONFIRM_TIMEOUT_MS = 25_000;
|
|
193
|
+
/** Why an agent's request failed when the user gave no answer in time. */
|
|
194
|
+
const AGENT_CONFIRM_TIMED_OUT = `No answer within ${AGENT_CONFIRM_TIMEOUT_MS / 1000} s; the dialog was closed. Ask the user and try again.`;
|
|
195
|
+
/** The most artifacts the agent's source search reads. */
|
|
196
|
+
const SOURCE_SEARCH_MAX_ARTIFACTS = 500;
|
|
197
|
+
/** The most artifact IDs one filter of the latest-version lookup lists. */
|
|
198
|
+
const ARTIFACT_VERSION_FILTER_CHUNK = 200;
|
|
199
|
+
/** Why the tab refuses an agent's edit while the editor's Save runs. */
|
|
200
|
+
const SAVE_IN_PROGRESS = 'A save is in progress. Try again when it finishes.';
|
|
201
|
+
/**
|
|
202
|
+
* The Dashboards application (metadata/applications/.dashboards-application.json). The AI pane's
|
|
203
|
+
* conversations belong to it, and its default agent answers in the pane.
|
|
204
|
+
*/
|
|
205
|
+
const DASHBOARDS_APPLICATION_ID = '4B439111-B492-4936-9E33-A428CB5725B4';
|
|
206
|
+
/** What the AI pane shows when the Dashboards application has no default agent of its own. */
|
|
207
|
+
const NO_DASHBOARD_ASSISTANT = 'No dashboard assistant is configured for the Dashboards app. Ask an administrator to set its default agent.';
|
|
128
208
|
/**
|
|
129
209
|
* Dashboard Resource Wrapper - displays a single dashboard in a tab
|
|
130
210
|
* Extends BaseResourceComponent to work with the resource type system
|
|
131
|
-
* Dynamically routes between code-based and config-based dashboards based on dashboard type
|
|
211
|
+
* Dynamically routes between code-based and config-based dashboards based on dashboard type:
|
|
212
|
+
* a Config dashboard shows in the shared dashboard editor (`mj-dashboard-editor`) with the AI pane
|
|
213
|
+
* beside it, and a Code dashboard or the Data Explorer shows in the tab's own container.
|
|
132
214
|
*/
|
|
133
215
|
let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
134
216
|
viewContainer;
|
|
135
217
|
cdr;
|
|
136
218
|
componentRef = null;
|
|
219
|
+
recentAccess = inject(RecentAccessService);
|
|
220
|
+
homePins = inject(HomeAppPinService);
|
|
221
|
+
realtimeSession = inject(RealtimeSessionService);
|
|
222
|
+
artifactPermissions = inject(ArtifactPermissionService);
|
|
223
|
+
/** The editor that shows the Config dashboard. Undefined while the tab shows none. */
|
|
224
|
+
editor;
|
|
225
|
+
/** The panels last reported to the agent, as JSON. Null until the open dashboard is first reported. */
|
|
226
|
+
reportedPanelsKey = null;
|
|
137
227
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
228
|
+
* True once the tab registered the agent tools for the dashboard the editor loaded. Until then the
|
|
229
|
+
* tab reports no context, so the first report comes with the tools.
|
|
230
|
+
*/
|
|
231
|
+
agentStatePublished = false;
|
|
232
|
+
/** True once the tab follows edit-mode requests (see watchEditModeRequests). */
|
|
233
|
+
watchingEditModeRequests = false;
|
|
234
|
+
/**
|
|
235
|
+
* A cache reattach moves this wrapper to another tab without recreating the dashboard inside it.
|
|
236
|
+
* A child resource component (a Code dashboard, the Data Explorer) has its own tab stamp, taken
|
|
237
|
+
* when we created it, and it has to move too. Without this the dashboard keeps reading and
|
|
238
|
+
* writing the params of the tab it was born in, from inside a tab it no longer belongs to. The
|
|
239
|
+
* editor has no tab of its own. The new tab can hold an edit-mode request (a card's Edit
|
|
240
|
+
* action reopens a closed tab this way), so the tab takes it here, where no load will.
|
|
142
241
|
*/
|
|
143
242
|
onTabIdRebound(tabId) {
|
|
144
|
-
this.
|
|
243
|
+
const child = this.componentRef?.instance;
|
|
244
|
+
if (child instanceof BaseResourceComponent) {
|
|
245
|
+
this.rehomeChildToTab(child, tabId);
|
|
246
|
+
}
|
|
247
|
+
this.takeEditModeRequest(tabId);
|
|
145
248
|
}
|
|
146
249
|
dataLoaded = false;
|
|
250
|
+
/** The element that holds a Code dashboard or the Data Explorer. */
|
|
147
251
|
ContainerElement;
|
|
148
252
|
/** @deprecated Use {@link ContainerElement}. */
|
|
149
253
|
get containerElement() {
|
|
@@ -165,67 +269,114 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
165
269
|
set errorDetails(value) {
|
|
166
270
|
this.ErrorDetails = value;
|
|
167
271
|
}
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
272
|
+
/** The Config dashboard the editor shows. Null for a Code dashboard, the Data Explorer, or none. */
|
|
273
|
+
ConfigDashboardId = null;
|
|
274
|
+
/** The Config dashboard the editor shows, or null (a Code dashboard, the Data Explorer, or none). */
|
|
275
|
+
get ConfigDashboard() {
|
|
276
|
+
return this.editor?.Dashboard ?? null;
|
|
277
|
+
}
|
|
174
278
|
/** @deprecated Use {@link ConfigDashboard}. */
|
|
175
279
|
get configDashboard() {
|
|
176
280
|
return this.ConfigDashboard;
|
|
177
281
|
}
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
this.
|
|
282
|
+
/** True while the editor is in edit mode. */
|
|
283
|
+
get IsEditMode() {
|
|
284
|
+
return this.editor?.IsEditing ?? false;
|
|
181
285
|
}
|
|
182
|
-
/** Whether we're in edit mode */
|
|
183
|
-
IsEditMode = false;
|
|
184
286
|
/** @deprecated Use {@link IsEditMode}. */
|
|
185
287
|
get isEditMode() {
|
|
186
288
|
return this.IsEditMode;
|
|
187
289
|
}
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
this.
|
|
191
|
-
}
|
|
192
|
-
/** Editing fields */
|
|
193
|
-
EditingName = '';
|
|
194
|
-
/** @deprecated Use {@link EditingName}. */
|
|
195
|
-
get editingName() {
|
|
196
|
-
return this.EditingName;
|
|
197
|
-
}
|
|
198
|
-
/** @deprecated Use {@link EditingName}. */
|
|
199
|
-
set editingName(value) {
|
|
200
|
-
this.EditingName = value;
|
|
201
|
-
}
|
|
202
|
-
EditingDescription = '';
|
|
203
|
-
/** @deprecated Use {@link EditingDescription}. */
|
|
204
|
-
get editingDescription() {
|
|
205
|
-
return this.EditingDescription;
|
|
206
|
-
}
|
|
207
|
-
/** @deprecated Use {@link EditingDescription}. */
|
|
208
|
-
set editingDescription(value) {
|
|
209
|
-
this.EditingDescription = value;
|
|
210
|
-
}
|
|
211
|
-
/** Current user's permissions for this dashboard */
|
|
212
|
-
DashboardPermissions = {
|
|
213
|
-
DashboardID: '',
|
|
214
|
-
CanRead: true,
|
|
215
|
-
CanEdit: true,
|
|
216
|
-
CanDelete: true,
|
|
217
|
-
CanShare: true,
|
|
218
|
-
IsOwner: true,
|
|
219
|
-
PermissionSource: 'owner'
|
|
220
|
-
};
|
|
221
|
-
/** @deprecated Use {@link DashboardPermissions}. */
|
|
222
|
-
get dashboardPermissions() {
|
|
223
|
-
return this.DashboardPermissions;
|
|
224
|
-
}
|
|
225
|
-
/** @deprecated Use {@link DashboardPermissions}. */
|
|
226
|
-
set dashboardPermissions(value) {
|
|
227
|
-
this.DashboardPermissions = value;
|
|
290
|
+
/** True when the user may share the dashboard the editor shows. */
|
|
291
|
+
get CanShare() {
|
|
292
|
+
return this.editor?.Permissions.CanShare ?? false;
|
|
228
293
|
}
|
|
294
|
+
/** The confirm dialog an agent's Request* tool opened, or null. The user, not the agent, saves or pins. */
|
|
295
|
+
AgentConfirm = null;
|
|
296
|
+
/** The request behind the open agent confirm dialog. Null while none is open. */
|
|
297
|
+
agentConfirmRequest = null;
|
|
298
|
+
// ── AI pane ──────────────────────────────────────────────────────
|
|
299
|
+
/** True while the AI pane shows beside the dashboard. */
|
|
300
|
+
ChatOpen = false;
|
|
301
|
+
/** True while the AI pane takes its largest width (CopilotMaxPct). */
|
|
302
|
+
CopilotExpanded = false;
|
|
303
|
+
/** The dashboard's width while the AI pane is open, in percent of the tab's width. */
|
|
304
|
+
MainSizePct = STUDIO_PANE_DEFAULTS.MainSizePct;
|
|
305
|
+
/** The AI pane's width, in percent of the tab's width. */
|
|
306
|
+
CopilotSizePct = STUDIO_PANE_DEFAULTS.CopilotSizePct;
|
|
307
|
+
/** The smallest width of the AI pane, in percent of the tab's width. */
|
|
308
|
+
CopilotMinPct = STUDIO_PANE_COPILOT_MIN_PCT;
|
|
309
|
+
/** The largest width of the AI pane, in percent of the tab's width. */
|
|
310
|
+
CopilotMaxPct = STUDIO_PANE_COPILOT_MAX_PCT;
|
|
311
|
+
/** True while the dialog that asks before a voice session ends with the AI pane is open. */
|
|
312
|
+
ShowCloseChatConfirm = false;
|
|
313
|
+
/** The agent the AI pane talks to: the Dashboards application's default agent. Null until it resolves. */
|
|
314
|
+
ChatAgentId = null;
|
|
315
|
+
/** The name of that agent, which the AI pane shows in its header. */
|
|
316
|
+
ChatAgentName = null;
|
|
317
|
+
/** Why the AI pane has no agent, or null. The pane then shows this instead of the chat. */
|
|
318
|
+
ChatAgentError = null;
|
|
319
|
+
/** The AI pane's conversation, from the moment its first message created it. */
|
|
320
|
+
ChatConversation = null;
|
|
321
|
+
/** The ID of the AI pane's conversation. */
|
|
322
|
+
ChatConversationId = null;
|
|
323
|
+
/** True until the first message creates the AI pane's conversation. */
|
|
324
|
+
ChatIsNewConversation = true;
|
|
325
|
+
/** The first message, which the chat area sends once it has created the conversation. Null after it is sent. */
|
|
326
|
+
ChatPendingMessage = null;
|
|
327
|
+
/** The files attached to the first message, sent with it. */
|
|
328
|
+
ChatPendingAttachments = null;
|
|
329
|
+
/** The shell's app context snapshot, as the chat area's AppContext input takes it. The agent reads it. */
|
|
330
|
+
ChatAppContext = null;
|
|
331
|
+
/** The application the AI pane's conversations belong to, and whose default agent answers in it. */
|
|
332
|
+
ChatApplicationId = DASHBOARDS_APPLICATION_ID;
|
|
333
|
+
/** True while the AI pane resolves its agent. */
|
|
334
|
+
resolvingChatAgent = false;
|
|
335
|
+
/** The environment of the AI pane's conversations and of the artifacts the agent searches: the tab's, else the default. */
|
|
336
|
+
get ChatEnvironmentId() {
|
|
337
|
+
const environmentId = this.Data?.Configuration?.['environmentId'];
|
|
338
|
+
return typeof environmentId === 'string' && environmentId ? environmentId : MJEnvironmentEntityExtended.DefaultEnvironmentID;
|
|
339
|
+
}
|
|
340
|
+
// ── Members the editor owns ──────────────────────────────────────
|
|
341
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingName}. */
|
|
342
|
+
get EditingName() { return this.editor?.EditingName ?? ''; }
|
|
343
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingName}. */
|
|
344
|
+
set EditingName(value) { if (this.editor)
|
|
345
|
+
this.editor.EditingName = value; }
|
|
346
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingName}. */
|
|
347
|
+
get editingName() { return this.EditingName; }
|
|
348
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingName}. */
|
|
349
|
+
set editingName(value) { this.EditingName = value; }
|
|
350
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingDescription}. */
|
|
351
|
+
get EditingDescription() { return this.editor?.EditingDescription ?? ''; }
|
|
352
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingDescription}. */
|
|
353
|
+
set EditingDescription(value) { if (this.editor)
|
|
354
|
+
this.editor.EditingDescription = value; }
|
|
355
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingDescription}. */
|
|
356
|
+
get editingDescription() { return this.EditingDescription; }
|
|
357
|
+
/** @deprecated Use {@link DashboardEditorComponent.EditingDescription}. */
|
|
358
|
+
set editingDescription(value) { this.EditingDescription = value; }
|
|
359
|
+
/** @deprecated Use {@link DashboardEditorComponent.Permissions}. */
|
|
360
|
+
get DashboardPermissions() { return this.editor?.Permissions ?? NO_DASHBOARD_PERMISSIONS; }
|
|
361
|
+
/** @deprecated Use {@link DashboardEditorComponent.Permissions}. */
|
|
362
|
+
get dashboardPermissions() { return this.DashboardPermissions; }
|
|
363
|
+
/** @deprecated Use {@link DashboardEditorComponent.ToggleEditMode}. */
|
|
364
|
+
ToggleEditMode() { this.editor?.ToggleEditMode(); }
|
|
365
|
+
/** @deprecated Use {@link DashboardEditorComponent.ToggleEditMode}. */
|
|
366
|
+
toggleEditMode() { this.ToggleEditMode(); }
|
|
367
|
+
/** @deprecated Use {@link DashboardEditorComponent.CancelEdit}. */
|
|
368
|
+
CancelEdit() { this.editor?.CancelEdit(); }
|
|
369
|
+
/** @deprecated Use {@link DashboardEditorComponent.CancelEdit}. */
|
|
370
|
+
cancelEdit() { this.CancelEdit(); }
|
|
371
|
+
/** @deprecated Use {@link DashboardEditorComponent.SaveDashboard}. */
|
|
372
|
+
SaveDashboard() { return this.editor?.SaveDashboard() ?? Promise.resolve(); }
|
|
373
|
+
/** @deprecated Use {@link DashboardEditorComponent.SaveDashboard}. */
|
|
374
|
+
saveDashboard() { return this.SaveDashboard(); }
|
|
375
|
+
/** @deprecated Use {@link DashboardEditorComponent.OpenAddPartDialog}. */
|
|
376
|
+
OpenAddPartDialog() { this.editor?.OpenAddPartDialog(); }
|
|
377
|
+
/** @deprecated Use {@link DashboardEditorComponent.OpenAddPartDialog}. */
|
|
378
|
+
openAddPartDialog() { this.OpenAddPartDialog(); }
|
|
379
|
+
// ── Share and pin ────────────────────────────────────────────────
|
|
229
380
|
/** Whether the share dialog is visible */
|
|
230
381
|
ShowShareDialog = false;
|
|
231
382
|
/** @deprecated Use {@link ShowShareDialog}. */
|
|
@@ -236,6 +387,10 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
236
387
|
set showShareDialog(value) {
|
|
237
388
|
this.ShowShareDialog = value;
|
|
238
389
|
}
|
|
390
|
+
/** True when the open Config dashboard is pinned to Home. */
|
|
391
|
+
get IsPinnedToHome() {
|
|
392
|
+
return this.readPlacement(this.ConfigDashboard?.ID, id => this.homePins.IsPinned(DASHBOARD_PIN_RESOURCE_TYPE, { dashboardId: id }));
|
|
393
|
+
}
|
|
239
394
|
/**
|
|
240
395
|
* Sets the error state with a user-friendly message and optional technical details
|
|
241
396
|
*/
|
|
@@ -276,8 +431,9 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
276
431
|
this.componentRef = null;
|
|
277
432
|
}
|
|
278
433
|
this.clearError();
|
|
279
|
-
this.
|
|
280
|
-
this.
|
|
434
|
+
this.abortAgentConfirm('The tab opened another dashboard before the user answered.');
|
|
435
|
+
this.ConfigDashboardId = null;
|
|
436
|
+
this.agentStatePublished = false;
|
|
281
437
|
this.loadDashboard();
|
|
282
438
|
}
|
|
283
439
|
}
|
|
@@ -285,97 +441,123 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
285
441
|
get Data() {
|
|
286
442
|
return super.Data;
|
|
287
443
|
}
|
|
444
|
+
ngOnInit() {
|
|
445
|
+
super.ngOnInit();
|
|
446
|
+
this.watchAppContext();
|
|
447
|
+
}
|
|
288
448
|
ngOnDestroy() {
|
|
449
|
+
this.abortAgentConfirm('The dashboard tab was closed before the user answered.');
|
|
289
450
|
super.ngOnDestroy();
|
|
290
451
|
if (this.componentRef) {
|
|
291
452
|
this.componentRef.destroy();
|
|
292
453
|
}
|
|
293
454
|
}
|
|
294
455
|
// ========================================
|
|
295
|
-
//
|
|
456
|
+
// The Editor
|
|
296
457
|
// ========================================
|
|
297
458
|
/**
|
|
298
|
-
*
|
|
459
|
+
* Shows a Config dashboard in the editor, which reads it from DashboardEngine. The editor's Loaded
|
|
460
|
+
* or LoadFailed event finishes the load (OnEditorLoaded, OnEditorLoadFailed).
|
|
299
461
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
/** @deprecated Use {@link ToggleEditMode}. */
|
|
309
|
-
toggleEditMode() {
|
|
310
|
-
return this.ToggleEditMode();
|
|
462
|
+
showConfigDashboard(dashboard) {
|
|
463
|
+
this.ContainerElement.nativeElement.innerHTML = '';
|
|
464
|
+
this.reportedPanelsKey = null;
|
|
465
|
+
this.ConfigDashboardId = dashboard.ID;
|
|
466
|
+
void this.loadHomePins();
|
|
467
|
+
this.watchEditModeRequests();
|
|
468
|
+
SafeDetectChanges(this.cdr);
|
|
311
469
|
}
|
|
312
470
|
/**
|
|
313
|
-
*
|
|
471
|
+
* The editor shows the dashboard and its layout is ready. The loading screen goes, the tab takes an
|
|
472
|
+
* edit-mode request (a new dashboard, or a card's Edit action) when it has one, and the agent gets
|
|
473
|
+
* its tools and the dashboard. NavigationService removes the request when the tab takes it, so a
|
|
474
|
+
* later load of the tab opens for viewing.
|
|
314
475
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
this.EditingName = this.ConfigDashboard.Name;
|
|
320
|
-
this.EditingDescription = this.ConfigDashboard.Description || '';
|
|
321
|
-
// Tell the viewer to enter edit mode
|
|
322
|
-
if (this.viewerInstance) {
|
|
323
|
-
this.viewerInstance.isEditing = true;
|
|
476
|
+
OnEditorLoaded(dashboard) {
|
|
477
|
+
this.NotifyLoadComplete();
|
|
478
|
+
if (this.navigationService.TakeDashboardEditModeRequest(this.getTabId(), dashboard.ID, this.tabApplicationId())) {
|
|
479
|
+
this.editor?.EnterEditMode();
|
|
324
480
|
}
|
|
325
|
-
this.cdr
|
|
481
|
+
SafeDetectChanges(this.cdr);
|
|
482
|
+
this.publishAgentState();
|
|
483
|
+
}
|
|
484
|
+
/** The editor could not show the dashboard: the tab shows why, and the loading screen goes. */
|
|
485
|
+
OnEditorLoadFailed(error) {
|
|
486
|
+
const name = this.cachedDashboardName(error.DashboardId);
|
|
487
|
+
const subject = name ? `The dashboard "${name}"` : 'The dashboard';
|
|
488
|
+
this.setError(`${subject} could not be loaded. There may be an issue with the dashboard configuration.`, error.Message);
|
|
489
|
+
this.NotifyLoadComplete();
|
|
490
|
+
SafeDetectChanges(this.cdr);
|
|
326
491
|
}
|
|
327
492
|
/**
|
|
328
|
-
*
|
|
493
|
+
* The dashboard's saved name changed, by a save here or elsewhere. The tab takes the name as its
|
|
494
|
+
* title and keeps its dashboard ID: ResourceRecordSaved would rewrite the record ID in a form the
|
|
495
|
+
* tab configuration does not have, and the tab would reload on each Save.
|
|
329
496
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (this.viewerInstance) {
|
|
334
|
-
this.viewerInstance.isEditing = false;
|
|
497
|
+
OnEditorNameChanged(name) {
|
|
498
|
+
if (name) {
|
|
499
|
+
this.NotifyDisplayNameChanged(name);
|
|
335
500
|
}
|
|
336
|
-
this.cdr.detectChanges();
|
|
337
501
|
}
|
|
338
|
-
/**
|
|
339
|
-
|
|
340
|
-
|
|
502
|
+
/** The editor entered or left edit mode: the tab reports it to the agent. */
|
|
503
|
+
OnEditorEditingChange() {
|
|
504
|
+
this.emitAgentContext();
|
|
505
|
+
}
|
|
506
|
+
/** The editor's parts or layout changed: the tab reports the panels to the agent when they changed. */
|
|
507
|
+
OnEditorConfigChanged() {
|
|
508
|
+
this.onViewerConfigChanged();
|
|
509
|
+
}
|
|
510
|
+
/** The editor rebuilt the saved layout after a save elsewhere: the rebuild reports nothing to the agent. */
|
|
511
|
+
OnEditorReloaded() {
|
|
512
|
+
this.treatPanelsAsReported();
|
|
513
|
+
}
|
|
514
|
+
/** The user starred or unstarred the dashboard in the editor. */
|
|
515
|
+
OnEditorFavoriteChange() {
|
|
516
|
+
this.onPlacementChanged();
|
|
517
|
+
}
|
|
518
|
+
/** A part of the dashboard asked to open something: a record, another dashboard, a query. */
|
|
519
|
+
OnEditorNavigationRequested(event) {
|
|
520
|
+
this.handleNavigationRequest(event);
|
|
341
521
|
}
|
|
342
522
|
/**
|
|
343
|
-
*
|
|
523
|
+
* Takes the tab's edit-mode request, if it has one for this tab's dashboard, and enters edit mode in
|
|
524
|
+
* the editor. A tab that has not finished loading leaves the request for its load (see
|
|
525
|
+
* OnEditorLoaded). A tab that is already editing keeps its changes. A request for what the tab shows
|
|
526
|
+
* now stays for that tab's component: a cached component can still be bound to a tab ID that OpenTab
|
|
527
|
+
* gave to another dashboard, or to this dashboard in another application.
|
|
344
528
|
*/
|
|
345
|
-
|
|
346
|
-
|
|
529
|
+
takeEditModeRequest(tabId) {
|
|
530
|
+
const editor = this.editor;
|
|
531
|
+
const dashboard = editor?.Dashboard;
|
|
532
|
+
if (!this.LoadComplete || !editor || !dashboard || !UUIDsEqual(dashboard.ID, this.ConfigDashboardId))
|
|
347
533
|
return;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.ConfigDashboard.Name = this.EditingName;
|
|
351
|
-
this.ConfigDashboard.Description = this.EditingDescription;
|
|
352
|
-
// Save via the viewer (which handles layout saving)
|
|
353
|
-
await this.viewerInstance.save();
|
|
354
|
-
// Exit edit mode
|
|
355
|
-
this.IsEditMode = false;
|
|
356
|
-
this.viewerInstance.isEditing = false;
|
|
357
|
-
this.cdr.detectChanges();
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
console.error('Error saving dashboard:', error);
|
|
534
|
+
if (this.navigationService.TakeDashboardEditModeRequest(tabId, dashboard.ID, this.tabApplicationId()) && !editor.IsEditing) {
|
|
535
|
+
editor.EnterEditMode();
|
|
361
536
|
}
|
|
362
537
|
}
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
538
|
+
/** The application of the tab this component was loaded for, from the resource data the tab container gives it. */
|
|
539
|
+
tabApplicationId() {
|
|
540
|
+
const applicationId = this.Data?.Configuration?.['applicationId'];
|
|
541
|
+
return typeof applicationId === 'string' ? applicationId : '';
|
|
366
542
|
}
|
|
367
543
|
/**
|
|
368
|
-
*
|
|
544
|
+
* Enters edit mode when OpenDashboard asks this open tab to (NavigationService.DashboardEditModeRequested$).
|
|
545
|
+
* Starts once per tab, and stops when the tab is destroyed.
|
|
369
546
|
*/
|
|
370
|
-
|
|
371
|
-
if (this.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
547
|
+
watchEditModeRequests() {
|
|
548
|
+
if (this.watchingEditModeRequests)
|
|
549
|
+
return;
|
|
550
|
+
this.watchingEditModeRequests = true;
|
|
551
|
+
this.navigationService.DashboardEditModeRequested$.pipe(filter(tabId => UUIDsEqual(tabId, this.getTabId())), takeUntil(this.destroy$)).subscribe(tabId => this.takeEditModeRequest(tabId));
|
|
375
552
|
}
|
|
376
|
-
/**
|
|
377
|
-
|
|
378
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Takes the viewer's current panels as the ones last reported to the agent, so the change events
|
|
555
|
+
* of a reloaded layout report nothing. Does nothing before the first report.
|
|
556
|
+
*/
|
|
557
|
+
treatPanelsAsReported() {
|
|
558
|
+
if (this.reportedPanelsKey !== null) {
|
|
559
|
+
this.reportedPanelsKey = JSON.stringify(this.livePanels());
|
|
560
|
+
}
|
|
379
561
|
}
|
|
380
562
|
/**
|
|
381
563
|
* Open the share dialog for this dashboard
|
|
@@ -400,16 +582,655 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
400
582
|
return this.CloseShareDialog();
|
|
401
583
|
}
|
|
402
584
|
/**
|
|
403
|
-
* Handle share dialog result
|
|
585
|
+
* Handle share dialog result. After a save the editor reads the user's permissions again, since
|
|
586
|
+
* sharing can change them.
|
|
404
587
|
*/
|
|
405
588
|
OnShareDialogResult(result) {
|
|
406
589
|
this.ShowShareDialog = false;
|
|
407
|
-
if (result.Action === 'save'
|
|
408
|
-
|
|
409
|
-
const md = this.ProviderToUse;
|
|
410
|
-
this.DashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(this.ConfigDashboard.ID, md.CurrentUser.ID);
|
|
590
|
+
if (result.Action === 'save') {
|
|
591
|
+
this.editor?.RefreshPermissions();
|
|
411
592
|
}
|
|
412
593
|
this.cdr.detectChanges();
|
|
594
|
+
if (result.Action === 'save') {
|
|
595
|
+
this.emitAgentContext();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// ========================================
|
|
599
|
+
// Add to menu
|
|
600
|
+
// ========================================
|
|
601
|
+
/** Pins the open dashboard to Home as a card, then adds a thumbnail of it in the background. */
|
|
602
|
+
async PinToHome() {
|
|
603
|
+
const dashboard = this.ConfigDashboard;
|
|
604
|
+
if (!dashboard)
|
|
605
|
+
return;
|
|
606
|
+
try {
|
|
607
|
+
await this.homePins.LoadPins();
|
|
608
|
+
if (this.homePins.AddPin(this.homePinFor(dashboard))) {
|
|
609
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Pinned "${dashboard.Name}" to Home`, 'success', 2000);
|
|
610
|
+
void this.attachPinThumbnail(dashboard.ID);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
MJNotificationService.Instance.CreateSimpleNotification(`"${dashboard.Name}" is already pinned to Home`, 'info', 2000);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
LogError(`Dashboard tab: could not pin the dashboard to Home: ${errorMessage(error)}`);
|
|
618
|
+
MJNotificationService.Instance.CreateSimpleNotification('Could not pin the dashboard to Home', 'error', 3000);
|
|
619
|
+
}
|
|
620
|
+
this.onPlacementChanged();
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Reads one placement flag (the pin) for a dashboard. False without a dashboard,
|
|
624
|
+
* or when the user cannot read that data (the engine throws PermissionConstrainedError).
|
|
625
|
+
*/
|
|
626
|
+
readPlacement(dashboardId, read) {
|
|
627
|
+
if (!dashboardId)
|
|
628
|
+
return false;
|
|
629
|
+
try {
|
|
630
|
+
return read(dashboardId);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/** The Home pin for a dashboard, in the shared dashboard pin shape. */
|
|
637
|
+
homePinFor(dashboard) {
|
|
638
|
+
return BuildDashboardPinInput(dashboard);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Adds a thumbnail of the dashboard (the editor's body) to its pin. Best effort: without one, Home
|
|
642
|
+
* shows the pin's icon. Logs a failure and never rejects, because the caller does not wait for it.
|
|
643
|
+
*/
|
|
644
|
+
async attachPinThumbnail(dashboardId) {
|
|
645
|
+
try {
|
|
646
|
+
const element = this.editor?.BodyElement;
|
|
647
|
+
const thumbnail = element ? await this.homePins.CaptureThumbnail(element) : undefined;
|
|
648
|
+
const pin = thumbnail ? this.homePins.FindPin(DASHBOARD_PIN_RESOURCE_TYPE, { dashboardId }) : undefined;
|
|
649
|
+
if (pin && thumbnail) {
|
|
650
|
+
this.homePins.UpdatePin(pin.Id, { Thumbnail: thumbnail });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
LogError(`Dashboard tab: could not add a thumbnail to the Home pin: ${errorMessage(error)}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/** Loads the user's Home pins, so the Add to menu can show whether this dashboard is pinned. */
|
|
658
|
+
async loadHomePins() {
|
|
659
|
+
try {
|
|
660
|
+
await this.homePins.LoadPins();
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
LogError(`Dashboard tab: could not load the Home pins: ${errorMessage(error)}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/** Updates the header buttons and reports the new placement to the agent. */
|
|
667
|
+
onPlacementChanged() {
|
|
668
|
+
SafeDetectChanges(this.cdr);
|
|
669
|
+
this.emitAgentContext();
|
|
670
|
+
}
|
|
671
|
+
// ========================================
|
|
672
|
+
// AI Pane
|
|
673
|
+
// ========================================
|
|
674
|
+
/** Opens the AI pane with the widths the user saved, or closes it as CloseChat does. */
|
|
675
|
+
ToggleChat() {
|
|
676
|
+
if (this.ChatOpen) {
|
|
677
|
+
this.CloseChat();
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.applyPanePrefs(this.savedPanePrefs());
|
|
681
|
+
this.ChatOpen = true;
|
|
682
|
+
void this.ensureChatAgentResolved();
|
|
683
|
+
SafeDetectChanges(this.cdr);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Closes the AI pane. Its conversation stays, so the pane shows it again when it opens. Closing the pane
|
|
687
|
+
* ends a voice session, so while one runs the user confirms first (OnCloseChatConfirmed).
|
|
688
|
+
*/
|
|
689
|
+
CloseChat() {
|
|
690
|
+
if (this.realtimeSession.IsActive) {
|
|
691
|
+
this.ShowCloseChatConfirm = true;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.closeChatPane();
|
|
695
|
+
}
|
|
696
|
+
SafeDetectChanges(this.cdr);
|
|
697
|
+
}
|
|
698
|
+
/** The user confirmed: the AI pane closes, and the voice session ends with it. */
|
|
699
|
+
OnCloseChatConfirmed() {
|
|
700
|
+
this.ShowCloseChatConfirm = false;
|
|
701
|
+
this.closeChatPane();
|
|
702
|
+
SafeDetectChanges(this.cdr);
|
|
703
|
+
}
|
|
704
|
+
/** The user keeps the AI pane open, and the voice session with it. */
|
|
705
|
+
OnCloseChatCancelled() {
|
|
706
|
+
this.ShowCloseChatConfirm = false;
|
|
707
|
+
SafeDetectChanges(this.cdr);
|
|
708
|
+
}
|
|
709
|
+
/** Gives the AI pane its largest width, or its own width again. */
|
|
710
|
+
ToggleCopilotExpanded() {
|
|
711
|
+
this.CopilotExpanded = !this.CopilotExpanded;
|
|
712
|
+
SafeDetectChanges(this.cdr);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Keeps the widths the user dragged the split to, in this tab and in the user's settings. The AI pane then
|
|
716
|
+
* has that width, not its largest one. Sizes that are not two numbers change nothing.
|
|
717
|
+
*/
|
|
718
|
+
OnCopilotSplitDragEnd(sizes) {
|
|
719
|
+
const prefs = PrefsFromSplitSizes(sizes);
|
|
720
|
+
if (!prefs)
|
|
721
|
+
return;
|
|
722
|
+
this.applyPanePrefs(prefs);
|
|
723
|
+
this.CopilotExpanded = false;
|
|
724
|
+
UserInfoEngine.Instance.SetSettingDebounced(STUDIO_PANE_SETTING_KEY, JSON.stringify(prefs));
|
|
725
|
+
SafeDetectChanges(this.cdr);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* The chat area created the AI pane's conversation for the first message. The pane keeps the conversation
|
|
729
|
+
* and gives the message back to the chat area, which then sends it in that conversation.
|
|
730
|
+
*/
|
|
731
|
+
OnChatConversationCreated(event) {
|
|
732
|
+
this.ChatConversation = event.conversation;
|
|
733
|
+
this.ChatConversationId = event.conversation.ID;
|
|
734
|
+
this.ChatIsNewConversation = false;
|
|
735
|
+
this.ChatPendingMessage = event.pendingMessage ?? null;
|
|
736
|
+
this.ChatPendingAttachments = event.pendingAttachments ?? null;
|
|
737
|
+
SafeDetectChanges(this.cdr);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* A voice call started before any message, so the server created the AI pane's conversation. When the
|
|
741
|
+
* call ends, the pane adopts that conversation, so the chat area shows the call's transcript and the
|
|
742
|
+
* next message goes to it. The start of the call reports the same conversation without `select`; the
|
|
743
|
+
* pane leaves the chat area alone until the call ends, as the Conversations app does.
|
|
744
|
+
*/
|
|
745
|
+
async OnChatRealtimeConversationReady(event) {
|
|
746
|
+
if (!event.select || !event.conversationId || this.ChatConversationId)
|
|
747
|
+
return;
|
|
748
|
+
const conversation = await this.loadChatConversation(event.conversationId);
|
|
749
|
+
if (!conversation || this.ChatConversationId)
|
|
750
|
+
return;
|
|
751
|
+
this.ChatConversation = conversation;
|
|
752
|
+
this.ChatConversationId = conversation.ID;
|
|
753
|
+
this.ChatIsNewConversation = false;
|
|
754
|
+
SafeDetectChanges(this.cdr);
|
|
755
|
+
}
|
|
756
|
+
/** The conversation with `conversationId` that the user can read, or null. */
|
|
757
|
+
async loadChatConversation(conversationId) {
|
|
758
|
+
const result = await RunView.FromMetadataProvider(this.ProviderToUse).RunView({
|
|
759
|
+
EntityName: 'MJ: Conversations',
|
|
760
|
+
ExtraFilter: `ID='${EscapeSQLString(conversationId)}'`,
|
|
761
|
+
ResultType: 'entity_object',
|
|
762
|
+
});
|
|
763
|
+
if (!result.Success) {
|
|
764
|
+
LogError(`DashboardResource: the AI pane could not load conversation ${conversationId}: ${result.ErrorMessage}`);
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
return result.Results?.[0] ?? null;
|
|
768
|
+
}
|
|
769
|
+
/** The chat area sent the first message. The pane drops it, so the chat area does not send it again. */
|
|
770
|
+
OnChatPendingMessageConsumed() {
|
|
771
|
+
this.ChatPendingMessage = null;
|
|
772
|
+
this.ChatPendingAttachments = null;
|
|
773
|
+
SafeDetectChanges(this.cdr);
|
|
774
|
+
}
|
|
775
|
+
/** Opens the app page that an artifact in the chat asks for. An app the metadata does not name opens the page in the current app. */
|
|
776
|
+
OnChatNavigationRequest(request) {
|
|
777
|
+
const appId = request.appName ? this.applicationIdByName(request.appName) : undefined;
|
|
778
|
+
void this.navigationService.OpenNavItemByName(request.navItemName, undefined, appId, { queryParams: request.queryParams })
|
|
779
|
+
.catch((error) => LogError(`Dashboard tab: could not open ${request.navItemName}: ${errorMessage(error)}`));
|
|
780
|
+
}
|
|
781
|
+
/** Opens a record that the chat links to. */
|
|
782
|
+
OnChatOpenEntityRecord(event) {
|
|
783
|
+
this.navigationService.OpenEntityRecord(event.entityName, event.compositeKey);
|
|
784
|
+
}
|
|
785
|
+
/** Keeps the shell's latest app context snapshot for the chat, until the tab is destroyed. Renders only while the pane is open. */
|
|
786
|
+
watchAppContext() {
|
|
787
|
+
this.navigationService.AppContextSnapshot$.pipe(takeUntil(this.destroy$)).subscribe(snapshot => {
|
|
788
|
+
this.ChatAppContext = snapshot ? { ...snapshot } : null;
|
|
789
|
+
if (this.ChatOpen) {
|
|
790
|
+
SafeDetectChanges(this.cdr);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/** The AI pane widths in the user's settings, or the defaults when the settings cannot be read. */
|
|
795
|
+
savedPanePrefs() {
|
|
796
|
+
try {
|
|
797
|
+
return ParseStudioPanePrefs(UserInfoEngine.Instance.GetSetting(STUDIO_PANE_SETTING_KEY));
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
return ParseStudioPanePrefs(null); // the user cannot read their settings
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
applyPanePrefs(prefs) {
|
|
804
|
+
this.MainSizePct = prefs.MainSizePct;
|
|
805
|
+
this.CopilotSizePct = prefs.CopilotSizePct;
|
|
806
|
+
}
|
|
807
|
+
closeChatPane() {
|
|
808
|
+
this.ChatOpen = false;
|
|
809
|
+
this.CopilotExpanded = false;
|
|
810
|
+
}
|
|
811
|
+
/** The ID of the application with this name, in any case, or undefined when the metadata has none. */
|
|
812
|
+
applicationIdByName(appName) {
|
|
813
|
+
const name = appName.trim().toLowerCase();
|
|
814
|
+
return this.ProviderToUse.Applications.find(app => app.Name.trim().toLowerCase() === name)?.ID;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Resolves the agent the AI pane talks to: the Dashboards application's default agent
|
|
818
|
+
* (Application.AgentSettings.DefaultAgentID). When the resolver falls back to Sage, the application has no
|
|
819
|
+
* agent of its own, and the pane says so instead of showing a chat. After a failure the next open tries again.
|
|
820
|
+
*/
|
|
821
|
+
async ensureChatAgentResolved() {
|
|
822
|
+
if (this.ChatAgentId || this.resolvingChatAgent)
|
|
823
|
+
return;
|
|
824
|
+
this.resolvingChatAgent = true;
|
|
825
|
+
this.ChatAgentError = null;
|
|
826
|
+
try {
|
|
827
|
+
const provider = this.ProviderToUse;
|
|
828
|
+
const agent = await new DefaultAgentResolver().Resolve({ applicationId: this.ChatApplicationId, contextUser: provider.CurrentUser, provider });
|
|
829
|
+
if (agent.Name === DefaultAgentResolver.FALLBACK_AGENT_NAME) {
|
|
830
|
+
this.ChatAgentError = NO_DASHBOARD_ASSISTANT;
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.ChatAgentId = agent.ID;
|
|
834
|
+
this.ChatAgentName = agent.Name;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
LogError(`Dashboard tab: could not resolve the dashboard assistant: ${errorMessage(error)}`);
|
|
839
|
+
this.ChatAgentError = `The dashboard assistant could not be loaded: ${errorMessage(error)}`;
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
this.resolvingChatAgent = false;
|
|
843
|
+
}
|
|
844
|
+
SafeDetectChanges(this.cdr);
|
|
845
|
+
}
|
|
846
|
+
// ========================================
|
|
847
|
+
// Agent Context & Client Tools
|
|
848
|
+
//
|
|
849
|
+
// 🔒 BOUNDARY: the tab's agent tools change the open dashboard in memory only. Save, Share,
|
|
850
|
+
// Favorite and Pin stay user actions; the Request* tools in dashboard-studio-tools.ts ask
|
|
851
|
+
// for them through a confirm dialog and never perform them.
|
|
852
|
+
// A Code dashboard is its own resource component and reports its own context and tools, so
|
|
853
|
+
// this tab reports nothing for it.
|
|
854
|
+
// ========================================
|
|
855
|
+
/**
|
|
856
|
+
* Registers the tab's tools and reports the open Config dashboard to the agent. The studio tools
|
|
857
|
+
* of one registration share an edit queue, so the tab registers them once for each dashboard load.
|
|
858
|
+
*/
|
|
859
|
+
publishAgentState() {
|
|
860
|
+
this.navigationService.SetAgentClientTools(this, [
|
|
861
|
+
...BuildDashboardTabAgentTools(this.agentHost()),
|
|
862
|
+
...BuildDashboardStudioTools(this.studioHost()),
|
|
863
|
+
]);
|
|
864
|
+
this.agentStatePublished = true;
|
|
865
|
+
this.emitAgentContext();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Reports the Config dashboard the editor shows to the agent. Does nothing while none is shown, and
|
|
869
|
+
* before the tab registered the tools for it: an edit-mode request taken on the load reports nothing
|
|
870
|
+
* of its own, so the load reports once.
|
|
871
|
+
*/
|
|
872
|
+
emitAgentContext() {
|
|
873
|
+
const editor = this.editor;
|
|
874
|
+
const dashboard = editor?.Dashboard;
|
|
875
|
+
if (!this.agentStatePublished || !editor || !dashboard)
|
|
876
|
+
return;
|
|
877
|
+
const panels = this.livePanels();
|
|
878
|
+
this.reportedPanelsKey = JSON.stringify(panels);
|
|
879
|
+
this.navigationService.SetAgentContext(this, BuildDashboardTabAgentContext({
|
|
880
|
+
Dashboard: dashboard,
|
|
881
|
+
IsEditing: editor.IsEditing,
|
|
882
|
+
CanEdit: editor.CanEdit,
|
|
883
|
+
Panels: panels,
|
|
884
|
+
IsFavorite: editor.IsFavorite,
|
|
885
|
+
IsPinnedToHome: this.IsPinnedToHome,
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
/** Reports the dashboard again when its panels change. A layout move or resize reports nothing. */
|
|
889
|
+
onViewerConfigChanged() {
|
|
890
|
+
if (this.reportedPanelsKey !== null && JSON.stringify(this.livePanels()) !== this.reportedPanelsKey) {
|
|
891
|
+
this.emitAgentContext();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/** The reads the agent tools make from this tab. */
|
|
895
|
+
agentHost() {
|
|
896
|
+
return {
|
|
897
|
+
CurrentDashboard: () => this.ConfigDashboard,
|
|
898
|
+
AccessibleDashboards: () => DashboardEngine.Instance.GetAccessibleDashboards(this.ProviderToUse.CurrentUser.ID),
|
|
899
|
+
Panels: dashboard => this.isOpenDashboard(dashboard) ? this.livePanels() : this.savedPanels(dashboard),
|
|
900
|
+
Permissions: dashboardId => DashboardEngine.Instance.GetDashboardPermissions(dashboardId, this.ProviderToUse.CurrentUser.ID),
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
isOpenDashboard(dashboard) {
|
|
904
|
+
return !!this.ConfigDashboard && UUIDsEqual(dashboard.ID, this.ConfigDashboard.ID);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* The open dashboard's panels, read from the editor's viewer and its live layout, so unsaved edits
|
|
908
|
+
* count. Empty while the viewer is not ready. Best effort: a failed read never stops the context report.
|
|
909
|
+
*/
|
|
910
|
+
livePanels() {
|
|
911
|
+
const viewer = this.editor?.Viewer;
|
|
912
|
+
if (!viewer)
|
|
913
|
+
return [];
|
|
914
|
+
try {
|
|
915
|
+
return SummarizeDashboardPanels(ExtractPanelsFromLayout(viewer.getConfig()?.layout ?? null), viewer.GetPartTypes());
|
|
916
|
+
}
|
|
917
|
+
catch {
|
|
918
|
+
return [];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
/** The panels saved in a dashboard's layout. Empty when the layout or the part types cannot be read. */
|
|
922
|
+
savedPanels(dashboard) {
|
|
923
|
+
try {
|
|
924
|
+
const config = SafeJSONParse(dashboard.UIConfigDetails);
|
|
925
|
+
return SummarizeDashboardPanels(ExtractPanelsFromLayout(config?.layout ?? null), DashboardEngine.Instance.DashboardPartTypes);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
return [];
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
// ========================================
|
|
932
|
+
// Studio Tools Host
|
|
933
|
+
// ========================================
|
|
934
|
+
/**
|
|
935
|
+
* The reads and edits the studio tools make on this tab, through its editor, and the requests they
|
|
936
|
+
* put to the user. DashboardStudioHost says what each member must do.
|
|
937
|
+
*/
|
|
938
|
+
studioHost() {
|
|
939
|
+
return {
|
|
940
|
+
Dashboard: () => {
|
|
941
|
+
const dashboard = this.ConfigDashboard;
|
|
942
|
+
return dashboard ? { ID: dashboard.ID, Name: dashboard.Name, Description: dashboard.Description ?? null } : null;
|
|
943
|
+
},
|
|
944
|
+
GetConfig: () => this.editor?.Viewer?.getConfig() ?? null,
|
|
945
|
+
GetPartTypes: () => this.editor?.PartTypes ?? [],
|
|
946
|
+
IsEditing: () => this.editor?.IsEditing ?? false,
|
|
947
|
+
CanEdit: () => this.editor?.CanEdit ?? false,
|
|
948
|
+
HasUnsavedChanges: () => this.editor?.Viewer?.HasUnsavedChanges ?? false,
|
|
949
|
+
IsSaving: () => this.editor?.IsSaving ?? false,
|
|
950
|
+
EnterEditMode: () => this.editor?.EnterEditMode() ?? false,
|
|
951
|
+
...this.studioEdits(),
|
|
952
|
+
GetPanelPath: panelId => this.editor?.Viewer?.GetPanelPath(panelId) ?? null,
|
|
953
|
+
CaptureScreenshot: maxWidth => this.editor
|
|
954
|
+
? CaptureElementJpeg(this.editor.BodyElement, { maxWidth })
|
|
955
|
+
: Promise.reject(new Error(NO_CONFIG_DASHBOARD)),
|
|
956
|
+
PanelBounds: () => this.panelBounds(),
|
|
957
|
+
IsVoiceSessionActive: () => this.realtimeSession.IsActive,
|
|
958
|
+
SendVoiceFrame: (base64, mimeType) => this.realtimeSession.SendVideoFrame(base64, mimeType),
|
|
959
|
+
Confirm: (kind, detail) => this.askAgentConfirm(kind, detail),
|
|
960
|
+
SearchSources: (query, kinds, limit) => this.searchSources(query, kinds, limit),
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* The studio host's edits. Each runs through runAgentEdit, so it rejects with the reason when it
|
|
965
|
+
* cannot be made, and an edit the viewer can skip without an error is checked afterwards.
|
|
966
|
+
*/
|
|
967
|
+
studioEdits() {
|
|
968
|
+
return {
|
|
969
|
+
AddPanel: (partTypeId, config, title, icon, position) => this.runAgentEdit('The assistant could not add the part', viewer => viewer.AddPanel(partTypeId, config, title, icon, position)),
|
|
970
|
+
RemovePanel: panelId => this.runAgentEdit('The assistant could not remove the part', viewer => {
|
|
971
|
+
viewer.RemovePanel(panelId);
|
|
972
|
+
if (viewer.GetPanel(panelId))
|
|
973
|
+
throw new Error('The dashboard did not remove the panel.');
|
|
974
|
+
}),
|
|
975
|
+
UpdatePanelConfig: (panelId, config, title, icon) => this.runAgentEdit('The assistant could not change the part', async (viewer) => {
|
|
976
|
+
await viewer.UpdatePanelConfig(panelId, config, title, icon);
|
|
977
|
+
if (JSON.stringify(viewer.GetPanel(panelId)?.config) !== JSON.stringify(config)) {
|
|
978
|
+
throw new Error('The dashboard did not change the panel.');
|
|
979
|
+
}
|
|
980
|
+
}),
|
|
981
|
+
ApplyLayout: layout => this.runAgentEdit('The assistant could not change the layout', async (viewer) => {
|
|
982
|
+
if (!viewer.getConfig())
|
|
983
|
+
throw new Error('The dashboard has not loaded its layout yet.');
|
|
984
|
+
await viewer.ApplyLayout(layout);
|
|
985
|
+
}),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Makes one agent edit on the editor's viewer, then reports the dashboard's parts to the agent again.
|
|
990
|
+
* The edit waits until the viewer's layout is ready: entering edit mode can reload the saved
|
|
991
|
+
* dashboard. Rejects with an Error that says why when no viewer is open, the editor's Save runs, or
|
|
992
|
+
* the edit fails; the user also sees a failed edit.
|
|
993
|
+
*/
|
|
994
|
+
async runAgentEdit(failureMessage, edit) {
|
|
995
|
+
const viewer = this.editor?.Viewer;
|
|
996
|
+
if (!viewer)
|
|
997
|
+
throw new Error(NO_CONFIG_DASHBOARD);
|
|
998
|
+
try {
|
|
999
|
+
await viewer.WaitForLayoutReady();
|
|
1000
|
+
if (this.editor?.IsSaving)
|
|
1001
|
+
throw new Error(SAVE_IN_PROGRESS);
|
|
1002
|
+
return await edit(viewer);
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
LogError(`Dashboard tab: ${failureMessage}: ${errorMessage(error)}`);
|
|
1006
|
+
MJNotificationService.Instance.CreateSimpleNotification(failureMessage, 'error', 3000);
|
|
1007
|
+
throw error instanceof Error ? error : new Error(errorMessage(error));
|
|
1008
|
+
}
|
|
1009
|
+
finally {
|
|
1010
|
+
this.onViewerConfigChanged();
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* The box of each panel the tab shows, in CSS pixels from the top left corner of the element that
|
|
1015
|
+
* CaptureScreenshot captures (the editor's body). A panel whose content is hidden, such as one in a
|
|
1016
|
+
* stack's inactive tab, is left out. html-to-image draws a scrolled container from its top, so each
|
|
1017
|
+
* box adds the container's scroll offset. None while the editor shows no dashboard.
|
|
1018
|
+
*/
|
|
1019
|
+
panelBounds() {
|
|
1020
|
+
const editor = this.editor;
|
|
1021
|
+
if (!editor)
|
|
1022
|
+
return [];
|
|
1023
|
+
const container = editor.BodyElement;
|
|
1024
|
+
const origin = container.getBoundingClientRect();
|
|
1025
|
+
const contents = new Map(Array.from(container.querySelectorAll('[data-panel-id]'), element => [element.dataset['panelId'], element]));
|
|
1026
|
+
return ExtractPanelsFromLayout(editor.Viewer?.getConfig()?.layout ?? null).flatMap(panel => {
|
|
1027
|
+
const content = contents.get(panel.id);
|
|
1028
|
+
if (!content || content.offsetParent === null)
|
|
1029
|
+
return [];
|
|
1030
|
+
const box = content.getBoundingClientRect();
|
|
1031
|
+
return [{
|
|
1032
|
+
panelId: panel.id,
|
|
1033
|
+
x: Math.round(box.left - origin.left + container.scrollLeft),
|
|
1034
|
+
y: Math.round(box.top - origin.top + container.scrollTop),
|
|
1035
|
+
width: Math.round(box.width),
|
|
1036
|
+
height: Math.round(box.height),
|
|
1037
|
+
}];
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
// ========================================
|
|
1041
|
+
// Source Search for the Agent
|
|
1042
|
+
// ========================================
|
|
1043
|
+
/**
|
|
1044
|
+
* Searches the sources the user may show in a panel. First loads the engines that the searched kinds
|
|
1045
|
+
* read: the tab starts these loads with the dashboard, and a load that failed then runs again here.
|
|
1046
|
+
*/
|
|
1047
|
+
async searchSources(query, kinds, limit) {
|
|
1048
|
+
await this.loadSourceEngines(kinds);
|
|
1049
|
+
return SearchDashboardSources(this.sourceSearchDeps(), query, kinds, limit);
|
|
1050
|
+
}
|
|
1051
|
+
/** The lists the source search reads, each limited to what the user may use. */
|
|
1052
|
+
sourceSearchDeps() {
|
|
1053
|
+
const provider = this.ProviderToUse;
|
|
1054
|
+
return {
|
|
1055
|
+
ListArtifacts: query => this.artifactSources(query),
|
|
1056
|
+
ListViews: () => viewSources(),
|
|
1057
|
+
ListQueries: () => querySources(provider.CurrentUser),
|
|
1058
|
+
ListEntities: () => entitySources(provider),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Loads the engines the source search reads for `kinds`: user views for 'view' and queries for
|
|
1063
|
+
* 'query'. Artifacts and entities need neither. A loaded engine returns at once.
|
|
1064
|
+
*/
|
|
1065
|
+
async loadSourceEngines(kinds) {
|
|
1066
|
+
const provider = this.ProviderToUse;
|
|
1067
|
+
const loads = [];
|
|
1068
|
+
if (kinds.includes('view'))
|
|
1069
|
+
loads.push(UserViewEngine.Instance.Config(false, provider.CurrentUser, provider));
|
|
1070
|
+
if (kinds.includes('query'))
|
|
1071
|
+
loads.push(QueryEngine.Instance.Config(false, provider.CurrentUser, provider));
|
|
1072
|
+
await Promise.all(loads);
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Starts to load the source search engines with a Config dashboard, so that the agent's first search
|
|
1076
|
+
* usually finds them loaded. The dashboard does not wait for it. Never rejects: a failure is logged,
|
|
1077
|
+
* and the search loads the engines again.
|
|
1078
|
+
*/
|
|
1079
|
+
async preloadSourceEngines() {
|
|
1080
|
+
try {
|
|
1081
|
+
await this.loadSourceEngines(SOURCE_KINDS);
|
|
1082
|
+
}
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
LogError(`Dashboard tab: could not load the views and queries for the assistant's source search: ${errorMessage(error)}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* The artifacts the user can read in the tab's environment (ChatEnvironmentId) whose name or description
|
|
1089
|
+
* contains `query` in any case (all of them for a blank query), the first SOURCE_SEARCH_MAX_ARTIFACTS by
|
|
1090
|
+
* name, each with its latest version number. An artifact whose versions cannot be read has none, so
|
|
1091
|
+
* its panel shows the latest version. Rejects when the artifacts cannot be read.
|
|
1092
|
+
*/
|
|
1093
|
+
async artifactSources(query) {
|
|
1094
|
+
const provider = this.ProviderToUse;
|
|
1095
|
+
const user = provider.CurrentUser;
|
|
1096
|
+
await ArtifactMetadataEngine.Instance.Config(false, user, provider);
|
|
1097
|
+
const readable = await this.artifactPermissions.GetReadableArtifactsFilter(user.ID, user);
|
|
1098
|
+
const environmentId = EscapeSQLString(this.ChatEnvironmentId);
|
|
1099
|
+
const result = await RunView.FromMetadataProvider(provider).RunView({
|
|
1100
|
+
EntityName: 'MJ: Artifacts',
|
|
1101
|
+
ExtraFilter: `${readable} AND EnvironmentID='${environmentId}' AND (Visibility IS NULL OR Visibility='Always')${artifactQueryFilter(query)}`,
|
|
1102
|
+
Fields: ['ID', 'Name', 'Description', 'Type'],
|
|
1103
|
+
OrderBy: 'Name',
|
|
1104
|
+
MaxRows: SOURCE_SEARCH_MAX_ARTIFACTS,
|
|
1105
|
+
ResultType: 'simple',
|
|
1106
|
+
}, user);
|
|
1107
|
+
if (!result.Success)
|
|
1108
|
+
throw new Error(`Could not read the artifacts: ${result.ErrorMessage || 'no reason given'}`);
|
|
1109
|
+
const artifacts = result.Results ?? [];
|
|
1110
|
+
const latest = await this.latestArtifactVersions(artifacts.map(artifact => artifact.ID));
|
|
1111
|
+
return artifacts.map(artifact => ({
|
|
1112
|
+
ID: artifact.ID,
|
|
1113
|
+
Name: artifact.Name,
|
|
1114
|
+
Description: artifact.Description,
|
|
1115
|
+
Type: artifact.Type,
|
|
1116
|
+
LatestVersion: latest.get(NormalizeUUID(artifact.ID)) ?? null,
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* The highest version number of each artifact, by normalized artifact ID. One RunViews call reads
|
|
1121
|
+
* every version of the artifacts, highest first and without the entity's row cap, with at most
|
|
1122
|
+
* ARTIFACT_VERSION_FILTER_CHUNK IDs in each filter. The artifacts whose versions cannot be read are
|
|
1123
|
+
* not in the map, and the failure is logged.
|
|
1124
|
+
*/
|
|
1125
|
+
async latestArtifactVersions(artifactIds) {
|
|
1126
|
+
const latest = new Map();
|
|
1127
|
+
if (artifactIds.length === 0)
|
|
1128
|
+
return latest;
|
|
1129
|
+
const provider = this.ProviderToUse;
|
|
1130
|
+
try {
|
|
1131
|
+
const results = await RunView.FromMetadataProvider(provider).RunViews(chunked(artifactIds, ARTIFACT_VERSION_FILTER_CHUNK).map(ids => ({
|
|
1132
|
+
EntityName: 'MJ: Artifact Versions',
|
|
1133
|
+
ExtraFilter: `ArtifactID IN (${ids.map(id => `'${EscapeSQLString(id)}'`).join(',')})`,
|
|
1134
|
+
Fields: ['ArtifactID', 'VersionNumber'],
|
|
1135
|
+
OrderBy: 'VersionNumber DESC',
|
|
1136
|
+
IgnoreMaxRows: true,
|
|
1137
|
+
ResultType: 'simple',
|
|
1138
|
+
})), provider.CurrentUser);
|
|
1139
|
+
for (const result of results) {
|
|
1140
|
+
if (result.Success)
|
|
1141
|
+
addLatestVersions(latest, result.Results ?? []);
|
|
1142
|
+
else
|
|
1143
|
+
LogError(`Dashboard tab: could not read the artifact versions: ${result.ErrorMessage || 'no reason given'}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
catch (error) {
|
|
1147
|
+
LogError(`Dashboard tab: could not read the artifact versions: ${errorMessage(error)}`);
|
|
1148
|
+
}
|
|
1149
|
+
return latest;
|
|
1150
|
+
}
|
|
1151
|
+
// ========================================
|
|
1152
|
+
// Requests from the Agent
|
|
1153
|
+
// ========================================
|
|
1154
|
+
/**
|
|
1155
|
+
* Opens the confirm dialog for an agent's request and settles with the user's answer: true once the
|
|
1156
|
+
* confirmed action is done, false when the user cancels. Rejects when no Config dashboard is open,
|
|
1157
|
+
* when another confirmation is open, when the confirmed action fails, and when no answer comes
|
|
1158
|
+
* within AGENT_CONFIRM_TIMEOUT_MS; the dialog closes first.
|
|
1159
|
+
*/
|
|
1160
|
+
askAgentConfirm(kind, detail) {
|
|
1161
|
+
const dashboard = this.ConfigDashboard;
|
|
1162
|
+
if (!dashboard)
|
|
1163
|
+
return Promise.reject(new Error(NO_CONFIG_DASHBOARD));
|
|
1164
|
+
if (this.agentConfirmRequest) {
|
|
1165
|
+
return Promise.reject(new Error('Another confirmation is already open. Wait for the user to answer it, then try again.'));
|
|
1166
|
+
}
|
|
1167
|
+
return new Promise((resolve, reject) => {
|
|
1168
|
+
const timer = setTimeout(() => {
|
|
1169
|
+
this.abortAgentConfirm(AGENT_CONFIRM_TIMED_OUT);
|
|
1170
|
+
SafeDetectChanges(this.cdr);
|
|
1171
|
+
}, AGENT_CONFIRM_TIMEOUT_MS);
|
|
1172
|
+
this.agentConfirmRequest = {
|
|
1173
|
+
answer: confirmed => {
|
|
1174
|
+
clearTimeout(timer);
|
|
1175
|
+
if (!confirmed) {
|
|
1176
|
+
resolve(false);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
this.performAgentRequest(kind, detail).then(() => resolve(true), reject);
|
|
1180
|
+
},
|
|
1181
|
+
abort: reason => {
|
|
1182
|
+
clearTimeout(timer);
|
|
1183
|
+
reject(reason);
|
|
1184
|
+
},
|
|
1185
|
+
};
|
|
1186
|
+
this.AgentConfirm = { Kind: kind, ...agentConfirmText(kind, dashboard.Name, detail) };
|
|
1187
|
+
SafeDetectChanges(this.cdr);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
/** Takes the user's answer to the agent's confirm dialog. Does nothing when no dialog is open. */
|
|
1191
|
+
OnAgentConfirm(confirmed) {
|
|
1192
|
+
const request = this.closeAgentConfirm();
|
|
1193
|
+
SafeDetectChanges(this.cdr);
|
|
1194
|
+
request?.answer(confirmed);
|
|
1195
|
+
}
|
|
1196
|
+
/** Closes the agent's confirm dialog, if one is open, and fails its request with `reason`. */
|
|
1197
|
+
abortAgentConfirm(reason) {
|
|
1198
|
+
this.closeAgentConfirm()?.abort(new Error(reason));
|
|
1199
|
+
}
|
|
1200
|
+
/** Closes the agent's confirm dialog and returns its request, or null when none is open. */
|
|
1201
|
+
closeAgentConfirm() {
|
|
1202
|
+
const request = this.agentConfirmRequest;
|
|
1203
|
+
this.agentConfirmRequest = null;
|
|
1204
|
+
this.AgentConfirm = null;
|
|
1205
|
+
return request;
|
|
1206
|
+
}
|
|
1207
|
+
/** Does what the user confirmed, as the editor's Save and the Add to menu do it. Rejects when it did not take effect. */
|
|
1208
|
+
async performAgentRequest(kind, detail) {
|
|
1209
|
+
switch (kind) {
|
|
1210
|
+
case 'save':
|
|
1211
|
+
return this.saveForAgent(detail);
|
|
1212
|
+
case 'pin':
|
|
1213
|
+
return this.pinForAgent();
|
|
1214
|
+
default:
|
|
1215
|
+
throw new Error(`The tab cannot do the request "${String(kind)}".`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Saves the dashboard through the editor, as its Save does, with the new name and description the
|
|
1220
|
+
* agent gave. Rejects with the reason the save did not happen. While a Save runs the editor refuses
|
|
1221
|
+
* at once and leaves its name and description fields as they are, because that Save reads them.
|
|
1222
|
+
*/
|
|
1223
|
+
async saveForAgent(detail) {
|
|
1224
|
+
const editor = this.editor;
|
|
1225
|
+
const failure = editor ? await editor.Save({ Name: detail.name, Description: detail.description }) : NO_CONFIG_DASHBOARD;
|
|
1226
|
+
if (failure)
|
|
1227
|
+
throw new Error(failure);
|
|
1228
|
+
}
|
|
1229
|
+
/** Pins the dashboard to Home as the Add to menu does. Rejects when it is not pinned afterwards. */
|
|
1230
|
+
async pinForAgent() {
|
|
1231
|
+
await this.PinToHome();
|
|
1232
|
+
if (!this.IsPinnedToHome)
|
|
1233
|
+
throw new Error(`Could not pin "${this.ConfigDashboard?.Name ?? 'the dashboard'}" to Home.`);
|
|
413
1234
|
}
|
|
414
1235
|
/** @deprecated Use {@link OnShareDialogResult}. */
|
|
415
1236
|
onShareDialogResult(result) {
|
|
@@ -432,7 +1253,7 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
432
1253
|
try {
|
|
433
1254
|
// Check if this is a special dashboard type (not a database record)
|
|
434
1255
|
const config = data.Configuration || {};
|
|
435
|
-
if (
|
|
1256
|
+
if (this.isDataExplorer(data)) {
|
|
436
1257
|
// Special case: Data Explorer dashboard with optional entity filter
|
|
437
1258
|
await this.loadDataExplorer(config['entityFilter'], config['appName'], config['appIcon']);
|
|
438
1259
|
return;
|
|
@@ -442,14 +1263,17 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
442
1263
|
if (!dashboard) {
|
|
443
1264
|
throw new Error(`Dashboard with ID ${data.ResourceRecordID} not found.`);
|
|
444
1265
|
}
|
|
1266
|
+
void this.recentAccess.LogAccess('MJ: Dashboards', dashboard.ID, 'dashboard');
|
|
445
1267
|
// Determine which dashboard component to load based on dashboard type
|
|
446
1268
|
if (dashboard.Type === 'Code') {
|
|
447
1269
|
// CODE-BASED DASHBOARD: Use registered class via DriverClass
|
|
448
1270
|
await this.loadCodeBasedDashboard(dashboard);
|
|
449
1271
|
}
|
|
450
1272
|
else {
|
|
451
|
-
// CONFIG-BASED DASHBOARD:
|
|
452
|
-
|
|
1273
|
+
// CONFIG-BASED DASHBOARD: the shared dashboard editor shows it. Its studio tools
|
|
1274
|
+
// search views and queries, so their engines start to load now; the dashboard does not wait.
|
|
1275
|
+
void this.preloadSourceEngines();
|
|
1276
|
+
this.showConfigDashboard(dashboard);
|
|
453
1277
|
}
|
|
454
1278
|
}
|
|
455
1279
|
catch (error) {
|
|
@@ -636,68 +1460,7 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
636
1460
|
return stateObject;
|
|
637
1461
|
}
|
|
638
1462
|
/**
|
|
639
|
-
*
|
|
640
|
-
*/
|
|
641
|
-
async loadConfigBasedDashboard(dashboard) {
|
|
642
|
-
try {
|
|
643
|
-
this.ContainerElement.nativeElement.innerHTML = '';
|
|
644
|
-
const componentRef = this.viewContainer.createComponent(DashboardViewerComponent);
|
|
645
|
-
this.componentRef = componentRef;
|
|
646
|
-
const instance = componentRef.instance;
|
|
647
|
-
// Store references for external toolbar control
|
|
648
|
-
this.viewerInstance = instance;
|
|
649
|
-
this.ConfigDashboard = dashboard;
|
|
650
|
-
// Compute user permissions for this dashboard
|
|
651
|
-
const md = this.ProviderToUse;
|
|
652
|
-
this.DashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, md.CurrentUser.ID);
|
|
653
|
-
// Manually append the component's native element inside the div
|
|
654
|
-
const nativeElement = this.componentRef.hostView.rootNodes[0];
|
|
655
|
-
nativeElement.style.width = '100%';
|
|
656
|
-
nativeElement.style.height = '100%';
|
|
657
|
-
this.ContainerElement.nativeElement.appendChild(nativeElement);
|
|
658
|
-
// Load categories for breadcrumb navigation (if not already loaded)
|
|
659
|
-
if (this.categories.length === 0) {
|
|
660
|
-
this.categories = DashboardEngine.Instance.DashboardCategories;
|
|
661
|
-
}
|
|
662
|
-
// Configure the viewer before assigning the dashboard. The dashboard input
|
|
663
|
-
// starts the layout lifecycle synchronously, so event handlers need to be
|
|
664
|
-
// wired first.
|
|
665
|
-
instance.showToolbar = false; // We provide external toolbar
|
|
666
|
-
instance.showBreadcrumb = false; // Already in its own tab, no breadcrumb needed
|
|
667
|
-
instance.showOpenInTabButton = false; // Already in its own tab
|
|
668
|
-
instance.showEditButton = false; // External toolbar handles edit
|
|
669
|
-
instance.Categories = this.categories;
|
|
670
|
-
// Wire up navigation events - handle navigation requests from the dashboard
|
|
671
|
-
instance.navigationRequested.subscribe((event) => {
|
|
672
|
-
this.handleNavigationRequest(event);
|
|
673
|
-
});
|
|
674
|
-
// Wire up "Open in Tab" button click
|
|
675
|
-
instance.openInTab.subscribe((event) => {
|
|
676
|
-
this.navigationService.OpenDashboard(event.dashboardId, event.dashboardName);
|
|
677
|
-
});
|
|
678
|
-
// Wire up dashboard saved event
|
|
679
|
-
instance.dashboardSaved.subscribe((savedDashboard) => {
|
|
680
|
-
this.ResourceRecordSaved(savedDashboard);
|
|
681
|
-
});
|
|
682
|
-
// Wire up error events
|
|
683
|
-
instance.error.subscribe((errorEvent) => {
|
|
684
|
-
console.error('Dashboard error:', errorEvent.message, errorEvent.error);
|
|
685
|
-
});
|
|
686
|
-
// Set the dashboard entity directly on the viewer and wait for Golden Layout
|
|
687
|
-
// to initialize against a real container before clearing the resource loader.
|
|
688
|
-
instance.dashboard = dashboard;
|
|
689
|
-
await instance.waitForLayoutReady();
|
|
690
|
-
this.NotifyLoadComplete();
|
|
691
|
-
this.cdr.detectChanges();
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
console.error('Error loading config-based dashboard:', error);
|
|
695
|
-
this.setError(`The dashboard "${dashboard.Name}" could not be loaded. There may be an issue with the dashboard configuration.`, error);
|
|
696
|
-
this.NotifyLoadComplete();
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Handle navigation requests from the dashboard viewer
|
|
1463
|
+
* Handle navigation requests from a part of the dashboard (the editor passes them on)
|
|
701
1464
|
*/
|
|
702
1465
|
handleNavigationRequest(event) {
|
|
703
1466
|
const request = event.request;
|
|
@@ -740,26 +1503,41 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
740
1503
|
}
|
|
741
1504
|
}
|
|
742
1505
|
/**
|
|
743
|
-
*
|
|
744
|
-
*
|
|
1506
|
+
* The tab title: the dashboard's name from the DashboardEngine cache, else its record name from
|
|
1507
|
+
* the server, else the given name. A Data Explorer tab keeps the name of the app it belongs to.
|
|
745
1508
|
*/
|
|
746
1509
|
async GetResourceDisplayName(data) {
|
|
1510
|
+
if (this.isDataExplorer(data)) {
|
|
1511
|
+
const config = data.Configuration ?? {};
|
|
1512
|
+
const appName = typeof config['appName'] === 'string' ? config['appName'] : '';
|
|
1513
|
+
return data.Name || appName || 'Data Explorer';
|
|
1514
|
+
}
|
|
1515
|
+
const dashboardId = typeof data.ResourceRecordID === 'string' ? data.ResourceRecordID : '';
|
|
1516
|
+
const name = dashboardId ? (this.cachedDashboardName(dashboardId) ?? await this.loadDashboardName(dashboardId)) : null;
|
|
1517
|
+
return name || data.Name || 'Dashboard';
|
|
1518
|
+
}
|
|
1519
|
+
/** True for the Data Explorer, which this wrapper hosts but which is not a dashboard record. */
|
|
1520
|
+
isDataExplorer(data) {
|
|
1521
|
+
const config = data.Configuration ?? {};
|
|
1522
|
+
return config['dashboardType'] === 'DataExplorer' || data.ResourceRecordID === 'DataExplorer';
|
|
1523
|
+
}
|
|
1524
|
+
/** The dashboard's name from the DashboardEngine cache, or null when the cache does not have it. */
|
|
1525
|
+
cachedDashboardName(dashboardId) {
|
|
747
1526
|
try {
|
|
748
|
-
|
|
749
|
-
if (data.ResourceRecordID && data.ResourceRecordID.length > 0) {
|
|
750
|
-
const md = this.ProviderToUse;
|
|
751
|
-
const compositeKey = CompositeKey.FromID(data.ResourceRecordID); // first-pk-ok: ResourceRecordID of a Dashboards resource — core entity keyed by ID
|
|
752
|
-
const name = await md.GetEntityRecordName('Dashboards', compositeKey);
|
|
753
|
-
if (name) {
|
|
754
|
-
return name;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
1527
|
+
return DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, dashboardId))?.Name || null;
|
|
757
1528
|
}
|
|
758
|
-
catch
|
|
759
|
-
//
|
|
1529
|
+
catch {
|
|
1530
|
+
return null; // the user cannot read the dashboards cache
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/** The dashboard's record name from the server, or null when it cannot be read. */
|
|
1534
|
+
async loadDashboardName(dashboardId) {
|
|
1535
|
+
try {
|
|
1536
|
+
return (await this.ProviderToUse.GetEntityRecordName('MJ: Dashboards', CompositeKey.FromID(dashboardId))) || null;
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
return null;
|
|
760
1540
|
}
|
|
761
|
-
// Fallback: use provided name or generic label
|
|
762
|
-
return data.Name || 'Dashboard';
|
|
763
1541
|
}
|
|
764
1542
|
/**
|
|
765
1543
|
* Get the icon class for dashboard resources
|
|
@@ -769,28 +1547,34 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
769
1547
|
}
|
|
770
1548
|
static ɵfac = function DashboardResource_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DashboardResource)(i0.ɵɵdirectiveInject(i0.ViewContainerRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
771
1549
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DashboardResource, selectors: [["mj-dashboard-resource"]], viewQuery: function DashboardResource_Query(rf, ctx) { if (rf & 1) {
|
|
772
|
-
i0.ɵɵviewQuery(_c0, 7);
|
|
1550
|
+
i0.ɵɵviewQuery(_c0, 5)(_c1, 7);
|
|
773
1551
|
} if (rf & 2) {
|
|
774
1552
|
let _t;
|
|
1553
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.editor = _t.first);
|
|
775
1554
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ContainerElement = _t.first);
|
|
776
|
-
} }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls:
|
|
777
|
-
i0.ɵɵelementStart(0, "div",
|
|
778
|
-
i0.ɵɵconditionalCreate(1, DashboardResource_Conditional_1_Template, 8, 2, "div",
|
|
779
|
-
i0.ɵɵconditionalCreate(2, DashboardResource_Conditional_2_Template,
|
|
780
|
-
i0.ɵɵ
|
|
781
|
-
i0.ɵɵ
|
|
782
|
-
i0.ɵɵconditionalCreate(6, DashboardResource_Conditional_6_Template, 1,
|
|
1555
|
+
} }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 8, vars: 7, consts: [["container", ""], ["editor", ""], [1, "dashboard-resource-wrapper"], [1, "error-state"], ["direction", "horizontal", "unit", "percent", 1, "studio-copilot-splitter", 3, "gutterSize"], [1, "dashboard-resource-container"], [3, "Visible", "Dashboard"], ["Type", "info", "CancelText", "Not now", 1, "agent-confirm", 3, "Visible", "Title", "Message", "ConfirmText"], ["Type", "warning", "Title", "End the voice session?", "Message", "Closing the AI assistant ends the voice session.", "ConfirmText", "Close", "CancelText", "Keep open", 1, "close-chat-confirm", 3, "Visible"], [1, "error-icon"], [1, "fa-solid", "fa-triangle-exclamation"], [1, "error-title"], [1, "error-message"], [1, "error-details"], ["direction", "horizontal", "unit", "percent", 1, "studio-copilot-splitter", 3, "dragEnd", "gutterSize"], [3, "size", "minSize"], [3, "Loaded", "LoadFailed", "EditingChange", "NameChanged", "ConfigChanged", "ReloadedFromSaved", "FavoriteChange", "NavigationRequested", "Provider", "DashboardId"], ["headerTools", "", "type", "button", "mjButton", "", "Variant", "icon", "Size", "sm", "title", "AI assistant", "aria-label", "AI assistant", 1, "dashboard-tab-ai-toggle", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-robot"], ["viewActions", "", 1, "dashboard-tab-view-actions"], [3, "PinToHome", "Share", "IsPinned", "CanShare"], ["type", "button", "mjButton", "", "Variant", "icon", "Size", "sm", "title", "Share dashboard", "aria-label", "Share dashboard", 1, "dashboard-tab-share"], [3, "size", "minSize", "maxSize"], ["type", "button", "mjButton", "", "Variant", "icon", "Size", "sm", "title", "Share dashboard", "aria-label", "Share dashboard", 1, "dashboard-tab-share", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-share-nodes"], ["data-testid", "dashboard-copilot", "aria-label", "AI assistant", 1, "dashboard-copilot"], [1, "dashboard-copilot-head"], [1, "dashboard-copilot-title"], [1, "fa-solid", "fa-robot"], [1, "dashboard-copilot-actions"], ["type", "button", 1, "dashboard-copilot-btn", "copilot-expand", 3, "click", "title"], [1, "fa-solid"], ["type", "button", "title", "Close assistant", "aria-label", "Close assistant", 1, "dashboard-copilot-btn", "copilot-close", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "dashboard-copilot-body"], ["ApplicationScope", "Application", "EmptyStateGreeting", "What should this dashboard show?", 3, "Provider", "EnvironmentId", "CurrentUser", "Conversation", "ConversationId", "IsNewConversation", "PendingMessage", "PendingAttachments", "SuppressNewConversationEmptyState", "AllowMentions", "OverlayMode", "ShowExportButton", "ShowShareButton", "ShowArtifactIndicator", "ShowAgentPicker", "ShowAgentModePicker", "DefaultAgentId", "AllowedAgentIDs", "ApplicationId", "AppContext"], ["role", "status", 1, "dashboard-copilot-empty"], [1, "dashboard-copilot-empty"], ["ApplicationScope", "Application", "EmptyStateGreeting", "What should this dashboard show?", 3, "ConversationCreated", "RealtimeConversationReady", "PendingMessageConsumed", "navigationRequest", "OpenEntityRecord", "Provider", "EnvironmentId", "CurrentUser", "Conversation", "ConversationId", "IsNewConversation", "PendingMessage", "PendingAttachments", "SuppressNewConversationEmptyState", "AllowMentions", "OverlayMode", "ShowExportButton", "ShowShareButton", "ShowArtifactIndicator", "ShowAgentPicker", "ShowAgentModePicker", "DefaultAgentId", "AllowedAgentIDs", "ApplicationId", "AppContext"], ["Text", "Connecting\u2026", "Size", "small"], [3, "Result", "Visible", "Dashboard"], ["Type", "info", "CancelText", "Not now", 1, "agent-confirm", 3, "Confirmed", "Cancelled", "Visible", "Title", "Message", "ConfirmText"], ["Type", "warning", "Title", "End the voice session?", "Message", "Closing the AI assistant ends the voice session.", "ConfirmText", "Close", "CancelText", "Keep open", 1, "close-chat-confirm", 3, "Confirmed", "Cancelled", "Visible"]], template: function DashboardResource_Template(rf, ctx) { if (rf & 1) {
|
|
1556
|
+
i0.ɵɵelementStart(0, "div", 2);
|
|
1557
|
+
i0.ɵɵconditionalCreate(1, DashboardResource_Conditional_1_Template, 8, 2, "div", 3);
|
|
1558
|
+
i0.ɵɵconditionalCreate(2, DashboardResource_Conditional_2_Template, 10, 12, "as-split", 4);
|
|
1559
|
+
i0.ɵɵelement(3, "div", 5, 0);
|
|
1560
|
+
i0.ɵɵconditionalCreate(5, DashboardResource_Conditional_5_Template, 1, 2, "mj-dashboard-share-dialog", 6);
|
|
1561
|
+
i0.ɵɵconditionalCreate(6, DashboardResource_Conditional_6_Template, 1, 4, "mj-confirm-dialog", 7);
|
|
1562
|
+
i0.ɵɵconditionalCreate(7, DashboardResource_Conditional_7_Template, 1, 1, "mj-confirm-dialog", 8);
|
|
783
1563
|
i0.ɵɵelementEnd();
|
|
784
1564
|
} if (rf & 2) {
|
|
785
1565
|
i0.ɵɵadvance();
|
|
786
1566
|
i0.ɵɵconditional(ctx.errorMessage ? 1 : -1);
|
|
787
1567
|
i0.ɵɵadvance();
|
|
788
|
-
i0.ɵɵconditional(ctx.
|
|
1568
|
+
i0.ɵɵconditional(ctx.ConfigDashboardId && !ctx.errorMessage ? 2 : -1);
|
|
1569
|
+
i0.ɵɵadvance();
|
|
1570
|
+
i0.ɵɵclassProp("is-hidden", !!ctx.ConfigDashboardId);
|
|
1571
|
+
i0.ɵɵadvance(2);
|
|
1572
|
+
i0.ɵɵconditional(ctx.ConfigDashboard ? 5 : -1);
|
|
1573
|
+
i0.ɵɵadvance();
|
|
1574
|
+
i0.ɵɵconditional(ctx.AgentConfirm ? 6 : -1);
|
|
789
1575
|
i0.ɵɵadvance();
|
|
790
|
-
i0.ɵɵconditional(ctx.
|
|
791
|
-
|
|
792
|
-
i0.ɵɵconditional(ctx.configDashboard ? 6 : -1);
|
|
793
|
-
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.DashboardShareDialogComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n .dashboard-resource-container[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n min-height: 0;\n }\n\n \n .viewer-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n gap: 16px;\n }\n .viewer-toolbar[_ngcontent-%COMP%] .toolbar-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .viewer-toolbar[_ngcontent-%COMP%] .dashboard-title[_ngcontent-%COMP%] {\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .viewer-toolbar[_ngcontent-%COMP%] .dashboard-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n }\n .shared-indicator[_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: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 11px;\n }\n .viewer-toolbar[_ngcontent-%COMP%] .toolbar-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n \n .viewer-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n transition: background 0.2s, border-color 0.2s;\n }\n .viewer-header.editing[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)) 0%, color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface)) 100%);\n border-bottom: 2px solid var(--mj-brand-primary);\n }\n .viewer-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n .viewer-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n \n .btn-add-part[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s, transform 0.1s;\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n }\n .btn-add-part[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n transform: translateY(-1px);\n box-shadow: 0 3px 6px color-mix(in srgb, var(--mj-brand-primary) 40%, transparent);\n }\n .btn-add-part[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { font-size: 12px; }\n\n \n .header-separator[_ngcontent-%COMP%] {\n width: 1px;\n height: 28px;\n background: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n margin: 0 4px;\n }\n\n \n .btn-primary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n .btn-primary[_ngcontent-%COMP%]:hover { background: var(--mj-brand-primary-hover); }\n\n .btn-icon[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-icon[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-sunken); }\n\n .btn-cancel[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n color: var(--mj-text-primary);\n }\n\n \n .dashboard-info-edit[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n flex: 1;\n }\n .dashboard-name-input[_ngcontent-%COMP%] {\n border: 1px solid transparent;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-primary);\n background: rgba(255, 255, 255, 0.7);\n outline: none;\n min-width: 200px;\n max-width: 300px;\n transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;\n }\n .dashboard-name-input[_ngcontent-%COMP%]:hover { background: rgba(255, 255, 255, 0.9); }\n .dashboard-name-input[_ngcontent-%COMP%]:focus {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n }\n .dashboard-description-input[_ngcontent-%COMP%] {\n border: 1px solid transparent;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n color: var(--mj-text-secondary);\n background: rgba(255, 255, 255, 0.5);\n outline: none;\n flex: 1;\n min-width: 150px;\n max-width: 400px;\n transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;\n }\n .dashboard-description-input[_ngcontent-%COMP%]:hover { background: rgba(255, 255, 255, 0.8); }\n .dashboard-description-input[_ngcontent-%COMP%]:focus {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n }\n .dashboard-description-input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n font-style: normal;\n }\n\n \n .error-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px;\n text-align: center;\n color: var(--mj-text-secondary);\n }\n .error-icon[_ngcontent-%COMP%] {\n font-size: 64px;\n color: var(--mj-status-error);\n margin-bottom: 24px;\n opacity: 0.8;\n }\n .error-title[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 500;\n margin: 0 0 12px 0;\n color: var(--mj-text-primary);\n }\n .error-message[_ngcontent-%COMP%] {\n font-size: 16px;\n color: var(--mj-text-muted);\n margin: 0 0 24px 0;\n max-width: 500px;\n line-height: 1.5;\n }\n .error-details[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 12px 16px;\n max-width: 600px;\n text-align: left;\n font-size: 13px;\n }\n .error-details[_ngcontent-%COMP%] summary[_ngcontent-%COMP%] {\n cursor: pointer;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n }\n .error-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--mj-status-error-text);\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n\n \n @media (max-width: 768px) {\n .viewer-header[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 12px;\n align-items: stretch;\n }\n .viewer-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] { flex-wrap: wrap; }\n .dashboard-info-edit[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: stretch;\n }\n .dashboard-name-input[_ngcontent-%COMP%], \n .dashboard-description-input[_ngcontent-%COMP%] { max-width: none; }\n }"] });
|
|
1576
|
+
i0.ɵɵconditional(ctx.ShowCloseChatConfirm ? 7 : -1);
|
|
1577
|
+
} }, dependencies: [i1.ConversationChatAreaComponent, i2.DashboardShareDialogComponent, i2.DashboardEditorComponent, i3.ConfirmDialogComponent, i4.SplitComponent, i4.SplitAreaComponent, i5.LoadingComponent, i6.MJButtonDirective, i7.DashboardAddToMenuComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n \n .studio-copilot-splitter[_ngcontent-%COMP%] {\n flex: 1;\n width: 100%;\n height: 100%;\n min-height: 0;\n min-width: 0;\n }\n \n .dashboard-resource-container[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n .dashboard-resource-container.is-hidden[_ngcontent-%COMP%] { display: none; }\n\n \n .dashboard-tab-view-actions[_ngcontent-%COMP%] { display: contents; }\n \n .dashboard-tab-ai-toggle.is-active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-brand-primary); }\n\n \n .dashboard-copilot[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .dashboard-copilot-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 14px;\n border-bottom: 1px solid var(--mj-border-default);\n }\n .dashboard-copilot-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n .dashboard-copilot-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-brand-primary); }\n .dashboard-copilot-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .dashboard-copilot-btn[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 6px 8px;\n border-radius: 6px;\n color: var(--mj-text-muted);\n }\n .dashboard-copilot-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n }\n .dashboard-copilot-body[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n .dashboard-copilot-body[_ngcontent-%COMP%] mj-conversation-chat-area[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n display: block;\n }\n .dashboard-copilot-empty[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n padding: 16px;\n color: var(--mj-text-secondary);\n text-align: center;\n }\n\n \n .error-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px;\n text-align: center;\n color: var(--mj-text-secondary);\n }\n .error-icon[_ngcontent-%COMP%] {\n font-size: 64px;\n color: var(--mj-status-error);\n margin-bottom: 24px;\n opacity: 0.8;\n }\n .error-title[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 500;\n margin: 0 0 12px 0;\n color: var(--mj-text-primary);\n }\n .error-message[_ngcontent-%COMP%] {\n font-size: 16px;\n color: var(--mj-text-muted);\n margin: 0 0 24px 0;\n max-width: 500px;\n line-height: 1.5;\n }\n .error-details[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 12px 16px;\n max-width: 600px;\n text-align: left;\n font-size: 13px;\n }\n .error-details[_ngcontent-%COMP%] summary[_ngcontent-%COMP%] {\n cursor: pointer;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n }\n .error-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--mj-status-error-text);\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }"] });
|
|
794
1578
|
};
|
|
795
1579
|
DashboardResource = __decorate([
|
|
796
1580
|
RegisterClass(BaseResourceComponent, 'DashboardResource')
|
|
@@ -817,91 +1601,258 @@ export { DashboardResource };
|
|
|
817
1601
|
</div>
|
|
818
1602
|
}
|
|
819
1603
|
|
|
820
|
-
<!--
|
|
821
|
-
@if (
|
|
822
|
-
<
|
|
823
|
-
<
|
|
824
|
-
<
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1604
|
+
<!-- A Config dashboard: the shared editor, with the AI pane on its right. The editor stays in the template while the pane opens and closes. -->
|
|
1605
|
+
@if (ConfigDashboardId && !errorMessage) {
|
|
1606
|
+
<as-split direction="horizontal" class="studio-copilot-splitter" unit="percent" [gutterSize]="ChatOpen ? 6 : 0" (dragEnd)="OnCopilotSplitDragEnd($event.sizes)">
|
|
1607
|
+
<as-split-area [size]="ChatOpen ? (CopilotExpanded ? 100 - CopilotMaxPct : MainSizePct) : 100" [minSize]="100 - CopilotMaxPct">
|
|
1608
|
+
<mj-dashboard-editor
|
|
1609
|
+
#editor
|
|
1610
|
+
[Provider]="Provider"
|
|
1611
|
+
[DashboardId]="ConfigDashboardId"
|
|
1612
|
+
(Loaded)="OnEditorLoaded($event)"
|
|
1613
|
+
(LoadFailed)="OnEditorLoadFailed($event)"
|
|
1614
|
+
(EditingChange)="OnEditorEditingChange()"
|
|
1615
|
+
(NameChanged)="OnEditorNameChanged($event)"
|
|
1616
|
+
(ConfigChanged)="OnEditorConfigChanged()"
|
|
1617
|
+
(ReloadedFromSaved)="OnEditorReloaded()"
|
|
1618
|
+
(FavoriteChange)="OnEditorFavoriteChange()"
|
|
1619
|
+
(NavigationRequested)="OnEditorNavigationRequested($event)">
|
|
836
1620
|
<button
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1621
|
+
headerTools
|
|
1622
|
+
type="button"
|
|
1623
|
+
mjButton
|
|
1624
|
+
Variant="icon"
|
|
1625
|
+
Size="sm"
|
|
1626
|
+
class="dashboard-tab-ai-toggle"
|
|
1627
|
+
[class.is-active]="ChatOpen"
|
|
1628
|
+
title="AI assistant"
|
|
1629
|
+
aria-label="AI assistant"
|
|
1630
|
+
[attr.aria-expanded]="ChatOpen ? 'true' : 'false'"
|
|
1631
|
+
(click)="ToggleChat()">
|
|
1632
|
+
<i class="fa-solid fa-robot" aria-hidden="true"></i>
|
|
841
1633
|
</button>
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1634
|
+
<span viewActions class="dashboard-tab-view-actions">
|
|
1635
|
+
<mj-dashboard-add-to-menu
|
|
1636
|
+
[IsPinned]="IsPinnedToHome"
|
|
1637
|
+
[CanShare]="CanShare"
|
|
1638
|
+
(PinToHome)="PinToHome()"
|
|
1639
|
+
(Share)="OpenShareDialog()">
|
|
1640
|
+
</mj-dashboard-add-to-menu>
|
|
1641
|
+
@if (CanShare) {
|
|
1642
|
+
<button
|
|
1643
|
+
type="button"
|
|
1644
|
+
mjButton
|
|
1645
|
+
Variant="icon"
|
|
1646
|
+
Size="sm"
|
|
1647
|
+
class="dashboard-tab-share"
|
|
1648
|
+
title="Share dashboard"
|
|
1649
|
+
aria-label="Share dashboard"
|
|
1650
|
+
(click)="OpenShareDialog()">
|
|
1651
|
+
<i class="fa-solid fa-share-nodes" aria-hidden="true"></i>
|
|
1652
|
+
</button>
|
|
1653
|
+
}
|
|
1654
|
+
</span>
|
|
1655
|
+
</mj-dashboard-editor>
|
|
1656
|
+
</as-split-area>
|
|
1657
|
+
@if (ChatOpen) {
|
|
1658
|
+
<as-split-area [size]="CopilotExpanded ? CopilotMaxPct : CopilotSizePct" [minSize]="CopilotMinPct" [maxSize]="CopilotMaxPct">
|
|
1659
|
+
<aside class="dashboard-copilot" [class.expanded]="CopilotExpanded" data-testid="dashboard-copilot" aria-label="AI assistant">
|
|
1660
|
+
<div class="dashboard-copilot-head">
|
|
1661
|
+
<div class="dashboard-copilot-title"><i class="fa-solid fa-robot"></i> {{ ChatAgentName ?? 'AI assistant' }}</div>
|
|
1662
|
+
<div class="dashboard-copilot-actions">
|
|
1663
|
+
<button
|
|
1664
|
+
type="button"
|
|
1665
|
+
class="dashboard-copilot-btn copilot-expand"
|
|
1666
|
+
[title]="CopilotExpanded ? 'Collapse panel' : 'Expand panel'"
|
|
1667
|
+
[attr.aria-label]="CopilotExpanded ? 'Collapse panel' : 'Expand panel'"
|
|
1668
|
+
(click)="ToggleCopilotExpanded()">
|
|
1669
|
+
<i class="fa-solid" [class.fa-up-right-and-down-left-from-center]="!CopilotExpanded" [class.fa-down-left-and-up-right-to-center]="CopilotExpanded"></i>
|
|
1670
|
+
</button>
|
|
1671
|
+
<button type="button" class="dashboard-copilot-btn copilot-close" title="Close assistant" aria-label="Close assistant" (click)="CloseChat()">
|
|
1672
|
+
<i class="fa-solid fa-xmark"></i>
|
|
1673
|
+
</button>
|
|
1674
|
+
</div>
|
|
1675
|
+
</div>
|
|
1676
|
+
<div class="dashboard-copilot-body">
|
|
1677
|
+
@if (ChatAgentId && ProviderToUse.CurrentUser) {
|
|
1678
|
+
<mj-conversation-chat-area
|
|
1679
|
+
[Provider]="Provider" [EnvironmentId]="ChatEnvironmentId" [CurrentUser]="ProviderToUse.CurrentUser"
|
|
1680
|
+
[Conversation]="ChatConversation" [ConversationId]="ChatConversationId" [IsNewConversation]="ChatIsNewConversation"
|
|
1681
|
+
[PendingMessage]="ChatPendingMessage" [PendingAttachments]="ChatPendingAttachments"
|
|
1682
|
+
[SuppressNewConversationEmptyState]="true" [AllowMentions]="false" [OverlayMode]="false"
|
|
1683
|
+
[ShowExportButton]="false" [ShowShareButton]="false" [ShowArtifactIndicator]="false"
|
|
1684
|
+
[ShowAgentPicker]="false" [ShowAgentModePicker]="false"
|
|
1685
|
+
[DefaultAgentId]="ChatAgentId" [AllowedAgentIDs]="[ChatAgentId]"
|
|
1686
|
+
ApplicationScope="Application" [ApplicationId]="ChatApplicationId" [AppContext]="ChatAppContext"
|
|
1687
|
+
EmptyStateGreeting="What should this dashboard show?"
|
|
1688
|
+
(ConversationCreated)="OnChatConversationCreated($event)"
|
|
1689
|
+
(RealtimeConversationReady)="OnChatRealtimeConversationReady($event)"
|
|
1690
|
+
(PendingMessageConsumed)="OnChatPendingMessageConsumed()"
|
|
1691
|
+
(navigationRequest)="OnChatNavigationRequest($event)"
|
|
1692
|
+
(OpenEntityRecord)="OnChatOpenEntityRecord($event)">
|
|
1693
|
+
</mj-conversation-chat-area>
|
|
1694
|
+
} @else if (ChatAgentError) {
|
|
1695
|
+
<div class="dashboard-copilot-empty" role="status">{{ ChatAgentError }}</div>
|
|
1696
|
+
} @else {
|
|
1697
|
+
<div class="dashboard-copilot-empty"><mj-loading Text="Connecting…" Size="small"></mj-loading></div>
|
|
1698
|
+
}
|
|
1699
|
+
</div>
|
|
1700
|
+
</aside>
|
|
1701
|
+
</as-split-area>
|
|
1702
|
+
}
|
|
1703
|
+
</as-split>
|
|
887
1704
|
}
|
|
888
1705
|
|
|
889
|
-
<!--
|
|
890
|
-
<div #container class="dashboard-resource-container"></div>
|
|
1706
|
+
<!-- Code dashboards and the Data Explorer render here. It stays in the template, so the tab finds it when it starts. -->
|
|
1707
|
+
<div #container class="dashboard-resource-container" [class.is-hidden]="!!ConfigDashboardId"></div>
|
|
891
1708
|
|
|
892
1709
|
<!-- Share Dashboard Dialog -->
|
|
893
|
-
@if (
|
|
1710
|
+
@if (ConfigDashboard) {
|
|
894
1711
|
<mj-dashboard-share-dialog
|
|
895
|
-
[Visible]="
|
|
896
|
-
[Dashboard]="
|
|
897
|
-
(Result)="
|
|
1712
|
+
[Visible]="ShowShareDialog"
|
|
1713
|
+
[Dashboard]="ConfigDashboard"
|
|
1714
|
+
(Result)="OnShareDialogResult($event)">
|
|
898
1715
|
</mj-dashboard-share-dialog>
|
|
899
1716
|
}
|
|
1717
|
+
|
|
1718
|
+
<!-- Confirmation for an agent's Request* tool: the user, not the agent, saves or pins -->
|
|
1719
|
+
@if (AgentConfirm) {
|
|
1720
|
+
<mj-confirm-dialog
|
|
1721
|
+
class="agent-confirm"
|
|
1722
|
+
[Visible]="true"
|
|
1723
|
+
Type="info"
|
|
1724
|
+
[Title]="AgentConfirm.Title"
|
|
1725
|
+
[Message]="AgentConfirm.Message"
|
|
1726
|
+
[ConfirmText]="AgentConfirm.ConfirmText"
|
|
1727
|
+
CancelText="Not now"
|
|
1728
|
+
(Confirmed)="OnAgentConfirm(true)"
|
|
1729
|
+
(Cancelled)="OnAgentConfirm(false)">
|
|
1730
|
+
</mj-confirm-dialog>
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
<!-- Closing the AI pane ends a voice session, so the user confirms it first -->
|
|
1734
|
+
@if (ShowCloseChatConfirm) {
|
|
1735
|
+
<mj-confirm-dialog
|
|
1736
|
+
class="close-chat-confirm"
|
|
1737
|
+
[Visible]="true"
|
|
1738
|
+
Type="warning"
|
|
1739
|
+
Title="End the voice session?"
|
|
1740
|
+
Message="Closing the AI assistant ends the voice session."
|
|
1741
|
+
ConfirmText="Close"
|
|
1742
|
+
CancelText="Keep open"
|
|
1743
|
+
(Confirmed)="OnCloseChatConfirmed()"
|
|
1744
|
+
(Cancelled)="OnCloseChatCancelled()">
|
|
1745
|
+
</mj-confirm-dialog>
|
|
1746
|
+
}
|
|
900
1747
|
</div>
|
|
901
|
-
`, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n
|
|
902
|
-
}], () => [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }], {
|
|
1748
|
+
`, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n /* The editor and the AI pane share the tab. The global MJ splitter theme styles the gutter. */\n .studio-copilot-splitter {\n flex: 1;\n width: 100%;\n height: 100%;\n min-height: 0;\n min-width: 0;\n }\n /* Holds a Code dashboard or the Data Explorer. Hidden while the editor shows a Config dashboard. */\n .dashboard-resource-container {\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n .dashboard-resource-container.is-hidden { display: none; }\n\n /* The view actions sit in the editor's header row as if they were its own buttons */\n .dashboard-tab-view-actions { display: contents; }\n /* The colour goes on the icon, never on the button: button.scss owns the button */\n .dashboard-tab-ai-toggle.is-active i { color: var(--mj-brand-primary); }\n\n /* AI pane */\n .dashboard-copilot {\n width: 100%;\n height: 100%;\n border-left: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .dashboard-copilot-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 14px;\n border-bottom: 1px solid var(--mj-border-default);\n }\n .dashboard-copilot-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n .dashboard-copilot-title i { color: var(--mj-brand-primary); }\n .dashboard-copilot-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .dashboard-copilot-btn {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 6px 8px;\n border-radius: 6px;\n color: var(--mj-text-muted);\n }\n .dashboard-copilot-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n }\n .dashboard-copilot-body {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n .dashboard-copilot-body mj-conversation-chat-area {\n flex: 1;\n min-height: 0;\n display: block;\n }\n .dashboard-copilot-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n padding: 16px;\n color: var(--mj-text-secondary);\n text-align: center;\n }\n\n /* Error state */\n .error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px;\n text-align: center;\n color: var(--mj-text-secondary);\n }\n .error-icon {\n font-size: 64px;\n color: var(--mj-status-error);\n margin-bottom: 24px;\n opacity: 0.8;\n }\n .error-title {\n font-size: 24px;\n font-weight: 500;\n margin: 0 0 12px 0;\n color: var(--mj-text-primary);\n }\n .error-message {\n font-size: 16px;\n color: var(--mj-text-muted);\n margin: 0 0 24px 0;\n max-width: 500px;\n line-height: 1.5;\n }\n .error-details {\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 12px 16px;\n max-width: 600px;\n text-align: left;\n font-size: 13px;\n }\n .error-details summary {\n cursor: pointer;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n }\n .error-details pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--mj-status-error-text);\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n "] }]
|
|
1749
|
+
}], () => [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }], { editor: [{
|
|
1750
|
+
type: ViewChild,
|
|
1751
|
+
args: ['editor']
|
|
1752
|
+
}], ContainerElement: [{
|
|
903
1753
|
type: ViewChild,
|
|
904
1754
|
args: ['container', { static: true }]
|
|
905
1755
|
}] }); })();
|
|
906
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardResource, { className: "DashboardResource", filePath: "src/lib/resource-wrappers/dashboard-resource.component.ts", lineNumber:
|
|
1756
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardResource, { className: "DashboardResource", filePath: "src/lib/resource-wrappers/dashboard-resource.component.ts", lineNumber: 429 }); })();
|
|
1757
|
+
function errorMessage(error) {
|
|
1758
|
+
return error instanceof Error ? error.message : String(error);
|
|
1759
|
+
}
|
|
1760
|
+
/** The title, question and confirm button text of the dialog that asks the user to do what the agent requests. */
|
|
1761
|
+
function agentConfirmText(kind, dashboardName, detail) {
|
|
1762
|
+
const name = `"${dashboardName}"`;
|
|
1763
|
+
switch (kind) {
|
|
1764
|
+
case 'save': {
|
|
1765
|
+
const rename = detail.name ? ` Its new name will be "${detail.name}".` : '';
|
|
1766
|
+
const describe = detail.description ? ` Its new description will be "${detail.description}".` : '';
|
|
1767
|
+
return { Title: 'Save dashboard?', Message: `The assistant asks to save ${name}.${rename}${describe}`, ConfirmText: 'Save' };
|
|
1768
|
+
}
|
|
1769
|
+
case 'pin':
|
|
1770
|
+
return { Title: 'Pin to Home?', Message: `The assistant asks to pin ${name} to your Home app.`, ConfirmText: 'Pin' };
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
/** The user's own views and the shared views the user may see. None when the user cannot read views. */
|
|
1774
|
+
function viewSources() {
|
|
1775
|
+
const engine = UserViewEngine.Instance;
|
|
1776
|
+
if (engine.IsPermissionConstrained)
|
|
1777
|
+
return [];
|
|
1778
|
+
const shared = engine.GetSharedViews().filter(view => view.UserCanView);
|
|
1779
|
+
return [...engine.GetViewsForCurrentUser(), ...shared].map(view => ({
|
|
1780
|
+
ID: view.ID,
|
|
1781
|
+
Name: view.Name,
|
|
1782
|
+
Description: view.Description,
|
|
1783
|
+
Entity: view.Entity,
|
|
1784
|
+
EntityID: view.EntityID,
|
|
1785
|
+
}));
|
|
1786
|
+
}
|
|
1787
|
+
/** The queries the user can run. None when the user cannot read queries. */
|
|
1788
|
+
function querySources(user) {
|
|
1789
|
+
const engine = QueryEngine.Instance;
|
|
1790
|
+
if (engine.IsPermissionConstrained)
|
|
1791
|
+
return [];
|
|
1792
|
+
return engine.Queries.filter(query => query.UserCanRun(user).canRun).map(query => ({
|
|
1793
|
+
ID: query.ID,
|
|
1794
|
+
Name: query.Name,
|
|
1795
|
+
Description: query.Description,
|
|
1796
|
+
Category: query.Category,
|
|
1797
|
+
}));
|
|
1798
|
+
}
|
|
1799
|
+
/** The entities the user can read. */
|
|
1800
|
+
function entitySources(provider) {
|
|
1801
|
+
const user = provider.CurrentUser;
|
|
1802
|
+
return provider.Entities.filter(entity => entity.GetUserPermisions(user).CanRead).map(entity => ({
|
|
1803
|
+
ID: entity.ID,
|
|
1804
|
+
Name: entity.Name,
|
|
1805
|
+
DisplayName: entity.DisplayName,
|
|
1806
|
+
Description: entity.Description,
|
|
1807
|
+
}));
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* The filter clause that keeps the artifacts whose name or description, in lower case, matches the query
|
|
1811
|
+
* as a LIKE pattern (see likeNarrowingPattern), or '' when the query is blank. The clause only narrows
|
|
1812
|
+
* the list: RankByQuery makes the exact match in the browser.
|
|
1813
|
+
*/
|
|
1814
|
+
function artifactQueryFilter(query) {
|
|
1815
|
+
const pattern = likeNarrowingPattern(query);
|
|
1816
|
+
return pattern ? ` AND (LOWER(Name) LIKE '%${pattern}%' OR LOWER(Description) LIKE '%${pattern}%')` : '';
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* The query without its outer spaces as a lower-case LIKE pattern that matches the query, and can match
|
|
1820
|
+
* more, in the same way on SQL Server and PostgreSQL. It needs no ESCAPE clause, which the server refuses
|
|
1821
|
+
* in a client's filter. Each quote, backslash and LIKE wildcard (`%`, `_`, `[`, `]`) becomes `_`, which
|
|
1822
|
+
* matches any one character. Each parenthesis becomes `_` too: the PostgreSQL provider rewrites date
|
|
1823
|
+
* functions such as `GETDATE()` in the whole filter, string literals included. Each other character that is
|
|
1824
|
+
* not ASCII becomes `_`, so the literal needs no `N` prefix, with two exceptions that become `%` (see
|
|
1825
|
+
* nonAsciiWildcard). The text is put in lower case last, when it holds only ASCII.
|
|
1826
|
+
*/
|
|
1827
|
+
function likeNarrowingPattern(query) {
|
|
1828
|
+
const ascii = query
|
|
1829
|
+
.trim()
|
|
1830
|
+
.replace(/[\\%_[\]'()]/g, '_')
|
|
1831
|
+
.replace(/[\u0080-\u{10FFFF}]/gu, char => nonAsciiWildcard(char));
|
|
1832
|
+
return EscapeSQLString(ascii.toLowerCase());
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* The LIKE wildcard for a character that is not ASCII: `_`, which matches one character, or `%` where one
|
|
1836
|
+
* character can be two on some database. SQL Server counts a character outside the BMP as two characters and
|
|
1837
|
+
* PostgreSQL as one. `İ` (U+0130) is one character, but ICU collations put it in lower case as `i` and a
|
|
1838
|
+
* combining dot.
|
|
1839
|
+
*/
|
|
1840
|
+
function nonAsciiWildcard(char) {
|
|
1841
|
+
return char.length > 1 || char === '\u0130' ? '%' : '_';
|
|
1842
|
+
}
|
|
1843
|
+
/** Records in `latest` the highest version number of each artifact in `rows`, by normalized artifact ID. */
|
|
1844
|
+
function addLatestVersions(latest, rows) {
|
|
1845
|
+
for (const row of rows) {
|
|
1846
|
+
const key = NormalizeUUID(row.ArtifactID);
|
|
1847
|
+
latest.set(key, Math.max(latest.get(key) ?? 0, row.VersionNumber));
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
/** The items in consecutive groups of at most `size`. */
|
|
1851
|
+
function chunked(items, size) {
|
|
1852
|
+
const groups = [];
|
|
1853
|
+
for (let start = 0; start < items.length; start += size) {
|
|
1854
|
+
groups.push(items.slice(start, start + size));
|
|
1855
|
+
}
|
|
1856
|
+
return groups;
|
|
1857
|
+
}
|
|
907
1858
|
//# sourceMappingURL=dashboard-resource.component.js.map
|