@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.
Files changed (131) hide show
  1. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  2. package/dist/lib/components/collection/collection-view.component.js +1 -0
  3. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  4. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
  5. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  6. package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
  7. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  8. package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
  9. package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
  10. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
  11. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
  12. package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
  13. package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
  14. package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
  15. package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
  16. package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
  17. package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
  18. package/dist/lib/components/message/message-input.component.d.ts +80 -53
  19. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  20. package/dist/lib/components/message/message-input.component.js +381 -277
  21. package/dist/lib/components/message/message-input.component.js.map +1 -1
  22. package/dist/lib/components/message/message-item.component.d.ts +18 -1
  23. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  24. package/dist/lib/components/message/message-item.component.js +548 -426
  25. package/dist/lib/components/message/message-item.component.js.map +1 -1
  26. package/dist/lib/components/message/message-list.component.d.ts +8 -1
  27. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  28. package/dist/lib/components/message/message-list.component.js +40 -2
  29. package/dist/lib/components/message/message-list.component.js.map +1 -1
  30. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
  31. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
  32. package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
  33. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
  34. package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
  35. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
  36. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
  37. package/dist/lib/components/realtime/media/media-channel.js +7 -2
  38. package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
  39. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
  40. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
  41. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
  42. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
  43. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
  44. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  45. package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
  46. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  47. package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
  48. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  49. package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
  50. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  51. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
  52. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  53. package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
  54. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  55. package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
  56. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
  57. package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
  58. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
  59. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  60. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
  61. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  62. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
  63. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
  64. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
  65. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
  66. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
  67. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
  68. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
  69. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
  70. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
  71. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
  72. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
  73. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
  74. package/dist/lib/conversations.module.d.ts +2 -1
  75. package/dist/lib/conversations.module.d.ts.map +1 -1
  76. package/dist/lib/conversations.module.js +9 -4
  77. package/dist/lib/conversations.module.js.map +1 -1
  78. package/dist/lib/events/chat-events.d.ts +5 -4
  79. package/dist/lib/events/chat-events.d.ts.map +1 -1
  80. package/dist/lib/events/chat-events.js +2 -2
  81. package/dist/lib/events/chat-events.js.map +1 -1
  82. package/dist/lib/models/agent-turn.model.d.ts +11 -7
  83. package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
  84. package/dist/lib/models/agent-turn.model.js.map +1 -1
  85. package/dist/lib/services/active-tasks.service.d.ts +5 -0
  86. package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
  87. package/dist/lib/services/active-tasks.service.js +16 -0
  88. package/dist/lib/services/active-tasks.service.js.map +1 -1
  89. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  90. package/dist/lib/services/agent-state.service.js +7 -1
  91. package/dist/lib/services/agent-state.service.js.map +1 -1
  92. package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
  93. package/dist/lib/services/artifact-permission.service.js +2 -0
  94. package/dist/lib/services/artifact-permission.service.js.map +1 -1
  95. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  96. package/dist/lib/services/artifact-state.service.js +1 -0
  97. package/dist/lib/services/artifact-state.service.js.map +1 -1
  98. package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
  99. package/dist/lib/services/collection-permission.service.js +1 -0
  100. package/dist/lib/services/collection-permission.service.js.map +1 -1
  101. package/dist/lib/services/conversation-agent.service.d.ts +1 -4
  102. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  103. package/dist/lib/services/conversation-agent.service.js +7 -45
  104. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  105. package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
  106. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  107. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  108. package/dist/lib/services/search.service.d.ts.map +1 -1
  109. package/dist/lib/services/search.service.js +1 -0
  110. package/dist/lib/services/search.service.js.map +1 -1
  111. package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
  112. package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
  113. package/dist/lib/utils/agent-handoff-map.js +270 -0
  114. package/dist/lib/utils/agent-handoff-map.js.map +1 -0
  115. package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
  116. package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
  117. package/dist/lib/utils/agent-turn-routing.js +147 -6
  118. package/dist/lib/utils/agent-turn-routing.js.map +1 -1
  119. package/dist/lib/utils/decision-routing.d.ts +17 -9
  120. package/dist/lib/utils/decision-routing.d.ts.map +1 -1
  121. package/dist/lib/utils/decision-routing.js +23 -14
  122. package/dist/lib/utils/decision-routing.js.map +1 -1
  123. package/dist/lib/utils/thread-replies.d.ts +34 -0
  124. package/dist/lib/utils/thread-replies.d.ts.map +1 -0
  125. package/dist/lib/utils/thread-replies.js +49 -0
  126. package/dist/lib/utils/thread-replies.js.map +1 -0
  127. package/dist/public-api.d.ts +5 -0
  128. package/dist/public-api.d.ts.map +1 -1
  129. package/dist/public-api.js +5 -0
  130. package/dist/public-api.js.map +1 -1
  131. package/package.json +36 -36
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-media-surface.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts","../../../../../src/lib/components/realtime/media/realtime-media-surface.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit,\n ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { Subscription } from 'rxjs';\nimport { LogError } from '@memberjunction/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJMediaPlayerComponent, MJStorageMediaPlayerComponent, MediaTrack } from '@memberjunction/ng-media-player';\nimport { MediaChannelState, MediaItem } from './media-channel-state';\n\n/** Zoom levels cycled by the image zoom control (×). */\nconst IMAGE_ZOOM_LEVELS = [1, 1.5, 2, 3] as const;\n\n/** Shape of the `CreateMediaAccessToken` server mutation result (one file). */\ninterface CreateMediaAccessTokenResult {\n Success: boolean;\n Url?: string | null;\n ErrorMessage?: string | null;\n}\n\n/**\n * The exact GraphQL mutation used to resolve a SECURE streaming URL for an `MJ: Files` id — mints a\n * short-lived signed token (after a server-side per-user permission check) and returns the\n * authenticated `/media/<fileId>?token=` URL. Image / PDF / web (iframe) panes render this URL; the\n * audio/video panes hand the file id straight to `mj-storage-media-player` (which mints its own).\n */\nconst CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fileId: String!) {\n CreateMediaAccessToken(fileId: $fileId) {\n Success\n Url\n ErrorMessage\n }\n}`;\n\n/** The per-item resolution status for FileID-backed image/pdf/web panes (audio/video resolve in the player). */\nexport type FileResolveState =\n | { Status: 'loading' }\n | { Status: 'ready'; Url: string }\n | { Status: 'error'; Message: string };\n\n/** The render route chosen for a {@link MediaItem} (pure function of its source + type). */\nexport type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';\n\n/** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */\nexport function IsPlayerMediaType(item: MediaItem): boolean {\n return item.Type === 'audio' || item.Type === 'video';\n}\n\n/**\n * Decides which render path an item uses based ONLY on its source + type — the single source of truth\n * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):\n * - audio/video + FileID → `'storage-player'` (`mj-storage-media-player`, secure streaming)\n * - audio/video + Url → `'url-player'` (`mj-media-player`, public URL)\n * - image/pdf/web → `'iframe-or-img'` (img/iframe; FileID resolves a streaming URL first)\n * - no usable source → `'none'`\n */\nexport function RouteForMediaItem(item: MediaItem): MediaRenderRoute {\n if (IsPlayerMediaType(item)) {\n if (item.FileID) {\n return 'storage-player';\n }\n return item.Url ? 'url-player' : 'none';\n }\n if (item.Url || item.FileID) {\n return 'iframe-or-img';\n }\n return 'none';\n}\n\n/**\n * LIVE MEDIA surface (`mj-realtime-media-surface`) — the Media channel tab's pane, rendered with a\n * TABS layout: a horizontal tab bar (one tab per {@link MediaItem}, type icon + name) over a single\n * media pane that swaps as you switch tabs. Pure PERCEPTION of the channel's {@link MediaChannelState}:\n * the agent's `Media_*` tools mutate the state through the channel plugin and this surface re-renders.\n *\n * SOURCE-AWARE rendering. Each item is backed by EITHER an external/public {@link MediaItem.Url} or an\n * MemberJunction {@link MediaItem.FileID} (an `MJ: Files` id, streamed securely + permission-gated):\n * - **audio/video + FileID** → `<mj-storage-media-player [FileID] [Provider]>` (real transport/waveform,\n * streams over HTTP Range under the session's authenticated provider).\n * - **audio/video + Url** → `<mj-media-player [Tracks]>` (the same real player, fed the public URL).\n * - **image/pdf/web + FileID** → a streaming URL is resolved via `CreateMediaAccessToken` (through the\n * channel's provider) and rendered in the existing img/iframe path; graceful no-access state on failure.\n * - **image/pdf/web + Url** → the public URL, rendered unchanged.\n *\n * Multi-provider safe: the channel plugin threads the live session's `IMetadataProvider` in via the\n * `Provider` input ({@link BaseAngularComponent}), and both the storage player and the token mutation\n * use it ({@link ProviderToUse}) rather than the global default.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-media-surface',\n imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-media-surface.component.html',\n styleUrls: ['./realtime-media-surface.component.css'],\n})\nexport class RealtimeMediaSurfaceComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n /** The shared media state engine (set by the channel's BindSurface before first CD). */\n @Input()\n set State(value: MediaChannelState | null) {\n if (value === this._state) {\n return;\n }\n this._state = value;\n this.resubscribe();\n }\n get State(): MediaChannelState | null {\n return this._state;\n }\n private _state: MediaChannelState | null = null;\n\n /** Display name of the agent fronting the session — used in the empty-state copy. */\n @Input() AgentName = 'The agent';\n\n /** The current image zoom multiplier (cycled by the zoom button; image panes only). */\n public ImageZoom = 1;\n\n /** Sanitized URL cache for `web` / `pdf` iframes (Angular blocks raw resource URLs in `[src]`). */\n private safeUrlCache = new Map<string, SafeResourceUrl>();\n\n /** The id of the item whose play we last actioned, so a re-render doesn't replay it. */\n private lastPlayedStamp = new Map<string, number>();\n\n /**\n * Resolution status for FileID-backed image/pdf/web items, keyed by item id. Audio/video FileID items\n * resolve inside `mj-storage-media-player` and never enter this map.\n */\n private fileResolveByItem = new Map<string, FileResolveState>();\n\n @ViewChild('mediaPane') private mediaPane?: ElementRef<HTMLElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly sanitizer = inject(DomSanitizer);\n private stateSub: Subscription | null = null;\n\n ngOnInit(): void {\n this.resubscribe();\n }\n\n ngOnDestroy(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n }\n\n /** The items currently on the surface (empty when no state is bound). */\n public get Items(): MediaItem[] {\n return this._state?.Items ?? [];\n }\n\n /** The active item, or `null` when the surface is empty. */\n public get ActiveItem(): MediaItem | null {\n return this._state?.ActiveItem ?? null;\n }\n\n /** Whether the active id matches `item` (drives the active tab + pane). */\n public IsActive(item: MediaItem): boolean {\n return this._state?.ActiveItemId === item.Id;\n }\n\n // ---------------------------------------------------------------------------\n // Source routing — which render path an item uses\n // ---------------------------------------------------------------------------\n\n /** Whether `item` is rendered by the real media player (audio/video). */\n public IsPlayerItem(item: MediaItem): boolean {\n return IsPlayerMediaType(item);\n }\n\n /** Whether `item` is an audio/video item backed by an MJStorage FileID → `mj-storage-media-player`. */\n public UsesStoragePlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'storage-player';\n }\n\n /** Whether `item` is an audio/video item backed by a public URL → `mj-media-player`. */\n public UsesUrlPlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'url-player';\n }\n\n /** The single-track payload for the URL-backed `mj-media-player` (audio/video). */\n public TracksFor(item: MediaItem): MediaTrack[] {\n if (!item.Url) {\n return [];\n }\n return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url }];\n }\n\n /**\n * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a\n * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access).\n */\n public RenderUrl(item: MediaItem): string | null {\n if (item.Url) {\n return item.Url;\n }\n if (item.FileID) {\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'ready' ? state.Url : null;\n }\n return null;\n }\n\n /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */\n public IsResolving(item: MediaItem): boolean {\n return !item.Url && !!item.FileID && this.fileResolveByItem.get(item.Id)?.Status === 'loading';\n }\n\n /** The no-access / load-failure message for a FileID-backed image/pdf/web item, or `null`. */\n public ResolveError(item: MediaItem): string | null {\n if (item.Url || !item.FileID) {\n return null;\n }\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'error' ? state.Message : null;\n }\n\n /** Switches the active tab to `item`. */\n public SelectTab(item: MediaItem): void {\n this._state?.SetActive(item.Id);\n }\n\n /** Keyboard handler for the tab buttons — Enter/Space activate, arrows move between tabs. */\n public OnTabKeydown(event: KeyboardEvent, index: number): void {\n const items = this.Items;\n if (items.length === 0) {\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const delta = event.key === 'ArrowRight' ? 1 : -1;\n const next = (index + delta + items.length) % items.length;\n this._state?.SetActive(items[next].Id);\n this.focusTabButton(next);\n }\n }\n\n /** Closes one media item (removes its tab + pane). */\n public CloseItem(event: Event, item: MediaItem): void {\n event.stopPropagation();\n this.fileResolveByItem.delete(item.Id);\n this._state?.RemoveItem(item.Id);\n }\n\n /** Whether the open-in-tab / download toolbar buttons are available for `item` (need a usable URL). */\n public CanOpenOrDownload(item: MediaItem): boolean {\n return !!this.RenderUrl(item);\n }\n\n /** Opens the active media in a new browser tab (its public or resolved streaming URL). */\n public OpenInNew(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (url) {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n }\n\n /** Triggers a browser download of the active media via a transient anchor (public or resolved URL). */\n public Download(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (!url) {\n return;\n }\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = item.DisplayName || '';\n anchor.target = '_blank';\n anchor.rel = 'noopener noreferrer';\n anchor.click();\n }\n\n /** Cycles the image zoom multiplier through {@link IMAGE_ZOOM_LEVELS}. */\n public CycleZoom(): void {\n const idx = IMAGE_ZOOM_LEVELS.indexOf(this.ImageZoom as (typeof IMAGE_ZOOM_LEVELS)[number]);\n this.ImageZoom = IMAGE_ZOOM_LEVELS[(idx + 1) % IMAGE_ZOOM_LEVELS.length];\n }\n\n /** The Font Awesome icon class for a media type's tab. */\n public IconFor(type: MediaItem['Type']): string {\n switch (type) {\n case 'image':\n return 'fa-solid fa-image';\n case 'video':\n return 'fa-solid fa-film';\n case 'audio':\n return 'fa-solid fa-music';\n case 'pdf':\n return 'fa-solid fa-file-pdf';\n case 'web':\n return 'fa-solid fa-globe';\n default:\n return 'fa-solid fa-file';\n }\n }\n\n /** A memoized sanitized resource URL for iframe-rendered media (web / pdf). */\n public SafeUrl(url: string): SafeResourceUrl {\n let cached = this.safeUrlCache.get(url);\n if (!cached) {\n cached = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n this.safeUrlCache.set(url, cached);\n }\n return cached;\n }\n\n /** Inline style for the fractional highlight rectangle (percentages of the pane box). */\n public HighlightStyle(item: MediaItem): Record<string, string> | null {\n const h = item.Highlight;\n if (!h) {\n return null;\n }\n const pct = (v: number): string => `${Math.max(0, Math.min(1, v)) * 100}%`;\n return { left: pct(h.X), top: pct(h.Y), width: pct(h.W), height: pct(h.H) };\n }\n\n /** Stable `@for` track for tab/pane diffing. */\n public TrackById(_index: number, item: MediaItem): string {\n return item.Id;\n }\n\n /** Re-subscribes the state's Changed$ stream (called on state input change + ngOnInit). */\n private resubscribe(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n if (!this._state) {\n this.cdr.markForCheck();\n return;\n }\n this.stateSub = this._state.Changed$.subscribe(() => {\n this.resolvePendingFileUrls();\n this.applyPendingPlay();\n this.cdr.markForCheck();\n });\n }\n\n /**\n * Mints streaming URLs for any FileID-backed image/pdf/web items not yet resolved (audio/video FileID\n * items resolve inside the storage player, so they're skipped). Idempotent — each item is resolved\n * once; a `loading`/`ready`/`error` entry guards against re-firing on every state change.\n */\n private resolvePendingFileUrls(): void {\n for (const item of this.Items) {\n if (item.Url || !item.FileID || this.IsPlayerItem(item)) {\n continue;\n }\n if (this.fileResolveByItem.has(item.Id)) {\n continue;\n }\n this.fileResolveByItem.set(item.Id, { Status: 'loading' });\n void this.resolveFileUrl(item.Id, item.FileID);\n }\n }\n\n /** Resolves one file id to a secure streaming URL via the channel's provider, then re-renders. */\n private async resolveFileUrl(itemId: string, fileId: string): Promise<void> {\n const result = await this.mintAccessToken(fileId);\n if (result && result.Success && result.Url) {\n this.fileResolveByItem.set(itemId, { Status: 'ready', Url: result.Url });\n } else {\n this.fileResolveByItem.set(itemId, {\n Status: 'error',\n Message: result?.ErrorMessage || \"This media couldn't be loaded, or you don't have access to it.\",\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Mints an authenticated streaming-URL token for one file id via the active provider. */\n private async mintAccessToken(fileId: string): Promise<CreateMediaAccessTokenResult | null> {\n try {\n const provider = this.ProviderToUse as unknown as GraphQLDataProvider;\n const data = await provider.ExecuteGQL(CREATE_MEDIA_ACCESS_TOKEN_MUTATION, { fileId });\n const payload = data?.CreateMediaAccessToken as CreateMediaAccessTokenResult | undefined;\n return payload ?? null;\n } catch (err) {\n LogError(`CreateMediaAccessToken failed for file '${fileId}': ${err instanceof Error ? err.message : String(err)}`);\n return { Success: false, ErrorMessage: 'Unable to load this media.' };\n }\n }\n\n /**\n * Honors a pending {@link MediaItem.PlayRequestedAt} stamp on the active video/audio element by\n * calling `.play()` once per new stamp (so a routine re-render never replays the media). Only applies\n * to the URL-backed `<mj-media-player>` / raw-element panes; the storage player owns its own autoplay.\n */\n private applyPendingPlay(): void {\n const item = this.ActiveItem;\n if (!item || item.PlayRequestedAt == null || (item.Type !== 'video' && item.Type !== 'audio')) {\n return;\n }\n if (this.lastPlayedStamp.get(item.Id) === item.PlayRequestedAt) {\n return;\n }\n this.lastPlayedStamp.set(item.Id, item.PlayRequestedAt);\n // The pane re-renders this CD pass; defer the play to the next microtask so the element exists.\n queueMicrotask(() => {\n const el = this.mediaPane?.nativeElement.querySelector('video, audio') as HTMLMediaElement | null;\n void el?.play().catch(() => undefined);\n });\n }\n\n /** Moves keyboard focus to the tab button at `index` (arrow-key navigation). */\n private focusTabButton(index: number): void {\n queueMicrotask(() => {\n const host = this.mediaPane?.nativeElement.parentElement;\n const buttons = host?.querySelectorAll<HTMLButtonElement>('.media-tab');\n buttons?.[index]?.focus();\n });\n }\n}\n","<div class=\"media-surface\">\n @if (Items.length === 0) {\n <div class=\"media-empty\">\n <i class=\"fa-solid fa-images media-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-empty__title\">No media yet</p>\n <p class=\"media-empty__hint\">{{ AgentName }} will share visuals here during the conversation.</p>\n </div>\n } @else {\n <!-- Tab bar: one tab per media item (the chosen \"Tabs\" layout — one pane, switch via tabs). -->\n <div class=\"media-tabs\" role=\"tablist\" aria-label=\"Shared media\">\n @for (item of Items; track TrackById($index, item); let i = $index) {\n <button\n type=\"button\"\n class=\"media-tab\"\n role=\"tab\"\n [class.media-tab--active]=\"IsActive(item)\"\n [attr.aria-selected]=\"IsActive(item)\"\n [attr.tabindex]=\"IsActive(item) ? 0 : -1\"\n [title]=\"item.DisplayName\"\n (click)=\"SelectTab(item)\"\n (keydown)=\"OnTabKeydown($event, i)\"\n >\n <i class=\"media-tab__icon\" [ngClass]=\"IconFor(item.Type)\" aria-hidden=\"true\"></i>\n <span class=\"media-tab__name\">{{ item.DisplayName }}</span>\n <span\n class=\"media-tab__close\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Close ' + item.DisplayName\"\n (click)=\"CloseItem($event, item)\"\n (keydown.enter)=\"CloseItem($event, item)\"\n (keydown.space)=\"CloseItem($event, item)\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </span>\n </button>\n }\n </div>\n\n @if (ActiveItem; as active) {\n <!-- Pane toolbar: per-type controls (open / download / zoom). -->\n <div class=\"media-toolbar\">\n <span class=\"media-toolbar__title\" [title]=\"active.DisplayName\">{{ active.DisplayName }}</span>\n <span class=\"media-toolbar__spacer\"></span>\n @if (active.Type === 'image') {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n [attr.aria-label]=\"'Zoom image (currently ' + ImageZoom + 'x)'\"\n title=\"Zoom\"\n (click)=\"CycleZoom()\"\n >\n <i class=\"fa-solid fa-magnifying-glass-plus\" aria-hidden=\"true\"></i>\n <span class=\"media-iconbtn__badge\">{{ ImageZoom }}×</span>\n </button>\n }\n @if (CanOpenOrDownload(active)) {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Download media\"\n title=\"Download\"\n (click)=\"Download(active)\"\n >\n <i class=\"fa-solid fa-download\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Open media in a new tab\"\n title=\"Open in new tab\"\n (click)=\"OpenInNew(active)\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- The single media pane (swaps as tabs switch). The highlight overlay sits on top. -->\n <div class=\"media-pane\" #mediaPane>\n <div class=\"media-stage\" [class.media-stage--scroll]=\"active.Type === 'image' && ImageZoom > 1\">\n @if (UsesStoragePlayer(active)) {\n <!-- Audio/video backed by an MJStorage file: secure, permission-gated streaming player. -->\n <mj-storage-media-player\n class=\"media-player\"\n [FileID]=\"active.FileID ?? null\"\n [Provider]=\"Provider\"\n ></mj-storage-media-player>\n } @else if (UsesUrlPlayer(active)) {\n <!-- Audio/video from a public URL: the real media player (waveform/transport). -->\n <mj-media-player class=\"media-player\" [Tracks]=\"TracksFor(active)\"></mj-media-player>\n } @else if (IsResolving(active)) {\n <div class=\"media-status\">\n <i class=\"fa-solid fa-spinner fa-spin media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">Loading secure media…</p>\n </div>\n } @else if (ResolveError(active); as err) {\n <div class=\"media-status media-status--error\">\n <i class=\"fa-solid fa-triangle-exclamation media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">{{ err }}</p>\n </div>\n } @else if (RenderUrl(active); as renderUrl) {\n @switch (active.Type) {\n @case ('image') {\n <img\n class=\"media-image\"\n [src]=\"renderUrl\"\n [style.transform]=\"'scale(' + ImageZoom + ')'\"\n [alt]=\"active.DisplayName\"\n />\n }\n @case ('pdf') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n ></iframe>\n }\n @case ('web') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n referrerpolicy=\"no-referrer\"\n ></iframe>\n }\n }\n }\n\n @if (HighlightStyle(active); as hs) {\n <div class=\"media-highlight\" [ngStyle]=\"hs\" aria-hidden=\"true\">\n @if (active.Highlight?.Label) {\n <span class=\"media-highlight__label\">{{ active.Highlight?.Label }}</span>\n }\n </div>\n }\n </div>\n\n @if (active.Caption) {\n <p class=\"media-caption\">{{ active.Caption }}</p>\n }\n </div>\n }\n }\n</div>\n"]}
1
+ {"version":3,"file":"realtime-media-surface.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts","../../../../../src/lib/components/realtime/media/realtime-media-surface.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit,\n ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { Subscription } from 'rxjs';\nimport { LogError } from '@memberjunction/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJMediaPlayerComponent, MJStorageMediaPlayerComponent, MediaTrack } from '@memberjunction/ng-media-player';\nimport { MediaChannelState, MediaItem } from './media-channel-state';\n\n/** Zoom levels cycled by the image zoom control (×). */\nconst IMAGE_ZOOM_LEVELS = [1, 1.5, 2, 3] as const;\n\n/** Shape of the `CreateMediaAccessToken` server mutation result (one file). */\ninterface CreateMediaAccessTokenResult {\n Success: boolean;\n Url?: string | null;\n ErrorMessage?: string | null;\n}\n\n/**\n * The exact GraphQL mutation used to resolve a SECURE streaming URL for an `MJ: Files` id — mints a\n * short-lived signed token (after a server-side per-user permission check) and returns the\n * authenticated `/media/<fileId>?token=` URL. Image / PDF / web (iframe) panes render this URL; the\n * audio/video panes hand the file id straight to `mj-storage-media-player` (which mints its own).\n */\nconst CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fileId: String!) {\n CreateMediaAccessToken(fileId: $fileId) {\n Success\n Url\n ErrorMessage\n }\n}`;\n\n/** The per-item resolution status for FileID-backed image/pdf/web panes (audio/video resolve in the player). */\nexport type FileResolveState =\n | { Status: 'loading' }\n | { Status: 'ready'; Url: string }\n | { Status: 'error'; Message: string };\n\n/** The render route chosen for a {@link MediaItem} (pure function of its source + type). */\nexport type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';\n\n/** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */\nexport function IsPlayerMediaType(item: MediaItem): boolean {\n return item.Type === 'audio' || item.Type === 'video';\n}\n\n/**\n * Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and\n * is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,\n * `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated\n * session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.\n */\nexport function IsEmbeddableSurfaceUrl(url: string | null | undefined, baseOrigin: string): boolean {\n if (typeof url !== 'string') {\n return false;\n }\n const trimmed = url.trim();\n if (!trimmed || trimmed.startsWith('//')) {\n return false;\n }\n try {\n const parsed = new URL(trimmed, baseOrigin);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\n/**\n * Decides which render path an item uses based ONLY on its source + type — the single source of truth\n * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):\n * - audio/video + FileID → `'storage-player'` (`mj-storage-media-player`, secure streaming)\n * - audio/video + Url → `'url-player'` (`mj-media-player`, public URL)\n * - image/pdf/web → `'iframe-or-img'` (img/iframe; FileID resolves a streaming URL first)\n * - no usable source → `'none'`\n */\nexport function RouteForMediaItem(item: MediaItem): MediaRenderRoute {\n if (IsPlayerMediaType(item)) {\n if (item.FileID) {\n return 'storage-player';\n }\n return item.Url ? 'url-player' : 'none';\n }\n if (item.Url || item.FileID) {\n return 'iframe-or-img';\n }\n return 'none';\n}\n\n/**\n * LIVE MEDIA surface (`mj-realtime-media-surface`) — the Media channel tab's pane, rendered with a\n * TABS layout: a horizontal tab bar (one tab per {@link MediaItem}, type icon + name) over a single\n * media pane that swaps as you switch tabs. Pure PERCEPTION of the channel's {@link MediaChannelState}:\n * the agent's `Media_*` tools mutate the state through the channel plugin and this surface re-renders.\n *\n * SOURCE-AWARE rendering. Each item is backed by EITHER an external/public {@link MediaItem.Url} or an\n * MemberJunction {@link MediaItem.FileID} (an `MJ: Files` id, streamed securely + permission-gated):\n * - **audio/video + FileID** → `<mj-storage-media-player [FileID] [Provider]>` (real transport/waveform,\n * streams over HTTP Range under the session's authenticated provider).\n * - **audio/video + Url** → `<mj-media-player [Tracks]>` (the same real player, fed the public URL).\n * - **image/pdf/web + FileID** → a streaming URL is resolved via `CreateMediaAccessToken` (through the\n * channel's provider) and rendered in the existing img/iframe path; graceful no-access state on failure.\n * - **image/pdf/web + Url** → the public URL, rendered unchanged.\n *\n * Multi-provider safe: the channel plugin threads the live session's `IMetadataProvider` in via the\n * `Provider` input ({@link BaseAngularComponent}), and both the storage player and the token mutation\n * use it ({@link ProviderToUse}) rather than the global default.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-media-surface',\n imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-media-surface.component.html',\n styleUrls: ['./realtime-media-surface.component.css'],\n})\nexport class RealtimeMediaSurfaceComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n /** The shared media state engine (set by the channel's BindSurface before first CD). */\n @Input()\n set State(value: MediaChannelState | null) {\n if (value === this._state) {\n return;\n }\n this._state = value;\n this.resubscribe();\n }\n get State(): MediaChannelState | null {\n return this._state;\n }\n private _state: MediaChannelState | null = null;\n\n /** Display name of the agent fronting the session — used in the empty-state copy. */\n @Input() AgentName = 'The agent';\n\n /** The current image zoom multiplier (cycled by the zoom button; image panes only). */\n public ImageZoom = 1;\n\n /** Sanitized URL cache for `web` / `pdf` iframes (Angular blocks raw resource URLs in `[src]`). */\n private safeUrlCache = new Map<string, SafeResourceUrl>();\n\n /** The id of the item whose play we last actioned, so a re-render doesn't replay it. */\n private lastPlayedStamp = new Map<string, number>();\n\n /**\n * Resolution status for FileID-backed image/pdf/web items, keyed by item id. Audio/video FileID items\n * resolve inside `mj-storage-media-player` and never enter this map.\n */\n private fileResolveByItem = new Map<string, FileResolveState>();\n\n @ViewChild('mediaPane') private mediaPane?: ElementRef<HTMLElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly sanitizer = inject(DomSanitizer);\n private stateSub: Subscription | null = null;\n\n ngOnInit(): void {\n this.resubscribe();\n }\n\n ngOnDestroy(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n }\n\n /** The items currently on the surface (empty when no state is bound). */\n public get Items(): MediaItem[] {\n return this._state?.Items ?? [];\n }\n\n /** The active item, or `null` when the surface is empty. */\n public get ActiveItem(): MediaItem | null {\n return this._state?.ActiveItem ?? null;\n }\n\n /** Whether the active id matches `item` (drives the active tab + pane). */\n public IsActive(item: MediaItem): boolean {\n return this._state?.ActiveItemId === item.Id;\n }\n\n // ---------------------------------------------------------------------------\n // Source routing — which render path an item uses\n // ---------------------------------------------------------------------------\n\n /** Whether `item` is rendered by the real media player (audio/video). */\n public IsPlayerItem(item: MediaItem): boolean {\n return IsPlayerMediaType(item);\n }\n\n /** Whether `item` is an audio/video item backed by an MJStorage FileID → `mj-storage-media-player`. */\n public UsesStoragePlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'storage-player';\n }\n\n /** Whether `item` is an audio/video item backed by a public URL → `mj-media-player`. */\n public UsesUrlPlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'url-player';\n }\n\n /** The single-track payload for the URL-backed `mj-media-player` (audio/video). */\n public TracksFor(item: MediaItem): MediaTrack[] {\n if (!item.Url || !this.isEmbeddableUrl(item.Url)) {\n return [];\n }\n return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url.trim() }];\n }\n\n /**\n * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a\n * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /\n * if the URL fails http(s) scheme screening).\n */\n public RenderUrl(item: MediaItem): string | null {\n let candidate: string | null = null;\n if (item.Url) {\n candidate = item.Url;\n } else if (item.FileID) {\n const state = this.fileResolveByItem.get(item.Id);\n candidate = state?.Status === 'ready' ? state.Url : null;\n }\n if (!candidate) {\n return null;\n }\n if (!this.isEmbeddableUrl(candidate)) {\n LogError('RealtimeMediaSurface: refused media URL with a non-http(s) or scheme-relative target.');\n return null;\n }\n return candidate.trim();\n }\n\n /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */\n public IsResolving(item: MediaItem): boolean {\n return !item.Url && !!item.FileID && this.fileResolveByItem.get(item.Id)?.Status === 'loading';\n }\n\n /** The no-access / load-failure message for a FileID-backed image/pdf/web item, or `null`. */\n public ResolveError(item: MediaItem): string | null {\n if (item.Url || !item.FileID) {\n return null;\n }\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'error' ? state.Message : null;\n }\n\n /** Switches the active tab to `item`. */\n public SelectTab(item: MediaItem): void {\n this._state?.SetActive(item.Id);\n }\n\n /** Keyboard handler for the tab buttons — Enter/Space activate, arrows move between tabs. */\n public OnTabKeydown(event: KeyboardEvent, index: number): void {\n const items = this.Items;\n if (items.length === 0) {\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const delta = event.key === 'ArrowRight' ? 1 : -1;\n const next = (index + delta + items.length) % items.length;\n this._state?.SetActive(items[next].Id);\n this.focusTabButton(next);\n }\n }\n\n /** Closes one media item (removes its tab + pane). */\n public CloseItem(event: Event, item: MediaItem): void {\n event.stopPropagation();\n this.fileResolveByItem.delete(item.Id);\n this._state?.RemoveItem(item.Id);\n }\n\n /** Whether the open-in-tab / download toolbar buttons are available for `item` (need a usable URL). */\n public CanOpenOrDownload(item: MediaItem): boolean {\n return !!this.RenderUrl(item);\n }\n\n /** Opens the active media in a new browser tab (its public or resolved streaming URL). */\n public OpenInNew(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (url) {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n }\n\n /** Triggers a browser download of the active media via a transient anchor (public or resolved URL). */\n public Download(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (!url) {\n return;\n }\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = item.DisplayName || '';\n anchor.target = '_blank';\n anchor.rel = 'noopener noreferrer';\n anchor.click();\n }\n\n /** Cycles the image zoom multiplier through {@link IMAGE_ZOOM_LEVELS}. */\n public CycleZoom(): void {\n const idx = IMAGE_ZOOM_LEVELS.indexOf(this.ImageZoom as (typeof IMAGE_ZOOM_LEVELS)[number]);\n this.ImageZoom = IMAGE_ZOOM_LEVELS[(idx + 1) % IMAGE_ZOOM_LEVELS.length];\n }\n\n /** The Font Awesome icon class for a media type's tab. */\n public IconFor(type: MediaItem['Type']): string {\n switch (type) {\n case 'image':\n return 'fa-solid fa-image';\n case 'video':\n return 'fa-solid fa-film';\n case 'audio':\n return 'fa-solid fa-music';\n case 'pdf':\n return 'fa-solid fa-file-pdf';\n case 'web':\n return 'fa-solid fa-globe';\n default:\n return 'fa-solid fa-file';\n }\n }\n\n /**\n * A memoized sanitized resource URL for iframe-rendered media (web / pdf).\n *\n * SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can\n * originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or\n * relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever\n * trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.\n */\n public SafeUrl(url: string): SafeResourceUrl {\n let cached = this.safeUrlCache.get(url);\n if (!cached) {\n const target = this.isEmbeddableUrl(url) ? url : 'about:blank';\n if (target !== url) {\n LogError(`RealtimeMediaSurface: refused to embed media URL with a non-http(s) scheme.`);\n }\n cached = this.sanitizer.bypassSecurityTrustResourceUrl(target);\n this.safeUrlCache.set(url, cached);\n }\n return cached;\n }\n\n /** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */\n private isEmbeddableUrl(url: string): boolean {\n const origin =\n typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'https://localhost';\n return IsEmbeddableSurfaceUrl(url, origin);\n }\n\n /** Inline style for the fractional highlight rectangle (percentages of the pane box). */\n public HighlightStyle(item: MediaItem): Record<string, string> | null {\n const h = item.Highlight;\n if (!h) {\n return null;\n }\n const pct = (v: number): string => `${Math.max(0, Math.min(1, v)) * 100}%`;\n return { left: pct(h.X), top: pct(h.Y), width: pct(h.W), height: pct(h.H) };\n }\n\n /** Stable `@for` track for tab/pane diffing. */\n public TrackById(_index: number, item: MediaItem): string {\n return item.Id;\n }\n\n /** Re-subscribes the state's Changed$ stream (called on state input change + ngOnInit). */\n private resubscribe(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n if (!this._state) {\n this.cdr.markForCheck();\n return;\n }\n this.stateSub = this._state.Changed$.subscribe(() => {\n this.resolvePendingFileUrls();\n this.applyPendingPlay();\n this.cdr.markForCheck();\n });\n }\n\n /**\n * Mints streaming URLs for any FileID-backed image/pdf/web items not yet resolved (audio/video FileID\n * items resolve inside the storage player, so they're skipped). Idempotent — each item is resolved\n * once; a `loading`/`ready`/`error` entry guards against re-firing on every state change.\n */\n private resolvePendingFileUrls(): void {\n for (const item of this.Items) {\n if (item.Url || !item.FileID || this.IsPlayerItem(item)) {\n continue;\n }\n if (this.fileResolveByItem.has(item.Id)) {\n continue;\n }\n this.fileResolveByItem.set(item.Id, { Status: 'loading' });\n void this.resolveFileUrl(item.Id, item.FileID);\n }\n }\n\n /** Resolves one file id to a secure streaming URL via the channel's provider, then re-renders. */\n private async resolveFileUrl(itemId: string, fileId: string): Promise<void> {\n const result = await this.mintAccessToken(fileId);\n if (result && result.Success && result.Url) {\n this.fileResolveByItem.set(itemId, { Status: 'ready', Url: result.Url });\n } else {\n this.fileResolveByItem.set(itemId, {\n Status: 'error',\n Message: result?.ErrorMessage || \"This media couldn't be loaded, or you don't have access to it.\",\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Mints an authenticated streaming-URL token for one file id via the active provider. */\n private async mintAccessToken(fileId: string): Promise<CreateMediaAccessTokenResult | null> {\n try {\n const provider = this.ProviderToUse as unknown as GraphQLDataProvider;\n const data = await provider.ExecuteGQL(CREATE_MEDIA_ACCESS_TOKEN_MUTATION, { fileId });\n const payload = data?.CreateMediaAccessToken as CreateMediaAccessTokenResult | undefined;\n return payload ?? null;\n } catch (err) {\n LogError(`CreateMediaAccessToken failed for file '${fileId}': ${err instanceof Error ? err.message : String(err)}`);\n return { Success: false, ErrorMessage: 'Unable to load this media.' };\n }\n }\n\n /**\n * Honors a pending {@link MediaItem.PlayRequestedAt} stamp on the active video/audio element by\n * calling `.play()` once per new stamp (so a routine re-render never replays the media). Only applies\n * to the URL-backed `<mj-media-player>` / raw-element panes; the storage player owns its own autoplay.\n */\n private applyPendingPlay(): void {\n const item = this.ActiveItem;\n if (!item || item.PlayRequestedAt == null || (item.Type !== 'video' && item.Type !== 'audio')) {\n return;\n }\n if (this.lastPlayedStamp.get(item.Id) === item.PlayRequestedAt) {\n return;\n }\n this.lastPlayedStamp.set(item.Id, item.PlayRequestedAt);\n // The pane re-renders this CD pass; defer the play to the next microtask so the element exists.\n queueMicrotask(() => {\n const el = this.mediaPane?.nativeElement.querySelector('video, audio') as HTMLMediaElement | null;\n void el?.play().catch(() => undefined);\n });\n }\n\n /** Moves keyboard focus to the tab button at `index` (arrow-key navigation). */\n private focusTabButton(index: number): void {\n queueMicrotask(() => {\n const host = this.mediaPane?.nativeElement.parentElement;\n const buttons = host?.querySelectorAll<HTMLButtonElement>('.media-tab');\n buttons?.[index]?.focus();\n });\n }\n}\n","<div class=\"media-surface\">\n @if (Items.length === 0) {\n <div class=\"media-empty\">\n <i class=\"fa-solid fa-images media-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-empty__title\">No media yet</p>\n <p class=\"media-empty__hint\">{{ AgentName }} will share visuals here during the conversation.</p>\n </div>\n } @else {\n <!-- Tab bar: one tab per media item (the chosen \"Tabs\" layout — one pane, switch via tabs). -->\n <div class=\"media-tabs\" role=\"tablist\" aria-label=\"Shared media\">\n @for (item of Items; track TrackById($index, item); let i = $index) {\n <button\n type=\"button\"\n class=\"media-tab\"\n role=\"tab\"\n [class.media-tab--active]=\"IsActive(item)\"\n [attr.aria-selected]=\"IsActive(item)\"\n [attr.tabindex]=\"IsActive(item) ? 0 : -1\"\n [title]=\"item.DisplayName\"\n (click)=\"SelectTab(item)\"\n (keydown)=\"OnTabKeydown($event, i)\"\n >\n <i class=\"media-tab__icon\" [ngClass]=\"IconFor(item.Type)\" aria-hidden=\"true\"></i>\n <span class=\"media-tab__name\">{{ item.DisplayName }}</span>\n <span\n class=\"media-tab__close\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Close ' + item.DisplayName\"\n (click)=\"CloseItem($event, item)\"\n (keydown.enter)=\"CloseItem($event, item)\"\n (keydown.space)=\"CloseItem($event, item)\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </span>\n </button>\n }\n </div>\n\n @if (ActiveItem; as active) {\n <!-- Pane toolbar: per-type controls (open / download / zoom). -->\n <div class=\"media-toolbar\">\n <span class=\"media-toolbar__title\" [title]=\"active.DisplayName\">{{ active.DisplayName }}</span>\n <span class=\"media-toolbar__spacer\"></span>\n @if (active.Type === 'image') {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n [attr.aria-label]=\"'Zoom image (currently ' + ImageZoom + 'x)'\"\n title=\"Zoom\"\n (click)=\"CycleZoom()\"\n >\n <i class=\"fa-solid fa-magnifying-glass-plus\" aria-hidden=\"true\"></i>\n <span class=\"media-iconbtn__badge\">{{ ImageZoom }}×</span>\n </button>\n }\n @if (CanOpenOrDownload(active)) {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Download media\"\n title=\"Download\"\n (click)=\"Download(active)\"\n >\n <i class=\"fa-solid fa-download\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Open media in a new tab\"\n title=\"Open in new tab\"\n (click)=\"OpenInNew(active)\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- The single media pane (swaps as tabs switch). The highlight overlay sits on top. -->\n <div class=\"media-pane\" #mediaPane>\n <div class=\"media-stage\" [class.media-stage--scroll]=\"active.Type === 'image' && ImageZoom > 1\">\n @if (UsesStoragePlayer(active)) {\n <!-- Audio/video backed by an MJStorage file: secure, permission-gated streaming player. -->\n <mj-storage-media-player\n class=\"media-player\"\n [FileID]=\"active.FileID ?? null\"\n [Provider]=\"Provider\"\n ></mj-storage-media-player>\n } @else if (UsesUrlPlayer(active)) {\n <!-- Audio/video from a public URL: the real media player (waveform/transport). -->\n <mj-media-player class=\"media-player\" [Tracks]=\"TracksFor(active)\"></mj-media-player>\n } @else if (IsResolving(active)) {\n <div class=\"media-status\">\n <i class=\"fa-solid fa-spinner fa-spin media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">Loading secure media…</p>\n </div>\n } @else if (ResolveError(active); as err) {\n <div class=\"media-status media-status--error\">\n <i class=\"fa-solid fa-triangle-exclamation media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">{{ err }}</p>\n </div>\n } @else if (RenderUrl(active); as renderUrl) {\n @switch (active.Type) {\n @case ('image') {\n <img\n class=\"media-image\"\n [src]=\"renderUrl\"\n [style.transform]=\"'scale(' + ImageZoom + ')'\"\n [alt]=\"active.DisplayName\"\n />\n }\n @case ('pdf') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n ></iframe>\n }\n @case ('web') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n referrerpolicy=\"no-referrer\"\n ></iframe>\n }\n }\n }\n\n @if (HighlightStyle(active); as hs) {\n <div class=\"media-highlight\" [ngStyle]=\"hs\" aria-hidden=\"true\">\n @if (active.Highlight?.Label) {\n <span class=\"media-highlight__label\">{{ active.Highlight?.Label }}</span>\n }\n </div>\n }\n </div>\n\n @if (active.Caption) {\n <p class=\"media-caption\">{{ active.Caption }}</p>\n }\n </div>\n }\n }\n</div>\n"]}
@@ -64,17 +64,28 @@ export declare class RealtimeAgentBannerComponent {
64
64
  ReviewStartedAt: Date | null;
65
65
  /** When reviewing: when the past session closed (null while it is still open). */
66
66
  ReviewClosedAt: Date | null;
67
- /** When reviewing: why the past session closed (`Explicit` | `Janitor` | `Shutdown` | `Error`), when known. */
67
+ /** When reviewing: why the past session closed (`Explicit` | `Janitor` | `Shutdown` | `Error` | `TimeLimit` | `Idle`), when known. */
68
68
  ReviewCloseReason: string | null;
69
- /** Whether the gear (density escape hatch + dev toggle) renders (disclosure level 2+). */
70
- ShowGear: boolean;
69
+ /**
70
+ * Whether the gear (density escape hatch + dev toggle) renders (disclosure level 2+). Taking the
71
+ * gear away also closes its popover, so no empty popover outlives the button that opened it.
72
+ */
73
+ set ShowGear(value: boolean);
74
+ get ShowGear(): boolean;
75
+ private _showGear;
71
76
  /**
72
77
  * Whether the interface-density segmented control renders inside the gear popover.
73
78
  * Default `true` (historical behaviour); a host may hide it via the overlay's
74
- * `[ShowDensityPicker]` input. The gear itself (and its developer-links toggle) still
75
- * renders when {@link ShowGear} is true even if the density picker is hidden.
79
+ * `[ShowDensityPicker]` input. The gear itself still renders when {@link ShowGear} is true
80
+ * even if the density picker is hidden; its developer-links toggle renders only when
81
+ * {@link AllowDevLinks} is also true.
76
82
  */
77
83
  ShowDensityPicker: boolean;
84
+ /**
85
+ * Host ceiling for developer links: `false` removes the gear's "Developer links" toggle.
86
+ * Per-session {@link DevMode} still decides whether links render when allowed.
87
+ */
88
+ AllowDevLinks: boolean;
78
89
  /** Whether the Minimize control renders (live sessions only). */
79
90
  ShowMinimize: boolean;
80
91
  /** Review only: whether the "Start live session" action renders. */
@@ -125,6 +136,6 @@ export declare class RealtimeAgentBannerComponent {
125
136
  /** True while the state pill should show a spinner instead of the waveform. */
126
137
  IsBusy(state: RealtimeConnectionState): boolean;
127
138
  static ɵfac: i0.ɵɵFactoryDeclaration<RealtimeAgentBannerComponent, never>;
128
- static ɵcmp: i0.ɵɵComponentDeclaration<RealtimeAgentBannerComponent, "mj-realtime-agent-banner", never, { "State": { "alias": "State"; "required": true; }; "Compact": { "alias": "Compact"; "required": false; }; "HideState": { "alias": "HideState"; "required": false; }; "AgentName": { "alias": "AgentName"; "required": false; }; "ErrorLabel": { "alias": "ErrorLabel"; "required": false; }; "ModelName": { "alias": "ModelName"; "required": false; }; "DevMode": { "alias": "DevMode"; "required": false; }; "SessionID": { "alias": "SessionID"; "required": false; }; "ReviewMode": { "alias": "ReviewMode"; "required": false; }; "ReviewStartedAt": { "alias": "ReviewStartedAt"; "required": false; }; "ReviewClosedAt": { "alias": "ReviewClosedAt"; "required": false; }; "ReviewCloseReason": { "alias": "ReviewCloseReason"; "required": false; }; "ShowGear": { "alias": "ShowGear"; "required": false; }; "ShowDensityPicker": { "alias": "ShowDensityPicker"; "required": false; }; "ShowMinimize": { "alias": "ShowMinimize"; "required": false; }; "ShowStartLive": { "alias": "ShowStartLive"; "required": false; }; "Density": { "alias": "Density"; "required": false; }; }, { "OpenSessionRequested": "OpenSessionRequested"; "DevModeToggled": "DevModeToggled"; "DensityChanged": "DensityChanged"; "MinimizeRequested": "MinimizeRequested"; "StartLiveRequested": "StartLiveRequested"; "CloseRequested": "CloseRequested"; }, never, never, true, never>;
139
+ static ɵcmp: i0.ɵɵComponentDeclaration<RealtimeAgentBannerComponent, "mj-realtime-agent-banner", never, { "State": { "alias": "State"; "required": true; }; "Compact": { "alias": "Compact"; "required": false; }; "HideState": { "alias": "HideState"; "required": false; }; "AgentName": { "alias": "AgentName"; "required": false; }; "ErrorLabel": { "alias": "ErrorLabel"; "required": false; }; "ModelName": { "alias": "ModelName"; "required": false; }; "DevMode": { "alias": "DevMode"; "required": false; }; "SessionID": { "alias": "SessionID"; "required": false; }; "ReviewMode": { "alias": "ReviewMode"; "required": false; }; "ReviewStartedAt": { "alias": "ReviewStartedAt"; "required": false; }; "ReviewClosedAt": { "alias": "ReviewClosedAt"; "required": false; }; "ReviewCloseReason": { "alias": "ReviewCloseReason"; "required": false; }; "ShowGear": { "alias": "ShowGear"; "required": false; }; "ShowDensityPicker": { "alias": "ShowDensityPicker"; "required": false; }; "AllowDevLinks": { "alias": "AllowDevLinks"; "required": false; }; "ShowMinimize": { "alias": "ShowMinimize"; "required": false; }; "ShowStartLive": { "alias": "ShowStartLive"; "required": false; }; "Density": { "alias": "Density"; "required": false; }; }, { "OpenSessionRequested": "OpenSessionRequested"; "DevModeToggled": "DevModeToggled"; "DensityChanged": "DensityChanged"; "MinimizeRequested": "MinimizeRequested"; "StartLiveRequested": "StartLiveRequested"; "CloseRequested": "CloseRequested"; }, never, never, true, never>;
129
140
  }
130
141
  //# sourceMappingURL=realtime-agent-banner.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-agent-banner.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-banner.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAA+B,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAOa,4BAA4B;IACvC,gFAAgF;IACrD,KAAK,EAAG,uBAAuB,CAAC;IAE3D;;;;;;;OAOG;IACM,OAAO,UAAS;IAEzB;;;OAGG;IACM,SAAS,UAAS;IAE3B,wEAAwE;IAC/D,SAAS,SAAe;IAEjC;;;OAGG;IACM,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACM,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC,yFAAyF;IAChF,OAAO,UAAS;IAEzB,wFAAwF;IAC/E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC;;;;OAIG;IACM,UAAU,UAAS;IAE5B,wEAAwE;IAC/D,eAAe,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE7C,kFAAkF;IACzE,cAAc,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE5C,+GAA+G;IACtG,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAMjD,0FAA0F;IACjF,QAAQ,UAAS;IAE1B;;;;;OAKG;IACM,iBAAiB,UAAQ;IAElC,iEAAiE;IACxD,YAAY,UAAS;IAE9B,oEAAoE;IAC3D,aAAa,UAAQ;IAE9B,0FAA0F;IACjF,OAAO,EAAE,iBAAiB,CAAU;IAE7C,wFAAwF;IAC9E,oBAAoB,uBAA8B;IAE5D,2EAA2E;IACjE,cAAc,wBAA+B;IAEvD,4EAA4E;IAClE,cAAc,kCAAyC;IAEjE,2EAA2E;IACjE,iBAAiB,qBAA4B;IAEvD,qFAAqF;IAC3E,kBAAkB,qBAA4B;IAExD,sFAAsF;IAC5E,cAAc,qBAA4B;IAEpD,sEAAsE;IAC/D,QAAQ,UAAS;IAExB,qEAAqE;IACrE,SAAgB,SAAS,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,iBAAiB,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAKjF;IAEF,wFAAwF;IACxF,IAAW,eAAe,IAAI,OAAO,CAEpC;IAED,wEAAwE;IACjE,WAAW,IAAI,IAAI;IAM1B,6FAA6F;IACtF,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAK1C,iDAAiD;IAE1C,eAAe,IAAI,IAAI;IAM9B,mFAAmF;IAC5E,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAItD,4CAA4C;IACrC,SAAS,IAAI,IAAI;IAKxB,iGAAiG;IACjG,IAAW,gBAAgB,IAAI,MAAM,CASpC;IAED,oEAAoE;IACpE,IAAW,sBAAsB,IAAI,MAAM,CAQ1C;IAED,iFAAiF;IACjF,OAAO,CAAC,WAAW;IAQnB,kGAAkG;IAC3F,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU;IAQtF,qEAAqE;IAC9D,UAAU,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;IAYzD,+EAA+E;IACxE,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;yCA1M3C,4BAA4B;2CAA5B,4BAA4B;CA6MxC"}
1
+ {"version":3,"file":"realtime-agent-banner.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-banner.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAA+B,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAOa,4BAA4B;IACvC,gFAAgF;IACrD,KAAK,EAAG,uBAAuB,CAAC;IAE3D;;;;;;;OAOG;IACM,OAAO,UAAS;IAEzB;;;OAGG;IACM,SAAS,UAAS;IAE3B,wEAAwE;IAC/D,SAAS,SAAe;IAEjC;;;OAGG;IACM,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACM,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC,yFAAyF;IAChF,OAAO,UAAS;IAEzB,wFAAwF;IAC/E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC;;;;OAIG;IACM,UAAU,UAAS;IAE5B,wEAAwE;IAC/D,eAAe,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE7C,kFAAkF;IACzE,cAAc,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE5C,sIAAsI;IAC7H,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAMjD;;;OAGG;IACH,IACI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAK1B;IACD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;;;OAMG;IACM,iBAAiB,UAAQ;IAElC;;;OAGG;IACM,aAAa,UAAQ;IAE9B,iEAAiE;IACxD,YAAY,UAAS;IAE9B,oEAAoE;IAC3D,aAAa,UAAQ;IAE9B,0FAA0F;IACjF,OAAO,EAAE,iBAAiB,CAAU;IAE7C,wFAAwF;IAC9E,oBAAoB,uBAA8B;IAE5D,2EAA2E;IACjE,cAAc,wBAA+B;IAEvD,4EAA4E;IAClE,cAAc,kCAAyC;IAEjE,2EAA2E;IACjE,iBAAiB,qBAA4B;IAEvD,qFAAqF;IAC3E,kBAAkB,qBAA4B;IAExD,sFAAsF;IAC5E,cAAc,qBAA4B;IAEpD,sEAAsE;IAC/D,QAAQ,UAAS;IAExB,qEAAqE;IACrE,SAAgB,SAAS,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,iBAAiB,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAKjF;IAEF,wFAAwF;IACxF,IAAW,eAAe,IAAI,OAAO,CAEpC;IAED,wEAAwE;IACjE,WAAW,IAAI,IAAI;IAM1B,6FAA6F;IACtF,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAK1C,iDAAiD;IAE1C,eAAe,IAAI,IAAI;IAM9B,mFAAmF;IAC5E,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAItD,4CAA4C;IACrC,SAAS,IAAI,IAAI;IAKxB,iGAAiG;IACjG,IAAW,gBAAgB,IAAI,MAAM,CASpC;IAED,oEAAoE;IACpE,IAAW,sBAAsB,IAAI,MAAM,CAU1C;IAED,iFAAiF;IACjF,OAAO,CAAC,WAAW;IAQnB,kGAAkG;IAC3F,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU;IAQtF,qEAAqE;IAC9D,UAAU,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;IAYzD,+EAA+E;IACxE,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;yCAhO3C,4BAA4B;2CAA5B,4BAA4B;CAmOxC"}
@@ -202,18 +202,18 @@ function RealtimeAgentBannerComponent_Conditional_3_Conditional_13_Template(rf,
202
202
  i0.ɵɵelementEnd();
203
203
  } }
204
204
  function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_For_5_Template(rf, ctx) { if (rf & 1) {
205
- const _r10 = i0.ɵɵgetCurrentView();
206
- i0.ɵɵelementStart(0, "button", 57);
207
- i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_For_5_Template_button_click_0_listener() { const d_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.SelectDensity(d_r11.Key)); });
205
+ const _r9 = i0.ɵɵgetCurrentView();
206
+ i0.ɵɵelementStart(0, "button", 56);
207
+ i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_For_5_Template_button_click_0_listener() { const d_r10 = i0.ɵɵrestoreView(_r9).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.SelectDensity(d_r10.Key)); });
208
208
  i0.ɵɵtext(1);
