@memberjunction/ng-conversations 6.2.0-edge.6 → 6.2.0-edge.7
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/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
- package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
- package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
- package/dist/lib/components/message/message-input.component.d.ts +80 -53
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +381 -277
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +18 -1
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +548 -426
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +8 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +40 -2
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +7 -2
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +2 -1
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +9 -4
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +5 -4
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +2 -2
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +11 -7
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/active-tasks.service.d.ts +5 -0
- package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
- package/dist/lib/services/active-tasks.service.js +16 -0
- package/dist/lib/services/active-tasks.service.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +7 -1
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-permission.service.js +2 -0
- package/dist/lib/services/artifact-permission.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -0
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
- package/dist/lib/services/collection-permission.service.js +1 -0
- package/dist/lib/services/collection-permission.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +1 -4
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +7 -45
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +1 -0
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
- package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
- package/dist/lib/utils/agent-handoff-map.js +270 -0
- package/dist/lib/utils/agent-handoff-map.js.map +1 -0
- package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
- package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
- package/dist/lib/utils/agent-turn-routing.js +147 -6
- package/dist/lib/utils/agent-turn-routing.js.map +1 -1
- package/dist/lib/utils/decision-routing.d.ts +17 -9
- package/dist/lib/utils/decision-routing.d.ts.map +1 -1
- package/dist/lib/utils/decision-routing.js +23 -14
- package/dist/lib/utils/decision-routing.js.map +1 -1
- package/dist/lib/utils/thread-replies.d.ts +34 -0
- package/dist/lib/utils/thread-replies.d.ts.map +1 -0
- package/dist/lib/utils/thread-replies.js +49 -0
- package/dist/lib/utils/thread-replies.js.map +1 -0
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +36 -36
|
@@ -158,7 +158,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template(r
|
|
|
158
158
|
i0.ɵɵadvance();
|
|
159
159
|
i0.ɵɵconditional(!ctx_r1.PresenterOnStage ? 5 : 6);
|
|
160
160
|
i0.ɵɵadvance(2);
|
|
161
|
-
i0.ɵɵconditional(ctx_r1.PresenterOnStage || !ctx_r1.Ui.showCaptionsControl ? 7 : -1);
|
|
161
|
+
i0.ɵɵconditional(ctx_r1.Ui.allowTextReveal && (ctx_r1.PresenterOnStage || !ctx_r1.Ui.showCaptionsControl) ? 7 : -1);
|
|
162
162
|
} }
|
|
163
163
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
164
164
|
const _r7 = i0.ɵɵgetCurrentView();
|
|
@@ -167,7 +167,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template(r
|
|
|
167
167
|
i0.ɵɵelementEnd();
|
|
168
168
|
} if (rf & 2) {
|
|
169
169
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
170
|
-
i0.ɵɵproperty("State", ctx_r1.State)("AgentName", ctx_r1.AgentName)("UserName", (ctx_r1.CurrentUser == null ? null : ctx_r1.CurrentUser.Name) || "You")("ShowCaptions", ctx_r1.IsReviewing ? true : ctx_r1.ShowCaptions)("DevMode", ctx_r1.
|
|
170
|
+
i0.ɵɵproperty("State", ctx_r1.State)("AgentName", ctx_r1.AgentName)("UserName", (ctx_r1.CurrentUser == null ? null : ctx_r1.CurrentUser.Name) || "You")("ShowCaptions", ctx_r1.IsReviewing ? true : ctx_r1.ShowCaptions)("DevMode", ctx_r1.Ui.showDevLinks);
|
|
171
171
|
} }
|
|
172
172
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_14_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
173
173
|
i0.ɵɵelement(0, "mj-realtime-channel-strip");
|
|
@@ -187,7 +187,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_14_Template(r
|
|
|
187
187
|
i0.ɵɵadvance();
|
|
188
188
|
i0.ɵɵconditional(ctx_r1.ChannelStripVisible ? 1 : -1);
|
|
189
189
|
i0.ɵɵadvance();
|
|
190
|
-
i0.ɵɵproperty("Open", ctx_r1.ComposerOpen)("Compact", ctx_r1.Ui.chrome === "orb" || ctx_r1.Ui.compact)("CaptionsOn", ctx_r1.ShowCaptions)("IsMuted", ctx_r1.Muted)("ShowCamera", ctx_r1.CaptureOffers.Camera)("ShowShare", ctx_r1.CaptureOffers.Screen)("CameraOn", ctx_r1.CameraOn)("Sharing", ctx_r1.Sharing)("SharePanels", ctx_r1.SharePanels)("Devices", ctx_r1.CallDevices)("SelectedMicrophoneID", ctx_r1.SelectedMicrophoneID)("SelectedCameraID", ctx_r1.SelectedCameraID)("ShowDetails", ctx_r1.ShowDetailsControl)("DetailsOn", ctx_r1.DetailsPeek);
|
|
190
|
+
i0.ɵɵproperty("Open", ctx_r1.ComposerOpen)("Compact", ctx_r1.Ui.chrome === "orb" || ctx_r1.Ui.compact)("AllowTyping", ctx_r1.Ui.allowTyping)("ShowEnd", ctx_r1.Ui.showEnd)("ShowCaptionsControl", ctx_r1.Ui.showCaptionsControl)("CaptionsOn", ctx_r1.ShowCaptions)("IsMuted", ctx_r1.Muted)("ShowCamera", ctx_r1.CaptureOffers.Camera)("ShowShare", ctx_r1.CaptureOffers.Screen)("CameraOn", ctx_r1.CameraOn)("Sharing", ctx_r1.Sharing)("SharePanels", ctx_r1.SharePanels)("Devices", ctx_r1.CallDevices)("SelectedMicrophoneID", ctx_r1.SelectedMicrophoneID)("SelectedCameraID", ctx_r1.SelectedCameraID)("ShowDetails", ctx_r1.ShowDetailsControl)("DetailsOn", ctx_r1.DetailsPeek);
|
|
191
191
|
} }
|
|
192
192
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_15_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
193
193
|
const _r10 = i0.ɵɵgetCurrentView();
|
|
@@ -210,7 +210,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_15_Template(r
|
|
|
210
210
|
i0.ɵɵadvance();
|
|
211
211
|
i0.ɵɵstyleProp("width", ctx_r1.PanelAreaSize, "px");
|
|
212
212
|
i0.ɵɵadvance();
|
|
213
|
-
i0.ɵɵproperty("State", ctx_r1.State)("DevMode", ctx_r1.
|
|
213
|
+
i0.ɵɵproperty("State", ctx_r1.State)("DevMode", ctx_r1.Ui.showDevLinks)("CurrentUser", ctx_r1.CurrentUser)("EnvironmentID", ctx_r1.EnvironmentID)("ShowActivityTab", ctx_r1.ShowActivityTab)("ExtraArtifacts", ctx_r1.ReviewArtifacts)("Collapsed", ctx_r1.PanelCollapsed)("SurfacePlacements", ctx_r1.SurfaceStage.Placements)("AllowedPlacements", ctx_r1.SurfaceStage.AllowedPlacements);
|
|
214
214
|
} }
|
|
215
215
|
function RealtimeSessionOverlayComponent_Conditional_0_ng_template_17_Conditional_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
216
216
|
const _r11 = i0.ɵɵgetCurrentView();
|
|
@@ -276,7 +276,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditiona
|
|
|
276
276
|
} }
|
|
277
277
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
278
278
|
const _r20 = i0.ɵɵgetCurrentView();
|
|
279
|
-
i0.ɵɵelementStart(0, "mj-media-move-menu",
|
|
279
|
+
i0.ɵɵelementStart(0, "mj-media-move-menu", 62);
|
|
280
280
|
i0.ɵɵlistener("MoveRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_10_Template_mj_media_move_menu_MoveRequested_0_listener($event) { i0.ɵɵrestoreView(_r20); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnMoveRequested($event)); })("ResetLayoutRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_10_Template_mj_media_move_menu_ResetLayoutRequested_0_listener() { i0.ɵɵrestoreView(_r20); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnResetLayout()); });
|
|
281
281
|
i0.ɵɵelementEnd();
|
|
282
282
|
} if (rf & 2) {
|
|
@@ -284,6 +284,13 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditiona
|
|
|
284
284
|
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
285
285
|
i0.ɵɵproperty("Key", stagePlugin_r21.ChannelName)("Title", stagePlugin_r21.TabTitle)("Allowed", ctx_r1.SurfaceStage.AllowedFor(stagePlugin_r21.ChannelName));
|
|
286
286
|
} }
|
|
287
|
+
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
288
|
+
const _r22 = i0.ɵɵgetCurrentView();
|
|
289
|
+
i0.ɵɵelementStart(0, "button", 63);
|
|
290
|
+
i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_11_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r22); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnFocusPillEnd()); });
|
|
291
|
+
i0.ɵɵelement(1, "i", 64);
|
|
292
|
+
i0.ɵɵelementEnd();
|
|
293
|
+
} }
|
|
287
294
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template(rf, ctx) { if (rf & 1) {
|
|
288
295
|
const _r19 = i0.ɵɵgetCurrentView();
|
|
289
296
|
i0.ɵɵelementStart(0, "div", 20);
|
|
@@ -297,18 +304,16 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template(r
|
|
|
297
304
|
i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillMute()); });
|
|
298
305
|
i0.ɵɵelement(7, "i", 58);
|
|
299
306
|
i0.ɵɵelementEnd();
|
|
300
|
-
i0.ɵɵelementStart(8, "button",
|
|
307
|
+
i0.ɵɵelementStart(8, "button", 57);
|
|
301
308
|
i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillExit()); });
|
|
302
|
-
i0.ɵɵelement(9, "i",
|
|
309
|
+
i0.ɵɵelement(9, "i", 59);
|
|
310
|
+
i0.ɵɵelementEnd();
|
|
311
|
+
i0.ɵɵconditionalCreate(10, RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_10_Template, 1, 3, "mj-media-move-menu", 60);
|
|
312
|
+
i0.ɵɵconditionalCreate(11, RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_11_Template, 2, 0, "button", 61);
|
|
303
313
|
i0.ɵɵelementEnd();
|
|
304
|
-
i0.ɵɵconditionalCreate(10, RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Conditional_10_Template, 1, 3, "mj-media-move-menu", 61);
|
|
305
|
-
i0.ɵɵelementStart(11, "button", 62);
|
|
306
|
-
i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillEnd()); });
|
|
307
|
-
i0.ɵɵelement(12, "i", 63);
|
|
308
|
-
i0.ɵɵelementEnd()();
|
|
309
314
|
} if (rf & 2) {
|
|
310
315
|
let tmp_8_0;
|
|
311
|
-
let
|
|
316
|
+
let tmp_16_0;
|
|
312
317
|
const state_r4 = i0.ɵɵnextContext();
|
|
313
318
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
314
319
|
i0.ɵɵadvance();
|
|
@@ -323,13 +328,18 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template(r
|
|
|
323
328
|
i0.ɵɵattribute("aria-label", ctx_r1.FocusPillMuted ? "Unmute microphone" : "Mute microphone");
|
|
324
329
|
i0.ɵɵadvance();
|
|
325
330
|
i0.ɵɵclassProp("fa-microphone", !ctx_r1.FocusPillMuted)("fa-microphone-slash", ctx_r1.FocusPillMuted);
|
|
326
|
-
i0.ɵɵadvance(
|
|
327
|
-
i0.ɵɵ
|
|
331
|
+
i0.ɵɵadvance();
|
|
332
|
+
i0.ɵɵproperty("title", ctx_r1.Ui.allowTextReveal ? "Show thread" : "Back to the call");
|
|
333
|
+
i0.ɵɵattribute("aria-label", ctx_r1.Ui.allowTextReveal ? "Show the conversation thread" : "Leave focus mode and return to the call");
|
|
334
|
+
i0.ɵɵadvance(2);
|
|
335
|
+
i0.ɵɵconditional((tmp_16_0 = ctx_r1.StagePlugin) ? 10 : -1, tmp_16_0);
|
|
336
|
+
i0.ɵɵadvance();
|
|
337
|
+
i0.ɵɵconditional(ctx_r1.Ui.showEnd ? 11 : -1);
|
|
328
338
|
} }
|
|
329
339
|
function RealtimeSessionOverlayComponent_Conditional_0_Conditional_20_Template(rf, ctx) { if (rf & 1) {
|
|
330
|
-
const
|
|
340
|
+
const _r23 = i0.ɵɵgetCurrentView();
|
|
331
341
|
i0.ɵɵelementStart(0, "mj-realtime-camera-check-card", 65);
|
|
332
|
-
i0.ɵɵlistener("Confirmed", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_20_Template_mj_realtime_camera_check_card_Confirmed_0_listener() { i0.ɵɵrestoreView(
|
|
342
|
+
i0.ɵɵlistener("Confirmed", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_20_Template_mj_realtime_camera_check_card_Confirmed_0_listener() { i0.ɵɵrestoreView(_r23); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnCameraCheckConfirmed()); })("Declined", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_20_Template_mj_realtime_camera_check_card_Declined_0_listener() { i0.ɵɵrestoreView(_r23); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnCameraCheckDeclined()); });
|
|
333
343
|
i0.ɵɵelementEnd();
|
|
334
344
|
} if (rf & 2) {
|
|
335
345
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
@@ -349,48 +359,48 @@ function RealtimeSessionOverlayComponent_Conditional_0_Template(rf, ctx) { if (r
|
|
|
349
359
|
i0.ɵɵconditionalCreate(9, RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template, 2, 0, "div", 13)(10, RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template, 8, 9, "div", 14)(11, RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template, 1, 5, "mj-realtime-session-thread", 15);
|
|
350
360
|
i0.ɵɵelementEnd();
|
|
351
361
|
i0.ɵɵelementStart(12, "div", 16, 2);
|
|
352
|
-
i0.ɵɵconditionalCreate(14, RealtimeSessionOverlayComponent_Conditional_0_Conditional_14_Template, 3,
|
|
362
|
+
i0.ɵɵconditionalCreate(14, RealtimeSessionOverlayComponent_Conditional_0_Conditional_14_Template, 3, 19);
|
|
353
363
|
i0.ɵɵelementEnd()();
|
|
354
364
|
i0.ɵɵconditionalCreate(15, RealtimeSessionOverlayComponent_Conditional_0_Conditional_15_Template, 3, 12);
|
|
355
365
|
i0.ɵɵelementStart(16, "mj-media-stage", 17);
|
|
356
366
|
i0.ɵɵlistener("PipRectChange", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_media_stage_PipRectChange_16_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPipRectChange($event)); });
|
|
357
367
|
i0.ɵɵtemplate(17, RealtimeSessionOverlayComponent_Conditional_0_ng_template_17_Template, 1, 1, "ng-template", 18)(18, RealtimeSessionOverlayComponent_Conditional_0_ng_template_18_Template, 1, 1, "ng-template", 19);
|
|
358
368
|
i0.ɵɵelementEnd();
|
|
359
|
-
i0.ɵɵconditionalCreate(19, RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template,
|
|
369
|
+
i0.ɵɵconditionalCreate(19, RealtimeSessionOverlayComponent_Conditional_0_Conditional_19_Template, 12, 15, "div", 20);
|
|
360
370
|
i0.ɵɵconditionalCreate(20, RealtimeSessionOverlayComponent_Conditional_0_Conditional_20_Template, 1, 3, "mj-realtime-camera-check-card", 21);
|
|
361
371
|
i0.ɵɵelementEnd();
|
|
362
372
|
} if (rf & 2) {
|
|
363
|
-
let
|
|
364
|
-
let
|
|
373
|
+
let tmp_27_0;
|
|
374
|
+
let tmp_42_0;
|
|
365
375
|
const state_r4 = ctx;
|
|
366
|
-
const
|
|
376
|
+
const callControls_r24 = i0.ɵɵreference(13);
|
|
367
377
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
368
378
|
i0.ɵɵclassProp("call-overlay--hidden", ctx_r1.Hidden)("call-overlay--review", ctx_r1.IsReviewing)("board-focus", ctx_r1.ChannelFocusMode);
|
|
369
379
|
i0.ɵɵattribute("aria-label", ctx_r1.IsReviewing ? "Past session review" : "Live voice session");
|
|
370
380
|
i0.ɵɵadvance(2);
|
|
371
|
-
i0.ɵɵproperty("State", state_r4)("Compact", !ctx_r1.IsReviewing && ctx_r1.Ui.compact && ctx_r1.Ui.chrome === "orb")("HideState", ctx_r1.ShowHero && !ctx_r1.PresenterOnStage)("AgentName", ctx_r1.AgentName)("ErrorLabel", ctx_r1.CallEndLabel)("ModelName", ctx_r1.IsReviewing ? null : i0.ɵɵpipeBind1(3,
|
|
381
|
+
i0.ɵɵproperty("State", state_r4)("Compact", !ctx_r1.IsReviewing && ctx_r1.Ui.compact && ctx_r1.Ui.chrome === "orb")("HideState", ctx_r1.ShowHero && !ctx_r1.PresenterOnStage)("AgentName", ctx_r1.AgentName)("ErrorLabel", ctx_r1.CallEndLabel)("ModelName", ctx_r1.IsReviewing ? null : i0.ɵɵpipeBind1(3, 42, ctx_r1.ModelName$))("DevMode", ctx_r1.Ui.showDevLinks)("AllowDevLinks", ctx_r1.AllowDevLinks)("SessionID", ctx_r1.IsReviewing ? ctx_r1.ReviewSessionID : ctx_r1.SessionID)("ReviewMode", ctx_r1.IsReviewing)("ReviewStartedAt", ctx_r1.ReviewStartedAt)("ReviewClosedAt", ctx_r1.ReviewClosedAt)("ReviewCloseReason", ctx_r1.ReviewCloseReason)("ShowGear", !ctx_r1.IsReviewing && ctx_r1.Ui.showGear)("ShowDensityPicker", ctx_r1.Ui.showDensityPicker)("ShowMinimize", !ctx_r1.IsReviewing && ctx_r1.Ui.showMinimize)("ShowStartLive", ctx_r1.AllowStartLive)("Density", ctx_r1.Disclosure.Milestones.Density);
|
|
372
382
|
i0.ɵɵadvance(2);
|
|
373
|
-
i0.ɵɵconditional((
|
|
383
|
+
i0.ɵɵconditional((tmp_27_0 = ctx_r1.MicrophoneNotice) ? 4 : -1, tmp_27_0);
|
|
374
384
|
i0.ɵɵadvance();
|
|
375
385
|
i0.ɵɵconditional(ctx_r1.AvatarNotice ? 5 : -1);
|
|
376
386
|
i0.ɵɵadvance();
|
|
377
|
-
i0.ɵɵconditional(!ctx_r1.IsReviewing && ctx_r1.State.Narration && !ctx_r1.State.ActiveCallId && ctx_r1.
|
|
387
|
+
i0.ɵɵconditional(!ctx_r1.IsReviewing && ctx_r1.State.Narration && !ctx_r1.State.ActiveCallId && ctx_r1.Ui.showThread ? 6 : -1);
|
|
378
388
|
i0.ɵɵadvance();
|
|
379
389
|
i0.ɵɵconditional(!ctx_r1.ShowHero ? 7 : -1);
|
|
380
390
|
i0.ɵɵadvance();
|
|
381
391
|
i0.ɵɵclassProp("call-body--hero", ctx_r1.ShowHero && !ctx_r1.ShowConnectingScreen(state_r4));
|
|
382
392
|
i0.ɵɵadvance();
|
|
383
|
-
i0.ɵɵconditional(ctx_r1.ShowConnectingScreen(state_r4) ? 9 : ctx_r1.ShowHero ? 10 : 11);
|
|
393
|
+
i0.ɵɵconditional(ctx_r1.ShowConnectingScreen(state_r4) ? 9 : ctx_r1.ShowHero ? 10 : ctx_r1.Ui.showThread ? 11 : -1);
|
|
384
394
|
i0.ɵɵadvance(5);
|
|
385
395
|
i0.ɵɵconditional(!ctx_r1.IsReviewing ? 14 : -1);
|
|
386
396
|
i0.ɵɵadvance();
|
|
387
397
|
i0.ɵɵconditional(ctx_r1.ShowPanelArea ? 15 : -1);
|
|
388
398
|
i0.ɵɵadvance();
|
|
389
|
-
i0.ɵɵproperty("Surfaces", ctx_r1.SurfaceStage.Surfaces)("TabSlot", (ctx_r1.StageSlot == null ? null : ctx_r1.StageSlot.Element) ?? null)("StageSlot", ctx_r1.StageSurfaceSlot)("ActiveTabKey", (ctx_r1.StageSlot == null ? null : ctx_r1.StageSlot.Key) ?? null)("PipRects", ctx_r1.PipRects)("PipKeepClear",
|
|
399
|
+
i0.ɵɵproperty("Surfaces", ctx_r1.SurfaceStage.Surfaces)("TabSlot", (ctx_r1.StageSlot == null ? null : ctx_r1.StageSlot.Element) ?? null)("StageSlot", ctx_r1.StageSurfaceSlot)("ActiveTabKey", (ctx_r1.StageSlot == null ? null : ctx_r1.StageSlot.Key) ?? null)("PipRects", ctx_r1.PipRects)("PipKeepClear", callControls_r24);
|
|
390
400
|
i0.ɵɵadvance(3);
|
|
391
401
|
i0.ɵɵconditional(!ctx_r1.IsReviewing && ctx_r1.ChannelFocusMode ? 19 : -1);
|
|
392
402
|
i0.ɵɵadvance();
|
|
393
|
-
i0.ɵɵconditional((
|
|
403
|
+
i0.ɵɵconditional((tmp_42_0 = ctx_r1.CameraCheck) ? 20 : -1, tmp_42_0);
|
|
394
404
|
} }
|
|
395
405
|
function RealtimeSessionOverlayComponent_ng_template_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
396
406
|
i0.ɵɵelementStart(0, "div", 66);
|
|
@@ -556,15 +566,27 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
556
566
|
*/
|
|
557
567
|
set Compact(value) { this.setUiInput('compact', value); }
|
|
558
568
|
get Compact() { return this._uiInputs.compact; }
|
|
559
|
-
/**
|
|
560
|
-
|
|
561
|
-
|
|
569
|
+
/**
|
|
570
|
+
* @deprecated No-op since 6.2: the idle fade was never implemented. Kept so existing bindings compile.
|
|
571
|
+
*/
|
|
572
|
+
set AutoHideControls(value) { void value; }
|
|
573
|
+
get AutoHideControls() { return undefined; }
|
|
562
574
|
/**
|
|
563
575
|
* May the user reveal the transcript at all? `false` makes the widget a pure voice orb
|
|
564
576
|
* with no path to text (and never graduates to a console in `'auto'`). Default `true`.
|
|
565
577
|
*/
|
|
566
578
|
set AllowTextReveal(value) { this.setUiInput('allowTextReveal', value); }
|
|
567
579
|
get AllowTextReveal() { return this._uiInputs.allowTextReveal; }
|
|
580
|
+
/**
|
|
581
|
+
* May the user type into the live session? Default `true`. Mirrors {@link RealtimeUiInputs.allowTyping}.
|
|
582
|
+
*
|
|
583
|
+
* Invariant: `false` removes every road to the composer (the strip's Type button, the lean sheet's Type
|
|
584
|
+
* button, the type-to-compose hotkey, and a programmatic open) and nothing else. It does not touch the
|
|
585
|
+
* transcript, captions, the surface panel or the channel strip. `AllowTextReveal=false` implies
|
|
586
|
+
* `AllowTyping=false`. Turning it off while the dock is open closes the dock; the draft is kept.
|
|
587
|
+
*/
|
|
588
|
+
set AllowTyping(value) { this.setUiInput('allowTyping', value); }
|
|
589
|
+
get AllowTyping() { return this._uiInputs.allowTyping; }
|
|
568
590
|
/** Show the captions toggle. Default `true`. */
|
|
569
591
|
set ShowCaptionsControl(value) { this.setUiInput('showCaptionsControl', value); }
|
|
570
592
|
get ShowCaptionsControl() { return this._uiInputs.showCaptionsControl; }
|
|
@@ -675,7 +697,8 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
675
697
|
/**
|
|
676
698
|
* A generic hook fired when ANY control is invoked — even one the host hid via inputs
|
|
677
699
|
* and re-rendered itself. Lets a host observe/react to the full control surface from a
|
|
678
|
-
* single binding.
|
|
700
|
+
* single binding. An open of the typing box that first reveals the conversation reports
|
|
701
|
+
* `'captions'` and `'reveal-text'` before `'type'`.
|
|
679
702
|
*/
|
|
680
703
|
ControlInvoked = new EventEmitter();
|
|
681
704
|
realtime = inject(RealtimeSessionService);
|
|
@@ -906,7 +929,7 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
906
929
|
}
|
|
907
930
|
/** Whether captions run over the agent's video: they are on, and the video presents in the hero. */
|
|
908
931
|
get ShowAvatarCaptions() {
|
|
909
|
-
return this.ShowCaptions && this.ShowHero && this.PresenterOnStage;
|
|
932
|
+
return this._ui.allowTextReveal && this.ShowCaptions && this.ShowHero && this.PresenterOnStage;
|
|
910
933
|
}
|
|
911
934
|
/**
|
|
912
935
|
* Whether the call body shows the connecting screen for `state`: while the call connects, outside review, unless the
|
|
@@ -1343,7 +1366,6 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1343
1366
|
// only asking for the conversation opens the thread.
|
|
1344
1367
|
TextRevealed: this.ShowCaptions && (this.conversationShown || !this.PresenterOnStage),
|
|
1345
1368
|
DisclosureShowThread: disclosure.ShowThread,
|
|
1346
|
-
DisclosureShowComposer: disclosure.ShowComposer,
|
|
1347
1369
|
DisclosureShowPanel: disclosure.ShowPanel,
|
|
1348
1370
|
DisclosureShowGear: disclosure.ShowGear,
|
|
1349
1371
|
// A surface to populate: the on-demand Details peek, or review (always has a surface).
|
|
@@ -1366,6 +1388,10 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1366
1388
|
const next = ResolveRealtimeUi(this.mergedUiInputs, this.buildSignals());
|
|
1367
1389
|
const prevChrome = this._ui.chrome;
|
|
1368
1390
|
this._ui = next;
|
|
1391
|
+
// The host just took typing away: close an open dock (the composer keeps its draft).
|
|
1392
|
+
if (!next.allowTyping && this.ComposerOpen) {
|
|
1393
|
+
this.ComposerOpen = false;
|
|
1394
|
+
}
|
|
1369
1395
|
if (next.chrome !== prevChrome) {
|
|
1370
1396
|
this.ChromeChanged.emit(next.chrome);
|
|
1371
1397
|
}
|
|
@@ -1466,15 +1492,22 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1466
1492
|
}
|
|
1467
1493
|
/**
|
|
1468
1494
|
* Resizing is meaningless while collapsed or when the surface fills (focus mode), and a
|
|
1469
|
-
* host may hard-disable it via `[AllowResize]="false"
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
* drag available in review exactly as before.
|
|
1495
|
+
* host may hard-disable it via `[AllowResize]="false"`. The resolver has no resize output,
|
|
1496
|
+
* so this reads the host flag directly (defaulting to the historical `true`), which keeps
|
|
1497
|
+
* drag available in review too.
|
|
1473
1498
|
*/
|
|
1474
1499
|
get PanelResizeDisabled() {
|
|
1475
1500
|
const hostAllows = this.mergedUiInputs.allowResize ?? DEFAULT_REALTIME_UI_INPUTS.allowResize;
|
|
1476
1501
|
return this.PanelCollapsed || this.ChannelFocusMode || !hostAllows;
|
|
1477
1502
|
}
|
|
1503
|
+
/**
|
|
1504
|
+
* The host's ceiling on developer links (`[ShowDevLinks]` / the bag), for the banner's gear toggle. Not the
|
|
1505
|
+
* resolved `Ui.showDevLinks`: that one is already ANDed with {@link DevMode}, which the toggle itself sets, so
|
|
1506
|
+
* binding it would hide the toggle until it was on.
|
|
1507
|
+
*/
|
|
1508
|
+
get AllowDevLinks() {
|
|
1509
|
+
return this.mergedUiInputs.showDevLinks ?? DEFAULT_REALTIME_UI_INPUTS.showDevLinks;
|
|
1510
|
+
}
|
|
1478
1511
|
OnPanelCollapsedChange(collapsed) {
|
|
1479
1512
|
this.PanelCollapsed = collapsed;
|
|
1480
1513
|
this.cdr.markForCheck();
|
|
@@ -1630,16 +1663,19 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1630
1663
|
get ShowPanelArea() {
|
|
1631
1664
|
return this._ui.showSurfacePanel || this.IsReviewing;
|
|
1632
1665
|
}
|
|
1633
|
-
/**
|
|
1666
|
+
/**
|
|
1667
|
+
* The Details (tabs) toggle is offered while live — it's the panel's one door — unless the host turned the surface
|
|
1668
|
+
* panel off. Reads the host ceiling, not the resolved {@link Ui.showSurfacePanel}: that one also needs the panel to be
|
|
1669
|
+
* earned, and Details is what earns it (same shape as {@link AllowDevLinks}).
|
|
1670
|
+
*/
|
|
1634
1671
|
get ShowDetailsControl() {
|
|
1635
|
-
return !this.IsReviewing;
|
|
1672
|
+
return !this.IsReviewing && (this.mergedUiInputs.showSurfacePanel ?? DEFAULT_REALTIME_UI_INPUTS.showSurfacePanel);
|
|
1636
1673
|
}
|
|
1637
1674
|
/**
|
|
1638
|
-
* Whether the active-channels strip renders
|
|
1639
|
-
* (
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
* itself self-hides when no channels are active, so this stays decluttered.
|
|
1675
|
+
* Whether the active-channels strip renders: the resolved {@link Ui.showChannelStrip}, which
|
|
1676
|
+
* honours the host's `[ShowChannels]` input (false hard-disables the strip), needs an active
|
|
1677
|
+
* channel and console chrome, and hides it in channel-focus mode. The strip component itself
|
|
1678
|
+
* self-hides when no channels are active, so this stays decluttered.
|
|
1643
1679
|
*/
|
|
1644
1680
|
get ChannelStripVisible() {
|
|
1645
1681
|
// Reads the RESOLVED Ui.showChannelStrip flag — which already ANDs the host's
|
|
@@ -1876,6 +1912,9 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1876
1912
|
}
|
|
1877
1913
|
/** The hero's "Show the conversation" affordance — turns the text preference on, and opens the thread even over the agent's video. */
|
|
1878
1914
|
OnTextReveal() {
|
|
1915
|
+
if (!this._ui.allowTextReveal) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1879
1918
|
this.conversationShown = true;
|
|
1880
1919
|
this.OnCaptionsToggled(true);
|
|
1881
1920
|
this.TextRevealed.emit();
|
|
@@ -1927,7 +1966,7 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1927
1966
|
* keyboard-capturing surface (the remote-browser canvas) already holds focus.
|
|
1928
1967
|
*/
|
|
1929
1968
|
OnDocumentKeydown(event) {
|
|
1930
|
-
if (this.Hidden || this.IsReviewing || !this.realtime.IsActive) {
|
|
1969
|
+
if (this.Hidden || this.IsReviewing || !this.realtime.IsActive || !this._ui.allowTyping) {
|
|
1931
1970
|
return;
|
|
1932
1971
|
}
|
|
1933
1972
|
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
@@ -1973,10 +2012,30 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
1973
2012
|
* The composer dock opened (strip Type control / type-to-compose keystroke) or closed (the
|
|
1974
2013
|
* dock's hide control). Opening raises the 'engaged' milestone for the cross-session ratchet —
|
|
1975
2014
|
* but the dock itself stays a per-session, user-owned toggle either way.
|
|
2015
|
+
*
|
|
2016
|
+
* Invariant: the typing box is open only while the conversation is readable — a participant must
|
|
2017
|
+
* see what they type (Caliber). Readable = the thread is shown AND captions are on: a console keeps
|
|
2018
|
+
* the thread with captions off, but the thread then hides every caption turn. (While the agent's
|
|
2019
|
+
* video presents in the hero, captions run over the video instead, and the thread waits for an
|
|
2020
|
+
* explicit reveal.) So an open while the conversation is unreadable reveals it first, through the
|
|
2021
|
+
* same path as the hero's "Show the conversation" ({@link OnTextReveal}: captions on and persisted,
|
|
2022
|
+
* {@link TextRevealed} emitted). Every road to the box (Type buttons, the hotkey, a programmatic
|
|
2023
|
+
* open) funnels through here; {@link OnCaptionsToggled} holds the other half.
|
|
1976
2024
|
*/
|
|
1977
2025
|
OnComposerOpenChanged(open) {
|
|
2026
|
+
// The host disallows typing: refuse any open (closing is always allowed). Checked before the reveal,
|
|
2027
|
+
// so a refused open reveals nothing.
|
|
2028
|
+
if (open && !this._ui.allowTyping) {
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
if (open && !(this._ui.showThread && this.ShowCaptions)) {
|
|
2032
|
+
this.OnTextReveal();
|
|
2033
|
+
}
|
|
1978
2034
|
this.ComposerOpen = open;
|
|
1979
2035
|
if (open) {
|
|
2036
|
+
// Opening the box asks for the conversation, like "Show the conversation": pin the thread, so the agent's video
|
|
2037
|
+
// arriving later (which keeps the hero for captions alone) does not hide it under an open box.
|
|
2038
|
+
this.conversationShown = true;
|
|
1980
2039
|
this.Disclosure.Raise('engaged'); // emits Changed$ → recomputeUi()
|
|
1981
2040
|
this.ControlInvoked.emit('type');
|
|
1982
2041
|
setTimeout(() => this.composer?.FocusInput());
|
|
@@ -2145,14 +2204,26 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2145
2204
|
* The captions toggle = the persisted text-vs-orb switch: on → the thread renders
|
|
2146
2205
|
* (raising the text disclosure milestone); off → the orb hero owns the screen again,
|
|
2147
2206
|
* at any level. Persisted per-user so the choice survives refresh and devices.
|
|
2207
|
+
*
|
|
2208
|
+
* Invariant: the typing box is open only while the conversation is readable (thread shown AND
|
|
2209
|
+
* captions on) — a participant must see what they type (Caliber). So turning captions off closes an
|
|
2210
|
+
* open box, in a console too, where the thread stays but hides its caption turns; the composer instance
|
|
2211
|
+
* is kept, so its draft survives and comes back with the next open. Every captions road (the CC
|
|
2212
|
+
* buttons, {@link SetCaptions}, {@link ToggleCaptions}) funnels through here;
|
|
2213
|
+
* {@link OnComposerOpenChanged} holds the other half.
|
|
2148
2214
|
*/
|
|
2149
2215
|
OnCaptionsToggled(on) {
|
|
2216
|
+
// The host disallows text: refuse turning captions on (nothing persisted or emitted). Off is always allowed.
|
|
2217
|
+
if (on && !this._ui.allowTextReveal) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2150
2220
|
this.ShowCaptions = on;
|
|
2151
2221
|
if (on) {
|
|
2152
2222
|
this.Disclosure.Raise('text'); // emits Changed$ → recomputeUi()
|
|
2153
2223
|
}
|
|
2154
2224
|
else {
|
|
2155
2225
|
this.conversationShown = false;
|
|
2226
|
+
this.ComposerOpen = false;
|
|
2156
2227
|
}
|
|
2157
2228
|
this.persistCaptionsPref();
|
|
2158
2229
|
this.ControlInvoked.emit('captions');
|
|
@@ -2461,7 +2532,7 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2461
2532
|
/**
|
|
2462
2533
|
* Reveal the transcript programmatically — the imperative twin of the orb's "Show the
|
|
2463
2534
|
* conversation" affordance. Raises the text disclosure level, turns captions on, and
|
|
2464
|
-
* emits {@link TextRevealed}.
|
|
2535
|
+
* emits {@link TextRevealed}. Does nothing when the host disallows text (`AllowTextReveal=false`).
|
|
2465
2536
|
*/
|
|
2466
2537
|
RevealText() {
|
|
2467
2538
|
this.OnTextReveal();
|
|
@@ -2492,7 +2563,8 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2492
2563
|
}
|
|
2493
2564
|
/**
|
|
2494
2565
|
* Set captions (the text-vs-orb preference) to an explicit state. Persisted per-user;
|
|
2495
|
-
* turning them on also raises the text disclosure level.
|
|
2566
|
+
* turning them on also raises the text disclosure level. Turning them on does nothing when
|
|
2567
|
+
* the host disallows text (`AllowTextReveal=false`); turning them off is always allowed.
|
|
2496
2568
|
*
|
|
2497
2569
|
* @param on whether captions/text should be shown.
|
|
2498
2570
|
*/
|
|
@@ -2565,8 +2637,8 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2565
2637
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.presenterSlotRef = _t.first);
|
|
2566
2638
|
} }, hostBindings: function RealtimeSessionOverlayComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
2567
2639
|
i0.ɵɵlistener("keydown", function RealtimeSessionOverlayComponent_keydown_HostBindingHandler($event) { return ctx.OnDocumentKeydown($event); }, i0.ɵɵresolveDocument);
|
|
2568
|
-
} }, inputs: { Hidden: "Hidden", AgentName: "AgentName", CurrentUser: "CurrentUser", EnvironmentID: "EnvironmentID", AvatarNoticeLabels: "AvatarNoticeLabels", ShowAvatarNotice: "ShowAvatarNotice", ShowPerceptionChip: "ShowPerceptionChip", Chrome: "Chrome", ConsoleBreakpointPx: "ConsoleBreakpointPx", Compact: "Compact", AutoHideControls: "AutoHideControls", AllowTextReveal: "AllowTextReveal", ShowCaptionsControl: "ShowCaptionsControl", ShowDensityPicker: "ShowDensityPicker", ShowMinimize: "ShowMinimize", ShowEnd: "ShowEnd", ShowSurfacePanel: "ShowSurfacePanel", ShowChannels: "ShowChannels", ShowActivityRail: "ShowActivityRail", ShowDevLinks: "ShowDevLinks", AllowResize: "AllowResize", UiConfig: "UiConfig", ReviewData: "ReviewData", AllowStartLive: "AllowStartLive" }, outputs: { Ended: "Ended", NavigateRequest: "NavigateRequest", StartLiveRequested: "StartLiveRequested", ReviewClosed: "ReviewClosed", Minimized: "Minimized", TextRevealed: "TextRevealed", ChromeChanged: "ChromeChanged", ConnectionStateChanged: "ConnectionStateChanged", MuteChanged: "MuteChanged", DensityChanged: "DensityChanged", SurfacePanelResized: "SurfacePanelResized", ControlInvoked: "ControlInvoked" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 3, consts: [["reviewBoardTpl", ""], ["recordingTpl", ""], ["callControls", ""], ["presenterSlot", ""], ["role", "region", 1, "call-overlay", 3, "call-overlay--hidden", "call-overlay--review", "board-focus"], ["role", "region", 1, "call-overlay"], [1, "call-main"], [3, "OpenSessionRequested", "DevModeToggled", "DensityChanged", "MinimizeRequested", "StartLiveRequested", "CloseRequested", "State", "Compact", "HideState", "AgentName", "ErrorLabel", "ModelName", "DevMode", "SessionID", "ReviewMode", "ReviewStartedAt", "ReviewClosedAt", "ReviewCloseReason", "ShowGear", "ShowDensityPicker", "ShowMinimize", "ShowStartLive", "Density"], ["Variant", "warning", "Size", "sm", "Icon", "fa-solid fa-microphone-slash", 1, "call-microphone-notice", 3, "Message"], ["Variant", "info", "Size", "sm", "Icon", "fa-solid fa-video-slash", "Dismissible", "", 1, "call-avatar-notice", 3, "Message"], ["role", "status", "aria-live", "polite", 1, "banner-live-note"], [1, "call-presenter", 3, "call-presenter--on"], [1, "call-body"], [1, "call-connecting"], ["role", "status", "aria-live", "polite", 1, "hero", 3, "hero--presenting"], [3, "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [1, "call-controls"], [1, "call-stage", 3, "PipRectChange", "Surfaces", "TabSlot", "StageSlot", "ActiveTabKey", "PipRects", "PipKeepClear"], ["mjMediaStageSurface", ""], ["mjMediaStagePipActions", ""], ["role", "status", "aria-label", "Live call (board focus)", 1, "board-focus-pill"], [3, "Camera", "AgentName", "Switch"], ["Variant", "info", "Size", "sm", "Icon", "fa-solid fa-video-slash", "Dismissible", "", 1, "call-avatar-notice", 3, "Dismissed", "Message"], ["aria-hidden", "true", 1, "fa-solid", "fa-comment-dots"], [1, "call-presenter"], ["text", "Connecting to the live session\u2026", "size", "medium"], ["role", "status", "aria-live", "polite", 1, "hero"], [1, "hero__presenter"], ["aria-hidden", "true", 1, "hero__caption"], [1, "hero__sr"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal"], [1, "hero__orb", 3, "State"], ["aria-hidden", "true", 1, "hero__eq"], [1, "hero__caption-window"], [1, "hero__caption-lines"], [1, "hero__caption-speaker"], [1, "hero__name"], [1, "hero__sub"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-closed-captioning"], [3, "OpenRunRequested", "OpenArtifactRequested", "CancelRequested", "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [3, "SourceToggled", "SourcePicked", "Sources"], [3, "OpenChanged", "CameraToggled", "ShareRequested", "StopShareRequested", "DeviceSelected", "CaptionsToggled", "DetailsToggled", "MuteChanged", "EndRequested", "Open", "Compact", "CaptionsOn", "IsMuted", "ShowCamera", "ShowShare", "CameraOn", "Sharing", "SharePanels", "Devices", "SelectedMicrophoneID", "SelectedCameraID", "ShowDetails", "DetailsOn"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "call-resizer--active"], [1, "call-panel"], [3, "OpenRunRequested", "CollapsedChange", "WideChanged", "ActiveChannelChange", "ChannelSlotChange", "MoveRequested", "ResetLayoutRequested", "State", "DevMode", "CurrentUser", "EnvironmentID", "ShowActivityTab", "ExtraArtifacts", "Collapsed", "SurfacePlacements", "AllowedPlacements"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "mousedown", "dblclick"], ["role", "region", 1, "stage-frame", 3, "mjSharePanel", "mjSharePanelIcon"], [3, "Plugin", "Visible", "Placement"], ["Placement", "stage", 1, "stage-presenter-move", 3, "Key", "Title", "Allowed"], ["Placement", "stage", 1, "stage-presenter-move", 3, "MoveRequested", "ResetLayoutRequested", "Key", "Title", "Allowed"], ["Placement", "pip", 3, "Key", "Title", "Allowed"], ["Placement", "pip", 3, "MoveRequested", "ResetLayoutRequested", "Key", "Title", "Allowed"], ["aria-hidden", "true", 1, "board-focus-pill__orb"], [1, "board-focus-pill__text"], [1, "board-focus-pill__name"], [1, "board-focus-pill__state"], ["type", "button", 1, "board-focus-pill__btn", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-solid"], ["
|
|
2569
|
-
i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Template, 21,
|
|
2640
|
+
} }, inputs: { Hidden: "Hidden", AgentName: "AgentName", CurrentUser: "CurrentUser", EnvironmentID: "EnvironmentID", AvatarNoticeLabels: "AvatarNoticeLabels", ShowAvatarNotice: "ShowAvatarNotice", ShowPerceptionChip: "ShowPerceptionChip", Chrome: "Chrome", ConsoleBreakpointPx: "ConsoleBreakpointPx", Compact: "Compact", AutoHideControls: "AutoHideControls", AllowTextReveal: "AllowTextReveal", AllowTyping: "AllowTyping", ShowCaptionsControl: "ShowCaptionsControl", ShowDensityPicker: "ShowDensityPicker", ShowMinimize: "ShowMinimize", ShowEnd: "ShowEnd", ShowSurfacePanel: "ShowSurfacePanel", ShowChannels: "ShowChannels", ShowActivityRail: "ShowActivityRail", ShowDevLinks: "ShowDevLinks", AllowResize: "AllowResize", UiConfig: "UiConfig", ReviewData: "ReviewData", AllowStartLive: "AllowStartLive" }, outputs: { Ended: "Ended", NavigateRequest: "NavigateRequest", StartLiveRequested: "StartLiveRequested", ReviewClosed: "ReviewClosed", Minimized: "Minimized", TextRevealed: "TextRevealed", ChromeChanged: "ChromeChanged", ConnectionStateChanged: "ConnectionStateChanged", MuteChanged: "MuteChanged", DensityChanged: "DensityChanged", SurfacePanelResized: "SurfacePanelResized", ControlInvoked: "ControlInvoked" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 3, consts: [["reviewBoardTpl", ""], ["recordingTpl", ""], ["callControls", ""], ["presenterSlot", ""], ["role", "region", 1, "call-overlay", 3, "call-overlay--hidden", "call-overlay--review", "board-focus"], ["role", "region", 1, "call-overlay"], [1, "call-main"], [3, "OpenSessionRequested", "DevModeToggled", "DensityChanged", "MinimizeRequested", "StartLiveRequested", "CloseRequested", "State", "Compact", "HideState", "AgentName", "ErrorLabel", "ModelName", "DevMode", "AllowDevLinks", "SessionID", "ReviewMode", "ReviewStartedAt", "ReviewClosedAt", "ReviewCloseReason", "ShowGear", "ShowDensityPicker", "ShowMinimize", "ShowStartLive", "Density"], ["Variant", "warning", "Size", "sm", "Icon", "fa-solid fa-microphone-slash", 1, "call-microphone-notice", 3, "Message"], ["Variant", "info", "Size", "sm", "Icon", "fa-solid fa-video-slash", "Dismissible", "", 1, "call-avatar-notice", 3, "Message"], ["role", "status", "aria-live", "polite", 1, "banner-live-note"], [1, "call-presenter", 3, "call-presenter--on"], [1, "call-body"], [1, "call-connecting"], ["role", "status", "aria-live", "polite", 1, "hero", 3, "hero--presenting"], [3, "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [1, "call-controls"], [1, "call-stage", 3, "PipRectChange", "Surfaces", "TabSlot", "StageSlot", "ActiveTabKey", "PipRects", "PipKeepClear"], ["mjMediaStageSurface", ""], ["mjMediaStagePipActions", ""], ["role", "status", "aria-label", "Live call (board focus)", 1, "board-focus-pill"], [3, "Camera", "AgentName", "Switch"], ["Variant", "info", "Size", "sm", "Icon", "fa-solid fa-video-slash", "Dismissible", "", 1, "call-avatar-notice", 3, "Dismissed", "Message"], ["aria-hidden", "true", 1, "fa-solid", "fa-comment-dots"], [1, "call-presenter"], ["text", "Connecting to the live session\u2026", "size", "medium"], ["role", "status", "aria-live", "polite", 1, "hero"], [1, "hero__presenter"], ["aria-hidden", "true", 1, "hero__caption"], [1, "hero__sr"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal"], [1, "hero__orb", 3, "State"], ["aria-hidden", "true", 1, "hero__eq"], [1, "hero__caption-window"], [1, "hero__caption-lines"], [1, "hero__caption-speaker"], [1, "hero__name"], [1, "hero__sub"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-closed-captioning"], [3, "OpenRunRequested", "OpenArtifactRequested", "CancelRequested", "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [3, "SourceToggled", "SourcePicked", "Sources"], [3, "OpenChanged", "CameraToggled", "ShareRequested", "StopShareRequested", "DeviceSelected", "CaptionsToggled", "DetailsToggled", "MuteChanged", "EndRequested", "Open", "Compact", "AllowTyping", "ShowEnd", "ShowCaptionsControl", "CaptionsOn", "IsMuted", "ShowCamera", "ShowShare", "CameraOn", "Sharing", "SharePanels", "Devices", "SelectedMicrophoneID", "SelectedCameraID", "ShowDetails", "DetailsOn"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "call-resizer--active"], [1, "call-panel"], [3, "OpenRunRequested", "CollapsedChange", "WideChanged", "ActiveChannelChange", "ChannelSlotChange", "MoveRequested", "ResetLayoutRequested", "State", "DevMode", "CurrentUser", "EnvironmentID", "ShowActivityTab", "ExtraArtifacts", "Collapsed", "SurfacePlacements", "AllowedPlacements"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "mousedown", "dblclick"], ["role", "region", 1, "stage-frame", 3, "mjSharePanel", "mjSharePanelIcon"], [3, "Plugin", "Visible", "Placement"], ["Placement", "stage", 1, "stage-presenter-move", 3, "Key", "Title", "Allowed"], ["Placement", "stage", 1, "stage-presenter-move", 3, "MoveRequested", "ResetLayoutRequested", "Key", "Title", "Allowed"], ["Placement", "pip", 3, "Key", "Title", "Allowed"], ["Placement", "pip", 3, "MoveRequested", "ResetLayoutRequested", "Key", "Title", "Allowed"], ["aria-hidden", "true", 1, "board-focus-pill__orb"], [1, "board-focus-pill__text"], [1, "board-focus-pill__name"], [1, "board-focus-pill__state"], ["type", "button", 1, "board-focus-pill__btn", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-solid"], ["aria-hidden", "true", 1, "fa-solid", "fa-table-columns"], ["Placement", "stage", 3, "Key", "Title", "Allowed"], ["type", "button", "title", "End call", "aria-label", "End the call", 1, "board-focus-pill__btn", "board-focus-pill__btn--end"], ["Placement", "stage", 3, "MoveRequested", "ResetLayoutRequested", "Key", "Title", "Allowed"], ["type", "button", "title", "End call", "aria-label", "End the call", 1, "board-focus-pill__btn", "board-focus-pill__btn--end", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone-slash"], [3, "Confirmed", "Declined", "Camera", "AgentName", "Switch"], [1, "review-board-host"], [3, "State", "AgentName", "ReadOnly"], [1, "review-recording-host"], [3, "FileID", "Transcript", "Provider"]], template: function RealtimeSessionOverlayComponent_Template(rf, ctx) { if (rf & 1) {
|
|
2641
|
+
i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Template, 21, 44, "div", 4);
|
|
2570
2642
|
i0.ɵɵpipe(1, "async");
|
|
2571
2643
|
i0.ɵɵtemplate(2, RealtimeSessionOverlayComponent_ng_template_2_Template, 1, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor)(4, RealtimeSessionOverlayComponent_ng_template_4_Template, 2, 3, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
|
|
2572
2644
|
} if (rf & 2) {
|
|
@@ -2613,7 +2685,7 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2613
2685
|
RealtimeWhiteboardBoardComponent,
|
|
2614
2686
|
MJStorageMediaPlayerComponent,
|
|
2615
2687
|
MJAlertComponent
|
|
2616
|
-
], template: "@if (ConnectionState$ | async; as state) {\n <div class=\"call-overlay\"\n [class.call-overlay--hidden]=\"Hidden\"\n [class.call-overlay--review]=\"IsReviewing\"\n [class.board-focus]=\"ChannelFocusMode\"\n role=\"region\" [attr.aria-label]=\"IsReviewing ? 'Past session review' : 'Live voice session'\">\n\n <!-- PROGRESSIVE DISCLOSURE (the console that grows with you \u2014 Redesign A Progressive):\n level 0 is PURE AUDIO (the hero orb owns the main column; no thread, no composer;\n the strip's Details control peeks at the panels on demand); level 1 reveals the\n caption thread + captions control + type-to-type hint; level 2+ docks the composer\n and keeps the surface panel. CONTENT NEVER FLIPS THE CONSOLE OPEN: finished\n artifacts land as glowing unfocused tabs; the ONE auto-reveal is a channel's\n first agent activity (e.g. the first whiteboard write), which opens the panel as\n a peek with that tab focused \u2014 the left column stays exactly as it was.\n REVIEW always renders the full console. -->\n <div class=\"call-main\">\n <!-- The UNIFIED APP-BAR: identity + state + every session action (Redesign A). -->\n <mj-realtime-agent-banner\n [State]=\"state\"\n [Compact]=\"!IsReviewing && Ui.compact && Ui.chrome === 'orb'\"\n [HideState]=\"ShowHero && !PresenterOnStage\"\n [AgentName]=\"AgentName\"\n [ErrorLabel]=\"CallEndLabel\"\n [ModelName]=\"IsReviewing ? null : (ModelName$ | async)\"\n [DevMode]=\"DevMode\"\n [SessionID]=\"IsReviewing ? ReviewSessionID : SessionID\"\n [ReviewMode]=\"IsReviewing\"\n [ReviewStartedAt]=\"ReviewStartedAt\"\n [ReviewClosedAt]=\"ReviewClosedAt\"\n [ReviewCloseReason]=\"ReviewCloseReason\"\n [ShowGear]=\"!IsReviewing && Ui.showGear\"\n [ShowDensityPicker]=\"Ui.showDensityPicker\"\n [ShowMinimize]=\"!IsReviewing && Ui.showMinimize\"\n [ShowStartLive]=\"AllowStartLive\"\n [Density]=\"Disclosure.Milestones.Density\"\n (OpenSessionRequested)=\"OnOpenSessionRequested($event)\"\n (DevModeToggled)=\"OnDevModeToggled($event)\"\n (DensityChanged)=\"OnDensityChanged($event)\"\n (MinimizeRequested)=\"OnMinimize()\"\n (StartLiveRequested)=\"OnStartLive()\"\n (CloseRequested)=\"OnReviewClose()\">\n </mj-realtime-agent-banner>\n\n <!-- No microphone works: the call lost its microphone, so the agent hears nothing until the user picks one in the\n device menu. It stays for as long as that lasts, with no dismiss button, and goes once a microphone is on.\n Never in review. -->\n @if (MicrophoneNotice; as message) {\n <mj-alert class=\"call-microphone-notice\"\n Variant=\"warning\"\n Size=\"sm\"\n Icon=\"fa-solid fa-microphone-slash\"\n [Message]=\"message\">\n </mj-alert>\n }\n\n <!-- Why the call shows no avatar its agent asked for: once per call, from the moment it connects, until the user\n dismisses it or it hides itself after 10 s. Never in review. -->\n @if (AvatarNotice) {\n <mj-alert class=\"call-avatar-notice\"\n Variant=\"info\"\n Size=\"sm\"\n Icon=\"fa-solid fa-video-slash\"\n [Message]=\"AvatarNoticeMessage\"\n Dismissible\n (Dismissed)=\"OnAvatarNoticeDismissed()\">\n </mj-alert>\n }\n\n <!-- Ephemeral narration with no active working card to anchor to \u2192 under the banner -->\n @if (!IsReviewing && State.Narration && !State.ActiveCallId && Disclosure.ShowThread) {\n <div class=\"banner-live-note\" role=\"status\" aria-live=\"polite\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n {{ State.Narration }}\n </div>\n }\n\n <!-- The agent's place above the thread in the console: the agent's video (the Avatar) lies over it while it presents.\n Outside the scrolling body, so it stays in view as the conversation grows. -->\n @if (!ShowHero) {\n <div class=\"call-presenter\" [class.call-presenter--on]=\"PresenterOnStage\" #presenterSlot></div>\n }\n\n <!-- While the call connects, the connecting screen takes the body, except from a hero the agent's video presents in:\n a resume on a new connection keeps that hero, so the video keeps its place and its last frame. -->\n <div class=\"call-body\" [class.call-body--hero]=\"ShowHero && !ShowConnectingScreen(state)\">\n @if (ShowConnectingScreen(state)) {\n <div class=\"call-connecting\">\n <mj-loading text=\"Connecting to the live session\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (ShowHero) {\n <!-- PURE-AUDIO HERO (level 0): the orb IS the interface \u2014 it breathes with the\n audio inside expanding sound-wave rings; nothing to read, nothing to configure. -->\n <div class=\"hero\" [class.hero--presenting]=\"PresenterOnStage\" [attr.data-state]=\"state\" role=\"status\" aria-live=\"polite\">\n @if (!PresenterOnStage) {\n <mj-realtime-agent-orb class=\"hero__orb\" [State]=\"HeroOrbState(state)\"></mj-realtime-agent-orb>\n <div class=\"hero__eq\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n }\n <!-- The agent's place in the hero: the agent's video (the Avatar) lies over it while it presents. With\n captions on, the newest caption runs over the video's lower third, as subtitles do. Hidden from\n assistive technology: the conversation, a click away, is the accessible transcript, and this live\n region would read every word as it arrives. -->\n <div class=\"hero__presenter\" [class.hero__presenter--on]=\"PresenterOnStage\" #presenterSlot>\n @if (ShowAvatarCaptions && AvatarCaption; as caption) {\n <p class=\"hero__caption\" aria-hidden=\"true\">\n <span class=\"hero__caption-window\">\n <span class=\"hero__caption-lines\">\n @if (caption.Speaker) {\n <span class=\"hero__caption-speaker\">{{ caption.Speaker }}:</span>\n }\n {{ caption.Text }}\n </span>\n </span>\n </p>\n }\n </div>\n @if (!PresenterOnStage) {\n <div class=\"hero__name\">{{ AgentName }}</div>\n <div class=\"hero__sub\">{{ HeroStateLabel(state) }}</div>\n } @else {\n <!-- The agent's tile names it and says what it is doing; this live region still says it for screen readers. -->\n <span class=\"hero__sr\">{{ HeroStateLabel(state) }}</span>\n }\n @if (PresenterOnStage || !Ui.showCaptionsControl) {\n <button type=\"button\" class=\"hero__reveal\" (click)=\"OnTextReveal()\"\n title=\"Show the conversation as text\">\n <i class=\"fa-solid fa-closed-captioning\" aria-hidden=\"true\"></i>\n Show the conversation\n </button>\n }\n </div>\n } @else {\n <mj-realtime-session-thread\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [UserName]=\"CurrentUser?.Name || 'You'\"\n [ShowCaptions]=\"IsReviewing ? true : ShowCaptions\"\n [DevMode]=\"DevMode\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (OpenArtifactRequested)=\"OnOpenArtifactRequested($event)\"\n (CancelRequested)=\"OnCancelDelegation($event)\">\n </mj-realtime-session-thread>\n }\n </div>\n\n <!-- The call's controls along the foot of the column. The stage starts its picture-in-picture boxes clear of\n them, so a camera or a shared screen never opens over End call (#5408). Empty, so with no size, in review. -->\n <div class=\"call-controls\" #callControls>\n @if (!IsReviewing) {\n <!-- \"Agent can see\": what the agent is looking at, with a switch per source. Shown while the user\n captures camera/screen (or when [ShowPerceptionChip]=\"true\"). -->\n <mj-realtime-perception-chip [Sources]=\"VisibleVideoSources\"\n (SourceToggled)=\"OnVideoSourceToggled($event)\"\n (SourcePicked)=\"OnVideoSourcePicked($event)\">\n </mj-realtime-perception-chip>\n\n <!-- Active channels (earned with the console at level 2+, host-gated via [ShowChannels]).\n The strip component self-hides when there are no active channels. -->\n @if (ChannelStripVisible) {\n <mj-realtime-channel-strip></mj-realtime-channel-strip>\n }\n\n <!-- The BOTTOM DOCK: phone-call strip \u21C4 fused minis+composer \u2014 the user's own\n two-way toggle (Type opens, hide closes), never level-forced. -->\n <mj-realtime-composer\n [Open]=\"ComposerOpen\"\n [Compact]=\"Ui.chrome === 'orb' || Ui.compact\"\n (OpenChanged)=\"OnComposerOpenChanged($event)\"\n [CaptionsOn]=\"ShowCaptions\"\n [IsMuted]=\"Muted\"\n [ShowCamera]=\"CaptureOffers.Camera\"\n [ShowShare]=\"CaptureOffers.Screen\"\n [CameraOn]=\"CameraOn\"\n [Sharing]=\"Sharing\"\n [SharePanels]=\"SharePanels\"\n [Devices]=\"CallDevices\"\n [SelectedMicrophoneID]=\"SelectedMicrophoneID\"\n [SelectedCameraID]=\"SelectedCameraID\"\n (CameraToggled)=\"OnCameraToggled($event)\"\n (ShareRequested)=\"OnShareRequested($event)\"\n (StopShareRequested)=\"OnStopShareRequested()\"\n (DeviceSelected)=\"OnDeviceSelected($event)\"\n [ShowDetails]=\"ShowDetailsControl\"\n [DetailsOn]=\"DetailsPeek\"\n (CaptionsToggled)=\"OnCaptionsToggled($event)\"\n (DetailsToggled)=\"OnDetailsToggled()\"\n (MuteChanged)=\"OnComposerMuteChanged($event)\"\n (EndRequested)=\"OnEndCall()\">\n </mj-realtime-composer>\n }\n </div>\n </div>\n\n <!-- RIGHT: TABBED SURFACE PANEL \u2014 disclosure-gated: it EXISTS once earned (level 2+,\n which the first delegation forces), peeked on demand via the strip's Details\n control, or always in review. Slides in from the right when it arrives \u2014 the\n panel's arrival IS the progress cue. -->\n @if (ShowPanelArea) {\n <!-- Resize handle (hidden while the panel is collapsed or a channel holds focus). -->\n @if (!PanelResizeDisabled) {\n <div class=\"call-resizer\"\n [class.call-resizer--active]=\"IsPanelResizing\"\n role=\"separator\" aria-orientation=\"vertical\"\n aria-label=\"Resize the surface panel (double-click to reset)\"\n title=\"Drag to resize \u2022 double-click to reset\"\n (mousedown)=\"OnPanelResizeStart($event)\"\n (dblclick)=\"OnPanelResizeReset()\"></div>\n }\n\n <div class=\"call-panel\" [style.width.px]=\"PanelAreaSize\">\n <mj-realtime-surface-tabs\n [State]=\"State\"\n [DevMode]=\"DevMode\"\n [CurrentUser]=\"CurrentUser\"\n [EnvironmentID]=\"EnvironmentID\"\n [ShowActivityTab]=\"ShowActivityTab\"\n [ExtraArtifacts]=\"ReviewArtifacts\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n [Collapsed]=\"PanelCollapsed\"\n (CollapsedChange)=\"OnPanelCollapsedChange($event)\"\n (WideChanged)=\"OnPanelWideChanged($event)\"\n (ActiveChannelChange)=\"OnActiveChannelChange($event)\"\n (ChannelSlotChange)=\"OnChannelSlotChange($event)\"\n [SurfacePlacements]=\"SurfaceStage.Placements\"\n [AllowedPlacements]=\"SurfaceStage.AllowedPlacements\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-realtime-surface-tabs>\n </div>\n }\n\n <!-- THE STAGE: every channel surface lives here, in one layer over the overlay. A surface\n is created the first time it is seen, lies over its tab's slot while that tab is\n active, fills the overlay while its channel holds focus (or, for the agent's video,\n lies in the agent's place in the call), and otherwise waits out of sight \u2014 so\n collapsing the panel, switching tabs or entering focus never reloads it.\n The pane tells each channel when its surface goes out of sight, so it can pause.\n A channel that opts in (SurfaceShareable, such as the whiteboard) marks its frame as a panel the Share menu\n offers under \"This panel\" while it is on screen. A picture-in-picture box the user has not moved starts\n clear of the call's controls. -->\n <mj-media-stage class=\"call-stage\"\n [Surfaces]=\"SurfaceStage.Surfaces\"\n [TabSlot]=\"StageSlot?.Element ?? null\"\n [StageSlot]=\"StageSurfaceSlot\"\n [ActiveTabKey]=\"StageSlot?.Key ?? null\"\n [PipRects]=\"PipRects\"\n [PipKeepClear]=\"callControls\"\n (PipRectChange)=\"OnPipRectChange($event)\">\n <ng-template mjMediaStageSurface let-key let-visible=\"Visible\" let-placement=\"Placement\">\n @if (SurfaceStage.PluginFor(key); as plugin) {\n <div class=\"stage-frame\" role=\"region\" [attr.aria-label]=\"plugin.TabTitle\"\n [mjSharePanel]=\"plugin.SurfaceShareable ? plugin.TabTitle : null\" [mjSharePanelIcon]=\"plugin.TabIcon\">\n <mj-realtime-channel-pane [Plugin]=\"plugin\" [Visible]=\"visible\" [Placement]=\"placement\"></mj-realtime-channel-pane>\n <!-- A surface presenting the agent has no focus pill, so its \"Move to\u2026\" menu rides on it. -->\n @if (placement === 'stage' && plugin.ShowsAgentVideo) {\n <mj-media-move-menu\n class=\"stage-presenter-move\"\n [Key]=\"key\"\n [Title]=\"plugin.TabTitle\"\n Placement=\"stage\"\n [Allowed]=\"SurfaceStage.AllowedFor(key)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n </div>\n }\n </ng-template>\n <!-- A picture-in-picture box's bar carries the same \"Move to\u2026\" menu. -->\n <ng-template mjMediaStagePipActions let-key>\n @if (SurfaceStage.PluginFor(key); as plugin) {\n <mj-media-move-menu\n [Key]=\"key\"\n [Title]=\"plugin.TabTitle\"\n Placement=\"pip\"\n [Allowed]=\"SurfaceStage.AllowedFor(key)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n </ng-template>\n </mj-media-stage>\n\n <!-- FOCUS floating call pill: the main column is hidden, so the live call's\n identity + state + mute / show-thread / end controls ride over the surface. -->\n @if (!IsReviewing && ChannelFocusMode) {\n <div class=\"board-focus-pill\" role=\"status\" aria-label=\"Live call (board focus)\">\n <span class=\"board-focus-pill__orb\" [attr.data-state]=\"state\" aria-hidden=\"true\"></span>\n <span class=\"board-focus-pill__text\">\n <span class=\"board-focus-pill__name\">Voice for {{ AgentName }}</span>\n @if (FocusStateLabel(state); as label) {\n <span class=\"board-focus-pill__state\">{{ label }}</span>\n }\n </span>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n [class.board-focus-pill__btn--active]=\"FocusPillMuted\"\n (click)=\"OnFocusPillMute()\"\n [title]=\"FocusPillMuted ? 'Unmute' : 'Mute'\"\n [attr.aria-label]=\"FocusPillMuted ? 'Unmute microphone' : 'Mute microphone'\">\n <i class=\"fa-solid\" [class.fa-microphone]=\"!FocusPillMuted\" [class.fa-microphone-slash]=\"FocusPillMuted\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n (click)=\"OnFocusPillExit()\"\n title=\"Show thread\" aria-label=\"Show the conversation thread\">\n <i class=\"fa-solid fa-table-columns\" aria-hidden=\"true\"></i>\n </button>\n @if (StagePlugin; as stagePlugin) {\n <mj-media-move-menu\n [Key]=\"stagePlugin.ChannelName\"\n [Title]=\"stagePlugin.TabTitle\"\n Placement=\"stage\"\n [Allowed]=\"SurfaceStage.AllowedFor(stagePlugin.ChannelName)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n <button type=\"button\" class=\"board-focus-pill__btn board-focus-pill__btn--end\"\n (click)=\"OnFocusPillEnd()\"\n title=\"End call\" aria-label=\"End the call\">\n <i class=\"fa-solid fa-phone-slash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <!-- THE CAMERA CHECK: the first time the user turns the camera on in a call, they see it, and can pick another\n camera, before the agent does. Until they choose, the camera's own box says it is waiting. -->\n @if (CameraCheck; as camera) {\n <mj-realtime-camera-check-card\n [Camera]=\"camera\"\n [AgentName]=\"AgentName\"\n [Switch]=\"SwitchCheckedCamera\"\n (Confirmed)=\"OnCameraCheckConfirmed()\"\n (Declined)=\"OnCameraCheckDeclined()\">\n </mj-realtime-camera-check-card>\n }\n\n </div>\n}\n\n<!-- SESSION REVIEW whiteboard pane body: the reviewed session's saved board, rehydrated\n locally (WhiteboardState.FromJSON) and rendered READ-ONLY (pan/zoom only). Registered\n as a template-based channel tab ONLY when a parseable Whiteboard state exists. -->\n<ng-template #reviewBoardTpl>\n @if (ReviewWhiteboard; as board) {\n <!-- The board host positions itself absolutely \u2014 give it a filling, positioned frame. -->\n <div class=\"review-board-host\">\n <mj-realtime-whiteboard\n [State]=\"board\"\n [AgentName]=\"AgentName\"\n [ReadOnly]=\"true\">\n </mj-realtime-whiteboard>\n </div>\n }\n</ng-template>\n\n<!-- SESSION REVIEW recording pane body: the reviewed session's recorded audio synced to its\n transcript, replayed via the shared storage media player (click-to-seek transcript + playhead\n highlight). Registered as a template-based channel tab ONLY when the reviewed session carries a\n RecordingFileID; the player resolves the authenticated audio itself from that file id. -->\n<ng-template #recordingTpl>\n <div class=\"review-recording-host\">\n <mj-storage-media-player\n [FileID]=\"ReviewRecordingFileID\"\n [Transcript]=\"ReviewCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n </div>\n</ng-template>\n", styles: [":host {\n display: contents;\n}\n\n/* Fills the conversation panel IN PLACE: the chat area host provides the positioned\n context (position:relative on .chat-area) and this covers it edge to edge \u2014 header,\n thread and composer included. Opaque-ish page surface with a slight blur so the\n conversation underneath reads as \"paused\", not as an app-wide modal. */\n.call-overlay {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n/* Minimized: hidden (NOT destroyed) so the shell's session/view state survives while\n the host shows its floating \"on call\" pill. The audio session is untouched. */\n.call-overlay--hidden {\n display: none;\n}\n\n/* MAIN column: banner (top) \u2192 scrolling thread \u2192 channel strip \u2192 composer \u2192 controls.\n The 420px floor keeps the call column readable while the panel is dragged wide\n (the drag clamp's 70% cap is the other guard). */\n.call-main {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* The call's controls at the foot of the column: the \"Agent can see\" chip, the channel strip and the composer. The\n stage starts its picture-in-picture boxes above this box, so it is never squeezed below its content's height. */\n.call-controls {\n flex: none;\n}\n\n.call-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body::-webkit-scrollbar {\n width: 6px;\n}\n.call-body::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n/* ---------- PURE-AUDIO HERO (disclosure level 0) ----------\n The orb IS the interface: it breathes with the audio inside expanding sound-wave\n rings, with a small voice equalizer underneath. Nothing to read, nothing to\n configure \u2014 text, panels and the composer are revealed later (or peeked on demand). */\n.call-body--hero {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n/* The orb (mj-realtime-agent-orb) at hero scale. */\n.hero__orb {\n margin-bottom: 4px;\n}\n/* Voice equalizer: lively while the agent speaks, near-still otherwise. */\n.hero__eq {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq span {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'] .hero__eq span {\n animation: hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq span:nth-child(1) { animation-delay: 0s; }\n.hero__eq span:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq span:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq span:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq span:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq span:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq span:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq span:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq span:nth-child(9) { animation-delay: 0.03s; }\n@keyframes hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n/* ---------- the agent's place while its video presents (the Avatar) ----------\n The stage lays the agent's video over these boxes; each has a size only while the video presents. Portrait, as\n avatar video is (Google's samples are 704x1280). */\n.hero--presenting {\n height: 100%;\n min-height: 0;\n}\n.hero__presenter,\n.call-presenter {\n display: none;\n}\n.hero__presenter--on {\n display: block;\n position: relative;\n container-type: size;\n flex: 1 1 auto;\n min-height: 160px;\n max-height: 520px;\n aspect-ratio: 3 / 4;\n margin-bottom: 8px;\n}\n/* Captions over the agent's video, as subtitles: the newest two lines, over the lower third and clear of the tile's\n footer. Above the stage (z-index 1), where the video is. Light on a dark band in both themes, like the tile's chips. */\n.hero__caption {\n position: absolute;\n left: 6%;\n right: 6%;\n bottom: 16%;\n z-index: 2;\n margin: 0;\n padding: 6px 10px;\n border-radius: var(--mj-radius-md);\n background: rgba(0, 0, 0, 0.6);\n color: rgba(255, 255, 255, 0.95);\n font-size: 0.95rem;\n line-height: 1.4;\n text-align: center;\n}\n/* Two lines, the newest: older ones leave through the top. The window clips, not the padded band, so nothing of an\n older line shows in the padding. */\n.hero__caption-window {\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n max-height: 2.8em;\n overflow: hidden;\n}\n.hero__caption-speaker {\n font-weight: 600;\n}\n/* A small tile (a short window): one smaller line, so the video still shows. */\n@container (max-height: 240px) {\n .hero__caption {\n bottom: 22%;\n padding: 3px 6px;\n font-size: 0.75rem;\n }\n .hero__caption-window {\n max-height: 1.4em;\n }\n}\n/* In the console it sits above the scrolling thread, so it stays in view as the conversation grows. Wide enough for\n the tile's \"AI-generated video\" label on one line. */\n.call-presenter--on {\n display: block;\n flex: none;\n align-self: center;\n height: 200px;\n aspect-ratio: 4 / 5;\n margin-top: 12px;\n}\n.hero__name {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n/* The hero's state for screen readers only, while the agent's tile shows it. */\n.hero__sr {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n/* ---------- AUDIO-REACTIVE MODE (data-audio-live, set by the rAF meter loop) ----------\n With real audio metering the visuals stop acting and start REACTING: the EQ renders the\n true spectrum (--eq-1..9), and [data-voice-dir] colors it by who is speaking (agent =\n brand, user = green). The orb (mj-realtime-agent-orb) reads the same attributes and the\n smoothed output envelope (--voice-out) itself. Without metering (driver attached no\n meters) the attribute stays 'false' and the turn-state keyframe animations stay in charge. */\n/* True-spectrum EQ: per-bar heights from the meter's bins. */\n.call-overlay[data-audio-live='true'] .hero__eq span {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__eq span {\n background: var(--mj-status-success);\n}\n\n/* The quiet first reveal: show the conversation as text (raises disclosure to level 1). */\n.hero__reveal {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal i {\n font-size: 11px;\n}\n\n/* Ephemeral narration shown under the banner when no working card anchors it */\n.banner-live-note {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: overlay-note-in 220ms ease;\n}\n.banner-live-note i {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* The call's notices under the banner (no microphone works; no avatar): mj-alert brings its own tint, border and text\n colours; this only places them. */\n.call-microphone-notice,\n.call-avatar-notice {\n flex-shrink: 0;\n margin: var(--mj-space-2) var(--mj-space-5) 0;\n}\n\n/* ---------- FOCUS-BOARD mode (whiteboard \"Focus board\" toggle) ----------\n The main call column collapses away entirely and the focused channel's surface fills\n the stage; a compact floating call pill keeps the live call reachable. */\n.call-overlay.board-focus .call-main {\n display: none;\n}\n\n/* ---------- the stage (every channel surface, in one layer) ----------\n mj-media-stage covers the overlay and places each surface itself. Above the panel it\n lays surfaces over, and above the call's controls, so its picture-in-picture boxes\n start clear of them (.call-controls); below the onboarding card (5), the menus of the\n dock and the chip (5) and the focus pill (60). */\n.call-stage {\n z-index: 1;\n}\n/* A surface's frame: fills the box the stage gives it. The surface component is created\n beside the pane host inside it, and sizes itself to it (height: 100%). */\n.stage-frame {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n/* The \"Move to\u2026\" menu on a surface that presents the agent, backed so it reads over video. Bottom right, in the\n tile's footer, which holds nothing there for the agent, so it never covers the tile's labels. */\n.stage-presenter-move {\n position: absolute;\n bottom: 4px;\n right: 6px;\n z-index: 1;\n border-radius: var(--mj-radius-full);\n background: color-mix(in srgb, var(--mj-bg-surface) 85%, transparent);\n}\n\n/* Floating call pill: orb + state + mute / show-thread / end (per the whiteboard mockup). */\n.board-focus-pill {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'],\n.board-focus-pill__orb[data-state='listening'] {\n animation: board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n/* ---------- Session review mode ---------- */\n/* Subtle archival tint so a glance says \"this is history, not a live call\". */\n.call-overlay--review {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n/* The review whiteboard pane body: the board component positions itself absolutely\n (inset: 0), so its template-based pane needs a filling, positioned frame. */\n.review-board-host {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* The review recording pane body: the evidence player fills its host (:host height:100%),\n so its template-based pane needs a filling, constrained frame. */\n.review-recording-host {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n.review-recording-host mj-realtime-evidence-playback {\n flex: 1;\n min-height: 0;\n}\n\n/* ---------- flex layout (main column | resize handle | surface panel) ---------- */\n/* Token-styled resize handle: invisible until hover, brand-tinted while interacting.\n 7px hit target, col-resize cursor; removed (not rendered) while collapsed / focus. */\n.call-resizer {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer:hover,\n.call-resizer--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n/* The surface panel: a plain fixed-width flex item (inline width set by the overlay;\n collapsed = the slim strip width). flex-shrink lets it yield to the call column's\n min-width on tight overlays; overflow hidden keeps the flex height chain intact so\n the panel's content scrolls internally. */\n.call-panel {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n /* Disclosure-gated: the panel is CREATED when earned/peeked \u2014 its arrival slides in\n from the right so the reveal doubles as the progress cue. */\n animation: panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel > * {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n"] }]
|
|
2688
|
+
], template: "@if (ConnectionState$ | async; as state) {\n <div class=\"call-overlay\"\n [class.call-overlay--hidden]=\"Hidden\"\n [class.call-overlay--review]=\"IsReviewing\"\n [class.board-focus]=\"ChannelFocusMode\"\n role=\"region\" [attr.aria-label]=\"IsReviewing ? 'Past session review' : 'Live voice session'\">\n\n <!-- PROGRESSIVE DISCLOSURE (the console that grows with you \u2014 Redesign A Progressive):\n level 0 is PURE AUDIO (the hero orb owns the main column; no thread, no composer;\n the strip's Details control peeks at the panels on demand); level 1 reveals the\n caption thread + captions control + type-to-type hint; level 2+ docks the composer\n and keeps the surface panel. CONTENT NEVER FLIPS THE CONSOLE OPEN: finished\n artifacts land as glowing unfocused tabs; the ONE auto-reveal is a channel's\n first agent activity (e.g. the first whiteboard write), which opens the panel as\n a peek with that tab focused \u2014 the left column stays exactly as it was.\n REVIEW always renders the full console. -->\n <div class=\"call-main\">\n <!-- The UNIFIED APP-BAR: identity + state + every session action (Redesign A). -->\n <mj-realtime-agent-banner\n [State]=\"state\"\n [Compact]=\"!IsReviewing && Ui.compact && Ui.chrome === 'orb'\"\n [HideState]=\"ShowHero && !PresenterOnStage\"\n [AgentName]=\"AgentName\"\n [ErrorLabel]=\"CallEndLabel\"\n [ModelName]=\"IsReviewing ? null : (ModelName$ | async)\"\n [DevMode]=\"Ui.showDevLinks\"\n [AllowDevLinks]=\"AllowDevLinks\"\n [SessionID]=\"IsReviewing ? ReviewSessionID : SessionID\"\n [ReviewMode]=\"IsReviewing\"\n [ReviewStartedAt]=\"ReviewStartedAt\"\n [ReviewClosedAt]=\"ReviewClosedAt\"\n [ReviewCloseReason]=\"ReviewCloseReason\"\n [ShowGear]=\"!IsReviewing && Ui.showGear\"\n [ShowDensityPicker]=\"Ui.showDensityPicker\"\n [ShowMinimize]=\"!IsReviewing && Ui.showMinimize\"\n [ShowStartLive]=\"AllowStartLive\"\n [Density]=\"Disclosure.Milestones.Density\"\n (OpenSessionRequested)=\"OnOpenSessionRequested($event)\"\n (DevModeToggled)=\"OnDevModeToggled($event)\"\n (DensityChanged)=\"OnDensityChanged($event)\"\n (MinimizeRequested)=\"OnMinimize()\"\n (StartLiveRequested)=\"OnStartLive()\"\n (CloseRequested)=\"OnReviewClose()\">\n </mj-realtime-agent-banner>\n\n <!-- No microphone works: the call lost its microphone, so the agent hears nothing until the user picks one in the\n device menu. It stays for as long as that lasts, with no dismiss button, and goes once a microphone is on.\n Never in review. -->\n @if (MicrophoneNotice; as message) {\n <mj-alert class=\"call-microphone-notice\"\n Variant=\"warning\"\n Size=\"sm\"\n Icon=\"fa-solid fa-microphone-slash\"\n [Message]=\"message\">\n </mj-alert>\n }\n\n <!-- Why the call shows no avatar its agent asked for: once per call, from the moment it connects, until the user\n dismisses it or it hides itself after 10 s. Never in review. -->\n @if (AvatarNotice) {\n <mj-alert class=\"call-avatar-notice\"\n Variant=\"info\"\n Size=\"sm\"\n Icon=\"fa-solid fa-video-slash\"\n [Message]=\"AvatarNoticeMessage\"\n Dismissible\n (Dismissed)=\"OnAvatarNoticeDismissed()\">\n </mj-alert>\n }\n\n <!-- Ephemeral narration with no active working card to anchor to \u2192 under the banner. It is conversation text, so it\n shows exactly while the thread does (Ui.showThread folds the host's text switch, the captions choice, console\n chrome and review) \u2014 never on the session's disclosure ratchet, which only goes up. -->\n @if (!IsReviewing && State.Narration && !State.ActiveCallId && Ui.showThread) {\n <div class=\"banner-live-note\" role=\"status\" aria-live=\"polite\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n {{ State.Narration }}\n </div>\n }\n\n <!-- The agent's place above the thread in the console: the agent's video (the Avatar) lies over it while it presents.\n Outside the scrolling body, so it stays in view as the conversation grows. -->\n @if (!ShowHero) {\n <div class=\"call-presenter\" [class.call-presenter--on]=\"PresenterOnStage\" #presenterSlot></div>\n }\n\n <!-- While the call connects, the connecting screen takes the body, except from a hero the agent's video presents in:\n a resume on a new connection keeps that hero, so the video keeps its place and its last frame. -->\n <div class=\"call-body\" [class.call-body--hero]=\"ShowHero && !ShowConnectingScreen(state)\">\n @if (ShowConnectingScreen(state)) {\n <div class=\"call-connecting\">\n <mj-loading text=\"Connecting to the live session\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (ShowHero) {\n <!-- PURE-AUDIO HERO (level 0): the orb IS the interface \u2014 it breathes with the\n audio inside expanding sound-wave rings; nothing to read, nothing to configure. -->\n <div class=\"hero\" [class.hero--presenting]=\"PresenterOnStage\" [attr.data-state]=\"state\" role=\"status\" aria-live=\"polite\">\n @if (!PresenterOnStage) {\n <mj-realtime-agent-orb class=\"hero__orb\" [State]=\"HeroOrbState(state)\"></mj-realtime-agent-orb>\n <div class=\"hero__eq\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n }\n <!-- The agent's place in the hero: the agent's video (the Avatar) lies over it while it presents. With\n captions on, the newest caption runs over the video's lower third, as subtitles do. Hidden from\n assistive technology: the conversation, a click away, is the accessible transcript, and this live\n region would read every word as it arrives. -->\n <div class=\"hero__presenter\" [class.hero__presenter--on]=\"PresenterOnStage\" #presenterSlot>\n @if (ShowAvatarCaptions && AvatarCaption; as caption) {\n <p class=\"hero__caption\" aria-hidden=\"true\">\n <span class=\"hero__caption-window\">\n <span class=\"hero__caption-lines\">\n @if (caption.Speaker) {\n <span class=\"hero__caption-speaker\">{{ caption.Speaker }}:</span>\n }\n {{ caption.Text }}\n </span>\n </span>\n </p>\n }\n </div>\n @if (!PresenterOnStage) {\n <div class=\"hero__name\">{{ AgentName }}</div>\n <div class=\"hero__sub\">{{ HeroStateLabel(state) }}</div>\n } @else {\n <!-- The agent's tile names it and says what it is doing; this live region still says it for screen readers. -->\n <span class=\"hero__sr\">{{ HeroStateLabel(state) }}</span>\n }\n @if (Ui.allowTextReveal && (PresenterOnStage || !Ui.showCaptionsControl)) {\n <button type=\"button\" class=\"hero__reveal\" (click)=\"OnTextReveal()\"\n title=\"Show the conversation as text\">\n <i class=\"fa-solid fa-closed-captioning\" aria-hidden=\"true\"></i>\n Show the conversation\n </button>\n }\n </div>\n } @else if (Ui.showThread) {\n <mj-realtime-session-thread\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [UserName]=\"CurrentUser?.Name || 'You'\"\n [ShowCaptions]=\"IsReviewing ? true : ShowCaptions\"\n [DevMode]=\"Ui.showDevLinks\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (OpenArtifactRequested)=\"OnOpenArtifactRequested($event)\"\n (CancelRequested)=\"OnCancelDelegation($event)\">\n </mj-realtime-session-thread>\n }\n </div>\n\n <!-- The call's controls along the foot of the column. The stage starts its picture-in-picture boxes clear of\n them, so a camera or a shared screen never opens over End call (#5408). Empty, so with no size, in review. -->\n <div class=\"call-controls\" #callControls>\n @if (!IsReviewing) {\n <!-- \"Agent can see\": what the agent is looking at, with a switch per source. Shown while the user\n captures camera/screen (or when [ShowPerceptionChip]=\"true\"). -->\n <mj-realtime-perception-chip [Sources]=\"VisibleVideoSources\"\n (SourceToggled)=\"OnVideoSourceToggled($event)\"\n (SourcePicked)=\"OnVideoSourcePicked($event)\">\n </mj-realtime-perception-chip>\n\n <!-- Active channels (earned with the console at level 2+, host-gated via [ShowChannels]).\n The strip component self-hides when there are no active channels. -->\n @if (ChannelStripVisible) {\n <mj-realtime-channel-strip></mj-realtime-channel-strip>\n }\n\n <!-- The BOTTOM DOCK: phone-call strip \u21C4 fused minis+composer \u2014 the user's own\n two-way toggle (Type opens, hide closes), never level-forced. -->\n <mj-realtime-composer\n [Open]=\"ComposerOpen\"\n [Compact]=\"Ui.chrome === 'orb' || Ui.compact\"\n (OpenChanged)=\"OnComposerOpenChanged($event)\"\n [AllowTyping]=\"Ui.allowTyping\"\n [ShowEnd]=\"Ui.showEnd\"\n [ShowCaptionsControl]=\"Ui.showCaptionsControl\"\n [CaptionsOn]=\"ShowCaptions\"\n [IsMuted]=\"Muted\"\n [ShowCamera]=\"CaptureOffers.Camera\"\n [ShowShare]=\"CaptureOffers.Screen\"\n [CameraOn]=\"CameraOn\"\n [Sharing]=\"Sharing\"\n [SharePanels]=\"SharePanels\"\n [Devices]=\"CallDevices\"\n [SelectedMicrophoneID]=\"SelectedMicrophoneID\"\n [SelectedCameraID]=\"SelectedCameraID\"\n (CameraToggled)=\"OnCameraToggled($event)\"\n (ShareRequested)=\"OnShareRequested($event)\"\n (StopShareRequested)=\"OnStopShareRequested()\"\n (DeviceSelected)=\"OnDeviceSelected($event)\"\n [ShowDetails]=\"ShowDetailsControl\"\n [DetailsOn]=\"DetailsPeek\"\n (CaptionsToggled)=\"OnCaptionsToggled($event)\"\n (DetailsToggled)=\"OnDetailsToggled()\"\n (MuteChanged)=\"OnComposerMuteChanged($event)\"\n (EndRequested)=\"OnEndCall()\">\n </mj-realtime-composer>\n }\n </div>\n </div>\n\n <!-- RIGHT: TABBED SURFACE PANEL \u2014 disclosure-gated: it EXISTS once earned (level 2+,\n which the first delegation forces), peeked on demand via the strip's Details\n control, or always in review. Slides in from the right when it arrives \u2014 the\n panel's arrival IS the progress cue. -->\n @if (ShowPanelArea) {\n <!-- Resize handle (hidden while the panel is collapsed or a channel holds focus). -->\n @if (!PanelResizeDisabled) {\n <div class=\"call-resizer\"\n [class.call-resizer--active]=\"IsPanelResizing\"\n role=\"separator\" aria-orientation=\"vertical\"\n aria-label=\"Resize the surface panel (double-click to reset)\"\n title=\"Drag to resize \u2022 double-click to reset\"\n (mousedown)=\"OnPanelResizeStart($event)\"\n (dblclick)=\"OnPanelResizeReset()\"></div>\n }\n\n <div class=\"call-panel\" [style.width.px]=\"PanelAreaSize\">\n <mj-realtime-surface-tabs\n [State]=\"State\"\n [DevMode]=\"Ui.showDevLinks\"\n [CurrentUser]=\"CurrentUser\"\n [EnvironmentID]=\"EnvironmentID\"\n [ShowActivityTab]=\"ShowActivityTab\"\n [ExtraArtifacts]=\"ReviewArtifacts\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n [Collapsed]=\"PanelCollapsed\"\n (CollapsedChange)=\"OnPanelCollapsedChange($event)\"\n (WideChanged)=\"OnPanelWideChanged($event)\"\n (ActiveChannelChange)=\"OnActiveChannelChange($event)\"\n (ChannelSlotChange)=\"OnChannelSlotChange($event)\"\n [SurfacePlacements]=\"SurfaceStage.Placements\"\n [AllowedPlacements]=\"SurfaceStage.AllowedPlacements\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-realtime-surface-tabs>\n </div>\n }\n\n <!-- THE STAGE: every channel surface lives here, in one layer over the overlay. A surface\n is created the first time it is seen, lies over its tab's slot while that tab is\n active, fills the overlay while its channel holds focus (or, for the agent's video,\n lies in the agent's place in the call), and otherwise waits out of sight \u2014 so\n collapsing the panel, switching tabs or entering focus never reloads it.\n The pane tells each channel when its surface goes out of sight, so it can pause.\n A channel that opts in (SurfaceShareable, such as the whiteboard) marks its frame as a panel the Share menu\n offers under \"This panel\" while it is on screen. A picture-in-picture box the user has not moved starts\n clear of the call's controls. -->\n <mj-media-stage class=\"call-stage\"\n [Surfaces]=\"SurfaceStage.Surfaces\"\n [TabSlot]=\"StageSlot?.Element ?? null\"\n [StageSlot]=\"StageSurfaceSlot\"\n [ActiveTabKey]=\"StageSlot?.Key ?? null\"\n [PipRects]=\"PipRects\"\n [PipKeepClear]=\"callControls\"\n (PipRectChange)=\"OnPipRectChange($event)\">\n <ng-template mjMediaStageSurface let-key let-visible=\"Visible\" let-placement=\"Placement\">\n @if (SurfaceStage.PluginFor(key); as plugin) {\n <div class=\"stage-frame\" role=\"region\" [attr.aria-label]=\"plugin.TabTitle\"\n [mjSharePanel]=\"plugin.SurfaceShareable ? plugin.TabTitle : null\" [mjSharePanelIcon]=\"plugin.TabIcon\">\n <mj-realtime-channel-pane [Plugin]=\"plugin\" [Visible]=\"visible\" [Placement]=\"placement\"></mj-realtime-channel-pane>\n <!-- A surface presenting the agent has no focus pill, so its \"Move to\u2026\" menu rides on it. -->\n @if (placement === 'stage' && plugin.ShowsAgentVideo) {\n <mj-media-move-menu\n class=\"stage-presenter-move\"\n [Key]=\"key\"\n [Title]=\"plugin.TabTitle\"\n Placement=\"stage\"\n [Allowed]=\"SurfaceStage.AllowedFor(key)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n </div>\n }\n </ng-template>\n <!-- A picture-in-picture box's bar carries the same \"Move to\u2026\" menu. -->\n <ng-template mjMediaStagePipActions let-key>\n @if (SurfaceStage.PluginFor(key); as plugin) {\n <mj-media-move-menu\n [Key]=\"key\"\n [Title]=\"plugin.TabTitle\"\n Placement=\"pip\"\n [Allowed]=\"SurfaceStage.AllowedFor(key)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n </ng-template>\n </mj-media-stage>\n\n <!-- FOCUS floating call pill: the main column is hidden, so the live call's\n identity + state + mute / show-thread / end controls ride over the surface. -->\n @if (!IsReviewing && ChannelFocusMode) {\n <div class=\"board-focus-pill\" role=\"status\" aria-label=\"Live call (board focus)\">\n <span class=\"board-focus-pill__orb\" [attr.data-state]=\"state\" aria-hidden=\"true\"></span>\n <span class=\"board-focus-pill__text\">\n <span class=\"board-focus-pill__name\">Voice for {{ AgentName }}</span>\n @if (FocusStateLabel(state); as label) {\n <span class=\"board-focus-pill__state\">{{ label }}</span>\n }\n </span>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n [class.board-focus-pill__btn--active]=\"FocusPillMuted\"\n (click)=\"OnFocusPillMute()\"\n [title]=\"FocusPillMuted ? 'Unmute' : 'Mute'\"\n [attr.aria-label]=\"FocusPillMuted ? 'Unmute microphone' : 'Mute microphone'\">\n <i class=\"fa-solid\" [class.fa-microphone]=\"!FocusPillMuted\" [class.fa-microphone-slash]=\"FocusPillMuted\" aria-hidden=\"true\"></i>\n </button>\n <!-- The focus-mode exit, always offered: it is the only way off a focused surface. With text\n disallowed the column comes back as the orb hero, so it is labelled as a return to the call. -->\n <button type=\"button\" class=\"board-focus-pill__btn\"\n (click)=\"OnFocusPillExit()\"\n [title]=\"Ui.allowTextReveal ? 'Show thread' : 'Back to the call'\"\n [attr.aria-label]=\"Ui.allowTextReveal ? 'Show the conversation thread' : 'Leave focus mode and return to the call'\">\n <i class=\"fa-solid fa-table-columns\" aria-hidden=\"true\"></i>\n </button>\n @if (StagePlugin; as stagePlugin) {\n <mj-media-move-menu\n [Key]=\"stagePlugin.ChannelName\"\n [Title]=\"stagePlugin.TabTitle\"\n Placement=\"stage\"\n [Allowed]=\"SurfaceStage.AllowedFor(stagePlugin.ChannelName)\"\n (MoveRequested)=\"OnMoveRequested($event)\"\n (ResetLayoutRequested)=\"OnResetLayout()\">\n </mj-media-move-menu>\n }\n @if (Ui.showEnd) {\n <button type=\"button\" class=\"board-focus-pill__btn board-focus-pill__btn--end\"\n (click)=\"OnFocusPillEnd()\"\n title=\"End call\" aria-label=\"End the call\">\n <i class=\"fa-solid fa-phone-slash\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n }\n\n <!-- THE CAMERA CHECK: the first time the user turns the camera on in a call, they see it, and can pick another\n camera, before the agent does. Until they choose, the camera's own box says it is waiting. -->\n @if (CameraCheck; as camera) {\n <mj-realtime-camera-check-card\n [Camera]=\"camera\"\n [AgentName]=\"AgentName\"\n [Switch]=\"SwitchCheckedCamera\"\n (Confirmed)=\"OnCameraCheckConfirmed()\"\n (Declined)=\"OnCameraCheckDeclined()\">\n </mj-realtime-camera-check-card>\n }\n\n </div>\n}\n\n<!-- SESSION REVIEW whiteboard pane body: the reviewed session's saved board, rehydrated\n locally (WhiteboardState.FromJSON) and rendered READ-ONLY (pan/zoom only). Registered\n as a template-based channel tab ONLY when a parseable Whiteboard state exists. -->\n<ng-template #reviewBoardTpl>\n @if (ReviewWhiteboard; as board) {\n <!-- The board host positions itself absolutely \u2014 give it a filling, positioned frame. -->\n <div class=\"review-board-host\">\n <mj-realtime-whiteboard\n [State]=\"board\"\n [AgentName]=\"AgentName\"\n [ReadOnly]=\"true\">\n </mj-realtime-whiteboard>\n </div>\n }\n</ng-template>\n\n<!-- SESSION REVIEW recording pane body: the reviewed session's recorded audio synced to its\n transcript, replayed via the shared storage media player (click-to-seek transcript + playhead\n highlight). Registered as a template-based channel tab ONLY when the reviewed session carries a\n RecordingFileID; the player resolves the authenticated audio itself from that file id. -->\n<ng-template #recordingTpl>\n <div class=\"review-recording-host\">\n <mj-storage-media-player\n [FileID]=\"ReviewRecordingFileID\"\n [Transcript]=\"ReviewCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n </div>\n</ng-template>\n", styles: [":host {\n display: contents;\n}\n\n/* Fills the conversation panel IN PLACE: the chat area host provides the positioned\n context (position:relative on .chat-area) and this covers it edge to edge \u2014 header,\n thread and composer included. Opaque-ish page surface with a slight blur so the\n conversation underneath reads as \"paused\", not as an app-wide modal. */\n.call-overlay {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n/* Minimized: hidden (NOT destroyed) so the shell's session/view state survives while\n the host shows its floating \"on call\" pill. The audio session is untouched. */\n.call-overlay--hidden {\n display: none;\n}\n\n/* MAIN column: banner (top) \u2192 scrolling thread \u2192 channel strip \u2192 composer \u2192 controls.\n The 420px floor keeps the call column readable while the panel is dragged wide\n (the drag clamp's 70% cap is the other guard). */\n.call-main {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* The call's controls at the foot of the column: the \"Agent can see\" chip, the channel strip and the composer. The\n stage starts its picture-in-picture boxes above this box, so it is never squeezed below its content's height. */\n.call-controls {\n flex: none;\n}\n\n.call-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body::-webkit-scrollbar {\n width: 6px;\n}\n.call-body::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n/* ---------- PURE-AUDIO HERO (disclosure level 0) ----------\n The orb IS the interface: it breathes with the audio inside expanding sound-wave\n rings, with a small voice equalizer underneath. Nothing to read, nothing to\n configure \u2014 text, panels and the composer are revealed later (or peeked on demand). */\n.call-body--hero {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n/* The orb (mj-realtime-agent-orb) at hero scale. */\n.hero__orb {\n margin-bottom: 4px;\n}\n/* Voice equalizer: lively while the agent speaks, near-still otherwise. */\n.hero__eq {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq span {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'] .hero__eq span {\n animation: hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq span:nth-child(1) { animation-delay: 0s; }\n.hero__eq span:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq span:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq span:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq span:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq span:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq span:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq span:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq span:nth-child(9) { animation-delay: 0.03s; }\n@keyframes hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n/* ---------- the agent's place while its video presents (the Avatar) ----------\n The stage lays the agent's video over these boxes; each has a size only while the video presents. Portrait, as\n avatar video is (Google's samples are 704x1280). */\n.hero--presenting {\n height: 100%;\n min-height: 0;\n}\n.hero__presenter,\n.call-presenter {\n display: none;\n}\n.hero__presenter--on {\n display: block;\n position: relative;\n container-type: size;\n flex: 1 1 auto;\n min-height: 160px;\n max-height: 520px;\n aspect-ratio: 3 / 4;\n margin-bottom: 8px;\n}\n/* Captions over the agent's video, as subtitles: the newest two lines, over the lower third and clear of the tile's\n footer. Above the stage (z-index 1), where the video is. Light on a dark band in both themes, like the tile's chips. */\n.hero__caption {\n position: absolute;\n left: 6%;\n right: 6%;\n bottom: 16%;\n z-index: 2;\n margin: 0;\n padding: 6px 10px;\n border-radius: var(--mj-radius-md);\n background: rgba(0, 0, 0, 0.6);\n color: rgba(255, 255, 255, 0.95);\n font-size: 0.95rem;\n line-height: 1.4;\n text-align: center;\n}\n/* Two lines, the newest: older ones leave through the top. The window clips, not the padded band, so nothing of an\n older line shows in the padding. */\n.hero__caption-window {\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n max-height: 2.8em;\n overflow: hidden;\n}\n.hero__caption-speaker {\n font-weight: 600;\n}\n/* A small tile (a short window): one smaller line, so the video still shows. */\n@container (max-height: 240px) {\n .hero__caption {\n bottom: 22%;\n padding: 3px 6px;\n font-size: 0.75rem;\n }\n .hero__caption-window {\n max-height: 1.4em;\n }\n}\n/* In the console it sits above the scrolling thread, so it stays in view as the conversation grows. Wide enough for\n the tile's \"AI-generated video\" label on one line. */\n.call-presenter--on {\n display: block;\n flex: none;\n align-self: center;\n height: 200px;\n aspect-ratio: 4 / 5;\n margin-top: 12px;\n}\n.hero__name {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n/* The hero's state for screen readers only, while the agent's tile shows it. */\n.hero__sr {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n/* ---------- AUDIO-REACTIVE MODE (data-audio-live, set by the rAF meter loop) ----------\n With real audio metering the visuals stop acting and start REACTING: the EQ renders the\n true spectrum (--eq-1..9), and [data-voice-dir] colors it by who is speaking (agent =\n brand, user = green). The orb (mj-realtime-agent-orb) reads the same attributes and the\n smoothed output envelope (--voice-out) itself. Without metering (driver attached no\n meters) the attribute stays 'false' and the turn-state keyframe animations stay in charge. */\n/* True-spectrum EQ: per-bar heights from the meter's bins. */\n.call-overlay[data-audio-live='true'] .hero__eq span {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__eq span {\n background: var(--mj-status-success);\n}\n\n/* The quiet first reveal: show the conversation as text (raises disclosure to level 1). */\n.hero__reveal {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal i {\n font-size: 11px;\n}\n\n/* Ephemeral narration shown under the banner when no working card anchors it */\n.banner-live-note {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: overlay-note-in 220ms ease;\n}\n.banner-live-note i {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* The call's notices under the banner (no microphone works; no avatar): mj-alert brings its own tint, border and text\n colours; this only places them. */\n.call-microphone-notice,\n.call-avatar-notice {\n flex-shrink: 0;\n margin: var(--mj-space-2) var(--mj-space-5) 0;\n}\n\n/* ---------- FOCUS-BOARD mode (whiteboard \"Focus board\" toggle) ----------\n The main call column collapses away entirely and the focused channel's surface fills\n the stage; a compact floating call pill keeps the live call reachable. */\n.call-overlay.board-focus .call-main {\n display: none;\n}\n\n/* ---------- the stage (every channel surface, in one layer) ----------\n mj-media-stage covers the overlay and places each surface itself. Above the panel it\n lays surfaces over, and above the call's controls, so its picture-in-picture boxes\n start clear of them (.call-controls); below the onboarding card (5), the menus of the\n dock and the chip (5) and the focus pill (60). */\n.call-stage {\n z-index: 1;\n}\n/* A surface's frame: fills the box the stage gives it. The surface component is created\n beside the pane host inside it, and sizes itself to it (height: 100%). */\n.stage-frame {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n/* The \"Move to\u2026\" menu on a surface that presents the agent, backed so it reads over video. Bottom right, in the\n tile's footer, which holds nothing there for the agent, so it never covers the tile's labels. */\n.stage-presenter-move {\n position: absolute;\n bottom: 4px;\n right: 6px;\n z-index: 1;\n border-radius: var(--mj-radius-full);\n background: color-mix(in srgb, var(--mj-bg-surface) 85%, transparent);\n}\n\n/* Floating call pill: orb + state + mute / show-thread / end (per the whiteboard mockup). */\n.board-focus-pill {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'],\n.board-focus-pill__orb[data-state='listening'] {\n animation: board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n/* ---------- Session review mode ---------- */\n/* Subtle archival tint so a glance says \"this is history, not a live call\". */\n.call-overlay--review {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n/* The review whiteboard pane body: the board component positions itself absolutely\n (inset: 0), so its template-based pane needs a filling, positioned frame. */\n.review-board-host {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* The review recording pane body: the evidence player fills its host (:host height:100%),\n so its template-based pane needs a filling, constrained frame. */\n.review-recording-host {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n.review-recording-host mj-realtime-evidence-playback {\n flex: 1;\n min-height: 0;\n}\n\n/* ---------- flex layout (main column | resize handle | surface panel) ---------- */\n/* Token-styled resize handle: invisible until hover, brand-tinted while interacting.\n 7px hit target, col-resize cursor; removed (not rendered) while collapsed / focus. */\n.call-resizer {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer:hover,\n.call-resizer--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n/* The surface panel: a plain fixed-width flex item (inline width set by the overlay;\n collapsed = the slim strip width). flex-shrink lets it yield to the call column's\n min-width on tight overlays; overflow hidden keeps the flex height chain intact so\n the panel's content scrolls internally. */\n.call-panel {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n /* Disclosure-gated: the panel is CREATED when earned/peeked \u2014 its arrival slides in\n from the right so the reveal doubles as the progress cue. */\n animation: panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel > * {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n"] }]
|
|
2617
2689
|
}], () => [], { Hidden: [{
|
|
2618
2690
|
type: Input
|
|
2619
2691
|
}], AgentName: [{
|
|
@@ -2638,6 +2710,8 @@ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
|
|
|
2638
2710
|
type: Input
|
|
2639
2711
|
}], AllowTextReveal: [{
|
|
2640
2712
|
type: Input
|
|
2713
|
+
}], AllowTyping: [{
|
|
2714
|
+
type: Input
|
|
2641
2715
|
}], ShowCaptionsControl: [{
|
|
2642
2716
|
type: Input
|
|
2643
2717
|
}], ShowDensityPicker: [{
|