@memberjunction/ng-conversations 6.2.0-edge.3 → 6.2.0-edge.4

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 (275) hide show
  1. package/README.md +4 -7
  2. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  3. package/dist/lib/components/collection/collection-view.component.js +1 -0
  4. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  5. package/dist/lib/components/composer/ai-composer.component.d.ts +14 -2
  6. package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
  7. package/dist/lib/components/composer/ai-composer.component.js +35 -4
  8. package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
  9. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +588 -51
  10. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  11. package/dist/lib/components/conversation/conversation-chat-area.component.js +2654 -665
  12. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  13. package/dist/lib/components/conversation/forks-popover.component.d.ts +37 -0
  14. package/dist/lib/components/conversation/forks-popover.component.d.ts.map +1 -0
  15. package/dist/lib/components/conversation/forks-popover.component.js +164 -0
  16. package/dist/lib/components/conversation/forks-popover.component.js.map +1 -0
  17. package/dist/lib/components/export/export-modal.component.d.ts +7 -2
  18. package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
  19. package/dist/lib/components/export/export-modal.component.js +16 -3
  20. package/dist/lib/components/export/export-modal.component.js.map +1 -1
  21. package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
  22. package/dist/lib/components/message/conversation-message-rating.component.js +11 -9
  23. package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
  24. package/dist/lib/components/message/message-input.component.d.ts +96 -11
  25. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  26. package/dist/lib/components/message/message-input.component.js +244 -103
  27. package/dist/lib/components/message/message-input.component.js.map +1 -1
  28. package/dist/lib/components/message/message-item.component.d.ts +171 -4
  29. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  30. package/dist/lib/components/message/message-item.component.js +899 -298
  31. package/dist/lib/components/message/message-item.component.js.map +1 -1
  32. package/dist/lib/components/message/message-list.component.d.ts +82 -21
  33. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  34. package/dist/lib/components/message/message-list.component.js +299 -52
  35. package/dist/lib/components/message/message-list.component.js.map +1 -1
  36. package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts +34 -0
  37. package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts.map +1 -0
  38. package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js +75 -0
  39. package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js.map +1 -0
  40. package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts +80 -0
  41. package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts.map +1 -0
  42. package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js +202 -0
  43. package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js.map +1 -0
  44. package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts +45 -0
  45. package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts.map +1 -0
  46. package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js +123 -0
  47. package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js.map +1 -0
  48. package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts +90 -0
  49. package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts.map +1 -0
  50. package/dist/lib/components/realtime/capture/realtime-capture-channel.js +223 -0
  51. package/dist/lib/components/realtime/capture/realtime-capture-channel.js.map +1 -0
  52. package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts +98 -0
  53. package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts.map +1 -0
  54. package/dist/lib/components/realtime/capture/realtime-capture-model.js +161 -0
  55. package/dist/lib/components/realtime/capture/realtime-capture-model.js.map +1 -0
  56. package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts +48 -0
  57. package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts.map +1 -0
  58. package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js +205 -0
  59. package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js.map +1 -0
  60. package/dist/lib/components/realtime/channels/client-context-channel.d.ts +19 -2
  61. package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -1
  62. package/dist/lib/components/realtime/channels/client-context-channel.js +83 -9
  63. package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -1
  64. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +30 -9
  65. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -1
  66. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +78 -14
  67. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -1
  68. package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts +71 -0
  69. package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts.map +1 -0
  70. package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js +259 -0
  71. package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js.map +1 -0
  72. package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts +129 -0
  73. package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts.map +1 -0
  74. package/dist/lib/components/realtime/identity-verification/identity-verification-model.js +373 -0
  75. package/dist/lib/components/realtime/identity-verification/identity-verification-model.js.map +1 -0
  76. package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts +77 -0
  77. package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts.map +1 -0
  78. package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js +420 -0
  79. package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js.map +1 -0
  80. package/dist/lib/components/realtime/interactive-component/component-contract.d.ts +83 -0
  81. package/dist/lib/components/realtime/interactive-component/component-contract.d.ts.map +1 -0
  82. package/dist/lib/components/realtime/interactive-component/component-contract.js +292 -0
  83. package/dist/lib/components/realtime/interactive-component/component-contract.js.map +1 -0
  84. package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts +39 -0
  85. package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts.map +1 -0
  86. package/dist/lib/components/realtime/interactive-component/component-data-state.js +152 -0
  87. package/dist/lib/components/realtime/interactive-component/component-data-state.js.map +1 -0
  88. package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts +109 -0
  89. package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts.map +1 -0
  90. package/dist/lib/components/realtime/interactive-component/component-instance-engine.js +146 -0
  91. package/dist/lib/components/realtime/interactive-component/component-instance-engine.js.map +1 -0
  92. package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts +19 -0
  93. package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts.map +1 -0
  94. package/dist/lib/components/realtime/interactive-component/component-spec-content.js +39 -0
  95. package/dist/lib/components/realtime/interactive-component/component-spec-content.js.map +1 -0
  96. package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts +183 -0
  97. package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts.map +1 -0
  98. package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js +761 -0
  99. package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js.map +1 -0
  100. package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts +25 -0
  101. package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts.map +1 -0
  102. package/dist/lib/components/realtime/interactive-component/interactive-component-config.js +57 -0
  103. package/dist/lib/components/realtime/interactive-component/interactive-component-config.js.map +1 -0
  104. package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts +40 -0
  105. package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts.map +1 -0
  106. package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js +97 -0
  107. package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js.map +1 -0
  108. package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts +144 -0
  109. package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts.map +1 -0
  110. package/dist/lib/components/realtime/interactive-component/interactive-component-types.js +57 -0
  111. package/dist/lib/components/realtime/interactive-component/interactive-component-types.js.map +1 -0
  112. package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts +36 -0
  113. package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts.map +1 -0
  114. package/dist/lib/components/realtime/interactive-component/react-component-handle.js +55 -0
  115. package/dist/lib/components/realtime/interactive-component/react-component-handle.js.map +1 -0
  116. package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts +55 -0
  117. package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts.map +1 -0
  118. package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js +210 -0
  119. package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js.map +1 -0
  120. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts +32 -0
  121. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts.map +1 -0
  122. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +90 -0
  123. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -0
  124. package/dist/lib/components/realtime/media/media-channel.d.ts +7 -0
  125. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
  126. package/dist/lib/components/realtime/media/media-channel.js +28 -1
  127. package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
  128. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +8 -1
  129. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  130. package/dist/lib/components/realtime/realtime-agent-banner.component.js +89 -71
  131. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  132. package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts +21 -0
  133. package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts.map +1 -0
  134. package/dist/lib/components/realtime/realtime-agent-orb.component.js +49 -0
  135. package/dist/lib/components/realtime/realtime-agent-orb.component.js.map +1 -0
  136. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +44 -11
  137. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  138. package/dist/lib/components/realtime/realtime-agent-picker.component.js +162 -50
  139. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  140. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +8 -2
  141. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
  142. package/dist/lib/components/realtime/realtime-audio-visuals.js +14 -18
  143. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
  144. package/dist/lib/components/realtime/realtime-composer.component.d.ts +42 -2
  145. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  146. package/dist/lib/components/realtime/realtime-composer.component.js +137 -102
  147. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  148. package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts +71 -0
  149. package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts.map +1 -0
  150. package/dist/lib/components/realtime/realtime-perception-chip.component.js +221 -0
  151. package/dist/lib/components/realtime/realtime-perception-chip.component.js.map +1 -0
  152. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +264 -22
  153. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  154. package/dist/lib/components/realtime/realtime-session-overlay.component.js +851 -135
  155. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  156. package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts +14 -0
  157. package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts.map +1 -0
  158. package/dist/lib/components/realtime/realtime-surface-placement-prefs.js +14 -0
  159. package/dist/lib/components/realtime/realtime-surface-placement-prefs.js.map +1 -0
  160. package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts +74 -0
  161. package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts.map +1 -0
  162. package/dist/lib/components/realtime/realtime-surface-stage.model.js +171 -0
  163. package/dist/lib/components/realtime/realtime-surface-stage.model.js.map +1 -0
  164. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +71 -13
  165. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
  166. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +232 -73
  167. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
  168. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +5 -0
  169. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
  170. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +8 -0
  171. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
  172. package/dist/lib/components/realtime/realtime-ui-config.d.ts +12 -2
  173. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
  174. package/dist/lib/components/realtime/realtime-ui-config.js +5 -1
  175. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
  176. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +10 -1
  177. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  178. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +42 -2
  179. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  180. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +20 -3
  181. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
  182. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +44 -10
  183. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
  184. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +28 -44
  185. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
  186. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +76 -162
  187. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
  188. package/dist/lib/components/search/search-panel.component.d.ts +2 -0
  189. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  190. package/dist/lib/components/search/search-panel.component.js +25 -12
  191. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  192. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +22 -31
  193. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  194. package/dist/lib/components/workspace/conversation-workspace.component.js +50 -63
  195. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  196. package/dist/lib/conversations.module.d.ts +30 -29
  197. package/dist/lib/conversations.module.d.ts.map +1 -1
  198. package/dist/lib/conversations.module.js +27 -5
  199. package/dist/lib/conversations.module.js.map +1 -1
  200. package/dist/lib/events/chat-events.d.ts +25 -0
  201. package/dist/lib/events/chat-events.d.ts.map +1 -1
  202. package/dist/lib/events/chat-events.js +36 -0
  203. package/dist/lib/events/chat-events.js.map +1 -1
  204. package/dist/lib/models/agent-turn.model.d.ts +5 -0
  205. package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
  206. package/dist/lib/models/agent-turn.model.js.map +1 -1
  207. package/dist/lib/services/agent-state.service.d.ts +34 -8
  208. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  209. package/dist/lib/services/agent-state.service.js +82 -29
  210. package/dist/lib/services/agent-state.service.js.map +1 -1
  211. package/dist/lib/services/artifact-state.service.d.ts +0 -9
  212. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  213. package/dist/lib/services/artifact-state.service.js +1 -32
  214. package/dist/lib/services/artifact-state.service.js.map +1 -1
  215. package/dist/lib/services/browser-realtime-media-host.d.ts +12 -0
  216. package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
  217. package/dist/lib/services/browser-realtime-media-host.js +19 -0
  218. package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
  219. package/dist/lib/services/browser-recording-unload-guard.d.ts +30 -0
  220. package/dist/lib/services/browser-recording-unload-guard.d.ts.map +1 -0
  221. package/dist/lib/services/browser-recording-unload-guard.js +43 -0
  222. package/dist/lib/services/browser-recording-unload-guard.js.map +1 -0
  223. package/dist/lib/services/conversation-agent.service.d.ts +35 -11
  224. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  225. package/dist/lib/services/conversation-agent.service.js +65 -20
  226. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  227. package/dist/lib/services/conversation-detail-window.store.d.ts +22 -4
  228. package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
  229. package/dist/lib/services/conversation-detail-window.store.js +50 -9
  230. package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
  231. package/dist/lib/services/conversation-naming.d.ts +72 -22
  232. package/dist/lib/services/conversation-naming.d.ts.map +1 -1
  233. package/dist/lib/services/conversation-naming.js +84 -10
  234. package/dist/lib/services/conversation-naming.js.map +1 -1
  235. package/dist/lib/services/data-cache.service.d.ts +13 -5
  236. package/dist/lib/services/data-cache.service.d.ts.map +1 -1
  237. package/dist/lib/services/data-cache.service.js +39 -19
  238. package/dist/lib/services/data-cache.service.js.map +1 -1
  239. package/dist/lib/services/export.service.d.ts +26 -9
  240. package/dist/lib/services/export.service.d.ts.map +1 -1
  241. package/dist/lib/services/export.service.js +61 -18
  242. package/dist/lib/services/export.service.js.map +1 -1
  243. package/dist/lib/services/realtime-audio-recorder.d.ts +33 -5
  244. package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
  245. package/dist/lib/services/realtime-audio-recorder.js +71 -11
  246. package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
  247. package/dist/lib/services/realtime-pairing.d.ts +10 -5
  248. package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
  249. package/dist/lib/services/realtime-pairing.js +15 -6
  250. package/dist/lib/services/realtime-pairing.js.map +1 -1
  251. package/dist/lib/services/realtime-session.service.d.ts +3 -2
  252. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  253. package/dist/lib/services/realtime-session.service.js +7 -2
  254. package/dist/lib/services/realtime-session.service.js.map +1 -1
  255. package/dist/lib/services/search.service.d.ts +7 -2
  256. package/dist/lib/services/search.service.d.ts.map +1 -1
  257. package/dist/lib/services/search.service.js +13 -4
  258. package/dist/lib/services/search.service.js.map +1 -1
  259. package/dist/lib/utils/conversation-branching.d.ts +4 -0
  260. package/dist/lib/utils/conversation-branching.d.ts.map +1 -0
  261. package/dist/lib/utils/conversation-branching.js +5 -0
  262. package/dist/lib/utils/conversation-branching.js.map +1 -0
  263. package/dist/lib/utils/conversation-forks.d.ts +315 -0
  264. package/dist/lib/utils/conversation-forks.d.ts.map +1 -0
  265. package/dist/lib/utils/conversation-forks.js +456 -0
  266. package/dist/lib/utils/conversation-forks.js.map +1 -0
  267. package/dist/public-api.d.ts +23 -1
  268. package/dist/public-api.d.ts.map +1 -1
  269. package/dist/public-api.js +30 -1
  270. package/dist/public-api.js.map +1 -1
  271. package/package.json +37 -33
  272. package/dist/lib/components/thread/thread-panel.component.d.ts +0 -126
  273. package/dist/lib/components/thread/thread-panel.component.d.ts.map +0 -1
  274. package/dist/lib/components/thread/thread-panel.component.js +0 -440
  275. package/dist/lib/components/thread/thread-panel.component.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"export.service.js","sourceRoot":"","sources":["../../../src/lib/services/export.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';\nimport { RunView, UserInfo, Metadata, IMetadataProvider } from '@memberjunction/core';\n\nexport type ExportFormat = 'json' | 'markdown' | 'html' | 'text';\n\n/**\n * Branding applied to exported files, across EVERY format. HTML gets the full\n * treatment (theme colors, an inlined logo, the title, and a trademark footer);\n * JSON/markdown/text carry the title and trademark (markdown also references the\n * logo). Hosts supply this via `mj-conversation-chat-area`'s `exportBranding`\n * input. Color theming (`brandTokens`) is HTML-only — it has no meaning in the\n * data/plain-text formats.\n */\nexport interface ExportBranding {\n /**\n * CSS custom-property snapshot emitted as a `:root{}` block in the exported\n * document, so the export's stylesheet (which reads `var(--mj-…, fallback)`)\n * renders in the app's theme. HTML export only. Keys must be valid\n * custom-property names (`--like-this`). Entries are DROPPED when the key is\n * invalid or the value carries declaration-escape / network-call characters\n * (`<>{};@\\`, `url(`, `expression(`) — plain colors, `var()`, and\n * `color-mix()` pass. When omitted and `ExportOptions.includeTheme` is true,\n * the current theme is auto-snapshotted from `document.documentElement` (see\n * {@link DEFAULT_EXPORT_THEME_TOKENS}).\n */\n brandTokens?: Record<string, string>;\n /**\n * Logo URL. In HTML it is fetched and inlined as a data URI (so the file stays\n * self-contained) and rendered above the title — requires\n * `ExportOptions.includeCSS`, since it needs its stylesheet rule. In markdown it is\n * referenced by URL. JSON carries it as a string; plain text omits it.\n *\n * If the fetch fails, or the response isn't `content-type: image/*`, the raw URL is\n * emitted instead — so the exported file will reach out to that URL when opened.\n * There is no size or timeout guard on the fetch: a very large logo inflates the\n * export.\n */\n logoUrl?: string;\n /** Overrides the exported document's title (defaults to the conversation name). */\n title?: string;\n /**\n * A short trademark / attribution line (e.g. `© 2026 Acme Association ·\n * Powered by Betty`) rendered at the FOOT of the exported document: a styled HTML\n * footer, a markdown/text trailer, and a field in the JSON `branding` block.\n *\n * In HTML it needs its stylesheet rule, so — like the logo — it is emitted only when\n * `ExportOptions.includeCSS` is true. The markdown/text/JSON trailers have no such\n * dependency and always emit.\n *\n * NOT escaped for markdown: a value containing `_`, `]`, `)`, or a newline can break\n * the surrounding markdown. Supply plain text (HTML and JSON are escaped).\n */\n trademark?: string;\n}\n\nexport interface ExportOptions {\n includeMessages?: boolean;\n includeMetadata?: boolean;\n prettyPrint?: boolean;\n includeCSS?: boolean;\n /**\n * Emit the current app theme into the HTML export. When true and\n * `branding.brandTokens` is absent, the {@link DEFAULT_EXPORT_THEME_TOKENS}\n * are auto-snapshotted from the live document at export time. Default false —\n * an unthemed export renders exactly as before (the stylesheet's `var()`\n * fallbacks carry the legacy palette).\n *\n * Gates only the TOKEN auto-snapshot: a supplied `branding`'s `logoUrl`,\n * `title`, and `trademark` apply whenever `branding` is present — in HTML AND\n * in the JSON/markdown/text formats. (The export modal passes `branding`\n * through only while its \"Include branding\" checkbox is on, so in the UI flow\n * everything travels together.)\n */\n includeTheme?: boolean;\n /**\n * Which theme mode the snapshot captures — NOT whichever mode the app happens to be\n * in when the user clicks Export. Defaults to `'light'`: an export taken during a\n * dark session would otherwise bake dark text onto the page's white background and\n * come out unreadable. `'dark'` pairs the dark palette with the dark\n * `--mj-bg-page` the body rule applies, so the file is coherent either way.\n * HTML only — the data formats carry no colors.\n */\n themeMode?: 'light' | 'dark';\n /** Branding (tokens / logo / title) applied to the export — see {@link ExportBranding}. */\n branding?: ExportBranding;\n}\n\n/**\n * Tokens captured by the theme auto-snapshot — exactly the custom properties the\n * HTML export's stylesheet consumes. Hosts wanting more control supply their own\n * map via {@link ExportBranding.brandTokens} instead.\n */\nexport const DEFAULT_EXPORT_THEME_TOKENS: readonly string[] = [\n '--mj-bg-page',\n '--mj-text-primary',\n '--mj-text-secondary',\n '--mj-text-disabled',\n '--mj-border-default',\n '--mj-brand-primary',\n '--mj-brand-accent-subtle',\n '--mj-bg-surface-card',\n '--mj-font-family',\n '--mj-radius-md',\n];\n\n/** Internal: options with defaults applied (branding stays optional). */\ntype ResolvedExportOptions = Required<Omit<ExportOptions, 'branding'>> & Pick<ExportOptions, 'branding'>;\n\n/** Internal: the theme pieces resolved once per HTML export. */\ninterface ResolvedExportTheme {\n /** `<style>:root{…}</style>` block (empty string when no tokens resolved). */\n rootBlock: string;\n /** Inlined logo (data URI, or the raw URL on fetch failure); null = no logo. */\n logoDataUri: string | null;\n /** Document-title override; null = use the conversation name. */\n title: string | null;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ExportService {\n private _provider: IMetadataProvider | null = null;\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n async ExportConversation(\n conversationId: string,\n format: ExportFormat,\n currentUser: UserInfo,\n options: ExportOptions = {}\n ): Promise<void> {\n const conversation = await this.loadConversationData(conversationId, currentUser);\n const { content, filename, mimeType } = await this.BuildExportContent(conversation, format, options);\n this.downloadFile(content, filename, mimeType);\n }\n\n /** @deprecated Use {@link ExportConversation}. */\n async exportConversation(\n conversationId: string,\n format: ExportFormat,\n currentUser: UserInfo,\n options: ExportOptions = {}\n ): Promise<void> {\n return this.ExportConversation(conversationId, format, currentUser, options);\n }\n\n /**\n * Build the export document for already-loaded conversation data — the\n * download-free seam of {@link exportConversation}. Async because HTML-format\n * branding may inline a logo. Exposed publicly so hosts (and tests) can build\n * export content without triggering a browser download.\n */\n public async BuildExportContent(\n data: { conversation: MJConversationEntity; details: MJConversationDetailEntity[] },\n format: ExportFormat,\n options: ExportOptions = {}\n ): Promise<{ content: string; filename: string; mimeType: string }> {\n const exportOptions: ResolvedExportOptions = {\n includeMessages: options.includeMessages ?? true,\n includeMetadata: options.includeMetadata ?? true,\n prettyPrint: options.prettyPrint ?? true,\n includeCSS: options.includeCSS ?? true,\n includeTheme: options.includeTheme ?? false,\n themeMode: options.themeMode ?? 'light',\n branding: options.branding\n };\n const baseName = `conversation-${data.conversation.Name}-${this.getTimestamp()}`;\n\n switch (format) {\n case 'json':\n return { content: this.exportAsJSON(data, exportOptions), filename: `${baseName}.json`, mimeType: 'application/json' };\n case 'markdown':\n return { content: this.exportAsMarkdown(data, exportOptions), filename: `${baseName}.md`, mimeType: 'text/markdown' };\n case 'html':\n return {\n content: this.exportAsHTML(data, exportOptions, await this.resolveTheme(exportOptions)),\n filename: `${baseName}.html`,\n mimeType: 'text/html'\n };\n case 'text':\n return { content: this.exportAsText(data, exportOptions), filename: `${baseName}.txt`, mimeType: 'text/plain' };\n default:\n throw new Error(`Unsupported export format: ${format}`);\n }\n }\n\n /**\n * Snapshot the given theme tokens from the live document. Empty/unset tokens are\n * skipped.\n *\n * `mode` captures a SPECIFIC palette rather than whatever the app is rendering right\n * now. Without it, exporting from a dark session bakes dark text onto the export's\n * white page and the file is unreadable. When the requested mode isn't the active one,\n * `data-theme` is flipped on `documentElement`, read, and restored — all synchronous\n * within one task, so the browser never paints the intermediate state. Omit `mode`\n * to read the live values as-is.\n */\n public SnapshotBrandTokens(\n tokens: readonly string[] = DEFAULT_EXPORT_THEME_TOKENS,\n mode?: 'light' | 'dark'\n ): Record<string, string> {\n const root = document.documentElement;\n const previous = root.getAttribute('data-theme');\n // The app's two real states are `data-theme=\"dark\"` and NO attribute at all —\n // `MJExplorerAppComponent` removes it for light rather than writing \"light\"\n // (explorer-app.component.ts). Reproduce those exactly rather than inventing a\n // third state, so the transient DOM is one the stylesheet has actually seen.\n const wantDark = mode === 'dark';\n const isDark = previous === 'dark';\n const mustSwap = mode != null && wantDark !== isDark;\n\n if (mustSwap) {\n if (wantDark) {\n root.setAttribute('data-theme', 'dark');\n } else {\n root.removeAttribute('data-theme');\n }\n }\n try {\n const style = getComputedStyle(root);\n const out: Record<string, string> = {};\n for (const token of tokens) {\n const value = style.getPropertyValue(token).trim();\n if (value) {\n out[token] = value;\n }\n }\n return out;\n } finally {\n // Restore even if reading throws — never strand the app in the other theme.\n if (mustSwap) {\n if (previous === null) {\n root.removeAttribute('data-theme');\n } else {\n root.setAttribute('data-theme', previous);\n }\n }\n }\n }\n\n /** Resolve the HTML export's theme pieces once: tokens (explicit > snapshot), logo, title. */\n private async resolveTheme(options: ResolvedExportOptions): Promise<ResolvedExportTheme> {\n const tokens = this.resolveBrandTokens(options);\n const rootBlock = tokens ? this.buildRootBlock(tokens) : '';\n const logoUrl = options.branding?.logoUrl?.trim();\n return {\n rootBlock,\n // The logo needs its `.brand-logo` rule (as the trademark footer needs its own),\n // so both are gated on includeCSS — a CSS-less export must never carry a\n // full-size unstyled logo. The modal enforces this too, but the public\n // BuildExportContent seam has to hold the line on its own.\n logoDataUri: options.includeCSS && logoUrl ? await this.inlineLogo(logoUrl) : null,\n // The title is deliberately NOT includeCSS-gated: it lands in <title>/<h1>, needs\n // no stylesheet, and is the one branding field every format carries unconditionally.\n title: options.branding?.title?.trim() || null\n };\n }\n\n /** Explicit host tokens win; `includeTheme` falls back to a live snapshot; else no theme. */\n private resolveBrandTokens(options: ResolvedExportOptions): Record<string, string> | null {\n const explicit = options.branding?.brandTokens;\n if (explicit && Object.keys(explicit).length > 0) {\n return explicit;\n }\n return options.includeTheme ? this.SnapshotBrandTokens(DEFAULT_EXPORT_THEME_TOKENS, options.themeMode) : null;\n }\n\n /** Document title used by every format: branding override → conversation name → generic. */\n private resolveTitle(\n data: { conversation: MJConversationEntity; details: MJConversationDetailEntity[] },\n options: ResolvedExportOptions\n ): string {\n return options.branding?.title?.trim() || data.conversation.Name || 'Conversation';\n }\n\n /**\n * The JSON `branding` block — the data-format analogue of the HTML header +\n * trademark footer. Carries the title, trademark, and logo URL the host set;\n * null when no branding (or none of those fields) is present. Theme color\n * tokens are HTML-only and never included here.\n */\n private buildBrandingBlock(options: ResolvedExportOptions): Record<string, string> | null {\n const b = options.branding;\n if (!b) {\n return null;\n }\n const block: Record<string, string> = {};\n const title = b.title?.trim();\n const trademark = b.trademark?.trim();\n const logoUrl = b.logoUrl?.trim();\n if (title) block.title = title;\n if (trademark) block.trademark = trademark;\n if (logoUrl) block.logoUrl = logoUrl;\n return Object.keys(block).length > 0 ? block : null;\n }\n\n /**\n * CSS functions permitted inside an exported token value. This is an ALLOWLIST\n * (not a blocklist) so no network-capable function can slip through: blocking\n * only `url(`/`expression(` let `image-set()`, `-webkit-image-set()`,\n * `cross-fade()`, `image()`, and `src()` fetch a beacon URL without ever\n * writing the literal `url(`. Anything not on this list drops the whole entry.\n * The set is the safe value/color-math functions the export could legitimately\n * carry; extend it only with functions that CANNOT load an external resource.\n */\n private static readonly SAFE_CSS_FUNCTIONS = new Set([\n 'var', 'calc', 'min', 'max', 'clamp',\n 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch',\n 'color', 'color-mix',\n ]);\n\n /**\n * Emit the sanitized `:root{}` style block. Keys must be custom-property names.\n * A value is REJECTED (the entry is dropped, not stripped) when it carries any\n * declaration/tag-escape or network-reach vector: `<>{}` (tag/rule breakout),\n * `;` (declaration breakout — e.g. `; background: url(beacon)`), `\\` (CSS\n * escapes), `@` (at-rules), OR any CSS function not in\n * {@link SAFE_CSS_FUNCTIONS} (which excludes every resource-loading function —\n * url/image-set/cross-fade/image/src/expression/…). Plain colors, `var()`, and\n * `color-mix()` pass untouched.\n */\n private buildRootBlock(tokens: Record<string, string>): string {\n const rules: string[] = [];\n for (const [key, rawValue] of Object.entries(tokens)) {\n if (!/^--[A-Za-z0-9_-]+$/.test(key)) {\n continue;\n }\n const value = String(rawValue).trim();\n if (!value || /[<>{};@\\\\]/.test(value) || this.hasUnsafeCssFunction(value)) {\n continue;\n }\n rules.push(`${key}: ${value};`);\n }\n return rules.length > 0 ? `\n <style>:root { ${rules.join(' ')} }</style>` : '';\n }\n\n /** True if the value calls any CSS function outside {@link SAFE_CSS_FUNCTIONS}. */\n private hasUnsafeCssFunction(value: string): boolean {\n // Every `name(` occurrence — the identifier immediately before a '('.\n const calls = value.match(/([a-zA-Z][a-zA-Z-]*)\\s*\\(/g) ?? [];\n return calls.some((c) => {\n const name = c.replace(/\\s*\\($/, '').toLowerCase();\n return !ExportService.SAFE_CSS_FUNCTIONS.has(name);\n });\n }\n\n /** Fetch → data URI so the export stays self-contained; raw URL on any failure\n * (or when the response isn't actually an image — don't inline HTML/other as\n * a bogus `data:` image). */\n private async inlineLogo(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n return url;\n }\n const contentType = response.headers.get('content-type') ?? '';\n if (!/^image\\//i.test(contentType)) {\n return url;\n }\n const blob = await response.blob();\n return await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(blob);\n });\n } catch {\n return url;\n }\n }\n\n private async loadConversationData(\n conversationId: string,\n currentUser: UserInfo\n ): Promise<{ conversation: MJConversationEntity; details: MJConversationDetailEntity[] }> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n\n // Load conversation and details in parallel\n const [conversationResult, detailsResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Conversations',\n ExtraFilter: `ID='${conversationId}'`,\n ResultType: 'entity_object'\n },\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: `ConversationID='${conversationId}'`,\n OrderBy: 'Sequence ASC',\n ResultType: 'entity_object'\n }\n ], currentUser);\n\n if (!conversationResult.Success || !conversationResult.Results?.length) {\n throw new Error('Conversation not found');\n }\n\n return {\n conversation: conversationResult.Results[0] as MJConversationEntity,\n details: (detailsResult.Results || []) as MJConversationDetailEntity[]\n };\n }\n\n private exportAsJSON(\n data: {\n conversation: MJConversationEntity;\n details: MJConversationDetailEntity[];\n },\n options: ResolvedExportOptions\n ): string {\n const exportData: Record<string, unknown> = {};\n\n // Add metadata if requested\n if (options.includeMetadata) {\n exportData.conversation = {\n id: data.conversation.ID,\n name: data.conversation.Name,\n description: data.conversation.Description,\n createdAt: data.conversation.__mj_CreatedAt,\n updatedAt: data.conversation.__mj_UpdatedAt\n };\n } else {\n exportData.conversation = {\n name: data.conversation.Name\n };\n }\n\n // Branding block (title / trademark / logo) when the host supplied branding —\n // the data-format analogue of the HTML header + trademark footer.\n const branding = this.buildBrandingBlock(options);\n if (branding) {\n exportData.branding = branding;\n }\n\n // Add messages if requested\n if (options.includeMessages) {\n exportData.messages = data.details.map((detail, index) => {\n const message: Record<string, unknown> = {\n role: detail.Role,\n message: detail.Message\n };\n\n if (options.includeMetadata) {\n message.id = detail.ID;\n message.sequence = index + 1;\n message.timestamp = detail.__mj_CreatedAt;\n }\n\n return message;\n });\n }\n\n // Use pretty print option\n return JSON.stringify(exportData, null, options.prettyPrint ? 2 : 0);\n }\n\n private exportAsMarkdown(\n data: {\n conversation: MJConversationEntity;\n details: MJConversationDetailEntity[];\n },\n options: ResolvedExportOptions\n ): string {\n const logoUrl = options.branding?.logoUrl?.trim();\n // Unbranded markdown must stay byte-identical to before (`# <Name>`), so DON'T\n // route through resolveTitle's 'Conversation' fallback here — only a supplied\n // branding title overrides the raw conversation name.\n const title = options.branding?.title?.trim() || data.conversation.Name;\n let md = '';\n if (logoUrl) {\n md += `![${title}](${logoUrl})\\n\\n`;\n }\n md += `# ${title}\\n\\n`;\n\n if (data.conversation.Description) {\n md += `${data.conversation.Description}\\n\\n`;\n }\n\n if (options.includeMetadata) {\n md += `**Created:** ${this.formatDate(data.conversation.__mj_CreatedAt)}\\n\\n`;\n }\n\n md += `---\\n\\n`;\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n md += `## ${this.capitalizeRole(detail.Role || 'Unknown')}\\n\\n`;\n md += `${detail.Message}\\n\\n`;\n\n if (options.includeMetadata) {\n md += `*${this.formatDate(detail.__mj_CreatedAt)}*\\n\\n`;\n }\n\n md += `---\\n\\n`;\n }\n }\n\n const trademark = options.branding?.trademark?.trim();\n if (trademark) {\n md += `_${trademark}_\\n`;\n }\n\n return md;\n }\n\n private exportAsHTML(\n data: {\n conversation: MJConversationEntity;\n details: MJConversationDetailEntity[];\n },\n options: ResolvedExportOptions,\n theme: ResolvedExportTheme\n ): string {\n const trademark = options.branding?.trademark?.trim();\n // Every color reads a theme token with the legacy hex as its fallback, so an\n // unthemed export (no :root block emitted) renders exactly as it always has,\n // while a themed one follows the app's palette.\n const styles = options.includeCSS ? `\n <style>\n body { font-family: var(--mj-font-family, system-ui, -apple-system, sans-serif); background: var(--mj-bg-page, #fff); color: var(--mj-text-primary, #333); max-width: 800px; margin: 40px auto; padding: 0 20px; line-height: 1.6; }\n h1 { color: var(--mj-text-primary, #333); border-bottom: 2px solid var(--mj-brand-primary, #007bff); padding-bottom: 10px; }\n .meta { color: var(--mj-text-secondary, #666); font-size: 14px; margin-bottom: 30px; }\n .message { margin-bottom: 30px; padding: 20px; border-radius: var(--mj-radius-md, 8px); background: var(--mj-bg-surface-card, #f5f5f5); }\n .message.user { background: var(--mj-brand-accent-subtle, #e3f2fd); }\n .message.assistant { background: var(--mj-bg-surface-card, #f5f5f5); }\n .role { font-weight: 600; color: var(--mj-brand-primary, #007bff); margin-bottom: 10px; }\n .content { white-space: pre-wrap; }\n .timestamp { color: var(--mj-text-disabled, #999); font-size: 12px; margin-top: 10px; }${theme.logoDataUri ? `\n .brand-logo { display: block; max-height: 48px; margin-bottom: 12px; }` : ''}${trademark ? `\n .brand-trademark { margin-top: 40px; padding-top: 16px; border-top: 1px solid var(--mj-border-default, #ddd); color: var(--mj-text-secondary, #666); font-size: 12px; text-align: center; }` : ''}\n </style>${theme.rootBlock}` : '';\n\n const title = theme.title ?? (data.conversation.Name || 'Conversation');\n let html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${this.escapeHtml(title)}</title>${styles}\n</head>\n<body>\n ${theme.logoDataUri ? `<img class=\"brand-logo\" src=\"${this.escapeAttr(theme.logoDataUri)}\" alt=\"\" />\n ` : ''}<h1>${this.escapeHtml(title)}</h1>`;\n\n if (options.includeMetadata) {\n html += `\n <div class=\"meta\">\n ${data.conversation.Description ? `<p>${this.escapeHtml(data.conversation.Description)}</p>` : ''}\n <p>Created: ${this.formatDate(data.conversation.__mj_CreatedAt)}</p>\n </div>`;\n }\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n const roleClass = detail.Role?.toLowerCase() || 'unknown';\n html += `\n <div class=\"message ${roleClass}\">\n <div class=\"role\">${this.capitalizeRole(detail.Role || 'Unknown')}</div>\n <div class=\"content\">${this.escapeHtml(detail.Message || '')}</div>`;\n\n if (options.includeMetadata) {\n html += `\n <div class=\"timestamp\">${this.formatDate(detail.__mj_CreatedAt)}</div>`;\n }\n\n html += `\n </div>`;\n }\n }\n\n if (options.includeCSS && trademark) {\n html += `\n <footer class=\"brand-trademark\">${this.escapeHtml(trademark)}</footer>`;\n }\n\n html += `\n</body>\n</html>`;\n\n return html;\n }\n\n private exportAsText(\n data: {\n conversation: MJConversationEntity;\n details: MJConversationDetailEntity[];\n },\n options: ResolvedExportOptions\n ): string {\n const name = this.resolveTitle(data, options) || 'Conversation';\n let text = `${name}\\n`;\n text += '='.repeat(name.length) + '\\n\\n';\n\n if (data.conversation.Description) {\n text += `${data.conversation.Description}\\n\\n`;\n }\n\n if (options.includeMetadata) {\n text += `Created: ${this.formatDate(data.conversation.__mj_CreatedAt)}\\n\\n`;\n }\n\n text += '-'.repeat(80) + '\\n\\n';\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n text += `[${this.capitalizeRole(detail.Role || 'Unknown')}]\\n`;\n text += `${detail.Message}\\n`;\n\n if (options.includeMetadata) {\n text += `(${this.formatDate(detail.__mj_CreatedAt)})\\n`;\n }\n\n text += '\\n' + '-'.repeat(80) + '\\n\\n';\n }\n }\n\n const trademark = options.branding?.trademark?.trim();\n if (trademark) {\n text += `${trademark}\\n`;\n }\n\n return text;\n }\n\n private downloadFile(content: string, filename: string, mimeType: string): void {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n private formatDate(date: Date | string): string {\n const d = new Date(date);\n return d.toLocaleString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n });\n }\n\n private capitalizeRole(role: string): string {\n return role.charAt(0).toUpperCase() + role.slice(1).toLowerCase();\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /** escapeHtml + quote escaping — for values interpolated into HTML attributes. */\n private escapeAttr(text: string): string {\n return this.escapeHtml(text).replace(/\"/g, '&quot;');\n }\n\n private getTimestamp(): string {\n const now = new Date();\n return now.toISOString().replace(/[:.]/g, '-').slice(0, -5);\n }\n}\n"]}