209
209
  i0.ɵɵelementEnd();
210
210
  } if (rf & 2) {
211
- const d_r11 = ctx.$implicit;
211
+ const d_r10 = ctx.$implicit;
212
212
  const ctx_r1 = i0.ɵɵnextContext(4);
213
- i0.ɵɵclassProp("on", ctx_r1.Density === d_r11.Key);
214
- i0.ɵɵattribute("aria-checked", ctx_r1.Density === d_r11.Key);
213
+ i0.ɵɵclassProp("on", ctx_r1.Density === d_r10.Key);
214
+ i0.ɵɵattribute("aria-checked", ctx_r1.Density === d_r10.Key);
215
215
  i0.ɵɵadvance();
216
- i0.ɵɵtextInterpolate(d_r11.Label);
216
+ i0.ɵɵtextInterpolate(d_r10.Label);
217
217
  } }
218
218
  function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_Template(rf, ctx) { if (rf & 1) {
219
219
  i0.ɵɵelementStart(0, "div", 51)(1, "span", 52);
@@ -225,35 +225,47 @@ function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3
225
225
  i0.ɵɵelementStart(6, "div", 55);
226
226
  i0.ɵɵtext(7, "Simple returns to the pure-audio surface. Auto grows the console as you use it.");
227
227
  i0.ɵɵelementEnd();
228
- i0.ɵɵelement(8, "div", 56);
229
228
  } if (rf & 2) {
230
229
  const ctx_r1 = i0.ɵɵnextContext(3);
231
230
  i0.ɵɵadvance(4);
232
231
  i0.ɵɵrepeater(ctx_r1.Densities);
233
232
  } }
