@memberjunction/ng-conversations 6.2.0-edge.6 → 6.2.0-edge.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
- package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
- package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
- package/dist/lib/components/message/message-input.component.d.ts +80 -53
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +381 -277
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +18 -1
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +548 -426
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +8 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +40 -2
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +7 -2
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +2 -1
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +9 -4
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +5 -4
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +2 -2
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +11 -7
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/active-tasks.service.d.ts +5 -0
- package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
- package/dist/lib/services/active-tasks.service.js +16 -0
- package/dist/lib/services/active-tasks.service.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +7 -1
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-permission.service.js +2 -0
- package/dist/lib/services/artifact-permission.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -0
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
- package/dist/lib/services/collection-permission.service.js +1 -0
- package/dist/lib/services/collection-permission.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +1 -4
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +7 -45
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +1 -0
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
- package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
- package/dist/lib/utils/agent-handoff-map.js +270 -0
- package/dist/lib/utils/agent-handoff-map.js.map +1 -0
- package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
- package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
- package/dist/lib/utils/agent-turn-routing.js +147 -6
- package/dist/lib/utils/agent-turn-routing.js.map +1 -1
- package/dist/lib/utils/decision-routing.d.ts +17 -9
- package/dist/lib/utils/decision-routing.d.ts.map +1 -1
- package/dist/lib/utils/decision-routing.js +23 -14
- package/dist/lib/utils/decision-routing.js.map +1 -1
- package/dist/lib/utils/thread-replies.d.ts +34 -0
- package/dist/lib/utils/thread-replies.d.ts.map +1 -0
- package/dist/lib/utils/thread-replies.js +49 -0
- package/dist/lib/utils/thread-replies.js.map +1 -0
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +36 -36
|
@@ -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
|
-
/**
|
|
70
|
-
|
|
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
|
|
75
|
-
*
|
|
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
|
|
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
|
|
206
|
-
i0.ɵɵelementStart(0, "button",
|
|
207
|
-
i0.ɵɵlistener("click", function RealtimeAgentBannerComponent_Conditional_3_Conditional_14_Conditional_3_For_5_Template_button_click_0_listener() { const
|
|
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
|
|
211
|
+
const d_r10 = ctx.$implicit;
|
|
212
212
|
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
213
|
-
i0.ɵɵclassProp("on", ctx_r1.Density ===
|
|
214
|
-
i0.ɵɵattribute("aria-checked", ctx_r1.Density ===
|
|
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(
|
|
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,
|
|
242
|
-
i0.ɵɵ
|
|
243
|
-
i0.ɵɵ
|
|
244
|
-
i0.ɵɵ
|
|
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.ɵɵ
|
|
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.ɵɵ
|
|
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,
|
|
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
|
-
/**
|
|
365
|
-
|
|
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
|
|
370
|
-
*
|
|
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"], [
|
|
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 & 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 & 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: [{
|