@memberjunction/ng-dashboards 5.40.2 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +38 -4
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -1
- package/dist/AI/components/analytics/ai-analytics-resource.component.js +148 -20
- package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-management-data.d.ts +216 -0
- package/dist/AI/components/analytics/realtime/realtime-management-data.d.ts.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-management-data.js +313 -0
- package/dist/AI/components/analytics/realtime/realtime-management-data.js.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-management.component.d.ts +103 -0
- package/dist/AI/components/analytics/realtime/realtime-management.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-management.component.js +1060 -0
- package/dist/AI/components/analytics/realtime/realtime-management.component.js.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-overview.component.d.ts +87 -0
- package/dist/AI/components/analytics/realtime/realtime-overview.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-overview.component.js +710 -0
- package/dist/AI/components/analytics/realtime/realtime-overview.component.js.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-session-data.d.ts +127 -0
- package/dist/AI/components/analytics/realtime/realtime-session-data.d.ts.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-session-data.js +233 -0
- package/dist/AI/components/analytics/realtime/realtime-session-data.js.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-sessions.component.d.ts +94 -0
- package/dist/AI/components/analytics/realtime/realtime-sessions.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/realtime/realtime-sessions.component.js +728 -0
- package/dist/AI/components/analytics/realtime/realtime-sessions.component.js.map +1 -0
- package/dist/AI/components/autotagging/tabs/sources-tab.component.js +2 -2
- package/dist/AI/components/autotagging/tabs/sources-tab.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +2 -2
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +2 -2
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/AI/index.d.ts +5 -0
- package/dist/AI/index.d.ts.map +1 -1
- package/dist/AI/index.js +7 -0
- package/dist/AI/index.js.map +1 -1
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +5 -5
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
- package/dist/MCP/mcp-dashboard.component.d.ts +16 -2
- package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
- package/dist/MCP/mcp-dashboard.component.js +33 -4
- package/dist/MCP/mcp-dashboard.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +22 -19
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +18 -0
- package/dist/ai-dashboards.module.js.map +1 -1
- package/package.json +55 -55
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Realtime Voice — analytics overview section.
|
|
3
|
+
*
|
|
4
|
+
* Operational analytics for voice-agent sessions, built from
|
|
5
|
+
* `MJ: AI Agent Sessions`, their `MJ: AI Agent Session Channels` rows, and the
|
|
6
|
+
* delegated `MJ: AI Agent Runs` linked via `AgentSessionID`. Renders the KPI
|
|
7
|
+
* row (active sessions, sessions in window, avg duration, delegated runs,
|
|
8
|
+
* janitor closes, cost), a sessions-over-time bar chart, the channel-usage
|
|
9
|
+
* donut, the top delegated target agents, and a recent-sessions list with a
|
|
10
|
+
* "View all" jump to the full sessions grid.
|
|
11
|
+
*/
|
|
12
|
+
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
|
|
13
|
+
import { CompositeKey } from '@memberjunction/core';
|
|
14
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
15
|
+
import { SharedService } from '@memberjunction/ng-shared';
|
|
16
|
+
import { LoadRealtimeSessionsDataset, FormatSessionDuration, FormatTokenCount, FormatSessionCost, ChannelIconClass, TimeRangeToMs, BuildSessionStatusDisplay } from './realtime-session-data';
|
|
17
|
+
import * as i0 from "@angular/core";
|
|
18
|
+
import * as i1 from "@memberjunction/ng-shared-generic";
|
|
19
|
+
import * as i2 from "../../widgets/kpi-card.component";
|
|
20
|
+
import * as i3 from "@memberjunction/ng-ui-components";
|
|
21
|
+
import * as i4 from "@angular/common";
|
|
22
|
+
const _forTrack0 = ($index, $item) => $item.title;
|
|
23
|
+
const _forTrack1 = ($index, $item) => $item.Name;
|
|
24
|
+
const _forTrack2 = ($index, $item) => $item.ID;
|
|
25
|
+
const _forTrack3 = ($index, $item) => $item.Label;
|
|
26
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
27
|
+
i0.ɵɵelementStart(0, "div", 0);
|
|
28
|
+
i0.ɵɵelement(1, "mj-loading", 1);
|
|
29
|
+
i0.ɵɵelementEnd();
|
|
30
|
+
} }
|
|
31
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
32
|
+
i0.ɵɵelement(0, "app-kpi-card", 3);
|
|
33
|
+
} if (rf & 2) {
|
|
34
|
+
const kpi_r2 = ctx.$implicit;
|
|
35
|
+
i0.ɵɵproperty("data", kpi_r2);
|
|
36
|
+
} }
|
|
37
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
38
|
+
i0.ɵɵelementStart(0, "div", 11);
|
|
39
|
+
i0.ɵɵtext(1, "No sessions in the selected period");
|
|
40
|
+
i0.ɵɵelementEnd();
|
|
41
|
+
} }
|
|
42
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_13_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
43
|
+
i0.ɵɵelementStart(0, "div", 24);
|
|
44
|
+
i0.ɵɵelement(1, "div", 25);
|
|
45
|
+
i0.ɵɵelementStart(2, "div", 26);
|
|
46
|
+
i0.ɵɵtext(3);
|
|
47
|
+
i0.ɵɵelementEnd()();
|
|
48
|
+
} if (rf & 2) {
|
|
49
|
+
const bucket_r3 = ctx.$implicit;
|
|
50
|
+
i0.ɵɵproperty("title", bucket_r3.Label + ": " + bucket_r3.Count + " session(s)");
|
|
51
|
+
i0.ɵɵadvance();
|
|
52
|
+
i0.ɵɵstyleProp("height", bucket_r3.HeightPercent, "%");
|
|
53
|
+
i0.ɵɵclassProp("bar--accent", bucket_r3.DelegatedHeavy);
|
|
54
|
+
i0.ɵɵadvance(2);
|
|
55
|
+
i0.ɵɵtextInterpolate(bucket_r3.Label);
|
|
56
|
+
} }
|
|
57
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
58
|
+
i0.ɵɵelementStart(0, "div", 12);
|
|
59
|
+
i0.ɵɵrepeaterCreate(1, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_13_For_2_Template, 4, 6, "div", 24, _forTrack3);
|
|
60
|
+
i0.ɵɵelementEnd();
|
|
61
|
+
} if (rf & 2) {
|
|
62
|
+
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
63
|
+
i0.ɵɵadvance();
|
|
64
|
+
i0.ɵɵrepeater(ctx_r3.TimeBuckets);
|
|
65
|
+
} }
|
|
66
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_22_Template(rf, ctx) { if (rf & 1) {
|
|
67
|
+
i0.ɵɵelementStart(0, "div", 11);
|
|
68
|
+
i0.ɵɵtext(1, "No channels attached in the selected period");
|
|
69
|
+
i0.ɵɵelementEnd();
|
|
70
|
+
} }
|
|
71
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_23_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
72
|
+
i0.ɵɵelementStart(0, "div", 29);
|
|
73
|
+
i0.ɵɵelement(1, "span", 30);
|
|
74
|
+
i0.ɵɵtext(2);
|
|
75
|
+
i0.ɵɵelementStart(3, "span", 31);
|
|
76
|
+
i0.ɵɵtext(4);
|
|
77
|
+
i0.ɵɵpipe(5, "number");
|
|
78
|
+
i0.ɵɵelementEnd()();
|
|
79
|
+
} if (rf & 2) {
|
|
80
|
+
const share_r5 = ctx.$implicit;
|
|
81
|
+
i0.ɵɵadvance();
|
|
82
|
+
i0.ɵɵstyleProp("background", share_r5.Color);
|
|
83
|
+
i0.ɵɵadvance();
|
|
84
|
+
i0.ɵɵtextInterpolate1(" ", share_r5.Name, " ");
|
|
85
|
+
i0.ɵɵadvance(2);
|
|
86
|
+
i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(5, 4, share_r5.Percent, "1.0-0"), "%");
|
|
87
|
+
} }
|
|
88
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_23_Template(rf, ctx) { if (rf & 1) {
|
|
89
|
+
i0.ɵɵelementStart(0, "div", 14);
|
|
90
|
+
i0.ɵɵelement(1, "div", 27);
|
|
91
|
+
i0.ɵɵelementStart(2, "div", 28);
|
|
92
|
+
i0.ɵɵrepeaterCreate(3, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_23_For_4_Template, 6, 7, "div", 29, _forTrack1);
|
|
93
|
+
i0.ɵɵelementEnd()();
|
|
94
|
+
} if (rf & 2) {
|
|
95
|
+
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
96
|
+
i0.ɵɵadvance();
|
|
97
|
+
i0.ɵɵstyleProp("background", ctx_r3.DonutGradient);
|
|
98
|
+
i0.ɵɵadvance(2);
|
|
99
|
+
i0.ɵɵrepeater(ctx_r3.ChannelShares);
|
|
100
|
+
} }
|
|
101
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_32_Template(rf, ctx) { if (rf & 1) {
|
|
102
|
+
i0.ɵɵelementStart(0, "div", 11);
|
|
103
|
+
i0.ɵɵtext(1, "No delegated runs in the selected period");
|
|
104
|
+
i0.ɵɵelementEnd();
|
|
105
|
+
} }
|
|
106
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_34_Template(rf, ctx) { if (rf & 1) {
|
|
107
|
+
i0.ɵɵelementStart(0, "div", 16)(1, "span", 32);
|
|
108
|
+
i0.ɵɵtext(2);
|
|
109
|
+
i0.ɵɵelementEnd();
|
|
110
|
+
i0.ɵɵelementStart(3, "span", 33);
|
|
111
|
+
i0.ɵɵelement(4, "span", 34);
|
|
112
|
+
i0.ɵɵelementEnd();
|
|
113
|
+
i0.ɵɵelementStart(5, "span", 35);
|
|
114
|
+
i0.ɵɵtext(6);
|
|
115
|
+
i0.ɵɵelementEnd()();
|
|
116
|
+
} if (rf & 2) {
|
|
117
|
+
const bar_r6 = ctx.$implicit;
|
|
118
|
+
i0.ɵɵadvance();
|
|
119
|
+
i0.ɵɵproperty("title", bar_r6.Name);
|
|
120
|
+
i0.ɵɵadvance();
|
|
121
|
+
i0.ɵɵtextInterpolate(bar_r6.Name);
|
|
122
|
+
i0.ɵɵadvance(2);
|
|
123
|
+
i0.ɵɵstyleProp("width", bar_r6.WidthPercent, "%");
|
|
124
|
+
i0.ɵɵadvance(2);
|
|
125
|
+
i0.ɵɵtextInterpolate(bar_r6.Count);
|
|
126
|
+
} }
|
|
127
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_63_Template(rf, ctx) { if (rf & 1) {
|
|
128
|
+
i0.ɵɵelementStart(0, "tr")(1, "td", 36);
|
|
129
|
+
i0.ɵɵtext(2, "No sessions in the selected period");
|
|
130
|
+
i0.ɵɵelementEnd()();
|
|
131
|
+
} }
|
|
132
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
133
|
+
i0.ɵɵelement(0, "span", 41);
|
|
134
|
+
} }
|
|
135
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
136
|
+
i0.ɵɵelement(0, "i");
|
|
137
|
+
} if (rf & 2) {
|
|
138
|
+
const row_r8 = i0.ɵɵnextContext().$implicit;
|
|
139
|
+
i0.ɵɵclassMap(row_r8.StatusIcon);
|
|
140
|
+
} }
|
|
141
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_For_16_Template(rf, ctx) { if (rf & 1) {
|
|
142
|
+
i0.ɵɵelement(0, "i");
|
|
143
|
+
} if (rf & 2) {
|
|
144
|
+
const icon_r9 = ctx.$implicit;
|
|
145
|
+
i0.ɵɵclassMap(icon_r9);
|
|
146
|
+
} }
|
|
147
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Template(rf, ctx) { if (rf & 1) {
|
|
148
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
149
|
+
i0.ɵɵelementStart(0, "tr", 37);
|
|
150
|
+
i0.ɵɵlistener("click", function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Template_tr_click_0_listener() { const row_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r3 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r3.OpenSession(row_r8.ID)); });
|
|
151
|
+
i0.ɵɵelementStart(1, "td")(2, "div", 38);
|
|
152
|
+
i0.ɵɵtext(3);
|
|
153
|
+
i0.ɵɵelementEnd();
|
|
154
|
+
i0.ɵɵelementStart(4, "div", 39);
|
|
155
|
+
i0.ɵɵtext(5);
|
|
156
|
+
i0.ɵɵelementEnd()();
|
|
157
|
+
i0.ɵɵelementStart(6, "td");
|
|
158
|
+
i0.ɵɵtext(7);
|
|
159
|
+
i0.ɵɵelementEnd();
|
|
160
|
+
i0.ɵɵelementStart(8, "td")(9, "span", 40);
|
|
161
|
+
i0.ɵɵconditionalCreate(10, AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Conditional_10_Template, 1, 0, "span", 41)(11, AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Conditional_11_Template, 1, 2, "i", 42);
|
|
162
|
+
i0.ɵɵtext(12);
|
|
163
|
+
i0.ɵɵelementEnd()();
|
|
164
|
+
i0.ɵɵelementStart(13, "td")(14, "span", 43);
|
|
165
|
+
i0.ɵɵrepeaterCreate(15, AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_For_16_Template, 1, 2, "i", 42, i0.ɵɵrepeaterTrackByIndex);
|
|
166
|
+
i0.ɵɵelementEnd()();
|
|
167
|
+
i0.ɵɵelementStart(17, "td", 44);
|
|
168
|
+
i0.ɵɵtext(18);
|
|
169
|
+
i0.ɵɵelementEnd();
|
|
170
|
+
i0.ɵɵelementStart(19, "td", 45);
|
|
171
|
+
i0.ɵɵtext(20);
|
|
172
|
+
i0.ɵɵelementEnd();
|
|
173
|
+
i0.ɵɵelementStart(21, "td", 45);
|
|
174
|
+
i0.ɵɵtext(22);
|
|
175
|
+
i0.ɵɵelementEnd();
|
|
176
|
+
i0.ɵɵelementStart(23, "td", 46)(24, "button", 47);
|
|
177
|
+
i0.ɵɵlistener("click", function AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Template_button_click_24_listener($event) { const row_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r3 = i0.ɵɵnextContext(2); ctx_r3.OpenSession(row_r8.ID); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
178
|
+
i0.ɵɵelement(25, "i", 48);
|
|
179
|
+
i0.ɵɵelementEnd()()();
|
|
180
|
+
} if (rf & 2) {
|
|
181
|
+
const row_r8 = ctx.$implicit;
|
|
182
|
+
i0.ɵɵadvance(3);
|
|
183
|
+
i0.ɵɵtextInterpolate(row_r8.Target);
|
|
184
|
+
i0.ɵɵadvance(2);
|
|
185
|
+
i0.ɵɵtextInterpolate1("via ", row_r8.ViaAgent);
|
|
186
|
+
i0.ɵɵadvance(2);
|
|
187
|
+
i0.ɵɵtextInterpolate(row_r8.User);
|
|
188
|
+
i0.ɵɵadvance(2);
|
|
189
|
+
i0.ɵɵclassMap(row_r8.StatusClass);
|
|
190
|
+
i0.ɵɵproperty("title", row_r8.StatusTitle);
|
|
191
|
+
i0.ɵɵadvance();
|
|
192
|
+
i0.ɵɵconditional(row_r8.Status === "Active" ? 10 : row_r8.StatusIcon ? 11 : -1);
|
|
193
|
+
i0.ɵɵadvance(2);
|
|
194
|
+
i0.ɵɵtextInterpolate1(" ", row_r8.StatusLabel, " ");
|
|
195
|
+
i0.ɵɵadvance(3);
|
|
196
|
+
i0.ɵɵrepeater(row_r8.ChannelIcons);
|
|
197
|
+
i0.ɵɵadvance(3);
|
|
198
|
+
i0.ɵɵtextInterpolate(row_r8.Runs);
|
|
199
|
+
i0.ɵɵadvance(2);
|
|
200
|
+
i0.ɵɵtextInterpolate(row_r8.Cost);
|
|
201
|
+
i0.ɵɵadvance(2);
|
|
202
|
+
i0.ɵɵtextInterpolate(row_r8.Duration);
|
|
203
|
+
} }
|
|
204
|
+
function AnalyticsRealtimeOverviewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
205
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
206
|
+
i0.ɵɵelementStart(0, "div", 2);
|
|
207
|
+
i0.ɵɵrepeaterCreate(1, AnalyticsRealtimeOverviewComponent_Conditional_1_For_2_Template, 1, 1, "app-kpi-card", 3, _forTrack0);
|
|
208
|
+
i0.ɵɵelementEnd();
|
|
209
|
+
i0.ɵɵelementStart(3, "div", 4)(4, "div", 5)(5, "div", 6)(6, "div", 7);
|
|
210
|
+
i0.ɵɵelement(7, "i", 8);
|
|
211
|
+
i0.ɵɵtext(8, " Sessions over time ");
|
|
212
|
+
i0.ɵɵelementEnd();
|
|
213
|
+
i0.ɵɵelementStart(9, "span", 9);
|
|
214
|
+
i0.ɵɵtext(10, "Accented bars = delegated-heavy");
|
|
215
|
+
i0.ɵɵelementEnd()();
|
|
216
|
+
i0.ɵɵelementStart(11, "div", 10);
|
|
217
|
+
i0.ɵɵconditionalCreate(12, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_12_Template, 2, 0, "div", 11)(13, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_13_Template, 3, 0, "div", 12);
|
|
218
|
+
i0.ɵɵelementEnd()();
|
|
219
|
+
i0.ɵɵelementStart(14, "div", 5)(15, "div", 6)(16, "div", 7);
|
|
220
|
+
i0.ɵɵelement(17, "i", 13);
|
|
221
|
+
i0.ɵɵtext(18, " Channel usage ");
|
|
222
|
+
i0.ɵɵelementEnd();
|
|
223
|
+
i0.ɵɵelementStart(19, "span", 9);
|
|
224
|
+
i0.ɵɵtext(20, "Share of attached session channels");
|
|
225
|
+
i0.ɵɵelementEnd()();
|
|
226
|
+
i0.ɵɵelementStart(21, "div", 10);
|
|
227
|
+
i0.ɵɵconditionalCreate(22, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_22_Template, 2, 0, "div", 11)(23, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_23_Template, 5, 2, "div", 14);
|
|
228
|
+
i0.ɵɵelementEnd()()();
|
|
229
|
+
i0.ɵɵelementStart(24, "div", 5)(25, "div", 6)(26, "div", 7);
|
|
230
|
+
i0.ɵɵelement(27, "i", 15);
|
|
231
|
+
i0.ɵɵtext(28, " Top delegated target agents ");
|
|
232
|
+
i0.ɵɵelementEnd();
|
|
233
|
+
i0.ɵɵelementStart(29, "span", 9);
|
|
234
|
+
i0.ɵɵtext(30, "Delegated agent runs by target \u00B7 selected period");
|
|
235
|
+
i0.ɵɵelementEnd()();
|
|
236
|
+
i0.ɵɵelementStart(31, "div", 10);
|
|
237
|
+
i0.ɵɵconditionalCreate(32, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_32_Template, 2, 0, "div", 11);
|
|
238
|
+
i0.ɵɵrepeaterCreate(33, AnalyticsRealtimeOverviewComponent_Conditional_1_For_34_Template, 7, 5, "div", 16, _forTrack1);
|
|
239
|
+
i0.ɵɵelementEnd()();
|
|
240
|
+
i0.ɵɵelementStart(35, "div", 5)(36, "div", 6)(37, "div", 7);
|
|
241
|
+
i0.ɵɵelement(38, "i", 17);
|
|
242
|
+
i0.ɵɵtext(39, " Recent sessions ");
|
|
243
|
+
i0.ɵɵelementEnd();
|
|
244
|
+
i0.ɵɵelementStart(40, "button", 18);
|
|
245
|
+
i0.ɵɵlistener("click", function AnalyticsRealtimeOverviewComponent_Conditional_1_Template_button_click_40_listener() { i0.ɵɵrestoreView(_r1); const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.SectionNavigate.emit("realtime-sessions")); });
|
|
246
|
+
i0.ɵɵtext(41, " View all ");
|
|
247
|
+
i0.ɵɵelement(42, "i", 19);
|
|
248
|
+
i0.ɵɵelementEnd()();
|
|
249
|
+
i0.ɵɵelementStart(43, "div", 20)(44, "table", 21)(45, "thead")(46, "tr")(47, "th");
|
|
250
|
+
i0.ɵɵtext(48, "Target");
|
|
251
|
+
i0.ɵɵelementEnd();
|
|
252
|
+
i0.ɵɵelementStart(49, "th");
|
|
253
|
+
i0.ɵɵtext(50, "User");
|
|
254
|
+
i0.ɵɵelementEnd();
|
|
255
|
+
i0.ɵɵelementStart(51, "th");
|
|
256
|
+
i0.ɵɵtext(52, "Status");
|
|
257
|
+
i0.ɵɵelementEnd();
|
|
258
|
+
i0.ɵɵelementStart(53, "th");
|
|
259
|
+
i0.ɵɵtext(54, "Channels");
|
|
260
|
+
i0.ɵɵelementEnd();
|
|
261
|
+
i0.ɵɵelementStart(55, "th", 22);
|
|
262
|
+
i0.ɵɵtext(56, "Runs");
|
|
263
|
+
i0.ɵɵelementEnd();
|
|
264
|
+
i0.ɵɵelementStart(57, "th", 22);
|
|
265
|
+
i0.ɵɵtext(58, "Cost");
|
|
266
|
+
i0.ɵɵelementEnd();
|
|
267
|
+
i0.ɵɵelementStart(59, "th", 22);
|
|
268
|
+
i0.ɵɵtext(60, "Duration");
|
|
269
|
+
i0.ɵɵelementEnd();
|
|
270
|
+
i0.ɵɵelement(61, "th");
|
|
271
|
+
i0.ɵɵelementEnd()();
|
|
272
|
+
i0.ɵɵelementStart(62, "tbody");
|
|
273
|
+
i0.ɵɵconditionalCreate(63, AnalyticsRealtimeOverviewComponent_Conditional_1_Conditional_63_Template, 3, 0, "tr");
|
|
274
|
+
i0.ɵɵrepeaterCreate(64, AnalyticsRealtimeOverviewComponent_Conditional_1_For_65_Template, 26, 11, "tr", 23, _forTrack2);
|
|
275
|
+
i0.ɵɵelementEnd()()()();
|
|
276
|
+
} if (rf & 2) {
|
|
277
|
+
const ctx_r3 = i0.ɵɵnextContext();
|
|
278
|
+
i0.ɵɵadvance();
|
|
279
|
+
i0.ɵɵrepeater(ctx_r3.KPICards);
|
|
280
|
+
i0.ɵɵadvance(11);
|
|
281
|
+
i0.ɵɵconditional(ctx_r3.TimeBuckets.length === 0 ? 12 : 13);
|
|
282
|
+
i0.ɵɵadvance(10);
|
|
283
|
+
i0.ɵɵconditional(ctx_r3.ChannelShares.length === 0 ? 22 : 23);
|
|
284
|
+
i0.ɵɵadvance(10);
|
|
285
|
+
i0.ɵɵconditional(ctx_r3.TopTargetAgents.length === 0 ? 32 : -1);
|
|
286
|
+
i0.ɵɵadvance();
|
|
287
|
+
i0.ɵɵrepeater(ctx_r3.TopTargetAgents);
|
|
288
|
+
i0.ɵɵadvance(30);
|
|
289
|
+
i0.ɵɵconditional(ctx_r3.RecentSessions.length === 0 ? 63 : -1);
|
|
290
|
+
i0.ɵɵadvance();
|
|
291
|
+
i0.ɵɵrepeater(ctx_r3.RecentSessions);
|
|
292
|
+
} }
|
|
293
|
+
const DONUT_COLORS = [
|
|
294
|
+
'var(--mj-brand-primary)',
|
|
295
|
+
'var(--mj-status-success)',
|
|
296
|
+
'var(--mj-brand-accent)',
|
|
297
|
+
'var(--mj-status-warning)',
|
|
298
|
+
'var(--mj-text-disabled)'
|
|
299
|
+
];
|
|
300
|
+
export class AnalyticsRealtimeOverviewComponent extends BaseAngularComponent {
|
|
301
|
+
_timeRange = '7d';
|
|
302
|
+
set TimeRange(value) {
|
|
303
|
+
const prev = this._timeRange;
|
|
304
|
+
this._timeRange = value;
|
|
305
|
+
if (prev !== value && this.initialized)
|
|
306
|
+
this.LoadData();
|
|
307
|
+
}
|
|
308
|
+
get TimeRange() { return this._timeRange; }
|
|
309
|
+
/** Lets the shell jump to the full sessions grid (mirrors Executive Summary's pattern). */
|
|
310
|
+
SectionNavigate = new EventEmitter();
|
|
311
|
+
initialized = false;
|
|
312
|
+
cdr = inject(ChangeDetectorRef);
|
|
313
|
+
dataset = null;
|
|
314
|
+
IsLoading = false;
|
|
315
|
+
KPICards = [];
|
|
316
|
+
TimeBuckets = [];
|
|
317
|
+
ChannelShares = [];
|
|
318
|
+
DonutGradient = '';
|
|
319
|
+
TopTargetAgents = [];
|
|
320
|
+
RecentSessions = [];
|
|
321
|
+
ngOnInit() {
|
|
322
|
+
this.initialized = true;
|
|
323
|
+
this.LoadData();
|
|
324
|
+
}
|
|
325
|
+
/** Reloads everything for the current time range. */
|
|
326
|
+
async LoadData() {
|
|
327
|
+
this.IsLoading = true;
|
|
328
|
+
this.cdr.detectChanges();
|
|
329
|
+
try {
|
|
330
|
+
this.dataset = await LoadRealtimeSessionsDataset(this.ProviderToUse, this.TimeRange);
|
|
331
|
+
this.buildKPICards();
|
|
332
|
+
this.buildTimeBuckets();
|
|
333
|
+
this.buildChannelShares();
|
|
334
|
+
this.buildTopTargetAgents();
|
|
335
|
+
this.buildRecentSessions();
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
console.error('Realtime Voice overview load error:', e);
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
this.IsLoading = false;
|
|
342
|
+
this.cdr.detectChanges();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Opens the AIAgentSession record using the shared Explorer record-open path. */
|
|
346
|
+
OpenSession(sessionId) {
|
|
347
|
+
const key = new CompositeKey();
|
|
348
|
+
key.LoadFromSingleKeyValuePair('ID', sessionId);
|
|
349
|
+
SharedService.Instance.OpenEntityRecord('MJ: AI Agent Sessions', key);
|
|
350
|
+
}
|
|
351
|
+
// ── Builders ──
|
|
352
|
+
get windowSessions() {
|
|
353
|
+
return this.dataset?.Sessions ?? [];
|
|
354
|
+
}
|
|
355
|
+
buildKPICards() {
|
|
356
|
+
const ds = this.dataset;
|
|
357
|
+
const sessions = this.windowSessions;
|
|
358
|
+
const activeCount = sessions.filter(s => s.Record.Status === 'Active').length;
|
|
359
|
+
const startedInWindow = sessions.filter(s => ds != null && s.StartedAt >= ds.WindowStart);
|
|
360
|
+
const priorCount = ds?.PriorWindowSessionCount ?? 0;
|
|
361
|
+
const durations = sessions.map(s => s.DurationMs).filter(d => d > 0);
|
|
362
|
+
const avgDuration = durations.length > 0
|
|
363
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
364
|
+
: 0;
|
|
365
|
+
const delegatedTotal = sessions.reduce((a, s) => a + s.DelegatedRunCount, 0);
|
|
366
|
+
const delegatedPerSession = sessions.length > 0 ? delegatedTotal / sessions.length : 0;
|
|
367
|
+
const janitorCloses = sessions.filter(s => s.IsJanitorClosed).length;
|
|
368
|
+
const totalCost = sessions.reduce((a, s) => a + s.TotalCost, 0);
|
|
369
|
+
const trendPct = priorCount > 0
|
|
370
|
+
? Math.round(((startedInWindow.length - priorCount) / priorCount) * 100)
|
|
371
|
+
: 0;
|
|
372
|
+
this.KPICards = [
|
|
373
|
+
{
|
|
374
|
+
title: 'Active sessions',
|
|
375
|
+
value: activeCount,
|
|
376
|
+
subtitle: 'live right now',
|
|
377
|
+
icon: 'fa-tower-broadcast',
|
|
378
|
+
color: 'success'
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
title: `Sessions (${this.TimeRange})`,
|
|
382
|
+
value: startedInWindow.length,
|
|
383
|
+
icon: 'fa-calendar-day',
|
|
384
|
+
color: 'primary',
|
|
385
|
+
trend: priorCount > 0 ? {
|
|
386
|
+
direction: trendPct > 0 ? 'up' : trendPct < 0 ? 'down' : 'stable',
|
|
387
|
+
percentage: Math.abs(trendPct),
|
|
388
|
+
period: `vs prior ${this.TimeRange}`
|
|
389
|
+
} : undefined,
|
|
390
|
+
subtitle: priorCount > 0 ? undefined : 'no prior-period data'
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
title: 'Avg duration',
|
|
394
|
+
value: FormatSessionDuration(avgDuration),
|
|
395
|
+
subtitle: 'min:sec per session',
|
|
396
|
+
icon: 'fa-stopwatch',
|
|
397
|
+
color: 'info'
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
title: 'Delegated runs',
|
|
401
|
+
value: delegatedTotal,
|
|
402
|
+
subtitle: `${delegatedPerSession.toFixed(1)} per session avg`,
|
|
403
|
+
icon: 'fa-diagram-project',
|
|
404
|
+
color: 'info'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
title: 'Janitor closes',
|
|
408
|
+
value: janitorCloses,
|
|
409
|
+
subtitle: `orphans reconciled (${this.TimeRange})`,
|
|
410
|
+
icon: 'fa-broom',
|
|
411
|
+
color: 'warning'
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
title: `Cost (${this.TimeRange})`,
|
|
415
|
+
value: FormatSessionCost(totalCost),
|
|
416
|
+
subtitle: 'co-agent + delegated',
|
|
417
|
+
icon: 'fa-coins',
|
|
418
|
+
color: 'danger'
|
|
419
|
+
}
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
buildTimeBuckets() {
|
|
423
|
+
const ds = this.dataset;
|
|
424
|
+
if (!ds) {
|
|
425
|
+
this.TimeBuckets = [];
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const hourly = this.TimeRange === '24h';
|
|
429
|
+
const bucketMs = hourly ? 3_600_000 : 86_400_000;
|
|
430
|
+
const bucketCount = Math.max(1, Math.round(ds.WindowMs / bucketMs));
|
|
431
|
+
const now = Date.now();
|
|
432
|
+
const buckets = [];
|
|
433
|
+
for (let i = bucketCount - 1; i >= 0; i--) {
|
|
434
|
+
buckets.push({ start: now - (i + 1) * bucketMs, count: 0, delegated: 0 });
|
|
435
|
+
}
|
|
436
|
+
for (const session of this.windowSessions) {
|
|
437
|
+
const t = session.StartedAt.getTime();
|
|
438
|
+
const idx = buckets.findIndex(b => t >= b.start && t < b.start + bucketMs);
|
|
439
|
+
if (idx >= 0) {
|
|
440
|
+
buckets[idx].count++;
|
|
441
|
+
buckets[idx].delegated += session.DelegatedRunCount;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const max = Math.max(1, ...buckets.map(b => b.count));
|
|
445
|
+
this.TimeBuckets = buckets.map((b, i) => {
|
|
446
|
+
const date = new Date(b.start + bucketMs);
|
|
447
|
+
const isLast = i === buckets.length - 1;
|
|
448
|
+
const label = hourly
|
|
449
|
+
? date.toLocaleTimeString([], { hour: 'numeric' }).toLowerCase()
|
|
450
|
+
: isLast ? 'Today' : date.toLocaleDateString([], { weekday: this.TimeRange === '7d' ? 'short' : undefined, month: this.TimeRange === '7d' ? undefined : 'numeric', day: this.TimeRange === '7d' ? undefined : 'numeric' });
|
|
451
|
+
return {
|
|
452
|
+
Label: label,
|
|
453
|
+
Count: b.count,
|
|
454
|
+
HeightPercent: Math.round((b.count / max) * 100),
|
|
455
|
+
DelegatedHeavy: b.count > 0 && b.delegated / b.count >= 2
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
buildChannelShares() {
|
|
460
|
+
const counts = new Map();
|
|
461
|
+
for (const session of this.windowSessions) {
|
|
462
|
+
for (const name of session.ChannelNames) {
|
|
463
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const total = Array.from(counts.values()).reduce((a, b) => a + b, 0);
|
|
467
|
+
if (total === 0) {
|
|
468
|
+
this.ChannelShares = [];
|
|
469
|
+
this.DonutGradient = '';
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const sorted = Array.from(counts.entries()).sort((a, b) => b[1] - a[1]);
|
|
473
|
+
this.ChannelShares = sorted.map(([name, count], i) => ({
|
|
474
|
+
Name: name,
|
|
475
|
+
Count: count,
|
|
476
|
+
Percent: (count / total) * 100,
|
|
477
|
+
Color: DONUT_COLORS[i % DONUT_COLORS.length]
|
|
478
|
+
}));
|
|
479
|
+
let cursor = 0;
|
|
480
|
+
const stops = [];
|
|
481
|
+
for (const share of this.ChannelShares) {
|
|
482
|
+
const end = cursor + share.Percent;
|
|
483
|
+
stops.push(`${share.Color} ${cursor.toFixed(2)}% ${end.toFixed(2)}%`);
|
|
484
|
+
cursor = end;
|
|
485
|
+
}
|
|
486
|
+
this.DonutGradient = `conic-gradient(${stops.join(', ')})`;
|
|
487
|
+
}
|
|
488
|
+
buildTopTargetAgents() {
|
|
489
|
+
const counts = new Map();
|
|
490
|
+
for (const session of this.windowSessions) {
|
|
491
|
+
if (session.DelegatedRunCount === 0)
|
|
492
|
+
continue;
|
|
493
|
+
const name = session.TargetAgentName ?? session.TargetAgentID ?? 'Unknown';
|
|
494
|
+
counts.set(name, (counts.get(name) ?? 0) + session.DelegatedRunCount);
|
|
495
|
+
}
|
|
496
|
+
const sorted = Array.from(counts.entries())
|
|
497
|
+
.sort((a, b) => b[1] - a[1])
|
|
498
|
+
.slice(0, 5);
|
|
499
|
+
const max = Math.max(1, ...sorted.map(([, c]) => c));
|
|
500
|
+
this.TopTargetAgents = sorted.map(([name, count]) => ({
|
|
501
|
+
Name: name,
|
|
502
|
+
Count: count,
|
|
503
|
+
WidthPercent: Math.round((count / max) * 100)
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
buildRecentSessions() {
|
|
507
|
+
this.RecentSessions = [...this.windowSessions]
|
|
508
|
+
.sort((a, b) => b.StartedAt.getTime() - a.StartedAt.getTime())
|
|
509
|
+
.slice(0, 8)
|
|
510
|
+
.map(s => {
|
|
511
|
+
const statusDisplay = BuildSessionStatusDisplay(s.Record.Status, s.CloseReason);
|
|
512
|
+
return {
|
|
513
|
+
ID: s.Record.ID,
|
|
514
|
+
Target: s.TargetAgentName ?? s.Record.Agent ?? 'Unknown',
|
|
515
|
+
ViaAgent: s.Record.Agent ?? 'Unknown',
|
|
516
|
+
User: s.Record.User,
|
|
517
|
+
Status: s.Record.Status,
|
|
518
|
+
StatusClass: 'status-pill status-' + s.Record.Status.toLowerCase(),
|
|
519
|
+
StatusLabel: statusDisplay.Label,
|
|
520
|
+
StatusTitle: statusDisplay.Title,
|
|
521
|
+
StatusIcon: statusDisplay.Icon,
|
|
522
|
+
ChannelIcons: s.ChannelNames.map(ChannelIconClass),
|
|
523
|
+
Runs: s.DelegatedRunCount,
|
|
524
|
+
Cost: FormatSessionCost(s.TotalCost),
|
|
525
|
+
Duration: FormatSessionDuration(s.DurationMs)
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/** Exposed for testability / template type narrowing. */
|
|
530
|
+
FormatTokens(value) {
|
|
531
|
+
return FormatTokenCount(value);
|
|
532
|
+
}
|
|
533
|
+
/** Window length in ms for the active range (exposed for tests). */
|
|
534
|
+
get WindowMs() {
|
|
535
|
+
return TimeRangeToMs(this.TimeRange);
|
|
536
|
+
}
|
|
537
|
+
static ɵfac = /*@__PURE__*/ (() => { let ɵAnalyticsRealtimeOverviewComponent_BaseFactory; return function AnalyticsRealtimeOverviewComponent_Factory(__ngFactoryType__) { return (ɵAnalyticsRealtimeOverviewComponent_BaseFactory || (ɵAnalyticsRealtimeOverviewComponent_BaseFactory = i0.ɵɵgetInheritedFactory(AnalyticsRealtimeOverviewComponent)))(__ngFactoryType__ || AnalyticsRealtimeOverviewComponent); }; })();
|
|
538
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsRealtimeOverviewComponent, selectors: [["app-analytics-realtime-overview"]], inputs: { TimeRange: "TimeRange" }, outputs: { SectionNavigate: "SectionNavigate" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "loading-container"], ["text", "Loading realtime voice analytics..."], [1, "kpi-grid"], [3, "data"], [1, "charts-2"], [1, "panel"], [1, "panel-header"], [1, "panel-header__title"], [1, "fa-solid", "fa-chart-column", "panel-header__icon"], [1, "panel-header__subtitle"], [1, "panel-body"], [1, "panel-empty"], [1, "bars"], [1, "fa-solid", "fa-chart-pie", "panel-header__icon"], [1, "donut-row"], [1, "fa-solid", "fa-diagram-project", "panel-header__icon"], [1, "hbar-row"], [1, "fa-solid", "fa-tower-broadcast", "panel-header__icon"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-arrow-right"], [1, "table-wrapper"], [1, "data-table"], [1, "th-numeric"], [1, "session-row"], [1, "bcol", 3, "title"], [1, "bar"], [1, "blabel"], [1, "donut"], [1, "legend"], [1, "legend-item"], [1, "legend-dot"], [1, "legend-pct"], [1, "hbar-name", 3, "title"], [1, "hbar-track"], [1, "hbar-fill"], [1, "hbar-val"], ["colspan", "8", 1, "empty-row"], [1, "session-row", 3, "click"], [1, "cell-strong"], [1, "cell-sub"], [1, "status-pill", 3, "title"], [1, "dot", "dot--live"], [3, "class"], [1, "chan-icons"], [1, "cell-numeric"], [1, "cell-numeric", "mono"], [1, "cell-action"], ["mjButton", "", "variant", "icon", "size", "sm", "title", "Open session record", 3, "click"], [1, "fa-solid", "fa-arrow-up-right-from-square"]], template: function AnalyticsRealtimeOverviewComponent_Template(rf, ctx) { if (rf & 1) {
|
|
539
|
+
i0.ɵɵconditionalCreate(0, AnalyticsRealtimeOverviewComponent_Conditional_0_Template, 2, 0, "div", 0)(1, AnalyticsRealtimeOverviewComponent_Conditional_1_Template, 66, 4);
|
|
540
|
+
} if (rf & 2) {
|
|
541
|
+
i0.ɵɵconditional(ctx.IsLoading ? 0 : 1);
|
|
542
|
+
} }, dependencies: [i1.LoadingComponent, i2.KPICardComponent, i3.MJButtonDirective, i4.DecimalPipe], styles: ["[_nghost-%COMP%] { display: block; }\n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n \n\n .kpi-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin-bottom: 16px;\n }\n\n \n\n .charts-2[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1.4fr 1fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n @media (max-width: 1000px) {\n .charts-2[_ngcontent-%COMP%] { grid-template-columns: 1fr; }\n }\n\n \n\n .panel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n margin-bottom: 16px;\n overflow: hidden;\n }\n\n .panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .panel-header__subtitle[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n .panel-body[_ngcontent-%COMP%] { padding: 16px 18px; }\n\n .panel-empty[_ngcontent-%COMP%] {\n text-align: center;\n padding: 24px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n }\n\n \n\n .bars[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n height: 160px;\n }\n\n .bcol[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n min-width: 0;\n }\n\n .bar[_ngcontent-%COMP%] {\n width: 100%;\n max-width: 42px;\n min-height: 2px;\n border-radius: 6px 6px 2px 2px;\n background: linear-gradient(180deg,\n color-mix(in srgb, var(--mj-brand-primary) 85%, transparent),\n color-mix(in srgb, var(--mj-brand-primary) 55%, transparent));\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .bar--accent[_ngcontent-%COMP%] {\n background: linear-gradient(180deg,\n color-mix(in srgb, var(--mj-status-success) 85%, transparent),\n color-mix(in srgb, var(--mj-status-success) 55%, transparent));\n }\n\n .blabel[_ngcontent-%COMP%] {\n margin-top: 6px;\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n }\n\n \n\n .donut-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 22px;\n flex-wrap: wrap;\n }\n\n .donut[_ngcontent-%COMP%] {\n width: 140px;\n height: 140px;\n border-radius: 50%;\n flex-shrink: 0;\n -webkit-mask: radial-gradient(circle at center, transparent 42px, #000 43px);\n mask: radial-gradient(circle at center, transparent 42px, #000 43px);\n }\n\n .legend[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 160px;\n flex: 1;\n }\n\n .legend-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12.5px;\n color: var(--mj-text-secondary);\n }\n\n .legend-dot[_ngcontent-%COMP%] {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .legend-pct[_ngcontent-%COMP%] {\n margin-left: auto;\n color: var(--mj-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n \n\n .hbar-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 10px;\n }\n\n .hbar-row[_ngcontent-%COMP%]:last-child { margin-bottom: 0; }\n\n .hbar-name[_ngcontent-%COMP%] {\n width: 140px;\n min-width: 140px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .hbar-track[_ngcontent-%COMP%] {\n flex: 1;\n height: 18px;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n }\n\n .hbar-fill[_ngcontent-%COMP%] {\n display: block;\n height: 100%;\n border-radius: 6px;\n background: linear-gradient(90deg,\n var(--mj-brand-primary),\n color-mix(in srgb, var(--mj-brand-primary) 65%, var(--mj-bg-surface)));\n transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .hbar-val[_ngcontent-%COMP%] {\n width: 48px;\n min-width: 48px;\n text-align: right;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n font-variant-numeric: tabular-nums;\n }\n\n \n\n .table-wrapper[_ngcontent-%COMP%] { overflow-x: auto; }\n\n .data-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .data-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n }\n\n .th-numeric[_ngcontent-%COMP%] { text-align: right; }\n\n .session-row[_ngcontent-%COMP%] { cursor: pointer; transition: background 0.15s; }\n .session-row[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-hover); }\n\n .cell-strong[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .cell-sub[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n }\n\n .cell-numeric[_ngcontent-%COMP%] {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-action[_ngcontent-%COMP%] { text-align: right; white-space: nowrap; }\n\n .mono[_ngcontent-%COMP%] {\n font-family: var(--mj-font-mono, monospace);\n font-size: 12px;\n }\n\n .empty-row[_ngcontent-%COMP%] {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n \n\n .status-pill[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.3px;\n white-space: nowrap;\n }\n\n .status-active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .status-idle[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n }\n\n .status-closed[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-disabled) 12%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n }\n\n .dot--live[_ngcontent-%COMP%] {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-status-success);\n animation: _ngcontent-%COMP%_pulse-live 1.6s ease-in-out infinite;\n }\n\n @keyframes _ngcontent-%COMP%_pulse-live {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.35; }\n }\n\n .chan-icons[_ngcontent-%COMP%] {\n display: inline-flex;\n gap: 8px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n }"] });
|
|
543
|
+
}
|
|
544
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsRealtimeOverviewComponent, [{
|
|
545
|
+
type: Component,
|
|
546
|
+
args: [{ standalone: false, selector: 'app-analytics-realtime-overview', template: `
|
|
547
|
+
@if (IsLoading) {
|
|
548
|
+
<div class="loading-container">
|
|
549
|
+
<mj-loading text="Loading realtime voice analytics..."></mj-loading>
|
|
550
|
+
</div>
|
|
551
|
+
} @else {
|
|
552
|
+
<!-- KPI Row -->
|
|
553
|
+
<div class="kpi-grid">
|
|
554
|
+
@for (kpi of KPICards; track kpi.title) {
|
|
555
|
+
<app-kpi-card [data]="kpi"></app-kpi-card>
|
|
556
|
+
}
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<!-- Charts row 1: sessions over time + channel usage -->
|
|
560
|
+
<div class="charts-2">
|
|
561
|
+
<div class="panel">
|
|
562
|
+
<div class="panel-header">
|
|
563
|
+
<div class="panel-header__title">
|
|
564
|
+
<i class="fa-solid fa-chart-column panel-header__icon"></i>
|
|
565
|
+
Sessions over time
|
|
566
|
+
</div>
|
|
567
|
+
<span class="panel-header__subtitle">Accented bars = delegated-heavy</span>
|
|
568
|
+
</div>
|
|
569
|
+
<div class="panel-body">
|
|
570
|
+
@if (TimeBuckets.length === 0) {
|
|
571
|
+
<div class="panel-empty">No sessions in the selected period</div>
|
|
572
|
+
} @else {
|
|
573
|
+
<div class="bars">
|
|
574
|
+
@for (bucket of TimeBuckets; track bucket.Label) {
|
|
575
|
+
<div class="bcol" [title]="bucket.Label + ': ' + bucket.Count + ' session(s)'">
|
|
576
|
+
<div class="bar"
|
|
577
|
+
[class.bar--accent]="bucket.DelegatedHeavy"
|
|
578
|
+
[style.height.%]="bucket.HeightPercent"></div>
|
|
579
|
+
<div class="blabel">{{ bucket.Label }}</div>
|
|
580
|
+
</div>
|
|
581
|
+
}
|
|
582
|
+
</div>
|
|
583
|
+
}
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
<div class="panel">
|
|
587
|
+
<div class="panel-header">
|
|
588
|
+
<div class="panel-header__title">
|
|
589
|
+
<i class="fa-solid fa-chart-pie panel-header__icon"></i>
|
|
590
|
+
Channel usage
|
|
591
|
+
</div>
|
|
592
|
+
<span class="panel-header__subtitle">Share of attached session channels</span>
|
|
593
|
+
</div>
|
|
594
|
+
<div class="panel-body">
|
|
595
|
+
@if (ChannelShares.length === 0) {
|
|
596
|
+
<div class="panel-empty">No channels attached in the selected period</div>
|
|
597
|
+
} @else {
|
|
598
|
+
<div class="donut-row">
|
|
599
|
+
<div class="donut" [style.background]="DonutGradient"></div>
|
|
600
|
+
<div class="legend">
|
|
601
|
+
@for (share of ChannelShares; track share.Name) {
|
|
602
|
+
<div class="legend-item">
|
|
603
|
+
<span class="legend-dot" [style.background]="share.Color"></span>
|
|
604
|
+
{{ share.Name }}
|
|
605
|
+
<span class="legend-pct">{{ share.Percent | number:'1.0-0' }}%</span>
|
|
606
|
+
</div>
|
|
607
|
+
}
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
}
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
<!-- Charts row 2: top delegated target agents -->
|
|
616
|
+
<div class="panel">
|
|
617
|
+
<div class="panel-header">
|
|
618
|
+
<div class="panel-header__title">
|
|
619
|
+
<i class="fa-solid fa-diagram-project panel-header__icon"></i>
|
|
620
|
+
Top delegated target agents
|
|
621
|
+
</div>
|
|
622
|
+
<span class="panel-header__subtitle">Delegated agent runs by target · selected period</span>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="panel-body">
|
|
625
|
+
@if (TopTargetAgents.length === 0) {
|
|
626
|
+
<div class="panel-empty">No delegated runs in the selected period</div>
|
|
627
|
+
}
|
|
628
|
+
@for (bar of TopTargetAgents; track bar.Name) {
|
|
629
|
+
<div class="hbar-row">
|
|
630
|
+
<span class="hbar-name" [title]="bar.Name">{{ bar.Name }}</span>
|
|
631
|
+
<span class="hbar-track">
|
|
632
|
+
<span class="hbar-fill" [style.width.%]="bar.WidthPercent"></span>
|
|
633
|
+
</span>
|
|
634
|
+
<span class="hbar-val">{{ bar.Count }}</span>
|
|
635
|
+
</div>
|
|
636
|
+
}
|
|
637
|
+
</div>
|
|
638
|
+
</div>
|
|
639
|
+
|
|
640
|
+
<!-- Recent sessions -->
|
|
641
|
+
<div class="panel">
|
|
642
|
+
<div class="panel-header">
|
|
643
|
+
<div class="panel-header__title">
|
|
644
|
+
<i class="fa-solid fa-tower-broadcast panel-header__icon"></i>
|
|
645
|
+
Recent sessions
|
|
646
|
+
</div>
|
|
647
|
+
<button mjButton variant="secondary" size="sm" (click)="SectionNavigate.emit('realtime-sessions')">
|
|
648
|
+
View all <i class="fa-solid fa-arrow-right"></i>
|
|
649
|
+
</button>
|
|
650
|
+
</div>
|
|
651
|
+
<div class="table-wrapper">
|
|
652
|
+
<table class="data-table">
|
|
653
|
+
<thead>
|
|
654
|
+
<tr>
|
|
655
|
+
<th>Target</th><th>User</th><th>Status</th><th>Channels</th>
|
|
656
|
+
<th class="th-numeric">Runs</th><th class="th-numeric">Cost</th>
|
|
657
|
+
<th class="th-numeric">Duration</th><th></th>
|
|
658
|
+
</tr>
|
|
659
|
+
</thead>
|
|
660
|
+
<tbody>
|
|
661
|
+
@if (RecentSessions.length === 0) {
|
|
662
|
+
<tr><td colspan="8" class="empty-row">No sessions in the selected period</td></tr>
|
|
663
|
+
}
|
|
664
|
+
@for (row of RecentSessions; track row.ID) {
|
|
665
|
+
<tr class="session-row" (click)="OpenSession(row.ID)">
|
|
666
|
+
<td>
|
|
667
|
+
<div class="cell-strong">{{ row.Target }}</div>
|
|
668
|
+
<div class="cell-sub">via {{ row.ViaAgent }}</div>
|
|
669
|
+
</td>
|
|
670
|
+
<td>{{ row.User }}</td>
|
|
671
|
+
<td>
|
|
672
|
+
<span class="status-pill" [class]="row.StatusClass"
|
|
673
|
+
[title]="row.StatusTitle">
|
|
674
|
+
@if (row.Status === 'Active') { <span class="dot dot--live"></span> }
|
|
675
|
+
@else if (row.StatusIcon) { <i [class]="row.StatusIcon"></i> }
|
|
676
|
+
{{ row.StatusLabel }}
|
|
677
|
+
</span>
|
|
678
|
+
</td>
|
|
679
|
+
<td>
|
|
680
|
+
<span class="chan-icons">
|
|
681
|
+
@for (icon of row.ChannelIcons; track $index) {
|
|
682
|
+
<i [class]="icon"></i>
|
|
683
|
+
}
|
|
684
|
+
</span>
|
|
685
|
+
</td>
|
|
686
|
+
<td class="cell-numeric">{{ row.Runs }}</td>
|
|
687
|
+
<td class="cell-numeric mono">{{ row.Cost }}</td>
|
|
688
|
+
<td class="cell-numeric mono">{{ row.Duration }}</td>
|
|
689
|
+
<td class="cell-action">
|
|
690
|
+
<button mjButton variant="icon" size="sm" title="Open session record"
|
|
691
|
+
(click)="OpenSession(row.ID); $event.stopPropagation()">
|
|
692
|
+
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
|
693
|
+
</button>
|
|
694
|
+
</td>
|
|
695
|
+
</tr>
|
|
696
|
+
}
|
|
697
|
+
</tbody>
|
|
698
|
+
</table>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
}
|
|
702
|
+
`, styles: ["\n :host { display: block; }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n /* \u2500\u2500 KPI Grid \u2500\u2500 */\n .kpi-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin-bottom: 16px;\n }\n\n /* \u2500\u2500 Layout \u2500\u2500 */\n .charts-2 {\n display: grid;\n grid-template-columns: 1.4fr 1fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n @media (max-width: 1000px) {\n .charts-2 { grid-template-columns: 1fr; }\n }\n\n /* \u2500\u2500 Panel (matches sibling analytics sections) \u2500\u2500 */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n margin-bottom: 16px;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .panel-header__subtitle {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n .panel-body { padding: 16px 18px; }\n\n .panel-empty {\n text-align: center;\n padding: 24px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n }\n\n /* \u2500\u2500 Vertical bars \u2500\u2500 */\n .bars {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n height: 160px;\n }\n\n .bcol {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n min-width: 0;\n }\n\n .bar {\n width: 100%;\n max-width: 42px;\n min-height: 2px;\n border-radius: 6px 6px 2px 2px;\n background: linear-gradient(180deg,\n color-mix(in srgb, var(--mj-brand-primary) 85%, transparent),\n color-mix(in srgb, var(--mj-brand-primary) 55%, transparent));\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .bar--accent {\n background: linear-gradient(180deg,\n color-mix(in srgb, var(--mj-status-success) 85%, transparent),\n color-mix(in srgb, var(--mj-status-success) 55%, transparent));\n }\n\n .blabel {\n margin-top: 6px;\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n }\n\n /* \u2500\u2500 Donut \u2500\u2500 */\n .donut-row {\n display: flex;\n align-items: center;\n gap: 22px;\n flex-wrap: wrap;\n }\n\n .donut {\n width: 140px;\n height: 140px;\n border-radius: 50%;\n flex-shrink: 0;\n -webkit-mask: radial-gradient(circle at center, transparent 42px, #000 43px);\n mask: radial-gradient(circle at center, transparent 42px, #000 43px);\n }\n\n .legend {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 160px;\n flex: 1;\n }\n\n .legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12.5px;\n color: var(--mj-text-secondary);\n }\n\n .legend-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .legend-pct {\n margin-left: auto;\n color: var(--mj-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n /* \u2500\u2500 Horizontal bars \u2500\u2500 */\n .hbar-row {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 10px;\n }\n\n .hbar-row:last-child { margin-bottom: 0; }\n\n .hbar-name {\n width: 140px;\n min-width: 140px;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .hbar-track {\n flex: 1;\n height: 18px;\n border-radius: 6px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n }\n\n .hbar-fill {\n display: block;\n height: 100%;\n border-radius: 6px;\n background: linear-gradient(90deg,\n var(--mj-brand-primary),\n color-mix(in srgb, var(--mj-brand-primary) 65%, var(--mj-bg-surface)));\n transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .hbar-val {\n width: 48px;\n min-width: 48px;\n text-align: right;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n font-variant-numeric: tabular-nums;\n }\n\n /* \u2500\u2500 Table \u2500\u2500 */\n .table-wrapper { overflow-x: auto; }\n\n .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table th,\n .data-table td {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-table th {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n }\n\n .th-numeric { text-align: right; }\n\n .session-row { cursor: pointer; transition: background 0.15s; }\n .session-row:hover { background: var(--mj-bg-surface-hover); }\n\n .cell-strong {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .cell-sub {\n font-size: 11px;\n color: var(--mj-text-muted);\n }\n\n .cell-numeric {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-action { text-align: right; white-space: nowrap; }\n\n .mono {\n font-family: var(--mj-font-mono, monospace);\n font-size: 12px;\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n /* \u2500\u2500 Status pills \u2500\u2500 */\n .status-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.3px;\n white-space: nowrap;\n }\n\n .status-active {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .status-idle {\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n }\n\n .status-closed {\n background: color-mix(in srgb, var(--mj-text-disabled) 12%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n }\n\n .dot--live {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-status-success);\n animation: pulse-live 1.6s ease-in-out infinite;\n }\n\n @keyframes pulse-live {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.35; }\n }\n\n .chan-icons {\n display: inline-flex;\n gap: 8px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n }\n "] }]
|
|
703
|
+
}], null, { TimeRange: [{
|
|
704
|
+
type: Input
|
|
705
|
+
}], SectionNavigate: [{
|
|
706
|
+
type: Output
|
|
707
|
+
}] }); })();
|
|
708
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsRealtimeOverviewComponent, { className: "AnalyticsRealtimeOverviewComponent", filePath: "src/AI/components/analytics/realtime/realtime-overview.component.ts", lineNumber: 560 }); })();
|
|
709
|
+
export function LoadAnalyticsRealtimeOverview() { }
|
|
710
|
+
//# sourceMappingURL=realtime-overview.component.js.map
|