1
+ {"version":3,"file":"export.service.js","sourceRoot":"","sources":["../../../src/lib/services/export.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { ConversationEngine, ConversationScope, MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';\nimport { RunView, UserInfo, Metadata, IMetadataProvider } from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\n\nexport type ExportFormat = 'json' | 'markdown' | 'html' | 'text';\n\n/**\n * Branding applied to exported files, across EVERY format. HTML gets the full\n * treatment (theme colors, an inlined logo, the title, and a trademark footer);\n * JSON/markdown/text carry the title and trademark (markdown also references the\n * logo). Hosts supply this via `mj-conversation-chat-area`'s `exportBranding`\n * input. Color theming (`brandTokens`) is HTML-only — it has no meaning in the\n * data/plain-text formats.\n */\nexport interface ExportBranding {\n /**\n * CSS custom-property snapshot emitted as a `:root{}` block in the exported\n * document, so the export's stylesheet (which reads `var(--mj-…, fallback)`)\n * renders in the app's theme. HTML export only. Keys must be valid\n * custom-property names (`--like-this`). Entries are DROPPED when the key is\n * invalid or the value carries declaration-escape / network-call characters\n * (`<>{};@\\`, `url(`, `expression(`) — plain colors, `var()`, and\n * `color-mix()` pass. When omitted and `ExportOptions.includeTheme` is true,\n * the current theme is auto-snapshotted from `document.documentElement` (see\n * {@link DEFAULT_EXPORT_THEME_TOKENS}).\n */\n brandTokens?: Record<string, string>;\n /**\n * Logo URL. In HTML it is fetched and inlined as a data URI (so the file stays\n * self-contained) and rendered above the title — requires\n * `ExportOptions.includeCSS`, since it needs its stylesheet rule. In markdown it is\n * referenced by URL. JSON carries it as a string; plain text omits it.\n *\n * If the fetch fails, or the response isn't `content-type: image/*`, the raw URL is\n * emitted instead — so the exported file will reach out to that URL when opened.\n * There is no size or timeout guard on the fetch: a very large logo inflates the\n * export.\n */\n logoUrl?: string;\n /** Overrides the exported document's title (defaults to the conversation name). */\n title?: string;\n /**\n * A short trademark / attribution line (e.g. `© 2026 Acme Association ·\n * Powered by Betty`) rendered at the FOOT of the exported document: a styled HTML\n * footer, a markdown/text trailer, and a field in the JSON `branding` block.\n *\n * In HTML it needs its stylesheet rule, so — like the logo — it is emitted only when\n * `ExportOptions.includeCSS` is true. The markdown/text/JSON trailers have no such\n * dependency and always emit.\n *\n * NOT escaped for markdown: a value containing `_`, `]`, `)`, or a newline can break\n * the surrounding markdown. Supply plain text (HTML and JSON are escaped).\n */\n trademark?: string;\n}\n\nexport interface ExportOptions {\n includeMessages?: boolean;\n includeMetadata?: boolean;\n prettyPrint?: boolean;\n includeCSS?: boolean;\n /**\n * Emit the current app theme into the HTML export. When true and\n * `branding.brandTokens` is absent, the {@link DEFAULT_EXPORT_THEME_TOKENS}\n * are auto-snapshotted from the live document at export time. Default false —\n * an unthemed export renders exactly as before (the stylesheet's `var()`\n * fallbacks carry the legacy palette).\n *\n * Gates only the TOKEN auto-snapshot: a supplied `branding`'s `logoUrl`,\n * `title`, and `trademark` apply whenever `branding` is present — in HTML AND\n * in the JSON/markdown/text formats. (The export modal passes `branding`\n * through only while its \"Include branding\" checkbox is on, so in the UI flow\n * everything travels together.)\n */\n includeTheme?: boolean;\n /**\n * Which theme mode the snapshot captures — NOT whichever mode the app happens to be\n * in when the user clicks Export. Defaults to `'light'`: an export taken during a\n * dark session would otherwise bake dark text onto the page's white background and\n * come out unreadable. `'dark'` pairs the dark palette with the dark\n * `--mj-bg-page` the body rule applies, so the file is coherent either way.\n * HTML only — the data formats carry no colors.\n */\n themeMode?: 'light' | 'dark';\n /** Branding (tokens / logo / title) applied to the export — see {@link ExportBranding}. */\n branding?: ExportBranding;\n}\n\n/**\n * Tokens captured by the theme auto-snapshot — exactly the custom properties the\n * HTML export's stylesheet consumes. Hosts wanting more control supply their own\n * map via {@link ExportBranding.brandTokens} instead.\n */\nexport const DEFAULT_EXPORT_THEME_TOKENS: readonly string[] = [\n '--mj-bg-page',\n '--mj-text-primary',\n '--mj-text-secondary',\n '--mj-text-disabled',\n '--mj-border-default',\n '--mj-brand-primary',\n '--mj-brand-accent-subtle',\n '--mj-bg-surface-card',\n '--mj-font-family',\n '--mj-radius-md',\n];\n\n/** The conversation and the messages one export writes. */\nexport interface ExportConversationData {\n conversation: MJConversationEntity;\n details: MJConversationDetailEntity[];\n /**\n * The fork the messages come from, as the header names it (its display name). Absent or null\n * in Main.\n */\n fork?: string | null;\n}\n\n/** Internal: options with defaults applied (branding stays optional). */\ntype ResolvedExportOptions = Required<Omit<ExportOptions, 'branding'>> & Pick<ExportOptions, 'branding'>;\n\n/** Internal: the theme pieces resolved once per HTML export. */\ninterface ResolvedExportTheme {\n /** `<style>:root{…}</style>` block (empty string when no tokens resolved). */\n rootBlock: string;\n /** Inlined logo (data URI, or the raw URL on fetch failure); null = no logo. */\n logoDataUri: string | null;\n /** Document-title override; null = use the conversation name. */\n title: string | null;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ExportService {\n private _provider: IMetadataProvider | null = null;\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n /**\n * Exports the messages of one scope of a conversation (Main, or one fork's path) and\n * downloads the file. In a fork the export header names the fork.\n * @throws when the scope belongs to another conversation, or the conversation is not found.\n */\n async ExportConversation(\n conversationId: string,\n scope: ConversationScope,\n format: ExportFormat,\n currentUser: UserInfo,\n options: ExportOptions = {}\n ): Promise<void> {\n const conversation = await this.loadConversationData(conversationId, scope, currentUser);\n const { content, filename, mimeType } = await this.BuildExportContent(conversation, format, options);\n this.downloadFile(content, filename, mimeType);\n }\n\n /**\n * @deprecated Use {@link ExportConversation}. Without `scope`, exports Main.\n */\n async exportConversation(\n conversationId: string,\n format: ExportFormat,\n currentUser: UserInfo,\n options: ExportOptions = {},\n scope?: ConversationScope\n ): Promise<void> {\n const resolved = scope ?? ConversationEngine.TrunkScope(conversationId);\n return this.ExportConversation(conversationId, resolved, format, currentUser, options);\n }\n\n /**\n * Build the export document for already-loaded conversation data — the\n * download-free seam of {@link ExportConversation}. Async because HTML-format\n * branding may inline a logo. Exposed publicly so hosts (and tests) can build\n * export content without triggering a browser download.\n */\n public async BuildExportContent(\n data: ExportConversationData,\n format: ExportFormat,\n options: ExportOptions = {}\n ): Promise<{ content: string; filename: string; mimeType: string }> {\n const exportOptions: ResolvedExportOptions = {\n includeMessages: options.includeMessages ?? true,\n includeMetadata: options.includeMetadata ?? true,\n prettyPrint: options.prettyPrint ?? true,\n includeCSS: options.includeCSS ?? true,\n includeTheme: options.includeTheme ?? false,\n themeMode: options.themeMode ?? 'light',\n branding: options.branding\n };\n const baseName = `conversation-${data.conversation.Name}-${this.getTimestamp()}`;\n\n switch (format) {\n case 'json':\n return { content: this.exportAsJSON(data, exportOptions), filename: `${baseName}.json`, mimeType: 'application/json' };\n case 'markdown':\n return { content: this.exportAsMarkdown(data, exportOptions), filename: `${baseName}.md`, mimeType: 'text/markdown' };\n case 'html':\n return {\n content: this.exportAsHTML(data, exportOptions, await this.resolveTheme(exportOptions)),\n filename: `${baseName}.html`,\n mimeType: 'text/html'\n };\n case 'text':\n return { content: this.exportAsText(data, exportOptions), filename: `${baseName}.txt`, mimeType: 'text/plain' };\n default:\n throw new Error(`Unsupported export format: ${format}`);\n }\n }\n\n /**\n * Snapshot the given theme tokens from the live document. Empty/unset tokens are\n * skipped.\n *\n * `mode` captures a SPECIFIC palette rather than whatever the app is rendering right\n * now. Without it, exporting from a dark session bakes dark text onto the export's\n * white page and the file is unreadable. When the requested mode isn't the active one,\n * `data-theme` is flipped on `documentElement`, read, and restored — all synchronous\n * within one task, so the browser never paints the intermediate state. Omit `mode`\n * to read the live values as-is.\n */\n public SnapshotBrandTokens(\n tokens: readonly string[] = DEFAULT_EXPORT_THEME_TOKENS,\n mode?: 'light' | 'dark'\n ): Record<string, string> {\n const root = document.documentElement;\n const previous = root.getAttribute('data-theme');\n // The app's two real states are `data-theme=\"dark\"` and NO attribute at all —\n // `MJExplorerAppComponent` removes it for light rather than writing \"light\"\n // (explorer-app.component.ts). Reproduce those exactly rather than inventing a\n // third state, so the transient DOM is one the stylesheet has actually seen.\n const wantDark = mode === 'dark';\n const isDark = previous === 'dark';\n const mustSwap = mode != null && wantDark !== isDark;\n\n if (mustSwap) {\n if (wantDark) {\n root.setAttribute('data-theme', 'dark');\n } else {\n root.removeAttribute('data-theme');\n }\n }\n try {\n const style = getComputedStyle(root);\n const out: Record<string, string> = {};\n for (const token of tokens) {\n const value = style.getPropertyValue(token).trim();\n if (value) {\n out[token] = value;\n }\n }\n return out;\n } finally {\n // Restore even if reading throws — never strand the app in the other theme.\n if (mustSwap) {\n if (previous === null) {\n root.removeAttribute('data-theme');\n } else {\n root.setAttribute('data-theme', previous);\n }\n }\n }\n }\n\n /** Resolve the HTML export's theme pieces once: tokens (explicit > snapshot), logo, title. */\n private async resolveTheme(options: ResolvedExportOptions): Promise<ResolvedExportTheme> {\n const tokens = this.resolveBrandTokens(options);\n const rootBlock = tokens ? this.buildRootBlock(tokens) : '';\n const logoUrl = options.branding?.logoUrl?.trim();\n return {\n rootBlock,\n // The logo needs its `.brand-logo` rule (as the trademark footer needs its own),\n // so both are gated on includeCSS — a CSS-less export must never carry a\n // full-size unstyled logo. The modal enforces this too, but the public\n // BuildExportContent seam has to hold the line on its own.\n logoDataUri: options.includeCSS && logoUrl ? await this.inlineLogo(logoUrl) : null,\n // The title is deliberately NOT includeCSS-gated: it lands in <title>/<h1>, needs\n // no stylesheet, and is the one branding field every format carries unconditionally.\n title: options.branding?.title?.trim() || null\n };\n }\n\n /** Explicit host tokens win; `includeTheme` falls back to a live snapshot; else no theme. */\n private resolveBrandTokens(options: ResolvedExportOptions): Record<string, string> | null {\n const explicit = options.branding?.brandTokens;\n if (explicit && Object.keys(explicit).length > 0) {\n return explicit;\n }\n return options.includeTheme ? this.SnapshotBrandTokens(DEFAULT_EXPORT_THEME_TOKENS, options.themeMode) : null;\n }\n\n /** Document title used by every format: branding override → conversation name → generic. */\n private resolveTitle(\n data: ExportConversationData,\n options: ResolvedExportOptions\n ): string {\n return options.branding?.title?.trim() || data.conversation.Name || 'Conversation';\n }\n\n /**\n * The JSON `branding` block — the data-format analogue of the HTML header +\n * trademark footer. Carries the title, trademark, and logo URL the host set;\n * null when no branding (or none of those fields) is present. Theme color\n * tokens are HTML-only and never included here.\n */\n private buildBrandingBlock(options: ResolvedExportOptions): Record<string, string> | null {\n const b = options.branding;\n if (!b) {\n return null;\n }\n const block: Record<string, string> = {};\n const title = b.title?.trim();\n const trademark = b.trademark?.trim();\n const logoUrl = b.logoUrl?.trim();\n if (title) block.title = title;\n if (trademark) block.trademark = trademark;\n if (logoUrl) block.logoUrl = logoUrl;\n return Object.keys(block).length > 0 ? block : null;\n }\n\n /**\n * CSS functions permitted inside an exported token value. This is an ALLOWLIST\n * (not a blocklist) so no network-capable function can slip through: blocking\n * only `url(`/`expression(` let `image-set()`, `-webkit-image-set()`,\n * `cross-fade()`, `image()`, and `src()` fetch a beacon URL without ever\n * writing the literal `url(`. Anything not on this list drops the whole entry.\n * The set is the safe value/color-math functions the export could legitimately\n * carry; extend it only with functions that CANNOT load an external resource.\n */\n private static readonly SAFE_CSS_FUNCTIONS = new Set([\n 'var', 'calc', 'min', 'max', 'clamp',\n 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch',\n 'color', 'color-mix',\n ]);\n\n /**\n * Emit the sanitized `:root{}` style block. Keys must be custom-property names.\n * A value is REJECTED (the entry is dropped, not stripped) when it carries any\n * declaration/tag-escape or network-reach vector: `<>{}` (tag/rule breakout),\n * `;` (declaration breakout — e.g. `; background: url(beacon)`), `\\` (CSS\n * escapes), `@` (at-rules), OR any CSS function not in\n * {@link SAFE_CSS_FUNCTIONS} (which excludes every resource-loading function —\n * url/image-set/cross-fade/image/src/expression/…). Plain colors, `var()`, and\n * `color-mix()` pass untouched.\n */\n private buildRootBlock(tokens: Record<string, string>): string {\n const rules: string[] = [];\n for (const [key, rawValue] of Object.entries(tokens)) {\n if (!/^--[A-Za-z0-9_-]+$/.test(key)) {\n continue;\n }\n const value = String(rawValue).trim();\n if (!value || /[<>{};@\\\\]/.test(value) || this.hasUnsafeCssFunction(value)) {\n continue;\n }\n rules.push(`${key}: ${value};`);\n }\n return rules.length > 0 ? `\n <style>:root { ${rules.join(' ')} }</style>` : '';\n }\n\n /** True if the value calls any CSS function outside {@link SAFE_CSS_FUNCTIONS}. */\n private hasUnsafeCssFunction(value: string): boolean {\n // Every `name(` occurrence — the identifier immediately before a '('.\n const calls = value.match(/([a-zA-Z][a-zA-Z-]*)\\s*\\(/g) ?? [];\n return calls.some((c) => {\n const name = c.replace(/\\s*\\($/, '').toLowerCase();\n return !ExportService.SAFE_CSS_FUNCTIONS.has(name);\n });\n }\n\n /** Fetch → data URI so the export stays self-contained; raw URL on any failure\n * (or when the response isn't actually an image — don't inline HTML/other as\n * a bogus `data:` image). */\n private async inlineLogo(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n return url;\n }\n const contentType = response.headers.get('content-type') ?? '';\n if (!/^image\\//i.test(contentType)) {\n return url;\n }\n const blob = await response.blob();\n return await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(blob);\n });\n } catch {\n return url;\n }\n }\n\n /** Loads the conversation and the messages in the scope that no rerun replaced, in Sequence order. */\n private async loadConversationData(\n conversationId: string,\n scope: ConversationScope,\n currentUser: UserInfo\n ): Promise<ExportConversationData> {\n if (!UUIDsEqual(scope.ConversationID, conversationId)) {\n throw new Error(`Conversation ${conversationId} cannot be exported with the scope of conversation ${scope.ConversationID}`);\n }\n const rv = RunView.FromMetadataProvider(this.Provider);\n\n // Load conversation and details in parallel\n const [conversationResult, detailsResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Conversations',\n ExtraFilter: `ID='${conversationId}'`,\n ResultType: 'entity_object'\n },\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: ConversationEngine.LiveRowsFilter(ConversationEngine.ScopeFilter(scope)),\n OrderBy: 'Sequence ASC',\n ResultType: 'entity_object'\n }\n ], currentUser);\n\n if (!conversationResult.Success || !conversationResult.Results?.length) {\n throw new Error('Conversation not found');\n }\n\n const details = (detailsResult.Results || []) as MJConversationDetailEntity[];\n return {\n conversation: conversationResult.Results[0] as MJConversationEntity,\n details,\n fork: this.forkLabel(scope, details)\n };\n }\n\n /** The header name of the scope's fork: its display name; null in Main. */\n private forkLabel(scope: ConversationScope, details: ReadonlyArray<MJConversationDetailEntity>): string | null {\n if (scope.BranchID == null) {\n return null;\n }\n const fork = scope.Branches.find(b => UUIDsEqual(b.ID, scope.BranchID));\n if (!fork) {\n return scope.BranchID;\n }\n const first = [...details]\n .filter(d => UUIDsEqual(d.BranchID ?? null, scope.BranchID))\n .sort((a, b) => a.Sequence - b.Sequence)\n .find(d => ConversationEngine.ForkPlainText(d.Message) != null);\n return ConversationEngine.ForkDisplayName(fork, first?.Message ?? null);\n }\n\n private exportAsJSON(\n data: ExportConversationData,\n options: ResolvedExportOptions\n ): string {\n const exportData: Record<string, unknown> = {};\n\n // Add metadata if requested\n const conversationBlock: Record<string, unknown> = options.includeMetadata\n ? {\n id: data.conversation.ID,\n name: data.conversation.Name,\n description: data.conversation.Description,\n createdAt: data.conversation.__mj_CreatedAt,\n updatedAt: data.conversation.__mj_UpdatedAt\n }\n : { name: data.conversation.Name };\n if (data.fork) {\n conversationBlock.fork = data.fork;\n }\n exportData.conversation = conversationBlock;\n\n // Branding block (title / trademark / logo) when the host supplied branding —\n // the data-format analogue of the HTML header + trademark footer.\n const branding = this.buildBrandingBlock(options);\n if (branding) {\n exportData.branding = branding;\n }\n\n // Add messages if requested\n if (options.includeMessages) {\n exportData.messages = data.details.map((detail, index) => {\n const message: Record<string, unknown> = {\n role: detail.Role,\n message: detail.Message\n };\n\n if (options.includeMetadata) {\n message.id = detail.ID;\n message.sequence = index + 1;\n message.timestamp = detail.__mj_CreatedAt;\n }\n\n return message;\n });\n }\n\n // Use pretty print option\n return JSON.stringify(exportData, null, options.prettyPrint ? 2 : 0);\n }\n\n private exportAsMarkdown(\n data: ExportConversationData,\n options: ResolvedExportOptions\n ): string {\n const logoUrl = options.branding?.logoUrl?.trim();\n // Unbranded markdown must stay byte-identical to before (`# <Name>`), so DON'T\n // route through resolveTitle's 'Conversation' fallback here — only a supplied\n // branding title overrides the raw conversation name.\n const title = options.branding?.title?.trim() || data.conversation.Name;\n let md = '';\n if (logoUrl) {\n md += `![${title}](${logoUrl})\\n\\n`;\n }\n md += `# ${title}\\n\\n`;\n\n if (data.conversation.Description) {\n md += `${data.conversation.Description}\\n\\n`;\n }\n\n if (options.includeMetadata) {\n md += `**Created:** ${this.formatDate(data.conversation.__mj_CreatedAt)}\\n\\n`;\n }\n\n if (data.fork) {\n md += `**Fork:** ${data.fork}\\n\\n`;\n }\n\n md += `---\\n\\n`;\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n md += `## ${this.capitalizeRole(detail.Role || 'Unknown')}\\n\\n`;\n md += `${detail.Message}\\n\\n`;\n\n if (options.includeMetadata) {\n md += `*${this.formatDate(detail.__mj_CreatedAt)}*\\n\\n`;\n }\n\n md += `---\\n\\n`;\n }\n }\n\n const trademark = options.branding?.trademark?.trim();\n if (trademark) {\n md += `_${trademark}_\\n`;\n }\n\n return md;\n }\n\n private exportAsHTML(\n data: ExportConversationData,\n options: ResolvedExportOptions,\n theme: ResolvedExportTheme\n ): string {\n const trademark = options.branding?.trademark?.trim();\n // Every color reads a theme token with the legacy hex as its fallback, so an\n // unthemed export (no :root block emitted) renders exactly as it always has,\n // while a themed one follows the app's palette.\n const styles = options.includeCSS ? `\n <style>\n body { font-family: var(--mj-font-family, system-ui, -apple-system, sans-serif); background: var(--mj-bg-page, #fff); color: var(--mj-text-primary, #333); max-width: 800px; margin: 40px auto; padding: 0 20px; line-height: 1.6; }\n h1 { color: var(--mj-text-primary, #333); border-bottom: 2px solid var(--mj-brand-primary, #007bff); padding-bottom: 10px; }\n .meta { color: var(--mj-text-secondary, #666); font-size: 14px; margin-bottom: 30px; }\n .message { margin-bottom: 30px; padding: 20px; border-radius: var(--mj-radius-md, 8px); background: var(--mj-bg-surface-card, #f5f5f5); }\n .message.user { background: var(--mj-brand-accent-subtle, #e3f2fd); }\n .message.assistant { background: var(--mj-bg-surface-card, #f5f5f5); }\n .role { font-weight: 600; color: var(--mj-brand-primary, #007bff); margin-bottom: 10px; }\n .content { white-space: pre-wrap; }\n .timestamp { color: var(--mj-text-disabled, #999); font-size: 12px; margin-top: 10px; }${theme.logoDataUri ? `\n .brand-logo { display: block; max-height: 48px; margin-bottom: 12px; }` : ''}${trademark ? `\n .brand-trademark { margin-top: 40px; padding-top: 16px; border-top: 1px solid var(--mj-border-default, #ddd); color: var(--mj-text-secondary, #666); font-size: 12px; text-align: center; }` : ''}\n </style>${theme.rootBlock}` : '';\n\n const title = theme.title ?? (data.conversation.Name || 'Conversation');\n let html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${this.escapeHtml(title)}</title>${styles}\n</head>\n<body>\n ${theme.logoDataUri ? `<img class=\"brand-logo\" src=\"${this.escapeAttr(theme.logoDataUri)}\" alt=\"\" />\n ` : ''}<h1>${this.escapeHtml(title)}</h1>`;\n\n const forkLine = data.fork ? `\n <p>Fork: ${this.escapeHtml(data.fork)}</p>` : '';\n if (options.includeMetadata) {\n html += `\n <div class=\"meta\">\n ${data.conversation.Description ? `<p>${this.escapeHtml(data.conversation.Description)}</p>` : ''}\n <p>Created: ${this.formatDate(data.conversation.__mj_CreatedAt)}</p>${forkLine}\n </div>`;\n } else if (forkLine) {\n html += `\n <div class=\"meta\">${forkLine}\n </div>`;\n }\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n const roleClass = detail.Role?.toLowerCase() || 'unknown';\n html += `\n <div class=\"message ${roleClass}\">\n <div class=\"role\">${this.capitalizeRole(detail.Role || 'Unknown')}</div>\n <div class=\"content\">${this.escapeHtml(detail.Message || '')}</div>`;\n\n if (options.includeMetadata) {\n html += `\n <div class=\"timestamp\">${this.formatDate(detail.__mj_CreatedAt)}</div>`;\n }\n\n html += `\n </div>`;\n }\n }\n\n if (options.includeCSS && trademark) {\n html += `\n <footer class=\"brand-trademark\">${this.escapeHtml(trademark)}</footer>`;\n }\n\n html += `\n</body>\n</html>`;\n\n return html;\n }\n\n private exportAsText(\n data: ExportConversationData,\n options: ResolvedExportOptions\n ): string {\n const name = this.resolveTitle(data, options) || 'Conversation';\n let text = `${name}\\n`;\n text += '='.repeat(name.length) + '\\n\\n';\n\n if (data.conversation.Description) {\n text += `${data.conversation.Description}\\n\\n`;\n }\n\n if (options.includeMetadata) {\n text += `Created: ${this.formatDate(data.conversation.__mj_CreatedAt)}\\n\\n`;\n }\n\n if (data.fork) {\n text += `Fork: ${data.fork}\\n\\n`;\n }\n\n text += '-'.repeat(80) + '\\n\\n';\n\n if (options.includeMessages) {\n for (const detail of data.details) {\n text += `[${this.capitalizeRole(detail.Role || 'Unknown')}]\\n`;\n text += `${detail.Message}\\n`;\n\n if (options.includeMetadata) {\n text += `(${this.formatDate(detail.__mj_CreatedAt)})\\n`;\n }\n\n text += '\\n' + '-'.repeat(80) + '\\n\\n';\n }\n }\n\n const trademark = options.branding?.trademark?.trim();\n if (trademark) {\n text += `${trademark}\\n`;\n }\n\n return text;\n }\n\n private downloadFile(content: string, filename: string, mimeType: string): void {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n private formatDate(date: Date | string): string {\n const d = new Date(date);\n return d.toLocaleString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n });\n }\n\n private capitalizeRole(role: string): string {\n return role.charAt(0).toUpperCase() + role.slice(1).toLowerCase();\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /** escapeHtml + quote escaping — for values interpolated into HTML attributes. */\n private escapeAttr(text: string): string {\n return this.escapeHtml(text).replace(/\"/g, '&quot;');\n }\n\n private getTimestamp(): string {\n const now = new Date();\n return now.toISOString().replace(/[:.]/g, '-').slice(0, -5);\n }\n}\n"]}
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.
3
3
  *
