@memberjunction/ng-conversations 5.47.0 → 5.49.0
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/README.md +37 -0
- package/dist/lib/components/composer/ai-composer.component.d.ts +8 -2
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +16 -3
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +90 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +322 -99
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +16 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.js +62 -24
- package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +48 -1
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +146 -6
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +13 -1
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +307 -235
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +18 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +48 -4
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +2 -2
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +2 -2
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
- package/dist/lib/services/composer-draft-store.d.ts +52 -0
- package/dist/lib/services/composer-draft-store.d.ts.map +1 -0
- package/dist/lib/services/composer-draft-store.js +123 -0
- package/dist/lib/services/composer-draft-store.js.map +1 -0
- package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.js +5 -1
- package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +2 -1
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +29 -29
package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-evidence-playback.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.ts","../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy,\n Output, ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/** Number of bars rendered in the synthetic waveform behind the scrubber. */\nconst WAVEFORM_BARS = 64;\n\n/**\n * The MINIMAL structural shape of a transcript turn this player needs — exactly the fields it reads\n * for the click-to-seek transcript and the playhead-sync highlight. Deliberately NOT the full\n * `MJConversationDetailEntity`: that keeps the player reusable by ANY caller that can supply these\n * fields (the in-conversation session-review overlay passes its lighter `RealtimeSessionReviewTurn`;\n * the Realtime Recordings dashboard passes full `MJConversationDetailEntity` rows — both satisfy this\n * widened shape structurally).\n *\n * Field unions/nullability are intentionally WIDER than the generated entity so an\n * `MJConversationDetailEntity[]` remains assignable here.\n */\nexport interface EvidencePlaybackTurn {\n /** Stable identity for the `@for` track. */\n ID: string;\n /** Speaker. The player only distinguishes `'User'` (human) from everything else (the agent). */\n Role: 'User' | 'AI' | 'Assistant' | 'Error' | string;\n /** The transcript text shown on the turn. */\n Message: string | null;\n /** Persisted turn time — fallback offset origin when the turn has no `UtteranceStartMs`. */\n __mj_CreatedAt: Date | null;\n /**\n * Precise media-relative start (ms from recording t0). `null`/absent when the driver supplied no\n * timing — the player falls back to (`__mj_CreatedAt` − `RecordingStartedAt`).\n */\n UtteranceStartMs?: number | null;\n /** Precise media-relative end (ms from recording t0). `null`/absent → derived from the next turn / duration. */\n UtteranceEndMs?: number | null;\n}\n\n/**\n * TIME-ALIGNED EVIDENCE PLAYBACK (`mj-realtime-evidence-playback`) — replays a realtime session's\n * recorded audio next to its transcript, with the two kept in sync. The audio gets a custom transport\n * (play/pause, a synthetic waveform scrubber, time/duration readout); the transcript is a scrollable\n * list of clickable turns. Clicking a turn seeks the audio to that turn's start and plays; as the audio\n * plays, the turn whose `[UtteranceStartMs, UtteranceEndMs]` window contains the playhead is highlighted.\n *\n * Timestamps come from {@link EvidencePlaybackTurn.UtteranceStartMs}; when a turn has none, the\n * component falls back to (`__mj_CreatedAt` − {@link RecordingStartedAt}). Generic + Router-free: all\n * state arrives via inputs, the only output is {@link TurnSeek}.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-evidence-playback',\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-evidence-playback.component.html',\n styleUrls: ['./realtime-evidence-playback.component.scss'],\n})\nexport class RealtimeEvidencePlaybackComponent implements OnDestroy {\n /** The session's transcript turns, in chronological order. */\n @Input()\n set Turns(value: EvidencePlaybackTurn[] | null) {\n this._turns = value ?? [];\n this.cdr.markForCheck();\n }\n get Turns(): EvidencePlaybackTurn[] {\n return this._turns;\n }\n private _turns: EvidencePlaybackTurn[] = [];\n\n /** The recorded session audio URL, or `null` when no recording is available. */\n @Input()\n set AudioUrl(value: string | null) {\n if (value === this._audioUrl) {\n return;\n }\n this._audioUrl = value;\n this.CurrentMs = 0;\n this.DurationMs = 0;\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n get AudioUrl(): string | null {\n return this._audioUrl;\n }\n private _audioUrl: string | null = null;\n\n /** When the recording started, used to derive a turn's offset when it has no `UtteranceStartMs`. */\n @Input() RecordingStartedAt: Date | null = null;\n\n /** Display name of the agent — labels and colors AI turns. */\n @Input() AgentName = 'Agent';\n\n /** Emitted when the user clicks a transcript turn (to seek the audio to it). */\n @Output() TurnSeek = new EventEmitter<EvidencePlaybackTurn>();\n\n /** Current playhead position in ms (driven by the audio element's `timeupdate`). */\n public CurrentMs = 0;\n /** Total audio duration in ms (set on `loadedmetadata`). */\n public DurationMs = 0;\n /** Whether the audio is currently playing. */\n public IsPlaying = false;\n\n /** The synthetic waveform bar heights (stable per AudioUrl). */\n public readonly WaveformBars: number[] = this.buildWaveform();\n\n @ViewChild('audio') private audioRef?: ElementRef<HTMLAudioElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n ngOnDestroy(): void {\n this.audioRef?.nativeElement.pause();\n }\n\n /** The `<audio>` element, or `null` before the view initializes / when there's no audio. */\n private get audioEl(): HTMLAudioElement | null {\n return this.audioRef?.nativeElement ?? null;\n }\n\n /** Playhead progress as a fraction (0..1), for the scrubber fill + waveform reveal. */\n public get Progress(): number {\n return this.DurationMs > 0 ? Math.min(1, this.CurrentMs / this.DurationMs) : 0;\n }\n\n /** Toggles audio play / pause. */\n public TogglePlay(): void {\n const el = this.audioEl;\n if (!el) {\n return;\n }\n if (el.paused) {\n void el.play().catch(() => undefined);\n } else {\n el.pause();\n }\n }\n\n /** Seeks to a fraction of the duration (scrubber click / keyboard). */\n public SeekToFraction(fraction: number): void {\n const el = this.audioEl;\n if (!el || this.DurationMs <= 0) {\n return;\n }\n el.currentTime = (Math.max(0, Math.min(1, fraction)) * this.DurationMs) / 1000;\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n\n /** Scrubber pointer handler — maps the click X within the track to a seek fraction. */\n public OnScrubberClick(event: MouseEvent, track: HTMLElement): void {\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) {\n return;\n }\n this.SeekToFraction((event.clientX - rect.left) / rect.width);\n }\n\n /** Scrubber keyboard handler — arrows nudge ±5s, Home/End jump to ends, Space toggles play. */\n public OnScrubberKeydown(event: KeyboardEvent): void {\n const stepMs = 5000;\n switch (event.key) {\n case 'ArrowRight':\n event.preventDefault();\n this.seekByMs(stepMs);\n break;\n case 'ArrowLeft':\n event.preventDefault();\n this.seekByMs(-stepMs);\n break;\n case 'Home':\n event.preventDefault();\n this.SeekToFraction(0);\n break;\n case 'End':\n event.preventDefault();\n this.SeekToFraction(1);\n break;\n case ' ':\n case 'Enter':\n event.preventDefault();\n this.TogglePlay();\n break;\n default:\n break;\n }\n }\n\n /** Clicking a transcript turn → seek the audio to it, play, and emit {@link TurnSeek}. */\n public SeekToTurn(turn: EvidencePlaybackTurn): void {\n const startMs = this.turnStartMs(turn);\n const el = this.audioEl;\n if (el && this.DurationMs > 0) {\n el.currentTime = Math.max(0, startMs / 1000);\n this.CurrentMs = startMs;\n void el.play().catch(() => undefined);\n }\n this.TurnSeek.emit(turn);\n this.cdr.markForCheck();\n }\n\n /** True when the playhead falls within this turn's utterance window (drives the active highlight). */\n public IsTurnActive(turn: EvidencePlaybackTurn): boolean {\n if (!this.IsPlaying && this.CurrentMs === 0) {\n return false;\n }\n const start = this.turnStartMs(turn);\n const end = this.turnEndMs(turn);\n return this.CurrentMs >= start && this.CurrentMs < end;\n }\n\n /** Whether a turn is from the human user (vs. the agent) — drives the speaker color + label. */\n public IsUserTurn(turn: EvidencePlaybackTurn): boolean {\n return turn.Role === 'User';\n }\n\n /** The speaker label for a turn. */\n public SpeakerLabel(turn: EvidencePlaybackTurn): string {\n return this.IsUserTurn(turn) ? 'You' : this.AgentName;\n }\n\n /** The mm:ss timestamp shown on a turn (its start offset within the recording). */\n public TurnTimestamp(turn: EvidencePlaybackTurn): string {\n return this.formatMs(this.turnStartMs(turn));\n }\n\n /** mm:ss of the current playhead. */\n public get CurrentLabel(): string {\n return this.formatMs(this.CurrentMs);\n }\n\n /** mm:ss of the total duration. */\n public get DurationLabel(): string {\n return this.formatMs(this.DurationMs);\n }\n\n /** Stable `@for` track for the transcript. */\n public TrackByTurn(_index: number, turn: EvidencePlaybackTurn): string {\n return turn.ID;\n }\n\n // ----- audio element event handlers (bound in the template) -------------------------------\n\n /** `loadedmetadata` → capture the duration. */\n public OnLoadedMetadata(): void {\n const el = this.audioEl;\n this.DurationMs = el && Number.isFinite(el.duration) ? el.duration * 1000 : 0;\n this.cdr.markForCheck();\n }\n\n /** `timeupdate` → advance the playhead + re-evaluate the active turn. */\n public OnTimeUpdate(): void {\n const el = this.audioEl;\n if (el) {\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n }\n\n /** `play` / `pause` / `ended` → reflect the transport state. */\n public OnPlay(): void {\n this.IsPlaying = true;\n this.cdr.markForCheck();\n }\n public OnPause(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n public OnEnded(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n\n // ----- helpers ----------------------------------------------------------------------------\n\n /** Nudges the playhead by `deltaMs`, clamped to the duration. */\n private seekByMs(deltaMs: number): void {\n if (this.DurationMs <= 0) {\n return;\n }\n this.SeekToFraction((this.CurrentMs + deltaMs) / this.DurationMs);\n }\n\n /**\n * A turn's start offset (ms) within the recording: prefer {@link EvidencePlaybackTurn.UtteranceStartMs};\n * fall back to (`__mj_CreatedAt` − {@link RecordingStartedAt}); 0 when neither is available.\n */\n private turnStartMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceStartMs != null) {\n return Math.max(0, turn.UtteranceStartMs);\n }\n if (this.RecordingStartedAt && turn.__mj_CreatedAt) {\n return Math.max(0, turn.__mj_CreatedAt.getTime() - this.RecordingStartedAt.getTime());\n }\n return 0;\n }\n\n /**\n * A turn's end offset (ms): prefer {@link EvidencePlaybackTurn.UtteranceEndMs}; otherwise the\n * next turn's start; otherwise the recording duration (or start + a small window when duration is unknown).\n */\n private turnEndMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceEndMs != null) {\n return Math.max(this.turnStartMs(turn), turn.UtteranceEndMs);\n }\n const idx = this._turns.indexOf(turn);\n const next = idx >= 0 ? this._turns[idx + 1] : undefined;\n if (next) {\n return this.turnStartMs(next);\n }\n return this.DurationMs > 0 ? this.DurationMs : this.turnStartMs(turn) + 4000;\n }\n\n /** Formats a ms offset as mm:ss (clamped at 0). */\n private formatMs(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000));\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n /** Builds a stable pseudo-random waveform (deterministic, no per-render churn). */\n private buildWaveform(): number[] {\n const bars: number[] = [];\n for (let i = 0; i < WAVEFORM_BARS; i++) {\n // A smooth, repeatable envelope — looks like audio without needing to decode any.\n const base = Math.sin(i * 0.5) * 0.5 + 0.5;\n const detail = Math.sin(i * 1.7) * 0.25 + 0.25;\n bars.push(Math.max(0.12, Math.min(1, base * 0.7 + detail * 0.5)));\n }\n return bars;\n }\n}\n","<div class=\"evp\">\n <!-- ---------- Audio transport ---------- -->\n <div class=\"evp-player\">\n @if (AudioUrl) {\n <audio\n #audio\n [src]=\"AudioUrl\"\n preload=\"metadata\"\n (loadedmetadata)=\"OnLoadedMetadata()\"\n (timeupdate)=\"OnTimeUpdate()\"\n (play)=\"OnPlay()\"\n (pause)=\"OnPause()\"\n (ended)=\"OnEnded()\"\n ></audio>\n\n <button\n type=\"button\"\n class=\"evp-play\"\n [attr.aria-label]=\"IsPlaying ? 'Pause recording' : 'Play recording'\"\n [title]=\"IsPlaying ? 'Pause' : 'Play'\"\n (click)=\"TogglePlay()\"\n >\n <i class=\"fa-solid\" [class.fa-pause]=\"IsPlaying\" [class.fa-play]=\"!IsPlaying\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"evp-transport\">\n <div\n #track\n class=\"evp-scrubber\"\n role=\"slider\"\n tabindex=\"0\"\n aria-label=\"Seek through the recording\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"100\"\n [attr.aria-valuenow]=\"(Progress * 100) | number: '1.0-0'\"\n [attr.aria-valuetext]=\"CurrentLabel + ' of ' + DurationLabel\"\n (click)=\"OnScrubberClick($event, track)\"\n (keydown)=\"OnScrubberKeydown($event)\"\n >\n <div class=\"evp-waveform\" aria-hidden=\"true\">\n @for (bar of WaveformBars; track $index) {\n <span\n class=\"evp-bar\"\n [class.evp-bar--played]=\"($index / WaveformBars.length) <= Progress\"\n [style.height.%]=\"bar * 100\"\n ></span>\n }\n </div>\n <div class=\"evp-progress\" [style.width.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n <div class=\"evp-thumb\" [style.left.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"evp-time\">\n <span class=\"evp-time__current\">{{ CurrentLabel }}</span>\n <span class=\"evp-time__sep\">/</span>\n <span class=\"evp-time__total\">{{ DurationLabel }}</span>\n </div>\n </div>\n } @else {\n <div class=\"evp-noaudio\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <span>No audio recording is available for this session.</span>\n </div>\n }\n </div>\n\n <!-- ---------- Transcript ---------- -->\n <div class=\"evp-transcript\" role=\"list\" aria-label=\"Session transcript\">\n @if (Turns.length === 0) {\n <div class=\"evp-transcript-empty\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n <p>No transcript turns to show.</p>\n </div>\n } @else {\n @for (turn of Turns; track TrackByTurn($index, turn)) {\n <button\n type=\"button\"\n class=\"evp-turn\"\n role=\"listitem\"\n tabindex=\"0\"\n [class.evp-turn--user]=\"IsUserTurn(turn)\"\n [class.evp-turn--agent]=\"!IsUserTurn(turn)\"\n [class.evp-turn--active]=\"IsTurnActive(turn)\"\n [attr.aria-label]=\"SpeakerLabel(turn) + ' at ' + TurnTimestamp(turn) + ': ' + turn.Message\"\n [attr.aria-current]=\"IsTurnActive(turn) ? 'true' : null\"\n (click)=\"SeekToTurn(turn)\"\n >\n <div class=\"evp-turn__meta\">\n <span class=\"evp-turn__speaker\">{{ SpeakerLabel(turn) }}</span>\n <span class=\"evp-turn__time\">{{ TurnTimestamp(turn) }}</span>\n </div>\n <p class=\"evp-turn__text\">{{ turn.Message }}</p>\n </button>\n }\n }\n </div>\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-evidence-playback.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.ts","../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy,\n Output, ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/** Number of bars rendered in the synthetic waveform behind the scrubber. */\nconst WAVEFORM_BARS = 64;\n\n/**\n * The MINIMAL structural shape of a transcript turn this player needs — exactly the fields it reads\n * for the click-to-seek transcript and the playhead-sync highlight. Deliberately NOT the full\n * `MJConversationDetailEntity`: that keeps the player reusable by ANY caller that can supply these\n * fields (the in-conversation session-review overlay passes its lighter `RealtimeSessionReviewTurn`;\n * the Realtime Recordings dashboard passes full `MJConversationDetailEntity` rows — both satisfy this\n * widened shape structurally).\n *\n * Field unions/nullability are intentionally WIDER than the generated entity so an\n * `MJConversationDetailEntity[]` remains assignable here.\n */\nexport interface EvidencePlaybackTurn {\n /** Stable identity for the `@for` track. */\n ID: string;\n /** Speaker. The player only distinguishes `'User'` (human) from everything else (the agent). */\n Role: 'User' | 'AI' | 'Assistant' | 'Error' | string;\n /** The transcript text shown on the turn. */\n Message: string | null;\n /** Persisted turn time — fallback offset origin when the turn has no `UtteranceStartMs`. */\n __mj_CreatedAt: Date | null;\n /**\n * Precise media-relative start (ms from recording t0). `null`/absent when the driver supplied no\n * timing — the player falls back to (`__mj_CreatedAt` − `RecordingStartedAt`).\n */\n UtteranceStartMs?: number | null;\n /** Precise media-relative end (ms from recording t0). `null`/absent → derived from the next turn / duration. */\n UtteranceEndMs?: number | null;\n}\n\n/**\n * TIME-ALIGNED EVIDENCE PLAYBACK (`mj-realtime-evidence-playback`) — replays a realtime session's\n * recorded audio next to its transcript, with the two kept in sync. The audio gets a custom transport\n * (play/pause, a synthetic waveform scrubber, time/duration readout); the transcript is a scrollable\n * list of clickable turns. Clicking a turn seeks the audio to that turn's start and plays; as the audio\n * plays, the turn whose `[UtteranceStartMs, UtteranceEndMs]` window contains the playhead is highlighted.\n *\n * Timestamps come from {@link EvidencePlaybackTurn.UtteranceStartMs}; when a turn has none, the\n * component falls back to (`__mj_CreatedAt` − {@link RecordingStartedAt}). Generic + Router-free: all\n * state arrives via inputs, the only output is {@link TurnSeek}.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-evidence-playback',\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-evidence-playback.component.html',\n styleUrls: ['./realtime-evidence-playback.component.css'],\n})\nexport class RealtimeEvidencePlaybackComponent implements OnDestroy {\n /** The session's transcript turns, in chronological order. */\n @Input()\n set Turns(value: EvidencePlaybackTurn[] | null) {\n this._turns = value ?? [];\n this.cdr.markForCheck();\n }\n get Turns(): EvidencePlaybackTurn[] {\n return this._turns;\n }\n private _turns: EvidencePlaybackTurn[] = [];\n\n /** The recorded session audio URL, or `null` when no recording is available. */\n @Input()\n set AudioUrl(value: string | null) {\n if (value === this._audioUrl) {\n return;\n }\n this._audioUrl = value;\n this.CurrentMs = 0;\n this.DurationMs = 0;\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n get AudioUrl(): string | null {\n return this._audioUrl;\n }\n private _audioUrl: string | null = null;\n\n /** When the recording started, used to derive a turn's offset when it has no `UtteranceStartMs`. */\n @Input() RecordingStartedAt: Date | null = null;\n\n /** Display name of the agent — labels and colors AI turns. */\n @Input() AgentName = 'Agent';\n\n /** Emitted when the user clicks a transcript turn (to seek the audio to it). */\n @Output() TurnSeek = new EventEmitter<EvidencePlaybackTurn>();\n\n /** Current playhead position in ms (driven by the audio element's `timeupdate`). */\n public CurrentMs = 0;\n /** Total audio duration in ms (set on `loadedmetadata`). */\n public DurationMs = 0;\n /** Whether the audio is currently playing. */\n public IsPlaying = false;\n\n /** The synthetic waveform bar heights (stable per AudioUrl). */\n public readonly WaveformBars: number[] = this.buildWaveform();\n\n @ViewChild('audio') private audioRef?: ElementRef<HTMLAudioElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n ngOnDestroy(): void {\n this.audioRef?.nativeElement.pause();\n }\n\n /** The `<audio>` element, or `null` before the view initializes / when there's no audio. */\n private get audioEl(): HTMLAudioElement | null {\n return this.audioRef?.nativeElement ?? null;\n }\n\n /** Playhead progress as a fraction (0..1), for the scrubber fill + waveform reveal. */\n public get Progress(): number {\n return this.DurationMs > 0 ? Math.min(1, this.CurrentMs / this.DurationMs) : 0;\n }\n\n /** Toggles audio play / pause. */\n public TogglePlay(): void {\n const el = this.audioEl;\n if (!el) {\n return;\n }\n if (el.paused) {\n void el.play().catch(() => undefined);\n } else {\n el.pause();\n }\n }\n\n /** Seeks to a fraction of the duration (scrubber click / keyboard). */\n public SeekToFraction(fraction: number): void {\n const el = this.audioEl;\n if (!el || this.DurationMs <= 0) {\n return;\n }\n el.currentTime = (Math.max(0, Math.min(1, fraction)) * this.DurationMs) / 1000;\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n\n /** Scrubber pointer handler — maps the click X within the track to a seek fraction. */\n public OnScrubberClick(event: MouseEvent, track: HTMLElement): void {\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) {\n return;\n }\n this.SeekToFraction((event.clientX - rect.left) / rect.width);\n }\n\n /** Scrubber keyboard handler — arrows nudge ±5s, Home/End jump to ends, Space toggles play. */\n public OnScrubberKeydown(event: KeyboardEvent): void {\n const stepMs = 5000;\n switch (event.key) {\n case 'ArrowRight':\n event.preventDefault();\n this.seekByMs(stepMs);\n break;\n case 'ArrowLeft':\n event.preventDefault();\n this.seekByMs(-stepMs);\n break;\n case 'Home':\n event.preventDefault();\n this.SeekToFraction(0);\n break;\n case 'End':\n event.preventDefault();\n this.SeekToFraction(1);\n break;\n case ' ':\n case 'Enter':\n event.preventDefault();\n this.TogglePlay();\n break;\n default:\n break;\n }\n }\n\n /** Clicking a transcript turn → seek the audio to it, play, and emit {@link TurnSeek}. */\n public SeekToTurn(turn: EvidencePlaybackTurn): void {\n const startMs = this.turnStartMs(turn);\n const el = this.audioEl;\n if (el && this.DurationMs > 0) {\n el.currentTime = Math.max(0, startMs / 1000);\n this.CurrentMs = startMs;\n void el.play().catch(() => undefined);\n }\n this.TurnSeek.emit(turn);\n this.cdr.markForCheck();\n }\n\n /** True when the playhead falls within this turn's utterance window (drives the active highlight). */\n public IsTurnActive(turn: EvidencePlaybackTurn): boolean {\n if (!this.IsPlaying && this.CurrentMs === 0) {\n return false;\n }\n const start = this.turnStartMs(turn);\n const end = this.turnEndMs(turn);\n return this.CurrentMs >= start && this.CurrentMs < end;\n }\n\n /** Whether a turn is from the human user (vs. the agent) — drives the speaker color + label. */\n public IsUserTurn(turn: EvidencePlaybackTurn): boolean {\n return turn.Role === 'User';\n }\n\n /** The speaker label for a turn. */\n public SpeakerLabel(turn: EvidencePlaybackTurn): string {\n return this.IsUserTurn(turn) ? 'You' : this.AgentName;\n }\n\n /** The mm:ss timestamp shown on a turn (its start offset within the recording). */\n public TurnTimestamp(turn: EvidencePlaybackTurn): string {\n return this.formatMs(this.turnStartMs(turn));\n }\n\n /** mm:ss of the current playhead. */\n public get CurrentLabel(): string {\n return this.formatMs(this.CurrentMs);\n }\n\n /** mm:ss of the total duration. */\n public get DurationLabel(): string {\n return this.formatMs(this.DurationMs);\n }\n\n /** Stable `@for` track for the transcript. */\n public TrackByTurn(_index: number, turn: EvidencePlaybackTurn): string {\n return turn.ID;\n }\n\n // ----- audio element event handlers (bound in the template) -------------------------------\n\n /** `loadedmetadata` → capture the duration. */\n public OnLoadedMetadata(): void {\n const el = this.audioEl;\n this.DurationMs = el && Number.isFinite(el.duration) ? el.duration * 1000 : 0;\n this.cdr.markForCheck();\n }\n\n /** `timeupdate` → advance the playhead + re-evaluate the active turn. */\n public OnTimeUpdate(): void {\n const el = this.audioEl;\n if (el) {\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n }\n\n /** `play` / `pause` / `ended` → reflect the transport state. */\n public OnPlay(): void {\n this.IsPlaying = true;\n this.cdr.markForCheck();\n }\n public OnPause(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n public OnEnded(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n\n // ----- helpers ----------------------------------------------------------------------------\n\n /** Nudges the playhead by `deltaMs`, clamped to the duration. */\n private seekByMs(deltaMs: number): void {\n if (this.DurationMs <= 0) {\n return;\n }\n this.SeekToFraction((this.CurrentMs + deltaMs) / this.DurationMs);\n }\n\n /**\n * A turn's start offset (ms) within the recording: prefer {@link EvidencePlaybackTurn.UtteranceStartMs};\n * fall back to (`__mj_CreatedAt` − {@link RecordingStartedAt}); 0 when neither is available.\n */\n private turnStartMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceStartMs != null) {\n return Math.max(0, turn.UtteranceStartMs);\n }\n if (this.RecordingStartedAt && turn.__mj_CreatedAt) {\n return Math.max(0, turn.__mj_CreatedAt.getTime() - this.RecordingStartedAt.getTime());\n }\n return 0;\n }\n\n /**\n * A turn's end offset (ms): prefer {@link EvidencePlaybackTurn.UtteranceEndMs}; otherwise the\n * next turn's start; otherwise the recording duration (or start + a small window when duration is unknown).\n */\n private turnEndMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceEndMs != null) {\n return Math.max(this.turnStartMs(turn), turn.UtteranceEndMs);\n }\n const idx = this._turns.indexOf(turn);\n const next = idx >= 0 ? this._turns[idx + 1] : undefined;\n if (next) {\n return this.turnStartMs(next);\n }\n return this.DurationMs > 0 ? this.DurationMs : this.turnStartMs(turn) + 4000;\n }\n\n /** Formats a ms offset as mm:ss (clamped at 0). */\n private formatMs(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000));\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n /** Builds a stable pseudo-random waveform (deterministic, no per-render churn). */\n private buildWaveform(): number[] {\n const bars: number[] = [];\n for (let i = 0; i < WAVEFORM_BARS; i++) {\n // A smooth, repeatable envelope — looks like audio without needing to decode any.\n const base = Math.sin(i * 0.5) * 0.5 + 0.5;\n const detail = Math.sin(i * 1.7) * 0.25 + 0.25;\n bars.push(Math.max(0.12, Math.min(1, base * 0.7 + detail * 0.5)));\n }\n return bars;\n }\n}\n","<div class=\"evp\">\n <!-- ---------- Audio transport ---------- -->\n <div class=\"evp-player\">\n @if (AudioUrl) {\n <audio\n #audio\n [src]=\"AudioUrl\"\n preload=\"metadata\"\n (loadedmetadata)=\"OnLoadedMetadata()\"\n (timeupdate)=\"OnTimeUpdate()\"\n (play)=\"OnPlay()\"\n (pause)=\"OnPause()\"\n (ended)=\"OnEnded()\"\n ></audio>\n\n <button\n type=\"button\"\n class=\"evp-play\"\n [attr.aria-label]=\"IsPlaying ? 'Pause recording' : 'Play recording'\"\n [title]=\"IsPlaying ? 'Pause' : 'Play'\"\n (click)=\"TogglePlay()\"\n >\n <i class=\"fa-solid\" [class.fa-pause]=\"IsPlaying\" [class.fa-play]=\"!IsPlaying\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"evp-transport\">\n <div\n #track\n class=\"evp-scrubber\"\n role=\"slider\"\n tabindex=\"0\"\n aria-label=\"Seek through the recording\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"100\"\n [attr.aria-valuenow]=\"(Progress * 100) | number: '1.0-0'\"\n [attr.aria-valuetext]=\"CurrentLabel + ' of ' + DurationLabel\"\n (click)=\"OnScrubberClick($event, track)\"\n (keydown)=\"OnScrubberKeydown($event)\"\n >\n <div class=\"evp-waveform\" aria-hidden=\"true\">\n @for (bar of WaveformBars; track $index) {\n <span\n class=\"evp-bar\"\n [class.evp-bar--played]=\"($index / WaveformBars.length) <= Progress\"\n [style.height.%]=\"bar * 100\"\n ></span>\n }\n </div>\n <div class=\"evp-progress\" [style.width.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n <div class=\"evp-thumb\" [style.left.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"evp-time\">\n <span class=\"evp-time__current\">{{ CurrentLabel }}</span>\n <span class=\"evp-time__sep\">/</span>\n <span class=\"evp-time__total\">{{ DurationLabel }}</span>\n </div>\n </div>\n } @else {\n <div class=\"evp-noaudio\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <span>No audio recording is available for this session.</span>\n </div>\n }\n </div>\n\n <!-- ---------- Transcript ---------- -->\n <div class=\"evp-transcript\" role=\"list\" aria-label=\"Session transcript\">\n @if (Turns.length === 0) {\n <div class=\"evp-transcript-empty\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n <p>No transcript turns to show.</p>\n </div>\n } @else {\n @for (turn of Turns; track TrackByTurn($index, turn)) {\n <button\n type=\"button\"\n class=\"evp-turn\"\n role=\"listitem\"\n tabindex=\"0\"\n [class.evp-turn--user]=\"IsUserTurn(turn)\"\n [class.evp-turn--agent]=\"!IsUserTurn(turn)\"\n [class.evp-turn--active]=\"IsTurnActive(turn)\"\n [attr.aria-label]=\"SpeakerLabel(turn) + ' at ' + TurnTimestamp(turn) + ': ' + turn.Message\"\n [attr.aria-current]=\"IsTurnActive(turn) ? 'true' : null\"\n (click)=\"SeekToTurn(turn)\"\n >\n <div class=\"evp-turn__meta\">\n <span class=\"evp-turn__speaker\">{{ SpeakerLabel(turn) }}</span>\n <span class=\"evp-turn__time\">{{ TurnTimestamp(turn) }}</span>\n </div>\n <p class=\"evp-turn__text\">{{ turn.Message }}</p>\n </button>\n }\n }\n </div>\n</div>\n"]}
|
|
@@ -557,11 +557,11 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
|
|
|
557
557
|
} if (rf & 2) {
|
|
558
558
|
i0.ɵɵadvance();
|
|
559
559
|
i0.ɵɵconditional(ctx.Items.length === 0 ? 1 : 2);
|
|
560
|
-
} }, dependencies: [CommonModule, i1.NgClass, i1.NgStyle, MJMediaPlayerComponent, MJStorageMediaPlayerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n}\n\n.media-surface[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n\n\n.media-empty[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-muted);\n\n
|
|
560
|
+
} }, dependencies: [CommonModule, i1.NgClass, i1.NgStyle, MJMediaPlayerComponent, MJStorageMediaPlayerComponent], styles: ["\n\n\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: block;\n height: 100%;\n}\n\n.media-surface[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n\n\n.media-empty[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.media-empty__icon[_ngcontent-%COMP%] {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n margin-bottom: 4px;\n}\n\n.media-empty__title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.media-empty__hint[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.8125rem;\n max-width: 32ch;\n}\n\n\n\n.media-tabs[_ngcontent-%COMP%] {\n display: flex;\n align-items: stretch;\n gap: 4px;\n flex: 0 0 auto;\n padding: 6px 8px 0;\n overflow-x: auto;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n scrollbar-width: thin;\n}\n\n.media-tab[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 220px;\n padding: 7px 8px 7px 10px;\n border: 1px solid transparent;\n border-bottom: none;\n border-top-left-radius: var(--mj-radius-md, 6px);\n border-top-right-radius: var(--mj-radius-md, 6px);\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n\n.media-tab[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.media-tab[_ngcontent-%COMP%]:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n}\n\n.media-tab--active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-default);\n box-shadow: inset 0 2px 0 var(--mj-brand-primary);\n}\n\n.media-tab__icon[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n font-size: 0.8125rem;\n color: var(--mj-brand-primary);\n}\n\n.media-tab__name[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.media-tab__close[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: var(--mj-radius-sm, 4px);\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6875rem;\n}\n\n.media-tab__close[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-error) 14%, transparent);\n color: var(--mj-status-error);\n}\n\n.media-tab__close[_ngcontent-%COMP%]:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n\n\n.media-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n padding: 6px 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.media-toolbar__title[_ngcontent-%COMP%] {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.8125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.media-toolbar__spacer[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n}\n\n.media-iconbtn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 0.8125rem;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n\n.media-iconbtn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.media-iconbtn[_ngcontent-%COMP%]:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n}\n\n.media-iconbtn__badge[_ngcontent-%COMP%] {\n font-size: 0.6875rem;\n font-weight: 600;\n color: var(--mj-brand-primary);\n}\n\n\n\n.media-pane[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 12px;\n gap: 8px;\n}\n\n.media-stage[_ngcontent-%COMP%] {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.media-stage--scroll[_ngcontent-%COMP%] {\n overflow: auto;\n}\n\n.media-image[_ngcontent-%COMP%] {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n transform-origin: center center;\n transition: transform 0.18s ease;\n}\n\n.media-video[_ngcontent-%COMP%] {\n max-width: 100%;\n max-height: 100%;\n background: var(--mj-color-neutral-950, #000);\n border-radius: var(--mj-radius-sm, 4px);\n}\n\n.media-audio-wrap[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n width: 100%;\n padding: 24px;\n}\n\n.media-audio-wrap__icon[_ngcontent-%COMP%] {\n font-size: 3rem;\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n}\n\n.media-audio[_ngcontent-%COMP%] {\n width: 100%;\n max-width: 480px;\n}\n\n.media-frame[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n border: none;\n background: var(--mj-bg-surface);\n}\n\n\n\n.media-player[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n\n\n.media-status[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.media-status__icon[_ngcontent-%COMP%] {\n font-size: 2rem;\n color: var(--mj-text-muted);\n}\n\n.media-status__text[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.9rem;\n}\n\n.media-status--error[_ngcontent-%COMP%] {\n color: var(--mj-status-error-text);\n}\n\n.media-status--error[_ngcontent-%COMP%] .media-status__icon[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n\n\n.media-highlight[_ngcontent-%COMP%] {\n position: absolute;\n border: 2px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-sm, 4px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n box-shadow: 0 0 0 9999px color-mix(in srgb, var(--mj-text-primary) 18%, transparent);\n pointer-events: none;\n animation: _ngcontent-%COMP%_media-highlight-in 0.2s ease;\n}\n\n.media-highlight__label[_ngcontent-%COMP%] {\n position: absolute;\n top: -1.5em;\n left: -2px;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.6875rem;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.media-caption[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: 0;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n text-align: center;\n}\n\n@keyframes _ngcontent-%COMP%_media-highlight-in {\n from {\n opacity: 0;\n transform: scale(1.04);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-image[_ngcontent-%COMP%], \n .media-tab[_ngcontent-%COMP%], \n .media-iconbtn[_ngcontent-%COMP%] {\n transition: none;\n }\n\n .media-highlight[_ngcontent-%COMP%] {\n animation: none;\n }\n}"], changeDetection: 0 });
|
|
561
561
|
}
|
|
562
562
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeMediaSurfaceComponent, [{
|
|
563
563
|
type: Component,
|
|
564
|
-
args: [{ standalone: true, selector: 'mj-realtime-media-surface', imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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 \u2014 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 }}\u00D7</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\u2026</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", styles: [":host {\n display: block;\n height: 100%;\n}\n\n.media-surface {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ---------- Empty state ---------- */\n.media-empty {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-muted);\n\n &__icon {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n margin-bottom: 4px;\n }\n\n &__title {\n margin: 0;\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n }\n\n &__hint {\n margin: 0;\n font-size: 0.8125rem;\n max-width: 32ch;\n }\n}\n\n/* ---------- Tab bar ---------- */\n.media-tabs {\n display: flex;\n align-items: stretch;\n gap: 4px;\n flex: 0 0 auto;\n padding: 6px 8px 0;\n overflow-x: auto;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n scrollbar-width: thin;\n}\n\n.media-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 220px;\n padding: 7px 8px 7px 10px;\n border: 1px solid transparent;\n border-bottom: none;\n border-top-left-radius: var(--mj-radius-md, 6px);\n border-top-right-radius: var(--mj-radius-md, 6px);\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n\n &:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n &:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n }\n\n &--active {\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-default);\n box-shadow: inset 0 2px 0 var(--mj-brand-primary);\n }\n\n &__icon {\n flex: 0 0 auto;\n font-size: 0.8125rem;\n color: var(--mj-brand-primary);\n }\n\n &__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__close {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: var(--mj-radius-sm, 4px);\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6875rem;\n\n &:hover {\n background: color-mix(in srgb, var(--mj-status-error) 14%, transparent);\n color: var(--mj-status-error);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n }\n }\n}\n\n/* ---------- Pane toolbar ---------- */\n.media-toolbar {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n padding: 6px 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n\n &__title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.8125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n &__spacer {\n flex: 1 1 auto;\n }\n}\n\n.media-iconbtn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 0.8125rem;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n\n &:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n &:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n }\n\n &__badge {\n font-size: 0.6875rem;\n font-weight: 600;\n color: var(--mj-brand-primary);\n }\n}\n\n/* ---------- Media pane ---------- */\n.media-pane {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 12px;\n gap: 8px;\n}\n\n.media-stage {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n\n &--scroll {\n overflow: auto;\n }\n}\n\n.media-image {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n transform-origin: center center;\n transition: transform 0.18s ease;\n}\n\n.media-video {\n max-width: 100%;\n max-height: 100%;\n background: var(--mj-color-neutral-950, #000);\n border-radius: var(--mj-radius-sm, 4px);\n}\n\n.media-audio-wrap {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n width: 100%;\n padding: 24px;\n\n &__icon {\n font-size: 3rem;\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n }\n}\n\n.media-audio {\n width: 100%;\n max-width: 480px;\n}\n\n.media-frame {\n width: 100%;\n height: 100%;\n border: none;\n background: var(--mj-bg-surface);\n}\n\n/* The real media player (mj-media-player / mj-storage-media-player) fills the stage. */\n.media-player {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n/* Loading / no-access status for a FileID-backed image/pdf/web pane awaiting its streaming URL. */\n.media-status {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-secondary);\n\n &__icon {\n font-size: 2rem;\n color: var(--mj-text-muted);\n }\n\n &__text {\n margin: 0;\n font-size: 0.9rem;\n }\n\n &--error {\n color: var(--mj-status-error-text);\n\n .media-status__icon {\n color: var(--mj-status-error);\n }\n }\n}\n\n/* ---------- Highlight overlay (fractional coords) ---------- */\n.media-highlight {\n position: absolute;\n border: 2px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-sm, 4px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n box-shadow: 0 0 0 9999px color-mix(in srgb, var(--mj-text-primary) 18%, transparent);\n pointer-events: none;\n animation: media-highlight-in 0.2s ease;\n\n &__label {\n position: absolute;\n top: -1.5em;\n left: -2px;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.6875rem;\n font-weight: 600;\n white-space: nowrap;\n }\n}\n\n.media-caption {\n flex: 0 0 auto;\n margin: 0;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n text-align: center;\n}\n\n@keyframes media-highlight-in {\n from {\n opacity: 0;\n transform: scale(1.04);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-image,\n .media-tab,\n .media-iconbtn {\n transition: none;\n }\n\n .media-highlight {\n animation: none;\n }\n}\n"] }]
|
|
564
|
+
args: [{ standalone: true, selector: 'mj-realtime-media-surface', imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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 \u2014 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 }}\u00D7</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\u2026</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", styles: ["/**\n * Realtime media surface \u2014 tabbed image/video/audio/pdf/web panes.\n *\n * NOTE: this package builds with bare `ngc` (no Sass step) \u2014 component styles\n * are embedded RAW into the compiled JS. Keep this file FLAT, plain CSS.\n * Sass nesting / BEM selector concatenation would be silently dropped by the\n * browser and the rules would be DEAD. See .github/scripts/check-ngc-scss.sh\n * (CI gate) and MemberJunction/MJ#3105.\n */\n\n:host {\n display: block;\n height: 100%;\n}\n\n.media-surface {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ---------- Empty state ---------- */\n.media-empty {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.media-empty__icon {\n font-size: 2.5rem;\n color: var(--mj-text-disabled);\n margin-bottom: 4px;\n}\n\n.media-empty__title {\n margin: 0;\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.media-empty__hint {\n margin: 0;\n font-size: 0.8125rem;\n max-width: 32ch;\n}\n\n/* ---------- Tab bar ---------- */\n.media-tabs {\n display: flex;\n align-items: stretch;\n gap: 4px;\n flex: 0 0 auto;\n padding: 6px 8px 0;\n overflow-x: auto;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n scrollbar-width: thin;\n}\n\n.media-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 220px;\n padding: 7px 8px 7px 10px;\n border: 1px solid transparent;\n border-bottom: none;\n border-top-left-radius: var(--mj-radius-md, 6px);\n border-top-right-radius: var(--mj-radius-md, 6px);\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n\n.media-tab:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.media-tab:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n}\n\n.media-tab--active {\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-default);\n box-shadow: inset 0 2px 0 var(--mj-brand-primary);\n}\n\n.media-tab__icon {\n flex: 0 0 auto;\n font-size: 0.8125rem;\n color: var(--mj-brand-primary);\n}\n\n.media-tab__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.media-tab__close {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: var(--mj-radius-sm, 4px);\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6875rem;\n}\n\n.media-tab__close:hover {\n background: color-mix(in srgb, var(--mj-status-error) 14%, transparent);\n color: var(--mj-status-error);\n}\n\n.media-tab__close:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n/* ---------- Pane toolbar ---------- */\n.media-toolbar {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n padding: 6px 10px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.media-toolbar__title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.8125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.media-toolbar__spacer {\n flex: 1 1 auto;\n}\n\n.media-iconbtn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 0.8125rem;\n transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n\n.media-iconbtn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.media-iconbtn:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n}\n\n.media-iconbtn__badge {\n font-size: 0.6875rem;\n font-weight: 600;\n color: var(--mj-brand-primary);\n}\n\n/* ---------- Media pane ---------- */\n.media-pane {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 12px;\n gap: 8px;\n}\n\n.media-stage {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.media-stage--scroll {\n overflow: auto;\n}\n\n.media-image {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n transform-origin: center center;\n transition: transform 0.18s ease;\n}\n\n.media-video {\n max-width: 100%;\n max-height: 100%;\n background: var(--mj-color-neutral-950, #000);\n border-radius: var(--mj-radius-sm, 4px);\n}\n\n.media-audio-wrap {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n width: 100%;\n padding: 24px;\n}\n\n.media-audio-wrap__icon {\n font-size: 3rem;\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n}\n\n.media-audio {\n width: 100%;\n max-width: 480px;\n}\n\n.media-frame {\n width: 100%;\n height: 100%;\n border: none;\n background: var(--mj-bg-surface);\n}\n\n/* The real media player (mj-media-player / mj-storage-media-player) fills the stage. */\n.media-player {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n/* Loading / no-access status for a FileID-backed image/pdf/web pane awaiting its streaming URL. */\n.media-status {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 24px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.media-status__icon {\n font-size: 2rem;\n color: var(--mj-text-muted);\n}\n\n.media-status__text {\n margin: 0;\n font-size: 0.9rem;\n}\n\n.media-status--error {\n color: var(--mj-status-error-text);\n}\n\n.media-status--error .media-status__icon {\n color: var(--mj-status-error);\n}\n\n/* ---------- Highlight overlay (fractional coords) ---------- */\n.media-highlight {\n position: absolute;\n border: 2px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-sm, 4px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n box-shadow: 0 0 0 9999px color-mix(in srgb, var(--mj-text-primary) 18%, transparent);\n pointer-events: none;\n animation: media-highlight-in 0.2s ease;\n}\n\n.media-highlight__label {\n position: absolute;\n top: -1.5em;\n left: -2px;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.6875rem;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.media-caption {\n flex: 0 0 auto;\n margin: 0;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n text-align: center;\n}\n\n@keyframes media-highlight-in {\n from {\n opacity: 0;\n transform: scale(1.04);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-image,\n .media-tab,\n .media-iconbtn {\n transition: none;\n }\n\n .media-highlight {\n animation: none;\n }\n}\n"] }]
|
|
565
565
|
}], null, { State: [{
|
|
566
566
|
type: Input
|
|
567
567
|
}], AgentName: [{
|
|
@@ -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.scss'],\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 * 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"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user persistence for in-progress (un-sent) composer drafts, so a message the
|
|
3
|
+
* user was writing survives conversation switches, page refreshes, and devices.
|
|
4
|
+
*
|
|
5
|
+
* Storage: ONE `MJ: User Settings` row via {@link UserInfoEngine} (never
|
|
6
|
+
* localStorage — see the root CLAUDE.md persistence rule), key
|
|
7
|
+
* {@link COMPOSER_DRAFTS_SETTING}, value = a JSON map of
|
|
8
|
+
* `'new' | <conversationId-lowercased>` → the SERIALIZED draft (the composer's
|
|
9
|
+
* `getPlainTextWithJsonMentions()` format, so mention pills round-trip losslessly
|
|
10
|
+
* through the editor's rehydrating `writeValue`).
|
|
11
|
+
*
|
|
12
|
+
* Contracts:
|
|
13
|
+
* - Writes while typing are debounced (`SetSettingDebounced`); {@link Flush}
|
|
14
|
+
* persists immediately (blur / conversation switch).
|
|
15
|
+
* - Sending a message calls {@link ClearDraft}, which DELETES the key from the
|
|
16
|
+
* map and persists — the map only ever holds live drafts.
|
|
17
|
+
* - Empty/whitespace drafts are treated as clears; drafts are capped at
|
|
18
|
+
* {@link MAX_DRAFT_LENGTH}; the map keeps at most {@link MAX_DRAFTS} entries
|
|
19
|
+
* (least-recently-updated evicted).
|
|
20
|
+
* - The setting is read ONCE per session (UserInfoEngine's cache makes this a
|
|
21
|
+
* sync hit anyway); the in-memory map is authoritative afterwards. Multi-tab
|
|
22
|
+
* is last-write-wins by design.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ComposerDraftStore {
|
|
25
|
+
/** The single User Setting key holding all drafts. */
|
|
26
|
+
static readonly COMPOSER_DRAFTS_SETTING = "mj.chat.drafts.v1";
|
|
27
|
+
/** Map key for the new-conversation (not-yet-created) composer. */
|
|
28
|
+
static readonly NEW_CONVERSATION_KEY = "new";
|
|
29
|
+
/** Most drafts retained; least-recently-updated evicted beyond this. */
|
|
30
|
+
static readonly MAX_DRAFTS = 20;
|
|
31
|
+
/** Per-draft length cap (chars of serialized text). */
|
|
32
|
+
static readonly MAX_DRAFT_LENGTH = 16000;
|
|
33
|
+
private drafts;
|
|
34
|
+
private loaded;
|
|
35
|
+
/** Normalizes a conversation id (null = the new-conversation composer) to its map key. */
|
|
36
|
+
static KeyFor(conversationId: string | null | undefined): string;
|
|
37
|
+
/** The stored draft for a conversation (or the new-convo composer), if any. */
|
|
38
|
+
GetDraft(conversationId: string | null | undefined): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Records the current draft (debounced persistence). Empty text clears the
|
|
41
|
+
* entry instead — the map never holds blank drafts.
|
|
42
|
+
*/
|
|
43
|
+
SetDraft(conversationId: string | null | undefined, serializedText: string): void;
|
|
44
|
+
/** Deletes a conversation's draft (message sent / draft abandoned) and persists immediately. */
|
|
45
|
+
ClearDraft(conversationId: string | null | undefined): void;
|
|
46
|
+
/** Persists the current map immediately (blur / conversation switch / teardown). */
|
|
47
|
+
Flush(): void;
|
|
48
|
+
private ensureLoaded;
|
|
49
|
+
private evictBeyondCap;
|
|
50
|
+
private serialize;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=composer-draft-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-draft-store.d.ts","sourceRoot":"","sources":["../../../src/lib/services/composer-draft-store.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,kBAAkB;IAC3B,sDAAsD;IACtD,gBAAuB,uBAAuB,uBAAuB;IACrE,mEAAmE;IACnE,gBAAuB,oBAAoB,SAAS;IACpD,wEAAwE;IACxE,gBAAuB,UAAU,MAAM;IACvC,uDAAuD;IACvD,gBAAuB,gBAAgB,SAAU;IAEjD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAAS;IAEvB,0FAA0F;WAC5E,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IAIvE,+EAA+E;IACxE,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAKzE;;;OAGG;IACI,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAsBxF,gGAAgG;IACzF,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAOlE,oFAAoF;IAC7E,KAAK,IAAI,IAAI;IAUpB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,SAAS;CAGpB"}
|