@memberjunction/ng-conversations 5.43.0 → 5.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +0 -1
  2. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
  3. package/dist/lib/components/active-tasks/active-tasks-panel.component.js +56 -52
  4. package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
  5. package/dist/lib/components/artifact/artifact-share-modal.component.js +37 -41
  6. package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
  7. package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
  8. package/dist/lib/components/collection/collection-form-modal.component.js +10 -14
  9. package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
  10. package/dist/lib/components/collection/collection-share-modal.component.js +41 -45
  11. package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
  12. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  13. package/dist/lib/components/collection/collection-view.component.js +22 -36
  14. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  15. package/dist/lib/components/collection/collections-full-view.component.d.ts +4 -0
  16. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  17. package/dist/lib/components/collection/collections-full-view.component.js +134 -166
  18. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  19. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +73 -8
  20. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  21. package/dist/lib/components/conversation/conversation-chat-area.component.js +487 -123
  22. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  23. package/dist/lib/components/conversation/pinned-messages-panel.component.js +6 -9
  24. package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
  25. package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
  26. package/dist/lib/components/library/library-full-view.component.js +20 -24
  27. package/dist/lib/components/library/library-full-view.component.js.map +1 -1
  28. package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
  29. package/dist/lib/components/members/members-modal.component.js +10 -11
  30. package/dist/lib/components/members/members-modal.component.js.map +1 -1
  31. package/dist/lib/components/mention/mention-dropdown.component.js +49 -28
  32. package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
  33. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  34. package/dist/lib/components/mention/mention-editor.component.js +24 -10
  35. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  36. package/dist/lib/components/message/message-input-box.component.d.ts +11 -16
  37. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  38. package/dist/lib/components/message/message-input-box.component.js +52 -46
  39. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  40. package/dist/lib/components/message/message-input.component.d.ts +83 -18
  41. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  42. package/dist/lib/components/message/message-input.component.js +238 -151
  43. package/dist/lib/components/message/message-input.component.js.map +1 -1
  44. package/dist/lib/components/message/message-item.component.d.ts +10 -1
  45. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  46. package/dist/lib/components/message/message-item.component.js +67 -15
  47. package/dist/lib/components/message/message-item.component.js.map +1 -1
  48. package/dist/lib/components/message/message-list.component.js +6 -9
  49. package/dist/lib/components/message/message-list.component.js.map +1 -1
  50. package/dist/lib/components/overlay/chat-overlay.component.d.ts +3 -0
  51. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  52. package/dist/lib/components/overlay/chat-overlay.component.js +9 -4
  53. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  54. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +45 -0
  55. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
  56. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +10 -0
  57. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
  58. package/dist/lib/components/realtime/channels/client-context-channel.d.ts +46 -0
  59. package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -0
  60. package/dist/lib/components/realtime/channels/client-context-channel.js +174 -0
  61. package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -0
  62. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts +123 -0
  63. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts.map +1 -0
  64. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +397 -0
  65. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -0
  66. package/dist/lib/components/realtime/media/media-channel-state.d.ts +103 -0
  67. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -0
  68. package/dist/lib/components/realtime/media/media-channel-state.js +143 -0
  69. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -0
  70. package/dist/lib/components/realtime/media/media-channel-tools.d.ts +24 -0
  71. package/dist/lib/components/realtime/media/media-channel-tools.d.ts.map +1 -0
  72. package/dist/lib/components/realtime/media/media-channel-tools.js +107 -0
  73. package/dist/lib/components/realtime/media/media-channel-tools.js.map +1 -0
  74. package/dist/lib/components/realtime/media/media-channel.d.ts +89 -0
  75. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -0
  76. package/dist/lib/components/realtime/media/media-channel.js +253 -0
  77. package/dist/lib/components/realtime/media/media-channel.js.map +1 -0
  78. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +141 -0
  79. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -0
  80. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +574 -0
  81. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -0
  82. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +59 -14
  83. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
  84. package/dist/lib/components/realtime/realtime-activity-rail.component.js +277 -74
  85. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
  86. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +28 -27
  87. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  88. package/dist/lib/components/realtime/realtime-agent-banner.component.js +144 -143
  89. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  90. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -0
  91. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  92. package/dist/lib/components/realtime/realtime-agent-picker.component.js +105 -43
  93. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  94. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +2 -2
  95. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
  96. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
  97. package/dist/lib/components/realtime/realtime-composer.component.d.ts +41 -7
  98. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  99. package/dist/lib/components/realtime/realtime-composer.component.js +216 -102
  100. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  101. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +0 -2
  102. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
  103. package/dist/lib/components/realtime/realtime-delegation-card.component.js +67 -71
  104. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
  105. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +364 -44
  106. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  107. package/dist/lib/components/realtime/realtime-session-overlay.component.js +839 -153
  108. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  109. package/dist/lib/components/realtime/realtime-session-state.d.ts +5 -5
  110. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
  111. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
  112. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts +70 -0
  113. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts.map +1 -0
  114. package/dist/lib/components/realtime/realtime-surface-tab-style.js +115 -0
  115. package/dist/lib/components/realtime/realtime-surface-tab-style.js.map +1 -0
  116. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +55 -57
  117. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
  118. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +165 -143
  119. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
  120. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +80 -71
  121. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
  122. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +83 -113
  123. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
  124. package/dist/lib/components/realtime/realtime-ui-config.d.ts +270 -0
  125. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -0
  126. package/dist/lib/components/realtime/realtime-ui-config.js +212 -0
  127. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -0
  128. package/dist/lib/components/search/search-panel.component.d.ts +2 -0
  129. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  130. package/dist/lib/components/search/search-panel.component.js +92 -101
  131. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  132. package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
  133. package/dist/lib/components/share/share-modal.component.js +10 -11
  134. package/dist/lib/components/share/share-modal.component.js.map +1 -1
  135. package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
  136. package/dist/lib/components/shared/user-picker.component.js +12 -15
  137. package/dist/lib/components/shared/user-picker.component.js.map +1 -1
  138. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +5 -0
  139. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  140. package/dist/lib/components/workspace/conversation-workspace.component.js +18 -7
  141. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  142. package/dist/lib/conversations.module.d.ts +29 -26
  143. package/dist/lib/conversations.module.d.ts.map +1 -1
  144. package/dist/lib/conversations.module.js +37 -6
  145. package/dist/lib/conversations.module.js.map +1 -1
  146. package/dist/lib/models/conversation-state.model.d.ts +6 -3
  147. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  148. package/dist/lib/models/conversation-state.model.js.map +1 -1
  149. package/dist/lib/services/conversation-agent.service.d.ts +2 -2
  150. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  151. package/dist/lib/services/conversation-agent.service.js +6 -2
  152. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  153. package/dist/lib/services/mention-autocomplete.service.d.ts +17 -1
  154. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  155. package/dist/lib/services/mention-autocomplete.service.js +58 -2
  156. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  157. package/dist/lib/services/realtime-audio-recorder.d.ts +143 -0
  158. package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -0
  159. package/dist/lib/services/realtime-audio-recorder.js +379 -0
  160. package/dist/lib/services/realtime-audio-recorder.js.map +1 -0
  161. package/dist/lib/services/realtime-pcm-wav.d.ts +75 -0
  162. package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -0
  163. package/dist/lib/services/realtime-pcm-wav.js +205 -0
  164. package/dist/lib/services/realtime-pcm-wav.js.map +1 -0
  165. package/dist/lib/services/realtime-session-review.service.d.ts +28 -1
  166. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  167. package/dist/lib/services/realtime-session-review.service.js +12 -3
  168. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  169. package/dist/lib/services/realtime-session.service.d.ts +210 -20
  170. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  171. package/dist/lib/services/realtime-session.service.js +452 -21
  172. package/dist/lib/services/realtime-session.service.js.map +1 -1
  173. package/dist/lib/services/realtime-sessions-adapter.js +1 -1
  174. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -1
  175. package/dist/lib/services/ui-command-handler.service.d.ts +7 -2
  176. package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
  177. package/dist/lib/services/ui-command-handler.service.js +6 -2
  178. package/dist/lib/services/ui-command-handler.service.js.map +1 -1
  179. package/dist/lib/utils/plan-mode-preference.d.ts +62 -0
  180. package/dist/lib/utils/plan-mode-preference.d.ts.map +1 -0
  181. package/dist/lib/utils/plan-mode-preference.js +130 -0
  182. package/dist/lib/utils/plan-mode-preference.js.map +1 -0
  183. package/dist/public-api.d.ts +6 -0
  184. package/dist/public-api.d.ts.map +1 -1
  185. package/dist/public-api.js +8 -0
  186. package/dist/public-api.js.map +1 -1
  187. package/package.json +28 -26
  188. package/dist/__tests__/attachment-badge.test.d.ts +0 -2
  189. package/dist/__tests__/attachment-badge.test.d.ts.map +0 -1
  190. package/dist/__tests__/attachment-badge.test.js +0 -66
  191. package/dist/__tests__/attachment-badge.test.js.map +0 -1
  192. package/dist/__tests__/channel-optional-surface.test.d.ts +0 -2
  193. package/dist/__tests__/channel-optional-surface.test.d.ts.map +0 -1
  194. package/dist/__tests__/channel-optional-surface.test.js +0 -53
  195. package/dist/__tests__/channel-optional-surface.test.js.map +0 -1
  196. package/dist/__tests__/chat-events.test.d.ts +0 -14
  197. package/dist/__tests__/chat-events.test.d.ts.map +0 -1
  198. package/dist/__tests__/chat-events.test.js +0 -109
  199. package/dist/__tests__/chat-events.test.js.map +0 -1
  200. package/dist/__tests__/collections-full-view.test.d.ts +0 -2
  201. package/dist/__tests__/collections-full-view.test.d.ts.map +0 -1
  202. package/dist/__tests__/collections-full-view.test.js +0 -225
  203. package/dist/__tests__/collections-full-view.test.js.map +0 -1
  204. package/dist/__tests__/conversation-cache.test.d.ts +0 -2
  205. package/dist/__tests__/conversation-cache.test.d.ts.map +0 -1
  206. package/dist/__tests__/conversation-cache.test.js +0 -233
  207. package/dist/__tests__/conversation-cache.test.js.map +0 -1
  208. package/dist/__tests__/conversation-naming.test.d.ts +0 -2
  209. package/dist/__tests__/conversation-naming.test.d.ts.map +0 -1
  210. package/dist/__tests__/conversation-naming.test.js +0 -110
  211. package/dist/__tests__/conversation-naming.test.js.map +0 -1
  212. package/dist/__tests__/delegation-result-parser.test.d.ts +0 -2
  213. package/dist/__tests__/delegation-result-parser.test.d.ts.map +0 -1
  214. package/dist/__tests__/delegation-result-parser.test.js +0 -107
  215. package/dist/__tests__/delegation-result-parser.test.js.map +0 -1
  216. package/dist/__tests__/distinct-artifacts.test.d.ts +0 -2
  217. package/dist/__tests__/distinct-artifacts.test.d.ts.map +0 -1
  218. package/dist/__tests__/distinct-artifacts.test.js +0 -56
  219. package/dist/__tests__/distinct-artifacts.test.js.map +0 -1
  220. package/dist/__tests__/event-wiring.test.d.ts +0 -15
  221. package/dist/__tests__/event-wiring.test.d.ts.map +0 -1
  222. package/dist/__tests__/event-wiring.test.js +0 -100
  223. package/dist/__tests__/event-wiring.test.js.map +0 -1
  224. package/dist/__tests__/index.test.d.ts +0 -2
  225. package/dist/__tests__/index.test.d.ts.map +0 -1
  226. package/dist/__tests__/index.test.js +0 -7
  227. package/dist/__tests__/index.test.js.map +0 -1
  228. package/dist/__tests__/narration-template.test.d.ts +0 -2
  229. package/dist/__tests__/narration-template.test.d.ts.map +0 -1
  230. package/dist/__tests__/narration-template.test.js +0 -76
  231. package/dist/__tests__/narration-template.test.js.map +0 -1
  232. package/dist/__tests__/notification-model.test.d.ts +0 -2
  233. package/dist/__tests__/notification-model.test.d.ts.map +0 -1
  234. package/dist/__tests__/notification-model.test.js +0 -68
  235. package/dist/__tests__/notification-model.test.js.map +0 -1
  236. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +0 -2
  237. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +0 -1
  238. package/dist/__tests__/realtime-agent-picker-models.test.js +0 -49
  239. package/dist/__tests__/realtime-agent-picker-models.test.js.map +0 -1
  240. package/dist/__tests__/realtime-audio-visuals.test.d.ts +0 -2
  241. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +0 -1
  242. package/dist/__tests__/realtime-audio-visuals.test.js +0 -123
  243. package/dist/__tests__/realtime-audio-visuals.test.js.map +0 -1
  244. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +0 -2
  245. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +0 -1
  246. package/dist/__tests__/realtime-delegation-card-cancel.test.js +0 -48
  247. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +0 -1
  248. package/dist/__tests__/realtime-disclosure.test.d.ts +0 -2
  249. package/dist/__tests__/realtime-disclosure.test.d.ts.map +0 -1
  250. package/dist/__tests__/realtime-disclosure.test.js +0 -164
  251. package/dist/__tests__/realtime-disclosure.test.js.map +0 -1
  252. package/dist/__tests__/realtime-pairing.test.d.ts +0 -2
  253. package/dist/__tests__/realtime-pairing.test.d.ts.map +0 -1
  254. package/dist/__tests__/realtime-pairing.test.js +0 -207
  255. package/dist/__tests__/realtime-pairing.test.js.map +0 -1
  256. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +0 -2
  257. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +0 -1
  258. package/dist/__tests__/realtime-review-lifecycle.test.js +0 -154
  259. package/dist/__tests__/realtime-review-lifecycle.test.js.map +0 -1
  260. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +0 -2
  261. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +0 -1
  262. package/dist/__tests__/realtime-session-cancel-usage.test.js +0 -230
  263. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +0 -1
  264. package/dist/__tests__/realtime-session-channels.test.d.ts +0 -2
  265. package/dist/__tests__/realtime-session-channels.test.d.ts.map +0 -1
  266. package/dist/__tests__/realtime-session-channels.test.js +0 -252
  267. package/dist/__tests__/realtime-session-channels.test.js.map +0 -1
  268. package/dist/__tests__/realtime-session-client-tools.test.d.ts +0 -2
  269. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +0 -1
  270. package/dist/__tests__/realtime-session-client-tools.test.js +0 -103
  271. package/dist/__tests__/realtime-session-client-tools.test.js.map +0 -1
  272. package/dist/__tests__/realtime-session-minimized.test.d.ts +0 -2
  273. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +0 -1
  274. package/dist/__tests__/realtime-session-minimized.test.js +0 -32
  275. package/dist/__tests__/realtime-session-minimized.test.js.map +0 -1
  276. package/dist/__tests__/realtime-session-mint.test.d.ts +0 -2
  277. package/dist/__tests__/realtime-session-mint.test.d.ts.map +0 -1
  278. package/dist/__tests__/realtime-session-mint.test.js +0 -69
  279. package/dist/__tests__/realtime-session-mint.test.js.map +0 -1
  280. package/dist/__tests__/realtime-session-policy.test.d.ts +0 -2
  281. package/dist/__tests__/realtime-session-policy.test.d.ts.map +0 -1
  282. package/dist/__tests__/realtime-session-policy.test.js +0 -303
  283. package/dist/__tests__/realtime-session-policy.test.js.map +0 -1
  284. package/dist/__tests__/realtime-session-review.service.test.d.ts +0 -2
  285. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +0 -1
  286. package/dist/__tests__/realtime-session-review.service.test.js +0 -743
  287. package/dist/__tests__/realtime-session-review.service.test.js.map +0 -1
  288. package/dist/__tests__/realtime-session-state.test.d.ts +0 -2
  289. package/dist/__tests__/realtime-session-state.test.d.ts.map +0 -1
  290. package/dist/__tests__/realtime-session-state.test.js +0 -83
  291. package/dist/__tests__/realtime-session-state.test.js.map +0 -1
  292. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +0 -2
  293. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +0 -1
  294. package/dist/__tests__/realtime-session-timeline-card.test.js +0 -106
  295. package/dist/__tests__/realtime-session-timeline-card.test.js.map +0 -1
  296. package/dist/__tests__/realtime-session-timeline.test.d.ts +0 -2
  297. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +0 -1
  298. package/dist/__tests__/realtime-session-timeline.test.js +0 -142
  299. package/dist/__tests__/realtime-session-timeline.test.js.map +0 -1
  300. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +0 -19
  301. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +0 -1
  302. package/dist/__tests__/realtime-sessions-adapter.test.js +0 -188
  303. package/dist/__tests__/realtime-sessions-adapter.test.js.map +0 -1
  304. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +0 -2
  305. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +0 -1
  306. package/dist/__tests__/realtime-surface-panel-prefs.test.js +0 -100
  307. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +0 -1
  308. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +0 -2
  309. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +0 -1
  310. package/dist/__tests__/realtime-surface-tabs-model.test.js +0 -193
  311. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +0 -1
  312. package/dist/__tests__/remote-browser-audio-player.test.d.ts +0 -2
  313. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +0 -1
  314. package/dist/__tests__/remote-browser-audio-player.test.js +0 -137
  315. package/dist/__tests__/remote-browser-audio-player.test.js.map +0 -1
  316. package/dist/__tests__/remote-browser-channel.test.d.ts +0 -2
  317. package/dist/__tests__/remote-browser-channel.test.d.ts.map +0 -1
  318. package/dist/__tests__/remote-browser-channel.test.js +0 -585
  319. package/dist/__tests__/remote-browser-channel.test.js.map +0 -1
  320. package/dist/__tests__/search-service.test.d.ts +0 -2
  321. package/dist/__tests__/search-service.test.d.ts.map +0 -1
  322. package/dist/__tests__/search-service.test.js +0 -114
  323. package/dist/__tests__/search-service.test.js.map +0 -1
  324. package/dist/__tests__/slot-defaults.test.d.ts +0 -24
  325. package/dist/__tests__/slot-defaults.test.d.ts.map +0 -1
  326. package/dist/__tests__/slot-defaults.test.js +0 -63
  327. package/dist/__tests__/slot-defaults.test.js.map +0 -1
  328. package/dist/__tests__/toast-service.test.d.ts +0 -2
  329. package/dist/__tests__/toast-service.test.d.ts.map +0 -1
  330. package/dist/__tests__/toast-service.test.js +0 -143
  331. package/dist/__tests__/toast-service.test.js.map +0 -1
  332. package/dist/__tests__/user-authorization.test.d.ts +0 -2
  333. package/dist/__tests__/user-authorization.test.d.ts.map +0 -1
  334. package/dist/__tests__/user-authorization.test.js +0 -97
  335. package/dist/__tests__/user-authorization.test.js.map +0 -1
  336. package/dist/__tests__/voice-session-narration.test.d.ts +0 -2
  337. package/dist/__tests__/voice-session-narration.test.d.ts.map +0 -1
  338. package/dist/__tests__/voice-session-narration.test.js +0 -609
  339. package/dist/__tests__/voice-session-narration.test.js.map +0 -1
  340. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +0 -2
  341. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +0 -1
  342. package/dist/__tests__/whiteboard-artifact-viewer.test.js +0 -101
  343. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +0 -1
  344. package/dist/__tests__/whiteboard-channel.test.d.ts +0 -2
  345. package/dist/__tests__/whiteboard-channel.test.d.ts.map +0 -1
  346. package/dist/__tests__/whiteboard-channel.test.js +0 -260
  347. package/dist/__tests__/whiteboard-channel.test.js.map +0 -1
  348. package/dist/__tests__/whiteboard-restore-state.test.d.ts +0 -2
  349. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +0 -1
  350. package/dist/__tests__/whiteboard-restore-state.test.js +0 -108
  351. package/dist/__tests__/whiteboard-restore-state.test.js.map +0 -1