4
- * Mixes the user's microphone stream with the agent's remote-audio stream (when the driver
5
- * exposes one — `OpenAIRealtimeClient.GetRemoteMediaStream()`) into a single mono **WAV** (16-bit
6
- * PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —
4
+ * Mixes the user's microphone stream with the agent's remote-audio stream (every driver exposes
5
+ * one via `GetRemoteMediaStream()`: WebRTC drivers publish their remote track, PCM drivers —
6
+ * Gemini, ElevenLabs, AssemblyAI, xAI, HuggingFace — publish their playout engine's output) into a
7
+ * single mono **WAV** (16-bit PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —
7
8
  * header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an
8
9
  * {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures
9
10
  * Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at
@@ -15,7 +16,10 @@
15
16
  * - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself
16
17
  * ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.
17
18
  * - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.
18
- * - No remote stream (non-WebRTC drivers, or the track hasn't landed yet) → records mic only.
19
+ * - No remote stream yet (e.g. the WebRTC track hasn't landed) → records mic only until it does.
20
+ * - A remote stream that can't be connected (e.g. a browser that rejects cross-sample-rate
21
+ * `createMediaStreamSource` — the PCM playout context runs at 24 kHz, this one at the device
22
+ * rate) → warns and records mic only; the mic recording is never lost to the agent stream.
19
23
  * - Any construction/start error is logged and disables the recorder rather than throwing into
20
24
  * the session-start path.
21
25
  *
@@ -49,12 +53,22 @@ export declare class RealtimeAudioRecorder {
49
53
  private remoteAttached;
50
54
  /** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */
51
55
  private pendingRemoteStream;
56
+ /** The microphone to record: the latest stream given to Start or ReplaceMicrophone. Setup connects it. */
57
+ private micStream;
58
+ /** The source node reading the microphone, swapped by ReplaceMicrophone. */
59
+ private micSource;
52
60
  /** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */
53
61
  private flushedSampleCount;
54
62
  /** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */
55
63
  private startedAtMs;
56
64
  /** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */
57
65
  private sampleRate;
66
+ /**
67
+ * Bumped by {@link cleanup}. The async setup snapshots it and, after each await, gives up if it moved:
68
+ * a {@link Stop} that lands mid-setup can only release what setup has already published, so setup
69
+ * must release the rest itself rather than build a live graph on a stopped recorder.
70
+ */
71
+ private setupGeneration;
58
72
  /** True while a recorder is actively capturing. */
59
73
  get IsRecording(): boolean;
60
74
  /** Wall-clock ms when recording started (0 before {@link Start}). */
@@ -86,6 +100,13 @@ export declare class RealtimeAudioRecorder {
86
100
  * before the audio graph finishes its async setup (the stream is connected once ready).
87
101
  */
88
102
  AttachRemoteStream(stream: MediaStream): void;
103
+ /**
104
+ * Records the microphone stream's current track from now on: call it after the track changes (a
105
+ * device switch). A source node reads the track it was created with, so the recording would otherwise
106
+ * carry silence from the old device. Safe before the audio graph is ready (setup connects the latest
107
+ * stream, and Start replaces it); a stream without an audio track keeps the current one.
108
+ */
109
+ ReplaceMicrophone(micStream: MediaStream): void;
89
110
  /**
90
111
  * Returns a Blob of the raw PCM captured since the last call (a window of header-less,
91
112
  * little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to
@@ -111,6 +132,13 @@ export declare class RealtimeAudioRecorder {
111
132
  Stop(): Promise<Blob | null>;
112
133
  /** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */
113
134
  private startMixedRecording;
135
+ /**
136
+ * Connects the agent stream, degrading to mic-only on failure. Unlike the mic (whose failure
137
+ * rightly disables the recorder), the agent stream is best-effort: a browser may reject it
138
+ * (cross-sample-rate `NotSupportedError`) and that must not discard the user's recording.
139
+ * Returns whether it was connected, so the caller only marks it attached on success.
140
+ */
141
+ private connectRemoteStream;
114
142
  /**
115
143
  * Creates the PCM capture node and inserts it into the graph: sources → captureNode → destination.
116
144
  * Prefers an {@link AudioWorkletNode} (off-main-thread, no deprecation); falls back to a
@@ -126,7 +154,7 @@ export declare class RealtimeAudioRecorder {
126
154
  private captureNode;
127
155
  /** Absorbs one captured PCM frame: accumulate samples + update the bounded peak array. */
128
156
  private captureFrame;
129
- /** Routes one stream's audio tracks into the capture node (no-op when track-less / no node). */
157
+ /** Routes one stream's audio tracks into the capture node; returns the source, or null when track-less / no node. */
130
158
  private connectStream;
131
159
  /** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */
132
160
  private collectSamples;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-audio-recorder.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-audio-recorder.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,qBAAqB;IAC9B,2EAA2E;IAC3E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAe;IAClD,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAO;IAC1C,mGAAmG;IACnG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAQ;IACzD,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAY5C;IAEF,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,UAAU,CAAoC;IACtD,kGAAkG;IAClG,OAAO,CAAC,SAAS,CAAsB;IACvC,+FAA+F;IAC/F,OAAO,CAAC,YAAY,CAAK;IACzB,oFAAoF;IACpF,OAAO,CAAC,KAAK,CAA0D;IACvE,gGAAgG;IAChG,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,SAAS,CAAS;IAE1B,gGAAgG;IAChG,OAAO,CAAC,cAAc,CAAS;IAC/B,qGAAqG;IACrG,OAAO,CAAC,mBAAmB,CAA4B;IAEvD,qGAAqG;IACrG,OAAO,CAAC,kBAAkB,CAAK;IAE/B,6FAA6F;IAC7F,OAAO,CAAC,WAAW,CAAK;IAExB,wGAAwG;IACxG,OAAO,CAAC,UAAU,CAAK;IAEvB,mDAAmD;IACnD,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED,qEAAqE;IACrE,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED;;;;OAIG;IACH,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED;;;OAGG;IACH,IAAW,UAAU,IAAI,MAAM,CAE9B;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAuB5E;;;;;OAKG;IACI,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAapD;;;;;;OAMG;IACI,kBAAkB,IAAI,IAAI,GAAG,IAAI;IAaxC,uEAAuE;IAChE,WAAW,IAAI,MAAM;IAO5B;;;;;OAKG;IACI,QAAQ,IAAI,MAAM,EAAE;IAI3B;;;;OAIG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAczC,kGAAkG;YACpF,mBAAmB;IA8BjC;;;;;OAKG;YACW,iBAAiB;IAO/B,oGAAoG;YACtF,oBAAoB;IAwBlC,sFAAsF;IACtF,OAAO,CAAC,yBAAyB;IAcjC,qFAAqF;IACrF,OAAO,CAAC,WAAW;IAInB,0FAA0F;IAC1F,OAAO,CAAC,YAAY;IAWpB,gGAAgG;IAChG,OAAO,CAAC,aAAa;IASrB,yGAAyG;IACzG,OAAO,CAAC,cAAc;IA0BtB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAUzB,oGAAoG;IACpG,OAAO,CAAC,aAAa;IASrB,4EAA4E;IAC5E,OAAO,CAAC,OAAO;CAyBlB"}
1
+ {"version":3,"file":"realtime-audio-recorder.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-audio-recorder.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,qBAAqB;IAC9B,2EAA2E;IAC3E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAe;IAClD,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAO;IAC1C,mGAAmG;IACnG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAQ;IACzD,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAY5C;IAEF,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,UAAU,CAAoC;IACtD,kGAAkG;IAClG,OAAO,CAAC,SAAS,CAAsB;IACvC,+FAA+F;IAC/F,OAAO,CAAC,YAAY,CAAK;IACzB,oFAAoF;IACpF,OAAO,CAAC,KAAK,CAA0D;IACvE,gGAAgG;IAChG,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,SAAS,CAAS;IAE1B,gGAAgG;IAChG,OAAO,CAAC,cAAc,CAAS;IAC/B,qGAAqG;IACrG,OAAO,CAAC,mBAAmB,CAA4B;IACvD,0GAA0G;IAC1G,OAAO,CAAC,SAAS,CAA4B;IAC7C,4EAA4E;IAC5E,OAAO,CAAC,SAAS,CAA2C;IAE5D,qGAAqG;IACrG,OAAO,CAAC,kBAAkB,CAAK;IAE/B,6FAA6F;IAC7F,OAAO,CAAC,WAAW,CAAK;IAExB,wGAAwG;IACxG,OAAO,CAAC,UAAU,CAAK;IAEvB;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAK;IAE5B,mDAAmD;IACnD,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED,qEAAqE;IACrE,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED;;;;OAIG;IACH,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED;;;OAGG;IACH,IAAW,UAAU,IAAI,MAAM,CAE9B;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAwB5E;;;;;OAKG;IACI,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAYpD;;;;;OAKG;IACI,iBAAiB,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAYtD;;;;;;OAMG;IACI,kBAAkB,IAAI,IAAI,GAAG,IAAI;IAaxC,uEAAuE;IAChE,WAAW,IAAI,MAAM;IAO5B;;;;;OAKG;IACI,QAAQ,IAAI,MAAM,EAAE;IAI3B;;;;OAIG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAczC,kGAAkG;YACpF,mBAAmB;IAuCjC;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;;;;OAKG;YACW,iBAAiB;IAO/B,oGAAoG;YACtF,oBAAoB;IAwBlC,sFAAsF;IACtF,OAAO,CAAC,yBAAyB;IAcjC,qFAAqF;IACrF,OAAO,CAAC,WAAW;IAInB,0FAA0F;IAC1F,OAAO,CAAC,YAAY;IAWpB,qHAAqH;IACrH,OAAO,CAAC,aAAa;IAUrB,yGAAyG;IACzG,OAAO,CAAC,cAAc;IA0BtB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAUzB,oGAAoG;IACpG,OAAO,CAAC,aAAa;IASrB,4EAA4E;IAC5E,OAAO,CAAC,OAAO;CA4BlB"}
@@ -2,9 +2,10 @@ import { EncodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';
2
2
  /**
3
3
  * Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.
4
4
  *
5
- * Mixes the user's microphone stream with the agent's remote-audio stream (when the driver
6
- * exposes one — `OpenAIRealtimeClient.GetRemoteMediaStream()`) into a single mono **WAV** (16-bit
7
- * PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —
5
+ * Mixes the user's microphone stream with the agent's remote-audio stream (every driver exposes
6
+ * one via `GetRemoteMediaStream()`: WebRTC drivers publish their remote track, PCM drivers —
7
+ * Gemini, ElevenLabs, AssemblyAI, xAI, HuggingFace — publish their playout engine's output) into a
8
+ * single mono **WAV** (16-bit PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —
8
9
  * header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an
9
10
  * {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures
10
11
  * Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at
@@ -16,7 +17,10 @@ import { EncodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';
16
17
  * - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself
17
18
  * ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.
18
19
  * - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.
19
- * - No remote stream (non-WebRTC drivers, or the track hasn't landed yet) → records mic only.
20
+ * - No remote stream yet (e.g. the WebRTC track hasn't landed) → records mic only until it does.
21
+ * - A remote stream that can't be connected (e.g. a browser that rejects cross-sample-rate
22
+ * `createMediaStreamSource` — the PCM playout context runs at 24 kHz, this one at the device
23
+ * rate) → warns and records mic only; the mic recording is never lost to the agent stream.
20
24
  * - Any construction/start error is logged and disables the recorder rather than throwing into
21
25
  * the session-start path.
22
26
  *
@@ -62,12 +66,22 @@ export class RealtimeAudioRecorder {
62
66
  remoteAttached = false;
63
67
  /** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */
64
68
  pendingRemoteStream = null;
69
+ /** The microphone to record: the latest stream given to Start or ReplaceMicrophone. Setup connects it. */
70
+ micStream = null;
71
+ /** The source node reading the microphone, swapped by ReplaceMicrophone. */
72
+ micSource = null;
65
73
  /** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */
66
74
  flushedSampleCount = 0;
67
75
  /** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */
68
76
  startedAtMs = 0;
69
77
  /** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */
70
78
  sampleRate = 0;
79
+ /**
80
+ * Bumped by {@link cleanup}. The async setup snapshots it and, after each await, gives up if it moved:
81
+ * a {@link Stop} that lands mid-setup can only release what setup has already published, so setup
82
+ * must release the rest itself rather than build a live graph on a stopped recorder.
83
+ */
84
+ setupGeneration = 0;
71
85
  /** True while a recorder is actively capturing. */
72
86
  get IsRecording() {
73
87
  return this.recording;
@@ -114,6 +128,7 @@ export class RealtimeAudioRecorder {
114
128
  this.startedAtMs = Date.now();
115
129
  this.finalPeaks = [];
116
130
  this.recording = true;
131
+ this.micStream = micStream;
117
132
  // Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any
118
133
  // failure disables the recorder rather than throwing into the session-start path.
119
134
  void this.startMixedRecording(micStream, remoteStream).catch((error) => {
@@ -132,14 +147,30 @@ export class RealtimeAudioRecorder {
132
147
  return;
133
148
  }
134
149
  if (this.audioContext && this.captureNode()) {
135
- this.connectStream(this.audioContext, stream);
136
- this.remoteAttached = true;
150
+ this.remoteAttached = this.connectRemoteStream(this.audioContext, stream);
137
151
  }
138
152
  else {
139
153
  // Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.
140
154
  this.pendingRemoteStream = stream;
141
155
  }
142
156
  }
157
+ /**
158
+ * Records the microphone stream's current track from now on: call it after the track changes (a
159
+ * device switch). A source node reads the track it was created with, so the recording would otherwise
160
+ * carry silence from the old device. Safe before the audio graph is ready (setup connects the latest
161
+ * stream, and Start replaces it); a stream without an audio track keeps the current one.
162
+ */
163
+ ReplaceMicrophone(micStream) {
164
+ this.micStream = micStream;
165
+ if (!this.audioContext || !this.captureNode()) {
166
+ return;
167
+ }
168
+ const replacement = this.connectStream(this.audioContext, micStream);
169
+ if (replacement) {
170
+ this.micSource?.disconnect();
171
+ this.micSource = replacement;
172
+ }
173
+ }
143
174
  /**
144
175
  * Returns a Blob of the raw PCM captured since the last call (a window of header-less,
145
176
  * little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to
@@ -195,6 +226,7 @@ export class RealtimeAudioRecorder {
195
226
  }
196
227
  /** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */
197
228
  async startMixedRecording(micStream, remoteStream) {
229
+ const generation = this.setupGeneration;
198
230
  const audioContext = new AudioContext();
199
231
  // CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.
200
232
  // Without resuming it, the mix graph never processes audio and we capture only silence. Starting
@@ -202,6 +234,11 @@ export class RealtimeAudioRecorder {
202
234
  if (audioContext.state === 'suspended') {
203
235
  await audioContext.resume();
204
236
  }
237
+ if (generation !== this.setupGeneration) {
238
+ // Stopped while resuming: the context was never published, so cleanup() could not close it.
239
+ void audioContext.close().catch(() => { });
240
+ return;
241
+ }
205
242
  const destination = audioContext.createMediaStreamDestination();
206
243
  // Publish the context + sample rate BEFORE wiring the remote so a concurrent AttachRemoteStream()
207
244
  // (the agent track can land during the awaits above) sees a ready graph to connect to.
@@ -211,14 +248,33 @@ export class RealtimeAudioRecorder {
211
248
  // Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the
212
249
  // node both pulls audio from the mix AND drives the destination so the graph keeps processing.
213
250
  await this.createCaptureNode(audioContext, destination);
214
- this.connectStream(audioContext, micStream);
251
+ if (generation !== this.setupGeneration) {
252
+ return; // Stopped while the capture node loaded: cleanup() already closed the published context.
253
+ }
254
+ // The microphone may have been replaced while the graph was being built.
255
+ this.micSource = this.connectStream(audioContext, this.micStream ?? micStream);
215
256
  // Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while
216
257
  // we were setting up. Either way, capture the agent voice — not just the mic.
217
258
  const remote = remoteStream ?? this.pendingRemoteStream;
218
259
  this.pendingRemoteStream = null;
219
260
  if (remote && remote.getAudioTracks().length > 0) {
220
- this.connectStream(audioContext, remote);
221
- this.remoteAttached = true;
261
+ this.remoteAttached = this.connectRemoteStream(audioContext, remote);
262
+ }
263
+ }
264
+ /**
265
+ * Connects the agent stream, degrading to mic-only on failure. Unlike the mic (whose failure
266
+ * rightly disables the recorder), the agent stream is best-effort: a browser may reject it
267
+ * (cross-sample-rate `NotSupportedError`) and that must not discard the user's recording.
268
+ * Returns whether it was connected, so the caller only marks it attached on success.
269
+ */
270
+ connectRemoteStream(audioContext, stream) {
271
+ try {
272
+ this.connectStream(audioContext, stream);
273
+ return true;
274
+ }
275
+ catch (error) {
276
+ console.warn('[RealtimeAudioRecorder] Could not mix the agent stream into the recording — continuing mic-only:', error);
277
+ return false;
222
278
  }
223
279
  }
224
280
  /**
@@ -286,14 +342,15 @@ export class RealtimeAudioRecorder {
286
342
  this.totalSamples += copy.length;
287
343
  this.peaks.Push(copy);
288
344
  }
289
- /** Routes one stream's audio tracks into the capture node (no-op when track-less / no node). */
345
+ /** Routes one stream's audio tracks into the capture node; returns the source, or null when track-less / no node. */
290
346
  connectStream(audioContext, stream) {
291
347
  const node = this.captureNode();
292
348
  if (!node || stream.getAudioTracks().length === 0) {
293
- return;
349
+ return null;
294
350
  }
295
351
  const source = audioContext.createMediaStreamSource(stream);
296
352
  source.connect(node);
353
+ return source;
297
354
  }
298
355
  /** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */
299
356
  collectSamples(from, to) {
@@ -345,6 +402,7 @@ export class RealtimeAudioRecorder {
345
402
  }
346
403
  /** Releases the audio graph + capture node and resets state. Idempotent. */
347
404
  cleanup() {
405
+ this.setupGeneration++;
348
406
  this.recording = false;
349
407
  this.pcmFrames = [];
350
408
  this.totalSamples = 0;
@@ -353,6 +411,8 @@ export class RealtimeAudioRecorder {
353
411
  this.peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);
354
412
  this.remoteAttached = false;
355
413
  this.pendingRemoteStream = null;
414
+ this.micStream = null;
415
+ this.micSource = null;
356
416
  if (this.workletNode) {
357
417
  this.workletNode.port.onmessage = null;
358
418
  try {