233
+ function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_4_Template(rf, ctx) { if (rf & 1) {
234
+ i0.ɵɵelement(0, "div", 49);
235
+ } }
236
+ function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_5_Template(rf, ctx) { if (rf & 1) {
237
+ const _r11 = i0.ɵɵgetCurrentView();
238
+ i0.ɵɵelementStart(0, "button", 57);
239
+ i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_5_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.ToggleDev()); });
240
+ i0.ɵɵelement(1, "i", 58);
241
+ i0.ɵɵtext(2);
242
+ i0.ɵɵelementEnd();
243
+ } if (rf & 2) {
244
+ const ctx_r1 = i0.ɵɵnextContext(3);
245
+ i0.ɵɵattribute("aria-pressed", ctx_r1.DevMode);
246
+ i0.ɵɵadvance();
247
+ i0.ɵɵproperty("ngClass", ctx_r1.DevMode ? "fa-toggle-on" : "fa-toggle-off");
248
+ i0.ɵɵadvance();
249
+ i0.ɵɵtextInterpolate1(" Developer links ", ctx_r1.DevMode ? "on" : "off", " ");
250
+ } }
234
251
  function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Template(rf, ctx) { if (rf & 1) {
235
- const _r9 = i0.ɵɵgetCurrentView();
236
252
  i0.ɵɵelementStart(0, "div", 47);
237
253
  i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Template_div_click_0_listener($event) { return $event.stopPropagation(); });
238
254
  i0.ɵɵelementStart(1, "div", 48);
239
255
  i0.ɵɵtext(2, "Interface");
240
256
  i0.ɵɵelementEnd();
241
- i0.ɵɵconditionalCreate(3, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_Template, 9, 0);
242
- i0.ɵɵelementStart(4, "button", 49);
243
- i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ToggleDev()); });
244
- i0.ɵɵelement(5, "i", 50);
245
- i0.ɵɵtext(6);
246
- i0.ɵɵelementEnd()();
257
+ i0.ɵɵconditionalCreate(3, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_Template, 8, 0);
258
+ i0.ɵɵconditionalCreate(4, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_4_Template, 1, 0, "div", 49);
259
+ i0.ɵɵconditionalCreate(5, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_5_Template, 3, 3, "button", 50);
260
+ i0.ɵɵelementEnd();
247
261
  } if (rf & 2) {
248
262
  const ctx_r1 = i0.ɵɵnextContext(2);
249
263
  i0.ɵɵadvance(3);
250
264
  i0.ɵɵconditional(ctx_r1.ShowDensityPicker ? 3 : -1);
251
265
  i0.ɵɵadvance();
252
- i0.ɵɵattribute("aria-pressed", ctx_r1.DevMode);
253
- i0.ɵɵadvance();
254
- i0.ɵɵproperty("ngClass", ctx_r1.DevMode ? "fa-toggle-on" : "fa-toggle-off");
266
+ i0.ɵɵconditional(ctx_r1.ShowDensityPicker && ctx_r1.AllowDevLinks ? 4 : -1);
255
267
  i0.ɵɵadvance();
256
- i0.ɵɵtextInterpolate1(" Developer links ", ctx_r1.DevMode ? "on" : "off", " ");
268
+ i0.ɵɵconditional(ctx_r1.AllowDevLinks ? 5 : -1);
257
269
  } }
