@memberjunction/ng-conversations 6.2.0-edge.5 → 6.2.0-edge.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/attachment/image-viewer.component.d.ts +3 -1
- package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/attachment/image-viewer.component.js +5 -1
- package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +188 -9
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +1333 -464
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
- package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
- package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
- package/dist/lib/components/message/message-input.component.d.ts +126 -59
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +559 -554
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +34 -2
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +678 -520
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +13 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +92 -39
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts +19 -0
- package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.js +125 -13
- package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
- package/dist/lib/components/project/project-form-modal.component.js +2 -2
- package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +7 -2
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +22 -6
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +160 -98
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts +13 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.js +21 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.js.map +1 -0
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +226 -120
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +73 -16
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +219 -108
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +34 -5
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +2 -1
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +9 -4
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +5 -4
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +2 -2
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +11 -7
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/active-tasks.service.d.ts +5 -0
- package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
- package/dist/lib/services/active-tasks.service.js +16 -0
- package/dist/lib/services/active-tasks.service.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +7 -1
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-permission.service.js +2 -0
- package/dist/lib/services/artifact-permission.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -0
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
- package/dist/lib/services/collection-permission.service.js +1 -0
- package/dist/lib/services/collection-permission.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +38 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +57 -71
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +5 -0
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.js +52 -5
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
- package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/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/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +1 -0
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/agent-failure-display.d.ts +14 -0
- package/dist/lib/utils/agent-failure-display.d.ts.map +1 -0
- package/dist/lib/utils/agent-failure-display.js +26 -0
- package/dist/lib/utils/agent-failure-display.js.map +1 -0
- package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
- package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
- package/dist/lib/utils/agent-handoff-map.js +270 -0
- package/dist/lib/utils/agent-handoff-map.js.map +1 -0
- package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
- package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
- package/dist/lib/utils/agent-turn-routing.js +147 -6
- package/dist/lib/utils/agent-turn-routing.js.map +1 -1
- package/dist/lib/utils/decision-routing.d.ts +17 -9
- package/dist/lib/utils/decision-routing.d.ts.map +1 -1
- package/dist/lib/utils/decision-routing.js +23 -14
- package/dist/lib/utils/decision-routing.js.map +1 -1
- package/dist/lib/utils/message-list-order.d.ts +13 -0
- package/dist/lib/utils/message-list-order.d.ts.map +1 -0
- package/dist/lib/utils/message-list-order.js +31 -0
- package/dist/lib/utils/message-list-order.js.map +1 -0
- package/dist/lib/utils/pin-state.d.ts +11 -0
- package/dist/lib/utils/pin-state.d.ts.map +1 -0
- package/dist/lib/utils/pin-state.js +9 -0
- package/dist/lib/utils/pin-state.js.map +1 -0
- package/dist/lib/utils/thread-replies.d.ts +34 -0
- package/dist/lib/utils/thread-replies.d.ts.map +1 -0
- package/dist/lib/utils/thread-replies.js +49 -0
- package/dist/lib/utils/thread-replies.js.map +1 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +36 -36
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-channel-state.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/** The media kinds the Media surface can render — one render path per value. */\nexport type MediaItemType = 'image' | 'video' | 'audio' | 'pdf' | 'web';\n\n/**\n * A fractional highlight rectangle drawn over a media item, in the item's OWN coordinate space\n * (0..1 on both axes). `X`,`Y` is the top-left corner; `W`,`H` are the width/height. Fractional\n * coordinates keep the highlight correct regardless of how the media is scaled to fit the pane.\n */\nexport interface MediaHighlight {\n /** Left edge as a fraction of width (0..1). */\n X: number;\n /** Top edge as a fraction of height (0..1). */\n Y: number;\n /** Width as a fraction of width (0..1). */\n W: number;\n /** Height as a fraction of height (0..1). */\n H: number;\n /** Optional short label rendered on the highlight. */\n Label?: string;\n}\n\n/** One piece of media on the surface — a tab + a render pane keyed by {@link MediaItemType}. */\nexport interface MediaItem {\n /** Stable id (returned to the agent by `Media_ShowMedia`, used by close/play/highlight). */\n Id: string;\n /** Which render path the pane uses. */\n Type: MediaItemType;\n /**\n * Absolute URL of the media — for an EXTERNAL/public asset. Optional/empty when {@link FileID}\n * is set (the surface resolves a secure streaming URL from the file id instead). At least one of\n * `Url` / `FileID` is always present.\n */\n Url?: string;\n /**\n * Id of an \"MJ: Files\" record this item is backed by. When set, the item is streamed securely\n * (permission-gated) from MJStorage rather than from a public {@link Url}; the surface resolves a\n * short-lived streaming URL (via `CreateMediaAccessToken`) or hands the id straight to\n * `mj-storage-media-player` for audio/video.\n */\n FileID?: string;\n /** Short tab label. */\n DisplayName: string;\n /** Optional one-line caption beneath the media. */\n Caption?: string;\n /** Optional fractional highlight overlay. */\n Highlight?: MediaHighlight;\n /** Set transiently to request playback of a video/audio item (consumed by the surface). */\n PlayRequestedAt?: number;\n}\n\n/** The serialized shape persisted as the channel's state of record (versioned for forward-compat). */\ninterface SerializedMediaState {\n V: 1;\n Items: MediaItem[];\n ActiveItemId: string | null;\n}\n\n/** Monotonic counter feeding {@link MediaChannelState.newId} so ids stay unique within a session. */\nlet mediaIdCounter = 0;\n\n/**\n * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media\n * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new\n * state through {@link Changed$} so the channel persists it and the surface re-renders. Mirrors\n * `WhiteboardState` (no Angular, tolerant (de)serialization, single `Changed$` notifier).\n */\nexport class MediaChannelState {\n /** The media currently on the surface, in tab order (oldest first). */\n public Items: MediaItem[] = [];\n\n /** The id of the active (visible) item, or `null` when the surface is empty. */\n public ActiveItemId: string | null = null;\n\n /**\n * Fires on every mutation. Replays the current state on subscribe (BehaviorSubject), so a late\n * surface binding immediately renders what's already on the board.\n */\n public readonly Changed$ = new BehaviorSubject<MediaChannelState>(this);\n\n /** Adds a media item, makes it active, and returns it. */\n public AddItem(item: Omit<MediaItem, 'Id'> & { Id?: string }): MediaItem {\n const created: MediaItem = { ...item, Id: item.Id ?? this.newId() };\n this.Items = [...this.Items, created];\n this.ActiveItemId = created.Id;\n this.notify();\n return created;\n }\n\n /** Removes the item with `id` (no-op if absent). When it was active, the last remaining item becomes active. */\n public RemoveItem(id: string): boolean {\n const next = this.Items.filter((i) => i.Id !== id);\n if (next.length === this.Items.length) {\n return false;\n }\n this.Items = next;\n if (this.ActiveItemId === id) {\n this.ActiveItemId = next.length > 0 ? next[next.length - 1].Id : null;\n }\n this.notify();\n return true;\n }\n\n /** Makes `id` the active item (no-op if absent). */\n public SetActive(id: string): boolean {\n if (!this.Items.some((i) => i.Id === id) || this.ActiveItemId === id) {\n return this.ActiveItemId === id;\n }\n this.ActiveItemId = id;\n this.notify();\n return true;\n }\n\n /**\n * Activates `id` and stamps a play request on it (the surface consumes the stamp to start\n * video/audio playback). No-op if the item is absent.\n */\n public RequestPlay(id: string): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.ActiveItemId = id;\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, PlayRequestedAt: Date.now() } : i));\n this.notify();\n return true;\n }\n\n /** Sets (or, with `null`, clears) the fractional highlight on `id`. No-op if the item is absent. */\n public Highlight(id: string, highlight: MediaHighlight | null): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, Highlight: highlight ?? undefined } : i));\n this.notify();\n return true;\n }\n\n /** Removes all media and resets the active item. */\n public Clear(): void {\n if (this.Items.length === 0 && this.ActiveItemId === null) {\n return;\n }\n this.Items = [];\n this.ActiveItemId = null;\n this.notify();\n }\n\n /** The active item, or `null` when the surface is empty / the active id is stale. */\n public get ActiveItem(): MediaItem | null {\n return this.Items.find((i) => i.Id === this.ActiveItemId) ?? null;\n }\n\n /** Serializes the state of record to a JSON string (persisted via the channel's RequestSave). */\n public ToJSON(): string {\n const payload: SerializedMediaState = { V: 1, Items: this.Items, ActiveItemId: this.ActiveItemId };\n return JSON.stringify(payload);\n }\n\n /**\n * Rehydrates a prior session's saved state IN PLACE (preserving this instance + its `Changed$`\n * subscribers). Tolerant by contract: malformed / incompatible JSON is ignored and the method\n * returns `false` without throwing, leaving the current state untouched.\n */\n public LoadFromJSON(json: string): boolean {\n try {\n const parsed = JSON.parse(json) as Partial<SerializedMediaState> | null;\n if (!parsed || !Array.isArray(parsed.Items)) {\n return false;\n }\n const items = parsed.Items.filter((i): i is MediaItem => this.isValidItem(i));\n this.Items = items;\n const active = typeof parsed.ActiveItemId === 'string' ? parsed.ActiveItemId : null;\n this.ActiveItemId = active && items.some((i) => i.Id === active) ? active : items.length > 0 ? items[items.length - 1].Id : null;\n this.notify();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Validates one parsed item enough to render it safely (defensive against hand-edited / old\n * payloads). An item must carry a SOURCE — a non-empty `Url` OR a non-empty `FileID` — since one\n * of the two backs every render path.\n */\n private isValidItem(value: unknown): value is MediaItem {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const item = value as Record<string, unknown>;\n const validTypes: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n const hasUrl = typeof item['Url'] === 'string' && (item['Url'] as string).length > 0;\n const hasFileID = typeof item['FileID'] === 'string' && (item['FileID'] as string).length > 0;\n return (\n typeof item['Id'] === 'string' &&\n (hasUrl || hasFileID) &&\n typeof item['DisplayName'] === 'string' &&\n typeof item['Type'] === 'string' &&\n validTypes.includes(item['Type'] as MediaItemType)\n );\n }\n\n /** Mints a unique-within-session item id. */\n private newId(): string {\n mediaIdCounter += 1;\n return `media-${Date.now().toString(36)}-${mediaIdCounter}`;\n }\n\n /** Pushes the current state to subscribers. */\n private notify(): void {\n this.Changed$.next(this);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"media-channel-state.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/** The media kinds the Media surface can render — one render path per value. */\nexport type MediaItemType = 'image' | 'video' | 'audio' | 'pdf' | 'web';\n\n/**\n * A fractional highlight rectangle drawn over a media item, in the item's OWN coordinate space\n * (0..1 on both axes). `X`,`Y` is the top-left corner; `W`,`H` are the width/height. Fractional\n * coordinates keep the highlight correct regardless of how the media is scaled to fit the pane.\n */\nexport interface MediaHighlight {\n /** Left edge as a fraction of width (0..1). */\n X: number;\n /** Top edge as a fraction of height (0..1). */\n Y: number;\n /** Width as a fraction of width (0..1). */\n W: number;\n /** Height as a fraction of height (0..1). */\n H: number;\n /** Optional short label rendered on the highlight. */\n Label?: string;\n}\n\n/** One piece of media on the surface — a tab + a render pane keyed by {@link MediaItemType}. */\nexport interface MediaItem {\n /** Stable id (returned to the agent by `Media_ShowMedia`, used by close/play/highlight). */\n Id: string;\n /** Which render path the pane uses. */\n Type: MediaItemType;\n /**\n * Absolute URL of the media — for an EXTERNAL/public asset. Optional/empty when {@link FileID}\n * is set (the surface resolves a secure streaming URL from the file id instead). At least one of\n * `Url` / `FileID` is always present.\n */\n Url?: string;\n /**\n * Id of an \"MJ: Files\" record this item is backed by. When set, the item is streamed securely\n * (permission-gated) from MJStorage rather than from a public {@link Url}; the surface resolves a\n * short-lived streaming URL (via `CreateMediaAccessToken`) or hands the id straight to\n * `mj-storage-media-player` for audio/video.\n */\n FileID?: string;\n /** Short tab label. */\n DisplayName: string;\n /** Optional one-line caption beneath the media. */\n Caption?: string;\n /** Optional fractional highlight overlay. */\n Highlight?: MediaHighlight;\n /** Set transiently to request playback of a video/audio item (consumed by the surface). */\n PlayRequestedAt?: number;\n}\n\n/** The serialized shape persisted as the channel's state of record (versioned for forward-compat). */\ninterface SerializedMediaState {\n V: 1;\n Items: MediaItem[];\n ActiveItemId: string | null;\n}\n\n/** Monotonic counter feeding {@link MediaChannelState.newId} so ids stay unique within a session. */\nlet mediaIdCounter = 0;\n\n/**\n * Validates that an external/public media URL uses an explicit `http:` or `https:` scheme with a\n * non-empty host (never `javascript:`, `data:`, `blob:`, `file:`, or scheme-relative `//...`).\n */\nexport function IsAllowedExternalMediaUrl(url: string | null | undefined): boolean {\n if (typeof url !== 'string') {\n return false;\n }\n const trimmed = url.trim();\n if (!trimmed || trimmed.startsWith('//') || !/^https?:\\/\\//i.test(trimmed)) {\n return false;\n }\n try {\n const parsed = new URL(trimmed);\n return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && Boolean(parsed.hostname);\n } catch {\n return false;\n }\n}\n\n/**\n * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media\n * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new\n * state through {@link Changed$} so the channel persists it and the surface re-renders. Mirrors\n * `WhiteboardState` (no Angular, tolerant (de)serialization, single `Changed$` notifier).\n */\nexport class MediaChannelState {\n /** The media currently on the surface, in tab order (oldest first). */\n public Items: MediaItem[] = [];\n\n /** The id of the active (visible) item, or `null` when the surface is empty. */\n public ActiveItemId: string | null = null;\n\n /**\n * Fires on every mutation. Replays the current state on subscribe (BehaviorSubject), so a late\n * surface binding immediately renders what's already on the board.\n */\n public readonly Changed$ = new BehaviorSubject<MediaChannelState>(this);\n\n /** Adds a media item, makes it active, and returns it. */\n public AddItem(item: Omit<MediaItem, 'Id'> & { Id?: string }): MediaItem {\n const created: MediaItem = { ...item, Id: item.Id ?? this.newId() };\n this.Items = [...this.Items, created];\n this.ActiveItemId = created.Id;\n this.notify();\n return created;\n }\n\n /** Removes the item with `id` (no-op if absent). When it was active, the last remaining item becomes active. */\n public RemoveItem(id: string): boolean {\n const next = this.Items.filter((i) => i.Id !== id);\n if (next.length === this.Items.length) {\n return false;\n }\n this.Items = next;\n if (this.ActiveItemId === id) {\n this.ActiveItemId = next.length > 0 ? next[next.length - 1].Id : null;\n }\n this.notify();\n return true;\n }\n\n /** Makes `id` the active item (no-op if absent). */\n public SetActive(id: string): boolean {\n if (!this.Items.some((i) => i.Id === id) || this.ActiveItemId === id) {\n return this.ActiveItemId === id;\n }\n this.ActiveItemId = id;\n this.notify();\n return true;\n }\n\n /**\n * Activates `id` and stamps a play request on it (the surface consumes the stamp to start\n * video/audio playback). No-op if the item is absent.\n */\n public RequestPlay(id: string): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.ActiveItemId = id;\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, PlayRequestedAt: Date.now() } : i));\n this.notify();\n return true;\n }\n\n /** Sets (or, with `null`, clears) the fractional highlight on `id`. No-op if the item is absent. */\n public Highlight(id: string, highlight: MediaHighlight | null): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, Highlight: highlight ?? undefined } : i));\n this.notify();\n return true;\n }\n\n /** Removes all media and resets the active item. */\n public Clear(): void {\n if (this.Items.length === 0 && this.ActiveItemId === null) {\n return;\n }\n this.Items = [];\n this.ActiveItemId = null;\n this.notify();\n }\n\n /** The active item, or `null` when the surface is empty / the active id is stale. */\n public get ActiveItem(): MediaItem | null {\n return this.Items.find((i) => i.Id === this.ActiveItemId) ?? null;\n }\n\n /** Serializes the state of record to a JSON string (persisted via the channel's RequestSave). */\n public ToJSON(): string {\n const payload: SerializedMediaState = { V: 1, Items: this.Items, ActiveItemId: this.ActiveItemId };\n return JSON.stringify(payload);\n }\n\n /**\n * Rehydrates a prior session's saved state IN PLACE (preserving this instance + its `Changed$`\n * subscribers). Tolerant by contract: malformed / incompatible JSON is ignored and the method\n * returns `false` without throwing, leaving the current state untouched.\n */\n public LoadFromJSON(json: string): boolean {\n try {\n const parsed = JSON.parse(json) as Partial<SerializedMediaState> | null;\n if (!parsed || !Array.isArray(parsed.Items)) {\n return false;\n }\n const items = parsed.Items.filter((i): i is MediaItem => this.isValidItem(i));\n this.Items = items;\n const active = typeof parsed.ActiveItemId === 'string' ? parsed.ActiveItemId : null;\n this.ActiveItemId = active && items.some((i) => i.Id === active) ? active : items.length > 0 ? items[items.length - 1].Id : null;\n this.notify();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Validates one parsed item enough to render it safely (defensive against hand-edited / old\n * payloads). An item must carry a SOURCE — a non-empty `Url` OR a non-empty `FileID` — since one\n * of the two backs every render path.\n */\n private isValidItem(value: unknown): value is MediaItem {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const item = value as Record<string, unknown>;\n const validTypes: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n const rawUrl = item['Url'];\n if (rawUrl !== undefined && rawUrl !== null && rawUrl !== '') {\n if (typeof rawUrl !== 'string' || !IsAllowedExternalMediaUrl(rawUrl)) {\n return false;\n }\n }\n const hasUrl = typeof rawUrl === 'string' && rawUrl.trim().length > 0;\n const hasFileID = typeof item['FileID'] === 'string' && (item['FileID'] as string).length > 0;\n return (\n typeof item['Id'] === 'string' &&\n (hasUrl || hasFileID) &&\n typeof item['DisplayName'] === 'string' &&\n typeof item['Type'] === 'string' &&\n validTypes.includes(item['Type'] as MediaItemType)\n );\n }\n\n /** Mints a unique-within-session item id. */\n private newId(): string {\n mediaIdCounter += 1;\n return `media-${Date.now().toString(36)}-${mediaIdCounter}`;\n }\n\n /** Pushes the current state to subscribers. */\n private notify(): void {\n this.Changed$.next(this);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAqC,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,EAAE,yBAAyB,EAAwB,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,
|
|
1
|
+
{"version":3,"file":"media-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAqC,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,EAAE,yBAAyB,EAAwB,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EAA6B,iBAAiB,EAAiC,MAAM,uBAAuB,CAAC;AACpH,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAkBnF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,oBAAqB,SAAQ,yBAAyB,CAAC,6BAA6B,CAAC;IAChG,mFAAmF;IACnF,SAAgB,KAAK,oBAA2B;IAEhD,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAA8C;IAC7D,8EAA8E;IAC9E,OAAO,CAAC,eAAe,CAA6B;IAEpD,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,cAAc,IAAI,MAAM,CAElC;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEM,kBAAkB,IAAI,sBAAsB,EAAE;IAIrD;;;;OAIG;IACa,aAAa,IAAI,yBAAyB;IAuB1C,mBAAmB,IAAI,IAAI,CAAC,6BAA6B,CAAC;IAI1E,yFAAyF;IACzE,oBAAoB,IAAI,wBAAwB;IAgBhE,kGAAkG;cAC/E,YAAY,IAAI,IAAI;IAMvC;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAOjD,aAAa,IAAI,IAAI;IAIrC;;;;;;OAMG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IA2BjE,+GAA+G;IAC/G,OAAO,CAAC,cAAc;IA8BtB,2BAA2B;IAC3B,OAAO,CAAC,eAAe;IAavB,qDAAqD;IACrD,OAAO,CAAC,cAAc;IAatB,mFAAmF;IACnF,OAAO,CAAC,cAAc;IAqBtB,wBAAwB;IACxB,OAAO,CAAC,aAAa;IAMrB,4FAA4F;IAC5E,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/C;;;;OAIG;IACa,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC,OAAO,IAAI,IAAI;IAO/B,sFAAsF;IACtF,OAAO,CAAC,IAAI;IAIZ;;;OAGG;IACH,OAAO,CAAC,IAAI;CAKb;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
|
|
@@ -8,7 +8,7 @@ import { RegisterClass } from '@memberjunction/global';
|
|
|
8
8
|
import { REALTIME_CHANNEL_CONTRACT_VERSION } from '@memberjunction/ai-core-plus';
|
|
9
9
|
import { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';
|
|
10
10
|
import { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';
|
|
11
|
-
import { MediaChannelState } from './media-channel-state';
|
|
11
|
+
import { IsAllowedExternalMediaUrl, MediaChannelState } from './media-channel-state';
|
|
12
12
|
import { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';
|
|
13
13
|
/** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */
|
|
14
14
|
function asString(value) {
|
|
@@ -169,12 +169,17 @@ let RealtimeMediaChannel = class RealtimeMediaChannel extends BaseRealtimeChanne
|
|
|
169
169
|
if (!url && !fileId) {
|
|
170
170
|
return this.fail('Media_ShowMedia requires a source — provide a "url" (external/public) OR a "fileId" (an MJ: Files id).');
|
|
171
171
|
}
|
|
172
|
+
// SECURITY: the url arg is model-supplied and is later rendered in an img/iframe/player, so
|
|
173
|
+
// only well-formed absolute http(s) URLs are accepted — never javascript:, data:, blob:, or scheme-relative.
|
|
174
|
+
if (url && !IsAllowedExternalMediaUrl(url)) {
|
|
175
|
+
return this.fail('Media_ShowMedia "url" must be an absolute http:// or https:// URL.');
|
|
176
|
+
}
|
|
172
177
|
if (!displayName) {
|
|
173
178
|
return this.fail('Media_ShowMedia requires a "displayName".');
|
|
174
179
|
}
|
|
175
180
|
const item = this.State.AddItem({
|
|
176
181
|
Type: mediaType,
|
|
177
|
-
Url: url,
|
|
182
|
+
Url: url ? url.trim() : undefined,
|
|
178
183
|
FileID: fileId,
|
|
179
184
|
DisplayName: displayName,
|
|
180
185
|
Caption: asString(args['caption']),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { RealtimeToolDefinition } from '@memberjunction/ai';\nimport { REALTIME_CHANNEL_CONTRACT_VERSION, type RealtimeChannelDescriptor } from '@memberjunction/ai-core-plus';\nimport { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';\nimport { ChannelOnboardingDetails } from '@memberjunction/realtime-runtime';\nimport { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';\nimport { MediaChannelState, MediaHighlight, MediaItemType } from './media-channel-state';\nimport { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';\n\n/** A JSON-serializable value (the building block of a parsed tool-args object). */\ntype ToolArgValue = string | number | boolean | null | ToolArgValue[] | { [key: string]: ToolArgValue };\n\n/** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */\nfunction asString(value: ToolArgValue | undefined): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerces a parsed arg to a finite number, or `undefined` when absent / wrong-typed. */\nfunction asNumber(value: ToolArgValue | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** The media types `Media_ShowMedia` accepts (validated before mutating state). */\nconst VALID_MEDIA_TYPES: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n\n/**\n * The shared MEDIA SURFACE as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeMediaChannel'`. One instance per session (created via ClassFactory at session start).\n *\n * It owns a {@link MediaChannelState} engine and contributes the channel's full contract:\n *\n * - **Action**: the `Media_*` client-executed tool set ({@link MEDIA_TOOL_DEFINITIONS});\n * {@link ApplyAgentTool} parses the args, mutates the state engine, returns a short JSON result\n * string, and sends the model a `[media]` background context note (so it stays aware of what's\n * on screen). Works with NO surface bound — the tools mutate the state engine directly.\n * - **Surface**: {@link RealtimeMediaSurfaceComponent}, created dynamically by the overlay's\n * channel tab; {@link BindSurface} hands it the shared state engine + agent name.\n * - **State of record**: every mutation requests a debounced save of {@link MediaChannelState.ToJSON}\n * under channel name `'Media'`; a prior session's media is rehydrated via {@link RestoreState}.\n *\n * Modeled directly on `RealtimeWhiteboardChannel`.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeMediaChannel')\nexport class RealtimeMediaChannel extends BaseRealtimeChannelClient<RealtimeMediaSurfaceComponent> {\n /** The media state of record — created fresh with the plugin (one per session). */\n public readonly State = new MediaChannelState();\n\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RealtimeMediaSurfaceComponent | null = null;\n /** Media-mutation subscription driving the debounced state-of-record save. */\n private stateChangedSub: Subscription | null = null;\n\n public get ChannelName(): string {\n return 'Media';\n }\n\n public get ToolNamePrefix(): string {\n return MEDIA_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Media';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-images';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return MEDIA_TOOL_DEFINITIONS;\n }\n\n /**\n * The media surface's self-description: what it holds, and every `Media_*` tool as a verb (so a tool a\n * subclass adds shows up here too). The model reads its state (what is on screen) but is never shown\n * pixels of it, so its exposure ceiling is `state`.\n */\n public override GetDescriptor(): RealtimeChannelDescriptor {\n return {\n Key: this.ChannelName,\n Version: REALTIME_CHANNEL_CONTRACT_VERSION,\n DisplayName: this.TabTitle,\n OwningPackage: '@memberjunction/ng-conversations',\n Instructions:\n 'A shared surface where you can show the user images, videos, audio clips, PDFs and web pages. ' +\n 'Each item opens in its own tab; you can switch between them and highlight a region to point at it.',\n Nouns: [\n {\n Name: 'items',\n Description: 'The media items currently shared, each with its type, title and source.',\n Schema: { type: 'array', items: { type: 'object' } }\n }\n ],\n Verbs: BuildToolBackedVerbs(this.GetToolDefinitions(), this.ToolNamePrefix),\n DisplayPolicy: 'open-on-start',\n DefaultAvailability: 'all-sessions',\n MaxExposure: 'state'\n };\n }\n\n public override GetSurfaceComponent(): Type<RealtimeMediaSurfaceComponent> {\n return RealtimeMediaSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Media tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Media',\n Description:\n 'A shared surface where the agent can show you visuals during the call — images, videos, ' +\n 'audio clips, PDFs and web pages. Each item opens in its own tab; the agent can switch ' +\n 'between them and highlight regions to point at exactly what it means.',\n Tips: [\n 'Switch between shared items using the tabs along the top.',\n 'The agent can highlight part of an image or page to draw your eye to it.',\n 'Open any item in a new browser tab or download it from the toolbar.',\n ],\n IconClass: 'fa-solid fa-images',\n };\n }\n\n /** Persist the media surface (host-debounced) on EVERY mutation — agent tools and tab changes. */\n protected override OnInitialize(): void {\n this.stateChangedSub = this.State.Changed$.subscribe(() => {\n this.Context?.RequestSave(this.State.ToJSON());\n });\n }\n\n /**\n * Wires the dynamically-created media surface (before first CD): shared state engine + agent name,\n * plus the live session's MJ provider so FileID-backed items stream securely (mj-storage-media-player\n * + CreateMediaAccessToken) under the SAME authenticated, multi-provider-safe provider.\n */\n public BindSurface(instance: RealtimeMediaSurfaceComponent): void {\n this.surface = instance;\n instance.State = this.State;\n instance.AgentName = this.Context?.AgentName ?? 'The agent';\n instance.Provider = this.Context?.Provider ?? null;\n }\n\n public override UnbindSurface(): void {\n this.surface = null;\n }\n\n /**\n * Executes one `Media_*` tool call LOCALLY: parse args, mutate the state engine, push a `[media]`\n * background context note so the agent stays aware of the surface, and return a short JSON result\n * string for the model. Never throws — malformed args resolve to a `{ success: false, error }`\n * payload the model can narrate. Works with or without a bound surface (it mutates the state engine,\n * which the surface observes).\n */\n public ApplyAgentTool(toolName: string, argsJson: string): string {\n let args: Record<string, ToolArgValue> = {};\n try {\n const parsed = argsJson ? (JSON.parse(argsJson) as unknown) : {};\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n args = parsed as Record<string, ToolArgValue>;\n }\n } catch {\n return this.fail(`Could not parse arguments for ${toolName}.`);\n }\n\n switch (toolName) {\n case MEDIA_TOOL_NAMES.ShowMedia:\n return this.applyShowMedia(args);\n case MEDIA_TOOL_NAMES.CloseMedia:\n return this.applyCloseMedia(args);\n case MEDIA_TOOL_NAMES.PlayMedia:\n return this.applyPlayMedia(args);\n case MEDIA_TOOL_NAMES.Highlight:\n return this.applyHighlight(args);\n case MEDIA_TOOL_NAMES.ClearAll:\n return this.applyClearAll();\n default:\n return this.fail(`Unknown Media tool \"${toolName}\".`);\n }\n }\n\n /** Adds a media item and makes it active. Source is a public `url` OR an MJStorage `fileId` (>=1 required). */\n private applyShowMedia(args: Record<string, ToolArgValue>): string {\n const mediaType = asString(args['mediaType']);\n const url = asString(args['url']);\n const fileId = asString(args['fileId']);\n const displayName = asString(args['displayName']);\n if (!mediaType || !VALID_MEDIA_TYPES.includes(mediaType as MediaItemType)) {\n return this.fail('Media_ShowMedia requires a \"mediaType\" of image | video | audio | pdf | web.');\n }\n if (!url && !fileId) {\n return this.fail('Media_ShowMedia requires a source — provide a \"url\" (external/public) OR a \"fileId\" (an MJ: Files id).');\n }\n if (!displayName) {\n return this.fail('Media_ShowMedia requires a \"displayName\".');\n }\n const item = this.State.AddItem({\n Type: mediaType as MediaItemType,\n Url: url,\n FileID: fileId,\n DisplayName: displayName,\n Caption: asString(args['caption']),\n });\n this.note(`now showing ${mediaType} \"${displayName}\"`);\n return JSON.stringify({ success: true, id: item.Id, active: true });\n }\n\n /** Closes a media item. */\n private applyCloseMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_CloseMedia requires an \"id\".');\n }\n const removed = this.State.RemoveItem(id);\n if (!removed) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`closed media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Activates + requests playback of a media item. */\n private applyPlayMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_PlayMedia requires an \"id\".');\n }\n const ok = this.State.RequestPlay(id);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`playing media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Draws (or, with absent dims, clears) a fractional highlight on a media item. */\n private applyHighlight(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_Highlight requires an \"id\".');\n }\n const x = asNumber(args['x']);\n const y = asNumber(args['y']);\n const w = asNumber(args['w']);\n const h = asNumber(args['h']);\n if (x === undefined || y === undefined || w === undefined || h === undefined) {\n return this.fail('Media_Highlight requires numeric fractional x, y, w, h (0..1).');\n }\n const highlight: MediaHighlight = { X: x, Y: y, W: w, H: h, Label: asString(args['label']) };\n const ok = this.State.Highlight(id, highlight);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`highlighted a region of media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Clears all media. */\n private applyClearAll(): string {\n this.State.Clear();\n this.note('cleared all media');\n return JSON.stringify({ success: true });\n }\n\n /** The Media channel's serialized state of record (persisted under {@link ChannelName}). */\n public override SerializeState(): string | null {\n return this.State.ToJSON();\n }\n\n /**\n * Rehydrates a prior session's saved media into THIS session's state engine (in place — the\n * {@link State} instance and its subscriptions are preserved). Tolerant: malformed JSON returns\n * `false` and the surface stays empty.\n */\n public override RestoreState(stateJson: string): boolean {\n return this.State.LoadFromJSON(stateJson);\n }\n\n public override Dispose(): void {\n this.stateChangedSub?.unsubscribe();\n this.stateChangedSub = null;\n this.surface = null;\n super.Dispose();\n }\n\n /** Builds a model-readable failure payload (never thrown — returned to the model). */\n private fail(error: string): string {\n return JSON.stringify({ success: false, error });\n }\n\n /**\n * Pushes a background context note so the agent stays aware of the surface state. Framed as\n * background (like the whiteboard's scene-delta notes) so the model doesn't narrate every change.\n */\n private note(summary: string): void {\n this.Context?.SendContextNote(\n `[media] ${summary} (background context — do NOT narrate routine changes; mention only if relevant).`,\n );\n }\n}\n\n/**\n * Tree-shaking prevention: the Media channel is resolved dynamically through the ClassFactory (by the\n * registry row's `ClientPluginClass` key), so this static call keeps its `@RegisterClass` side effect\n * from being eliminated by the bundler. Called alongside `LoadRealtimeWhiteboardChannel()`.\n */\nexport function LoadRealtimeMediaChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
|
|
1
|
+
{"version":3,"file":"media-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { RealtimeToolDefinition } from '@memberjunction/ai';\nimport { REALTIME_CHANNEL_CONTRACT_VERSION, type RealtimeChannelDescriptor } from '@memberjunction/ai-core-plus';\nimport { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';\nimport { ChannelOnboardingDetails } from '@memberjunction/realtime-runtime';\nimport { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';\nimport { IsAllowedExternalMediaUrl, MediaChannelState, MediaHighlight, MediaItemType } from './media-channel-state';\nimport { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';\n\n/** A JSON-serializable value (the building block of a parsed tool-args object). */\ntype ToolArgValue = string | number | boolean | null | ToolArgValue[] | { [key: string]: ToolArgValue };\n\n/** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */\nfunction asString(value: ToolArgValue | undefined): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerces a parsed arg to a finite number, or `undefined` when absent / wrong-typed. */\nfunction asNumber(value: ToolArgValue | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** The media types `Media_ShowMedia` accepts (validated before mutating state). */\nconst VALID_MEDIA_TYPES: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n\n/**\n * The shared MEDIA SURFACE as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeMediaChannel'`. One instance per session (created via ClassFactory at session start).\n *\n * It owns a {@link MediaChannelState} engine and contributes the channel's full contract:\n *\n * - **Action**: the `Media_*` client-executed tool set ({@link MEDIA_TOOL_DEFINITIONS});\n * {@link ApplyAgentTool} parses the args, mutates the state engine, returns a short JSON result\n * string, and sends the model a `[media]` background context note (so it stays aware of what's\n * on screen). Works with NO surface bound — the tools mutate the state engine directly.\n * - **Surface**: {@link RealtimeMediaSurfaceComponent}, created dynamically by the overlay's\n * channel tab; {@link BindSurface} hands it the shared state engine + agent name.\n * - **State of record**: every mutation requests a debounced save of {@link MediaChannelState.ToJSON}\n * under channel name `'Media'`; a prior session's media is rehydrated via {@link RestoreState}.\n *\n * Modeled directly on `RealtimeWhiteboardChannel`.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeMediaChannel')\nexport class RealtimeMediaChannel extends BaseRealtimeChannelClient<RealtimeMediaSurfaceComponent> {\n /** The media state of record — created fresh with the plugin (one per session). */\n public readonly State = new MediaChannelState();\n\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RealtimeMediaSurfaceComponent | null = null;\n /** Media-mutation subscription driving the debounced state-of-record save. */\n private stateChangedSub: Subscription | null = null;\n\n public get ChannelName(): string {\n return 'Media';\n }\n\n public get ToolNamePrefix(): string {\n return MEDIA_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Media';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-images';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return MEDIA_TOOL_DEFINITIONS;\n }\n\n /**\n * The media surface's self-description: what it holds, and every `Media_*` tool as a verb (so a tool a\n * subclass adds shows up here too). The model reads its state (what is on screen) but is never shown\n * pixels of it, so its exposure ceiling is `state`.\n */\n public override GetDescriptor(): RealtimeChannelDescriptor {\n return {\n Key: this.ChannelName,\n Version: REALTIME_CHANNEL_CONTRACT_VERSION,\n DisplayName: this.TabTitle,\n OwningPackage: '@memberjunction/ng-conversations',\n Instructions:\n 'A shared surface where you can show the user images, videos, audio clips, PDFs and web pages. ' +\n 'Each item opens in its own tab; you can switch between them and highlight a region to point at it.',\n Nouns: [\n {\n Name: 'items',\n Description: 'The media items currently shared, each with its type, title and source.',\n Schema: { type: 'array', items: { type: 'object' } }\n }\n ],\n Verbs: BuildToolBackedVerbs(this.GetToolDefinitions(), this.ToolNamePrefix),\n DisplayPolicy: 'open-on-start',\n DefaultAvailability: 'all-sessions',\n MaxExposure: 'state'\n };\n }\n\n public override GetSurfaceComponent(): Type<RealtimeMediaSurfaceComponent> {\n return RealtimeMediaSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Media tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Media',\n Description:\n 'A shared surface where the agent can show you visuals during the call — images, videos, ' +\n 'audio clips, PDFs and web pages. Each item opens in its own tab; the agent can switch ' +\n 'between them and highlight regions to point at exactly what it means.',\n Tips: [\n 'Switch between shared items using the tabs along the top.',\n 'The agent can highlight part of an image or page to draw your eye to it.',\n 'Open any item in a new browser tab or download it from the toolbar.',\n ],\n IconClass: 'fa-solid fa-images',\n };\n }\n\n /** Persist the media surface (host-debounced) on EVERY mutation — agent tools and tab changes. */\n protected override OnInitialize(): void {\n this.stateChangedSub = this.State.Changed$.subscribe(() => {\n this.Context?.RequestSave(this.State.ToJSON());\n });\n }\n\n /**\n * Wires the dynamically-created media surface (before first CD): shared state engine + agent name,\n * plus the live session's MJ provider so FileID-backed items stream securely (mj-storage-media-player\n * + CreateMediaAccessToken) under the SAME authenticated, multi-provider-safe provider.\n */\n public BindSurface(instance: RealtimeMediaSurfaceComponent): void {\n this.surface = instance;\n instance.State = this.State;\n instance.AgentName = this.Context?.AgentName ?? 'The agent';\n instance.Provider = this.Context?.Provider ?? null;\n }\n\n public override UnbindSurface(): void {\n this.surface = null;\n }\n\n /**\n * Executes one `Media_*` tool call LOCALLY: parse args, mutate the state engine, push a `[media]`\n * background context note so the agent stays aware of the surface, and return a short JSON result\n * string for the model. Never throws — malformed args resolve to a `{ success: false, error }`\n * payload the model can narrate. Works with or without a bound surface (it mutates the state engine,\n * which the surface observes).\n */\n public ApplyAgentTool(toolName: string, argsJson: string): string {\n let args: Record<string, ToolArgValue> = {};\n try {\n const parsed = argsJson ? (JSON.parse(argsJson) as unknown) : {};\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n args = parsed as Record<string, ToolArgValue>;\n }\n } catch {\n return this.fail(`Could not parse arguments for ${toolName}.`);\n }\n\n switch (toolName) {\n case MEDIA_TOOL_NAMES.ShowMedia:\n return this.applyShowMedia(args);\n case MEDIA_TOOL_NAMES.CloseMedia:\n return this.applyCloseMedia(args);\n case MEDIA_TOOL_NAMES.PlayMedia:\n return this.applyPlayMedia(args);\n case MEDIA_TOOL_NAMES.Highlight:\n return this.applyHighlight(args);\n case MEDIA_TOOL_NAMES.ClearAll:\n return this.applyClearAll();\n default:\n return this.fail(`Unknown Media tool \"${toolName}\".`);\n }\n }\n\n /** Adds a media item and makes it active. Source is a public `url` OR an MJStorage `fileId` (>=1 required). */\n private applyShowMedia(args: Record<string, ToolArgValue>): string {\n const mediaType = asString(args['mediaType']);\n const url = asString(args['url']);\n const fileId = asString(args['fileId']);\n const displayName = asString(args['displayName']);\n if (!mediaType || !VALID_MEDIA_TYPES.includes(mediaType as MediaItemType)) {\n return this.fail('Media_ShowMedia requires a \"mediaType\" of image | video | audio | pdf | web.');\n }\n if (!url && !fileId) {\n return this.fail('Media_ShowMedia requires a source — provide a \"url\" (external/public) OR a \"fileId\" (an MJ: Files id).');\n }\n // SECURITY: the url arg is model-supplied and is later rendered in an img/iframe/player, so\n // only well-formed absolute http(s) URLs are accepted — never javascript:, data:, blob:, or scheme-relative.\n if (url && !IsAllowedExternalMediaUrl(url)) {\n return this.fail('Media_ShowMedia \"url\" must be an absolute http:// or https:// URL.');\n }\n if (!displayName) {\n return this.fail('Media_ShowMedia requires a \"displayName\".');\n }\n const item = this.State.AddItem({\n Type: mediaType as MediaItemType,\n Url: url ? url.trim() : undefined,\n FileID: fileId,\n DisplayName: displayName,\n Caption: asString(args['caption']),\n });\n this.note(`now showing ${mediaType} \"${displayName}\"`);\n return JSON.stringify({ success: true, id: item.Id, active: true });\n }\n\n /** Closes a media item. */\n private applyCloseMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_CloseMedia requires an \"id\".');\n }\n const removed = this.State.RemoveItem(id);\n if (!removed) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`closed media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Activates + requests playback of a media item. */\n private applyPlayMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_PlayMedia requires an \"id\".');\n }\n const ok = this.State.RequestPlay(id);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`playing media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Draws (or, with absent dims, clears) a fractional highlight on a media item. */\n private applyHighlight(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_Highlight requires an \"id\".');\n }\n const x = asNumber(args['x']);\n const y = asNumber(args['y']);\n const w = asNumber(args['w']);\n const h = asNumber(args['h']);\n if (x === undefined || y === undefined || w === undefined || h === undefined) {\n return this.fail('Media_Highlight requires numeric fractional x, y, w, h (0..1).');\n }\n const highlight: MediaHighlight = { X: x, Y: y, W: w, H: h, Label: asString(args['label']) };\n const ok = this.State.Highlight(id, highlight);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`highlighted a region of media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Clears all media. */\n private applyClearAll(): string {\n this.State.Clear();\n this.note('cleared all media');\n return JSON.stringify({ success: true });\n }\n\n /** The Media channel's serialized state of record (persisted under {@link ChannelName}). */\n public override SerializeState(): string | null {\n return this.State.ToJSON();\n }\n\n /**\n * Rehydrates a prior session's saved media into THIS session's state engine (in place — the\n * {@link State} instance and its subscriptions are preserved). Tolerant: malformed JSON returns\n * `false` and the surface stays empty.\n */\n public override RestoreState(stateJson: string): boolean {\n return this.State.LoadFromJSON(stateJson);\n }\n\n public override Dispose(): void {\n this.stateChangedSub?.unsubscribe();\n this.stateChangedSub = null;\n this.surface = null;\n super.Dispose();\n }\n\n /** Builds a model-readable failure payload (never thrown — returned to the model). */\n private fail(error: string): string {\n return JSON.stringify({ success: false, error });\n }\n\n /**\n * Pushes a background context note so the agent stays aware of the surface state. Framed as\n * background (like the whiteboard's scene-delta notes) so the model doesn't narrate every change.\n */\n private note(summary: string): void {\n this.Context?.SendContextNote(\n `[media] ${summary} (background context — do NOT narrate routine changes; mention only if relevant).`,\n );\n }\n}\n\n/**\n * Tree-shaking prevention: the Media channel is resolved dynamically through the ClassFactory (by the\n * registry row's `ClientPluginClass` key), so this static call keeps its `@RegisterClass` side effect\n * from being eliminated by the bundler. Called alongside `LoadRealtimeWhiteboardChannel()`.\n */\nexport function LoadRealtimeMediaChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
|
|
@@ -18,6 +18,13 @@ export type FileResolveState = {
|
|
|
18
18
|
export type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';
|
|
19
19
|
/** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */
|
|
20
20
|
export declare function IsPlayerMediaType(item: MediaItem): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and
|
|
23
|
+
* is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,
|
|
24
|
+
* `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated
|
|
25
|
+
* session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.
|
|
26
|
+
*/
|
|
27
|
+
export declare function IsEmbeddableSurfaceUrl(url: string | null | undefined, baseOrigin: string): boolean;
|
|
21
28
|
/**
|
|
22
29
|
* Decides which render path an item uses based ONLY on its source + type — the single source of truth
|
|
23
30
|
* the surface methods delegate to (kept pure so it's unit-testable without an Angular context):
|
|
@@ -86,7 +93,8 @@ export declare class RealtimeMediaSurfaceComponent extends BaseAngularComponent
|
|
|
86
93
|
TracksFor(item: MediaItem): MediaTrack[];
|
|
87
94
|
/**
|
|
88
95
|
* The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a
|
|
89
|
-
* FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access
|
|
96
|
+
* FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /
|
|
97
|
+
* if the URL fails http(s) scheme screening).
|
|
90
98
|
*/
|
|
91
99
|
RenderUrl(item: MediaItem): string | null;
|
|
92
100
|
/** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */
|
|
@@ -109,8 +117,17 @@ export declare class RealtimeMediaSurfaceComponent extends BaseAngularComponent
|
|
|
109
117
|
CycleZoom(): void;
|
|
110
118
|
/** The Font Awesome icon class for a media type's tab. */
|
|
111
119
|
IconFor(type: MediaItem['Type']): string;
|
|
112
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* A memoized sanitized resource URL for iframe-rendered media (web / pdf).
|
|
122
|
+
*
|
|
123
|
+
* SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can
|
|
124
|
+
* originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or
|
|
125
|
+
* relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever
|
|
126
|
+
* trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.
|
|
127
|
+
*/
|
|
113
128
|
SafeUrl(url: string): SafeResourceUrl;
|
|
129
|
+
/** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */
|
|
130
|
+
private isEmbeddableUrl;
|
|
114
131
|
/** Inline style for the fractional highlight rectangle (percentages of the pane box). */
|
|
115
132
|
HighlightStyle(item: MediaItem): Record<string, string> | null;
|
|
116
133
|
/** Stable `@for` track for tab/pane diffing. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-media-surface.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACqE,SAAS,EAAE,MAAM,EAE5F,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAgB,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAI1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAyD,UAAU,EAAE,MAAM,iCAAiC,CAAC;AACpH,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;;AA0BrE,gHAAgH;AAChH,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,GAAG,YAAY,GAAG,eAAe,GAAG,MAAM,CAAC;AAE1F,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,gBAAgB,CAWnE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAQa,6BAA8B,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS;IAClG,wFAAwF;IACxF,IACI,KAAK,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAMxC;IACD,IAAI,KAAK,IAAI,iBAAiB,GAAG,IAAI,CAEpC;IACD,OAAO,CAAC,MAAM,CAAkC;IAEhD,qFAAqF;IAC5E,SAAS,SAAe;IAEjC,uFAAuF;IAChF,SAAS,SAAK;IAErB,mGAAmG;IACnG,OAAO,CAAC,YAAY,CAAsC;IAE1D,wFAAwF;IACxF,OAAO,CAAC,eAAe,CAA6B;IAEpD;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAuC;IAExC,OAAO,CAAC,SAAS,CAAC,CAA0B;IAEpE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAA6B;IAE7C,QAAQ,IAAI,IAAI;IAIhB,WAAW,IAAI,IAAI;IAKnB,yEAAyE;IACzE,IAAW,KAAK,IAAI,SAAS,EAAE,CAE9B;IAED,4DAA4D;IAC5D,IAAW,UAAU,IAAI,SAAS,GAAG,IAAI,CAExC;IAED,2EAA2E;IACpE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAQzC,yEAAyE;IAClE,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAI7C,uGAAuG;IAChG,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAIlD,wFAAwF;IACjF,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAI9C,mFAAmF;IAC5E,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE;IAO/C
|
|
1
|
+
{"version":3,"file":"realtime-media-surface.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -230,6 +230,28 @@ const CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fil
|
|
|
230
230
|
export function IsPlayerMediaType(item) {
|
|
231
231
|
return item.Type === 'audio' || item.Type === 'video';
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and
|
|
235
|
+
* is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,
|
|
236
|
+
* `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated
|
|
237
|
+
* session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.
|
|
238
|
+
*/
|
|
239
|
+
export function IsEmbeddableSurfaceUrl(url, baseOrigin) {
|
|
240
|
+
if (typeof url !== 'string') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const trimmed = url.trim();
|
|
244
|
+
if (!trimmed || trimmed.startsWith('//')) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const parsed = new URL(trimmed, baseOrigin);
|
|
249
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
233
255
|
/**
|
|
234
256
|
* Decides which render path an item uses based ONLY on its source + type — the single source of truth
|
|
235
257
|
* the surface methods delegate to (kept pure so it's unit-testable without an Angular context):
|
|
@@ -335,24 +357,33 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
|
|
|
335
357
|
}
|
|
336
358
|
/** The single-track payload for the URL-backed `mj-media-player` (audio/video). */
|
|
337
359
|
TracksFor(item) {
|
|
338
|
-
if (!item.Url) {
|
|
360
|
+
if (!item.Url || !this.isEmbeddableUrl(item.Url)) {
|
|
339
361
|
return [];
|
|
340
362
|
}
|
|
341
|
-
return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url }];
|
|
363
|
+
return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url.trim() }];
|
|
342
364
|
}
|
|
343
365
|
/**
|
|
344
366
|
* The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a
|
|
345
|
-
* FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access
|
|
367
|
+
* FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /
|
|
368
|
+
* if the URL fails http(s) scheme screening).
|
|
346
369
|
*/
|
|
347
370
|
RenderUrl(item) {
|
|
371
|
+
let candidate = null;
|
|
348
372
|
if (item.Url) {
|
|
349
|
-
|
|
373
|
+
candidate = item.Url;
|
|
350
374
|
}
|
|
351
|
-
if (item.FileID) {
|
|
375
|
+
else if (item.FileID) {
|
|
352
376
|
const state = this.fileResolveByItem.get(item.Id);
|
|
353
|
-
|
|
377
|
+
candidate = state?.Status === 'ready' ? state.Url : null;
|
|
354
378
|
}
|
|
355
|
-
|
|
379
|
+
if (!candidate) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
if (!this.isEmbeddableUrl(candidate)) {
|
|
383
|
+
LogError('RealtimeMediaSurface: refused media URL with a non-http(s) or scheme-relative target.');
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
return candidate.trim();
|
|
356
387
|
}
|
|
357
388
|
/** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */
|
|
358
389
|
IsResolving(item) {
|
|
@@ -436,15 +467,33 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
|
|
|
436
467
|
return 'fa-solid fa-file';
|
|
437
468
|
}
|
|
438
469
|
}
|
|
439
|
-
/**
|
|
470
|
+
/**
|
|
471
|
+
* A memoized sanitized resource URL for iframe-rendered media (web / pdf).
|
|
472
|
+
*
|
|
473
|
+
* SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can
|
|
474
|
+
* originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or
|
|
475
|
+
* relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever
|
|
476
|
+
* trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.
|
|
477
|
+
*/
|
|
440
478
|
SafeUrl(url) {
|
|
441
479
|
let cached = this.safeUrlCache.get(url);
|
|
442
480
|
if (!cached) {
|
|
443
|
-
|
|
481
|
+
const target = this.isEmbeddableUrl(url) ? url : 'about:blank';
|
|
482
|
+
if (target !== url) {
|
|
483
|
+
LogError(`RealtimeMediaSurface: refused to embed media URL with a non-http(s) scheme.`);
|
|
484
|
+
}
|
|
485
|
+
cached = this.sanitizer.bypassSecurityTrustResourceUrl(target);
|
|
444
486
|
this.safeUrlCache.set(url, cached);
|
|
445
487
|
}
|
|
446
488
|
return cached;
|
|
447
489
|
}
|
|
490
|
+
/** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */
|
|
491
|
+
isEmbeddableUrl(url) {
|
|
492
|
+
const origin = typeof window !== 'undefined' && window.location?.origin
|
|
493
|
+
? window.location.origin
|
|
494
|
+
: 'https://localhost';
|
|
495
|
+
return IsEmbeddableSurfaceUrl(url, origin);
|
|
496
|
+
}
|
|
448
497
|
/** Inline style for the fractional highlight rectangle (percentages of the pane box). */
|
|
449
498
|
HighlightStyle(item) {
|
|
450
499
|
const h = item.Highlight;
|
|
@@ -570,5 +619,5 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
|
|
|
570
619
|
type: ViewChild,
|
|
571
620
|
args: ['mediaPane']
|
|
572
621
|
}] }); })();
|
|
573
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeMediaSurfaceComponent, { className: "RealtimeMediaSurfaceComponent", filePath: "src/lib/components/realtime/media/realtime-media-surface.component.ts", lineNumber:
|
|
622
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeMediaSurfaceComponent, { className: "RealtimeMediaSurfaceComponent", filePath: "src/lib/components/realtime/media/realtime-media-surface.component.ts", lineNumber: 122 }); })();
|
|
574
623
|
//# sourceMappingURL=realtime-media-surface.component.js.map
|
|
@@ -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":"AAAA,OAAO,EACL,uBAAuB,EAAE,iBAAiB,EAAE,SAAS,EAAc,KAAK,EACxE,SAAS,EAAE,MAAM,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAmB,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAc,MAAM,iCAAiC,CAAC;;;;;ICRhH,8BAAyB;IACvB,uBAAuE;IACvE,4BAA8B;IAAA,4BAAY;IAAA,iBAAI;IAC9C,4BAA6B;IAAA,YAAgE;IAC/F,AAD+F,iBAAI,EAC7F;;;IADyB,eAAgE;IAAhE,gGAAgE;;;;IAM3F,iCAUC;IADC,AADA,kOAAS,yBAAe,KAAC,kOACd,0CAAuB,KAAC;IAEnC,uBAAiF;IACjF,gCAA8B;IAAA,YAAsB;IAAA,iBAAO;IAC3D,gCAQC;IADC,AADA,AADA,sOAAS,iCAAuB,KAAC,yOAChB,iCAAuB,KAAC,yOACxB,iCAAuB,KAAC;IAEzC,wBAAoD;IAExD,AADE,iBAAO,EACA;;;;IApBP,6DAA0C;IAG1C,2CAA0B;;IAIC,cAA8B;IAA9B,sDAA8B;IAC3B,eAAsB;IAAtB,yCAAsB;IAKlD,cAA+C;;;;;IAiBjD,kCAMC;IADC,8NAAS,kBAAW,KAAC;IAErB,wBAAoE;IACpE,gCAAmC;IAAA,YAAgB;IACrD,AADqD,iBAAO,EACnD;;;;IAD4B,eAAgB;IAAhB,qDAAgB;;;;IAIrD,kCAMC;IADC,oQAAS,0BAAgB,KAAC;IAE1B,wBAAuD;IACzD,iBAAS;IACT,kCAMC;IADC,oQAAS,2BAAiB,KAAC;IAE3B,wBAAyE;IAC3E,iBAAS;;;IASP,8CAI2B;;;;IADzB,AADA,iDAAgC,6BACX;;;IAIvB,sCAAqF;;;;IAA/C,oDAA4B;;;IAElE,+BAA0B;IACxB,wBAAiF;IACjF,6BAA8B;IAAA,0CAAqB;IACrD,AADqD,iBAAI,EACnD;;;IAEN,+BAA8C;IAC5C,wBAAsF;IACtF,6BAA8B;IAAA,YAAS;IACzC,AADyC,iBAAI,EACvC;;IAD0B,eAAS;IAAT,yBAAS;;;IAKrC,0BAKE;;;;;IAFA,8DAA8C;IAC9C,AAFA,oDAAiB,8BAES;;;IAI5B,6BAIU;;;;;IADR,AADA,4EAA0B,gCACE;;;IAI9B,6BAMU;;;;;IAHR,AADA,4EAA0B,gCACE;;;IAJhC,AAPA,AARA,oIAAiB,iHAQF,iHAOA;;;;IAhBjB,gDAAA,OAAO,mBAAP,KAAK,mBAAL,KAAK,UAyBJ;;;IAMG,gCAAqC;IAAA,YAA6B;IAAA,iBAAO;;;IAApC,cAA6B;IAA7B,oFAA6B;;;IAFtE,+BAA+D;IAC7D,4IAA+B;IAGjC,iBAAM;;;IAJuB,6BAAc;IACzC,cAEC;IAFD,2FAEC;;;IAML,6BAAyB;IAAA,YAAoB;IAAA,iBAAI;;;IAAxB,cAAoB;IAApB,uCAAoB;;;IAlG/C,AADF,+BAA2B,eACuC;IAAA,YAAwB;IAAA,iBAAO;IAC/F,2BAA2C;IAC3C,+HAA+B;IAY/B,iHAAiC;IAoBnC,iBAAM;IAIJ,AADF,kCAAmC,cAC+D;IAqB5F,AALA,AALA,AAHA,AAPF,gJAAiC,oHAOG,wGAGF,wGAKS,6FAKG;IA6B9C,8HAAqC;IAOvC,iBAAM;IAEN,4HAAsB;IAGxB,iBAAM;;;;;;IApG+B,cAA4B;IAA5B,6CAA4B;IAAC,cAAwB;IAAxB,2CAAwB;IAExF,eAWC;IAXD,qDAWC;IACD,cAmBC;IAnBD,8DAmBC;IAKwB,eAAsE;IAAtE,yFAAsE;IAC7F,cA+CC;IA/CD,oPA+CC;IAED,eAMC;IAND,mFAMC;IAGH,cAEC;IAFD,6CAEC;;;IApIL,8BAAiE;IAC/D,+IA0BC;IACH,iBAAM;IAEN,oGAA6B;;;;IA7B3B,cA0BC;IA1BD,2BA0BC;IAGH,eAwGC;IAxGD,iEAwGC;;ADlIL,wDAAwD;AACxD,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAU,CAAC;AASlD;;;;;GAKG;AACH,MAAM,kCAAkC,GAAG;;;;;;EAMzC,CAAC;AAWH,iGAAiG;AACjG,MAAM,UAAU,iBAAiB,CAAC,IAAe;IAC/C,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAe;IAC/C,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,gBAAgB,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1C,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AASH,MAAM,OAAO,6BAA8B,SAAQ,oBAAoB;IACrE,wFAAwF;IACxF,IACI,KAAK,CAAC,KAA+B;QACvC,IAAI,KAAK,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IACD,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACO,MAAM,GAA6B,IAAI,CAAC;IAEhD,qFAAqF;IAC5E,SAAS,GAAG,WAAW,CAAC;IAEjC,uFAAuF;IAChF,SAAS,GAAG,CAAC,CAAC;IAErB,mGAAmG;IAC3F,YAAY,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE1D,wFAAwF;IAChF,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEpD;;;OAGG;IACK,iBAAiB,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEhC,SAAS,CAA2B;IAEnD,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1C,QAAQ,GAAwB,IAAI,CAAC;IAE7C,QAAQ;QACN,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAED,WAAW;QACT,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,yEAAyE;IACzE,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;IAClC,CAAC;IAED,4DAA4D;IAC5D,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI,CAAC;IACzC,CAAC;IAED,2EAA2E;IACpE,QAAQ,CAAC,IAAe;QAC7B,OAAO,IAAI,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,8EAA8E;IAC9E,kDAAkD;IAClD,8EAA8E;IAE9E,yEAAyE;IAClE,YAAY,CAAC,IAAe;QACjC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,uGAAuG;IAChG,iBAAiB,CAAC,IAAe;QACtC,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,gBAAgB,CAAC;IACtD,CAAC;IAED,wFAAwF;IACjF,aAAa,CAAC,IAAe;QAClC,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,YAAY,CAAC;IAClD,CAAC;IAED,mFAAmF;IAC5E,SAAS,CAAC,IAAe;QAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;OAGG;IACI,SAAS,CAAC,IAAe;QAC9B,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAClD,OAAO,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uFAAuF;IAChF,WAAW,CAAC,IAAe;QAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,CAAC;IACjG,CAAC;IAED,8FAA8F;IACvF,YAAY,CAAC,IAAe;QACjC,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IAED,yCAAyC;IAClC,SAAS,CAAC,IAAe;QAC9B,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,6FAA6F;IACtF,YAAY,CAAC,KAAoB,EAAE,KAAa;QACrD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;YAC5D,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,IAAI,GAAG,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YAC3D,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YACvC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,sDAAsD;IAC/C,SAAS,CAAC,KAAY,EAAE,IAAe;QAC5C,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,uGAAuG;IAChG,iBAAiB,CAAC,IAAe;QACtC,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,0FAA0F;IACnF,SAAS,CAAC,IAAe;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,uGAAuG;IAChG,QAAQ,CAAC,IAAe;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC;QAClB,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;QACzC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,qBAAqB,CAAC;QACnC,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAED,0EAA0E;IACnE,SAAS;QACd,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,SAA+C,CAAC,CAAC;QAC5F,IAAI,CAAC,SAAS,GAAG,iBAAiB,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC;IAED,0DAA0D;IACnD,OAAO,CAAC,IAAuB;QACpC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,OAAO;gBACV,OAAO,mBAAmB,CAAC;YAC7B,KAAK,OAAO;gBACV,OAAO,kBAAkB,CAAC;YAC5B,KAAK,OAAO;gBACV,OAAO,mBAAmB,CAAC;YAC7B,KAAK,KAAK;gBACR,OAAO,sBAAsB,CAAC;YAChC,KAAK,KAAK;gBACR,OAAO,mBAAmB,CAAC;YAC7B;gBACE,OAAO,kBAAkB,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,+EAA+E;IACxE,OAAO,CAAC,GAAW;QACxB,IAAI,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,8BAA8B,CAAC,GAAG,CAAC,CAAC;YAC5D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,yFAAyF;IAClF,cAAc,CAAC,IAAe;QACnC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;QAC3E,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,gDAAgD;IACzC,SAAS,CAAC,MAAc,EAAE,IAAe;QAC9C,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,2FAA2F;IACnF,WAAW;QACjB,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;YAClD,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC9B,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,sBAAsB;QAC5B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxD,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxC,SAAS;YACX,CAAC;YACD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAC3D,KAAK,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,kGAAkG;IAC1F,KAAK,CAAC,cAAc,CAAC,MAAc,EAAE,MAAc;QACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YAC3C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE;gBACjC,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,MAAM,EAAE,YAAY,IAAI,gEAAgE;aAClG,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAED,0FAA0F;IAClF,KAAK,CAAC,eAAe,CAAC,MAAc;QAC1C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,aAA+C,CAAC;YACtE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,kCAAkC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACvF,MAAM,OAAO,GAAG,IAAI,EAAE,sBAAkE,CAAC;YACzF,OAAO,OAAO,IAAI,IAAI,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,2CAA2C,MAAM,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,4BAA4B,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,gBAAgB;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;YAC9F,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAC/D,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACxD,gGAAgG;QAChG,cAAc,CAAC,GAAG,EAAE;YAClB,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,cAAc,CAA4B,CAAC;YAClG,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IACxE,cAAc,CAAC,KAAa;QAClC,cAAc,CAAC,GAAG,EAAE;YAClB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,EAAE,gBAAgB,CAAoB,YAAY,CAAC,CAAC;YACxE,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;iSAtTU,6BAA6B,yBAA7B,6BAA6B;6DAA7B,6BAA6B;;;;;;YCnG1C,8BAA2B;YAOvB,AANF,+FAA0B,+DAMjB;YA0IX,iBAAM;;YAhJJ,cA+IC;YA/ID,gDA+IC;4BDlDS,YAAY,0BAAE,sBAAsB,EAAE,6BAA6B;;iFAKlE,6BAA6B;cARzC,SAAS;6BACI,IAAI,YACN,2BAA2B,WAC5B,CAAC,YAAY,EAAE,sBAAsB,EAAE,6BAA6B,CAAC,mBAC7D,uBAAuB,CAAC,MAAM;;kBAM9C,KAAK;;kBAcL,KAAK;;kBAiBL,SAAS;mBAAC,WAAW;;kFAjCX,6BAA6B","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit,\n ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { Subscription } from 'rxjs';\nimport { LogError } from '@memberjunction/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJMediaPlayerComponent, MJStorageMediaPlayerComponent, MediaTrack } from '@memberjunction/ng-media-player';\nimport { MediaChannelState, MediaItem } from './media-channel-state';\n\n/** Zoom levels cycled by the image zoom control (×). */\nconst IMAGE_ZOOM_LEVELS = [1, 1.5, 2, 3] as const;\n\n/** Shape of the `CreateMediaAccessToken` server mutation result (one file). */\ninterface CreateMediaAccessTokenResult {\n Success: boolean;\n Url?: string | null;\n ErrorMessage?: string | null;\n}\n\n/**\n * The exact GraphQL mutation used to resolve a SECURE streaming URL for an `MJ: Files` id — mints a\n * short-lived signed token (after a server-side per-user permission check) and returns the\n * authenticated `/media/<fileId>?token=` URL. Image / PDF / web (iframe) panes render this URL; the\n * audio/video panes hand the file id straight to `mj-storage-media-player` (which mints its own).\n */\nconst CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fileId: String!) {\n CreateMediaAccessToken(fileId: $fileId) {\n Success\n Url\n ErrorMessage\n }\n}`;\n\n/** The per-item resolution status for FileID-backed image/pdf/web panes (audio/video resolve in the player). */\nexport type FileResolveState =\n | { Status: 'loading' }\n | { Status: 'ready'; Url: string }\n | { Status: 'error'; Message: string };\n\n/** The render route chosen for a {@link MediaItem} (pure function of its source + type). */\nexport type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';\n\n/** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */\nexport function IsPlayerMediaType(item: MediaItem): boolean {\n return item.Type === 'audio' || item.Type === 'video';\n}\n\n/**\n * Decides which render path an item uses based ONLY on its source + type — the single source of truth\n * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):\n * - audio/video + FileID → `'storage-player'` (`mj-storage-media-player`, secure streaming)\n * - audio/video + Url → `'url-player'` (`mj-media-player`, public URL)\n * - image/pdf/web → `'iframe-or-img'` (img/iframe; FileID resolves a streaming URL first)\n * - no usable source → `'none'`\n */\nexport function RouteForMediaItem(item: MediaItem): MediaRenderRoute {\n if (IsPlayerMediaType(item)) {\n if (item.FileID) {\n return 'storage-player';\n }\n return item.Url ? 'url-player' : 'none';\n }\n if (item.Url || item.FileID) {\n return 'iframe-or-img';\n }\n return 'none';\n}\n\n/**\n * LIVE MEDIA surface (`mj-realtime-media-surface`) — the Media channel tab's pane, rendered with a\n * TABS layout: a horizontal tab bar (one tab per {@link MediaItem}, type icon + name) over a single\n * media pane that swaps as you switch tabs. Pure PERCEPTION of the channel's {@link MediaChannelState}:\n * the agent's `Media_*` tools mutate the state through the channel plugin and this surface re-renders.\n *\n * SOURCE-AWARE rendering. Each item is backed by EITHER an external/public {@link MediaItem.Url} or an\n * MemberJunction {@link MediaItem.FileID} (an `MJ: Files` id, streamed securely + permission-gated):\n * - **audio/video + FileID** → `<mj-storage-media-player [FileID] [Provider]>` (real transport/waveform,\n * streams over HTTP Range under the session's authenticated provider).\n * - **audio/video + Url** → `<mj-media-player [Tracks]>` (the same real player, fed the public URL).\n * - **image/pdf/web + FileID** → a streaming URL is resolved via `CreateMediaAccessToken` (through the\n * channel's provider) and rendered in the existing img/iframe path; graceful no-access state on failure.\n * - **image/pdf/web + Url** → the public URL, rendered unchanged.\n *\n * Multi-provider safe: the channel plugin threads the live session's `IMetadataProvider` in via the\n * `Provider` input ({@link BaseAngularComponent}), and both the storage player and the token mutation\n * use it ({@link ProviderToUse}) rather than the global default.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-media-surface',\n imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-media-surface.component.html',\n styleUrls: ['./realtime-media-surface.component.css'],\n})\nexport class RealtimeMediaSurfaceComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n /** The shared media state engine (set by the channel's BindSurface before first CD). */\n @Input()\n set State(value: MediaChannelState | null) {\n if (value === this._state) {\n return;\n }\n this._state = value;\n this.resubscribe();\n }\n get State(): MediaChannelState | null {\n return this._state;\n }\n private _state: MediaChannelState | null = null;\n\n /** Display name of the agent fronting the session — used in the empty-state copy. */\n @Input() AgentName = 'The agent';\n\n /** The current image zoom multiplier (cycled by the zoom button; image panes only). */\n public ImageZoom = 1;\n\n /** Sanitized URL cache for `web` / `pdf` iframes (Angular blocks raw resource URLs in `[src]`). */\n private safeUrlCache = new Map<string, SafeResourceUrl>();\n\n /** The id of the item whose play we last actioned, so a re-render doesn't replay it. */\n private lastPlayedStamp = new Map<string, number>();\n\n /**\n * Resolution status for FileID-backed image/pdf/web items, keyed by item id. Audio/video FileID items\n * resolve inside `mj-storage-media-player` and never enter this map.\n */\n private fileResolveByItem = new Map<string, FileResolveState>();\n\n @ViewChild('mediaPane') private mediaPane?: ElementRef<HTMLElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly sanitizer = inject(DomSanitizer);\n private stateSub: Subscription | null = null;\n\n ngOnInit(): void {\n this.resubscribe();\n }\n\n ngOnDestroy(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n }\n\n /** The items currently on the surface (empty when no state is bound). */\n public get Items(): MediaItem[] {\n return this._state?.Items ?? [];\n }\n\n /** The active item, or `null` when the surface is empty. */\n public get ActiveItem(): MediaItem | null {\n return this._state?.ActiveItem ?? null;\n }\n\n /** Whether the active id matches `item` (drives the active tab + pane). */\n public IsActive(item: MediaItem): boolean {\n return this._state?.ActiveItemId === item.Id;\n }\n\n // ---------------------------------------------------------------------------\n // Source routing — which render path an item uses\n // ---------------------------------------------------------------------------\n\n /** Whether `item` is rendered by the real media player (audio/video). */\n public IsPlayerItem(item: MediaItem): boolean {\n return IsPlayerMediaType(item);\n }\n\n /** Whether `item` is an audio/video item backed by an MJStorage FileID → `mj-storage-media-player`. */\n public UsesStoragePlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'storage-player';\n }\n\n /** Whether `item` is an audio/video item backed by a public URL → `mj-media-player`. */\n public UsesUrlPlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'url-player';\n }\n\n /** The single-track payload for the URL-backed `mj-media-player` (audio/video). */\n public TracksFor(item: MediaItem): MediaTrack[] {\n if (!item.Url) {\n return [];\n }\n return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url }];\n }\n\n /**\n * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a\n * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access).\n */\n public RenderUrl(item: MediaItem): string | null {\n if (item.Url) {\n return item.Url;\n }\n if (item.FileID) {\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'ready' ? state.Url : null;\n }\n return null;\n }\n\n /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */\n public IsResolving(item: MediaItem): boolean {\n return !item.Url && !!item.FileID && this.fileResolveByItem.get(item.Id)?.Status === 'loading';\n }\n\n /** The no-access / load-failure message for a FileID-backed image/pdf/web item, or `null`. */\n public ResolveError(item: MediaItem): string | null {\n if (item.Url || !item.FileID) {\n return null;\n }\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'error' ? state.Message : null;\n }\n\n /** Switches the active tab to `item`. */\n public SelectTab(item: MediaItem): void {\n this._state?.SetActive(item.Id);\n }\n\n /** Keyboard handler for the tab buttons — Enter/Space activate, arrows move between tabs. */\n public OnTabKeydown(event: KeyboardEvent, index: number): void {\n const items = this.Items;\n if (items.length === 0) {\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const delta = event.key === 'ArrowRight' ? 1 : -1;\n const next = (index + delta + items.length) % items.length;\n this._state?.SetActive(items[next].Id);\n this.focusTabButton(next);\n }\n }\n\n /** Closes one media item (removes its tab + pane). */\n public CloseItem(event: Event, item: MediaItem): void {\n event.stopPropagation();\n this.fileResolveByItem.delete(item.Id);\n this._state?.RemoveItem(item.Id);\n }\n\n /** Whether the open-in-tab / download toolbar buttons are available for `item` (need a usable URL). */\n public CanOpenOrDownload(item: MediaItem): boolean {\n return !!this.RenderUrl(item);\n }\n\n /** Opens the active media in a new browser tab (its public or resolved streaming URL). */\n public OpenInNew(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (url) {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n }\n\n /** Triggers a browser download of the active media via a transient anchor (public or resolved URL). */\n public Download(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (!url) {\n return;\n }\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = item.DisplayName || '';\n anchor.target = '_blank';\n anchor.rel = 'noopener noreferrer';\n anchor.click();\n }\n\n /** Cycles the image zoom multiplier through {@link IMAGE_ZOOM_LEVELS}. */\n public CycleZoom(): void {\n const idx = IMAGE_ZOOM_LEVELS.indexOf(this.ImageZoom as (typeof IMAGE_ZOOM_LEVELS)[number]);\n this.ImageZoom = IMAGE_ZOOM_LEVELS[(idx + 1) % IMAGE_ZOOM_LEVELS.length];\n }\n\n /** The Font Awesome icon class for a media type's tab. */\n public IconFor(type: MediaItem['Type']): string {\n switch (type) {\n case 'image':\n return 'fa-solid fa-image';\n case 'video':\n return 'fa-solid fa-film';\n case 'audio':\n return 'fa-solid fa-music';\n case 'pdf':\n return 'fa-solid fa-file-pdf';\n case 'web':\n return 'fa-solid fa-globe';\n default:\n return 'fa-solid fa-file';\n }\n }\n\n /** A memoized sanitized resource URL for iframe-rendered media (web / pdf). */\n public SafeUrl(url: string): SafeResourceUrl {\n let cached = this.safeUrlCache.get(url);\n if (!cached) {\n cached = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n this.safeUrlCache.set(url, cached);\n }\n return cached;\n }\n\n /** Inline style for the fractional highlight rectangle (percentages of the pane box). */\n public HighlightStyle(item: MediaItem): Record<string, string> | null {\n const h = item.Highlight;\n if (!h) {\n return null;\n }\n const pct = (v: number): string => `${Math.max(0, Math.min(1, v)) * 100}%`;\n return { left: pct(h.X), top: pct(h.Y), width: pct(h.W), height: pct(h.H) };\n }\n\n /** Stable `@for` track for tab/pane diffing. */\n public TrackById(_index: number, item: MediaItem): string {\n return item.Id;\n }\n\n /** Re-subscribes the state's Changed$ stream (called on state input change + ngOnInit). */\n private resubscribe(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n if (!this._state) {\n this.cdr.markForCheck();\n return;\n }\n this.stateSub = this._state.Changed$.subscribe(() => {\n this.resolvePendingFileUrls();\n this.applyPendingPlay();\n this.cdr.markForCheck();\n });\n }\n\n /**\n * Mints streaming URLs for any FileID-backed image/pdf/web items not yet resolved (audio/video FileID\n * items resolve inside the storage player, so they're skipped). Idempotent — each item is resolved\n * once; a `loading`/`ready`/`error` entry guards against re-firing on every state change.\n */\n private resolvePendingFileUrls(): void {\n for (const item of this.Items) {\n if (item.Url || !item.FileID || this.IsPlayerItem(item)) {\n continue;\n }\n if (this.fileResolveByItem.has(item.Id)) {\n continue;\n }\n this.fileResolveByItem.set(item.Id, { Status: 'loading' });\n void this.resolveFileUrl(item.Id, item.FileID);\n }\n }\n\n /** Resolves one file id to a secure streaming URL via the channel's provider, then re-renders. */\n private async resolveFileUrl(itemId: string, fileId: string): Promise<void> {\n const result = await this.mintAccessToken(fileId);\n if (result && result.Success && result.Url) {\n this.fileResolveByItem.set(itemId, { Status: 'ready', Url: result.Url });\n } else {\n this.fileResolveByItem.set(itemId, {\n Status: 'error',\n Message: result?.ErrorMessage || \"This media couldn't be loaded, or you don't have access to it.\",\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Mints an authenticated streaming-URL token for one file id via the active provider. */\n private async mintAccessToken(fileId: string): Promise<CreateMediaAccessTokenResult | null> {\n try {\n const provider = this.ProviderToUse as unknown as GraphQLDataProvider;\n const data = await provider.ExecuteGQL(CREATE_MEDIA_ACCESS_TOKEN_MUTATION, { fileId });\n const payload = data?.CreateMediaAccessToken as CreateMediaAccessTokenResult | undefined;\n return payload ?? null;\n } catch (err) {\n LogError(`CreateMediaAccessToken failed for file '${fileId}': ${err instanceof Error ? err.message : String(err)}`);\n return { Success: false, ErrorMessage: 'Unable to load this media.' };\n }\n }\n\n /**\n * Honors a pending {@link MediaItem.PlayRequestedAt} stamp on the active video/audio element by\n * calling `.play()` once per new stamp (so a routine re-render never replays the media). Only applies\n * to the URL-backed `<mj-media-player>` / raw-element panes; the storage player owns its own autoplay.\n */\n private applyPendingPlay(): void {\n const item = this.ActiveItem;\n if (!item || item.PlayRequestedAt == null || (item.Type !== 'video' && item.Type !== 'audio')) {\n return;\n }\n if (this.lastPlayedStamp.get(item.Id) === item.PlayRequestedAt) {\n return;\n }\n this.lastPlayedStamp.set(item.Id, item.PlayRequestedAt);\n // The pane re-renders this CD pass; defer the play to the next microtask so the element exists.\n queueMicrotask(() => {\n const el = this.mediaPane?.nativeElement.querySelector('video, audio') as HTMLMediaElement | null;\n void el?.play().catch(() => undefined);\n });\n }\n\n /** Moves keyboard focus to the tab button at `index` (arrow-key navigation). */\n private focusTabButton(index: number): void {\n queueMicrotask(() => {\n const host = this.mediaPane?.nativeElement.parentElement;\n const buttons = host?.querySelectorAll<HTMLButtonElement>('.media-tab');\n buttons?.[index]?.focus();\n });\n }\n}\n","<div class=\"media-surface\">\n @if (Items.length === 0) {\n <div class=\"media-empty\">\n <i class=\"fa-solid fa-images media-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-empty__title\">No media yet</p>\n <p class=\"media-empty__hint\">{{ AgentName }} will share visuals here during the conversation.</p>\n </div>\n } @else {\n <!-- Tab bar: one tab per media item (the chosen \"Tabs\" layout — one pane, switch via tabs). -->\n <div class=\"media-tabs\" role=\"tablist\" aria-label=\"Shared media\">\n @for (item of Items; track TrackById($index, item); let i = $index) {\n <button\n type=\"button\"\n class=\"media-tab\"\n role=\"tab\"\n [class.media-tab--active]=\"IsActive(item)\"\n [attr.aria-selected]=\"IsActive(item)\"\n [attr.tabindex]=\"IsActive(item) ? 0 : -1\"\n [title]=\"item.DisplayName\"\n (click)=\"SelectTab(item)\"\n (keydown)=\"OnTabKeydown($event, i)\"\n >\n <i class=\"media-tab__icon\" [ngClass]=\"IconFor(item.Type)\" aria-hidden=\"true\"></i>\n <span class=\"media-tab__name\">{{ item.DisplayName }}</span>\n <span\n class=\"media-tab__close\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Close ' + item.DisplayName\"\n (click)=\"CloseItem($event, item)\"\n (keydown.enter)=\"CloseItem($event, item)\"\n (keydown.space)=\"CloseItem($event, item)\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </span>\n </button>\n }\n </div>\n\n @if (ActiveItem; as active) {\n <!-- Pane toolbar: per-type controls (open / download / zoom). -->\n <div class=\"media-toolbar\">\n <span class=\"media-toolbar__title\" [title]=\"active.DisplayName\">{{ active.DisplayName }}</span>\n <span class=\"media-toolbar__spacer\"></span>\n @if (active.Type === 'image') {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n [attr.aria-label]=\"'Zoom image (currently ' + ImageZoom + 'x)'\"\n title=\"Zoom\"\n (click)=\"CycleZoom()\"\n >\n <i class=\"fa-solid fa-magnifying-glass-plus\" aria-hidden=\"true\"></i>\n <span class=\"media-iconbtn__badge\">{{ ImageZoom }}×</span>\n </button>\n }\n @if (CanOpenOrDownload(active)) {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Download media\"\n title=\"Download\"\n (click)=\"Download(active)\"\n >\n <i class=\"fa-solid fa-download\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Open media in a new tab\"\n title=\"Open in new tab\"\n (click)=\"OpenInNew(active)\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- The single media pane (swaps as tabs switch). The highlight overlay sits on top. -->\n <div class=\"media-pane\" #mediaPane>\n <div class=\"media-stage\" [class.media-stage--scroll]=\"active.Type === 'image' && ImageZoom > 1\">\n @if (UsesStoragePlayer(active)) {\n <!-- Audio/video backed by an MJStorage file: secure, permission-gated streaming player. -->\n <mj-storage-media-player\n class=\"media-player\"\n [FileID]=\"active.FileID ?? null\"\n [Provider]=\"Provider\"\n ></mj-storage-media-player>\n } @else if (UsesUrlPlayer(active)) {\n <!-- Audio/video from a public URL: the real media player (waveform/transport). -->\n <mj-media-player class=\"media-player\" [Tracks]=\"TracksFor(active)\"></mj-media-player>\n } @else if (IsResolving(active)) {\n <div class=\"media-status\">\n <i class=\"fa-solid fa-spinner fa-spin media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">Loading secure media…</p>\n </div>\n } @else if (ResolveError(active); as err) {\n <div class=\"media-status media-status--error\">\n <i class=\"fa-solid fa-triangle-exclamation media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">{{ err }}</p>\n </div>\n } @else if (RenderUrl(active); as renderUrl) {\n @switch (active.Type) {\n @case ('image') {\n <img\n class=\"media-image\"\n [src]=\"renderUrl\"\n [style.transform]=\"'scale(' + ImageZoom + ')'\"\n [alt]=\"active.DisplayName\"\n />\n }\n @case ('pdf') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n ></iframe>\n }\n @case ('web') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n referrerpolicy=\"no-referrer\"\n ></iframe>\n }\n }\n }\n\n @if (HighlightStyle(active); as hs) {\n <div class=\"media-highlight\" [ngStyle]=\"hs\" aria-hidden=\"true\">\n @if (active.Highlight?.Label) {\n <span class=\"media-highlight__label\">{{ active.Highlight?.Label }}</span>\n }\n </div>\n }\n </div>\n\n @if (active.Caption) {\n <p class=\"media-caption\">{{ active.Caption }}</p>\n }\n </div>\n }\n }\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-media-surface.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts","../../../../../src/lib/components/realtime/media/realtime-media-surface.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit,\n ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { Subscription } from 'rxjs';\nimport { LogError } from '@memberjunction/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { MJMediaPlayerComponent, MJStorageMediaPlayerComponent, MediaTrack } from '@memberjunction/ng-media-player';\nimport { MediaChannelState, MediaItem } from './media-channel-state';\n\n/** Zoom levels cycled by the image zoom control (×). */\nconst IMAGE_ZOOM_LEVELS = [1, 1.5, 2, 3] as const;\n\n/** Shape of the `CreateMediaAccessToken` server mutation result (one file). */\ninterface CreateMediaAccessTokenResult {\n Success: boolean;\n Url?: string | null;\n ErrorMessage?: string | null;\n}\n\n/**\n * The exact GraphQL mutation used to resolve a SECURE streaming URL for an `MJ: Files` id — mints a\n * short-lived signed token (after a server-side per-user permission check) and returns the\n * authenticated `/media/<fileId>?token=` URL. Image / PDF / web (iframe) panes render this URL; the\n * audio/video panes hand the file id straight to `mj-storage-media-player` (which mints its own).\n */\nconst CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fileId: String!) {\n CreateMediaAccessToken(fileId: $fileId) {\n Success\n Url\n ErrorMessage\n }\n}`;\n\n/** The per-item resolution status for FileID-backed image/pdf/web panes (audio/video resolve in the player). */\nexport type FileResolveState =\n | { Status: 'loading' }\n | { Status: 'ready'; Url: string }\n | { Status: 'error'; Message: string };\n\n/** The render route chosen for a {@link MediaItem} (pure function of its source + type). */\nexport type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';\n\n/** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */\nexport function IsPlayerMediaType(item: MediaItem): boolean {\n return item.Type === 'audio' || item.Type === 'video';\n}\n\n/**\n * Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and\n * is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,\n * `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated\n * session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.\n */\nexport function IsEmbeddableSurfaceUrl(url: string | null | undefined, baseOrigin: string): boolean {\n if (typeof url !== 'string') {\n return false;\n }\n const trimmed = url.trim();\n if (!trimmed || trimmed.startsWith('//')) {\n return false;\n }\n try {\n const parsed = new URL(trimmed, baseOrigin);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\n/**\n * Decides which render path an item uses based ONLY on its source + type — the single source of truth\n * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):\n * - audio/video + FileID → `'storage-player'` (`mj-storage-media-player`, secure streaming)\n * - audio/video + Url → `'url-player'` (`mj-media-player`, public URL)\n * - image/pdf/web → `'iframe-or-img'` (img/iframe; FileID resolves a streaming URL first)\n * - no usable source → `'none'`\n */\nexport function RouteForMediaItem(item: MediaItem): MediaRenderRoute {\n if (IsPlayerMediaType(item)) {\n if (item.FileID) {\n return 'storage-player';\n }\n return item.Url ? 'url-player' : 'none';\n }\n if (item.Url || item.FileID) {\n return 'iframe-or-img';\n }\n return 'none';\n}\n\n/**\n * LIVE MEDIA surface (`mj-realtime-media-surface`) — the Media channel tab's pane, rendered with a\n * TABS layout: a horizontal tab bar (one tab per {@link MediaItem}, type icon + name) over a single\n * media pane that swaps as you switch tabs. Pure PERCEPTION of the channel's {@link MediaChannelState}:\n * the agent's `Media_*` tools mutate the state through the channel plugin and this surface re-renders.\n *\n * SOURCE-AWARE rendering. Each item is backed by EITHER an external/public {@link MediaItem.Url} or an\n * MemberJunction {@link MediaItem.FileID} (an `MJ: Files` id, streamed securely + permission-gated):\n * - **audio/video + FileID** → `<mj-storage-media-player [FileID] [Provider]>` (real transport/waveform,\n * streams over HTTP Range under the session's authenticated provider).\n * - **audio/video + Url** → `<mj-media-player [Tracks]>` (the same real player, fed the public URL).\n * - **image/pdf/web + FileID** → a streaming URL is resolved via `CreateMediaAccessToken` (through the\n * channel's provider) and rendered in the existing img/iframe path; graceful no-access state on failure.\n * - **image/pdf/web + Url** → the public URL, rendered unchanged.\n *\n * Multi-provider safe: the channel plugin threads the live session's `IMetadataProvider` in via the\n * `Provider` input ({@link BaseAngularComponent}), and both the storage player and the token mutation\n * use it ({@link ProviderToUse}) rather than the global default.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-media-surface',\n imports: [CommonModule, MJMediaPlayerComponent, MJStorageMediaPlayerComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-media-surface.component.html',\n styleUrls: ['./realtime-media-surface.component.css'],\n})\nexport class RealtimeMediaSurfaceComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n /** The shared media state engine (set by the channel's BindSurface before first CD). */\n @Input()\n set State(value: MediaChannelState | null) {\n if (value === this._state) {\n return;\n }\n this._state = value;\n this.resubscribe();\n }\n get State(): MediaChannelState | null {\n return this._state;\n }\n private _state: MediaChannelState | null = null;\n\n /** Display name of the agent fronting the session — used in the empty-state copy. */\n @Input() AgentName = 'The agent';\n\n /** The current image zoom multiplier (cycled by the zoom button; image panes only). */\n public ImageZoom = 1;\n\n /** Sanitized URL cache for `web` / `pdf` iframes (Angular blocks raw resource URLs in `[src]`). */\n private safeUrlCache = new Map<string, SafeResourceUrl>();\n\n /** The id of the item whose play we last actioned, so a re-render doesn't replay it. */\n private lastPlayedStamp = new Map<string, number>();\n\n /**\n * Resolution status for FileID-backed image/pdf/web items, keyed by item id. Audio/video FileID items\n * resolve inside `mj-storage-media-player` and never enter this map.\n */\n private fileResolveByItem = new Map<string, FileResolveState>();\n\n @ViewChild('mediaPane') private mediaPane?: ElementRef<HTMLElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly sanitizer = inject(DomSanitizer);\n private stateSub: Subscription | null = null;\n\n ngOnInit(): void {\n this.resubscribe();\n }\n\n ngOnDestroy(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n }\n\n /** The items currently on the surface (empty when no state is bound). */\n public get Items(): MediaItem[] {\n return this._state?.Items ?? [];\n }\n\n /** The active item, or `null` when the surface is empty. */\n public get ActiveItem(): MediaItem | null {\n return this._state?.ActiveItem ?? null;\n }\n\n /** Whether the active id matches `item` (drives the active tab + pane). */\n public IsActive(item: MediaItem): boolean {\n return this._state?.ActiveItemId === item.Id;\n }\n\n // ---------------------------------------------------------------------------\n // Source routing — which render path an item uses\n // ---------------------------------------------------------------------------\n\n /** Whether `item` is rendered by the real media player (audio/video). */\n public IsPlayerItem(item: MediaItem): boolean {\n return IsPlayerMediaType(item);\n }\n\n /** Whether `item` is an audio/video item backed by an MJStorage FileID → `mj-storage-media-player`. */\n public UsesStoragePlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'storage-player';\n }\n\n /** Whether `item` is an audio/video item backed by a public URL → `mj-media-player`. */\n public UsesUrlPlayer(item: MediaItem): boolean {\n return RouteForMediaItem(item) === 'url-player';\n }\n\n /** The single-track payload for the URL-backed `mj-media-player` (audio/video). */\n public TracksFor(item: MediaItem): MediaTrack[] {\n if (!item.Url || !this.isEmbeddableUrl(item.Url)) {\n return [];\n }\n return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url.trim() }];\n }\n\n /**\n * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a\n * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /\n * if the URL fails http(s) scheme screening).\n */\n public RenderUrl(item: MediaItem): string | null {\n let candidate: string | null = null;\n if (item.Url) {\n candidate = item.Url;\n } else if (item.FileID) {\n const state = this.fileResolveByItem.get(item.Id);\n candidate = state?.Status === 'ready' ? state.Url : null;\n }\n if (!candidate) {\n return null;\n }\n if (!this.isEmbeddableUrl(candidate)) {\n LogError('RealtimeMediaSurface: refused media URL with a non-http(s) or scheme-relative target.');\n return null;\n }\n return candidate.trim();\n }\n\n /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */\n public IsResolving(item: MediaItem): boolean {\n return !item.Url && !!item.FileID && this.fileResolveByItem.get(item.Id)?.Status === 'loading';\n }\n\n /** The no-access / load-failure message for a FileID-backed image/pdf/web item, or `null`. */\n public ResolveError(item: MediaItem): string | null {\n if (item.Url || !item.FileID) {\n return null;\n }\n const state = this.fileResolveByItem.get(item.Id);\n return state?.Status === 'error' ? state.Message : null;\n }\n\n /** Switches the active tab to `item`. */\n public SelectTab(item: MediaItem): void {\n this._state?.SetActive(item.Id);\n }\n\n /** Keyboard handler for the tab buttons — Enter/Space activate, arrows move between tabs. */\n public OnTabKeydown(event: KeyboardEvent, index: number): void {\n const items = this.Items;\n if (items.length === 0) {\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const delta = event.key === 'ArrowRight' ? 1 : -1;\n const next = (index + delta + items.length) % items.length;\n this._state?.SetActive(items[next].Id);\n this.focusTabButton(next);\n }\n }\n\n /** Closes one media item (removes its tab + pane). */\n public CloseItem(event: Event, item: MediaItem): void {\n event.stopPropagation();\n this.fileResolveByItem.delete(item.Id);\n this._state?.RemoveItem(item.Id);\n }\n\n /** Whether the open-in-tab / download toolbar buttons are available for `item` (need a usable URL). */\n public CanOpenOrDownload(item: MediaItem): boolean {\n return !!this.RenderUrl(item);\n }\n\n /** Opens the active media in a new browser tab (its public or resolved streaming URL). */\n public OpenInNew(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (url) {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n }\n\n /** Triggers a browser download of the active media via a transient anchor (public or resolved URL). */\n public Download(item: MediaItem): void {\n const url = this.RenderUrl(item);\n if (!url) {\n return;\n }\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = item.DisplayName || '';\n anchor.target = '_blank';\n anchor.rel = 'noopener noreferrer';\n anchor.click();\n }\n\n /** Cycles the image zoom multiplier through {@link IMAGE_ZOOM_LEVELS}. */\n public CycleZoom(): void {\n const idx = IMAGE_ZOOM_LEVELS.indexOf(this.ImageZoom as (typeof IMAGE_ZOOM_LEVELS)[number]);\n this.ImageZoom = IMAGE_ZOOM_LEVELS[(idx + 1) % IMAGE_ZOOM_LEVELS.length];\n }\n\n /** The Font Awesome icon class for a media type's tab. */\n public IconFor(type: MediaItem['Type']): string {\n switch (type) {\n case 'image':\n return 'fa-solid fa-image';\n case 'video':\n return 'fa-solid fa-film';\n case 'audio':\n return 'fa-solid fa-music';\n case 'pdf':\n return 'fa-solid fa-file-pdf';\n case 'web':\n return 'fa-solid fa-globe';\n default:\n return 'fa-solid fa-file';\n }\n }\n\n /**\n * A memoized sanitized resource URL for iframe-rendered media (web / pdf).\n *\n * SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can\n * originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or\n * relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever\n * trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.\n */\n public SafeUrl(url: string): SafeResourceUrl {\n let cached = this.safeUrlCache.get(url);\n if (!cached) {\n const target = this.isEmbeddableUrl(url) ? url : 'about:blank';\n if (target !== url) {\n LogError(`RealtimeMediaSurface: refused to embed media URL with a non-http(s) scheme.`);\n }\n cached = this.sanitizer.bypassSecurityTrustResourceUrl(target);\n this.safeUrlCache.set(url, cached);\n }\n return cached;\n }\n\n /** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */\n private isEmbeddableUrl(url: string): boolean {\n const origin =\n typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'https://localhost';\n return IsEmbeddableSurfaceUrl(url, origin);\n }\n\n /** Inline style for the fractional highlight rectangle (percentages of the pane box). */\n public HighlightStyle(item: MediaItem): Record<string, string> | null {\n const h = item.Highlight;\n if (!h) {\n return null;\n }\n const pct = (v: number): string => `${Math.max(0, Math.min(1, v)) * 100}%`;\n return { left: pct(h.X), top: pct(h.Y), width: pct(h.W), height: pct(h.H) };\n }\n\n /** Stable `@for` track for tab/pane diffing. */\n public TrackById(_index: number, item: MediaItem): string {\n return item.Id;\n }\n\n /** Re-subscribes the state's Changed$ stream (called on state input change + ngOnInit). */\n private resubscribe(): void {\n this.stateSub?.unsubscribe();\n this.stateSub = null;\n if (!this._state) {\n this.cdr.markForCheck();\n return;\n }\n this.stateSub = this._state.Changed$.subscribe(() => {\n this.resolvePendingFileUrls();\n this.applyPendingPlay();\n this.cdr.markForCheck();\n });\n }\n\n /**\n * Mints streaming URLs for any FileID-backed image/pdf/web items not yet resolved (audio/video FileID\n * items resolve inside the storage player, so they're skipped). Idempotent — each item is resolved\n * once; a `loading`/`ready`/`error` entry guards against re-firing on every state change.\n */\n private resolvePendingFileUrls(): void {\n for (const item of this.Items) {\n if (item.Url || !item.FileID || this.IsPlayerItem(item)) {\n continue;\n }\n if (this.fileResolveByItem.has(item.Id)) {\n continue;\n }\n this.fileResolveByItem.set(item.Id, { Status: 'loading' });\n void this.resolveFileUrl(item.Id, item.FileID);\n }\n }\n\n /** Resolves one file id to a secure streaming URL via the channel's provider, then re-renders. */\n private async resolveFileUrl(itemId: string, fileId: string): Promise<void> {\n const result = await this.mintAccessToken(fileId);\n if (result && result.Success && result.Url) {\n this.fileResolveByItem.set(itemId, { Status: 'ready', Url: result.Url });\n } else {\n this.fileResolveByItem.set(itemId, {\n Status: 'error',\n Message: result?.ErrorMessage || \"This media couldn't be loaded, or you don't have access to it.\",\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Mints an authenticated streaming-URL token for one file id via the active provider. */\n private async mintAccessToken(fileId: string): Promise<CreateMediaAccessTokenResult | null> {\n try {\n const provider = this.ProviderToUse as unknown as GraphQLDataProvider;\n const data = await provider.ExecuteGQL(CREATE_MEDIA_ACCESS_TOKEN_MUTATION, { fileId });\n const payload = data?.CreateMediaAccessToken as CreateMediaAccessTokenResult | undefined;\n return payload ?? null;\n } catch (err) {\n LogError(`CreateMediaAccessToken failed for file '${fileId}': ${err instanceof Error ? err.message : String(err)}`);\n return { Success: false, ErrorMessage: 'Unable to load this media.' };\n }\n }\n\n /**\n * Honors a pending {@link MediaItem.PlayRequestedAt} stamp on the active video/audio element by\n * calling `.play()` once per new stamp (so a routine re-render never replays the media). Only applies\n * to the URL-backed `<mj-media-player>` / raw-element panes; the storage player owns its own autoplay.\n */\n private applyPendingPlay(): void {\n const item = this.ActiveItem;\n if (!item || item.PlayRequestedAt == null || (item.Type !== 'video' && item.Type !== 'audio')) {\n return;\n }\n if (this.lastPlayedStamp.get(item.Id) === item.PlayRequestedAt) {\n return;\n }\n this.lastPlayedStamp.set(item.Id, item.PlayRequestedAt);\n // The pane re-renders this CD pass; defer the play to the next microtask so the element exists.\n queueMicrotask(() => {\n const el = this.mediaPane?.nativeElement.querySelector('video, audio') as HTMLMediaElement | null;\n void el?.play().catch(() => undefined);\n });\n }\n\n /** Moves keyboard focus to the tab button at `index` (arrow-key navigation). */\n private focusTabButton(index: number): void {\n queueMicrotask(() => {\n const host = this.mediaPane?.nativeElement.parentElement;\n const buttons = host?.querySelectorAll<HTMLButtonElement>('.media-tab');\n buttons?.[index]?.focus();\n });\n }\n}\n","<div class=\"media-surface\">\n @if (Items.length === 0) {\n <div class=\"media-empty\">\n <i class=\"fa-solid fa-images media-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-empty__title\">No media yet</p>\n <p class=\"media-empty__hint\">{{ AgentName }} will share visuals here during the conversation.</p>\n </div>\n } @else {\n <!-- Tab bar: one tab per media item (the chosen \"Tabs\" layout — one pane, switch via tabs). -->\n <div class=\"media-tabs\" role=\"tablist\" aria-label=\"Shared media\">\n @for (item of Items; track TrackById($index, item); let i = $index) {\n <button\n type=\"button\"\n class=\"media-tab\"\n role=\"tab\"\n [class.media-tab--active]=\"IsActive(item)\"\n [attr.aria-selected]=\"IsActive(item)\"\n [attr.tabindex]=\"IsActive(item) ? 0 : -1\"\n [title]=\"item.DisplayName\"\n (click)=\"SelectTab(item)\"\n (keydown)=\"OnTabKeydown($event, i)\"\n >\n <i class=\"media-tab__icon\" [ngClass]=\"IconFor(item.Type)\" aria-hidden=\"true\"></i>\n <span class=\"media-tab__name\">{{ item.DisplayName }}</span>\n <span\n class=\"media-tab__close\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Close ' + item.DisplayName\"\n (click)=\"CloseItem($event, item)\"\n (keydown.enter)=\"CloseItem($event, item)\"\n (keydown.space)=\"CloseItem($event, item)\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </span>\n </button>\n }\n </div>\n\n @if (ActiveItem; as active) {\n <!-- Pane toolbar: per-type controls (open / download / zoom). -->\n <div class=\"media-toolbar\">\n <span class=\"media-toolbar__title\" [title]=\"active.DisplayName\">{{ active.DisplayName }}</span>\n <span class=\"media-toolbar__spacer\"></span>\n @if (active.Type === 'image') {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n [attr.aria-label]=\"'Zoom image (currently ' + ImageZoom + 'x)'\"\n title=\"Zoom\"\n (click)=\"CycleZoom()\"\n >\n <i class=\"fa-solid fa-magnifying-glass-plus\" aria-hidden=\"true\"></i>\n <span class=\"media-iconbtn__badge\">{{ ImageZoom }}×</span>\n </button>\n }\n @if (CanOpenOrDownload(active)) {\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Download media\"\n title=\"Download\"\n (click)=\"Download(active)\"\n >\n <i class=\"fa-solid fa-download\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"media-iconbtn\"\n aria-label=\"Open media in a new tab\"\n title=\"Open in new tab\"\n (click)=\"OpenInNew(active)\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n\n <!-- The single media pane (swaps as tabs switch). The highlight overlay sits on top. -->\n <div class=\"media-pane\" #mediaPane>\n <div class=\"media-stage\" [class.media-stage--scroll]=\"active.Type === 'image' && ImageZoom > 1\">\n @if (UsesStoragePlayer(active)) {\n <!-- Audio/video backed by an MJStorage file: secure, permission-gated streaming player. -->\n <mj-storage-media-player\n class=\"media-player\"\n [FileID]=\"active.FileID ?? null\"\n [Provider]=\"Provider\"\n ></mj-storage-media-player>\n } @else if (UsesUrlPlayer(active)) {\n <!-- Audio/video from a public URL: the real media player (waveform/transport). -->\n <mj-media-player class=\"media-player\" [Tracks]=\"TracksFor(active)\"></mj-media-player>\n } @else if (IsResolving(active)) {\n <div class=\"media-status\">\n <i class=\"fa-solid fa-spinner fa-spin media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">Loading secure media…</p>\n </div>\n } @else if (ResolveError(active); as err) {\n <div class=\"media-status media-status--error\">\n <i class=\"fa-solid fa-triangle-exclamation media-status__icon\" aria-hidden=\"true\"></i>\n <p class=\"media-status__text\">{{ err }}</p>\n </div>\n } @else if (RenderUrl(active); as renderUrl) {\n @switch (active.Type) {\n @case ('image') {\n <img\n class=\"media-image\"\n [src]=\"renderUrl\"\n [style.transform]=\"'scale(' + ImageZoom + ')'\"\n [alt]=\"active.DisplayName\"\n />\n }\n @case ('pdf') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n ></iframe>\n }\n @case ('web') {\n <iframe\n class=\"media-frame\"\n [src]=\"SafeUrl(renderUrl)\"\n [title]=\"active.DisplayName\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n referrerpolicy=\"no-referrer\"\n ></iframe>\n }\n }\n }\n\n @if (HighlightStyle(active); as hs) {\n <div class=\"media-highlight\" [ngStyle]=\"hs\" aria-hidden=\"true\">\n @if (active.Highlight?.Label) {\n <span class=\"media-highlight__label\">{{ active.Highlight?.Label }}</span>\n }\n </div>\n }\n </div>\n\n @if (active.Caption) {\n <p class=\"media-caption\">{{ active.Caption }}</p>\n }\n </div>\n }\n }\n</div>\n"]}
|