@memberjunction/ng-dashboards 6.2.0-edge.3 → 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/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
- package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
- package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
- package/dist/AI/components/duplicates/compare-value.component.js +168 -0
- package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
- package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +12 -6
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
- package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-index-auto.js +59 -0
- package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +9 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +65 -20
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
- package/dist/Actions/components/actions-overview.component.js +7 -3
- package/dist/Actions/components/actions-overview.component.js.map +1 -1
- package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
- package/dist/Actions/components/categories-list-view.component.js +3 -2
- package/dist/Actions/components/categories-list-view.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
- package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
- package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
- package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
- package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
- package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
- package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
- package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +26 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +302 -219
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
- package/dist/DataExplorer/services/explorer-state.service.js +2 -5
- package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
- package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.js +7 -29
- package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
- package/dist/Home/home-agent-context.d.ts +29 -10
- package/dist/Home/home-agent-context.d.ts.map +1 -1
- package/dist/Home/home-agent-context.js +50 -24
- package/dist/Home/home-agent-context.js.map +1 -1
- package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
- package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
- package/dist/Home/home-dashboard-switcher.component.js +442 -0
- package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
- package/dist/Home/home-dashboard.component.d.ts +330 -17
- package/dist/Home/home-dashboard.component.d.ts.map +1 -1
- package/dist/Home/home-dashboard.component.js +1702 -1014
- package/dist/Home/home-dashboard.component.js.map +1 -1
- package/dist/Home/home-menus.d.ts +38 -0
- package/dist/Home/home-menus.d.ts.map +1 -0
- package/dist/Home/home-menus.js +53 -0
- package/dist/Home/home-menus.js.map +1 -0
- package/dist/Home/home-pinned-dashboards.d.ts +33 -0
- package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
- package/dist/Home/home-pinned-dashboards.js +76 -0
- package/dist/Home/home-pinned-dashboards.js.map +1 -0
- package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
- package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
- package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
- package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-explorer.component.js +7 -7
- package/dist/Testing/components/testing-explorer.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +21 -20
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +4 -1
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +69 -62
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +22 -2
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/data-explorer-dashboards.module.d.ts +1 -1
- package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
- package/dist/data-explorer-dashboards.module.js +3 -1
- package/dist/data-explorer-dashboards.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 +3 -0
- package/dist/public-api.js.map +1 -1
- package/dist/shared/dashboard-library-changes.d.ts +23 -0
- package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
- package/dist/shared/dashboard-library-changes.js +26 -0
- package/dist/shared/dashboard-library-changes.js.map +1 -0
- package/dist/shared/dashboard-owner-names.d.ts +17 -0
- package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
- package/dist/shared/dashboard-owner-names.js +65 -0
- package/dist/shared/dashboard-owner-names.js.map +1 -0
- package/dist/shared/dashboard-recents.d.ts +34 -0
- package/dist/shared/dashboard-recents.d.ts.map +1 -0
- package/dist/shared/dashboard-recents.js +87 -0
- package/dist/shared/dashboard-recents.js.map +1 -0
- package/dist/shared/dashboards-app-install.d.ts +59 -0
- package/dist/shared/dashboards-app-install.d.ts.map +1 -0
- package/dist/shared/dashboards-app-install.js +97 -0
- package/dist/shared/dashboards-app-install.js.map +1 -0
- package/dist/shared/dashboards-app.helpers.d.ts +42 -0
- package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
- package/dist/shared/dashboards-app.helpers.js +143 -0
- package/dist/shared/dashboards-app.helpers.js.map +1 -0
- package/package.json +69 -68
|
@@ -100,7 +100,7 @@ function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Template(rf, c
|
|
|
100
100
|
i0.ɵɵclassProp("rr-list-item--active", (ctx_r0.SelectedSession == null ? null : ctx_r0.SelectedSession.ID) === session_r4.ID);
|
|
101
101
|
i0.ɵɵattribute("aria-selected", (ctx_r0.SelectedSession == null ? null : ctx_r0.SelectedSession.ID) === session_r4.ID);
|
|
102
102
|
i0.ɵɵadvance(2);
|
|
103
|
-
i0.ɵɵclassMap(session_r4.RecordingMedia === "AudioVideo" ? "fa-solid fa-video" : "fa-solid fa-
|
|
103
|
+
i0.ɵɵclassMap(session_r4.RecordingMedia === "AudioVideo" ? "fa-solid fa-video" : "fa-solid fa-wave-square");
|
|
104
104
|
i0.ɵɵadvance(3);
|
|
105
105
|
i0.ɵɵtextInterpolate(session_r4.AgentName);
|
|
106
106
|
i0.ɵɵadvance(4);
|
|
@@ -671,7 +671,7 @@ RealtimeRecordingsDashboardComponent = __decorate([
|
|
|
671
671
|
export { RealtimeRecordingsDashboardComponent };
|
|
672
672
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeRecordingsDashboardComponent, [{
|
|
673
673
|
type: Component,
|
|
674
|
-
args: [{ standalone: false, selector: 'mj-realtime-recordings-dashboard', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Realtime Recordings\"\n Icon=\"fa-solid fa-circle-play\"\n Subtitle=\"Review and replay recorded realtime sessions \u2014 time-aligned audio + transcript.\">\n @if (!IsLoading && Sessions.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"VisibleSessions.length\" [Total]=\"Sessions.length\" Label=\"recorded sessions\" />\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (IsLoading) {\n <div class=\"rr-state\">\n <mj-loading text=\"Loading recorded sessions...\"></mj-loading>\n </div>\n } @else if (ErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ ErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"LoadData()\">Try again</button>\n </div>\n } @else if (Sessions.length === 0) {\n <div class=\"rr-state rr-state--empty\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">No recorded sessions yet.</p>\n <p class=\"rr-state-hint\">\n When a realtime session is recorded, it will appear here for review and replay.\n </p>\n </div>\n } @else {\n <as-split direction=\"horizontal\" unit=\"pixel\" class=\"rr-split\">\n <as-split-area [size]=\"360\" [minSize]=\"260\" [maxSize]=\"560\">\n <ul class=\"rr-list\" role=\"listbox\" aria-label=\"Recorded sessions\">\n @for (session of VisibleSessions; track TrackBySession($index, session)) {\n <li\n class=\"rr-list-item\"\n role=\"option\"\n tabindex=\"0\"\n [class.rr-list-item--active]=\"SelectedSession?.ID === session.ID\"\n [attr.aria-selected]=\"SelectedSession?.ID === session.ID\"\n (click)=\"SelectSession(session)\"\n (keydown)=\"OnRowKeydown($event, session)\">\n <div class=\"rr-list-item-icon\" aria-hidden=\"true\">\n <i [class]=\"session.RecordingMedia === 'AudioVideo' ? 'fa-solid fa-video' : 'fa-solid fa-
|
|
674
|
+
args: [{ standalone: false, selector: 'mj-realtime-recordings-dashboard', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Realtime Recordings\"\n Icon=\"fa-solid fa-circle-play\"\n Subtitle=\"Review and replay recorded realtime sessions \u2014 time-aligned audio + transcript.\">\n @if (!IsLoading && Sessions.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"VisibleSessions.length\" [Total]=\"Sessions.length\" Label=\"recorded sessions\" />\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (IsLoading) {\n <div class=\"rr-state\">\n <mj-loading text=\"Loading recorded sessions...\"></mj-loading>\n </div>\n } @else if (ErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ ErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"LoadData()\">Try again</button>\n </div>\n } @else if (Sessions.length === 0) {\n <div class=\"rr-state rr-state--empty\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">No recorded sessions yet.</p>\n <p class=\"rr-state-hint\">\n When a realtime session is recorded, it will appear here for review and replay.\n </p>\n </div>\n } @else {\n <as-split direction=\"horizontal\" unit=\"pixel\" class=\"rr-split\">\n <as-split-area [size]=\"360\" [minSize]=\"260\" [maxSize]=\"560\">\n <ul class=\"rr-list\" role=\"listbox\" aria-label=\"Recorded sessions\">\n @for (session of VisibleSessions; track TrackBySession($index, session)) {\n <li\n class=\"rr-list-item\"\n role=\"option\"\n tabindex=\"0\"\n [class.rr-list-item--active]=\"SelectedSession?.ID === session.ID\"\n [attr.aria-selected]=\"SelectedSession?.ID === session.ID\"\n (click)=\"SelectSession(session)\"\n (keydown)=\"OnRowKeydown($event, session)\">\n <div class=\"rr-list-item-icon\" aria-hidden=\"true\">\n <i [class]=\"session.RecordingMedia === 'AudioVideo' ? 'fa-solid fa-video' : 'fa-solid fa-wave-square'\"></i>\n </div>\n <div class=\"rr-list-item-body\">\n <div class=\"rr-list-item-title\">{{ session.AgentName }}</div>\n <div class=\"rr-list-item-meta\">\n <span class=\"rr-list-item-date\">\n <i class=\"fa-regular fa-clock\" aria-hidden=\"true\"></i>\n {{ SessionDateLabel(session) }}\n </span>\n @if (session.DurationLabel) {\n <span class=\"rr-list-item-duration\">\n <i class=\"fa-solid fa-stopwatch\" aria-hidden=\"true\"></i>\n {{ session.DurationLabel }}\n </span>\n }\n </div>\n @if (session.ConversationName) {\n <div class=\"rr-list-item-conversation\" [title]=\"session.ConversationName\">\n <i class=\"fa-regular fa-comments\" aria-hidden=\"true\"></i>\n {{ session.ConversationName }}\n </div>\n }\n </div>\n </li>\n }\n </ul>\n </as-split-area>\n\n <as-split-area [size]=\"640\" [minSize]=\"360\">\n <div class=\"rr-detail\">\n @if (!SelectedSession) {\n <div class=\"rr-state rr-state--empty rr-detail-empty\">\n <i class=\"fa-solid fa-hand-pointer\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">Select a session to replay</p>\n <p class=\"rr-state-hint\">\n Pick a recording from the list to hear its audio alongside the synchronized transcript.\n </p>\n </div>\n } @else if (IsDetailLoading) {\n <div class=\"rr-state rr-detail-loading\">\n <mj-loading text=\"Loading recording...\"></mj-loading>\n </div>\n } @else if (DetailErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ DetailErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"SelectSession(SelectedSession!)\">Try again</button>\n </div>\n } @else {\n <mj-storage-media-player\n [FileID]=\"SelectedSession.RecordingFileID\"\n [Transcript]=\"SelectedCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n }\n </div>\n </as-split-area>\n </as-split>\n }\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/* ===== Realtime Recordings dashboard ===== */\n\n.rr-split {\n height: 100%;\n width: 100%;\n}\n\n/* ----- master list pane ----- */\n\n.rr-list {\n list-style: none;\n margin: 0;\n padding: 0.5rem;\n height: 100%;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n.rr-list-item {\n display: flex;\n gap: 0.75rem;\n align-items: flex-start;\n padding: 0.75rem;\n border-radius: var(--mj-radius-md, 8px);\n cursor: pointer;\n border: 1px solid transparent;\n transition: background 0.12s ease, border-color 0.12s ease;\n}\n\n.rr-list-item + .rr-list-item {\n margin-top: 0.25rem;\n}\n\n.rr-list-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.rr-list-item:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n\n.rr-list-item--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.rr-list-item-icon {\n flex: 0 0 auto;\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n.rr-list-item-body {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.rr-list-item-title {\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.rr-list-item-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n margin-top: 0.2rem;\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n}\n\n.rr-list-item-meta i,\n.rr-list-item-conversation i {\n margin-right: 0.25rem;\n opacity: 0.85;\n}\n\n.rr-list-item-conversation {\n margin-top: 0.3rem;\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ----- detail (playback) pane ----- */\n\n.rr-detail {\n height: 100%;\n overflow: auto;\n background: var(--mj-bg-page);\n padding: 1rem;\n}\n\n.rr-detail mj-realtime-evidence-playback {\n display: block;\n height: 100%;\n}\n\n/* ----- state blocks (loading / empty / error) ----- */\n\n.rr-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.6rem;\n text-align: center;\n padding: 2.5rem 1.5rem;\n min-height: 240px;\n color: var(--mj-text-secondary);\n}\n\n.rr-state > i {\n font-size: 2rem;\n color: var(--mj-text-muted);\n}\n\n.rr-state-title {\n font-weight: 600;\n font-size: 1rem;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.rr-state-hint {\n max-width: 380px;\n font-size: 0.85rem;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n.rr-state--error {\n color: var(--mj-status-error-text);\n}\n\n.rr-state--error > i {\n color: var(--mj-status-error);\n}\n\n.rr-detail-empty,\n.rr-detail-loading {\n height: 100%;\n}\n\n/* ----- responsive: tighten paddings on narrow viewports -----\n (angular-split owns pane layout/direction via its [direction] input, so we\n only adjust inner spacing here rather than overriding its flex-direction) */\n\n@media (max-width: 720px) {\n .rr-list {\n padding: 0.25rem;\n }\n\n .rr-detail {\n padding: 0.5rem;\n }\n}\n"] }]
|
|
675
675
|
}], null, null); })();
|
|
676
676
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeRecordingsDashboardComponent, { className: "RealtimeRecordingsDashboardComponent", filePath: "src/RealtimeRecordings/realtime-recordings-dashboard.component.ts", lineNumber: 63 }); })();
|
|
677
677
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-recordings-dashboard.component.js","sourceRoot":"","sources":["../../src/RealtimeRecordings/realtime-recordings-dashboard.component.ts","../../src/RealtimeRecordings/realtime-recordings-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';\nimport { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { RunView } from '@memberjunction/core';\nimport { MJConversationDetailEntity, ResourceData } from '@memberjunction/core-entities';\nimport { MediaTranscriptCue } from '@memberjunction/ng-media-player';\nimport { AgentToolResult, ValidateStringParam } from '../shared/agent-tool-validation';\nimport {\n BuildRealtimeRecordingsAgentContext,\n BuildSessionNotFoundError,\n IsValidSessionSortDirection,\n IsValidSessionSortField,\n ResolveSessionByIdOrName,\n SessionMatchesQuery,\n type SessionSortDirection,\n type SessionSortField,\n} from './realtime-recordings-agent-context';\n\n/**\n * A recorded realtime session, projected from `MJ: AI Agent Sessions` for the list pane.\n * Only the fields the master list needs are kept (the detail pane loads turns on demand).\n */\ninterface RecordedSession {\n /** The `MJ: AI Agent Sessions` id. */\n ID: string;\n /** Denormalized agent name (view field), or a friendly fallback. */\n AgentName: string;\n /** Denormalized conversation name (view field), or null. */\n ConversationName: string | null;\n /** The `MJ: Files` id of the recording audio (always non-null here — we filter on it). */\n RecordingFileID: string;\n /** What was captured (`Audio` / `AudioVideo`). */\n RecordingMedia: string | null;\n /** Recording alignment origin (t0), used by the playback transcript sync. */\n RecordingStartedAt: Date | null;\n /** When the session row was created — the list's primary sort + date column. */\n CreatedAt: Date | null;\n /** Human-readable duration derived from t0 → last turn, or null when not derivable. */\n DurationLabel: string | null;\n}\n\n\n/**\n * REALTIME RECORDINGS — an Explorer resource dashboard for reviewing & replaying historical realtime\n * sessions that were recorded. The left pane lists every recorded session (agent · date · duration ·\n * conversation); selecting one loads its time-aligned transcript turns and resolves the recording's\n * builds transcript cues, then hands the recording's `RecordingFileID` + cues to\n * {@link MJStorageMediaPlayerComponent} in the right pane — the player resolves the authenticated\n * audio itself for synchronized audio + transcript playback.\n *\n * Data sources:\n * - List: `RunView('MJ: AI Agent Sessions', RecordingFileID IS NOT NULL)` — `simple` rows.\n * - Turns: `RunView('MJ: Conversation Details', AgentSessionID = <id>)` — `entity_object` rows.\n * - Audio: resolved by the storage media player from `RecordingFileID` (authenticated bytes → blob).\n */\n@RegisterClass(BaseResourceComponent, 'RealtimeRecordingsDashboard')\n@Component({\n standalone: false,\n selector: 'mj-realtime-recordings-dashboard',\n templateUrl: './realtime-recordings-dashboard.component.html',\n styleUrls: ['./realtime-recordings-dashboard.component.css']\n})\nexport class RealtimeRecordingsDashboardComponent extends BaseResourceComponent implements OnInit, OnDestroy, AfterViewInit {\n /** All recorded sessions (master list), newest first. */\n public Sessions: RecordedSession[] = [];\n /** Active client-side text filter applied to the list (empty when none). */\n public SearchQuery = '';\n /** Active client-side sort field for the list. */\n public SortField: SessionSortField = 'date';\n /** Active client-side sort direction for the list. */\n public SortDirection: SessionSortDirection = 'desc';\n /** The session currently selected for playback, or null. */\n public SelectedSession: RecordedSession | null = null;\n /** Transcript turns for the selected session (used to build the cues). */\n public SelectedTurns: MJConversationDetailEntity[] = [];\n /** Transcript cues for the selected session, built from its turns (passed to the media player). */\n public SelectedCues: MediaTranscriptCue[] = [];\n\n /** True while the master list is loading. */\n public IsLoading = true;\n /** True while a selected session's turns are loading. */\n public IsDetailLoading = false;\n /** Master-list load error, shown in the body when set. */\n public ErrorMessage: string | null = null;\n /** Detail-pane (turns) error, shown in the right pane when set. */\n public DetailErrorMessage: string | null = null;\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n override async GetResourceDisplayName(_data: ResourceData): Promise<string> {\n return 'Realtime Recordings';\n }\n\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-circle-play';\n }\n\n override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n await this.LoadData();\n this.NotifyLoadComplete();\n }\n\n /**\n * After the view initializes, publish the initial agent context and register the read-only\n * playback-navigation tools the AI agent can invoke against this surface. The ongoing context\n * re-emit happens after every meaningful state change (load, select, deselect).\n */\n ngAfterViewInit(): void {\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n override ngOnDestroy(): void {\n super.ngOnDestroy();\n }\n\n /** Loads the master list of recorded sessions. */\n public async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.ErrorMessage = null;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<RecordedSessionRow>({\n EntityName: 'MJ: AI Agent Sessions',\n ExtraFilter: 'RecordingFileID IS NOT NULL',\n OrderBy: '__mj_CreatedAt DESC',\n ResultType: 'simple'\n });\n\n if (!result.Success) {\n this.ErrorMessage = result.ErrorMessage || 'Failed to load recorded sessions.';\n this.Sessions = [];\n return;\n }\n\n this.Sessions = (result.Results ?? []).map(row => this.toRecordedSession(row));\n } catch (err) {\n this.ErrorMessage = err instanceof Error ? err.message : String(err);\n this.Sessions = [];\n } finally {\n this.IsLoading = false;\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n }\n\n /** Selects a session and loads its transcript into the playback pane (the player resolves audio). */\n public async SelectSession(session: RecordedSession): Promise<void> {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n return;\n }\n this.SelectedSession = session;\n this.SelectedTurns = [];\n this.SelectedCues = [];\n this.DetailErrorMessage = null;\n this.IsDetailLoading = true;\n this.publishAgentContext();\n this.cdr.detectChanges();\n\n try {\n const turns = await this.loadTurns(session.ID);\n // Guard against a newer selection having superseded this load.\n if (!UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n return;\n }\n this.SelectedTurns = turns;\n this.SelectedCues = this.buildCues(turns, session);\n this.maybeBackfillDuration(session, turns);\n } catch (err) {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n this.DetailErrorMessage = err instanceof Error ? err.message : String(err);\n }\n } finally {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n this.IsDetailLoading = false;\n }\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n }\n\n /** Keyboard activation for a list row (Enter / Space select it). */\n public OnRowKeydown(event: KeyboardEvent, session: RecordedSession): void {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n void this.SelectSession(session);\n }\n }\n\n /** Stable `@for` track for the session list. */\n public TrackBySession(_index: number, session: RecordedSession): string {\n return session.ID;\n }\n\n /**\n * The recordings currently visible — the master list narrowed by the active client-side\n * text filter and ordered by the active sort field + direction. Pure derivation from\n * `Sessions` / `SearchQuery` / `SortField` / `SortDirection`; drives both the list UI and\n * the agent context, so the agent's \"what's selectable\" view matches the screen exactly.\n */\n public get VisibleSessions(): RecordedSession[] {\n const filtered = this.Sessions.filter(s => SessionMatchesQuery(s, this.SearchQuery));\n const dir = this.SortDirection === 'asc' ? 1 : -1;\n return filtered.sort((a, b) => this.compareSessions(a, b, this.SortField) * dir);\n }\n\n /** Comparator for two recordings on the given sort field (ascending; caller applies direction). */\n private compareSessions(a: RecordedSession, b: RecordedSession, field: SessionSortField): number {\n switch (field) {\n case 'agent':\n return a.AgentName.localeCompare(b.AgentName);\n case 'duration':\n return this.durationSortKey(a) - this.durationSortKey(b);\n case 'date':\n default:\n return (a.CreatedAt?.getTime() ?? 0) - (b.CreatedAt?.getTime() ?? 0);\n }\n }\n\n /** A stable numeric sort key for a recording's duration (createdAt epoch as a coarse proxy when absent). */\n private durationSortKey(session: RecordedSession): number {\n if (!session.DurationLabel) {\n return 0;\n }\n // DurationLabel is `m:ss` or `h:mm:ss` — parse to total seconds for a stable ordering.\n const parts = session.DurationLabel.split(':').map(p => Number(p));\n if (parts.some(n => !Number.isFinite(n))) {\n return 0;\n }\n return parts.reduce((acc, n) => acc * 60 + n, 0);\n }\n\n /** A short, localized date/time label for a session row. */\n public SessionDateLabel(session: RecordedSession): string {\n if (!session.CreatedAt) {\n return '—';\n }\n return session.CreatedAt.toLocaleString(undefined, {\n year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'\n });\n }\n\n /** Clears the current playback selection, returning the surface to the empty playback state. */\n public DeselectSession(): void {\n if (this.SelectedSession === null) {\n return;\n }\n this.SelectedSession = null;\n this.SelectedTurns = [];\n this.SelectedCues = [];\n this.DetailErrorMessage = null;\n this.IsDetailLoading = false;\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n\n // ========================================\n // AI AGENT CONTEXT & CLIENT TOOLS\n //\n // 🚨 SAFETY BOUNDARY: This is a read-only session-playback surface. Only playback-navigation\n // tools are exposed to the agent (select / play / deselect a recording, refresh the list, narrow\n // the list by a client-side text filter, re-order the list). NO tool mutates, deletes, or exports\n // a recording, a session, or its transcript — there is no such tool, and none may ever be added.\n // ========================================\n\n /**\n * Publish the current Realtime Recordings state to the AI agent via NavigationService.\n * Reports the recordings-list size (total + filtered), the active search/sort state, the open\n * selection (id / agent / conversation / media / duration), the loaded turn count, the detail\n * loading flag, and a bounded structured summary of the visible recordings. The shaping lives in\n * the pure {@link buildRealtimeRecordingsAgentContext} helper so it stays unit-testable. Called\n * on init and after every meaningful state change (load, select, deselect, search, sort).\n */\n private publishAgentContext(): void {\n const selected = this.SelectedSession;\n const context = BuildRealtimeRecordingsAgentContext({\n SessionCount: this.Sessions.length,\n SelectedSession: selected\n ? {\n ID: selected.ID,\n AgentName: selected.AgentName,\n ConversationName: selected.ConversationName,\n RecordingMedia: selected.RecordingMedia,\n DurationLabel: selected.DurationLabel,\n }\n : null,\n TurnCount: this.SelectedTurns.length,\n IsDetailLoading: this.IsDetailLoading,\n SearchQuery: this.SearchQuery,\n VisibleSessions: this.VisibleSessions.map(s => ({\n ID: s.ID,\n AgentName: s.AgentName,\n ConversationName: s.ConversationName,\n RecordingMedia: s.RecordingMedia,\n DurationLabel: s.DurationLabel,\n })),\n SortField: this.SortField,\n SortDirection: this.SortDirection,\n });\n this.navigationService.SetAgentContext(this, context);\n }\n\n /**\n * Register the read-only playback-navigation tools the AI agent can invoke against this surface.\n * Each handler delegates to the same component method a user interaction would call, and returns\n * `{ Success: true, Data? }` on success or `{ Success: false, ErrorMessage }` on failure.\n * Handlers are tolerant — they never throw.\n *\n * Tools (read-only — see the SAFETY BOUNDARY comment above):\n * - SelectSession / PlayRecording: select a recording by id OR name and load/play its transcript.\n * - DeselectSession: clear the current playback selection.\n * - RefreshSessionList: reload the master list of recorded sessions.\n * - SearchSessions: narrow the visible list by a client-side text filter (updates the list).\n * - SortSessions: re-order the visible list by date / agent / duration, asc or desc.\n */\n private registerAgentClientTools(): void {\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'SelectSession',\n Description: 'Select a recorded realtime session by its session ID OR name (the agent name, conversation name, or \"Agent — Conversation\" label) and load its transcript for playback.',\n ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectSession(params),\n },\n {\n Name: 'PlayRecording',\n Description: 'Open and play a recording by its session ID OR name — selects it and mounts the synchronized audio + transcript player (alias of SelectSession).',\n ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectSession(params),\n },\n {\n Name: 'DeselectSession',\n Description: 'Clear the currently selected recording, returning playback to the empty state.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n this.DeselectSession();\n return { Success: true } satisfies AgentToolResult;\n },\n },\n {\n Name: 'RefreshSessionList',\n Description: 'Reload the master list of recorded realtime sessions.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n await this.LoadData();\n return { Success: true, Data: { SessionCount: this.Sessions.length } };\n },\n },\n {\n Name: 'SearchSessions',\n Description: 'Narrow the visible recordings by a free-text query, matched against the agent name, conversation name, and captured-media type. Updates the list shown to the user (read-only; does not change the selection). Pass an empty string to clear the filter.',\n ParameterSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },\n Handler: async (params: Record<string, unknown>) => this.toolSearchSessions(params),\n },\n {\n Name: 'SortSessions',\n Description: 'Re-order the visible recordings by \"date\", \"agent\", or \"duration\", in \"asc\" or \"desc\" direction. Read-only display ordering.',\n ParameterSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', enum: ['date', 'agent', 'duration'] },\n direction: { type: 'string', enum: ['asc', 'desc'] },\n },\n required: ['field'],\n },\n Handler: async (params: Record<string, unknown>) => this.toolSortSessions(params),\n },\n ]);\n }\n\n /**\n * Resolve a session reference (id OR name) to a loaded recording and select it for playback.\n * Tolerant: returns a structured failure (with a sample of available recordings) on a miss.\n */\n private async toolSelectSession(params: Record<string, unknown>): Promise<AgentToolResult & { Data?: Record<string, unknown> }> {\n const validation = ValidateStringParam(params['session'], 'session');\n if (!validation.ok) {\n return validation.result;\n }\n const ref = validation.value.trim();\n if (ref.length === 0) {\n return { Success: false, ErrorMessage: 'A session ID or name is required.' };\n }\n const session = ResolveSessionByIdOrName(ref, this.Sessions);\n if (!session) {\n return { Success: false, ErrorMessage: BuildSessionNotFoundError(ref, this.Sessions) };\n }\n await this.SelectSession(session);\n return { Success: true, Data: { SelectedSessionId: session.ID, AgentName: session.AgentName } };\n }\n\n /** Apply a client-side text filter to the visible list (updates the UI + agent context). */\n private toolSearchSessions(params: Record<string, unknown>): AgentToolResult & { Data?: Record<string, unknown> } {\n const validation = ValidateStringParam(params['query'], 'query');\n if (!validation.ok) {\n return validation.result;\n }\n this.SearchQuery = validation.value;\n this.publishAgentContext();\n this.cdr.detectChanges();\n const matches = this.VisibleSessions;\n return {\n Success: true,\n Data: {\n MatchCount: matches.length,\n Matches: matches.map(s => ({\n ID: s.ID,\n AgentName: s.AgentName,\n ConversationName: s.ConversationName,\n RecordingMedia: s.RecordingMedia,\n DurationLabel: s.DurationLabel,\n })),\n },\n };\n }\n\n /** Re-order the visible list by date / agent / duration, asc or desc. Read-only display ordering. */\n private toolSortSessions(params: Record<string, unknown>): AgentToolResult & { Data?: Record<string, unknown> } {\n const rawField = params['field'];\n if (!IsValidSessionSortField(rawField)) {\n return { Success: false, ErrorMessage: 'field must be one of: date, agent, duration.' };\n }\n const rawDirection = params['direction'];\n // direction is optional — default to the sensible per-field default (date: desc, else asc).\n let direction: SessionSortDirection;\n if (rawDirection === undefined || rawDirection === null) {\n direction = rawField === 'date' ? 'desc' : 'asc';\n } else if (IsValidSessionSortDirection(rawDirection)) {\n direction = rawDirection;\n } else {\n return { Success: false, ErrorMessage: 'direction must be one of: asc, desc.' };\n }\n this.SortField = rawField;\n this.SortDirection = direction;\n this.publishAgentContext();\n this.cdr.detectChanges();\n return { Success: true, Data: { SortField: this.SortField, SortDirection: this.SortDirection } };\n }\n\n // ----- helpers ----------------------------------------------------------------------------\n\n /** Loads the transcript turns for a session as full entity objects (for the playback component). */\n private async loadTurns(sessionId: string): Promise<MJConversationDetailEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJConversationDetailEntity>({\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: `AgentSessionID='${sessionId}'`,\n OrderBy: '__mj_CreatedAt ASC',\n ResultType: 'entity_object'\n });\n if (!result.Success) {\n throw new Error(result.ErrorMessage || 'Failed to load transcript turns.');\n }\n return result.Results ?? [];\n }\n\n /**\n * Builds time-aligned {@link MediaTranscriptCue}s from the loaded turns, for the media player's\n * transcript panel. Skips turns with no text; derives each cue's start from `UtteranceStartMs`,\n * falling back to (turn-created − recording-start) ms, else 0. Cues sort by start ascending.\n */\n private buildCues(turns: MJConversationDetailEntity[], session: RecordedSession): MediaTranscriptCue[] {\n const startedAt = session.RecordingStartedAt;\n const cues: MediaTranscriptCue[] = [];\n turns.forEach((turn, index) => {\n const text = turn.Message?.trim() ?? '';\n if (text.length === 0) {\n return;\n }\n cues.push({\n Id: turn.ID || `turn-${index}`,\n StartMs: this.cueStartMs(turn, startedAt),\n EndMs: turn.UtteranceEndMs ?? undefined,\n SpeakerLabel: turn.Role === 'User' ? 'You' : session.AgentName,\n Text: text\n });\n });\n cues.sort((a, b) => a.StartMs - b.StartMs);\n return cues;\n }\n\n /** A turn's media-relative start (ms): precise offset when present, else derived from t0, else 0. */\n private cueStartMs(turn: MJConversationDetailEntity, startedAt: Date | null): number {\n if (turn.UtteranceStartMs != null) {\n return turn.UtteranceStartMs;\n }\n if (startedAt && turn.__mj_CreatedAt) {\n return Math.max(0, turn.__mj_CreatedAt.getTime() - startedAt.getTime());\n }\n return 0;\n }\n\n /** Projects a raw `simple` view row into the list model, deriving display fields. */\n private toRecordedSession(row: RecordedSessionRow): RecordedSession {\n const startedAt = this.toDate(row.RecordingStartedAt);\n const createdAt = this.toDate(row.__mj_CreatedAt);\n return {\n ID: row.ID,\n AgentName: row.Agent || 'Realtime Agent',\n ConversationName: row.Conversation ?? null,\n RecordingFileID: row.RecordingFileID,\n RecordingMedia: row.RecordingMedia ?? null,\n RecordingStartedAt: startedAt,\n CreatedAt: createdAt,\n DurationLabel: null\n };\n }\n\n /**\n * Once turns are loaded, derive a duration (t0 → last turn's end/start) and stamp it on the row\n * so the list shows it. Best-effort: skipped when we lack a recording start time or any turns.\n */\n private maybeBackfillDuration(session: RecordedSession, turns: MJConversationDetailEntity[]): void {\n if (session.DurationLabel || !session.RecordingStartedAt || turns.length === 0) {\n return;\n }\n const endMs = this.lastTurnOffsetMs(turns, session.RecordingStartedAt);\n if (endMs != null && endMs > 0) {\n session.DurationLabel = this.formatDuration(endMs);\n }\n }\n\n /** The end offset (ms) of the last turn relative to the recording start, or null. */\n private lastTurnOffsetMs(turns: MJConversationDetailEntity[], startedAt: Date): number | null {\n const last = turns[turns.length - 1];\n if (last.UtteranceEndMs != null) {\n return last.UtteranceEndMs;\n }\n if (last.UtteranceStartMs != null) {\n return last.UtteranceStartMs;\n }\n if (last.__mj_CreatedAt) {\n return last.__mj_CreatedAt.getTime() - startedAt.getTime();\n }\n return null;\n }\n\n /** Formats a ms span as `m:ss` (or `h:mm:ss` past an hour). */\n private formatDuration(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n /** Coerces a `simple` row's date-ish value (string | Date | null) to a Date or null. */\n private toDate(value: string | Date | null | undefined): Date | null {\n if (!value) {\n return null;\n }\n const date = value instanceof Date ? value : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n}\n\n/**\n * The raw `simple` row shape from the `MJ: AI Agent Sessions` view — denormalized name fields plus\n * the recording columns. `simple` rows carry plain values (dates may arrive as ISO strings).\n */\ninterface RecordedSessionRow {\n ID: string;\n Agent: string | null;\n Conversation: string | null;\n RecordingFileID: string;\n RecordingMedia: string | null;\n RecordingStartedAt: string | Date | null;\n __mj_CreatedAt: string | Date | null;\n}\n\n/**\n * Tree-shaking guard — referenced from the feature module's public-api so the bundler can't drop the\n * `@RegisterClass`-decorated component (which is only ever instantiated dynamically via the class factory).\n */\nexport function LoadRealtimeRecordingsDashboard(): void {\n // intentionally empty\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Realtime Recordings\"\n Icon=\"fa-solid fa-circle-play\"\n Subtitle=\"Review and replay recorded realtime sessions — time-aligned audio + transcript.\">\n @if (!IsLoading && Sessions.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"VisibleSessions.length\" [Total]=\"Sessions.length\" Label=\"recorded sessions\" />\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (IsLoading) {\n <div class=\"rr-state\">\n <mj-loading text=\"Loading recorded sessions...\"></mj-loading>\n </div>\n } @else if (ErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ ErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"LoadData()\">Try again</button>\n </div>\n } @else if (Sessions.length === 0) {\n <div class=\"rr-state rr-state--empty\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">No recorded sessions yet.</p>\n <p class=\"rr-state-hint\">\n When a realtime session is recorded, it will appear here for review and replay.\n </p>\n </div>\n } @else {\n <as-split direction=\"horizontal\" unit=\"pixel\" class=\"rr-split\">\n <as-split-area [size]=\"360\" [minSize]=\"260\" [maxSize]=\"560\">\n <ul class=\"rr-list\" role=\"listbox\" aria-label=\"Recorded sessions\">\n @for (session of VisibleSessions; track TrackBySession($index, session)) {\n <li\n class=\"rr-list-item\"\n role=\"option\"\n tabindex=\"0\"\n [class.rr-list-item--active]=\"SelectedSession?.ID === session.ID\"\n [attr.aria-selected]=\"SelectedSession?.ID === session.ID\"\n (click)=\"SelectSession(session)\"\n (keydown)=\"OnRowKeydown($event, session)\">\n <div class=\"rr-list-item-icon\" aria-hidden=\"true\">\n <i [class]=\"session.RecordingMedia === 'AudioVideo' ? 'fa-solid fa-video' : 'fa-solid fa-waveform-lines'\"></i>\n </div>\n <div class=\"rr-list-item-body\">\n <div class=\"rr-list-item-title\">{{ session.AgentName }}</div>\n <div class=\"rr-list-item-meta\">\n <span class=\"rr-list-item-date\">\n <i class=\"fa-regular fa-clock\" aria-hidden=\"true\"></i>\n {{ SessionDateLabel(session) }}\n </span>\n @if (session.DurationLabel) {\n <span class=\"rr-list-item-duration\">\n <i class=\"fa-solid fa-stopwatch\" aria-hidden=\"true\"></i>\n {{ session.DurationLabel }}\n </span>\n }\n </div>\n @if (session.ConversationName) {\n <div class=\"rr-list-item-conversation\" [title]=\"session.ConversationName\">\n <i class=\"fa-regular fa-comments\" aria-hidden=\"true\"></i>\n {{ session.ConversationName }}\n </div>\n }\n </div>\n </li>\n }\n </ul>\n </as-split-area>\n\n <as-split-area [size]=\"640\" [minSize]=\"360\">\n <div class=\"rr-detail\">\n @if (!SelectedSession) {\n <div class=\"rr-state rr-state--empty rr-detail-empty\">\n <i class=\"fa-solid fa-hand-pointer\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">Select a session to replay</p>\n <p class=\"rr-state-hint\">\n Pick a recording from the list to hear its audio alongside the synchronized transcript.\n </p>\n </div>\n } @else if (IsDetailLoading) {\n <div class=\"rr-state rr-detail-loading\">\n <mj-loading text=\"Loading recording...\"></mj-loading>\n </div>\n } @else if (DetailErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ DetailErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"SelectSession(SelectedSession!)\">Try again</button>\n </div>\n } @else {\n <mj-storage-media-player\n [FileID]=\"SelectedSession.RecordingFileID\"\n [Transcript]=\"SelectedCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n }\n </div>\n </as-split-area>\n </as-split>\n }\n </mj-page-body>\n</mj-page-layout>\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-recordings-dashboard.component.js","sourceRoot":"","sources":["../../src/RealtimeRecordings/realtime-recordings-dashboard.component.ts","../../src/RealtimeRecordings/realtime-recordings-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';\nimport { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { RunView } from '@memberjunction/core';\nimport { MJConversationDetailEntity, ResourceData } from '@memberjunction/core-entities';\nimport { MediaTranscriptCue } from '@memberjunction/ng-media-player';\nimport { AgentToolResult, ValidateStringParam } from '../shared/agent-tool-validation';\nimport {\n BuildRealtimeRecordingsAgentContext,\n BuildSessionNotFoundError,\n IsValidSessionSortDirection,\n IsValidSessionSortField,\n ResolveSessionByIdOrName,\n SessionMatchesQuery,\n type SessionSortDirection,\n type SessionSortField,\n} from './realtime-recordings-agent-context';\n\n/**\n * A recorded realtime session, projected from `MJ: AI Agent Sessions` for the list pane.\n * Only the fields the master list needs are kept (the detail pane loads turns on demand).\n */\ninterface RecordedSession {\n /** The `MJ: AI Agent Sessions` id. */\n ID: string;\n /** Denormalized agent name (view field), or a friendly fallback. */\n AgentName: string;\n /** Denormalized conversation name (view field), or null. */\n ConversationName: string | null;\n /** The `MJ: Files` id of the recording audio (always non-null here — we filter on it). */\n RecordingFileID: string;\n /** What was captured (`Audio` / `AudioVideo`). */\n RecordingMedia: string | null;\n /** Recording alignment origin (t0), used by the playback transcript sync. */\n RecordingStartedAt: Date | null;\n /** When the session row was created — the list's primary sort + date column. */\n CreatedAt: Date | null;\n /** Human-readable duration derived from t0 → last turn, or null when not derivable. */\n DurationLabel: string | null;\n}\n\n\n/**\n * REALTIME RECORDINGS — an Explorer resource dashboard for reviewing & replaying historical realtime\n * sessions that were recorded. The left pane lists every recorded session (agent · date · duration ·\n * conversation); selecting one loads its time-aligned transcript turns and resolves the recording's\n * builds transcript cues, then hands the recording's `RecordingFileID` + cues to\n * {@link MJStorageMediaPlayerComponent} in the right pane — the player resolves the authenticated\n * audio itself for synchronized audio + transcript playback.\n *\n * Data sources:\n * - List: `RunView('MJ: AI Agent Sessions', RecordingFileID IS NOT NULL)` — `simple` rows.\n * - Turns: `RunView('MJ: Conversation Details', AgentSessionID = <id>)` — `entity_object` rows.\n * - Audio: resolved by the storage media player from `RecordingFileID` (authenticated bytes → blob).\n */\n@RegisterClass(BaseResourceComponent, 'RealtimeRecordingsDashboard')\n@Component({\n standalone: false,\n selector: 'mj-realtime-recordings-dashboard',\n templateUrl: './realtime-recordings-dashboard.component.html',\n styleUrls: ['./realtime-recordings-dashboard.component.css']\n})\nexport class RealtimeRecordingsDashboardComponent extends BaseResourceComponent implements OnInit, OnDestroy, AfterViewInit {\n /** All recorded sessions (master list), newest first. */\n public Sessions: RecordedSession[] = [];\n /** Active client-side text filter applied to the list (empty when none). */\n public SearchQuery = '';\n /** Active client-side sort field for the list. */\n public SortField: SessionSortField = 'date';\n /** Active client-side sort direction for the list. */\n public SortDirection: SessionSortDirection = 'desc';\n /** The session currently selected for playback, or null. */\n public SelectedSession: RecordedSession | null = null;\n /** Transcript turns for the selected session (used to build the cues). */\n public SelectedTurns: MJConversationDetailEntity[] = [];\n /** Transcript cues for the selected session, built from its turns (passed to the media player). */\n public SelectedCues: MediaTranscriptCue[] = [];\n\n /** True while the master list is loading. */\n public IsLoading = true;\n /** True while a selected session's turns are loading. */\n public IsDetailLoading = false;\n /** Master-list load error, shown in the body when set. */\n public ErrorMessage: string | null = null;\n /** Detail-pane (turns) error, shown in the right pane when set. */\n public DetailErrorMessage: string | null = null;\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n override async GetResourceDisplayName(_data: ResourceData): Promise<string> {\n return 'Realtime Recordings';\n }\n\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-circle-play';\n }\n\n override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n await this.LoadData();\n this.NotifyLoadComplete();\n }\n\n /**\n * After the view initializes, publish the initial agent context and register the read-only\n * playback-navigation tools the AI agent can invoke against this surface. The ongoing context\n * re-emit happens after every meaningful state change (load, select, deselect).\n */\n ngAfterViewInit(): void {\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n override ngOnDestroy(): void {\n super.ngOnDestroy();\n }\n\n /** Loads the master list of recorded sessions. */\n public async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.ErrorMessage = null;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<RecordedSessionRow>({\n EntityName: 'MJ: AI Agent Sessions',\n ExtraFilter: 'RecordingFileID IS NOT NULL',\n OrderBy: '__mj_CreatedAt DESC',\n ResultType: 'simple'\n });\n\n if (!result.Success) {\n this.ErrorMessage = result.ErrorMessage || 'Failed to load recorded sessions.';\n this.Sessions = [];\n return;\n }\n\n this.Sessions = (result.Results ?? []).map(row => this.toRecordedSession(row));\n } catch (err) {\n this.ErrorMessage = err instanceof Error ? err.message : String(err);\n this.Sessions = [];\n } finally {\n this.IsLoading = false;\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n }\n\n /** Selects a session and loads its transcript into the playback pane (the player resolves audio). */\n public async SelectSession(session: RecordedSession): Promise<void> {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n return;\n }\n this.SelectedSession = session;\n this.SelectedTurns = [];\n this.SelectedCues = [];\n this.DetailErrorMessage = null;\n this.IsDetailLoading = true;\n this.publishAgentContext();\n this.cdr.detectChanges();\n\n try {\n const turns = await this.loadTurns(session.ID);\n // Guard against a newer selection having superseded this load.\n if (!UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n return;\n }\n this.SelectedTurns = turns;\n this.SelectedCues = this.buildCues(turns, session);\n this.maybeBackfillDuration(session, turns);\n } catch (err) {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n this.DetailErrorMessage = err instanceof Error ? err.message : String(err);\n }\n } finally {\n if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {\n this.IsDetailLoading = false;\n }\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n }\n\n /** Keyboard activation for a list row (Enter / Space select it). */\n public OnRowKeydown(event: KeyboardEvent, session: RecordedSession): void {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n void this.SelectSession(session);\n }\n }\n\n /** Stable `@for` track for the session list. */\n public TrackBySession(_index: number, session: RecordedSession): string {\n return session.ID;\n }\n\n /**\n * The recordings currently visible — the master list narrowed by the active client-side\n * text filter and ordered by the active sort field + direction. Pure derivation from\n * `Sessions` / `SearchQuery` / `SortField` / `SortDirection`; drives both the list UI and\n * the agent context, so the agent's \"what's selectable\" view matches the screen exactly.\n */\n public get VisibleSessions(): RecordedSession[] {\n const filtered = this.Sessions.filter(s => SessionMatchesQuery(s, this.SearchQuery));\n const dir = this.SortDirection === 'asc' ? 1 : -1;\n return filtered.sort((a, b) => this.compareSessions(a, b, this.SortField) * dir);\n }\n\n /** Comparator for two recordings on the given sort field (ascending; caller applies direction). */\n private compareSessions(a: RecordedSession, b: RecordedSession, field: SessionSortField): number {\n switch (field) {\n case 'agent':\n return a.AgentName.localeCompare(b.AgentName);\n case 'duration':\n return this.durationSortKey(a) - this.durationSortKey(b);\n case 'date':\n default:\n return (a.CreatedAt?.getTime() ?? 0) - (b.CreatedAt?.getTime() ?? 0);\n }\n }\n\n /** A stable numeric sort key for a recording's duration (createdAt epoch as a coarse proxy when absent). */\n private durationSortKey(session: RecordedSession): number {\n if (!session.DurationLabel) {\n return 0;\n }\n // DurationLabel is `m:ss` or `h:mm:ss` — parse to total seconds for a stable ordering.\n const parts = session.DurationLabel.split(':').map(p => Number(p));\n if (parts.some(n => !Number.isFinite(n))) {\n return 0;\n }\n return parts.reduce((acc, n) => acc * 60 + n, 0);\n }\n\n /** A short, localized date/time label for a session row. */\n public SessionDateLabel(session: RecordedSession): string {\n if (!session.CreatedAt) {\n return '—';\n }\n return session.CreatedAt.toLocaleString(undefined, {\n year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'\n });\n }\n\n /** Clears the current playback selection, returning the surface to the empty playback state. */\n public DeselectSession(): void {\n if (this.SelectedSession === null) {\n return;\n }\n this.SelectedSession = null;\n this.SelectedTurns = [];\n this.SelectedCues = [];\n this.DetailErrorMessage = null;\n this.IsDetailLoading = false;\n this.publishAgentContext();\n this.cdr.detectChanges();\n }\n\n // ========================================\n // AI AGENT CONTEXT & CLIENT TOOLS\n //\n // 🚨 SAFETY BOUNDARY: This is a read-only session-playback surface. Only playback-navigation\n // tools are exposed to the agent (select / play / deselect a recording, refresh the list, narrow\n // the list by a client-side text filter, re-order the list). NO tool mutates, deletes, or exports\n // a recording, a session, or its transcript — there is no such tool, and none may ever be added.\n // ========================================\n\n /**\n * Publish the current Realtime Recordings state to the AI agent via NavigationService.\n * Reports the recordings-list size (total + filtered), the active search/sort state, the open\n * selection (id / agent / conversation / media / duration), the loaded turn count, the detail\n * loading flag, and a bounded structured summary of the visible recordings. The shaping lives in\n * the pure {@link buildRealtimeRecordingsAgentContext} helper so it stays unit-testable. Called\n * on init and after every meaningful state change (load, select, deselect, search, sort).\n */\n private publishAgentContext(): void {\n const selected = this.SelectedSession;\n const context = BuildRealtimeRecordingsAgentContext({\n SessionCount: this.Sessions.length,\n SelectedSession: selected\n ? {\n ID: selected.ID,\n AgentName: selected.AgentName,\n ConversationName: selected.ConversationName,\n RecordingMedia: selected.RecordingMedia,\n DurationLabel: selected.DurationLabel,\n }\n : null,\n TurnCount: this.SelectedTurns.length,\n IsDetailLoading: this.IsDetailLoading,\n SearchQuery: this.SearchQuery,\n VisibleSessions: this.VisibleSessions.map(s => ({\n ID: s.ID,\n AgentName: s.AgentName,\n ConversationName: s.ConversationName,\n RecordingMedia: s.RecordingMedia,\n DurationLabel: s.DurationLabel,\n })),\n SortField: this.SortField,\n SortDirection: this.SortDirection,\n });\n this.navigationService.SetAgentContext(this, context);\n }\n\n /**\n * Register the read-only playback-navigation tools the AI agent can invoke against this surface.\n * Each handler delegates to the same component method a user interaction would call, and returns\n * `{ Success: true, Data? }` on success or `{ Success: false, ErrorMessage }` on failure.\n * Handlers are tolerant — they never throw.\n *\n * Tools (read-only — see the SAFETY BOUNDARY comment above):\n * - SelectSession / PlayRecording: select a recording by id OR name and load/play its transcript.\n * - DeselectSession: clear the current playback selection.\n * - RefreshSessionList: reload the master list of recorded sessions.\n * - SearchSessions: narrow the visible list by a client-side text filter (updates the list).\n * - SortSessions: re-order the visible list by date / agent / duration, asc or desc.\n */\n private registerAgentClientTools(): void {\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'SelectSession',\n Description: 'Select a recorded realtime session by its session ID OR name (the agent name, conversation name, or \"Agent — Conversation\" label) and load its transcript for playback.',\n ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectSession(params),\n },\n {\n Name: 'PlayRecording',\n Description: 'Open and play a recording by its session ID OR name — selects it and mounts the synchronized audio + transcript player (alias of SelectSession).',\n ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectSession(params),\n },\n {\n Name: 'DeselectSession',\n Description: 'Clear the currently selected recording, returning playback to the empty state.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n this.DeselectSession();\n return { Success: true } satisfies AgentToolResult;\n },\n },\n {\n Name: 'RefreshSessionList',\n Description: 'Reload the master list of recorded realtime sessions.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n await this.LoadData();\n return { Success: true, Data: { SessionCount: this.Sessions.length } };\n },\n },\n {\n Name: 'SearchSessions',\n Description: 'Narrow the visible recordings by a free-text query, matched against the agent name, conversation name, and captured-media type. Updates the list shown to the user (read-only; does not change the selection). Pass an empty string to clear the filter.',\n ParameterSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },\n Handler: async (params: Record<string, unknown>) => this.toolSearchSessions(params),\n },\n {\n Name: 'SortSessions',\n Description: 'Re-order the visible recordings by \"date\", \"agent\", or \"duration\", in \"asc\" or \"desc\" direction. Read-only display ordering.',\n ParameterSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', enum: ['date', 'agent', 'duration'] },\n direction: { type: 'string', enum: ['asc', 'desc'] },\n },\n required: ['field'],\n },\n Handler: async (params: Record<string, unknown>) => this.toolSortSessions(params),\n },\n ]);\n }\n\n /**\n * Resolve a session reference (id OR name) to a loaded recording and select it for playback.\n * Tolerant: returns a structured failure (with a sample of available recordings) on a miss.\n */\n private async toolSelectSession(params: Record<string, unknown>): Promise<AgentToolResult & { Data?: Record<string, unknown> }> {\n const validation = ValidateStringParam(params['session'], 'session');\n if (!validation.ok) {\n return validation.result;\n }\n const ref = validation.value.trim();\n if (ref.length === 0) {\n return { Success: false, ErrorMessage: 'A session ID or name is required.' };\n }\n const session = ResolveSessionByIdOrName(ref, this.Sessions);\n if (!session) {\n return { Success: false, ErrorMessage: BuildSessionNotFoundError(ref, this.Sessions) };\n }\n await this.SelectSession(session);\n return { Success: true, Data: { SelectedSessionId: session.ID, AgentName: session.AgentName } };\n }\n\n /** Apply a client-side text filter to the visible list (updates the UI + agent context). */\n private toolSearchSessions(params: Record<string, unknown>): AgentToolResult & { Data?: Record<string, unknown> } {\n const validation = ValidateStringParam(params['query'], 'query');\n if (!validation.ok) {\n return validation.result;\n }\n this.SearchQuery = validation.value;\n this.publishAgentContext();\n this.cdr.detectChanges();\n const matches = this.VisibleSessions;\n return {\n Success: true,\n Data: {\n MatchCount: matches.length,\n Matches: matches.map(s => ({\n ID: s.ID,\n AgentName: s.AgentName,\n ConversationName: s.ConversationName,\n RecordingMedia: s.RecordingMedia,\n DurationLabel: s.DurationLabel,\n })),\n },\n };\n }\n\n /** Re-order the visible list by date / agent / duration, asc or desc. Read-only display ordering. */\n private toolSortSessions(params: Record<string, unknown>): AgentToolResult & { Data?: Record<string, unknown> } {\n const rawField = params['field'];\n if (!IsValidSessionSortField(rawField)) {\n return { Success: false, ErrorMessage: 'field must be one of: date, agent, duration.' };\n }\n const rawDirection = params['direction'];\n // direction is optional — default to the sensible per-field default (date: desc, else asc).\n let direction: SessionSortDirection;\n if (rawDirection === undefined || rawDirection === null) {\n direction = rawField === 'date' ? 'desc' : 'asc';\n } else if (IsValidSessionSortDirection(rawDirection)) {\n direction = rawDirection;\n } else {\n return { Success: false, ErrorMessage: 'direction must be one of: asc, desc.' };\n }\n this.SortField = rawField;\n this.SortDirection = direction;\n this.publishAgentContext();\n this.cdr.detectChanges();\n return { Success: true, Data: { SortField: this.SortField, SortDirection: this.SortDirection } };\n }\n\n // ----- helpers ----------------------------------------------------------------------------\n\n /** Loads the transcript turns for a session as full entity objects (for the playback component). */\n private async loadTurns(sessionId: string): Promise<MJConversationDetailEntity[]> {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJConversationDetailEntity>({\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: `AgentSessionID='${sessionId}'`,\n OrderBy: '__mj_CreatedAt ASC',\n ResultType: 'entity_object'\n });\n if (!result.Success) {\n throw new Error(result.ErrorMessage || 'Failed to load transcript turns.');\n }\n return result.Results ?? [];\n }\n\n /**\n * Builds time-aligned {@link MediaTranscriptCue}s from the loaded turns, for the media player's\n * transcript panel. Skips turns with no text; derives each cue's start from `UtteranceStartMs`,\n * falling back to (turn-created − recording-start) ms, else 0. Cues sort by start ascending.\n */\n private buildCues(turns: MJConversationDetailEntity[], session: RecordedSession): MediaTranscriptCue[] {\n const startedAt = session.RecordingStartedAt;\n const cues: MediaTranscriptCue[] = [];\n turns.forEach((turn, index) => {\n const text = turn.Message?.trim() ?? '';\n if (text.length === 0) {\n return;\n }\n cues.push({\n Id: turn.ID || `turn-${index}`,\n StartMs: this.cueStartMs(turn, startedAt),\n EndMs: turn.UtteranceEndMs ?? undefined,\n SpeakerLabel: turn.Role === 'User' ? 'You' : session.AgentName,\n Text: text\n });\n });\n cues.sort((a, b) => a.StartMs - b.StartMs);\n return cues;\n }\n\n /** A turn's media-relative start (ms): precise offset when present, else derived from t0, else 0. */\n private cueStartMs(turn: MJConversationDetailEntity, startedAt: Date | null): number {\n if (turn.UtteranceStartMs != null) {\n return turn.UtteranceStartMs;\n }\n if (startedAt && turn.__mj_CreatedAt) {\n return Math.max(0, turn.__mj_CreatedAt.getTime() - startedAt.getTime());\n }\n return 0;\n }\n\n /** Projects a raw `simple` view row into the list model, deriving display fields. */\n private toRecordedSession(row: RecordedSessionRow): RecordedSession {\n const startedAt = this.toDate(row.RecordingStartedAt);\n const createdAt = this.toDate(row.__mj_CreatedAt);\n return {\n ID: row.ID,\n AgentName: row.Agent || 'Realtime Agent',\n ConversationName: row.Conversation ?? null,\n RecordingFileID: row.RecordingFileID,\n RecordingMedia: row.RecordingMedia ?? null,\n RecordingStartedAt: startedAt,\n CreatedAt: createdAt,\n DurationLabel: null\n };\n }\n\n /**\n * Once turns are loaded, derive a duration (t0 → last turn's end/start) and stamp it on the row\n * so the list shows it. Best-effort: skipped when we lack a recording start time or any turns.\n */\n private maybeBackfillDuration(session: RecordedSession, turns: MJConversationDetailEntity[]): void {\n if (session.DurationLabel || !session.RecordingStartedAt || turns.length === 0) {\n return;\n }\n const endMs = this.lastTurnOffsetMs(turns, session.RecordingStartedAt);\n if (endMs != null && endMs > 0) {\n session.DurationLabel = this.formatDuration(endMs);\n }\n }\n\n /** The end offset (ms) of the last turn relative to the recording start, or null. */\n private lastTurnOffsetMs(turns: MJConversationDetailEntity[], startedAt: Date): number | null {\n const last = turns[turns.length - 1];\n if (last.UtteranceEndMs != null) {\n return last.UtteranceEndMs;\n }\n if (last.UtteranceStartMs != null) {\n return last.UtteranceStartMs;\n }\n if (last.__mj_CreatedAt) {\n return last.__mj_CreatedAt.getTime() - startedAt.getTime();\n }\n return null;\n }\n\n /** Formats a ms span as `m:ss` (or `h:mm:ss` past an hour). */\n private formatDuration(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n /** Coerces a `simple` row's date-ish value (string | Date | null) to a Date or null. */\n private toDate(value: string | Date | null | undefined): Date | null {\n if (!value) {\n return null;\n }\n const date = value instanceof Date ? value : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n}\n\n/**\n * The raw `simple` row shape from the `MJ: AI Agent Sessions` view — denormalized name fields plus\n * the recording columns. `simple` rows carry plain values (dates may arrive as ISO strings).\n */\ninterface RecordedSessionRow {\n ID: string;\n Agent: string | null;\n Conversation: string | null;\n RecordingFileID: string;\n RecordingMedia: string | null;\n RecordingStartedAt: string | Date | null;\n __mj_CreatedAt: string | Date | null;\n}\n\n/**\n * Tree-shaking guard — referenced from the feature module's public-api so the bundler can't drop the\n * `@RegisterClass`-decorated component (which is only ever instantiated dynamically via the class factory).\n */\nexport function LoadRealtimeRecordingsDashboard(): void {\n // intentionally empty\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Realtime Recordings\"\n Icon=\"fa-solid fa-circle-play\"\n Subtitle=\"Review and replay recorded realtime sessions — time-aligned audio + transcript.\">\n @if (!IsLoading && Sessions.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"VisibleSessions.length\" [Total]=\"Sessions.length\" Label=\"recorded sessions\" />\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (IsLoading) {\n <div class=\"rr-state\">\n <mj-loading text=\"Loading recorded sessions...\"></mj-loading>\n </div>\n } @else if (ErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ ErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"LoadData()\">Try again</button>\n </div>\n } @else if (Sessions.length === 0) {\n <div class=\"rr-state rr-state--empty\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">No recorded sessions yet.</p>\n <p class=\"rr-state-hint\">\n When a realtime session is recorded, it will appear here for review and replay.\n </p>\n </div>\n } @else {\n <as-split direction=\"horizontal\" unit=\"pixel\" class=\"rr-split\">\n <as-split-area [size]=\"360\" [minSize]=\"260\" [maxSize]=\"560\">\n <ul class=\"rr-list\" role=\"listbox\" aria-label=\"Recorded sessions\">\n @for (session of VisibleSessions; track TrackBySession($index, session)) {\n <li\n class=\"rr-list-item\"\n role=\"option\"\n tabindex=\"0\"\n [class.rr-list-item--active]=\"SelectedSession?.ID === session.ID\"\n [attr.aria-selected]=\"SelectedSession?.ID === session.ID\"\n (click)=\"SelectSession(session)\"\n (keydown)=\"OnRowKeydown($event, session)\">\n <div class=\"rr-list-item-icon\" aria-hidden=\"true\">\n <i [class]=\"session.RecordingMedia === 'AudioVideo' ? 'fa-solid fa-video' : 'fa-solid fa-wave-square'\"></i>\n </div>\n <div class=\"rr-list-item-body\">\n <div class=\"rr-list-item-title\">{{ session.AgentName }}</div>\n <div class=\"rr-list-item-meta\">\n <span class=\"rr-list-item-date\">\n <i class=\"fa-regular fa-clock\" aria-hidden=\"true\"></i>\n {{ SessionDateLabel(session) }}\n </span>\n @if (session.DurationLabel) {\n <span class=\"rr-list-item-duration\">\n <i class=\"fa-solid fa-stopwatch\" aria-hidden=\"true\"></i>\n {{ session.DurationLabel }}\n </span>\n }\n </div>\n @if (session.ConversationName) {\n <div class=\"rr-list-item-conversation\" [title]=\"session.ConversationName\">\n <i class=\"fa-regular fa-comments\" aria-hidden=\"true\"></i>\n {{ session.ConversationName }}\n </div>\n }\n </div>\n </li>\n }\n </ul>\n </as-split-area>\n\n <as-split-area [size]=\"640\" [minSize]=\"360\">\n <div class=\"rr-detail\">\n @if (!SelectedSession) {\n <div class=\"rr-state rr-state--empty rr-detail-empty\">\n <i class=\"fa-solid fa-hand-pointer\" aria-hidden=\"true\"></i>\n <p class=\"rr-state-title\">Select a session to replay</p>\n <p class=\"rr-state-hint\">\n Pick a recording from the list to hear its audio alongside the synchronized transcript.\n </p>\n </div>\n } @else if (IsDetailLoading) {\n <div class=\"rr-state rr-detail-loading\">\n <mj-loading text=\"Loading recording...\"></mj-loading>\n </div>\n } @else if (DetailErrorMessage) {\n <div class=\"rr-state rr-state--error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <p>{{ DetailErrorMessage }}</p>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"SelectSession(SelectedSession!)\">Try again</button>\n </div>\n } @else {\n <mj-storage-media-player\n [FileID]=\"SelectedSession.RecordingFileID\"\n [Transcript]=\"SelectedCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n }\n </div>\n </as-split-area>\n </as-split>\n }\n </mj-page-body>\n</mj-page-layout>\n"]}
|
|
@@ -30,6 +30,7 @@ export declare class TelephonyOperationsDashboardComponent extends BaseResourceC
|
|
|
30
30
|
RawPhoneNumbers: PhoneNumberRecordLike[];
|
|
31
31
|
Metrics: OperationsMetrics;
|
|
32
32
|
private refreshTimer;
|
|
33
|
+
private destroyed;
|
|
33
34
|
private readonly cdr;
|
|
34
35
|
GetResourceDisplayName(_data: ResourceData): Promise<string>;
|
|
35
36
|
GetResourceIconClass(_data: ResourceData): Promise<string>;
|
|
@@ -1 +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;
|
|
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,SAAS,CAAS;IAC1B,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;IAkBxC,eAAe,IAAI,IAAI;IAKd,WAAW,IAAI,IAAI;IASf,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;yCA7OrB,qCAAqC;2CAArC,qCAAqC;CA0SjD"}
|
|
@@ -308,6 +308,7 @@ let TelephonyOperationsDashboardComponent = class TelephonyOperationsDashboardCo
|
|
|
308
308
|
RawPhoneNumbers = [];
|
|
309
309
|
Metrics = CalculateOperationsMetrics([], [], []);
|
|
310
310
|
refreshTimer = null;
|
|
311
|
+
destroyed = false;
|
|
311
312
|
cdr = inject(ChangeDetectorRef);
|
|
312
313
|
async GetResourceDisplayName(_data) {
|
|
313
314
|
return 'Telephony & Interaction Operations';
|
|
@@ -319,6 +320,11 @@ let TelephonyOperationsDashboardComponent = class TelephonyOperationsDashboardCo
|
|
|
319
320
|
super.ngOnInit();
|
|
320
321
|
await this.LoadData();
|
|
321
322
|
this.NotifyLoadComplete();
|
|
323
|
+
// The tab may have been closed while LoadData was in flight; ngOnDestroy already ran and
|
|
324
|
+
// found no timer, so arming one now would leak an interval on a destroyed component.
|
|
325
|
+
if (this.destroyed) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
322
328
|
// Auto-refresh live status every 15 seconds
|
|
323
329
|
this.refreshTimer = setInterval(() => {
|
|
324
330
|
this.recalculateMetrics();
|
|
@@ -330,6 +336,7 @@ let TelephonyOperationsDashboardComponent = class TelephonyOperationsDashboardCo
|
|
|
330
336
|
this.registerAgentClientTools();
|
|
331
337
|
}
|
|
332
338
|
ngOnDestroy() {
|
|
339
|
+
this.destroyed = true;
|
|
333
340
|
super.ngOnDestroy();
|
|
334
341
|
if (this.refreshTimer) {
|
|
335
342
|
clearInterval(this.refreshTimer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telephony-operations-dashboard.component.js","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-dashboard.component.ts","../../src/TelephonyOperations/telephony-operations-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { RunView } from '@memberjunction/core';\nimport { ResourceData } from '@memberjunction/core-entities';\nimport {\n BuildTelephonyOperationsAgentContext,\n CalculateOperationsMetrics,\n FormatCurrency,\n FormatDuration,\n ValidateChannelParam,\n ValidateTimeRangeParam,\n type ChannelFilter,\n type InteractionEventRecordLike,\n type InteractionRecordLike,\n type OperationsMetrics,\n type PhoneNumberRecordLike,\n type TimeRangeFilter,\n} from './telephony-operations-agent-context';\n\n/**\n * TELEPHONY & INTERACTION OPERATIONS DASHBOARD\n *\n * An Explorer L3 dashboard providing operational analytics across phone calls, web realtime\n * sessions, and multi-party meetings.\n *\n * Metrics computed from `MJ: Interactions`, `MJ: Interaction Events`, and `MJ: Phone Numbers`:\n * - Volume by channel (Phone, Web, Meeting) and direction (Inbound, Outbound, Internal)\n * - Answer rate and abandonment rate\n * - Average handle time (AHT) and speed to answer (ASA)\n * - Transfer and escalation volumes\n * - Cost estimates by day and by phone number\n * - Active live calls / sessions in real time\n *\n * Data is loaded in a single batched `RunViews` call and aggregated in memory.\n */\n@RegisterClass(BaseResourceComponent, 'TelephonyOperationsDashboard')\n@RegisterClass(BaseResourceComponent, 'InteractionOperationsDashboard')\n@Component({\n standalone: false,\n selector: 'mj-telephony-operations-dashboard',\n templateUrl: './telephony-operations-dashboard.component.html',\n styleUrls: ['./telephony-operations-dashboard.component.css'],\n})\nexport class TelephonyOperationsDashboardComponent\n extends BaseResourceComponent\n implements OnInit, OnDestroy, AfterViewInit\n{\n public IsLoading = true;\n public ErrorMessage: string | null = null;\n\n public SelectedTimeRange: TimeRangeFilter = '7d';\n public SelectedChannelFilter: ChannelFilter = 'all';\n public SearchQuery = '';\n\n public RawInteractions: InteractionRecordLike[] = [];\n public RawEvents: InteractionEventRecordLike[] = [];\n public RawPhoneNumbers: PhoneNumberRecordLike[] = [];\n\n public Metrics: OperationsMetrics = CalculateOperationsMetrics([], [], []);\n\n private refreshTimer: ReturnType<typeof setInterval> | null = null;\n private readonly cdr = inject(ChangeDetectorRef);\n\n override async GetResourceDisplayName(_data: ResourceData): Promise<string> {\n return 'Telephony & Interaction Operations';\n }\n\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-chart-line';\n }\n\n override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n await this.LoadData();\n this.NotifyLoadComplete();\n\n // Auto-refresh live status every 15 seconds\n this.refreshTimer = setInterval(() => {\n this.recalculateMetrics();\n this.cdr.markForCheck();\n }, 15000);\n }\n\n ngAfterViewInit(): void {\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n override ngOnDestroy(): void {\n super.ngOnDestroy();\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n\n public async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.ErrorMessage = null;\n this.cdr.markForCheck();\n\n try {\n const dateFilterInteractions = this.getDateFilter('StartedAt');\n const dateFilterEvents = this.getDateFilter('OccurredAt');\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const [interactionsResult, eventsResult, phoneNumbersResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Interactions',\n ResultType: 'simple',\n ExtraFilter: dateFilterInteractions,\n OrderBy: 'StartedAt DESC',\n MaxRows: 1000,\n },\n {\n EntityName: 'MJ: Interaction Events',\n ResultType: 'simple',\n ExtraFilter: dateFilterEvents,\n OrderBy: 'OccurredAt DESC',\n MaxRows: 2500,\n },\n {\n EntityName: 'MJ: Phone Numbers',\n ResultType: 'simple',\n ExtraFilter: \"Status = 'Active'\",\n MaxRows: 200,\n },\n ]);\n\n if (interactionsResult.Success && Array.isArray(interactionsResult.Results)) {\n this.RawInteractions = interactionsResult.Results as unknown as InteractionRecordLike[];\n } else {\n this.RawInteractions = [];\n }\n\n if (eventsResult.Success && Array.isArray(eventsResult.Results)) {\n this.RawEvents = eventsResult.Results as unknown as InteractionEventRecordLike[];\n } else {\n this.RawEvents = [];\n }\n\n if (phoneNumbersResult.Success && Array.isArray(phoneNumbersResult.Results)) {\n this.RawPhoneNumbers = phoneNumbersResult.Results as unknown as PhoneNumberRecordLike[];\n } else {\n this.RawPhoneNumbers = [];\n }\n\n this.recalculateMetrics();\n } catch (err) {\n this.ErrorMessage = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n this.publishAgentContext();\n }\n }\n\n public SetTimeRange(range: TimeRangeFilter): void {\n if (this.SelectedTimeRange !== range) {\n this.SelectedTimeRange = range;\n void this.LoadData();\n }\n }\n\n public SetChannelFilter(channel: ChannelFilter): void {\n if (this.SelectedChannelFilter !== channel) {\n this.SelectedChannelFilter = channel;\n this.recalculateMetrics();\n this.cdr.markForCheck();\n this.publishAgentContext();\n }\n }\n\n public OnSearchQueryChange(query: string): void {\n this.SearchQuery = query;\n this.cdr.markForCheck();\n }\n\n public get FilteredLiveInteractions(): OperationsMetrics['LiveInteractions'] {\n if (!this.SearchQuery.trim()) {\n return this.Metrics.LiveInteractions;\n }\n const q = this.SearchQuery.toLowerCase();\n return this.Metrics.LiveInteractions.filter(\n (i) =>\n (i.RemoteAddress && i.RemoteAddress.toLowerCase().includes(q)) ||\n i.Channel.toLowerCase().includes(q) ||\n i.Direction.toLowerCase().includes(q) ||\n i.ID.toLowerCase().includes(q),\n );\n }\n\n public get FilteredRecentInteractions(): InteractionRecordLike[] {\n let rows = this.filteredRawInteractions;\n if (this.SearchQuery.trim()) {\n const q = this.SearchQuery.toLowerCase();\n rows = rows.filter(\n (r) =>\n (r.RemoteAddress && r.RemoteAddress.toLowerCase().includes(q)) ||\n (r.EndReason && r.EndReason.toLowerCase().includes(q)) ||\n r.Channel.toLowerCase().includes(q) ||\n r.Direction.toLowerCase().includes(q) ||\n r.ID.toLowerCase().includes(q),\n );\n }\n return rows.slice(0, 15);\n }\n\n public FormatCost(amount: number | null): string {\n return FormatCurrency(amount ?? 0);\n }\n\n public FormatTime(date: Date | string | null): string {\n if (!date) return '-';\n const d = date instanceof Date ? date : new Date(date);\n return isNaN(d.getTime()) ? '-' : d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n\n public FormatDate(date: Date | string | null): string {\n if (!date) return '-';\n const d = date instanceof Date ? date : new Date(date);\n return isNaN(d.getTime()) ? '-' : d.toLocaleDateString([], { month: 'short', day: 'numeric' });\n }\n\n public ComputeDuration(started: Date | string, ended: Date | string | null): string {\n const s = started instanceof Date ? started : new Date(started);\n if (isNaN(s.getTime())) return '-';\n const e = ended ? (ended instanceof Date ? ended : new Date(ended)) : new Date();\n if (isNaN(e.getTime())) return '-';\n const sec = Math.max(0, Math.floor((e.getTime() - s.getTime()) / 1000));\n return FormatDuration(sec);\n }\n\n private get filteredRawInteractions(): InteractionRecordLike[] {\n if (this.SelectedChannelFilter === 'all') {\n return this.RawInteractions;\n }\n return this.RawInteractions.filter((i) => i.Channel === this.SelectedChannelFilter);\n }\n\n private recalculateMetrics(): void {\n const filtered = this.filteredRawInteractions;\n const activeIds = new Set(filtered.map((i) => i.ID.toLowerCase()));\n const relevantEvents = this.RawEvents.filter((e) => activeIds.has(e.InteractionID.toLowerCase()));\n\n this.Metrics = CalculateOperationsMetrics(filtered, relevantEvents, this.RawPhoneNumbers);\n }\n\n private getDateFilter(field: string): string {\n switch (this.SelectedTimeRange) {\n case '24h':\n return `${field} >= DATEADD(hour, -24, GETUTCDATE())`;\n case '7d':\n return `${field} >= DATEADD(day, -7, GETUTCDATE())`;\n case '30d':\n return `${field} >= DATEADD(day, -30, GETUTCDATE())`;\n case 'all':\n default:\n return '1=1';\n }\n }\n\n private publishAgentContext(): void {\n if (!this.navigationService) return;\n const snapshot = BuildTelephonyOperationsAgentContext(\n this.Metrics,\n this.SelectedTimeRange,\n this.SelectedChannelFilter,\n );\n this.navigationService.SetAgentContext(this, snapshot);\n }\n\n private registerAgentClientTools(): void {\n if (!this.navigationService) return;\n\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'RefreshOperationsData',\n Description: 'Refresh telephony and interaction operations data from the database.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n await this.LoadData();\n return { Success: true };\n },\n },\n {\n Name: 'SetTimeRange',\n Description: 'Set the active time range window (24h, 7d, 30d, all).',\n ParameterSchema: {\n type: 'object',\n properties: {\n timeRange: {\n type: 'string',\n description: 'Time range to display: 24h, 7d, 30d, or all',\n enum: ['24h', '7d', '30d', 'all'],\n },\n },\n required: ['timeRange'],\n },\n Handler: async (params: Record<string, unknown>) => {\n const validated = ValidateTimeRangeParam(params['timeRange']);\n if (!validated.ok) {\n return validated.result;\n }\n this.SetTimeRange(validated.value);\n return { Success: true };\n },\n },\n {\n Name: 'FilterByChannel',\n Description: 'Filter operations metrics by interaction channel (all, Phone, Web, Meeting).',\n ParameterSchema: {\n type: 'object',\n properties: {\n channel: {\n type: 'string',\n description: 'Channel to filter: all, Phone, Web, or Meeting',\n enum: ['all', 'Phone', 'Web', 'Meeting'],\n },\n },\n required: ['channel'],\n },\n Handler: async (params: Record<string, unknown>) => {\n const validated = ValidateChannelParam(params['channel']);\n if (!validated.ok) {\n return validated.result;\n }\n this.SetChannelFilter(validated.value);\n return { Success: true };\n },\n },\n ]);\n }\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Telephony & Interaction Operations\"\n Subtitle=\"Real-time metrics, volume breakdown, answer rates, and cost analysis across channels\"\n Icon=\"fa-solid fa-chart-line\"\n >\n <div meta>\n <mj-stat-badge\n [Count]=\"Metrics.LiveCallsCount\"\n [Total]=\"Metrics.TotalVolume\"\n Label=\"live now\"\n [Variant]=\"Metrics.LiveCallsCount > 0 ? 'success' : 'default'\"\n />\n </div>\n\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n\n <div toolbar>\n <div class=\"toolbar-controls\">\n <mj-page-search [Value]=\"SearchQuery\" (ValueChange)=\"OnSearchQueryChange($event)\" />\n\n <div class=\"time-range-pills\" role=\"radiogroup\" aria-label=\"Time Range\">\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '24h'\"\n (click)=\"SetTimeRange('24h')\"\n >\n 24h\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '7d'\"\n (click)=\"SetTimeRange('7d')\"\n >\n 7d\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '30d'\"\n (click)=\"SetTimeRange('30d')\"\n >\n 30d\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === 'all'\"\n (click)=\"SetTimeRange('all')\"\n >\n All Time\n </button>\n </div>\n\n <div class=\"channel-filter-pills\" role=\"radiogroup\" aria-label=\"Channel Filter\">\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'all'\"\n (click)=\"SetChannelFilter('all')\"\n >\n All Channels\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Phone'\"\n (click)=\"SetChannelFilter('Phone')\"\n >\n <i class=\"fa-solid fa-phone\"></i> Phone\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Web'\"\n (click)=\"SetChannelFilter('Web')\"\n >\n <i class=\"fa-solid fa-globe\"></i> Web\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Meeting'\"\n (click)=\"SetChannelFilter('Meeting')\"\n >\n <i class=\"fa-solid fa-users\"></i> Meeting\n </button>\n </div>\n </div>\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (ErrorMessage) {\n <div class=\"error-banner\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n\n <!-- KPI Metric Cards Grid -->\n <div class=\"kpi-grid\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap primary\">\n <i class=\"fa-solid fa-headset\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Total Volume</span>\n <span class=\"kpi-value\">{{ Metrics.TotalVolume }}</span>\n <span class=\"kpi-subtext\">in selected window</span>\n </div>\n </div>\n\n <div class=\"kpi-card\" [class.live-active]=\"Metrics.LiveCallsCount > 0\">\n <div class=\"kpi-icon-wrap success\">\n <span class=\"live-dot-ping\"></span>\n <i class=\"fa-solid fa-phone-volume\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Live Now</span>\n <span class=\"kpi-value\">{{ Metrics.LiveCallsCount }}</span>\n <span class=\"kpi-subtext\">active sessions</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap success\">\n <i class=\"fa-solid fa-circle-check\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Answer Rate</span>\n <span class=\"kpi-value\">{{ Metrics.AnswerRate | number:'1.0-1' }}%</span>\n <span class=\"kpi-subtext\">{{ Metrics.AnsweredCount }} answered</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap warning\">\n <i class=\"fa-solid fa-phone-slash\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Abandonment Rate</span>\n <span class=\"kpi-value\">{{ Metrics.AbandonmentRate | number:'1.0-1' }}%</span>\n <span class=\"kpi-subtext\">{{ Metrics.AbandonedCount }} caller abandons</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap info\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Avg Handle Time</span>\n <span class=\"kpi-value\">{{ Metrics.AverageHandleTimeFormatted }}</span>\n <span class=\"kpi-subtext\">ASA: {{ Metrics.AverageSpeedToAnswerFormatted }}</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap accent\">\n <i class=\"fa-solid fa-arrows-split-up-and-left\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Transfers & Esc.</span>\n <span class=\"kpi-value\">{{ Metrics.TransferCount }} / {{ Metrics.EscalationCount }}</span>\n <span class=\"kpi-subtext\">transfers / escalations</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap cost\">\n <i class=\"fa-solid fa-dollar-sign\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Total Cost Est.</span>\n <span class=\"kpi-value\">{{ Metrics.TotalCostFormatted }}</span>\n <span class=\"kpi-subtext\">usage & carrier minutes</span>\n </div>\n </div>\n </div>\n\n <!-- Active Live Calls Section -->\n <div class=\"dashboard-section live-section\">\n <div class=\"section-header\">\n <div class=\"section-title\">\n <span class=\"live-pulse-badge\"></span>\n <h2>Active Live Sessions</h2>\n <span class=\"count-pill\">{{ FilteredLiveInteractions.length }}</span>\n </div>\n </div>\n\n @if (FilteredLiveInteractions.length === 0) {\n <div class=\"empty-feed\">\n <i class=\"fa-solid fa-phone-slash empty-icon\"></i>\n <p>No active live interactions at this moment.</p>\n </div>\n } @else {\n <div class=\"live-table-container\">\n <table class=\"ops-table\">\n <thead>\n <tr>\n <th>Remote Address</th>\n <th>Channel</th>\n <th>Direction</th>\n <th>Status</th>\n <th>Started</th>\n <th>Elapsed</th>\n </tr>\n </thead>\n <tbody>\n @for (live of FilteredLiveInteractions; track live.ID) {\n <tr class=\"live-row\">\n <td class=\"address-col\">\n <i class=\"fa-solid fa-user-circle address-icon\"></i>\n <span class=\"address-text\">{{ live.RemoteAddress || 'Unknown' }}</span>\n </td>\n <td>\n <span class=\"badge channel-badge {{ live.Channel.toLowerCase() }}\">\n {{ live.Channel }}\n </span>\n </td>\n <td>\n <span class=\"badge direction-badge {{ live.Direction.toLowerCase() }}\">\n {{ live.Direction }}\n </span>\n </td>\n <td>\n <span class=\"status-indicator active\">\n <span class=\"dot\"></span>\n {{ live.Status }}\n </span>\n </td>\n <td>{{ FormatTime(live.StartedAt) }}</td>\n <td class=\"elapsed-col\">\n <span class=\"elapsed-badge\">{{ live.ElapsedLabel }}</span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n\n <!-- Middle Row: Volume Breakdown Cards -->\n <div class=\"two-col-grid\">\n <!-- Volume by Channel & Direction -->\n <div class=\"dashboard-card\">\n <div class=\"card-header\">\n <h3><i class=\"fa-solid fa-layer-group\"></i> Volume Distribution</h3>\n </div>\n <div class=\"card-body\">\n <div class=\"distribution-group\">\n <h4>By Channel</h4>\n <div class=\"bars-container\">\n @for (item of Metrics.VolumeByChannel; track item.Channel) {\n <div class=\"bar-row\">\n <div class=\"bar-info\">\n <span class=\"bar-label\">{{ item.Channel }}</span>\n <span class=\"bar-stats\">{{ item.Count }} ({{ item.Percentage }}%)</span>\n </div>\n <div class=\"bar-track\">\n <div class=\"bar-fill channel-{{ item.Channel.toLowerCase() }}\" [style.width.%]=\"item.Percentage\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <div class=\"distribution-group\">\n <h4>By Direction</h4>\n <div class=\"bars-container\">\n @for (item of Metrics.VolumeByDirection; track item.Direction) {\n <div class=\"bar-row\">\n <div class=\"bar-info\">\n <span class=\"bar-label\">{{ item.Direction }}</span>\n <span class=\"bar-stats\">{{ item.Count }} ({{ item.Percentage }}%)</span>\n </div>\n <div class=\"bar-track\">\n <div class=\"bar-fill direction-{{ item.Direction.toLowerCase() }}\" [style.width.%]=\"item.Percentage\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Cost by Number -->\n <div class=\"dashboard-card\">\n <div class=\"card-header\">\n <h3><i class=\"fa-solid fa-money-bill-wave\"></i> Cost by Phone Number</h3>\n </div>\n <div class=\"card-body\">\n @if (Metrics.CostByNumber.length === 0) {\n <div class=\"empty-feed\">\n <p>No phone number cost records in this window.</p>\n </div>\n } @else {\n <div class=\"cost-table-container\">\n <table class=\"ops-table compact\">\n <thead>\n <tr>\n <th>Phone Number / Target</th>\n <th>Calls</th>\n <th class=\"text-right\">Est. Cost</th>\n </tr>\n </thead>\n <tbody>\n @for (num of Metrics.CostByNumber; track num.PhoneNumber) {\n <tr>\n <td class=\"font-medium\">{{ num.PhoneNumber }}</td>\n <td>{{ num.Count }}</td>\n <td class=\"text-right font-semibold\">{{ FormatCost(num.Cost) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Bottom Row: Recent Interactions Audit Table -->\n <div class=\"dashboard-section\">\n <div class=\"section-header\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-list-check\"></i>\n <h2>Recent Interactions</h2>\n <span class=\"count-pill\">{{ FilteredRecentInteractions.length }}</span>\n </div>\n </div>\n\n @if (FilteredRecentInteractions.length === 0) {\n <div class=\"empty-feed\">\n <p>No interactions found matching criteria.</p>\n </div>\n } @else {\n <div class=\"table-container\">\n <table class=\"ops-table\">\n <thead>\n <tr>\n <th>Date / Time</th>\n <th>Remote Party</th>\n <th>Channel</th>\n <th>Direction</th>\n <th>Status</th>\n <th>Duration</th>\n <th>End Reason</th>\n <th class=\"text-right\">Est. Cost</th>\n </tr>\n </thead>\n <tbody>\n @for (row of FilteredRecentInteractions; track row.ID) {\n <tr>\n <td>\n <div class=\"datetime-cell\">\n <span class=\"date\">{{ FormatDate(row.StartedAt) }}</span>\n <span class=\"time\">{{ FormatTime(row.StartedAt) }}</span>\n </div>\n </td>\n <td class=\"address-col\">\n <span>{{ row.RemoteAddress || 'Unknown' }}</span>\n </td>\n <td>\n <span class=\"badge channel-badge {{ row.Channel.toLowerCase() }}\">\n {{ row.Channel }}\n </span>\n </td>\n <td>\n <span class=\"badge direction-badge {{ row.Direction.toLowerCase() }}\">\n {{ row.Direction }}\n </span>\n </td>\n <td>\n <span class=\"status-indicator {{ row.Status.toLowerCase() }}\">\n <span class=\"dot\"></span>\n {{ row.Status }}\n </span>\n </td>\n <td>{{ ComputeDuration(row.StartedAt, row.EndedAt) }}</td>\n <td class=\"end-reason-cell\">{{ row.EndReason || '-' }}</td>\n <td class=\"text-right\">{{ FormatCost(row.CostEstimate) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n </mj-page-body>\n</mj-page-layout>\n"]}
|
|
1
|
+
{"version":3,"file":"telephony-operations-dashboard.component.js","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-dashboard.component.ts","../../src/TelephonyOperations/telephony-operations-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { RunView } from '@memberjunction/core';\nimport { ResourceData } from '@memberjunction/core-entities';\nimport {\n BuildTelephonyOperationsAgentContext,\n CalculateOperationsMetrics,\n FormatCurrency,\n FormatDuration,\n ValidateChannelParam,\n ValidateTimeRangeParam,\n type ChannelFilter,\n type InteractionEventRecordLike,\n type InteractionRecordLike,\n type OperationsMetrics,\n type PhoneNumberRecordLike,\n type TimeRangeFilter,\n} from './telephony-operations-agent-context';\n\n/**\n * TELEPHONY & INTERACTION OPERATIONS DASHBOARD\n *\n * An Explorer L3 dashboard providing operational analytics across phone calls, web realtime\n * sessions, and multi-party meetings.\n *\n * Metrics computed from `MJ: Interactions`, `MJ: Interaction Events`, and `MJ: Phone Numbers`:\n * - Volume by channel (Phone, Web, Meeting) and direction (Inbound, Outbound, Internal)\n * - Answer rate and abandonment rate\n * - Average handle time (AHT) and speed to answer (ASA)\n * - Transfer and escalation volumes\n * - Cost estimates by day and by phone number\n * - Active live calls / sessions in real time\n *\n * Data is loaded in a single batched `RunViews` call and aggregated in memory.\n */\n@RegisterClass(BaseResourceComponent, 'TelephonyOperationsDashboard')\n@RegisterClass(BaseResourceComponent, 'InteractionOperationsDashboard')\n@Component({\n standalone: false,\n selector: 'mj-telephony-operations-dashboard',\n templateUrl: './telephony-operations-dashboard.component.html',\n styleUrls: ['./telephony-operations-dashboard.component.css'],\n})\nexport class TelephonyOperationsDashboardComponent\n extends BaseResourceComponent\n implements OnInit, OnDestroy, AfterViewInit\n{\n public IsLoading = true;\n public ErrorMessage: string | null = null;\n\n public SelectedTimeRange: TimeRangeFilter = '7d';\n public SelectedChannelFilter: ChannelFilter = 'all';\n public SearchQuery = '';\n\n public RawInteractions: InteractionRecordLike[] = [];\n public RawEvents: InteractionEventRecordLike[] = [];\n public RawPhoneNumbers: PhoneNumberRecordLike[] = [];\n\n public Metrics: OperationsMetrics = CalculateOperationsMetrics([], [], []);\n\n private refreshTimer: ReturnType<typeof setInterval> | null = null;\n private destroyed = false;\n private readonly cdr = inject(ChangeDetectorRef);\n\n override async GetResourceDisplayName(_data: ResourceData): Promise<string> {\n return 'Telephony & Interaction Operations';\n }\n\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-chart-line';\n }\n\n override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n await this.LoadData();\n this.NotifyLoadComplete();\n\n // The tab may have been closed while LoadData was in flight; ngOnDestroy already ran and\n // found no timer, so arming one now would leak an interval on a destroyed component.\n if (this.destroyed) {\n return;\n }\n\n // Auto-refresh live status every 15 seconds\n this.refreshTimer = setInterval(() => {\n this.recalculateMetrics();\n this.cdr.markForCheck();\n }, 15000);\n }\n\n ngAfterViewInit(): void {\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n override ngOnDestroy(): void {\n this.destroyed = true;\n super.ngOnDestroy();\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n\n public async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.ErrorMessage = null;\n this.cdr.markForCheck();\n\n try {\n const dateFilterInteractions = this.getDateFilter('StartedAt');\n const dateFilterEvents = this.getDateFilter('OccurredAt');\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const [interactionsResult, eventsResult, phoneNumbersResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Interactions',\n ResultType: 'simple',\n ExtraFilter: dateFilterInteractions,\n OrderBy: 'StartedAt DESC',\n MaxRows: 1000,\n },\n {\n EntityName: 'MJ: Interaction Events',\n ResultType: 'simple',\n ExtraFilter: dateFilterEvents,\n OrderBy: 'OccurredAt DESC',\n MaxRows: 2500,\n },\n {\n EntityName: 'MJ: Phone Numbers',\n ResultType: 'simple',\n ExtraFilter: \"Status = 'Active'\",\n MaxRows: 200,\n },\n ]);\n\n if (interactionsResult.Success && Array.isArray(interactionsResult.Results)) {\n this.RawInteractions = interactionsResult.Results as unknown as InteractionRecordLike[];\n } else {\n this.RawInteractions = [];\n }\n\n if (eventsResult.Success && Array.isArray(eventsResult.Results)) {\n this.RawEvents = eventsResult.Results as unknown as InteractionEventRecordLike[];\n } else {\n this.RawEvents = [];\n }\n\n if (phoneNumbersResult.Success && Array.isArray(phoneNumbersResult.Results)) {\n this.RawPhoneNumbers = phoneNumbersResult.Results as unknown as PhoneNumberRecordLike[];\n } else {\n this.RawPhoneNumbers = [];\n }\n\n this.recalculateMetrics();\n } catch (err) {\n this.ErrorMessage = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n this.publishAgentContext();\n }\n }\n\n public SetTimeRange(range: TimeRangeFilter): void {\n if (this.SelectedTimeRange !== range) {\n this.SelectedTimeRange = range;\n void this.LoadData();\n }\n }\n\n public SetChannelFilter(channel: ChannelFilter): void {\n if (this.SelectedChannelFilter !== channel) {\n this.SelectedChannelFilter = channel;\n this.recalculateMetrics();\n this.cdr.markForCheck();\n this.publishAgentContext();\n }\n }\n\n public OnSearchQueryChange(query: string): void {\n this.SearchQuery = query;\n this.cdr.markForCheck();\n }\n\n public get FilteredLiveInteractions(): OperationsMetrics['LiveInteractions'] {\n if (!this.SearchQuery.trim()) {\n return this.Metrics.LiveInteractions;\n }\n const q = this.SearchQuery.toLowerCase();\n return this.Metrics.LiveInteractions.filter(\n (i) =>\n (i.RemoteAddress && i.RemoteAddress.toLowerCase().includes(q)) ||\n i.Channel.toLowerCase().includes(q) ||\n i.Direction.toLowerCase().includes(q) ||\n i.ID.toLowerCase().includes(q),\n );\n }\n\n public get FilteredRecentInteractions(): InteractionRecordLike[] {\n let rows = this.filteredRawInteractions;\n if (this.SearchQuery.trim()) {\n const q = this.SearchQuery.toLowerCase();\n rows = rows.filter(\n (r) =>\n (r.RemoteAddress && r.RemoteAddress.toLowerCase().includes(q)) ||\n (r.EndReason && r.EndReason.toLowerCase().includes(q)) ||\n r.Channel.toLowerCase().includes(q) ||\n r.Direction.toLowerCase().includes(q) ||\n r.ID.toLowerCase().includes(q),\n );\n }\n return rows.slice(0, 15);\n }\n\n public FormatCost(amount: number | null): string {\n return FormatCurrency(amount ?? 0);\n }\n\n public FormatTime(date: Date | string | null): string {\n if (!date) return '-';\n const d = date instanceof Date ? date : new Date(date);\n return isNaN(d.getTime()) ? '-' : d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n\n public FormatDate(date: Date | string | null): string {\n if (!date) return '-';\n const d = date instanceof Date ? date : new Date(date);\n return isNaN(d.getTime()) ? '-' : d.toLocaleDateString([], { month: 'short', day: 'numeric' });\n }\n\n public ComputeDuration(started: Date | string, ended: Date | string | null): string {\n const s = started instanceof Date ? started : new Date(started);\n if (isNaN(s.getTime())) return '-';\n const e = ended ? (ended instanceof Date ? ended : new Date(ended)) : new Date();\n if (isNaN(e.getTime())) return '-';\n const sec = Math.max(0, Math.floor((e.getTime() - s.getTime()) / 1000));\n return FormatDuration(sec);\n }\n\n private get filteredRawInteractions(): InteractionRecordLike[] {\n if (this.SelectedChannelFilter === 'all') {\n return this.RawInteractions;\n }\n return this.RawInteractions.filter((i) => i.Channel === this.SelectedChannelFilter);\n }\n\n private recalculateMetrics(): void {\n const filtered = this.filteredRawInteractions;\n const activeIds = new Set(filtered.map((i) => i.ID.toLowerCase()));\n const relevantEvents = this.RawEvents.filter((e) => activeIds.has(e.InteractionID.toLowerCase()));\n\n this.Metrics = CalculateOperationsMetrics(filtered, relevantEvents, this.RawPhoneNumbers);\n }\n\n private getDateFilter(field: string): string {\n switch (this.SelectedTimeRange) {\n case '24h':\n return `${field} >= DATEADD(hour, -24, GETUTCDATE())`;\n case '7d':\n return `${field} >= DATEADD(day, -7, GETUTCDATE())`;\n case '30d':\n return `${field} >= DATEADD(day, -30, GETUTCDATE())`;\n case 'all':\n default:\n return '1=1';\n }\n }\n\n private publishAgentContext(): void {\n if (!this.navigationService) return;\n const snapshot = BuildTelephonyOperationsAgentContext(\n this.Metrics,\n this.SelectedTimeRange,\n this.SelectedChannelFilter,\n );\n this.navigationService.SetAgentContext(this, snapshot);\n }\n\n private registerAgentClientTools(): void {\n if (!this.navigationService) return;\n\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'RefreshOperationsData',\n Description: 'Refresh telephony and interaction operations data from the database.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n await this.LoadData();\n return { Success: true };\n },\n },\n {\n Name: 'SetTimeRange',\n Description: 'Set the active time range window (24h, 7d, 30d, all).',\n ParameterSchema: {\n type: 'object',\n properties: {\n timeRange: {\n type: 'string',\n description: 'Time range to display: 24h, 7d, 30d, or all',\n enum: ['24h', '7d', '30d', 'all'],\n },\n },\n required: ['timeRange'],\n },\n Handler: async (params: Record<string, unknown>) => {\n const validated = ValidateTimeRangeParam(params['timeRange']);\n if (!validated.ok) {\n return validated.result;\n }\n this.SetTimeRange(validated.value);\n return { Success: true };\n },\n },\n {\n Name: 'FilterByChannel',\n Description: 'Filter operations metrics by interaction channel (all, Phone, Web, Meeting).',\n ParameterSchema: {\n type: 'object',\n properties: {\n channel: {\n type: 'string',\n description: 'Channel to filter: all, Phone, Web, or Meeting',\n enum: ['all', 'Phone', 'Web', 'Meeting'],\n },\n },\n required: ['channel'],\n },\n Handler: async (params: Record<string, unknown>) => {\n const validated = ValidateChannelParam(params['channel']);\n if (!validated.ok) {\n return validated.result;\n }\n this.SetChannelFilter(validated.value);\n return { Success: true };\n },\n },\n ]);\n }\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Telephony & Interaction Operations\"\n Subtitle=\"Real-time metrics, volume breakdown, answer rates, and cost analysis across channels\"\n Icon=\"fa-solid fa-chart-line\"\n >\n <div meta>\n <mj-stat-badge\n [Count]=\"Metrics.LiveCallsCount\"\n [Total]=\"Metrics.TotalVolume\"\n Label=\"live now\"\n [Variant]=\"Metrics.LiveCallsCount > 0 ? 'success' : 'default'\"\n />\n </div>\n\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n </div>\n\n <div toolbar>\n <div class=\"toolbar-controls\">\n <mj-page-search [Value]=\"SearchQuery\" (ValueChange)=\"OnSearchQueryChange($event)\" />\n\n <div class=\"time-range-pills\" role=\"radiogroup\" aria-label=\"Time Range\">\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '24h'\"\n (click)=\"SetTimeRange('24h')\"\n >\n 24h\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '7d'\"\n (click)=\"SetTimeRange('7d')\"\n >\n 7d\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === '30d'\"\n (click)=\"SetTimeRange('30d')\"\n >\n 30d\n </button>\n <button\n type=\"button\"\n class=\"range-pill\"\n [class.active]=\"SelectedTimeRange === 'all'\"\n (click)=\"SetTimeRange('all')\"\n >\n All Time\n </button>\n </div>\n\n <div class=\"channel-filter-pills\" role=\"radiogroup\" aria-label=\"Channel Filter\">\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'all'\"\n (click)=\"SetChannelFilter('all')\"\n >\n All Channels\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Phone'\"\n (click)=\"SetChannelFilter('Phone')\"\n >\n <i class=\"fa-solid fa-phone\"></i> Phone\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Web'\"\n (click)=\"SetChannelFilter('Web')\"\n >\n <i class=\"fa-solid fa-globe\"></i> Web\n </button>\n <button\n type=\"button\"\n class=\"channel-pill\"\n [class.active]=\"SelectedChannelFilter === 'Meeting'\"\n (click)=\"SetChannelFilter('Meeting')\"\n >\n <i class=\"fa-solid fa-users\"></i> Meeting\n </button>\n </div>\n </div>\n </div>\n </mj-page-header>\n\n <mj-page-body>\n @if (ErrorMessage) {\n <div class=\"error-banner\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n\n <!-- KPI Metric Cards Grid -->\n <div class=\"kpi-grid\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap primary\">\n <i class=\"fa-solid fa-headset\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Total Volume</span>\n <span class=\"kpi-value\">{{ Metrics.TotalVolume }}</span>\n <span class=\"kpi-subtext\">in selected window</span>\n </div>\n </div>\n\n <div class=\"kpi-card\" [class.live-active]=\"Metrics.LiveCallsCount > 0\">\n <div class=\"kpi-icon-wrap success\">\n <span class=\"live-dot-ping\"></span>\n <i class=\"fa-solid fa-phone-volume\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Live Now</span>\n <span class=\"kpi-value\">{{ Metrics.LiveCallsCount }}</span>\n <span class=\"kpi-subtext\">active sessions</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap success\">\n <i class=\"fa-solid fa-circle-check\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Answer Rate</span>\n <span class=\"kpi-value\">{{ Metrics.AnswerRate | number:'1.0-1' }}%</span>\n <span class=\"kpi-subtext\">{{ Metrics.AnsweredCount }} answered</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap warning\">\n <i class=\"fa-solid fa-phone-slash\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Abandonment Rate</span>\n <span class=\"kpi-value\">{{ Metrics.AbandonmentRate | number:'1.0-1' }}%</span>\n <span class=\"kpi-subtext\">{{ Metrics.AbandonedCount }} caller abandons</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap info\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Avg Handle Time</span>\n <span class=\"kpi-value\">{{ Metrics.AverageHandleTimeFormatted }}</span>\n <span class=\"kpi-subtext\">ASA: {{ Metrics.AverageSpeedToAnswerFormatted }}</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap accent\">\n <i class=\"fa-solid fa-arrows-split-up-and-left\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Transfers & Esc.</span>\n <span class=\"kpi-value\">{{ Metrics.TransferCount }} / {{ Metrics.EscalationCount }}</span>\n <span class=\"kpi-subtext\">transfers / escalations</span>\n </div>\n </div>\n\n <div class=\"kpi-card\">\n <div class=\"kpi-icon-wrap cost\">\n <i class=\"fa-solid fa-dollar-sign\"></i>\n </div>\n <div class=\"kpi-content\">\n <span class=\"kpi-label\">Total Cost Est.</span>\n <span class=\"kpi-value\">{{ Metrics.TotalCostFormatted }}</span>\n <span class=\"kpi-subtext\">usage & carrier minutes</span>\n </div>\n </div>\n </div>\n\n <!-- Active Live Calls Section -->\n <div class=\"dashboard-section live-section\">\n <div class=\"section-header\">\n <div class=\"section-title\">\n <span class=\"live-pulse-badge\"></span>\n <h2>Active Live Sessions</h2>\n <span class=\"count-pill\">{{ FilteredLiveInteractions.length }}</span>\n </div>\n </div>\n\n @if (FilteredLiveInteractions.length === 0) {\n <div class=\"empty-feed\">\n <i class=\"fa-solid fa-phone-slash empty-icon\"></i>\n <p>No active live interactions at this moment.</p>\n </div>\n } @else {\n <div class=\"live-table-container\">\n <table class=\"ops-table\">\n <thead>\n <tr>\n <th>Remote Address</th>\n <th>Channel</th>\n <th>Direction</th>\n <th>Status</th>\n <th>Started</th>\n <th>Elapsed</th>\n </tr>\n </thead>\n <tbody>\n @for (live of FilteredLiveInteractions; track live.ID) {\n <tr class=\"live-row\">\n <td class=\"address-col\">\n <i class=\"fa-solid fa-user-circle address-icon\"></i>\n <span class=\"address-text\">{{ live.RemoteAddress || 'Unknown' }}</span>\n </td>\n <td>\n <span class=\"badge channel-badge {{ live.Channel.toLowerCase() }}\">\n {{ live.Channel }}\n </span>\n </td>\n <td>\n <span class=\"badge direction-badge {{ live.Direction.toLowerCase() }}\">\n {{ live.Direction }}\n </span>\n </td>\n <td>\n <span class=\"status-indicator active\">\n <span class=\"dot\"></span>\n {{ live.Status }}\n </span>\n </td>\n <td>{{ FormatTime(live.StartedAt) }}</td>\n <td class=\"elapsed-col\">\n <span class=\"elapsed-badge\">{{ live.ElapsedLabel }}</span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n\n <!-- Middle Row: Volume Breakdown Cards -->\n <div class=\"two-col-grid\">\n <!-- Volume by Channel & Direction -->\n <div class=\"dashboard-card\">\n <div class=\"card-header\">\n <h3><i class=\"fa-solid fa-layer-group\"></i> Volume Distribution</h3>\n </div>\n <div class=\"card-body\">\n <div class=\"distribution-group\">\n <h4>By Channel</h4>\n <div class=\"bars-container\">\n @for (item of Metrics.VolumeByChannel; track item.Channel) {\n <div class=\"bar-row\">\n <div class=\"bar-info\">\n <span class=\"bar-label\">{{ item.Channel }}</span>\n <span class=\"bar-stats\">{{ item.Count }} ({{ item.Percentage }}%)</span>\n </div>\n <div class=\"bar-track\">\n <div class=\"bar-fill channel-{{ item.Channel.toLowerCase() }}\" [style.width.%]=\"item.Percentage\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <div class=\"distribution-group\">\n <h4>By Direction</h4>\n <div class=\"bars-container\">\n @for (item of Metrics.VolumeByDirection; track item.Direction) {\n <div class=\"bar-row\">\n <div class=\"bar-info\">\n <span class=\"bar-label\">{{ item.Direction }}</span>\n <span class=\"bar-stats\">{{ item.Count }} ({{ item.Percentage }}%)</span>\n </div>\n <div class=\"bar-track\">\n <div class=\"bar-fill direction-{{ item.Direction.toLowerCase() }}\" [style.width.%]=\"item.Percentage\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Cost by Number -->\n <div class=\"dashboard-card\">\n <div class=\"card-header\">\n <h3><i class=\"fa-solid fa-money-bill-wave\"></i> Cost by Phone Number</h3>\n </div>\n <div class=\"card-body\">\n @if (Metrics.CostByNumber.length === 0) {\n <div class=\"empty-feed\">\n <p>No phone number cost records in this window.</p>\n </div>\n } @else {\n <div class=\"cost-table-container\">\n <table class=\"ops-table compact\">\n <thead>\n <tr>\n <th>Phone Number / Target</th>\n <th>Calls</th>\n <th class=\"text-right\">Est. Cost</th>\n </tr>\n </thead>\n <tbody>\n @for (num of Metrics.CostByNumber; track num.PhoneNumber) {\n <tr>\n <td class=\"font-medium\">{{ num.PhoneNumber }}</td>\n <td>{{ num.Count }}</td>\n <td class=\"text-right font-semibold\">{{ FormatCost(num.Cost) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Bottom Row: Recent Interactions Audit Table -->\n <div class=\"dashboard-section\">\n <div class=\"section-header\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-list-check\"></i>\n <h2>Recent Interactions</h2>\n <span class=\"count-pill\">{{ FilteredRecentInteractions.length }}</span>\n </div>\n </div>\n\n @if (FilteredRecentInteractions.length === 0) {\n <div class=\"empty-feed\">\n <p>No interactions found matching criteria.</p>\n </div>\n } @else {\n <div class=\"table-container\">\n <table class=\"ops-table\">\n <thead>\n <tr>\n <th>Date / Time</th>\n <th>Remote Party</th>\n <th>Channel</th>\n <th>Direction</th>\n <th>Status</th>\n <th>Duration</th>\n <th>End Reason</th>\n <th class=\"text-right\">Est. Cost</th>\n </tr>\n </thead>\n <tbody>\n @for (row of FilteredRecentInteractions; track row.ID) {\n <tr>\n <td>\n <div class=\"datetime-cell\">\n <span class=\"date\">{{ FormatDate(row.StartedAt) }}</span>\n <span class=\"time\">{{ FormatTime(row.StartedAt) }}</span>\n </div>\n </td>\n <td class=\"address-col\">\n <span>{{ row.RemoteAddress || 'Unknown' }}</span>\n </td>\n <td>\n <span class=\"badge channel-badge {{ row.Channel.toLowerCase() }}\">\n {{ row.Channel }}\n </span>\n </td>\n <td>\n <span class=\"badge direction-badge {{ row.Direction.toLowerCase() }}\">\n {{ row.Direction }}\n </span>\n </td>\n <td>\n <span class=\"status-indicator {{ row.Status.toLowerCase() }}\">\n <span class=\"dot\"></span>\n {{ row.Status }}\n </span>\n </td>\n <td>{{ ComputeDuration(row.StartedAt, row.EndedAt) }}</td>\n <td class=\"end-reason-cell\">{{ row.EndReason || '-' }}</td>\n <td class=\"text-right\">{{ FormatCost(row.CostEstimate) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n </mj-page-body>\n</mj-page-layout>\n"]}
|