@memberjunction/ng-dashboards 6.2.0-edge.2 → 6.2.0-edge.3
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/ComponentStudio/components/form-override-dialog.component.d.ts +12 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.js +128 -81
- package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +4 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +47 -13
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-agent-context.d.ts +107 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js +216 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +57 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +805 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.d.ts +37 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js +210 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts +70 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js +496 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts +22 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js +99 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts +29 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js +278 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts +59 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js +359 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts +22 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js +44 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts +67 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.js +119 -0
- package/dist/WorkQueue/work-queue-agent-context.js.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts +94 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js +545 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js.map +1 -0
- package/dist/WorkQueue/work-queue-types.d.ts +40 -0
- package/dist/WorkQueue/work-queue-types.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-types.js +2 -0
- package/dist/WorkQueue/work-queue-types.js.map +1 -0
- package/dist/core-dashboards.module.d.ts +29 -28
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +7 -0
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/module.d.ts +14 -13
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +7 -0
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -0
- package/dist/public-api.js.map +1 -1
- package/dist/work-queue-dashboards.module.d.ts +23 -0
- package/dist/work-queue-dashboards.module.d.ts.map +1 -0
- package/dist/work-queue-dashboards.module.js +92 -0
- package/dist/work-queue-dashboards.module.js.map +1 -0
- package/package.json +72 -66
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type AgentToolResult } from '../shared/agent-tool-validation';
|
|
2
|
+
export type TimeRangeFilter = '24h' | '7d' | '30d' | 'all';
|
|
3
|
+
export type ChannelFilter = 'all' | 'Phone' | 'Web' | 'Meeting';
|
|
4
|
+
export declare const VALID_TIME_RANGES: readonly TimeRangeFilter[];
|
|
5
|
+
export declare const VALID_CHANNELS: readonly ChannelFilter[];
|
|
6
|
+
export interface ChannelMetrics {
|
|
7
|
+
Channel: 'Phone' | 'Web' | 'Meeting';
|
|
8
|
+
Count: number;
|
|
9
|
+
Percentage: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DirectionMetrics {
|
|
12
|
+
Direction: 'Inbound' | 'Outbound' | 'Internal';
|
|
13
|
+
Count: number;
|
|
14
|
+
Percentage: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CostByDay {
|
|
17
|
+
Date: string;
|
|
18
|
+
Cost: number;
|
|
19
|
+
Count: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CostByNumber {
|
|
22
|
+
PhoneNumberID: string | null;
|
|
23
|
+
PhoneNumber: string;
|
|
24
|
+
Cost: number;
|
|
25
|
+
Count: number;
|
|
26
|
+
}
|
|
27
|
+
export interface LiveInteractionSummary {
|
|
28
|
+
ID: string;
|
|
29
|
+
Channel: 'Phone' | 'Web' | 'Meeting';
|
|
30
|
+
Direction: 'Inbound' | 'Outbound' | 'Internal';
|
|
31
|
+
Status: string;
|
|
32
|
+
RemoteAddress: string | null;
|
|
33
|
+
StartedAt: Date;
|
|
34
|
+
ElapsedSeconds: number;
|
|
35
|
+
ElapsedLabel: string;
|
|
36
|
+
}
|
|
37
|
+
export interface OperationsMetrics {
|
|
38
|
+
TotalVolume: number;
|
|
39
|
+
LiveCallsCount: number;
|
|
40
|
+
AnsweredCount: number;
|
|
41
|
+
AnswerRate: number;
|
|
42
|
+
AbandonedCount: number;
|
|
43
|
+
AbandonmentRate: number;
|
|
44
|
+
AverageHandleTimeSeconds: number;
|
|
45
|
+
AverageHandleTimeFormatted: string;
|
|
46
|
+
AverageSpeedToAnswerSeconds: number;
|
|
47
|
+
AverageSpeedToAnswerFormatted: string;
|
|
48
|
+
TransferCount: number;
|
|
49
|
+
EscalationCount: number;
|
|
50
|
+
TotalCost: number;
|
|
51
|
+
TotalCostFormatted: string;
|
|
52
|
+
VolumeByChannel: ChannelMetrics[];
|
|
53
|
+
VolumeByDirection: DirectionMetrics[];
|
|
54
|
+
CostByDay: CostByDay[];
|
|
55
|
+
CostByNumber: CostByNumber[];
|
|
56
|
+
LiveInteractions: LiveInteractionSummary[];
|
|
57
|
+
}
|
|
58
|
+
export interface InteractionRecordLike {
|
|
59
|
+
ID: string;
|
|
60
|
+
Channel: 'Meeting' | 'Phone' | 'Web' | string;
|
|
61
|
+
Direction: 'Inbound' | 'Internal' | 'Outbound' | string;
|
|
62
|
+
Status: 'Abandoned' | 'Active' | 'Ended' | 'Failed' | 'Queued' | string;
|
|
63
|
+
AgentSessionID: string | null;
|
|
64
|
+
RoomName: string | null;
|
|
65
|
+
PhoneNumberID: string | null;
|
|
66
|
+
RemoteAddress: string | null;
|
|
67
|
+
StartedAt: Date | string;
|
|
68
|
+
AnsweredAt: Date | string | null;
|
|
69
|
+
EndedAt: Date | string | null;
|
|
70
|
+
EndReason: string | null;
|
|
71
|
+
RecordingEnabled: boolean;
|
|
72
|
+
CostEstimate: number | null;
|
|
73
|
+
}
|
|
74
|
+
export interface InteractionEventRecordLike {
|
|
75
|
+
ID: string;
|
|
76
|
+
InteractionID: string;
|
|
77
|
+
EventType: 'Abandoned' | 'Accepted' | 'Answered' | 'Created' | 'Declined' | 'Ended' | 'Escalated' | 'Held' | 'Offered' | 'Queued' | 'RecordingStarted' | 'RecordingStopped' | 'Resumed' | 'Transferred' | string;
|
|
78
|
+
OccurredAt: Date | string;
|
|
79
|
+
ActorUserID: string | null;
|
|
80
|
+
ActorAgentID: string | null;
|
|
81
|
+
Details: string | null;
|
|
82
|
+
}
|
|
83
|
+
export interface PhoneNumberRecordLike {
|
|
84
|
+
ID: string;
|
|
85
|
+
PhoneNumber: string;
|
|
86
|
+
FriendlyName?: string | null;
|
|
87
|
+
}
|
|
88
|
+
export declare function FormatDuration(seconds: number): string;
|
|
89
|
+
export declare function FormatCurrency(amount: number): string;
|
|
90
|
+
export declare function ParseDate(val: Date | string | null | undefined): Date | null;
|
|
91
|
+
export declare function CalculateOperationsMetrics(interactions: readonly InteractionRecordLike[], events: readonly InteractionEventRecordLike[], phoneNumbers: readonly PhoneNumberRecordLike[], now?: Date): OperationsMetrics;
|
|
92
|
+
export declare function BuildTelephonyOperationsAgentContext(metrics: OperationsMetrics, timeRange: TimeRangeFilter, channelFilter: ChannelFilter): Record<string, unknown>;
|
|
93
|
+
export declare function ValidateTimeRangeParam(raw: unknown): {
|
|
94
|
+
ok: true;
|
|
95
|
+
value: TimeRangeFilter;
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
result: AgentToolResult;
|
|
99
|
+
};
|
|
100
|
+
export declare function ValidateChannelParam(raw: unknown): {
|
|
101
|
+
ok: true;
|
|
102
|
+
value: ChannelFilter;
|
|
103
|
+
} | {
|
|
104
|
+
ok: false;
|
|
105
|
+
result: AgentToolResult;
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=telephony-operations-agent-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-operations-agent-context.d.ts","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-agent-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { BoundNameList, ValidateEnumParam } from '../shared/agent-tool-validation';
|
|
2
|
+
export const VALID_TIME_RANGES = ['24h', '7d', '30d', 'all'];
|
|
3
|
+
export const VALID_CHANNELS = ['all', 'Phone', 'Web', 'Meeting'];
|
|
4
|
+
export function FormatDuration(seconds) {
|
|
5
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
6
|
+
return '0s';
|
|
7
|
+
}
|
|
8
|
+
const rounded = Math.floor(seconds);
|
|
9
|
+
const mins = Math.floor(rounded / 60);
|
|
10
|
+
const secs = rounded % 60;
|
|
11
|
+
if (mins === 0) {
|
|
12
|
+
return `${secs}s`;
|
|
13
|
+
}
|
|
14
|
+
return `${mins}m ${secs.toString().padStart(2, '0')}s`;
|
|
15
|
+
}
|
|
16
|
+
export function FormatCurrency(amount) {
|
|
17
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
18
|
+
return '$0.00';
|
|
19
|
+
}
|
|
20
|
+
return new Intl.NumberFormat('en-US', {
|
|
21
|
+
style: 'currency',
|
|
22
|
+
currency: 'USD',
|
|
23
|
+
minimumFractionDigits: 2,
|
|
24
|
+
maximumFractionDigits: 2,
|
|
25
|
+
}).format(amount);
|
|
26
|
+
}
|
|
27
|
+
export function ParseDate(val) {
|
|
28
|
+
if (!val)
|
|
29
|
+
return null;
|
|
30
|
+
if (val instanceof Date)
|
|
31
|
+
return val;
|
|
32
|
+
const parsed = new Date(val);
|
|
33
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
34
|
+
}
|
|
35
|
+
export function CalculateOperationsMetrics(interactions, events, phoneNumbers, now = new Date()) {
|
|
36
|
+
const totalVolume = interactions.length;
|
|
37
|
+
const phoneNumberMap = new Map();
|
|
38
|
+
for (const pn of phoneNumbers) {
|
|
39
|
+
phoneNumberMap.set(pn.ID.toLowerCase(), pn.FriendlyName?.trim() || pn.PhoneNumber);
|
|
40
|
+
}
|
|
41
|
+
let answeredCount = 0;
|
|
42
|
+
let abandonedCount = 0;
|
|
43
|
+
let totalHandleTime = 0;
|
|
44
|
+
let completedHandledCount = 0;
|
|
45
|
+
let totalWaitTime = 0;
|
|
46
|
+
let speedToAnswerCount = 0;
|
|
47
|
+
let totalCost = 0;
|
|
48
|
+
const channelCounts = { Phone: 0, Web: 0, Meeting: 0 };
|
|
49
|
+
const directionCounts = { Inbound: 0, Outbound: 0, Internal: 0 };
|
|
50
|
+
const costByDayMap = new Map();
|
|
51
|
+
const costByNumberMap = new Map();
|
|
52
|
+
const liveInteractions = [];
|
|
53
|
+
// Group events by interaction id for quick checks
|
|
54
|
+
const eventsByInteraction = new Map();
|
|
55
|
+
let transferCount = 0;
|
|
56
|
+
let escalationCount = 0;
|
|
57
|
+
for (const evt of events) {
|
|
58
|
+
if (evt.EventType === 'Transferred')
|
|
59
|
+
transferCount++;
|
|
60
|
+
if (evt.EventType === 'Escalated')
|
|
61
|
+
escalationCount++;
|
|
62
|
+
const key = evt.InteractionID.toLowerCase();
|
|
63
|
+
const existing = eventsByInteraction.get(key);
|
|
64
|
+
if (existing) {
|
|
65
|
+
existing.push(evt);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
eventsByInteraction.set(key, [evt]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const row of interactions) {
|
|
72
|
+
const started = ParseDate(row.StartedAt) ?? new Date();
|
|
73
|
+
const answered = ParseDate(row.AnsweredAt);
|
|
74
|
+
const ended = ParseDate(row.EndedAt);
|
|
75
|
+
const status = row.Status;
|
|
76
|
+
// Channel count
|
|
77
|
+
const ch = row.Channel || 'Phone';
|
|
78
|
+
if (ch in channelCounts) {
|
|
79
|
+
channelCounts[ch]++;
|
|
80
|
+
}
|
|
81
|
+
// Direction count
|
|
82
|
+
const dir = row.Direction || 'Inbound';
|
|
83
|
+
if (dir in directionCounts) {
|
|
84
|
+
directionCounts[dir]++;
|
|
85
|
+
}
|
|
86
|
+
// Cost accumulation
|
|
87
|
+
const cost = typeof row.CostEstimate === 'number' && Number.isFinite(row.CostEstimate) ? row.CostEstimate : 0;
|
|
88
|
+
totalCost += cost;
|
|
89
|
+
// Cost by day
|
|
90
|
+
const dayKey = started.toISOString().split('T')[0];
|
|
91
|
+
const dayEntry = costByDayMap.get(dayKey) ?? { cost: 0, count: 0 };
|
|
92
|
+
dayEntry.cost += cost;
|
|
93
|
+
dayEntry.count++;
|
|
94
|
+
costByDayMap.set(dayKey, dayEntry);
|
|
95
|
+
// Cost by number
|
|
96
|
+
const pnId = row.PhoneNumberID ? row.PhoneNumberID.toLowerCase() : null;
|
|
97
|
+
const pnLabel = pnId ? (phoneNumberMap.get(pnId) ?? row.RemoteAddress ?? 'Assigned Pool') : (row.RemoteAddress ?? 'Non-Phone');
|
|
98
|
+
const pnKey = pnId ?? pnLabel;
|
|
99
|
+
const numberEntry = costByNumberMap.get(pnKey) ?? { phoneNumber: pnLabel, cost: 0, count: 0 };
|
|
100
|
+
numberEntry.cost += cost;
|
|
101
|
+
numberEntry.count++;
|
|
102
|
+
costByNumberMap.set(pnKey, numberEntry);
|
|
103
|
+
// Live interactions
|
|
104
|
+
const isLive = (status === 'Active' || status === 'Queued') && ended === null;
|
|
105
|
+
if (isLive) {
|
|
106
|
+
const elapsed = Math.max(0, Math.floor((now.getTime() - started.getTime()) / 1000));
|
|
107
|
+
liveInteractions.push({
|
|
108
|
+
ID: row.ID,
|
|
109
|
+
Channel: ch,
|
|
110
|
+
Direction: dir,
|
|
111
|
+
Status: status,
|
|
112
|
+
RemoteAddress: row.RemoteAddress,
|
|
113
|
+
StartedAt: started,
|
|
114
|
+
ElapsedSeconds: elapsed,
|
|
115
|
+
ElapsedLabel: FormatDuration(elapsed),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Answered check
|
|
119
|
+
const hasAnswerEvent = (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some((e) => e.EventType === 'Answered' || e.EventType === 'Accepted');
|
|
120
|
+
const isAnswered = answered !== null || hasAnswerEvent || status === 'Active' || status === 'Ended';
|
|
121
|
+
if (isAnswered) {
|
|
122
|
+
answeredCount++;
|
|
123
|
+
}
|
|
124
|
+
// Abandoned check
|
|
125
|
+
const isAbandoned = status === 'Abandoned' || (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some((e) => e.EventType === 'Abandoned');
|
|
126
|
+
if (isAbandoned) {
|
|
127
|
+
abandonedCount++;
|
|
128
|
+
}
|
|
129
|
+
// Handle time: for ended interactions
|
|
130
|
+
if (ended !== null) {
|
|
131
|
+
const effectiveStart = answered ?? started;
|
|
132
|
+
const durationSec = Math.max(0, Math.floor((ended.getTime() - effectiveStart.getTime()) / 1000));
|
|
133
|
+
totalHandleTime += durationSec;
|
|
134
|
+
completedHandledCount++;
|
|
135
|
+
}
|
|
136
|
+
// Speed to answer
|
|
137
|
+
if (answered !== null) {
|
|
138
|
+
const waitSec = Math.max(0, Math.floor((answered.getTime() - started.getTime()) / 1000));
|
|
139
|
+
totalWaitTime += waitSec;
|
|
140
|
+
speedToAnswerCount++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const answerRate = totalVolume > 0 ? (answeredCount / totalVolume) * 100 : 0;
|
|
144
|
+
const abandonmentRate = totalVolume > 0 ? (abandonedCount / totalVolume) * 100 : 0;
|
|
145
|
+
const avgHandleTime = completedHandledCount > 0 ? totalHandleTime / completedHandledCount : 0;
|
|
146
|
+
const avgSpeedToAnswer = speedToAnswerCount > 0 ? totalWaitTime / speedToAnswerCount : 0;
|
|
147
|
+
const volumeByChannel = ['Phone', 'Web', 'Meeting'].map((channel) => ({
|
|
148
|
+
Channel: channel,
|
|
149
|
+
Count: channelCounts[channel],
|
|
150
|
+
Percentage: totalVolume > 0 ? Math.round((channelCounts[channel] / totalVolume) * 100) : 0,
|
|
151
|
+
}));
|
|
152
|
+
const volumeByDirection = ['Inbound', 'Outbound', 'Internal'].map((direction) => ({
|
|
153
|
+
Direction: direction,
|
|
154
|
+
Count: directionCounts[direction],
|
|
155
|
+
Percentage: totalVolume > 0 ? Math.round((directionCounts[direction] / totalVolume) * 100) : 0,
|
|
156
|
+
}));
|
|
157
|
+
const costByDay = Array.from(costByDayMap.entries())
|
|
158
|
+
.map(([date, data]) => ({ Date: date, Cost: data.cost, Count: data.count }))
|
|
159
|
+
.sort((a, b) => a.Date.localeCompare(b.Date));
|
|
160
|
+
const costByNumber = Array.from(costByNumberMap.entries())
|
|
161
|
+
.map(([id, data]) => ({
|
|
162
|
+
PhoneNumberID: id.includes('-') ? id : null,
|
|
163
|
+
PhoneNumber: data.phoneNumber,
|
|
164
|
+
Cost: data.cost,
|
|
165
|
+
Count: data.count,
|
|
166
|
+
}))
|
|
167
|
+
.sort((a, b) => b.Cost - a.Cost);
|
|
168
|
+
return {
|
|
169
|
+
TotalVolume: totalVolume,
|
|
170
|
+
LiveCallsCount: liveInteractions.length,
|
|
171
|
+
AnsweredCount: answeredCount,
|
|
172
|
+
AnswerRate: answerRate,
|
|
173
|
+
AbandonedCount: abandonedCount,
|
|
174
|
+
AbandonmentRate: abandonmentRate,
|
|
175
|
+
AverageHandleTimeSeconds: avgHandleTime,
|
|
176
|
+
AverageHandleTimeFormatted: FormatDuration(avgHandleTime),
|
|
177
|
+
AverageSpeedToAnswerSeconds: avgSpeedToAnswer,
|
|
178
|
+
AverageSpeedToAnswerFormatted: FormatDuration(avgSpeedToAnswer),
|
|
179
|
+
TransferCount: transferCount,
|
|
180
|
+
EscalationCount: escalationCount,
|
|
181
|
+
TotalCost: totalCost,
|
|
182
|
+
TotalCostFormatted: FormatCurrency(totalCost),
|
|
183
|
+
VolumeByChannel: volumeByChannel,
|
|
184
|
+
VolumeByDirection: volumeByDirection,
|
|
185
|
+
CostByDay: costByDay,
|
|
186
|
+
CostByNumber: costByNumber,
|
|
187
|
+
LiveInteractions: liveInteractions.sort((a, b) => b.ElapsedSeconds - a.ElapsedSeconds),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function BuildTelephonyOperationsAgentContext(metrics, timeRange, channelFilter) {
|
|
191
|
+
return {
|
|
192
|
+
Surface: 'TelephonyOperationsDashboard',
|
|
193
|
+
TimeRange: timeRange,
|
|
194
|
+
ChannelFilter: channelFilter,
|
|
195
|
+
TotalVolume: metrics.TotalVolume,
|
|
196
|
+
LiveCallsCount: metrics.LiveCallsCount,
|
|
197
|
+
AnswerRatePercent: Math.round(metrics.AnswerRate * 10) / 10,
|
|
198
|
+
AbandonmentRatePercent: Math.round(metrics.AbandonmentRate * 10) / 10,
|
|
199
|
+
AverageHandleTime: metrics.AverageHandleTimeFormatted,
|
|
200
|
+
AverageSpeedToAnswer: metrics.AverageSpeedToAnswerFormatted,
|
|
201
|
+
TransferCount: metrics.TransferCount,
|
|
202
|
+
EscalationCount: metrics.EscalationCount,
|
|
203
|
+
TotalCostEstimate: metrics.TotalCostFormatted,
|
|
204
|
+
VolumeByChannel: metrics.VolumeByChannel.map((c) => `${c.Channel}: ${c.Count} (${c.Percentage}%)`),
|
|
205
|
+
VolumeByDirection: metrics.VolumeByDirection.map((d) => `${d.Direction}: ${d.Count} (${d.Percentage}%)`),
|
|
206
|
+
VisibleLiveCallAddresses: BoundNameList(metrics.LiveInteractions.map((i) => `${i.Channel} (${i.Direction}): ${i.RemoteAddress || i.ID} [${i.ElapsedLabel}]`)),
|
|
207
|
+
TopCostNumbers: BoundNameList(metrics.CostByNumber.map((n) => `${n.PhoneNumber}: $${n.Cost.toFixed(2)} (${n.Count} calls)`)),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function ValidateTimeRangeParam(raw) {
|
|
211
|
+
return ValidateEnumParam(raw, VALID_TIME_RANGES, 'timeRange');
|
|
212
|
+
}
|
|
213
|
+
export function ValidateChannelParam(raw) {
|
|
214
|
+
return ValidateEnumParam(raw, VALID_CHANNELS, 'channel');
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=telephony-operations-agent-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-operations-agent-context.js","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-agent-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { BoundNameList, ValidateEnumParam, type AgentToolResult } from '../shared/agent-tool-validation';\n\nexport type TimeRangeFilter = '24h' | '7d' | '30d' | 'all';\nexport type ChannelFilter = 'all' | 'Phone' | 'Web' | 'Meeting';\n\nexport const VALID_TIME_RANGES: readonly TimeRangeFilter[] = ['24h', '7d', '30d', 'all'] as const;\nexport const VALID_CHANNELS: readonly ChannelFilter[] = ['all', 'Phone', 'Web', 'Meeting'] as const;\n\nexport interface ChannelMetrics {\n Channel: 'Phone' | 'Web' | 'Meeting';\n Count: number;\n Percentage: number;\n}\n\nexport interface DirectionMetrics {\n Direction: 'Inbound' | 'Outbound' | 'Internal';\n Count: number;\n Percentage: number;\n}\n\nexport interface CostByDay {\n Date: string;\n Cost: number;\n Count: number;\n}\n\nexport interface CostByNumber {\n PhoneNumberID: string | null;\n PhoneNumber: string;\n Cost: number;\n Count: number;\n}\n\nexport interface LiveInteractionSummary {\n ID: string;\n Channel: 'Phone' | 'Web' | 'Meeting';\n Direction: 'Inbound' | 'Outbound' | 'Internal';\n Status: string;\n RemoteAddress: string | null;\n StartedAt: Date;\n ElapsedSeconds: number;\n ElapsedLabel: string;\n}\n\nexport interface OperationsMetrics {\n TotalVolume: number;\n LiveCallsCount: number;\n AnsweredCount: number;\n AnswerRate: number;\n AbandonedCount: number;\n AbandonmentRate: number;\n AverageHandleTimeSeconds: number;\n AverageHandleTimeFormatted: string;\n AverageSpeedToAnswerSeconds: number;\n AverageSpeedToAnswerFormatted: string;\n TransferCount: number;\n EscalationCount: number;\n TotalCost: number;\n TotalCostFormatted: string;\n VolumeByChannel: ChannelMetrics[];\n VolumeByDirection: DirectionMetrics[];\n CostByDay: CostByDay[];\n CostByNumber: CostByNumber[];\n LiveInteractions: LiveInteractionSummary[];\n}\n\nexport interface InteractionRecordLike {\n ID: string;\n Channel: 'Meeting' | 'Phone' | 'Web' | string;\n Direction: 'Inbound' | 'Internal' | 'Outbound' | string;\n Status: 'Abandoned' | 'Active' | 'Ended' | 'Failed' | 'Queued' | string;\n AgentSessionID: string | null;\n RoomName: string | null;\n PhoneNumberID: string | null;\n RemoteAddress: string | null;\n StartedAt: Date | string;\n AnsweredAt: Date | string | null;\n EndedAt: Date | string | null;\n EndReason: string | null;\n RecordingEnabled: boolean;\n CostEstimate: number | null;\n}\n\nexport interface InteractionEventRecordLike {\n ID: string;\n InteractionID: string;\n EventType: 'Abandoned' | 'Accepted' | 'Answered' | 'Created' | 'Declined' | 'Ended' | 'Escalated' | 'Held' | 'Offered' | 'Queued' | 'RecordingStarted' | 'RecordingStopped' | 'Resumed' | 'Transferred' | string;\n OccurredAt: Date | string;\n ActorUserID: string | null;\n ActorAgentID: string | null;\n Details: string | null;\n}\n\nexport interface PhoneNumberRecordLike {\n ID: string;\n PhoneNumber: string;\n FriendlyName?: string | null;\n}\n\nexport function FormatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds <= 0) {\n return '0s';\n }\n const rounded = Math.floor(seconds);\n const mins = Math.floor(rounded / 60);\n const secs = rounded % 60;\n if (mins === 0) {\n return `${secs}s`;\n }\n return `${mins}m ${secs.toString().padStart(2, '0')}s`;\n}\n\nexport function FormatCurrency(amount: number): string {\n if (!Number.isFinite(amount) || amount <= 0) {\n return '$0.00';\n }\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount);\n}\n\nexport function ParseDate(val: Date | string | null | undefined): Date | null {\n if (!val) return null;\n if (val instanceof Date) return val;\n const parsed = new Date(val);\n return isNaN(parsed.getTime()) ? null : parsed;\n}\n\nexport function CalculateOperationsMetrics(\n interactions: readonly InteractionRecordLike[],\n events: readonly InteractionEventRecordLike[],\n phoneNumbers: readonly PhoneNumberRecordLike[],\n now: Date = new Date(),\n): OperationsMetrics {\n const totalVolume = interactions.length;\n const phoneNumberMap = new Map<string, string>();\n for (const pn of phoneNumbers) {\n phoneNumberMap.set(pn.ID.toLowerCase(), pn.FriendlyName?.trim() || pn.PhoneNumber);\n }\n\n let answeredCount = 0;\n let abandonedCount = 0;\n let totalHandleTime = 0;\n let completedHandledCount = 0;\n let totalWaitTime = 0;\n let speedToAnswerCount = 0;\n let totalCost = 0;\n\n const channelCounts: Record<'Phone' | 'Web' | 'Meeting', number> = { Phone: 0, Web: 0, Meeting: 0 };\n const directionCounts: Record<'Inbound' | 'Outbound' | 'Internal', number> = { Inbound: 0, Outbound: 0, Internal: 0 };\n const costByDayMap = new Map<string, { cost: number; count: number }>();\n const costByNumberMap = new Map<string, { phoneNumber: string; cost: number; count: number }>();\n const liveInteractions: LiveInteractionSummary[] = [];\n\n // Group events by interaction id for quick checks\n const eventsByInteraction = new Map<string, InteractionEventRecordLike[]>();\n let transferCount = 0;\n let escalationCount = 0;\n\n for (const evt of events) {\n if (evt.EventType === 'Transferred') transferCount++;\n if (evt.EventType === 'Escalated') escalationCount++;\n\n const key = evt.InteractionID.toLowerCase();\n const existing = eventsByInteraction.get(key);\n if (existing) {\n existing.push(evt);\n } else {\n eventsByInteraction.set(key, [evt]);\n }\n }\n\n for (const row of interactions) {\n const started = ParseDate(row.StartedAt) ?? new Date();\n const answered = ParseDate(row.AnsweredAt);\n const ended = ParseDate(row.EndedAt);\n const status = row.Status;\n\n // Channel count\n const ch = (row.Channel as 'Phone' | 'Web' | 'Meeting') || 'Phone';\n if (ch in channelCounts) {\n channelCounts[ch]++;\n }\n\n // Direction count\n const dir = (row.Direction as 'Inbound' | 'Outbound' | 'Internal') || 'Inbound';\n if (dir in directionCounts) {\n directionCounts[dir]++;\n }\n\n // Cost accumulation\n const cost = typeof row.CostEstimate === 'number' && Number.isFinite(row.CostEstimate) ? row.CostEstimate : 0;\n totalCost += cost;\n\n // Cost by day\n const dayKey = started.toISOString().split('T')[0];\n const dayEntry = costByDayMap.get(dayKey) ?? { cost: 0, count: 0 };\n dayEntry.cost += cost;\n dayEntry.count++;\n costByDayMap.set(dayKey, dayEntry);\n\n // Cost by number\n const pnId = row.PhoneNumberID ? row.PhoneNumberID.toLowerCase() : null;\n const pnLabel = pnId ? (phoneNumberMap.get(pnId) ?? row.RemoteAddress ?? 'Assigned Pool') : (row.RemoteAddress ?? 'Non-Phone');\n const pnKey = pnId ?? pnLabel;\n const numberEntry = costByNumberMap.get(pnKey) ?? { phoneNumber: pnLabel, cost: 0, count: 0 };\n numberEntry.cost += cost;\n numberEntry.count++;\n costByNumberMap.set(pnKey, numberEntry);\n\n // Live interactions\n const isLive = (status === 'Active' || status === 'Queued') && ended === null;\n if (isLive) {\n const elapsed = Math.max(0, Math.floor((now.getTime() - started.getTime()) / 1000));\n liveInteractions.push({\n ID: row.ID,\n Channel: ch,\n Direction: dir,\n Status: status,\n RemoteAddress: row.RemoteAddress,\n StartedAt: started,\n ElapsedSeconds: elapsed,\n ElapsedLabel: FormatDuration(elapsed),\n });\n }\n\n // Answered check\n const hasAnswerEvent = (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some(\n (e) => e.EventType === 'Answered' || e.EventType === 'Accepted',\n );\n const isAnswered = answered !== null || hasAnswerEvent || status === 'Active' || status === 'Ended';\n if (isAnswered) {\n answeredCount++;\n }\n\n // Abandoned check\n const isAbandoned = status === 'Abandoned' || (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some(\n (e) => e.EventType === 'Abandoned',\n );\n if (isAbandoned) {\n abandonedCount++;\n }\n\n // Handle time: for ended interactions\n if (ended !== null) {\n const effectiveStart = answered ?? started;\n const durationSec = Math.max(0, Math.floor((ended.getTime() - effectiveStart.getTime()) / 1000));\n totalHandleTime += durationSec;\n completedHandledCount++;\n }\n\n // Speed to answer\n if (answered !== null) {\n const waitSec = Math.max(0, Math.floor((answered.getTime() - started.getTime()) / 1000));\n totalWaitTime += waitSec;\n speedToAnswerCount++;\n }\n }\n\n const answerRate = totalVolume > 0 ? (answeredCount / totalVolume) * 100 : 0;\n const abandonmentRate = totalVolume > 0 ? (abandonedCount / totalVolume) * 100 : 0;\n const avgHandleTime = completedHandledCount > 0 ? totalHandleTime / completedHandledCount : 0;\n const avgSpeedToAnswer = speedToAnswerCount > 0 ? totalWaitTime / speedToAnswerCount : 0;\n\n const volumeByChannel: ChannelMetrics[] = (['Phone', 'Web', 'Meeting'] as const).map((channel) => ({\n Channel: channel,\n Count: channelCounts[channel],\n Percentage: totalVolume > 0 ? Math.round((channelCounts[channel] / totalVolume) * 100) : 0,\n }));\n\n const volumeByDirection: DirectionMetrics[] = (['Inbound', 'Outbound', 'Internal'] as const).map((direction) => ({\n Direction: direction,\n Count: directionCounts[direction],\n Percentage: totalVolume > 0 ? Math.round((directionCounts[direction] / totalVolume) * 100) : 0,\n }));\n\n const costByDay: CostByDay[] = Array.from(costByDayMap.entries())\n .map(([date, data]) => ({ Date: date, Cost: data.cost, Count: data.count }))\n .sort((a, b) => a.Date.localeCompare(b.Date));\n\n const costByNumber: CostByNumber[] = Array.from(costByNumberMap.entries())\n .map(([id, data]) => ({\n PhoneNumberID: id.includes('-') ? id : null,\n PhoneNumber: data.phoneNumber,\n Cost: data.cost,\n Count: data.count,\n }))\n .sort((a, b) => b.Cost - a.Cost);\n\n return {\n TotalVolume: totalVolume,\n LiveCallsCount: liveInteractions.length,\n AnsweredCount: answeredCount,\n AnswerRate: answerRate,\n AbandonedCount: abandonedCount,\n AbandonmentRate: abandonmentRate,\n AverageHandleTimeSeconds: avgHandleTime,\n AverageHandleTimeFormatted: FormatDuration(avgHandleTime),\n AverageSpeedToAnswerSeconds: avgSpeedToAnswer,\n AverageSpeedToAnswerFormatted: FormatDuration(avgSpeedToAnswer),\n TransferCount: transferCount,\n EscalationCount: escalationCount,\n TotalCost: totalCost,\n TotalCostFormatted: FormatCurrency(totalCost),\n VolumeByChannel: volumeByChannel,\n VolumeByDirection: volumeByDirection,\n CostByDay: costByDay,\n CostByNumber: costByNumber,\n LiveInteractions: liveInteractions.sort((a, b) => b.ElapsedSeconds - a.ElapsedSeconds),\n };\n}\n\nexport function BuildTelephonyOperationsAgentContext(\n metrics: OperationsMetrics,\n timeRange: TimeRangeFilter,\n channelFilter: ChannelFilter,\n): Record<string, unknown> {\n return {\n Surface: 'TelephonyOperationsDashboard',\n TimeRange: timeRange,\n ChannelFilter: channelFilter,\n TotalVolume: metrics.TotalVolume,\n LiveCallsCount: metrics.LiveCallsCount,\n AnswerRatePercent: Math.round(metrics.AnswerRate * 10) / 10,\n AbandonmentRatePercent: Math.round(metrics.AbandonmentRate * 10) / 10,\n AverageHandleTime: metrics.AverageHandleTimeFormatted,\n AverageSpeedToAnswer: metrics.AverageSpeedToAnswerFormatted,\n TransferCount: metrics.TransferCount,\n EscalationCount: metrics.EscalationCount,\n TotalCostEstimate: metrics.TotalCostFormatted,\n VolumeByChannel: metrics.VolumeByChannel.map((c) => `${c.Channel}: ${c.Count} (${c.Percentage}%)`),\n VolumeByDirection: metrics.VolumeByDirection.map((d) => `${d.Direction}: ${d.Count} (${d.Percentage}%)`),\n VisibleLiveCallAddresses: BoundNameList(\n metrics.LiveInteractions.map((i) => `${i.Channel} (${i.Direction}): ${i.RemoteAddress || i.ID} [${i.ElapsedLabel}]`),\n ),\n TopCostNumbers: BoundNameList(\n metrics.CostByNumber.map((n) => `${n.PhoneNumber}: $${n.Cost.toFixed(2)} (${n.Count} calls)`),\n ),\n };\n}\n\nexport function ValidateTimeRangeParam(raw: unknown): { ok: true; value: TimeRangeFilter } | { ok: false; result: AgentToolResult } {\n return ValidateEnumParam(raw, VALID_TIME_RANGES, 'timeRange');\n}\n\nexport function ValidateChannelParam(raw: unknown): { ok: true; value: ChannelFilter } | { ok: false; result: AgentToolResult } {\n return ValidateEnumParam(raw, VALID_CHANNELS, 'channel');\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AfterViewInit, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
3
|
+
import { ResourceData } from '@memberjunction/core-entities';
|
|
4
|
+
import { type ChannelFilter, type InteractionEventRecordLike, type InteractionRecordLike, type OperationsMetrics, type PhoneNumberRecordLike, type TimeRangeFilter } from './telephony-operations-agent-context';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/**
|
|
7
|
+
* TELEPHONY & INTERACTION OPERATIONS DASHBOARD
|
|
8
|
+
*
|
|
9
|
+
* An Explorer L3 dashboard providing operational analytics across phone calls, web realtime
|
|
10
|
+
* sessions, and multi-party meetings.
|
|
11
|
+
*
|
|
12
|
+
* Metrics computed from `MJ: Interactions`, `MJ: Interaction Events`, and `MJ: Phone Numbers`:
|
|
13
|
+
* - Volume by channel (Phone, Web, Meeting) and direction (Inbound, Outbound, Internal)
|
|
14
|
+
* - Answer rate and abandonment rate
|
|
15
|
+
* - Average handle time (AHT) and speed to answer (ASA)
|
|
16
|
+
* - Transfer and escalation volumes
|
|
17
|
+
* - Cost estimates by day and by phone number
|
|
18
|
+
* - Active live calls / sessions in real time
|
|
19
|
+
*
|
|
20
|
+
* Data is loaded in a single batched `RunViews` call and aggregated in memory.
|
|
21
|
+
*/
|
|
22
|
+
export declare class TelephonyOperationsDashboardComponent extends BaseResourceComponent implements OnInit, OnDestroy, AfterViewInit {
|
|
23
|
+
IsLoading: boolean;
|
|
24
|
+
ErrorMessage: string | null;
|
|
25
|
+
SelectedTimeRange: TimeRangeFilter;
|
|
26
|
+
SelectedChannelFilter: ChannelFilter;
|
|
27
|
+
SearchQuery: string;
|
|
28
|
+
RawInteractions: InteractionRecordLike[];
|
|
29
|
+
RawEvents: InteractionEventRecordLike[];
|
|
30
|
+
RawPhoneNumbers: PhoneNumberRecordLike[];
|
|
31
|
+
Metrics: OperationsMetrics;
|
|
32
|
+
private refreshTimer;
|
|
33
|
+
private readonly cdr;
|
|
34
|
+
GetResourceDisplayName(_data: ResourceData): Promise<string>;
|
|
35
|
+
GetResourceIconClass(_data: ResourceData): Promise<string>;
|
|
36
|
+
ngOnInit(): Promise<void>;
|
|
37
|
+
ngAfterViewInit(): void;
|
|
38
|
+
ngOnDestroy(): void;
|
|
39
|
+
LoadData(): Promise<void>;
|
|
40
|
+
SetTimeRange(range: TimeRangeFilter): void;
|
|
41
|
+
SetChannelFilter(channel: ChannelFilter): void;
|
|
42
|
+
OnSearchQueryChange(query: string): void;
|
|
43
|
+
get FilteredLiveInteractions(): OperationsMetrics['LiveInteractions'];
|
|
44
|
+
get FilteredRecentInteractions(): InteractionRecordLike[];
|
|
45
|
+
FormatCost(amount: number | null): string;
|
|
46
|
+
FormatTime(date: Date | string | null): string;
|
|
47
|
+
FormatDate(date: Date | string | null): string;
|
|
48
|
+
ComputeDuration(started: Date | string, ended: Date | string | null): string;
|
|
49
|
+
private get filteredRawInteractions();
|
|
50
|
+
private recalculateMetrics;
|
|
51
|
+
private getDateFilter;
|
|
52
|
+
private publishAgentContext;
|
|
53
|
+
private registerAgentClientTools;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TelephonyOperationsDashboardComponent, never>;
|
|
55
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TelephonyOperationsDashboardComponent, "mj-telephony-operations-dashboard", never, {}, {}, never, never, false, never>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=telephony-operations-dashboard.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-operations-dashboard.component.d.ts","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-dashboard.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAgC,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,sCAAsC,CAAC;;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,qBAQa,qCACX,SAAQ,qBACR,YAAW,MAAM,EAAE,SAAS,EAAE,aAAa;IAEpC,SAAS,UAAQ;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEnC,iBAAiB,EAAE,eAAe,CAAQ;IAC1C,qBAAqB,EAAE,aAAa,CAAS;IAC7C,WAAW,SAAM;IAEjB,eAAe,EAAE,qBAAqB,EAAE,CAAM;IAC9C,SAAS,EAAE,0BAA0B,EAAE,CAAM;IAC7C,eAAe,EAAE,qBAAqB,EAAE,CAAM;IAE9C,OAAO,EAAE,iBAAiB,CAA0C;IAE3E,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAElC,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5D,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC,eAAe,IAAI,IAAI;IAKd,WAAW,IAAI,IAAI;IAQf,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D/B,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAO1C,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAS9C,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK/C,IAAW,wBAAwB,IAAI,iBAAiB,CAAC,kBAAkB,CAAC,CAY3E;IAED,IAAW,0BAA0B,IAAI,qBAAqB,EAAE,CAc/D;IAEM,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAIzC,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IAM9C,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IAM9C,eAAe,CAAC,OAAO,EAAE,IAAI,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IASnF,OAAO,KAAK,uBAAuB,GAKlC;IAED,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,wBAAwB;yCArOrB,qCAAqC;2CAArC,qCAAqC;CAkSjD"}
|