@@ -0,0 +1,397 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from "@angular/core";
4
+ import * as i1 from "@angular/common";
5
+ const _c0 = ["audio"];
6
+ function RealtimeEvidencePlaybackComponent_Conditional_2_For_10_Template(rf, ctx) { if (rf & 1) {
7
+ i0.ɵɵdomElement(0, "span", 20);
8
+ } if (rf & 2) {
9
+ const bar_r4 = ctx.$implicit;
10
+ const $index_r5 = ctx.$index;
11
+ const ctx_r1 = i0.ɵɵnextContext(2);
12
+ i0.ɵɵstyleProp("height", bar_r4 * 100, "%");
13
+ i0.ɵɵclassProp("evp-bar--played", $index_r5 / ctx_r1.WaveformBars.length <= ctx_r1.Progress);
14
+ } }
15
+ function RealtimeEvidencePlaybackComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
16
+ const _r1 = i0.ɵɵgetCurrentView();
17
+ i0.ɵɵdomElementStart(0, "audio", 7, 0);
18
+ i0.ɵɵdomListener("loadedmetadata", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_audio_loadedmetadata_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnLoadedMetadata()); })("timeupdate", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_audio_timeupdate_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnTimeUpdate()); })("play", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_audio_play_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPlay()); })("pause", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_audio_pause_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPause()); })("ended", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_audio_ended_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnEnded()); });
19
+ i0.ɵɵdomElementEnd();
20
+ i0.ɵɵdomElementStart(2, "button", 8);
21
+ i0.ɵɵdomListener("click", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.TogglePlay()); });
22
+ i0.ɵɵdomElement(3, "i", 9);
23
+ i0.ɵɵdomElementEnd();
24
+ i0.ɵɵdomElementStart(4, "div", 10)(5, "div", 11, 1);
25
+ i0.ɵɵpipe(7, "number");
26
+ i0.ɵɵdomListener("click", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_div_click_5_listener($event) { i0.ɵɵrestoreView(_r1); const track_r3 = i0.ɵɵreference(6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnScrubberClick($event, track_r3)); })("keydown", function RealtimeEvidencePlaybackComponent_Conditional_2_Template_div_keydown_5_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnScrubberKeydown($event)); });
27
+ i0.ɵɵdomElementStart(8, "div", 12);
28
+ i0.ɵɵrepeaterCreate(9, RealtimeEvidencePlaybackComponent_Conditional_2_For_10_Template, 1, 4, "span", 13, i0.ɵɵrepeaterTrackByIndex);
29
+ i0.ɵɵdomElementEnd();
30
+ i0.ɵɵdomElement(11, "div", 14)(12, "div", 15);
31
+ i0.ɵɵdomElementEnd();
32
+ i0.ɵɵdomElementStart(13, "div", 16)(14, "span", 17);
33
+ i0.ɵɵtext(15);
34
+ i0.ɵɵdomElementEnd();
35
+ i0.ɵɵdomElementStart(16, "span", 18);
36
+ i0.ɵɵtext(17, "/");
37
+ i0.ɵɵdomElementEnd();
38
+ i0.ɵɵdomElementStart(18, "span", 19);
39
+ i0.ɵɵtext(19);
40
+ i0.ɵɵdomElementEnd()()();
41
+ } if (rf & 2) {
42
+ const ctx_r1 = i0.ɵɵnextContext();
43
+ i0.ɵɵdomProperty("src", ctx_r1.AudioUrl);
44
+ i0.ɵɵadvance(2);
45
+ i0.ɵɵdomProperty("title", ctx_r1.IsPlaying ? "Pause" : "Play");
46
+ i0.ɵɵattribute("aria-label", ctx_r1.IsPlaying ? "Pause recording" : "Play recording");
47
+ i0.ɵɵadvance();
48
+ i0.ɵɵclassProp("fa-pause", ctx_r1.IsPlaying)("fa-play", !ctx_r1.IsPlaying);
49
+ i0.ɵɵadvance(2);
50
+ i0.ɵɵattribute("aria-valuemin", 0)("aria-valuemax", 100)("aria-valuenow", i0.ɵɵpipeBind2(7, 17, ctx_r1.Progress * 100, "1.0-0"))("aria-valuetext", ctx_r1.CurrentLabel + " of " + ctx_r1.DurationLabel);
51
+ i0.ɵɵadvance(4);
52
+ i0.ɵɵrepeater(ctx_r1.WaveformBars);
53
+ i0.ɵɵadvance(2);
54
+ i0.ɵɵstyleProp("width", ctx_r1.Progress * 100, "%");
55
+ i0.ɵɵadvance();
56
+ i0.ɵɵstyleProp("left", ctx_r1.Progress * 100, "%");
57
+ i0.ɵɵadvance(3);
58
+ i0.ɵɵtextInterpolate(ctx_r1.CurrentLabel);
59
+ i0.ɵɵadvance(4);
60
+ i0.ɵɵtextInterpolate(ctx_r1.DurationLabel);
61
+ } }
62
+ function RealtimeEvidencePlaybackComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
63
+ i0.ɵɵdomElementStart(0, "div", 4);
64
+ i0.ɵɵdomElement(1, "i", 21);
65
+ i0.ɵɵdomElementStart(2, "span");
66
+ i0.ɵɵtext(3, "No audio recording is available for this session.");
67
+ i0.ɵɵdomElementEnd()();
68
+ } }
69
+ function RealtimeEvidencePlaybackComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵdomElementStart(0, "div", 6);
71
+ i0.ɵɵdomElement(1, "i", 22);
72
+ i0.ɵɵdomElementStart(2, "p");
73
+ i0.ɵɵtext(3, "No transcript turns to show.");
74
+ i0.ɵɵdomElementEnd()();
75
+ } }
76
+ function RealtimeEvidencePlaybackComponent_Conditional_6_For_1_Template(rf, ctx) { if (rf & 1) {
77
+ const _r6 = i0.ɵɵgetCurrentView();
78
+ i0.ɵɵdomElementStart(0, "button", 24);
79
+ i0.ɵɵdomListener("click", function RealtimeEvidencePlaybackComponent_Conditional_6_For_1_Template_button_click_0_listener() { const turn_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SeekToTurn(turn_r7)); });
80
+ i0.ɵɵdomElementStart(1, "div", 25)(2, "span", 26);
81
+ i0.ɵɵtext(3);
82
+ i0.ɵɵdomElementEnd();
83
+ i0.ɵɵdomElementStart(4, "span", 27);
84
+ i0.ɵɵtext(5);
85
+ i0.ɵɵdomElementEnd()();
86
+ i0.ɵɵdomElementStart(6, "p", 28);
87
+ i0.ɵɵtext(7);
88
+ i0.ɵɵdomElementEnd()();
89
+ } if (rf & 2) {
90
+ const turn_r7 = ctx.$implicit;
91
+ const ctx_r1 = i0.ɵɵnextContext(2);
92
+ i0.ɵɵclassProp("evp-turn--user", ctx_r1.IsUserTurn(turn_r7))("evp-turn--agent", !ctx_r1.IsUserTurn(turn_r7))("evp-turn--active", ctx_r1.IsTurnActive(turn_r7));
93
+ i0.ɵɵattribute("aria-label", ctx_r1.SpeakerLabel(turn_r7) + " at " + ctx_r1.TurnTimestamp(turn_r7) + ": " + turn_r7.Message)("aria-current", ctx_r1.IsTurnActive(turn_r7) ? "true" : null);
94
+ i0.ɵɵadvance(3);
95
+ i0.ɵɵtextInterpolate(ctx_r1.SpeakerLabel(turn_r7));
96
+ i0.ɵɵadvance(2);
97
+ i0.ɵɵtextInterpolate(ctx_r1.TurnTimestamp(turn_r7));
98
+ i0.ɵɵadvance(2);
99
+ i0.ɵɵtextInterpolate(turn_r7.Message);
100
+ } }
101
+ function RealtimeEvidencePlaybackComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
102
+ i0.ɵɵrepeaterCreate(0, RealtimeEvidencePlaybackComponent_Conditional_6_For_1_Template, 8, 11, "button", 23, i0.ɵɵcomponentInstance().TrackByTurn, true);
103
+ } if (rf & 2) {
104
+ const ctx_r1 = i0.ɵɵnextContext();
105
+ i0.ɵɵrepeater(ctx_r1.Turns);
106
+ } }
107
+ /** Number of bars rendered in the synthetic waveform behind the scrubber. */
108
+ const WAVEFORM_BARS = 64;
109
+ /**
110
+ * TIME-ALIGNED EVIDENCE PLAYBACK (`mj-realtime-evidence-playback`) — replays a realtime session's
111
+ * recorded audio next to its transcript, with the two kept in sync. The audio gets a custom transport
112
+ * (play/pause, a synthetic waveform scrubber, time/duration readout); the transcript is a scrollable
113
+ * list of clickable turns. Clicking a turn seeks the audio to that turn's start and plays; as the audio
114
+ * plays, the turn whose `[UtteranceStartMs, UtteranceEndMs]` window contains the playhead is highlighted.
115
+ *
116
+ * Timestamps come from {@link EvidencePlaybackTurn.UtteranceStartMs}; when a turn has none, the
117
+ * component falls back to (`__mj_CreatedAt` − {@link RecordingStartedAt}). Generic + Router-free: all
118
+ * state arrives via inputs, the only output is {@link TurnSeek}.
119
+ */
120
+ export class RealtimeEvidencePlaybackComponent {
121
+ /** The session's transcript turns, in chronological order. */
122
+ set Turns(value) {
123
+ this._turns = value ?? [];
124
+ this.cdr.markForCheck();
125
+ }
126
+ get Turns() {
127
+ return this._turns;
128
+ }
129
+ _turns = [];
130
+ /** The recorded session audio URL, or `null` when no recording is available. */
131
+ set AudioUrl(value) {
132
+ if (value === this._audioUrl) {
133
+ return;
134
+ }
135
+ this._audioUrl = value;
136
+ this.CurrentMs = 0;
137
+ this.DurationMs = 0;
138
+ this.IsPlaying = false;
139
+ this.cdr.markForCheck();
140
+ }
141
+ get AudioUrl() {
142
+ return this._audioUrl;
143
+ }
144
+ _audioUrl = null;
145
+ /** When the recording started, used to derive a turn's offset when it has no `UtteranceStartMs`. */
146
+ RecordingStartedAt = null;
147
+ /** Display name of the agent — labels and colors AI turns. */
148
+ AgentName = 'Agent';
149
+ /** Emitted when the user clicks a transcript turn (to seek the audio to it). */
150
+ TurnSeek = new EventEmitter();
151
+ /** Current playhead position in ms (driven by the audio element's `timeupdate`). */
152
+ CurrentMs = 0;
153
+ /** Total audio duration in ms (set on `loadedmetadata`). */
154
+ DurationMs = 0;
155
+ /** Whether the audio is currently playing. */
156
+ IsPlaying = false;
157
+ /** The synthetic waveform bar heights (stable per AudioUrl). */
158
+ WaveformBars = this.buildWaveform();
159
+ audioRef;
160
+ cdr = inject(ChangeDetectorRef);
161
+ ngOnDestroy() {
162
+ this.audioRef?.nativeElement.pause();
163
+ }
164
+ /** The `<audio>` element, or `null` before the view initializes / when there's no audio. */
165
+ get audioEl() {
166
+ return this.audioRef?.nativeElement ?? null;
167
+ }
168
+ /** Playhead progress as a fraction (0..1), for the scrubber fill + waveform reveal. */
169
+ get Progress() {
170
+ return this.DurationMs > 0 ? Math.min(1, this.CurrentMs / this.DurationMs) : 0;
171
+ }
172
+ /** Toggles audio play / pause. */
173
+ TogglePlay() {
174
+ const el = this.audioEl;
175
+ if (!el) {
176
+ return;
177
+ }
178
+ if (el.paused) {
179
+ void el.play().catch(() => undefined);
180
+ }
181
+ else {
182
+ el.pause();
183
+ }
184
+ }
185
+ /** Seeks to a fraction of the duration (scrubber click / keyboard). */
186
+ SeekToFraction(fraction) {
187
+ const el = this.audioEl;
188
+ if (!el || this.DurationMs <= 0) {
189
+ return;
190
+ }
191
+ el.currentTime = (Math.max(0, Math.min(1, fraction)) * this.DurationMs) / 1000;
192
+ this.CurrentMs = el.currentTime * 1000;
193
+ this.cdr.markForCheck();
194
+ }
195
+ /** Scrubber pointer handler — maps the click X within the track to a seek fraction. */
196
+ OnScrubberClick(event, track) {
197
+ const rect = track.getBoundingClientRect();
198
+ if (rect.width <= 0) {
199
+ return;
200
+ }
201
+ this.SeekToFraction((event.clientX - rect.left) / rect.width);
202
+ }
203
+ /** Scrubber keyboard handler — arrows nudge ±5s, Home/End jump to ends, Space toggles play. */
204
+ OnScrubberKeydown(event) {
205
+ const stepMs = 5000;
206
+ switch (event.key) {
207
+ case 'ArrowRight':
208
+ event.preventDefault();
209
+ this.seekByMs(stepMs);
210
+ break;
211
+ case 'ArrowLeft':
212
+ event.preventDefault();
213
+ this.seekByMs(-stepMs);
214
+ break;
215
+ case 'Home':
216
+ event.preventDefault();
217
+ this.SeekToFraction(0);
218
+ break;
219
+ case 'End':
220
+ event.preventDefault();
221
+ this.SeekToFraction(1);
222
+ break;
223
+ case ' ':
224
+ case 'Enter':
225
+ event.preventDefault();
226
+ this.TogglePlay();
227
+ break;
228
+ default:
229
+ break;
230
+ }
231
+ }
232
+ /** Clicking a transcript turn → seek the audio to it, play, and emit {@link TurnSeek}. */
233
+ SeekToTurn(turn) {
234
+ const startMs = this.turnStartMs(turn);
235
+ const el = this.audioEl;
236
+ if (el && this.DurationMs > 0) {
237
+ el.currentTime = Math.max(0, startMs / 1000);
238
+ this.CurrentMs = startMs;
239
+ void el.play().catch(() => undefined);
240
+ }
241
+ this.TurnSeek.emit(turn);
242
+ this.cdr.markForCheck();
243
+ }
244
+ /** True when the playhead falls within this turn's utterance window (drives the active highlight). */
245
+ IsTurnActive(turn) {
246
+ if (!this.IsPlaying && this.CurrentMs === 0) {
247
+ return false;
248
+ }
249
+ const start = this.turnStartMs(turn);
250
+ const end = this.turnEndMs(turn);
251
+ return this.CurrentMs >= start && this.CurrentMs < end;
252
+ }
253
+ /** Whether a turn is from the human user (vs. the agent) — drives the speaker color + label. */
254
+ IsUserTurn(turn) {
255
+ return turn.Role === 'User';
256
+ }
257
+ /** The speaker label for a turn. */
258
+ SpeakerLabel(turn) {
259
+ return this.IsUserTurn(turn) ? 'You' : this.AgentName;
260
+ }
261
+ /** The mm:ss timestamp shown on a turn (its start offset within the recording). */
262
+ TurnTimestamp(turn) {
263
+ return this.formatMs(this.turnStartMs(turn));
264
+ }
265
+ /** mm:ss of the current playhead. */
266
+ get CurrentLabel() {
267
+ return this.formatMs(this.CurrentMs);
268
+ }
269
+ /** mm:ss of the total duration. */
270
+ get DurationLabel() {
271
+ return this.formatMs(this.DurationMs);
272
+ }
273
+ /** Stable `@for` track for the transcript. */
274
+ TrackByTurn(_index, turn) {
275
+ return turn.ID;
276
+ }
277
+ // ----- audio element event handlers (bound in the template) -------------------------------
278
+ /** `loadedmetadata` → capture the duration. */
279
+ OnLoadedMetadata() {
280
+ const el = this.audioEl;
281
+ this.DurationMs = el && Number.isFinite(el.duration) ? el.duration * 1000 : 0;
282
+ this.cdr.markForCheck();
283
+ }
284
+ /** `timeupdate` → advance the playhead + re-evaluate the active turn. */
285
+ OnTimeUpdate() {
286
+ const el = this.audioEl;
287
+ if (el) {
288
+ this.CurrentMs = el.currentTime * 1000;
289
+ this.cdr.markForCheck();
290
+ }
291
+ }
292
+ /** `play` / `pause` / `ended` → reflect the transport state. */
293
+ OnPlay() {
294
+ this.IsPlaying = true;
295
+ this.cdr.markForCheck();
296
+ }
297
+ OnPause() {
298
+ this.IsPlaying = false;
299
+ this.cdr.markForCheck();
300
+ }
301
+ OnEnded() {
302
+ this.IsPlaying = false;
303
+ this.cdr.markForCheck();
304
+ }
305
+ // ----- helpers ----------------------------------------------------------------------------
306
+ /** Nudges the playhead by `deltaMs`, clamped to the duration. */
307
+ seekByMs(deltaMs) {
308
+ if (this.DurationMs <= 0) {
309
+ return;
310
+ }
311
+ this.SeekToFraction((this.CurrentMs + deltaMs) / this.DurationMs);
312
+ }
313
+ /**
314
+ * A turn's start offset (ms) within the recording: prefer {@link EvidencePlaybackTurn.UtteranceStartMs};
315
+ * fall back to (`__mj_CreatedAt` − {@link RecordingStartedAt}); 0 when neither is available.
316
+ */
317
+ turnStartMs(turn) {
318
+ if (turn.UtteranceStartMs != null) {
319
+ return Math.max(0, turn.UtteranceStartMs);
320
+ }
321
+ if (this.RecordingStartedAt && turn.__mj_CreatedAt) {
322
+ return Math.max(0, turn.__mj_CreatedAt.getTime() - this.RecordingStartedAt.getTime());
323
+ }
324
+ return 0;
325
+ }
326
+ /**
327
+ * A turn's end offset (ms): prefer {@link EvidencePlaybackTurn.UtteranceEndMs}; otherwise the
328
+ * next turn's start; otherwise the recording duration (or start + a small window when duration is unknown).
329
+ */
330
+ turnEndMs(turn) {
331
+ if (turn.UtteranceEndMs != null) {
332
+ return Math.max(this.turnStartMs(turn), turn.UtteranceEndMs);
333
+ }
334
+ const idx = this._turns.indexOf(turn);
335
+ const next = idx >= 0 ? this._turns[idx + 1] : undefined;
336
+ if (next) {
337
+ return this.turnStartMs(next);
338
+ }
339
+ return this.DurationMs > 0 ? this.DurationMs : this.turnStartMs(turn) + 4000;
340
+ }
341
+ /** Formats a ms offset as mm:ss (clamped at 0). */
342
+ formatMs(ms) {
343
+ const total = Math.max(0, Math.floor(ms / 1000));
344
+ const minutes = Math.floor(total / 60);
345
+ const seconds = total % 60;
346
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`;
347
+ }
348
+ /** Builds a stable pseudo-random waveform (deterministic, no per-render churn). */
349
+ buildWaveform() {
350
+ const bars = [];
351
+ for (let i = 0; i < WAVEFORM_BARS; i++) {
352
+ // A smooth, repeatable envelope — looks like audio without needing to decode any.
353
+ const base = Math.sin(i * 0.5) * 0.5 + 0.5;
354
+ const detail = Math.sin(i * 1.7) * 0.25 + 0.25;
355
+ bars.push(Math.max(0.12, Math.min(1, base * 0.7 + detail * 0.5)));
356
+ }
357
+ return bars;
358
+ }
359
+ static ɵfac = function RealtimeEvidencePlaybackComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeEvidencePlaybackComponent)(); };
360
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeEvidencePlaybackComponent, selectors: [["mj-realtime-evidence-playback"]], viewQuery: function RealtimeEvidencePlaybackComponent_Query(rf, ctx) { if (rf & 1) {
361
+ i0.ɵɵviewQuery(_c0, 5);
362
+ } if (rf & 2) {
363
+ let _t;
364
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.audioRef = _t.first);
365
+ } }, inputs: { Turns: "Turns", AudioUrl: "AudioUrl", RecordingStartedAt: "RecordingStartedAt", AgentName: "AgentName" }, outputs: { TurnSeek: "TurnSeek" }, decls: 7, vars: 2, consts: [["audio", ""], ["track", ""], [1, "evp"], [1, "evp-player"], [1, "evp-noaudio"], ["role", "list", "aria-label", "Session transcript", 1, "evp-transcript"], [1, "evp-transcript-empty"], ["preload", "metadata", 3, "loadedmetadata", "timeupdate", "play", "pause", "ended", "src"], ["type", "button", 1, "evp-play", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-solid"], [1, "evp-transport"], ["role", "slider", "tabindex", "0", "aria-label", "Seek through the recording", 1, "evp-scrubber", 3, "click", "keydown"], ["aria-hidden", "true", 1, "evp-waveform"], [1, "evp-bar", 3, "evp-bar--played", "height"], ["aria-hidden", "true", 1, "evp-progress"], ["aria-hidden", "true", 1, "evp-thumb"], [1, "evp-time"], [1, "evp-time__current"], [1, "evp-time__sep"], [1, "evp-time__total"], [1, "evp-bar"], ["aria-hidden", "true", 1, "fa-solid", "fa-microphone-slash"], ["aria-hidden", "true", 1, "fa-solid", "fa-comment-dots"], ["type", "button", "role", "listitem", "tabindex", "0", 1, "evp-turn", 3, "evp-turn--user", "evp-turn--agent", "evp-turn--active"], ["type", "button", "role", "listitem", "tabindex", "0", 1, "evp-turn", 3, "click"], [1, "evp-turn__meta"], [1, "evp-turn__speaker"], [1, "evp-turn__time"], [1, "evp-turn__text"]], template: function RealtimeEvidencePlaybackComponent_Template(rf, ctx) { if (rf & 1) {
366
+ i0.ɵɵdomElementStart(0, "div", 2)(1, "div", 3);
367
+ i0.ɵɵconditionalCreate(2, RealtimeEvidencePlaybackComponent_Conditional_2_Template, 20, 20)(3, RealtimeEvidencePlaybackComponent_Conditional_3_Template, 4, 0, "div", 4);
368
+ i0.ɵɵdomElementEnd();
369
+ i0.ɵɵdomElementStart(4, "div", 5);
370
+ i0.ɵɵconditionalCreate(5, RealtimeEvidencePlaybackComponent_Conditional_5_Template, 4, 0, "div", 6)(6, RealtimeEvidencePlaybackComponent_Conditional_6_Template, 2, 0);
371
+ i0.ɵɵdomElementEnd()();
372
+ } if (rf & 2) {
373
+ i0.ɵɵadvance(2);
374
+ i0.ɵɵconditional(ctx.AudioUrl ? 2 : 3);
375
+ i0.ɵɵadvance(3);
376
+ i0.ɵɵconditional(ctx.Turns.length === 0 ? 5 : 6);
377
+ } }, dependencies: [CommonModule, i1.DecimalPipe], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n}\n\n.evp[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n\n\n.evp-player[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 0 0 auto;\n padding: 12px 14px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.evp-play[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: var(--mj-radius-full, 50%);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n font-size: 0.9375rem;\n transition: background 0.12s ease, transform 0.12s ease;\n\n &:hover {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n }\n\n &:active {\n transform: scale(0.94);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n}\n\n.evp-transport[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.evp-scrubber[_ngcontent-%COMP%] {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n height: 36px;\n cursor: pointer;\n border-radius: var(--mj-radius-sm, 4px);\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n}\n\n.evp-waveform[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 2px;\n height: 100%;\n pointer-events: none;\n}\n\n.evp-bar[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 1px;\n border-radius: 1px;\n background: var(--mj-border-strong);\n transition: background 0.1s ease;\n\n &--played {\n background: var(--mj-brand-primary);\n }\n}\n\n.evp-progress[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n pointer-events: none;\n border-radius: var(--mj-radius-sm, 4px) 0 0 var(--mj-radius-sm, 4px);\n}\n\n.evp-thumb[_ngcontent-%COMP%] {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: var(--mj-radius-full, 50%);\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.evp-time[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n\n &__current {\n color: var(--mj-text-primary);\n font-weight: 600;\n }\n\n &__sep {\n color: var(--mj-text-disabled);\n }\n}\n\n.evp-noaudio[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n\n i {\n color: var(--mj-text-disabled);\n }\n}\n\n\n\n.evp-transcript[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n scrollbar-width: thin;\n}\n\n.evp-transcript-empty[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n color: var(--mj-text-muted);\n font-size: 0.8125rem;\n\n i {\n font-size: 1.75rem;\n color: var(--mj-text-disabled);\n }\n\n p {\n margin: 0;\n }\n}\n\n.evp-turn[_ngcontent-%COMP%] {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 3px;\n width: 100%;\n margin: 0;\n padding: 8px 10px 8px 14px;\n border: none;\n border-left: 3px solid transparent;\n border-radius: var(--mj-radius-md, 6px);\n background: transparent;\n text-align: left;\n font: inherit;\n color: inherit;\n cursor: pointer;\n transition: background 0.12s ease, border-color 0.12s ease;\n\n &:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n }\n\n &--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left-color: var(--mj-brand-primary);\n }\n\n &__meta {\n display: flex;\n align-items: baseline;\n gap: 8px;\n }\n\n &__speaker {\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.01em;\n }\n\n &--user &__speaker {\n color: var(--mj-status-info, var(--mj-brand-primary));\n }\n\n &--agent &__speaker {\n color: var(--mj-brand-primary);\n }\n\n &__time {\n font-size: 0.6875rem;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-muted);\n }\n\n &__text {\n margin: 0;\n font-size: 0.8125rem;\n line-height: 1.45;\n color: var(--mj-text-primary);\n white-space: pre-wrap;\n word-break: break-word;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .evp-play[_ngcontent-%COMP%], \n .evp-bar[_ngcontent-%COMP%], \n .evp-turn[_ngcontent-%COMP%] {\n transition: none;\n }\n}"], changeDetection: 0 });
378
+ }
379
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeEvidencePlaybackComponent, [{
380
+ type: Component,
381
+ args: [{ standalone: true, selector: 'mj-realtime-evidence-playback', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"evp\">\n <!-- ---------- Audio transport ---------- -->\n <div class=\"evp-player\">\n @if (AudioUrl) {\n <audio\n #audio\n [src]=\"AudioUrl\"\n preload=\"metadata\"\n (loadedmetadata)=\"OnLoadedMetadata()\"\n (timeupdate)=\"OnTimeUpdate()\"\n (play)=\"OnPlay()\"\n (pause)=\"OnPause()\"\n (ended)=\"OnEnded()\"\n ></audio>\n\n <button\n type=\"button\"\n class=\"evp-play\"\n [attr.aria-label]=\"IsPlaying ? 'Pause recording' : 'Play recording'\"\n [title]=\"IsPlaying ? 'Pause' : 'Play'\"\n (click)=\"TogglePlay()\"\n >\n <i class=\"fa-solid\" [class.fa-pause]=\"IsPlaying\" [class.fa-play]=\"!IsPlaying\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"evp-transport\">\n <div\n #track\n class=\"evp-scrubber\"\n role=\"slider\"\n tabindex=\"0\"\n aria-label=\"Seek through the recording\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"100\"\n [attr.aria-valuenow]=\"(Progress * 100) | number: '1.0-0'\"\n [attr.aria-valuetext]=\"CurrentLabel + ' of ' + DurationLabel\"\n (click)=\"OnScrubberClick($event, track)\"\n (keydown)=\"OnScrubberKeydown($event)\"\n >\n <div class=\"evp-waveform\" aria-hidden=\"true\">\n @for (bar of WaveformBars; track $index) {\n <span\n class=\"evp-bar\"\n [class.evp-bar--played]=\"($index / WaveformBars.length) <= Progress\"\n [style.height.%]=\"bar * 100\"\n ></span>\n }\n </div>\n <div class=\"evp-progress\" [style.width.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n <div class=\"evp-thumb\" [style.left.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"evp-time\">\n <span class=\"evp-time__current\">{{ CurrentLabel }}</span>\n <span class=\"evp-time__sep\">/</span>\n <span class=\"evp-time__total\">{{ DurationLabel }}</span>\n </div>\n </div>\n } @else {\n <div class=\"evp-noaudio\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <span>No audio recording is available for this session.</span>\n </div>\n }\n </div>\n\n <!-- ---------- Transcript ---------- -->\n <div class=\"evp-transcript\" role=\"list\" aria-label=\"Session transcript\">\n @if (Turns.length === 0) {\n <div class=\"evp-transcript-empty\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n <p>No transcript turns to show.</p>\n </div>\n } @else {\n @for (turn of Turns; track TrackByTurn($index, turn)) {\n <button\n type=\"button\"\n class=\"evp-turn\"\n role=\"listitem\"\n tabindex=\"0\"\n [class.evp-turn--user]=\"IsUserTurn(turn)\"\n [class.evp-turn--agent]=\"!IsUserTurn(turn)\"\n [class.evp-turn--active]=\"IsTurnActive(turn)\"\n [attr.aria-label]=\"SpeakerLabel(turn) + ' at ' + TurnTimestamp(turn) + ': ' + turn.Message\"\n [attr.aria-current]=\"IsTurnActive(turn) ? 'true' : null\"\n (click)=\"SeekToTurn(turn)\"\n >\n <div class=\"evp-turn__meta\">\n <span class=\"evp-turn__speaker\">{{ SpeakerLabel(turn) }}</span>\n <span class=\"evp-turn__time\">{{ TurnTimestamp(turn) }}</span>\n </div>\n <p class=\"evp-turn__text\">{{ turn.Message }}</p>\n </button>\n }\n }\n </div>\n</div>\n", styles: [":host {\n display: block;\n height: 100%;\n}\n\n.evp {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ---------- Audio transport ---------- */\n.evp-player {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 0 0 auto;\n padding: 12px 14px;\n border-bottom: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.evp-play {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: var(--mj-radius-full, 50%);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n font-size: 0.9375rem;\n transition: background 0.12s ease, transform 0.12s ease;\n\n &:hover {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n }\n\n &:active {\n transform: scale(0.94);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n}\n\n.evp-transport {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.evp-scrubber {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n height: 36px;\n cursor: pointer;\n border-radius: var(--mj-radius-sm, 4px);\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n}\n\n.evp-waveform {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 2px;\n height: 100%;\n pointer-events: none;\n}\n\n.evp-bar {\n flex: 1 1 auto;\n min-width: 1px;\n border-radius: 1px;\n background: var(--mj-border-strong);\n transition: background 0.1s ease;\n\n &--played {\n background: var(--mj-brand-primary);\n }\n}\n\n.evp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n pointer-events: none;\n border-radius: var(--mj-radius-sm, 4px) 0 0 var(--mj-radius-sm, 4px);\n}\n\n.evp-thumb {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: var(--mj-radius-full, 50%);\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.evp-time {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n\n &__current {\n color: var(--mj-text-primary);\n font-weight: 600;\n }\n\n &__sep {\n color: var(--mj-text-disabled);\n }\n}\n\n.evp-noaudio {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n\n i {\n color: var(--mj-text-disabled);\n }\n}\n\n/* ---------- Transcript ---------- */\n.evp-transcript {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n scrollbar-width: thin;\n}\n\n.evp-transcript-empty {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n color: var(--mj-text-muted);\n font-size: 0.8125rem;\n\n i {\n font-size: 1.75rem;\n color: var(--mj-text-disabled);\n }\n\n p {\n margin: 0;\n }\n}\n\n.evp-turn {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 3px;\n width: 100%;\n margin: 0;\n padding: 8px 10px 8px 14px;\n border: none;\n border-left: 3px solid transparent;\n border-radius: var(--mj-radius-md, 6px);\n background: transparent;\n text-align: left;\n font: inherit;\n color: inherit;\n cursor: pointer;\n transition: background 0.12s ease, border-color 0.12s ease;\n\n &:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n &:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n }\n\n &--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-left-color: var(--mj-brand-primary);\n }\n\n &__meta {\n display: flex;\n align-items: baseline;\n gap: 8px;\n }\n\n &__speaker {\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.01em;\n }\n\n &--user &__speaker {\n color: var(--mj-status-info, var(--mj-brand-primary));\n }\n\n &--agent &__speaker {\n color: var(--mj-brand-primary);\n }\n\n &__time {\n font-size: 0.6875rem;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-muted);\n }\n\n &__text {\n margin: 0;\n font-size: 0.8125rem;\n line-height: 1.45;\n color: var(--mj-text-primary);\n white-space: pre-wrap;\n word-break: break-word;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .evp-play,\n .evp-bar,\n .evp-turn {\n transition: none;\n }\n}\n"] }]
382
+ }], null, { Turns: [{
383
+ type: Input
384
+ }], AudioUrl: [{
385
+ type: Input
386
+ }], RecordingStartedAt: [{
387
+ type: Input
388
+ }], AgentName: [{
389
+ type: Input
390
+ }], TurnSeek: [{
391
+ type: Output
392
+ }], audioRef: [{
393
+ type: ViewChild,
394
+ args: ['audio']
395
+ }] }); })();
396
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeEvidencePlaybackComponent, { className: "RealtimeEvidencePlaybackComponent", filePath: "src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.ts", lineNumber: 58 }); })();
397
+ //# sourceMappingURL=realtime-evidence-playback.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-evidence-playback.component.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.ts","../../../../../src/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy,\n Output, ViewChild, inject\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/** Number of bars rendered in the synthetic waveform behind the scrubber. */\nconst WAVEFORM_BARS = 64;\n\n/**\n * The MINIMAL structural shape of a transcript turn this player needs — exactly the fields it reads\n * for the click-to-seek transcript and the playhead-sync highlight. Deliberately NOT the full\n * `MJConversationDetailEntity`: that keeps the player reusable by ANY caller that can supply these\n * fields (the in-conversation session-review overlay passes its lighter `RealtimeSessionReviewTurn`;\n * the Realtime Recordings dashboard passes full `MJConversationDetailEntity` rows — both satisfy this\n * widened shape structurally).\n *\n * Field unions/nullability are intentionally WIDER than the generated entity so an\n * `MJConversationDetailEntity[]` remains assignable here.\n */\nexport interface EvidencePlaybackTurn {\n /** Stable identity for the `@for` track. */\n ID: string;\n /** Speaker. The player only distinguishes `'User'` (human) from everything else (the agent). */\n Role: 'User' | 'AI' | 'Assistant' | 'Error' | string;\n /** The transcript text shown on the turn. */\n Message: string | null;\n /** Persisted turn time — fallback offset origin when the turn has no `UtteranceStartMs`. */\n __mj_CreatedAt: Date | null;\n /**\n * Precise media-relative start (ms from recording t0). `null`/absent when the driver supplied no\n * timing — the player falls back to (`__mj_CreatedAt` − `RecordingStartedAt`).\n */\n UtteranceStartMs?: number | null;\n /** Precise media-relative end (ms from recording t0). `null`/absent → derived from the next turn / duration. */\n UtteranceEndMs?: number | null;\n}\n\n/**\n * TIME-ALIGNED EVIDENCE PLAYBACK (`mj-realtime-evidence-playback`) — replays a realtime session's\n * recorded audio next to its transcript, with the two kept in sync. The audio gets a custom transport\n * (play/pause, a synthetic waveform scrubber, time/duration readout); the transcript is a scrollable\n * list of clickable turns. Clicking a turn seeks the audio to that turn's start and plays; as the audio\n * plays, the turn whose `[UtteranceStartMs, UtteranceEndMs]` window contains the playhead is highlighted.\n *\n * Timestamps come from {@link EvidencePlaybackTurn.UtteranceStartMs}; when a turn has none, the\n * component falls back to (`__mj_CreatedAt` − {@link RecordingStartedAt}). Generic + Router-free: all\n * state arrives via inputs, the only output is {@link TurnSeek}.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-evidence-playback',\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './realtime-evidence-playback.component.html',\n styleUrls: ['./realtime-evidence-playback.component.scss'],\n})\nexport class RealtimeEvidencePlaybackComponent implements OnDestroy {\n /** The session's transcript turns, in chronological order. */\n @Input()\n set Turns(value: EvidencePlaybackTurn[] | null) {\n this._turns = value ?? [];\n this.cdr.markForCheck();\n }\n get Turns(): EvidencePlaybackTurn[] {\n return this._turns;\n }\n private _turns: EvidencePlaybackTurn[] = [];\n\n /** The recorded session audio URL, or `null` when no recording is available. */\n @Input()\n set AudioUrl(value: string | null) {\n if (value === this._audioUrl) {\n return;\n }\n this._audioUrl = value;\n this.CurrentMs = 0;\n this.DurationMs = 0;\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n get AudioUrl(): string | null {\n return this._audioUrl;\n }\n private _audioUrl: string | null = null;\n\n /** When the recording started, used to derive a turn's offset when it has no `UtteranceStartMs`. */\n @Input() RecordingStartedAt: Date | null = null;\n\n /** Display name of the agent — labels and colors AI turns. */\n @Input() AgentName = 'Agent';\n\n /** Emitted when the user clicks a transcript turn (to seek the audio to it). */\n @Output() TurnSeek = new EventEmitter<EvidencePlaybackTurn>();\n\n /** Current playhead position in ms (driven by the audio element's `timeupdate`). */\n public CurrentMs = 0;\n /** Total audio duration in ms (set on `loadedmetadata`). */\n public DurationMs = 0;\n /** Whether the audio is currently playing. */\n public IsPlaying = false;\n\n /** The synthetic waveform bar heights (stable per AudioUrl). */\n public readonly WaveformBars: number[] = this.buildWaveform();\n\n @ViewChild('audio') private audioRef?: ElementRef<HTMLAudioElement>;\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n ngOnDestroy(): void {\n this.audioRef?.nativeElement.pause();\n }\n\n /** The `<audio>` element, or `null` before the view initializes / when there's no audio. */\n private get audioEl(): HTMLAudioElement | null {\n return this.audioRef?.nativeElement ?? null;\n }\n\n /** Playhead progress as a fraction (0..1), for the scrubber fill + waveform reveal. */\n public get Progress(): number {\n return this.DurationMs > 0 ? Math.min(1, this.CurrentMs / this.DurationMs) : 0;\n }\n\n /** Toggles audio play / pause. */\n public TogglePlay(): void {\n const el = this.audioEl;\n if (!el) {\n return;\n }\n if (el.paused) {\n void el.play().catch(() => undefined);\n } else {\n el.pause();\n }\n }\n\n /** Seeks to a fraction of the duration (scrubber click / keyboard). */\n public SeekToFraction(fraction: number): void {\n const el = this.audioEl;\n if (!el || this.DurationMs <= 0) {\n return;\n }\n el.currentTime = (Math.max(0, Math.min(1, fraction)) * this.DurationMs) / 1000;\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n\n /** Scrubber pointer handler — maps the click X within the track to a seek fraction. */\n public OnScrubberClick(event: MouseEvent, track: HTMLElement): void {\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) {\n return;\n }\n this.SeekToFraction((event.clientX - rect.left) / rect.width);\n }\n\n /** Scrubber keyboard handler — arrows nudge ±5s, Home/End jump to ends, Space toggles play. */\n public OnScrubberKeydown(event: KeyboardEvent): void {\n const stepMs = 5000;\n switch (event.key) {\n case 'ArrowRight':\n event.preventDefault();\n this.seekByMs(stepMs);\n break;\n case 'ArrowLeft':\n event.preventDefault();\n this.seekByMs(-stepMs);\n break;\n case 'Home':\n event.preventDefault();\n this.SeekToFraction(0);\n break;\n case 'End':\n event.preventDefault();\n this.SeekToFraction(1);\n break;\n case ' ':\n case 'Enter':\n event.preventDefault();\n this.TogglePlay();\n break;\n default:\n break;\n }\n }\n\n /** Clicking a transcript turn → seek the audio to it, play, and emit {@link TurnSeek}. */\n public SeekToTurn(turn: EvidencePlaybackTurn): void {\n const startMs = this.turnStartMs(turn);\n const el = this.audioEl;\n if (el && this.DurationMs > 0) {\n el.currentTime = Math.max(0, startMs / 1000);\n this.CurrentMs = startMs;\n void el.play().catch(() => undefined);\n }\n this.TurnSeek.emit(turn);\n this.cdr.markForCheck();\n }\n\n /** True when the playhead falls within this turn's utterance window (drives the active highlight). */\n public IsTurnActive(turn: EvidencePlaybackTurn): boolean {\n if (!this.IsPlaying && this.CurrentMs === 0) {\n return false;\n }\n const start = this.turnStartMs(turn);\n const end = this.turnEndMs(turn);\n return this.CurrentMs >= start && this.CurrentMs < end;\n }\n\n /** Whether a turn is from the human user (vs. the agent) — drives the speaker color + label. */\n public IsUserTurn(turn: EvidencePlaybackTurn): boolean {\n return turn.Role === 'User';\n }\n\n /** The speaker label for a turn. */\n public SpeakerLabel(turn: EvidencePlaybackTurn): string {\n return this.IsUserTurn(turn) ? 'You' : this.AgentName;\n }\n\n /** The mm:ss timestamp shown on a turn (its start offset within the recording). */\n public TurnTimestamp(turn: EvidencePlaybackTurn): string {\n return this.formatMs(this.turnStartMs(turn));\n }\n\n /** mm:ss of the current playhead. */\n public get CurrentLabel(): string {\n return this.formatMs(this.CurrentMs);\n }\n\n /** mm:ss of the total duration. */\n public get DurationLabel(): string {\n return this.formatMs(this.DurationMs);\n }\n\n /** Stable `@for` track for the transcript. */\n public TrackByTurn(_index: number, turn: EvidencePlaybackTurn): string {\n return turn.ID;\n }\n\n // ----- audio element event handlers (bound in the template) -------------------------------\n\n /** `loadedmetadata` → capture the duration. */\n public OnLoadedMetadata(): void {\n const el = this.audioEl;\n this.DurationMs = el && Number.isFinite(el.duration) ? el.duration * 1000 : 0;\n this.cdr.markForCheck();\n }\n\n /** `timeupdate` → advance the playhead + re-evaluate the active turn. */\n public OnTimeUpdate(): void {\n const el = this.audioEl;\n if (el) {\n this.CurrentMs = el.currentTime * 1000;\n this.cdr.markForCheck();\n }\n }\n\n /** `play` / `pause` / `ended` → reflect the transport state. */\n public OnPlay(): void {\n this.IsPlaying = true;\n this.cdr.markForCheck();\n }\n public OnPause(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n public OnEnded(): void {\n this.IsPlaying = false;\n this.cdr.markForCheck();\n }\n\n // ----- helpers ----------------------------------------------------------------------------\n\n /** Nudges the playhead by `deltaMs`, clamped to the duration. */\n private seekByMs(deltaMs: number): void {\n if (this.DurationMs <= 0) {\n return;\n }\n this.SeekToFraction((this.CurrentMs + deltaMs) / this.DurationMs);\n }\n\n /**\n * A turn's start offset (ms) within the recording: prefer {@link EvidencePlaybackTurn.UtteranceStartMs};\n * fall back to (`__mj_CreatedAt` − {@link RecordingStartedAt}); 0 when neither is available.\n */\n private turnStartMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceStartMs != null) {\n return Math.max(0, turn.UtteranceStartMs);\n }\n if (this.RecordingStartedAt && turn.__mj_CreatedAt) {\n return Math.max(0, turn.__mj_CreatedAt.getTime() - this.RecordingStartedAt.getTime());\n }\n return 0;\n }\n\n /**\n * A turn's end offset (ms): prefer {@link EvidencePlaybackTurn.UtteranceEndMs}; otherwise the\n * next turn's start; otherwise the recording duration (or start + a small window when duration is unknown).\n */\n private turnEndMs(turn: EvidencePlaybackTurn): number {\n if (turn.UtteranceEndMs != null) {\n return Math.max(this.turnStartMs(turn), turn.UtteranceEndMs);\n }\n const idx = this._turns.indexOf(turn);\n const next = idx >= 0 ? this._turns[idx + 1] : undefined;\n if (next) {\n return this.turnStartMs(next);\n }\n return this.DurationMs > 0 ? this.DurationMs : this.turnStartMs(turn) + 4000;\n }\n\n /** Formats a ms offset as mm:ss (clamped at 0). */\n private formatMs(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000));\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n /** Builds a stable pseudo-random waveform (deterministic, no per-render churn). */\n private buildWaveform(): number[] {\n const bars: number[] = [];\n for (let i = 0; i < WAVEFORM_BARS; i++) {\n // A smooth, repeatable envelope — looks like audio without needing to decode any.\n const base = Math.sin(i * 0.5) * 0.5 + 0.5;\n const detail = Math.sin(i * 1.7) * 0.25 + 0.25;\n bars.push(Math.max(0.12, Math.min(1, base * 0.7 + detail * 0.5)));\n }\n return bars;\n }\n}\n","<div class=\"evp\">\n <!-- ---------- Audio transport ---------- -->\n <div class=\"evp-player\">\n @if (AudioUrl) {\n <audio\n #audio\n [src]=\"AudioUrl\"\n preload=\"metadata\"\n (loadedmetadata)=\"OnLoadedMetadata()\"\n (timeupdate)=\"OnTimeUpdate()\"\n (play)=\"OnPlay()\"\n (pause)=\"OnPause()\"\n (ended)=\"OnEnded()\"\n ></audio>\n\n <button\n type=\"button\"\n class=\"evp-play\"\n [attr.aria-label]=\"IsPlaying ? 'Pause recording' : 'Play recording'\"\n [title]=\"IsPlaying ? 'Pause' : 'Play'\"\n (click)=\"TogglePlay()\"\n >\n <i class=\"fa-solid\" [class.fa-pause]=\"IsPlaying\" [class.fa-play]=\"!IsPlaying\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"evp-transport\">\n <div\n #track\n class=\"evp-scrubber\"\n role=\"slider\"\n tabindex=\"0\"\n aria-label=\"Seek through the recording\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"100\"\n [attr.aria-valuenow]=\"(Progress * 100) | number: '1.0-0'\"\n [attr.aria-valuetext]=\"CurrentLabel + ' of ' + DurationLabel\"\n (click)=\"OnScrubberClick($event, track)\"\n (keydown)=\"OnScrubberKeydown($event)\"\n >\n <div class=\"evp-waveform\" aria-hidden=\"true\">\n @for (bar of WaveformBars; track $index) {\n <span\n class=\"evp-bar\"\n [class.evp-bar--played]=\"($index / WaveformBars.length) <= Progress\"\n [style.height.%]=\"bar * 100\"\n ></span>\n }\n </div>\n <div class=\"evp-progress\" [style.width.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n <div class=\"evp-thumb\" [style.left.%]=\"Progress * 100\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"evp-time\">\n <span class=\"evp-time__current\">{{ CurrentLabel }}</span>\n <span class=\"evp-time__sep\">/</span>\n <span class=\"evp-time__total\">{{ DurationLabel }}</span>\n </div>\n </div>\n } @else {\n <div class=\"evp-noaudio\">\n <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n <span>No audio recording is available for this session.</span>\n </div>\n }\n </div>\n\n <!-- ---------- Transcript ---------- -->\n <div class=\"evp-transcript\" role=\"list\" aria-label=\"Session transcript\">\n @if (Turns.length === 0) {\n <div class=\"evp-transcript-empty\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n <p>No transcript turns to show.</p>\n </div>\n } @else {\n @for (turn of Turns; track TrackByTurn($index, turn)) {\n <button\n type=\"button\"\n class=\"evp-turn\"\n role=\"listitem\"\n tabindex=\"0\"\n [class.evp-turn--user]=\"IsUserTurn(turn)\"\n [class.evp-turn--agent]=\"!IsUserTurn(turn)\"\n [class.evp-turn--active]=\"IsTurnActive(turn)\"\n [attr.aria-label]=\"SpeakerLabel(turn) + ' at ' + TurnTimestamp(turn) + ': ' + turn.Message\"\n [attr.aria-current]=\"IsTurnActive(turn) ? 'true' : null\"\n (click)=\"SeekToTurn(turn)\"\n >\n <div class=\"evp-turn__meta\">\n <span class=\"evp-turn__speaker\">{{ SpeakerLabel(turn) }}</span>\n <span class=\"evp-turn__time\">{{ TurnTimestamp(turn) }}</span>\n </div>\n <p class=\"evp-turn__text\">{{ turn.Message }}</p>\n </button>\n }\n }\n </div>\n</div>\n"]}
@@ -0,0 +1,103 @@
1
+ import { BehaviorSubject } from 'rxjs';
2
+ /** The media kinds the Media surface can render — one render path per value. */
3
+ export type MediaItemType = 'image' | 'video' | 'audio' | 'pdf' | 'web';
4
+ /**
5
+ * A fractional highlight rectangle drawn over a media item, in the item's OWN coordinate space
6
+ * (0..1 on both axes). `X`,`Y` is the top-left corner; `W`,`H` are the width/height. Fractional
7
+ * coordinates keep the highlight correct regardless of how the media is scaled to fit the pane.
8
+ */
9
+ export interface MediaHighlight {
10
+ /** Left edge as a fraction of width (0..1). */
11
+ X: number;
12
+ /** Top edge as a fraction of height (0..1). */
13
+ Y: number;
14
+ /** Width as a fraction of width (0..1). */
15
+ W: number;
16
+ /** Height as a fraction of height (0..1). */
17
+ H: number;
18
+ /** Optional short label rendered on the highlight. */
19
+ Label?: string;
20
+ }
21
+ /** One piece of media on the surface — a tab + a render pane keyed by {@link MediaItemType}. */
22
+ export interface MediaItem {
23
+ /** Stable id (returned to the agent by `Media_ShowMedia`, used by close/play/highlight). */
24
+ Id: string;
25
+ /** Which render path the pane uses. */
26
+ Type: MediaItemType;
27
+ /**
28
+ * Absolute URL of the media — for an EXTERNAL/public asset. Optional/empty when {@link FileID}
29
+ * is set (the surface resolves a secure streaming URL from the file id instead). At least one of
30
+ * `Url` / `FileID` is always present.
31
+ */
32
+ Url?: string;
33
+ /**
34
+ * Id of an "MJ: Files" record this item is backed by. When set, the item is streamed securely
35
+ * (permission-gated) from MJStorage rather than from a public {@link Url}; the surface resolves a
36
+ * short-lived streaming URL (via `CreateMediaAccessToken`) or hands the id straight to
37
+ * `mj-storage-media-player` for audio/video.
38
+ */
39
+ FileID?: string;
40
+ /** Short tab label. */
41
+ DisplayName: string;
42
+ /** Optional one-line caption beneath the media. */
43
+ Caption?: string;
44
+ /** Optional fractional highlight overlay. */
45
+ Highlight?: MediaHighlight;
46
+ /** Set transiently to request playback of a video/audio item (consumed by the surface). */
47
+ PlayRequestedAt?: number;
48
+ }
49
+ /**
50
+ * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media
51
+ * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new
52
+ * state through {@link Changed$} so the channel persists it and the surface re-renders. Mirrors
53
+ * `WhiteboardState` (no Angular, tolerant (de)serialization, single `Changed$` notifier).
54
+ */
55
+ export declare class MediaChannelState {
56
+ /** The media currently on the surface, in tab order (oldest first). */
57
+ Items: MediaItem[];
58
+ /** The id of the active (visible) item, or `null` when the surface is empty. */
59
+ ActiveItemId: string | null;
60
+ /**
61
+ * Fires on every mutation. Replays the current state on subscribe (BehaviorSubject), so a late
62
+ * surface binding immediately renders what's already on the board.
63
+ */
64
+ readonly Changed$: BehaviorSubject<MediaChannelState>;
65
+ /** Adds a media item, makes it active, and returns it. */
66
+ AddItem(item: Omit<MediaItem, 'Id'> & {
67
+ Id?: string;
68
+ }): MediaItem;
69
+ /** Removes the item with `id` (no-op if absent). When it was active, the last remaining item becomes active. */
70
+ RemoveItem(id: string): boolean;
71
+ /** Makes `id` the active item (no-op if absent). */
72
+ SetActive(id: string): boolean;
73
+ /**
74
+ * Activates `id` and stamps a play request on it (the surface consumes the stamp to start
75
+ * video/audio playback). No-op if the item is absent.
76
+ */
77
+ RequestPlay(id: string): boolean;
78
+ /** Sets (or, with `null`, clears) the fractional highlight on `id`. No-op if the item is absent. */
79
+ Highlight(id: string, highlight: MediaHighlight | null): boolean;
80
+ /** Removes all media and resets the active item. */
81
+ Clear(): void;
82
+ /** The active item, or `null` when the surface is empty / the active id is stale. */
83
+ get ActiveItem(): MediaItem | null;
84
+ /** Serializes the state of record to a JSON string (persisted via the channel's RequestSave). */
85
+ ToJSON(): string;
86
+ /**
87
+ * Rehydrates a prior session's saved state IN PLACE (preserving this instance + its `Changed$`
88
+ * subscribers). Tolerant by contract: malformed / incompatible JSON is ignored and the method
89
+ * returns `false` without throwing, leaving the current state untouched.
90
+ */
91
+ LoadFromJSON(json: string): boolean;
92
+ /**
93
+ * Validates one parsed item enough to render it safely (defensive against hand-edited / old
94
+ * payloads). An item must carry a SOURCE — a non-empty `Url` OR a non-empty `FileID` — since one
95
+ * of the two backs every render path.
96
+ */
97
+ private isValidItem;
98
+ /** Mints a unique-within-session item id. */
99
+ private newId;
100
+ /** Pushes the current state to subscribers. */
101
+ private notify;
102
+ }
103
+ //# sourceMappingURL=media-channel-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-channel-state.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAEvC,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,2CAA2C;IAC3C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gGAAgG;AAChG,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,uEAAuE;IAChE,KAAK,EAAE,SAAS,EAAE,CAAM;IAE/B,gFAAgF;IACzE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACH,SAAgB,QAAQ,qCAAgD;IAExE,0DAA0D;IACnD,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAQxE,gHAAgH;IACzG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAatC,oDAAoD;IAC7C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IASrC;;;OAGG;IACI,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAWvC,oGAAoG;IAC7F,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO;IAUvE,oDAAoD;IAC7C,KAAK,IAAI,IAAI;IASpB,qFAAqF;IACrF,IAAW,UAAU,IAAI,SAAS,GAAG,IAAI,CAExC;IAED,iGAAiG;IAC1F,MAAM,IAAI,MAAM;IAKvB;;;;OAIG;IACI,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAiB1C;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAiBnB,6CAA6C;IAC7C,OAAO,CAAC,KAAK;IAKb,+CAA+C;IAC/C,OAAO,CAAC,MAAM;CAGf"}