258
270
  function RealtimeAgentBannerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
259
271
  i0.ɵɵelement(0, "div", 30);
@@ -273,7 +285,7 @@ function RealtimeAgentBannerComponent_Conditional_3_Template(rf, ctx) { if (rf &
273
285
  i0.ɵɵconditionalCreate(12, RealtimeAgentBannerComponent_Conditional_3_Conditional_12_Template, 2, 3, "button", 36);
274
286
  i0.ɵɵconditionalCreate(13, RealtimeAgentBannerComponent_Conditional_3_Conditional_13_Template, 2, 0, "button", 37);
275
287
  i0.ɵɵelementEnd();
276
- i0.ɵɵconditionalCreate(14, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Template, 7, 4, "div", 38);
288
+ i0.ɵɵconditionalCreate(14, RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Template, 6, 3, "div", 38);
277
289
  } if (rf & 2) {
278
290
  const ctx_r1 = i0.ɵɵnextContext();
279
291
  i0.ɵɵattribute("data-state", ctx_r1.OrbState(ctx_r1.State));
@@ -356,20 +368,38 @@ export class RealtimeAgentBannerComponent {
356
368
  ReviewStartedAt = null;
357
369
  /** When reviewing: when the past session closed (null while it is still open). */
358
370
  ReviewClosedAt = null;
359
- /** When reviewing: why the past session closed (`Explicit` | `Janitor` | `Shutdown` | `Error`), when known. */
371
+ /** When reviewing: why the past session closed (`Explicit` | `Janitor` | `Shutdown` | `Error` | `TimeLimit` | `Idle`), when known. */
360
372
  ReviewCloseReason = null;
361
373
  // ── App-bar action cluster (disclosure-gated by the overlay) ────────────────
362
374
  // STATE + WINDOW-CHROME only. Captions & End were removed (they duplicated the bottom dock's
363
375
  // controls — one home per control), and "pure audio" was folded into the gear's Density = Simple.
364
- /** Whether the gear (density escape hatch + dev toggle) renders (disclosure level 2+). */
365
- ShowGear = false;
376
+ /**
377
+ * Whether the gear (density escape hatch + dev toggle) renders (disclosure level 2+). Taking the
378
+ * gear away also closes its popover, so no empty popover outlives the button that opened it.
379
+ */
380
+ set ShowGear(value) {
381
+ this._showGear = value;
382
+ if (!value) {
383
+ this.GearOpen = false;
384
+ }
385
+ }
386
+ get ShowGear() {
387
+ return this._showGear;
388
+ }
389
+ _showGear = false;
366
390
  /**
367
391
  * Whether the interface-density segmented control renders inside the gear popover.
368
392
  * Default `true` (historical behaviour); a host may hide it via the overlay's
369
- * `[ShowDensityPicker]` input. The gear itself (and its developer-links toggle) still
370
- * renders when {@link ShowGear} is true even if the density picker is hidden.
393
+ * `[ShowDensityPicker]` input. The gear itself still renders when {@link ShowGear} is true
394
+ * even if the density picker is hidden; its developer-links toggle renders only when
395
+ * {@link AllowDevLinks} is also true.
371
396
  */
372
397
  ShowDensityPicker = true;
398
+ /**
399
+ * Host ceiling for developer links: `false` removes the gear's "Developer links" toggle.
400
+ * Per-session {@link DevMode} still decides whether links render when allowed.
401
+ */
402
+ AllowDevLinks = true;
373
403
  /** Whether the Minimize control renders (live sessions only). */
374
404
  ShowMinimize = false;
375
405
  /** Review only: whether the "Start live session" action renders. */
@@ -443,6 +473,8 @@ export class RealtimeAgentBannerComponent {
443
473
  switch (this.ReviewCloseReason) {
444
474
  case 'Explicit': return 'Ended by user';
445
475
  case 'Janitor': return 'Closed by janitor';
476
+ case 'TimeLimit': return 'Time limit';
477
+ case 'Idle': return 'Idle — no activity';
446
478
  case 'Shutdown': return 'Closed at shutdown';
447
479
  case 'Error': return 'Ended with error';
448
480
  default: return '';
@@ -483,7 +515,7 @@ export class RealtimeAgentBannerComponent {
483
515
  static ɵfac = function RealtimeAgentBannerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeAgentBannerComponent)(); };
484
516
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeAgentBannerComponent, selectors: [["mj-realtime-agent-banner"]], hostBindings: function RealtimeAgentBannerComponent_HostBindings(rf, ctx) { if (rf & 1) {
485
517
  i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_click_HostBindingHandler() { return ctx.OnDocumentClick(); }, i0.ɵɵresolveDocument);
486
- } }, inputs: { State: "State", Compact: "Compact", HideState: "HideState", AgentName: "AgentName", ErrorLabel: "ErrorLabel", ModelName: "ModelName", DevMode: "DevMode", SessionID: "SessionID", ReviewMode: "ReviewMode", ReviewStartedAt: "ReviewStartedAt", ReviewClosedAt: "ReviewClosedAt", ReviewCloseReason: "ReviewCloseReason", ShowGear: "ShowGear", ShowDensityPicker: "ShowDensityPicker", ShowMinimize: "ShowMinimize", ShowStartLive: "ShowStartLive", Density: "Density" }, outputs: { OpenSessionRequested: "OpenSessionRequested", DevModeToggled: "DevModeToggled", DensityChanged: "DensityChanged", MinimizeRequested: "MinimizeRequested", StartLiveRequested: "StartLiveRequested", CloseRequested: "CloseRequested" }, decls: 4, vars: 5, consts: [[1, "banner"], ["aria-hidden", "true", 1, "agent-orb", "agent-orb--review"], [1, "banner__id"], [1, "banner__name"], [1, "pill", "pill--review"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock-rotate-left"], [1, "banner__for", "banner__range"], [1, "pill", "pill--close-reason", 3, "title"], ["type", "button", "title", "Open the agent session record", 1, "dev-link"], [1, "banner__state", "banner__state--review"], ["aria-hidden", "true", 1, "fa-solid", "fa-box-archive"], [1, "banner__state-label", "banner__state-label--review"], [1, "bar-actions"], ["type", "button", "title", "Resume this session as a new live call", 1, "start-live-pill"], ["type", "button", "title", "Close review", "aria-label", "Close session review", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"], ["type", "button", "title", "Open the agent session record", 1, "dev-link", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-arrow-up-right-from-square"], ["type", "button", "title", "Resume this session as a new live call", 1, "start-live-pill", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone"], [1, "banner__id", "banner__id--compact"], ["aria-hidden", "true", 1, "banner__avatar"], [1, "banner__name", "banner__name--compact"], [1, "banner__state-chip"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", "banner__minimize--compact"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "banner__state-dot"], [1, "banner__state-chip-label"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", "banner__minimize--compact", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-minus"], ["aria-hidden", "true", 1, "agent-orb"], [1, "pill", "pill--sage"], ["aria-hidden", "true", 1, "fa-solid", "fa-feather"], [1, "banner__for"], [1, "banner__model", 3, "title"], [1, "banner__state", 3, "banner__state--busy"], ["type", "button", "title", "Interface density & developer links", 1, "iconb", 3, "iconb--on"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb"], ["role", "menu", "aria-label", "Interface settings", 1, "gear-pop"], [1, "banner__state"], ["aria-hidden", "true", 1, "waveform", 3, "waveform--idle"], [1, "banner__state-label"], ["aria-hidden", "true", 1, "waveform"], ["type", "button", "title", "Interface density & developer links", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-gear"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-down-left-and-up-right-to-center"], ["role", "menu", "aria-label", "Interface settings", 1, "gear-pop", 3, "click"], [1, "gp-h"], ["type", "button", 1, "gp-link", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", 3, "ngClass"], [1, "gp-row"], [1, "gp-l"], ["role", "radiogroup", "aria-label", "Interface density", 1, "gp-seg"], ["type", "button", "role", "radio", 3, "on"], [1, "gp-note"], [1, "gp-div"], ["type", "button", "role", "radio", 3, "click"]], template: function RealtimeAgentBannerComponent_Template(rf, ctx) { if (rf & 1) {
518
+ } }, inputs: { State: "State", Compact: "Compact", HideState: "HideState", AgentName: "AgentName", ErrorLabel: "ErrorLabel", ModelName: "ModelName", DevMode: "DevMode", SessionID: "SessionID", ReviewMode: "ReviewMode", ReviewStartedAt: "ReviewStartedAt", ReviewClosedAt: "ReviewClosedAt", ReviewCloseReason: "ReviewCloseReason", ShowGear: "ShowGear", ShowDensityPicker: "ShowDensityPicker", AllowDevLinks: "AllowDevLinks", ShowMinimize: "ShowMinimize", ShowStartLive: "ShowStartLive", Density: "Density" }, outputs: { OpenSessionRequested: "OpenSessionRequested", DevModeToggled: "DevModeToggled", DensityChanged: "DensityChanged", MinimizeRequested: "MinimizeRequested", StartLiveRequested: "StartLiveRequested", CloseRequested: "CloseRequested" }, decls: 4, vars: 5, consts: [[1, "banner"], ["aria-hidden", "true", 1, "agent-orb", "agent-orb--review"], [1, "banner__id"], [1, "banner__name"], [1, "pill", "pill--review"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock-rotate-left"], [1, "banner__for", "banner__range"], [1, "pill", "pill--close-reason", 3, "title"], ["type", "button", "title", "Open the agent session record", 1, "dev-link"], [1, "banner__state", "banner__state--review"], ["aria-hidden", "true", 1, "fa-solid", "fa-box-archive"], [1, "banner__state-label", "banner__state-label--review"], [1, "bar-actions"], ["type", "button", "title", "Resume this session as a new live call", 1, "start-live-pill"], ["type", "button", "title", "Close review", "aria-label", "Close session review", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"], ["type", "button", "title", "Open the agent session record", 1, "dev-link", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-arrow-up-right-from-square"], ["type", "button", "title", "Resume this session as a new live call", 1, "start-live-pill", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone"], [1, "banner__id", "banner__id--compact"], ["aria-hidden", "true", 1, "banner__avatar"], [1, "banner__name", "banner__name--compact"], [1, "banner__state-chip"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", "banner__minimize--compact"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "banner__state-dot"], [1, "banner__state-chip-label"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", "banner__minimize--compact", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-minus"], ["aria-hidden", "true", 1, "agent-orb"], [1, "pill", "pill--sage"], ["aria-hidden", "true", 1, "fa-solid", "fa-feather"], [1, "banner__for"], [1, "banner__model", 3, "title"], [1, "banner__state", 3, "banner__state--busy"], ["type", "button", "title", "Interface density & developer links", 1, "iconb", 3, "iconb--on"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb"], ["role", "menu", "aria-label", "Interface settings", 1, "gear-pop"], [1, "banner__state"], ["aria-hidden", "true", 1, "waveform", 3, "waveform--idle"], [1, "banner__state-label"], ["aria-hidden", "true", 1, "waveform"], ["type", "button", "title", "Interface density & developer links", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-gear"], ["type", "button", "title", "Hide the call view \u2014 the call stays live", "aria-label", "Minimize the call view", 1, "iconb", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-down-left-and-up-right-to-center"], ["role", "menu", "aria-label", "Interface settings", 1, "gear-pop", 3, "click"], [1, "gp-h"], [1, "gp-div"], ["type", "button", 1, "gp-link"], [1, "gp-row"], [1, "gp-l"], ["role", "radiogroup", "aria-label", "Interface density", 1, "gp-seg"], ["type", "button", "role", "radio", 3, "on"], [1, "gp-note"], ["type", "button", "role", "radio", 3, "click"], ["type", "button", 1, "gp-link", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", 3, "ngClass"]], template: function RealtimeAgentBannerComponent_Template(rf, ctx) { if (rf & 1) {
487
519
  i0.ɵɵelementStart(0, "div", 0);
488
520
  i0.ɵɵconditionalCreate(1, RealtimeAgentBannerComponent_Conditional_1_Template, 18, 5)(2, RealtimeAgentBannerComponent_Conditional_2_Template, 7, 4)(3, RealtimeAgentBannerComponent_Conditional_3_Template, 15, 9);
489
521
  i0.ɵɵelementEnd();
@@ -495,7 +527,7 @@ export class RealtimeAgentBannerComponent {
495
527
  }
496
528
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeAgentBannerComponent, [{
497
529
  type: Component,
498
- args: [{ standalone: true, selector: 'mj-realtime-agent-banner', imports: [CommonModule], template: "<div class=\"banner\" [class.banner--review]=\"ReviewMode\" [class.banner--compact]=\"Compact && !ReviewMode\">\n @if (ReviewMode) {\n <!-- SESSION REVIEW: static orb \u00B7 name \u00B7 review badge \u00B7 started\u2192closed \u00B7 close-reason chip,\n with the review's exit actions up here in the header bar (Start live leads, Close trails). -->\n <div class=\"agent-orb agent-orb--review\" aria-hidden=\"true\"></div>\n\n <div class=\"banner__id\">\n <span class=\"banner__name\">Voice for {{ AgentName }}</span>\n <span class=\"pill pill--review\"><i class=\"fa-solid fa-clock-rotate-left\" aria-hidden=\"true\"></i> Session review</span>\n @if (ReviewRangeLabel) {\n <span class=\"banner__for banner__range\">{{ ReviewRangeLabel }}</span>\n }\n @if (ReviewCloseReasonLabel) {\n <span class=\"pill pill--close-reason\" [title]=\"'Close reason: ' + ReviewCloseReason\">{{ ReviewCloseReasonLabel }}</span>\n }\n @if (ShowOpenSession) {\n <button type=\"button\" class=\"dev-link\" (click)=\"OpenSession()\"\n title=\"Open the agent session record\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n <span>Open session</span>\n </button>\n }\n </div>\n\n <div class=\"banner__state banner__state--review\">\n <i class=\"fa-solid fa-box-archive\" aria-hidden=\"true\"></i>\n <span class=\"banner__state-label banner__state-label--review\">Reviewing past session</span>\n </div>\n\n <div class=\"bar-actions\">\n @if (ShowStartLive) {\n <button type=\"button\" class=\"start-live-pill\" (click)=\"StartLiveRequested.emit()\"\n title=\"Resume this session as a new live call\">\n <i class=\"fa-solid fa-phone\" aria-hidden=\"true\"></i>\n <span>Start live session</span>\n </button>\n }\n <button type=\"button\" class=\"iconb\" (click)=\"CloseRequested.emit()\"\n title=\"Close review\" aria-label=\"Close session review\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @else if (Compact) {\n <!-- COMPACT (Calm Orb \u00B7 overlay): a tiny identity + state row only. No duplicate orb,\n no state pill, no headphones / gear / expand \u2014 the body shows the big orb + name +\n state, and the lean dock owns the actions. Minimize stays reachable here. -->\n <div class=\"banner__id banner__id--compact\">\n <span class=\"banner__avatar\" aria-hidden=\"true\">{{ AgentName.charAt(0) }}</span>\n <span class=\"banner__name banner__name--compact\">{{ AgentName }}</span>\n @if (!HideState) {\n <span class=\"banner__state-chip\">\n @if (IsBusy(State)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <span class=\"banner__state-dot\" [attr.data-state]=\"OrbState(State)\" aria-hidden=\"true\"></span>\n }\n <span class=\"banner__state-chip-label\">{{ StateLabel(State) }}</span>\n </span>\n }\n </div>\n\n @if (ShowMinimize) {\n <button type=\"button\" class=\"iconb banner__minimize--compact\" (click)=\"MinimizeRequested.emit()\"\n title=\"Hide the call view \u2014 the call stays live\"\n aria-label=\"Minimize the call view\">\n <i class=\"fa-solid fa-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n } @else {\n <div class=\"agent-orb\" [attr.data-state]=\"OrbState(State)\" aria-hidden=\"true\"></div>\n\n <div class=\"banner__id\">\n <span class=\"banner__name\">Voice for {{ AgentName }}</span>\n <span class=\"pill pill--sage\"><i class=\"fa-solid fa-feather\" aria-hidden=\"true\"></i> Co-Agent</span>\n @if (!HideState) {\n <span class=\"banner__for\">speaking as <b>{{ AgentName }}</b></span>\n }\n @if (ModelName) {\n <span class=\"banner__model\" [title]=\"'Realtime voice model: ' + ModelName\">\u00B7 {{ ModelName }}</span>\n }\n @if (ShowOpenSession) {\n <button type=\"button\" class=\"dev-link\" (click)=\"OpenSession()\"\n title=\"Open the agent session record\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n <span>Open session</span>\n </button>\n }\n </div>\n\n @if (!HideState) {\n <div class=\"banner__state\" [class.banner__state--busy]=\"IsBusy(State)\">\n @if (IsBusy(State)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <div class=\"waveform\" [class.waveform--idle]=\"State !== 'speaking'\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n }\n <span class=\"banner__state-label\">{{ StateLabel(State) }}</span>\n </div>\n }\n\n <!-- Disclosure-gated app-bar action cluster: STATE + WINDOW-CHROME only. Call actions\n (Mute / Captions / Details / Type / End) live in the bottom dock \u2014 the single home for\n each control. Captions & End were removed from here (they were exact duplicates of the\n dock's), and the standalone \"pure audio\" headphones was folded into the gear's Density =\n Simple. What remains up here is non-duplicated chrome: the gear (density + dev links) and\n Minimize. -->\n <div class=\"bar-actions\">\n @if (ShowGear) {\n <button type=\"button\" class=\"iconb\" [class.iconb--on]=\"GearOpen || DevMode\"\n (click)=\"ToggleGear($event)\"\n title=\"Interface density &amp; developer links\"\n [attr.aria-expanded]=\"GearOpen\">\n <i class=\"fa-solid fa-gear\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (ShowMinimize) {\n <button type=\"button\" class=\"iconb\" (click)=\"MinimizeRequested.emit()\"\n title=\"Hide the call view \u2014 the call stays live\"\n aria-label=\"Minimize the call view\">\n <i class=\"fa-solid fa-down-left-and-up-right-to-center\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- Gear popover: interface density (the disclosure escape hatch) + developer links. -->\n @if (GearOpen) {\n <div class=\"gear-pop\" (click)=\"$event.stopPropagation()\" role=\"menu\" aria-label=\"Interface settings\">\n <div class=\"gp-h\">Interface</div>\n @if (ShowDensityPicker) {\n <div class=\"gp-row\">\n <span class=\"gp-l\">Density</span>\n <div class=\"gp-seg\" role=\"radiogroup\" aria-label=\"Interface density\">\n @for (d of Densities; track d.Key) {\n <button type=\"button\" [class.on]=\"Density === d.Key\"\n (click)=\"SelectDensity(d.Key)\"\n [attr.aria-checked]=\"Density === d.Key\" role=\"radio\">{{ d.Label }}</button>\n }\n </div>\n </div>\n <div class=\"gp-note\">Simple returns to the pure-audio surface. Auto grows the console as you use it.</div>\n <div class=\"gp-div\"></div>\n }\n <button type=\"button\" class=\"gp-link\" (click)=\"ToggleDev()\" [attr.aria-pressed]=\"DevMode\">\n <i class=\"fa-solid\" [ngClass]=\"DevMode ? 'fa-toggle-on' : 'fa-toggle-off'\" aria-hidden=\"true\"></i>\n Developer links {{ DevMode ? 'on' : 'off' }}\n </button>\n </div>\n }\n }\n</div>\n", styles: [":host {\n display: block;\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n}\n\n/* The UNIFIED APP-BAR (Redesign A): orb \u00B7 name \u00B7 Co-Agent pill \u00B7 \"speaking as\" \u00B7\n state pill \u00B7 disclosure-gated action cluster (captions / gear / minimize / End). */\n.banner {\n position: relative; /* anchors the gear popover */\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 16px 10px 22px;\n border-bottom: 1px solid var(--mj-border-default);\n background: linear-gradient(180deg, color-mix(in srgb, var(--mj-brand-primary) 10%, transparent), transparent);\n flex-shrink: 0;\n}\n\n.banner__id {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n overflow: hidden;\n}\n.banner__name {\n font-size: 14.5px;\n font-weight: 700;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n flex-shrink: 0;\n}\n.banner__for {\n font-size: 11.5px;\n color: var(--mj-text-muted);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex-shrink: 5; /* gives way before the name/pill, after the model suffix */\n}\n.banner__for b {\n color: var(--mj-text-secondary);\n}\n/* Subtle realtime-model suffix (e.g. \"\u00B7 GPT Realtime 2\") in the identity line. */\n.banner__model {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex-shrink: 10; /* first to give way when the banner narrows */\n min-width: 0;\n}\n\n.banner__state {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 12px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n.banner__state--busy {\n background: var(--mj-status-warning-bg);\n border-color: var(--mj-status-warning);\n}\n.banner__state--busy .fa-spinner {\n color: var(--mj-status-warning);\n}\n.banner__state-label {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.banner__state--busy .banner__state-label {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n}\n\n/* ---------- Glowing agent orb ---------- */\n.agent-orb {\n --orb-size: 34px;\n width: var(--orb-size);\n height: var(--orb-size);\n border-radius: 50%;\n position: relative;\n flex-shrink: 0;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.agent-orb::after {\n content: \"\";\n position: absolute;\n inset: -4px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-accent, var(--mj-brand-primary)) 40%, transparent);\n animation: orb-ring 2.4s ease-out infinite;\n}\n.agent-orb[data-state=\"speaking\"] {\n animation: orb-bob 0.9s ease-in-out infinite;\n}\n.agent-orb[data-state=\"listening\"] {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.agent-orb[data-state=\"thinking\"] {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 26px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n\n@keyframes orb-ring { 0% { transform: scale(1); opacity: .6 } 100% { transform: scale(1.5); opacity: 0 } }\n@keyframes orb-bob { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-3px) } }\n\n/* ---------- Audio-reactive mode ----------\n The overlay's rAF meter loop writes --voice-out / --voice-in + data attributes on\n .call-overlay; custom properties INHERIT into this component and :host-context()\n reaches the ancestor's attributes \u2014 so the app-bar orb rides the SAME live signal as\n the hero (scaled subtler at app-bar size). Un-metered sessions keep the keyframes. */\n:host-context(.call-overlay[data-audio-live='true']) .agent-orb:not(.agent-orb--review) {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.14));\n transition: transform 70ms linear;\n}\n:host-context(.call-overlay[data-audio-live='true']) .agent-orb:not(.agent-orb--review)::after {\n animation: none;\n opacity: calc(0.15 + var(--voice-out, 0) * 0.85);\n}\n:host-context(.call-overlay[data-audio-live='true'][data-voice-dir='user']) .agent-orb:not(.agent-orb--review) {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n\n/* ---------- Voice activity waveform ---------- */\n.waveform {\n display: flex;\n align-items: center;\n gap: 3px;\n height: 18px;\n}\n.waveform span {\n width: 3px;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n animation: wave 1.1s ease-in-out infinite;\n}\n.waveform span:nth-child(1) { height: 30%; animation-delay: 0s }\n.waveform span:nth-child(2) { height: 65%; animation-delay: .1s }\n.waveform span:nth-child(3) { height: 100%; animation-delay: .2s }\n.waveform span:nth-child(4) { height: 55%; animation-delay: .3s }\n.waveform span:nth-child(5) { height: 85%; animation-delay: .15s }\n.waveform span:nth-child(6) { height: 40%; animation-delay: .25s }\n.waveform span:nth-child(7) { height: 70%; animation-delay: .05s }\n.waveform span:nth-child(8) { height: 35%; animation-delay: .35s }\n.waveform--idle span {\n animation: none;\n height: 18% !important;\n background: var(--mj-text-disabled);\n}\n@keyframes wave { 0%, 100% { transform: scaleY(.4) } 50% { transform: scaleY(1) } }\n\n.pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px;\n border-radius: var(--mj-radius-full, 9999px);\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid transparent;\n line-height: 1.4;\n}\n.pill i { font-size: 10px; }\n.pill--sage {\n background: var(--sage-bg);\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n border-color: var(--sage-border);\n}\n\n/* ---------- Session review variant ---------- */\n.banner--review {\n background: linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 10%, transparent), transparent);\n}\n/* Static orb: no pulse ring, dimmed glow \u2014 the session is history, nothing is live. */\n.agent-orb--review {\n box-shadow: 0 0 0 6px color-mix(in srgb, var(--mj-text-muted) 12%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-text-muted) 55%, var(--mj-bg-surface)),\n var(--mj-text-muted) 65%);\n}\n.agent-orb--review::after {\n animation: none;\n border-color: color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n}\n.pill--review {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-text-link, var(--mj-brand-primary));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n.pill--close-reason {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border-color: var(--mj-border-default);\n}\n.banner__range {\n flex-shrink: 3;\n}\n.banner__state--review {\n background: var(--mj-bg-surface-card);\n}\n.banner__state--review i {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n.banner__state-label--review {\n color: var(--mj-text-secondary);\n}\n\n/* Gear-gated developer link to the live agent session record */\n.dev-link {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 9px;\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: 10.5px;\n font-weight: 700;\n white-space: nowrap;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.dev-link:hover {\n color: var(--mj-text-link, var(--mj-brand-primary));\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.dev-link i {\n font-size: 9px;\n}\n\n/* ---------- App-bar action cluster (disclosure-gated; Redesign A) ---------- */\n.bar-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n.iconb {\n width: 34px;\n height: 34px;\n border-radius: var(--mj-radius-md, 8px);\n display: grid;\n place-items: center;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 13px;\n transition: all 140ms ease;\n}\n.iconb:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-hover);\n}\n.iconb--on {\n background: color-mix(in srgb, var(--mj-brand-primary) 16%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.bar-sep {\n width: 1px;\n height: 26px;\n background: var(--mj-border-default);\n}\n.end-pill {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n padding: 8px 18px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 700;\n white-space: nowrap;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px solid var(--mj-status-error);\n background: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n cursor: pointer;\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-status-error) 35%, transparent);\n transition: filter 140ms ease;\n}\n.end-pill:hover {\n filter: brightness(0.92);\n}\n.start-live-pill {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n padding: 8px 18px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 700;\n white-space: nowrap;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px solid var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, #fff);\n cursor: pointer;\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n transition: filter 140ms ease;\n}\n.start-live-pill:hover {\n filter: brightness(1.05);\n}\n\n/* ---------- Gear popover: density escape hatch + developer links ---------- */\n.gear-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 14px;\n z-index: 70;\n width: 268px;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-lg, 12px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n padding: 12px 14px 10px;\n animation: gear-pop-in 160ms ease;\n}\n@keyframes gear-pop-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.gp-h {\n font-size: 10.5px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n margin-bottom: 9px;\n}\n.gp-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.gp-l {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.gp-seg {\n display: inline-flex;\n flex: 1;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface);\n padding: 2px;\n gap: 2px;\n}\n.gp-seg button {\n flex: 1;\n border: 0;\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11px;\n font-weight: 700;\n padding: 4px 0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 120ms ease;\n}\n.gp-seg button.on {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, #fff);\n}\n.gp-note {\n font-size: 10.5px;\n color: var(--mj-text-muted);\n line-height: 1.45;\n margin-top: 8px;\n}\n.gp-div {\n height: 1px;\n background: var(--mj-border-default);\n margin: 10px -14px;\n}\n.gp-link {\n display: flex;\n width: 100%;\n align-items: center;\n gap: 9px;\n padding: 6px 2px;\n border: 0;\n background: transparent;\n color: var(--mj-text-secondary);\n font-family: inherit;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n text-align: left;\n}\n.gp-link:hover {\n color: var(--mj-text-primary);\n}\n.gp-link i {\n width: 16px;\n text-align: center;\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n.gp-link:hover i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n\n/* ============================================================= COMPACT (Calm Orb \u00B7 overlay)\n A tiny identity + state row only \u2014 the body owns the big orb + name + state, and the lean\n dock owns the actions. No duplicate orb / state pill / headphones / gear / expand here. */\n.banner--compact {\n gap: 10px;\n padding: 12px 14px;\n background: transparent;\n border-bottom: none;\n}\n.banner__id--compact {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 9px;\n overflow: hidden;\n}\n.banner__avatar {\n flex-shrink: 0;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 800;\n color: var(--mj-text-inverse);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-brand-primary) 60%, white), var(--mj-brand-primary) 65%);\n}\n.banner__name--compact {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: -0.01em;\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.banner__state-chip {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 4px 10px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n font-size: 11.5px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n.banner__state-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 130px;\n}\n.banner__state-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-status-success);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-status-success) 70%, transparent);\n}\n.banner__state-dot[data-state=\"speaking\"] {\n background: var(--mj-brand-primary);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 70%, transparent);\n animation: compact-dot-blink 0.9s infinite;\n}\n.banner__state-dot[data-state=\"thinking\"] {\n background: var(--mj-status-warning);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-status-warning) 70%, transparent);\n}\n@keyframes compact-dot-blink { 50% { opacity: 0.35; } }\n/* Minimize is the only control left in the compact bar \u2014 render it round + de-emphasized. */\n.banner__minimize--compact {\n flex-shrink: 0;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: transparent;\n border-color: transparent;\n color: var(--mj-text-muted);\n}\n.banner__minimize--compact:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-default);\n}\n"] }]
530
+ args: [{ standalone: true, selector: 'mj-realtime-agent-banner', imports: [CommonModule], template: "<div class=\"banner\" [class.banner--review]=\"ReviewMode\" [class.banner--compact]=\"Compact && !ReviewMode\">\n @if (ReviewMode) {\n <!-- SESSION REVIEW: static orb \u00B7 name \u00B7 review badge \u00B7 started\u2192closed \u00B7 close-reason chip,\n with the review's exit actions up here in the header bar (Start live leads, Close trails). -->\n <div class=\"agent-orb agent-orb--review\" aria-hidden=\"true\"></div>\n\n <div class=\"banner__id\">\n <span class=\"banner__name\">Voice for {{ AgentName }}</span>\n <span class=\"pill pill--review\"><i class=\"fa-solid fa-clock-rotate-left\" aria-hidden=\"true\"></i> Session review</span>\n @if (ReviewRangeLabel) {\n <span class=\"banner__for banner__range\">{{ ReviewRangeLabel }}</span>\n }\n @if (ReviewCloseReasonLabel) {\n <span class=\"pill pill--close-reason\" [title]=\"'Close reason: ' + ReviewCloseReason\">{{ ReviewCloseReasonLabel }}</span>\n }\n @if (ShowOpenSession) {\n <button type=\"button\" class=\"dev-link\" (click)=\"OpenSession()\"\n title=\"Open the agent session record\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n <span>Open session</span>\n </button>\n }\n </div>\n\n <div class=\"banner__state banner__state--review\">\n <i class=\"fa-solid fa-box-archive\" aria-hidden=\"true\"></i>\n <span class=\"banner__state-label banner__state-label--review\">Reviewing past session</span>\n </div>\n\n <div class=\"bar-actions\">\n @if (ShowStartLive) {\n <button type=\"button\" class=\"start-live-pill\" (click)=\"StartLiveRequested.emit()\"\n title=\"Resume this session as a new live call\">\n <i class=\"fa-solid fa-phone\" aria-hidden=\"true\"></i>\n <span>Start live session</span>\n </button>\n }\n <button type=\"button\" class=\"iconb\" (click)=\"CloseRequested.emit()\"\n title=\"Close review\" aria-label=\"Close session review\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @else if (Compact) {\n <!-- COMPACT (Calm Orb \u00B7 overlay): a tiny identity + state row only. No duplicate orb,\n no state pill, no headphones / gear / expand \u2014 the body shows the big orb + name +\n state, and the lean dock owns the actions. Minimize stays reachable here. -->\n <div class=\"banner__id banner__id--compact\">\n <span class=\"banner__avatar\" aria-hidden=\"true\">{{ AgentName.charAt(0) }}</span>\n <span class=\"banner__name banner__name--compact\">{{ AgentName }}</span>\n @if (!HideState) {\n <span class=\"banner__state-chip\">\n @if (IsBusy(State)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <span class=\"banner__state-dot\" [attr.data-state]=\"OrbState(State)\" aria-hidden=\"true\"></span>\n }\n <span class=\"banner__state-chip-label\">{{ StateLabel(State) }}</span>\n </span>\n }\n </div>\n\n @if (ShowMinimize) {\n <button type=\"button\" class=\"iconb banner__minimize--compact\" (click)=\"MinimizeRequested.emit()\"\n title=\"Hide the call view \u2014 the call stays live\"\n aria-label=\"Minimize the call view\">\n <i class=\"fa-solid fa-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n } @else {\n <div class=\"agent-orb\" [attr.data-state]=\"OrbState(State)\" aria-hidden=\"true\"></div>\n\n <div class=\"banner__id\">\n <span class=\"banner__name\">Voice for {{ AgentName }}</span>\n <span class=\"pill pill--sage\"><i class=\"fa-solid fa-feather\" aria-hidden=\"true\"></i> Co-Agent</span>\n @if (!HideState) {\n <span class=\"banner__for\">speaking as <b>{{ AgentName }}</b></span>\n }\n @if (ModelName) {\n <span class=\"banner__model\" [title]=\"'Realtime voice model: ' + ModelName\">\u00B7 {{ ModelName }}</span>\n }\n @if (ShowOpenSession) {\n <button type=\"button\" class=\"dev-link\" (click)=\"OpenSession()\"\n title=\"Open the agent session record\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n <span>Open session</span>\n </button>\n }\n </div>\n\n @if (!HideState) {\n <div class=\"banner__state\" [class.banner__state--busy]=\"IsBusy(State)\">\n @if (IsBusy(State)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <div class=\"waveform\" [class.waveform--idle]=\"State !== 'speaking'\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n }\n <span class=\"banner__state-label\">{{ StateLabel(State) }}</span>\n </div>\n }\n\n <!-- Disclosure-gated app-bar action cluster: STATE + WINDOW-CHROME only. Call actions\n (Mute / Captions / Details / Type / End) live in the bottom dock \u2014 the single home for\n each control. Captions & End were removed from here (they were exact duplicates of the\n dock's), and the standalone \"pure audio\" headphones was folded into the gear's Density =\n Simple. What remains up here is non-duplicated chrome: the gear (density + dev links) and\n Minimize. -->\n <div class=\"bar-actions\">\n @if (ShowGear) {\n <button type=\"button\" class=\"iconb\" [class.iconb--on]=\"GearOpen || DevMode\"\n (click)=\"ToggleGear($event)\"\n title=\"Interface density &amp; developer links\"\n [attr.aria-expanded]=\"GearOpen\">\n <i class=\"fa-solid fa-gear\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (ShowMinimize) {\n <button type=\"button\" class=\"iconb\" (click)=\"MinimizeRequested.emit()\"\n title=\"Hide the call view \u2014 the call stays live\"\n aria-label=\"Minimize the call view\">\n <i class=\"fa-solid fa-down-left-and-up-right-to-center\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- Gear popover: interface density (the disclosure escape hatch) + developer links. -->\n @if (GearOpen) {\n <div class=\"gear-pop\" (click)=\"$event.stopPropagation()\" role=\"menu\" aria-label=\"Interface settings\">\n <div class=\"gp-h\">Interface</div>\n @if (ShowDensityPicker) {\n <div class=\"gp-row\">\n <span class=\"gp-l\">Density</span>\n <div class=\"gp-seg\" role=\"radiogroup\" aria-label=\"Interface density\">\n @for (d of Densities; track d.Key) {\n <button type=\"button\" [class.on]=\"Density === d.Key\"\n (click)=\"SelectDensity(d.Key)\"\n [attr.aria-checked]=\"Density === d.Key\" role=\"radio\">{{ d.Label }}</button>\n }\n </div>\n </div>\n <div class=\"gp-note\">Simple returns to the pure-audio surface. Auto grows the console as you use it.</div>\n }\n @if (ShowDensityPicker && AllowDevLinks) {\n <div class=\"gp-div\"></div>\n }\n @if (AllowDevLinks) {\n <button type=\"button\" class=\"gp-link\" (click)=\"ToggleDev()\" [attr.aria-pressed]=\"DevMode\">\n <i class=\"fa-solid\" [ngClass]=\"DevMode ? 'fa-toggle-on' : 'fa-toggle-off'\" aria-hidden=\"true\"></i>\n Developer links {{ DevMode ? 'on' : 'off' }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [":host {\n display: block;\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n}\n\n/* The UNIFIED APP-BAR (Redesign A): orb \u00B7 name \u00B7 Co-Agent pill \u00B7 \"speaking as\" \u00B7\n state pill \u00B7 disclosure-gated action cluster (captions / gear / minimize / End). */\n.banner {\n position: relative; /* anchors the gear popover */\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 16px 10px 22px;\n border-bottom: 1px solid var(--mj-border-default);\n background: linear-gradient(180deg, color-mix(in srgb, var(--mj-brand-primary) 10%, transparent), transparent);\n flex-shrink: 0;\n}\n\n.banner__id {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n overflow: hidden;\n}\n.banner__name {\n font-size: 14.5px;\n font-weight: 700;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n flex-shrink: 0;\n}\n.banner__for {\n font-size: 11.5px;\n color: var(--mj-text-muted);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex-shrink: 5; /* gives way before the name/pill, after the model suffix */\n}\n.banner__for b {\n color: var(--mj-text-secondary);\n}\n/* Subtle realtime-model suffix (e.g. \"\u00B7 GPT Realtime 2\") in the identity line. */\n.banner__model {\n font-size: 11px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex-shrink: 10; /* first to give way when the banner narrows */\n min-width: 0;\n}\n\n.banner__state {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 12px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n.banner__state--busy {\n background: var(--mj-status-warning-bg);\n border-color: var(--mj-status-warning);\n}\n.banner__state--busy .fa-spinner {\n color: var(--mj-status-warning);\n}\n.banner__state-label {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.banner__state--busy .banner__state-label {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n}\n\n/* ---------- Glowing agent orb ---------- */\n.agent-orb {\n --orb-size: 34px;\n width: var(--orb-size);\n height: var(--orb-size);\n border-radius: 50%;\n position: relative;\n flex-shrink: 0;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.agent-orb::after {\n content: \"\";\n position: absolute;\n inset: -4px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-accent, var(--mj-brand-primary)) 40%, transparent);\n animation: orb-ring 2.4s ease-out infinite;\n}\n.agent-orb[data-state=\"speaking\"] {\n animation: orb-bob 0.9s ease-in-out infinite;\n}\n.agent-orb[data-state=\"listening\"] {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.agent-orb[data-state=\"thinking\"] {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 26px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n\n@keyframes orb-ring { 0% { transform: scale(1); opacity: .6 } 100% { transform: scale(1.5); opacity: 0 } }\n@keyframes orb-bob { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-3px) } }\n\n/* ---------- Audio-reactive mode ----------\n The overlay's rAF meter loop writes --voice-out / --voice-in + data attributes on\n .call-overlay; custom properties INHERIT into this component and :host-context()\n reaches the ancestor's attributes \u2014 so the app-bar orb rides the SAME live signal as\n the hero (scaled subtler at app-bar size). Un-metered sessions keep the keyframes. */\n:host-context(.call-overlay[data-audio-live='true']) .agent-orb:not(.agent-orb--review) {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.14));\n transition: transform 70ms linear;\n}\n:host-context(.call-overlay[data-audio-live='true']) .agent-orb:not(.agent-orb--review)::after {\n animation: none;\n opacity: calc(0.15 + var(--voice-out, 0) * 0.85);\n}\n:host-context(.call-overlay[data-audio-live='true'][data-voice-dir='user']) .agent-orb:not(.agent-orb--review) {\n box-shadow:\n 0 0 0 6px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 30px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n\n/* ---------- Voice activity waveform ---------- */\n.waveform {\n display: flex;\n align-items: center;\n gap: 3px;\n height: 18px;\n}\n.waveform span {\n width: 3px;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n animation: wave 1.1s ease-in-out infinite;\n}\n.waveform span:nth-child(1) { height: 30%; animation-delay: 0s }\n.waveform span:nth-child(2) { height: 65%; animation-delay: .1s }\n.waveform span:nth-child(3) { height: 100%; animation-delay: .2s }\n.waveform span:nth-child(4) { height: 55%; animation-delay: .3s }\n.waveform span:nth-child(5) { height: 85%; animation-delay: .15s }\n.waveform span:nth-child(6) { height: 40%; animation-delay: .25s }\n.waveform span:nth-child(7) { height: 70%; animation-delay: .05s }\n.waveform span:nth-child(8) { height: 35%; animation-delay: .35s }\n.waveform--idle span {\n animation: none;\n height: 18% !important;\n background: var(--mj-text-disabled);\n}\n@keyframes wave { 0%, 100% { transform: scaleY(.4) } 50% { transform: scaleY(1) } }\n\n.pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px;\n border-radius: var(--mj-radius-full, 9999px);\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid transparent;\n line-height: 1.4;\n}\n.pill i { font-size: 10px; }\n.pill--sage {\n background: var(--sage-bg);\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n border-color: var(--sage-border);\n}\n\n/* ---------- Session review variant ---------- */\n.banner--review {\n background: linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 10%, transparent), transparent);\n}\n/* Static orb: no pulse ring, dimmed glow \u2014 the session is history, nothing is live. */\n.agent-orb--review {\n box-shadow: 0 0 0 6px color-mix(in srgb, var(--mj-text-muted) 12%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-text-muted) 55%, var(--mj-bg-surface)),\n var(--mj-text-muted) 65%);\n}\n.agent-orb--review::after {\n animation: none;\n border-color: color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n}\n.pill--review {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-text-link, var(--mj-brand-primary));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n.pill--close-reason {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border-color: var(--mj-border-default);\n}\n.banner__range {\n flex-shrink: 3;\n}\n.banner__state--review {\n background: var(--mj-bg-surface-card);\n}\n.banner__state--review i {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n.banner__state-label--review {\n color: var(--mj-text-secondary);\n}\n\n/* Gear-gated developer link to the live agent session record */\n.dev-link {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 9px;\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: 10.5px;\n font-weight: 700;\n white-space: nowrap;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.dev-link:hover {\n color: var(--mj-text-link, var(--mj-brand-primary));\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.dev-link i {\n font-size: 9px;\n}\n\n/* ---------- App-bar action cluster (disclosure-gated; Redesign A) ---------- */\n.bar-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n.iconb {\n width: 34px;\n height: 34px;\n border-radius: var(--mj-radius-md, 8px);\n display: grid;\n place-items: center;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 13px;\n transition: all 140ms ease;\n}\n.iconb:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-hover);\n}\n.iconb--on {\n background: color-mix(in srgb, var(--mj-brand-primary) 16%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.bar-sep {\n width: 1px;\n height: 26px;\n background: var(--mj-border-default);\n}\n.end-pill {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n padding: 8px 18px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 700;\n white-space: nowrap;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px solid var(--mj-status-error);\n background: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n cursor: pointer;\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-status-error) 35%, transparent);\n transition: filter 140ms ease;\n}\n.end-pill:hover {\n filter: brightness(0.92);\n}\n.start-live-pill {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n padding: 8px 18px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 700;\n white-space: nowrap;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px solid var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, #fff);\n cursor: pointer;\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n transition: filter 140ms ease;\n}\n.start-live-pill:hover {\n filter: brightness(1.05);\n}\n\n/* ---------- Gear popover: density escape hatch + developer links ---------- */\n.gear-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 14px;\n z-index: 70;\n width: 268px;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-lg, 12px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n padding: 12px 14px 10px;\n animation: gear-pop-in 160ms ease;\n}\n@keyframes gear-pop-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.gp-h {\n font-size: 10.5px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--mj-text-muted);\n margin-bottom: 9px;\n}\n.gp-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.gp-l {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.gp-seg {\n display: inline-flex;\n flex: 1;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface);\n padding: 2px;\n gap: 2px;\n}\n.gp-seg button {\n flex: 1;\n border: 0;\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11px;\n font-weight: 700;\n padding: 4px 0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 120ms ease;\n}\n.gp-seg button.on {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, #fff);\n}\n.gp-note {\n font-size: 10.5px;\n color: var(--mj-text-muted);\n line-height: 1.45;\n margin-top: 8px;\n}\n.gp-div {\n height: 1px;\n background: var(--mj-border-default);\n margin: 10px -14px;\n}\n.gp-link {\n display: flex;\n width: 100%;\n align-items: center;\n gap: 9px;\n padding: 6px 2px;\n border: 0;\n background: transparent;\n color: var(--mj-text-secondary);\n font-family: inherit;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n text-align: left;\n}\n.gp-link:hover {\n color: var(--mj-text-primary);\n}\n.gp-link i {\n width: 16px;\n text-align: center;\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n.gp-link:hover i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n\n/* ============================================================= COMPACT (Calm Orb \u00B7 overlay)\n A tiny identity + state row only \u2014 the body owns the big orb + name + state, and the lean\n dock owns the actions. No duplicate orb / state pill / headphones / gear / expand here. */\n.banner--compact {\n gap: 10px;\n padding: 12px 14px;\n background: transparent;\n border-bottom: none;\n}\n.banner__id--compact {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 9px;\n overflow: hidden;\n}\n.banner__avatar {\n flex-shrink: 0;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 800;\n color: var(--mj-text-inverse);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-brand-primary) 60%, white), var(--mj-brand-primary) 65%);\n}\n.banner__name--compact {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: -0.01em;\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.banner__state-chip {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 4px 10px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n font-size: 11.5px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n.banner__state-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 130px;\n}\n.banner__state-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-status-success);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-status-success) 70%, transparent);\n}\n.banner__state-dot[data-state=\"speaking\"] {\n background: var(--mj-brand-primary);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 70%, transparent);\n animation: compact-dot-blink 0.9s infinite;\n}\n.banner__state-dot[data-state=\"thinking\"] {\n background: var(--mj-status-warning);\n box-shadow: 0 0 8px color-mix(in srgb, var(--mj-status-warning) 70%, transparent);\n}\n@keyframes compact-dot-blink { 50% { opacity: 0.35; } }\n/* Minimize is the only control left in the compact bar \u2014 render it round + de-emphasized. */\n.banner__minimize--compact {\n flex-shrink: 0;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: transparent;\n border-color: transparent;\n color: var(--mj-text-muted);\n}\n.banner__minimize--compact:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-default);\n}\n"] }]
499
531
  }], null, { State: [{
500
532
  type: Input,
501
533
  args: [{ required: true }]
@@ -525,6 +557,8 @@ export class RealtimeAgentBannerComponent {
525
557
  type: Input
526
558
  }], ShowDensityPicker: [{
527
559
  type: Input
560
+ }], AllowDevLinks: [{
561
+ type: Input
528
562
  }], ShowMinimize: [{
529
563
  type: Input
530
564
  }], ShowStartLive: [{