@memberjunction/ng-dashboards 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/ComponentStudio/components/form-override-dialog.component.d.ts +12 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.js +128 -81
- package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -1
- package/dist/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 +30 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +349 -232
- 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-agent-context.d.ts +107 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js +216 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +58 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +812 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -0
- 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/WorkQueue/components/work-queue-bindings.component.d.ts +37 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js +210 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts +70 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js +496 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts +22 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js +99 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts +29 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js +278 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts +59 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js +359 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts +22 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js +44 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts +67 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.js +119 -0
- package/dist/WorkQueue/work-queue-agent-context.js.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts +94 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js +545 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js.map +1 -0
- package/dist/WorkQueue/work-queue-types.d.ts +40 -0
- package/dist/WorkQueue/work-queue-types.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-types.js +2 -0
- package/dist/WorkQueue/work-queue-types.js.map +1 -0
- package/dist/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 -61
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +29 -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/module.d.ts +14 -13
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +7 -0
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +9 -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/dist/work-queue-dashboards.module.d.ts +23 -0
- package/dist/work-queue-dashboards.module.d.ts.map +1 -0
- package/dist/work-queue-dashboards.module.js +92 -0
- package/dist/work-queue-dashboards.module.js.map +1 -0
- package/package.json +73 -66
|
@@ -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":";;;;;;AAAA,OAAO,EAAiB,iBAAiB,EAAE,SAAS,EAAqB,MAAM,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAmB,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EACL,mCAAmC,EACnC,yBAAyB,EACzB,2BAA2B,EAC3B,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,GAGpB,MAAM,qCAAqC,CAAC;;;;;;;ICVvC,8BAAU;IACR,mCAAsG;IACxG,iBAAM;;;IADW,cAAgC;IAAC,AAAjC,qDAAgC,iCAA0B;;;IAU3E,8BAAsB;IACpB,gCAA6D;IAC/D,iBAAM;;;;IAEN,8BAAmD;IACjD,wBAAmE;IACnE,yBAAG;IAAA,YAAkB;IAAA,iBAAI;IACzB,kCAAkE;IAArB,wMAAS,iBAAU,KAAC;IAAC,yBAAS;IAC7E,AAD6E,iBAAS,EAChF;;;IAFD,eAAkB;IAAlB,yCAAkB;;;IAIvB,8BAAsC;IACpC,wBAA+D;IAC/D,6BAA0B;IAAA,yCAAyB;IAAA,iBAAI;IACvD,6BAAyB;IACvB,iGACF;IACF,AADE,iBAAI,EACA;;;IAyBU,gCAAoC;IAClC,wBAAwD;IACxD,YACF;IAAA,iBAAO;;;IADL,eACF;IADE,yDACF;;;IAIF,+BAA0E;IACxE,wBAAyD;IACzD,YACF;IAAA,iBAAM;;;IAHiC,mDAAkC;IAEvE,eACF;IADE,4DACF;;;;IA7BN,8BAO4C;IAA1C,AADA,wOAAS,gCAAsB,KAAC,qOACrB,uCAA6B,KAAC;IACzC,+BAAkD;IAChD,oBAA8G;IAChH,iBAAM;IAEJ,AADF,+BAA+B,cACG;IAAA,YAAuB;IAAA,iBAAM;IAE3D,AADF,+BAA+B,eACG;IAC9B,wBAAsD;IACtD,YACF;IAAA,iBAAO;IACP,8HAA6B;IAM/B,iBAAM;IACN,6HAAgC;IAOpC,AADE,iBAAM,EACH;;;;IA5BH,6HAAiE;;IAK5D,eAAsG;IAAtG,8GAAsG;IAGzE,eAAuB;IAAvB,0CAAuB;IAInD,eACF;IADE,oEACF;IACA,cAKC;IALD,oDAKC;IAEH,cAKC;IALD,uDAKC;;;IAUL,+BAAsD;IACpD,wBAA2D;IAC3D,6BAA0B;IAAA,0CAA0B;IAAA,iBAAI;IACxD,6BAAyB;IACvB,yGACF;IACF,AADE,iBAAI,EACA;;;IAEN,+BAAwC;IACtC,iCAAqD;IACvD,iBAAM;;;;IAEN,8BAAmD;IACjD,wBAAmE;IACnE,yBAAG;IAAA,YAAwB;IAAA,iBAAI;IAC/B,kCAAuF;IAA1C,uNAAS,4CAA+B,KAAC;IAAC,yBAAS;IAClG,AADkG,iBAAS,EACrG;;;IAFD,eAAwB;IAAxB,+CAAwB;;;IAI7B,8CAI0B;;;IADxB,AADA,AADA,+DAA0C,mCACf,6BACN;;;IA/D3B,AADF,AADF,mCAA+D,wBACD,aACQ;IAChE,yJAkCC;IAEL,AADE,iBAAK,EACS;IAGd,AADF,yCAA4C,cACnB;IAmBnB,AANA,AAJA,AARF,qHAAwB,+FAQM,8FAIG,qHAMxB;IASf,AADE,AADE,iBAAM,EACQ,EACP;;;IArEM,cAAY;IAAiB,AAAhB,AAAb,0BAAY,gBAAgB,gBAAgB;IAEvD,eAkCC;IAlCD,qCAkCC;IAIU,eAAY;IAAC,AAAb,0BAAY,gBAAgB;IAEvC,eAwBC;IAxBD,+GAwBC;;AD5Db;;;;;;;;;;;;GAYG;AAQI,IAAM,oCAAoC,GAA1C,MAAM,oCAAqC,SAAQ,qBAAqB;IAC7E,yDAAyD;IAClD,QAAQ,GAAsB,EAAE,CAAC;IACxC,4EAA4E;IACrE,WAAW,GAAG,EAAE,CAAC;IACxB,kDAAkD;IAC3C,SAAS,GAAqB,MAAM,CAAC;IAC5C,sDAAsD;IAC/C,aAAa,GAAyB,MAAM,CAAC;IACpD,4DAA4D;IACrD,eAAe,GAA2B,IAAI,CAAC;IACtD,0EAA0E;IACnE,aAAa,GAAiC,EAAE,CAAC;IACxD,mGAAmG;IAC5F,YAAY,GAAyB,EAAE,CAAC;IAE/C,6CAA6C;IACtC,SAAS,GAAG,IAAI,CAAC;IACxB,yDAAyD;IAClD,eAAe,GAAG,KAAK,CAAC;IAC/B,0DAA0D;IACnD,YAAY,GAAkB,IAAI,CAAC;IAC1C,mEAAmE;IAC5D,kBAAkB,GAAkB,IAAI,CAAC;IAE/B,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAExC,KAAK,CAAC,sBAAsB,CAAC,KAAmB;QACvD,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAEQ,KAAK,CAAC,oBAAoB,CAAC,KAAmB;QACrD,OAAO,yBAAyB,CAAC;IACnC,CAAC;IAEQ,KAAK,CAAC,QAAQ;QACrB,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,eAAe;QACb,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,wBAAwB,EAAE,CAAC;IAClC,CAAC;IAEQ,WAAW;QAClB,KAAK,CAAC,WAAW,EAAE,CAAC;IACtB,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,QAAQ;QACnB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAqB;gBAClD,UAAU,EAAE,uBAAuB;gBACnC,WAAW,EAAE,6BAA6B;gBAC1C,OAAO,EAAE,qBAAqB;gBAC9B,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,mCAAmC,CAAC;gBAC/E,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;gBACnB,OAAO;YACT,CAAC;YAED,IAAI,CAAC,QAAQ,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QACjF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,qGAAqG;IAC9F,KAAK,CAAC,aAAa,CAAC,OAAwB;QACjD,IAAI,UAAU,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YACrD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC/C,+DAA+D;YAC/D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gBACtD,OAAO;YACT,CAAC;YACD,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACnD,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,UAAU,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gBACrD,IAAI,CAAC,kBAAkB,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,UAAU,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gBACrD,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC/B,CAAC;YACD,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,oEAAoE;IAC7D,YAAY,CAAC,KAAoB,EAAE,OAAwB;QAChE,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YAC/C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,KAAK,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,gDAAgD;IACzC,cAAc,CAAC,MAAc,EAAE,OAAwB;QAC5D,OAAO,OAAO,CAAC,EAAE,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,IAAW,eAAe;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QACrF,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,mGAAmG;IAC3F,eAAe,CAAC,CAAkB,EAAE,CAAkB,EAAE,KAAuB;QACrF,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,OAAO;gBACV,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAChD,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YAC3D,KAAK,MAAM,CAAC;YACZ;gBACE,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,4GAA4G;IACpG,eAAe,CAAC,OAAwB;QAC9C,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,uFAAuF;QACvF,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,4DAA4D;IACrD,gBAAgB,CAAC,OAAwB;QAC9C,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YACvB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE;YACjD,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS;SACpF,CAAC,CAAC;IACL,CAAC;IAED,gGAAgG;IACzF,eAAe;QACpB,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAED,2CAA2C;IAC3C,kCAAkC;IAClC,EAAE;IACF,6FAA6F;IAC7F,iGAAiG;IACjG,kGAAkG;IAClG,iGAAiG;IACjG,2CAA2C;IAE3C;;;;;;;OAOG;IACK,mBAAmB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC;QACtC,MAAM,OAAO,GAAG,mCAAmC,CAAC;YAClD,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;YAClC,eAAe,EAAE,QAAQ;gBACvB,CAAC,CAAC;oBACE,EAAE,EAAE,QAAQ,CAAC,EAAE;oBACf,SAAS,EAAE,QAAQ,CAAC,SAAS;oBAC7B,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;oBAC3C,cAAc,EAAE,QAAQ,CAAC,cAAc;oBACvC,aAAa,EAAE,QAAQ,CAAC,aAAa;iBACtC;gBACH,CAAC,CAAC,IAAI;YACR,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM;YACpC,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,eAAe,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC9C,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;gBACpC,cAAc,EAAE,CAAC,CAAC,cAAc;gBAChC,aAAa,EAAE,CAAC,CAAC,aAAa;aAC/B,CAAC,CAAC;YACH,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,aAAa,EAAE,IAAI,CAAC,aAAa;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,wBAAwB;QAC9B,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,IAAI,EAAE;YAC/C;gBACE,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE,yKAAyK;gBACtL,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qBAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE;gBAC3I,OAAO,EAAE,KAAK,EAAE,MAA+B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;aACnF;YACD;gBACE,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE,kJAAkJ;gBAC/J,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qBAAqB,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE;gBAC3I,OAAO,EAAE,KAAK,EAAE,MAA+B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;aACnF;YACD;gBACE,IAAI,EAAE,iBAAiB;gBACvB,WAAW,EAAE,gFAAgF;gBAC7F,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;gBACnD,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,IAAI,CAAC,eAAe,EAAE,CAAC;oBACvB,OAAO,EAAE,OAAO,EAAE,IAAI,EAA4B,CAAC;gBACrD,CAAC;aACF;YACD;gBACE,IAAI,EAAE,oBAAoB;gBAC1B,WAAW,EAAE,uDAAuD;gBACpE,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;gBACnD,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACtB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzE,CAAC;aACF;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,WAAW,EAAE,0PAA0P;gBACvQ,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE;gBACnG,OAAO,EAAE,KAAK,EAAE,MAA+B,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;aACpF;YACD;gBACE,IAAI,EAAE,cAAc;gBACpB,WAAW,EAAE,8HAA8H;gBAC3I,eAAe,EAAE;oBACf,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE;wBAC9D,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;qBACrD;oBACD,QAAQ,EAAE,CAAC,OAAO,CAAC;iBACpB;gBACD,OAAO,EAAE,KAAK,EAAE,MAA+B,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;aAClF;SACF,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,iBAAiB,CAAC,MAA+B;QAC7D,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,OAAO,UAAU,CAAC,MAAM,CAAC;QAC3B,CAAC;QACD,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,mCAAmC,EAAE,CAAC;QAC/E,CAAC;QACD,MAAM,OAAO,GAAG,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,yBAAyB,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzF,CAAC;QACD,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;IAClG,CAAC;IAED,4FAA4F;IACpF,kBAAkB,CAAC,MAA+B;QACxD,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,OAAO,UAAU,CAAC,MAAM,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC;QACpC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC;QACrC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,UAAU,EAAE,OAAO,CAAC,MAAM;gBAC1B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACzB,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,SAAS,EAAE,CAAC,CAAC,SAAS;oBACtB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;oBACpC,cAAc,EAAE,CAAC,CAAC,cAAc;oBAChC,aAAa,EAAE,CAAC,CAAC,aAAa;iBAC/B,CAAC,CAAC;aACJ;SACF,CAAC;IACJ,CAAC;IAED,qGAAqG;IAC7F,gBAAgB,CAAC,MAA+B;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,8CAA8C,EAAE,CAAC;QAC1F,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QACzC,4FAA4F;QAC5F,IAAI,SAA+B,CAAC;QACpC,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YACxD,SAAS,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACnD,CAAC;aAAM,IAAI,2BAA2B,CAAC,YAAY,CAAC,EAAE,CAAC;YACrD,SAAS,GAAG,YAAY,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,sCAAsC,EAAE,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;IACnG,CAAC;IAED,6FAA6F;IAE7F,oGAAoG;IAC5F,KAAK,CAAC,SAAS,CAAC,SAAiB;QACvC,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA6B;YAC1D,UAAU,EAAE,0BAA0B;YACtC,WAAW,EAAE,mBAAmB,SAAS,GAAG;YAC5C,OAAO,EAAE,oBAAoB;YAC7B,UAAU,EAAE,eAAe;SAC5B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,YAAY,IAAI,kCAAkC,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACK,SAAS,CAAC,KAAmC,EAAE,OAAwB;QAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC;QAC7C,MAAM,IAAI,GAAyB,EAAE,CAAC;QACtC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,IAAI,CAAC;gBACR,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,QAAQ,KAAK,EAAE;gBAC9B,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC;gBACzC,KAAK,EAAE,IAAI,CAAC,cAAc,IAAI,SAAS;gBACvC,YAAY,EAAE,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS;gBAC9D,IAAI,EAAE,IAAI;aACX,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qGAAqG;IAC7F,UAAU,CAAC,IAAgC,EAAE,SAAsB;QACzE,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,gBAAgB,CAAC;QAC/B,CAAC;QACD,IAAI,SAAS,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,qFAAqF;IAC7E,iBAAiB,CAAC,GAAuB;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACtD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAClD,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS,EAAE,GAAG,CAAC,KAAK,IAAI,gBAAgB;YACxC,gBAAgB,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;YAC1C,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI;YAC1C,kBAAkB,EAAE,SAAS;YAC7B,SAAS,EAAE,SAAS;YACpB,aAAa,EAAE,IAAI;SACpB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,qBAAqB,CAAC,OAAwB,EAAE,KAAmC;QACzF,IAAI,OAAO,CAAC,aAAa,IAAI,CAAC,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/E,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACvE,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,qFAAqF;IAC7E,gBAAgB,CAAC,KAAmC,EAAE,SAAe;QAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;QACD,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,gBAAgB,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;QAC7D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+DAA+D;IACvD,cAAc,CAAC,EAAU;QAC/B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;QAClC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,OAAO,GAAG,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QAClG,CAAC;QACD,OAAO,GAAG,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,wFAAwF;IAChF,MAAM,CAAC,KAAuC;QACpD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,CAAC;6TAhfU,oCAAoC,yBAApC,oCAAoC;6DAApC,oCAAoC;YC7D/C,AADF,sCAAgB,wBAI+E;YAC3F,sGAAyC;YAMvC,AADF,8BAAa,2BACuD;YAAzB,gIAAW,cAAU,IAAC;YAEnE,AADE,AADE,iBAAkE,EAC9D,EACS;YAEjB,oCAAc;YAmBV,AARA,AANA,AAJF,sGAAiB,gFAIU,gFAMS,sFAQ3B;YA0Eb,AADE,iBAAe,EACA;;YAvGb,eAIC;YAJD,oEAIC;YAEoB,eAAqB;YAArB,uCAAqB;YAK1C,eA0FC;YA1FD,8FA0FC;;;AD5CQ,oCAAoC;IAPhD,aAAa,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;GAOvD,oCAAoC,CAifhD;;iFAjfY,oCAAoC;cANhD,SAAS;6BACI,KAAK,YACP,kCAAkC;;kFAIjC,oCAAoC;AAigBjD;;;GAGG;AACH,MAAM,UAAU,+BAA+B;IAC7C,sBAAsB;AACxB,CAAC","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"]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type AgentToolResult } from '../shared/agent-tool-validation';
|
|
2
|
+
export type TimeRangeFilter = '24h' | '7d' | '30d' | 'all';
|
|
3
|
+
export type ChannelFilter = 'all' | 'Phone' | 'Web' | 'Meeting';
|
|
4
|
+
export declare const VALID_TIME_RANGES: readonly TimeRangeFilter[];
|
|
5
|
+
export declare const VALID_CHANNELS: readonly ChannelFilter[];
|
|
6
|
+
export interface ChannelMetrics {
|
|
7
|
+
Channel: 'Phone' | 'Web' | 'Meeting';
|
|
8
|
+
Count: number;
|
|
9
|
+
Percentage: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DirectionMetrics {
|
|
12
|
+
Direction: 'Inbound' | 'Outbound' | 'Internal';
|
|
13
|
+
Count: number;
|
|
14
|
+
Percentage: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CostByDay {
|
|
17
|
+
Date: string;
|
|
18
|
+
Cost: number;
|
|
19
|
+
Count: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CostByNumber {
|
|
22
|
+
PhoneNumberID: string | null;
|
|
23
|
+
PhoneNumber: string;
|
|
24
|
+
Cost: number;
|
|
25
|
+
Count: number;
|
|
26
|
+
}
|
|
27
|
+
export interface LiveInteractionSummary {
|
|
28
|
+
ID: string;
|
|
29
|
+
Channel: 'Phone' | 'Web' | 'Meeting';
|
|
30
|
+
Direction: 'Inbound' | 'Outbound' | 'Internal';
|
|
31
|
+
Status: string;
|
|
32
|
+
RemoteAddress: string | null;
|
|
33
|
+
StartedAt: Date;
|
|
34
|
+
ElapsedSeconds: number;
|
|
35
|
+
ElapsedLabel: string;
|
|
36
|
+
}
|
|
37
|
+
export interface OperationsMetrics {
|
|
38
|
+
TotalVolume: number;
|
|
39
|
+
LiveCallsCount: number;
|
|
40
|
+
AnsweredCount: number;
|
|
41
|
+
AnswerRate: number;
|
|
42
|
+
AbandonedCount: number;
|
|
43
|
+
AbandonmentRate: number;
|
|
44
|
+
AverageHandleTimeSeconds: number;
|
|
45
|
+
AverageHandleTimeFormatted: string;
|
|
46
|
+
AverageSpeedToAnswerSeconds: number;
|
|
47
|
+
AverageSpeedToAnswerFormatted: string;
|
|
48
|
+
TransferCount: number;
|
|
49
|
+
EscalationCount: number;
|
|
50
|
+
TotalCost: number;
|
|
51
|
+
TotalCostFormatted: string;
|
|
52
|
+
VolumeByChannel: ChannelMetrics[];
|
|
53
|
+
VolumeByDirection: DirectionMetrics[];
|
|
54
|
+
CostByDay: CostByDay[];
|
|
55
|
+
CostByNumber: CostByNumber[];
|
|
56
|
+
LiveInteractions: LiveInteractionSummary[];
|
|
57
|
+
}
|
|
58
|
+
export interface InteractionRecordLike {
|
|
59
|
+
ID: string;
|
|
60
|
+
Channel: 'Meeting' | 'Phone' | 'Web' | string;
|
|
61
|
+
Direction: 'Inbound' | 'Internal' | 'Outbound' | string;
|
|
62
|
+
Status: 'Abandoned' | 'Active' | 'Ended' | 'Failed' | 'Queued' | string;
|
|
63
|
+
AgentSessionID: string | null;
|
|
64
|
+
RoomName: string | null;
|
|
65
|
+
PhoneNumberID: string | null;
|
|
66
|
+
RemoteAddress: string | null;
|
|
67
|
+
StartedAt: Date | string;
|
|
68
|
+
AnsweredAt: Date | string | null;
|
|
69
|
+
EndedAt: Date | string | null;
|
|
70
|
+
EndReason: string | null;
|
|
71
|
+
RecordingEnabled: boolean;
|
|
72
|
+
CostEstimate: number | null;
|
|
73
|
+
}
|
|
74
|
+
export interface InteractionEventRecordLike {
|
|
75
|
+
ID: string;
|
|
76
|
+
InteractionID: string;
|
|
77
|
+
EventType: 'Abandoned' | 'Accepted' | 'Answered' | 'Created' | 'Declined' | 'Ended' | 'Escalated' | 'Held' | 'Offered' | 'Queued' | 'RecordingStarted' | 'RecordingStopped' | 'Resumed' | 'Transferred' | string;
|
|
78
|
+
OccurredAt: Date | string;
|
|
79
|
+
ActorUserID: string | null;
|
|
80
|
+
ActorAgentID: string | null;
|
|
81
|
+
Details: string | null;
|
|
82
|
+
}
|
|
83
|
+
export interface PhoneNumberRecordLike {
|
|
84
|
+
ID: string;
|
|
85
|
+
PhoneNumber: string;
|
|
86
|
+
FriendlyName?: string | null;
|
|
87
|
+
}
|
|
88
|
+
export declare function FormatDuration(seconds: number): string;
|
|
89
|
+
export declare function FormatCurrency(amount: number): string;
|
|
90
|
+
export declare function ParseDate(val: Date | string | null | undefined): Date | null;
|
|
91
|
+
export declare function CalculateOperationsMetrics(interactions: readonly InteractionRecordLike[], events: readonly InteractionEventRecordLike[], phoneNumbers: readonly PhoneNumberRecordLike[], now?: Date): OperationsMetrics;
|
|
92
|
+
export declare function BuildTelephonyOperationsAgentContext(metrics: OperationsMetrics, timeRange: TimeRangeFilter, channelFilter: ChannelFilter): Record<string, unknown>;
|
|
93
|
+
export declare function ValidateTimeRangeParam(raw: unknown): {
|
|
94
|
+
ok: true;
|
|
95
|
+
value: TimeRangeFilter;
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
result: AgentToolResult;
|
|
99
|
+
};
|
|
100
|
+
export declare function ValidateChannelParam(raw: unknown): {
|
|
101
|
+
ok: true;
|
|
102
|
+
value: ChannelFilter;
|
|
103
|
+
} | {
|
|
104
|
+
ok: false;
|
|
105
|
+
result: AgentToolResult;
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=telephony-operations-agent-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-operations-agent-context.d.ts","sourceRoot":"","sources":["../../src/TelephonyOperations/telephony-operations-agent-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { BoundNameList, ValidateEnumParam } from '../shared/agent-tool-validation';
|
|
2
|
+
export const VALID_TIME_RANGES = ['24h', '7d', '30d', 'all'];
|
|
3
|
+
export const VALID_CHANNELS = ['all', 'Phone', 'Web', 'Meeting'];
|
|
4
|
+
export function FormatDuration(seconds) {
|
|
5
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
6
|
+
return '0s';
|
|
7
|
+
}
|
|
8
|
+
const rounded = Math.floor(seconds);
|
|
9
|
+
const mins = Math.floor(rounded / 60);
|
|
10
|
+
const secs = rounded % 60;
|
|
11
|
+
if (mins === 0) {
|
|
12
|
+
return `${secs}s`;
|
|
13
|
+
}
|
|
14
|
+
return `${mins}m ${secs.toString().padStart(2, '0')}s`;
|
|
15
|
+
}
|
|
16
|
+
export function FormatCurrency(amount) {
|
|
17
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
18
|
+
return '$0.00';
|
|
19
|
+
}
|
|
20
|
+
return new Intl.NumberFormat('en-US', {
|
|
21
|
+
style: 'currency',
|
|
22
|
+
currency: 'USD',
|
|
23
|
+
minimumFractionDigits: 2,
|
|
24
|
+
maximumFractionDigits: 2,
|
|
25
|
+
}).format(amount);
|
|
26
|
+
}
|
|
27
|
+
export function ParseDate(val) {
|
|
28
|
+
if (!val)
|
|
29
|
+
return null;
|
|
30
|
+
if (val instanceof Date)
|
|
31
|
+
return val;
|
|
32
|
+
const parsed = new Date(val);
|
|
33
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
34
|
+
}
|
|
35
|
+
export function CalculateOperationsMetrics(interactions, events, phoneNumbers, now = new Date()) {
|
|
36
|
+
const totalVolume = interactions.length;
|
|
37
|
+
const phoneNumberMap = new Map();
|
|
38
|
+
for (const pn of phoneNumbers) {
|
|
39
|
+
phoneNumberMap.set(pn.ID.toLowerCase(), pn.FriendlyName?.trim() || pn.PhoneNumber);
|
|
40
|
+
}
|
|
41
|
+
let answeredCount = 0;
|
|
42
|
+
let abandonedCount = 0;
|
|
43
|
+
let totalHandleTime = 0;
|
|
44
|
+
let completedHandledCount = 0;
|
|
45
|
+
let totalWaitTime = 0;
|
|
46
|
+
let speedToAnswerCount = 0;
|
|
47
|
+
let totalCost = 0;
|
|
48
|
+
const channelCounts = { Phone: 0, Web: 0, Meeting: 0 };
|
|
49
|
+
const directionCounts = { Inbound: 0, Outbound: 0, Internal: 0 };
|
|
50
|
+
const costByDayMap = new Map();
|
|
51
|
+
const costByNumberMap = new Map();
|
|
52
|
+
const liveInteractions = [];
|
|
53
|
+
// Group events by interaction id for quick checks
|
|
54
|
+
const eventsByInteraction = new Map();
|
|
55
|
+
let transferCount = 0;
|
|
56
|
+
let escalationCount = 0;
|
|
57
|
+
for (const evt of events) {
|
|
58
|
+
if (evt.EventType === 'Transferred')
|
|
59
|
+
transferCount++;
|
|
60
|
+
if (evt.EventType === 'Escalated')
|
|
61
|
+
escalationCount++;
|
|
62
|
+
const key = evt.InteractionID.toLowerCase();
|
|
63
|
+
const existing = eventsByInteraction.get(key);
|
|
64
|
+
if (existing) {
|
|
65
|
+
existing.push(evt);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
eventsByInteraction.set(key, [evt]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const row of interactions) {
|
|
72
|
+
const started = ParseDate(row.StartedAt) ?? new Date();
|
|
73
|
+
const answered = ParseDate(row.AnsweredAt);
|
|
74
|
+
const ended = ParseDate(row.EndedAt);
|
|
75
|
+
const status = row.Status;
|
|
76
|
+
// Channel count
|
|
77
|
+
const ch = row.Channel || 'Phone';
|
|
78
|
+
if (ch in channelCounts) {
|
|
79
|
+
channelCounts[ch]++;
|
|
80
|
+
}
|
|
81
|
+
// Direction count
|
|
82
|
+
const dir = row.Direction || 'Inbound';
|
|
83
|
+
if (dir in directionCounts) {
|
|
84
|
+
directionCounts[dir]++;
|
|
85
|
+
}
|
|
86
|
+
// Cost accumulation
|
|
87
|
+
const cost = typeof row.CostEstimate === 'number' && Number.isFinite(row.CostEstimate) ? row.CostEstimate : 0;
|
|
88
|
+
totalCost += cost;
|
|
89
|
+
// Cost by day
|
|
90
|
+
const dayKey = started.toISOString().split('T')[0];
|
|
91
|
+
const dayEntry = costByDayMap.get(dayKey) ?? { cost: 0, count: 0 };
|
|
92
|
+
dayEntry.cost += cost;
|
|
93
|
+
dayEntry.count++;
|
|
94
|
+
costByDayMap.set(dayKey, dayEntry);
|
|
95
|
+
// Cost by number
|
|
96
|
+
const pnId = row.PhoneNumberID ? row.PhoneNumberID.toLowerCase() : null;
|
|
97
|
+
const pnLabel = pnId ? (phoneNumberMap.get(pnId) ?? row.RemoteAddress ?? 'Assigned Pool') : (row.RemoteAddress ?? 'Non-Phone');
|
|
98
|
+
const pnKey = pnId ?? pnLabel;
|
|
99
|
+
const numberEntry = costByNumberMap.get(pnKey) ?? { phoneNumber: pnLabel, cost: 0, count: 0 };
|
|
100
|
+
numberEntry.cost += cost;
|
|
101
|
+
numberEntry.count++;
|
|
102
|
+
costByNumberMap.set(pnKey, numberEntry);
|
|
103
|
+
// Live interactions
|
|
104
|
+
const isLive = (status === 'Active' || status === 'Queued') && ended === null;
|
|
105
|
+
if (isLive) {
|
|
106
|
+
const elapsed = Math.max(0, Math.floor((now.getTime() - started.getTime()) / 1000));
|
|
107
|
+
liveInteractions.push({
|
|
108
|
+
ID: row.ID,
|
|
109
|
+
Channel: ch,
|
|
110
|
+
Direction: dir,
|
|
111
|
+
Status: status,
|
|
112
|
+
RemoteAddress: row.RemoteAddress,
|
|
113
|
+
StartedAt: started,
|
|
114
|
+
ElapsedSeconds: elapsed,
|
|
115
|
+
ElapsedLabel: FormatDuration(elapsed),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Answered check
|
|
119
|
+
const hasAnswerEvent = (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some((e) => e.EventType === 'Answered' || e.EventType === 'Accepted');
|
|
120
|
+
const isAnswered = answered !== null || hasAnswerEvent || status === 'Active' || status === 'Ended';
|
|
121
|
+
if (isAnswered) {
|
|
122
|
+
answeredCount++;
|
|
123
|
+
}
|
|
124
|
+
// Abandoned check
|
|
125
|
+
const isAbandoned = status === 'Abandoned' || (eventsByInteraction.get(row.ID.toLowerCase()) ?? []).some((e) => e.EventType === 'Abandoned');
|
|
126
|
+
if (isAbandoned) {
|
|
127
|
+
abandonedCount++;
|
|
128
|
+
}
|
|
129
|
+
// Handle time: for ended interactions
|
|
130
|
+
if (ended !== null) {
|
|
131
|
+
const effectiveStart = answered ?? started;
|
|
132
|
+
const durationSec = Math.max(0, Math.floor((ended.getTime() - effectiveStart.getTime()) / 1000));
|
|
133
|
+
totalHandleTime += durationSec;
|
|
134
|
+
completedHandledCount++;
|
|
135
|
+
}
|
|
136
|
+
// Speed to answer
|
|
137
|
+
if (answered !== null) {
|
|
138
|
+
const waitSec = Math.max(0, Math.floor((answered.getTime() - started.getTime()) / 1000));
|
|
139
|
+
totalWaitTime += waitSec;
|
|
140
|
+
speedToAnswerCount++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const answerRate = totalVolume > 0 ? (answeredCount / totalVolume) * 100 : 0;
|
|
144
|
+
const abandonmentRate = totalVolume > 0 ? (abandonedCount / totalVolume) * 100 : 0;
|
|
145
|
+
const avgHandleTime = completedHandledCount > 0 ? totalHandleTime / completedHandledCount : 0;
|
|
146
|
+
const avgSpeedToAnswer = speedToAnswerCount > 0 ? totalWaitTime / speedToAnswerCount : 0;
|
|
147
|
+
const volumeByChannel = ['Phone', 'Web', 'Meeting'].map((channel) => ({
|
|
148
|
+
Channel: channel,
|
|
149
|
+
Count: channelCounts[channel],
|
|
150
|
+
Percentage: totalVolume > 0 ? Math.round((channelCounts[channel] / totalVolume) * 100) : 0,
|
|
151
|
+
}));
|
|
152
|
+
const volumeByDirection = ['Inbound', 'Outbound', 'Internal'].map((direction) => ({
|
|
153
|
+
Direction: direction,
|
|
154
|
+
Count: directionCounts[direction],
|
|
155
|
+
Percentage: totalVolume > 0 ? Math.round((directionCounts[direction] / totalVolume) * 100) : 0,
|
|
156
|
+
}));
|
|
157
|
+
const costByDay = Array.from(costByDayMap.entries())
|
|
158
|
+
.map(([date, data]) => ({ Date: date, Cost: data.cost, Count: data.count }))
|
|
159
|
+
.sort((a, b) => a.Date.localeCompare(b.Date));
|
|
160
|
+
const costByNumber = Array.from(costByNumberMap.entries())
|
|
161
|
+
.map(([id, data]) => ({
|
|
162
|
+
PhoneNumberID: id.includes('-') ? id : null,
|
|
163
|
+
PhoneNumber: data.phoneNumber,
|
|
164
|
+
Cost: data.cost,
|
|
165
|
+
Count: data.count,
|
|
166
|
+
}))
|
|
167
|
+
.sort((a, b) => b.Cost - a.Cost);
|
|
168
|
+
return {
|
|
169
|
+
TotalVolume: totalVolume,
|
|
170
|
+
LiveCallsCount: liveInteractions.length,
|
|
171
|
+
AnsweredCount: answeredCount,
|
|
172
|
+
AnswerRate: answerRate,
|
|
173
|
+
AbandonedCount: abandonedCount,
|
|
174
|
+
AbandonmentRate: abandonmentRate,
|
|
175
|
+
AverageHandleTimeSeconds: avgHandleTime,
|
|
176
|
+
AverageHandleTimeFormatted: FormatDuration(avgHandleTime),
|
|
177
|
+
AverageSpeedToAnswerSeconds: avgSpeedToAnswer,
|
|
178
|
+
AverageSpeedToAnswerFormatted: FormatDuration(avgSpeedToAnswer),
|
|
179
|
+
TransferCount: transferCount,
|
|
180
|
+
EscalationCount: escalationCount,
|
|
181
|
+
TotalCost: totalCost,
|
|
182
|
+
TotalCostFormatted: FormatCurrency(totalCost),
|
|
183
|
+
VolumeByChannel: volumeByChannel,
|
|
184
|
+
VolumeByDirection: volumeByDirection,
|
|
185
|
+
CostByDay: costByDay,
|
|
186
|
+
CostByNumber: costByNumber,
|
|
187
|
+
LiveInteractions: liveInteractions.sort((a, b) => b.ElapsedSeconds - a.ElapsedSeconds),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function BuildTelephonyOperationsAgentContext(metrics, timeRange, channelFilter) {
|
|
191
|
+
return {
|
|
192
|
+
Surface: 'TelephonyOperationsDashboard',
|
|
193
|
+
TimeRange: timeRange,
|
|
194
|
+
ChannelFilter: channelFilter,
|
|
195
|
+
TotalVolume: metrics.TotalVolume,
|
|
196
|
+
LiveCallsCount: metrics.LiveCallsCount,
|
|
197
|
+
AnswerRatePercent: Math.round(metrics.AnswerRate * 10) / 10,
|
|
198
|
+
AbandonmentRatePercent: Math.round(metrics.AbandonmentRate * 10) / 10,
|
|
199
|
+
AverageHandleTime: metrics.AverageHandleTimeFormatted,
|
|
200
|
+
AverageSpeedToAnswer: metrics.AverageSpeedToAnswerFormatted,
|
|
201
|
+
TransferCount: metrics.TransferCount,
|
|
202
|
+
EscalationCount: metrics.EscalationCount,
|
|
203
|
+
TotalCostEstimate: metrics.TotalCostFormatted,
|
|
204
|
+
VolumeByChannel: metrics.VolumeByChannel.map((c) => `${c.Channel}: ${c.Count} (${c.Percentage}%)`),
|
|
205
|
+
VolumeByDirection: metrics.VolumeByDirection.map((d) => `${d.Direction}: ${d.Count} (${d.Percentage}%)`),
|
|
206
|
+
VisibleLiveCallAddresses: BoundNameList(metrics.LiveInteractions.map((i) => `${i.Channel} (${i.Direction}): ${i.RemoteAddress || i.ID} [${i.ElapsedLabel}]`)),
|
|
207
|
+
TopCostNumbers: BoundNameList(metrics.CostByNumber.map((n) => `${n.PhoneNumber}: $${n.Cost.toFixed(2)} (${n.Count} calls)`)),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function ValidateTimeRangeParam(raw) {
|
|
211
|
+
return ValidateEnumParam(raw, VALID_TIME_RANGES, 'timeRange');
|
|
212
|
+
}
|
|
213
|
+
export function ValidateChannelParam(raw) {
|
|
214
|
+
return ValidateEnumParam(raw, VALID_CHANNELS, 'channel');
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=telephony-operations-agent-context.js.map
|