@dxos/plugin-transcription 0.8.3 → 0.8.4-main.1c7ec43d41

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 (330) hide show
  1. package/dist/lib/browser/blueprints/index.mjs +31 -0
  2. package/dist/lib/browser/blueprints/index.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-3L623EVL.mjs +17 -0
  4. package/dist/lib/browser/chunk-3L623EVL.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  6. package/dist/lib/browser/chunk-KAEXRX3Y.mjs +35 -0
  7. package/dist/lib/browser/chunk-KAEXRX3Y.mjs.map +7 -0
  8. package/dist/lib/browser/chunk-L4HW35L6.mjs +111 -0
  9. package/dist/lib/browser/chunk-L4HW35L6.mjs.map +7 -0
  10. package/dist/lib/browser/chunk-MLRJWAQH.mjs +179 -0
  11. package/dist/lib/browser/chunk-MLRJWAQH.mjs.map +7 -0
  12. package/dist/lib/browser/chunk-NQQK7UZQ.mjs +244 -0
  13. package/dist/lib/browser/chunk-NQQK7UZQ.mjs.map +7 -0
  14. package/dist/lib/browser/components/index.mjs +207 -0
  15. package/dist/lib/browser/components/index.mjs.map +7 -0
  16. package/dist/lib/browser/create-ULR5DPJZ.mjs +21 -0
  17. package/dist/lib/browser/create-ULR5DPJZ.mjs.map +7 -0
  18. package/dist/lib/browser/hooks/index.mjs +17 -0
  19. package/dist/lib/browser/index.mjs +442 -94
  20. package/dist/lib/browser/index.mjs.map +4 -4
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/open-SFMPLZVW.mjs +27 -0
  23. package/dist/lib/browser/open-SFMPLZVW.mjs.map +7 -0
  24. package/dist/lib/browser/operations/index.mjs +13 -0
  25. package/dist/lib/browser/operations/index.mjs.map +7 -0
  26. package/dist/lib/browser/summarize-BDFX4APN.mjs +78 -0
  27. package/dist/lib/browser/summarize-BDFX4APN.mjs.map +7 -0
  28. package/dist/lib/browser/types/index.mjs +4 -8
  29. package/dist/lib/node-esm/blueprints/index.mjs +32 -0
  30. package/dist/lib/node-esm/blueprints/index.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-7UIZWCD4.mjs +112 -0
  32. package/dist/lib/node-esm/chunk-7UIZWCD4.mjs.map +7 -0
  33. package/dist/lib/node-esm/chunk-C3AE2NYJ.mjs +181 -0
  34. package/dist/lib/node-esm/chunk-C3AE2NYJ.mjs.map +7 -0
  35. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  36. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  37. package/dist/lib/node-esm/chunk-KO3IOD2O.mjs +19 -0
  38. package/dist/lib/node-esm/chunk-KO3IOD2O.mjs.map +7 -0
  39. package/dist/lib/node-esm/chunk-MQK4R6GT.mjs +245 -0
  40. package/dist/lib/node-esm/chunk-MQK4R6GT.mjs.map +7 -0
  41. package/dist/lib/node-esm/chunk-O533NBMK.mjs +37 -0
  42. package/dist/lib/node-esm/chunk-O533NBMK.mjs.map +7 -0
  43. package/dist/lib/node-esm/components/index.mjs +208 -0
  44. package/dist/lib/node-esm/components/index.mjs.map +7 -0
  45. package/dist/lib/node-esm/create-A5QHNWRF.mjs +22 -0
  46. package/dist/lib/node-esm/create-A5QHNWRF.mjs.map +7 -0
  47. package/dist/lib/node-esm/hooks/index.mjs +18 -0
  48. package/dist/lib/node-esm/hooks/index.mjs.map +7 -0
  49. package/dist/lib/node-esm/index.mjs +442 -94
  50. package/dist/lib/node-esm/index.mjs.map +4 -4
  51. package/dist/lib/node-esm/meta.json +1 -1
  52. package/dist/lib/node-esm/open-OI24QNTO.mjs +28 -0
  53. package/dist/lib/node-esm/open-OI24QNTO.mjs.map +7 -0
  54. package/dist/lib/node-esm/operations/index.mjs +14 -0
  55. package/dist/lib/node-esm/operations/index.mjs.map +7 -0
  56. package/dist/lib/node-esm/summarize-3UNN24I2.mjs +79 -0
  57. package/dist/lib/node-esm/summarize-3UNN24I2.mjs.map +7 -0
  58. package/dist/lib/node-esm/types/index.mjs +4 -8
  59. package/dist/types/src/TranscriptionPlugin.d.ts +3 -1
  60. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  61. package/dist/types/src/TranscriptionPlugin.node.d.ts +4 -0
  62. package/dist/types/src/TranscriptionPlugin.node.d.ts.map +1 -0
  63. package/dist/types/src/TranscriptionPlugin.test.d.ts +2 -0
  64. package/dist/types/src/TranscriptionPlugin.test.d.ts.map +1 -0
  65. package/dist/types/src/blueprints/index.d.ts +2 -0
  66. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  67. package/dist/types/src/blueprints/transcription-blueprint.d.ts +4 -0
  68. package/dist/types/src/blueprints/transcription-blueprint.d.ts.map +1 -0
  69. package/dist/types/src/capabilities/blueprint-definition.d.ts +6 -0
  70. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
  71. package/dist/types/src/capabilities/index.d.ts +10 -4
  72. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  74. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  75. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  76. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/text-content.d.ts +8 -0
  78. package/dist/types/src/capabilities/text-content.d.ts.map +1 -0
  79. package/dist/types/src/capabilities/transcriber.d.ts +4 -3
  80. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  81. package/dist/types/src/components/Transcription/Transcription.d.ts +18 -0
  82. package/dist/types/src/components/Transcription/Transcription.d.ts.map +1 -0
  83. package/dist/types/src/components/Transcription/Transcription.stories.d.ts +40 -0
  84. package/dist/types/src/components/Transcription/Transcription.stories.d.ts.map +1 -0
  85. package/dist/types/src/components/Transcription/index.d.ts +2 -0
  86. package/dist/types/src/components/Transcription/index.d.ts.map +1 -0
  87. package/dist/types/src/components/Transcription/transcription-extension.d.ts +15 -0
  88. package/dist/types/src/components/Transcription/transcription-extension.d.ts.map +1 -0
  89. package/dist/types/src/components/index.d.ts +1 -6
  90. package/dist/types/src/components/index.d.ts.map +1 -1
  91. package/dist/types/src/containers/TranscriptionContainer/TranscriptionContainer.d.ts +6 -0
  92. package/dist/types/src/containers/TranscriptionContainer/TranscriptionContainer.d.ts.map +1 -0
  93. package/dist/types/src/containers/TranscriptionContainer/index.d.ts +2 -0
  94. package/dist/types/src/containers/TranscriptionContainer/index.d.ts.map +1 -0
  95. package/dist/types/src/containers/index.d.ts +3 -0
  96. package/dist/types/src/containers/index.d.ts.map +1 -0
  97. package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
  98. package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
  99. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +1 -1
  100. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -1
  101. package/dist/types/src/hooks/useTranscriber.d.ts +2 -2
  102. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  103. package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
  104. package/dist/types/src/index.d.ts +4 -4
  105. package/dist/types/src/index.d.ts.map +1 -1
  106. package/dist/types/src/meta.d.ts +2 -3
  107. package/dist/types/src/meta.d.ts.map +1 -1
  108. package/dist/types/src/model/model.d.ts +2 -2
  109. package/dist/types/src/model/model.d.ts.map +1 -1
  110. package/dist/types/src/{segments-normalization → normalization}/index.d.ts +1 -1
  111. package/dist/types/src/normalization/index.d.ts.map +1 -0
  112. package/dist/types/src/{segments-normalization → normalization}/message-normalizer.d.ts +5 -5
  113. package/dist/types/src/normalization/message-normalizer.d.ts.map +1 -0
  114. package/dist/types/src/normalization/normalization.d.ts +364 -0
  115. package/dist/types/src/normalization/normalization.d.ts.map +1 -0
  116. package/dist/types/src/normalization/normalization.test.d.ts.map +1 -0
  117. package/dist/types/src/normalization/utils.d.ts +3 -0
  118. package/dist/types/src/normalization/utils.d.ts.map +1 -0
  119. package/dist/types/src/operations/create.d.ts +5 -0
  120. package/dist/types/src/operations/create.d.ts.map +1 -0
  121. package/dist/types/src/operations/definitions.d.ts +917 -0
  122. package/dist/types/src/operations/definitions.d.ts.map +1 -0
  123. package/dist/types/src/operations/index.d.ts +4 -0
  124. package/dist/types/src/operations/index.d.ts.map +1 -0
  125. package/dist/types/src/operations/open.d.ts +5 -0
  126. package/dist/types/src/operations/open.d.ts.map +1 -0
  127. package/dist/types/src/operations/summarize.d.ts +8 -0
  128. package/dist/types/src/operations/summarize.d.ts.map +1 -0
  129. package/dist/types/src/stories/FileTranscription.stories.d.ts +18 -0
  130. package/dist/types/src/stories/FileTranscription.stories.d.ts.map +1 -0
  131. package/dist/types/src/stories/KeyWordRecognition.stories.d.ts +14 -0
  132. package/dist/types/src/stories/KeyWordRecognition.stories.d.ts.map +1 -0
  133. package/dist/types/src/stories/MicrophoneTranscription.stories.d.ts +20 -0
  134. package/dist/types/src/stories/MicrophoneTranscription.stories.d.ts.map +1 -0
  135. package/dist/types/src/stories/TranscriptionStory.d.ts +23 -0
  136. package/dist/types/src/stories/TranscriptionStory.d.ts.map +1 -0
  137. package/dist/types/src/stories/useIsSpeaking.d.ts.map +1 -0
  138. package/dist/types/src/testing/testing.d.ts +10 -10
  139. package/dist/types/src/testing/testing.d.ts.map +1 -1
  140. package/dist/types/src/transcriber/media-stream-recorder.d.ts +3 -3
  141. package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
  142. package/dist/types/src/transcriber/transcriber.d.ts +60 -5
  143. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  144. package/dist/types/src/transcriber/transcriber.test.d.ts +2 -0
  145. package/dist/types/src/transcriber/transcriber.test.d.ts.map +1 -0
  146. package/dist/types/src/transcriber/transcription-manager.d.ts +12 -13
  147. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  148. package/dist/types/src/translations.d.ts +10 -11
  149. package/dist/types/src/translations.d.ts.map +1 -1
  150. package/dist/types/src/types/capabilities.d.ts +19 -0
  151. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  152. package/dist/types/src/types/defs.d.ts +5 -0
  153. package/dist/types/src/types/defs.d.ts.map +1 -0
  154. package/dist/types/src/types/index.d.ts +2 -2
  155. package/dist/types/src/types/index.d.ts.map +1 -1
  156. package/dist/types/src/util/array.d.ts.map +1 -1
  157. package/dist/types/src/util/get-time-str.d.ts.map +1 -1
  158. package/dist/types/src/util/index.d.ts +1 -0
  159. package/dist/types/src/util/index.d.ts.map +1 -1
  160. package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
  161. package/dist/types/src/util/render-byline.d.ts +4 -0
  162. package/dist/types/src/util/render-byline.d.ts.map +1 -0
  163. package/dist/types/tsconfig.tsbuildinfo +1 -1
  164. package/package.json +127 -82
  165. package/src/TranscriptionPlugin.node.ts +20 -0
  166. package/src/TranscriptionPlugin.test.ts +31 -0
  167. package/src/TranscriptionPlugin.tsx +22 -61
  168. package/src/blueprints/index.ts +5 -0
  169. package/src/blueprints/transcription-blueprint.ts +29 -0
  170. package/src/capabilities/blueprint-definition.ts +19 -0
  171. package/src/capabilities/index.ts +11 -6
  172. package/src/capabilities/operation-handler.ts +16 -0
  173. package/src/capabilities/react-surface.tsx +23 -14
  174. package/src/capabilities/text-content.ts +33 -0
  175. package/src/capabilities/transcriber.ts +54 -42
  176. package/src/components/{Transcript/Transcript.stories.tsx → Transcription/Transcription.stories.tsx} +78 -73
  177. package/src/components/Transcription/Transcription.tsx +64 -0
  178. package/src/components/{Transcript → Transcription}/index.ts +1 -1
  179. package/src/components/{Transcript/transcript-extension.ts → Transcription/transcription-extension.ts} +15 -48
  180. package/src/components/index.ts +1 -6
  181. package/src/containers/TranscriptionContainer/TranscriptionContainer.tsx +35 -0
  182. package/src/containers/TranscriptionContainer/index.ts +5 -0
  183. package/src/containers/index.ts +7 -0
  184. package/src/hooks/useAudioFile.ts +49 -53
  185. package/src/hooks/useQueueModelAdapter.ts +1 -1
  186. package/src/hooks/useTranscriber.ts +15 -7
  187. package/src/hooks/useVoiceInput.ts +3 -3
  188. package/src/index.ts +10 -5
  189. package/src/meta.ts +9 -6
  190. package/src/model/model.test.ts +27 -21
  191. package/src/model/model.ts +3 -3
  192. package/src/{segments-normalization → normalization}/message-normalizer.ts +20 -19
  193. package/src/{segments-normalization → normalization}/normalization.test.ts +26 -33
  194. package/src/normalization/normalization.ts +77 -0
  195. package/src/{segments-normalization → normalization}/utils.ts +2 -2
  196. package/src/operations/create.ts +24 -0
  197. package/src/operations/definitions.ts +101 -0
  198. package/src/operations/index.ts +13 -0
  199. package/src/operations/open.ts +28 -0
  200. package/src/operations/summarize.ts +101 -0
  201. package/src/{components/stories → stories}/FileTranscription.stories.tsx +99 -72
  202. package/src/{components/stories → stories}/KeyWordRecognition.stories.tsx +46 -40
  203. package/src/{components/stories → stories}/MicrophoneTranscription.stories.tsx +85 -101
  204. package/src/stories/TranscriptionStory.tsx +93 -0
  205. package/src/testing/testing.ts +41 -41
  206. package/src/transcriber/media-stream-recorder.ts +4 -4
  207. package/src/transcriber/transcriber.browser.test.ts +5 -5
  208. package/src/transcriber/transcriber.test.ts +104 -0
  209. package/src/transcriber/transcriber.ts +101 -62
  210. package/src/transcriber/transcription-manager.ts +37 -52
  211. package/src/translations.ts +12 -10
  212. package/src/types/capabilities.ts +39 -0
  213. package/src/types/defs.ts +9 -0
  214. package/src/types/index.ts +2 -2
  215. package/src/util/index.ts +1 -0
  216. package/src/util/render-byline.ts +33 -0
  217. package/dist/lib/browser/TranscriptContainer-H3AZQ4CQ.mjs +0 -11
  218. package/dist/lib/browser/chunk-53AXUEIQ.mjs +0 -764
  219. package/dist/lib/browser/chunk-53AXUEIQ.mjs.map +0 -7
  220. package/dist/lib/browser/chunk-GOF7MJNV.mjs +0 -19
  221. package/dist/lib/browser/chunk-GOF7MJNV.mjs.map +0 -7
  222. package/dist/lib/browser/chunk-IQ5ZRMHZ.mjs +0 -495
  223. package/dist/lib/browser/chunk-IQ5ZRMHZ.mjs.map +0 -7
  224. package/dist/lib/browser/chunk-OSISJM2F.mjs +0 -55
  225. package/dist/lib/browser/chunk-OSISJM2F.mjs.map +0 -7
  226. package/dist/lib/browser/chunk-UDCCT32F.mjs +0 -8
  227. package/dist/lib/browser/chunk-UDCCT32F.mjs.map +0 -7
  228. package/dist/lib/browser/chunk-W2KNYJD6.mjs +0 -16
  229. package/dist/lib/browser/chunk-W2KNYJD6.mjs.map +0 -7
  230. package/dist/lib/browser/intent-resolver-BGADCHVM.mjs +0 -30
  231. package/dist/lib/browser/intent-resolver-BGADCHVM.mjs.map +0 -7
  232. package/dist/lib/browser/react-surface-HMJS5FSS.mjs +0 -34
  233. package/dist/lib/browser/react-surface-HMJS5FSS.mjs.map +0 -7
  234. package/dist/lib/browser/transcriber-OYPLZFG5.mjs +0 -56
  235. package/dist/lib/browser/transcriber-OYPLZFG5.mjs.map +0 -7
  236. package/dist/lib/node/TranscriptContainer-KIG3GMAB.cjs +0 -32
  237. package/dist/lib/node/TranscriptContainer-KIG3GMAB.cjs.map +0 -7
  238. package/dist/lib/node/chunk-3PYT3BDM.cjs +0 -40
  239. package/dist/lib/node/chunk-3PYT3BDM.cjs.map +0 -7
  240. package/dist/lib/node/chunk-6LW5RA3K.cjs +0 -75
  241. package/dist/lib/node/chunk-6LW5RA3K.cjs.map +0 -7
  242. package/dist/lib/node/chunk-AFUZI63A.cjs +0 -42
  243. package/dist/lib/node/chunk-AFUZI63A.cjs.map +0 -7
  244. package/dist/lib/node/chunk-CVUN3UO7.cjs +0 -778
  245. package/dist/lib/node/chunk-CVUN3UO7.cjs.map +0 -7
  246. package/dist/lib/node/chunk-FKDJNJ57.cjs +0 -35
  247. package/dist/lib/node/chunk-FKDJNJ57.cjs.map +0 -7
  248. package/dist/lib/node/chunk-ZFAUMZXE.cjs +0 -518
  249. package/dist/lib/node/chunk-ZFAUMZXE.cjs.map +0 -7
  250. package/dist/lib/node/index.cjs +0 -133
  251. package/dist/lib/node/index.cjs.map +0 -7
  252. package/dist/lib/node/intent-resolver-LL3TTI4H.cjs +0 -45
  253. package/dist/lib/node/intent-resolver-LL3TTI4H.cjs.map +0 -7
  254. package/dist/lib/node/meta.json +0 -1
  255. package/dist/lib/node/react-surface-N25TBBG4.cjs +0 -56
  256. package/dist/lib/node/react-surface-N25TBBG4.cjs.map +0 -7
  257. package/dist/lib/node/transcriber-6J4FVFSZ.cjs +0 -68
  258. package/dist/lib/node/transcriber-6J4FVFSZ.cjs.map +0 -7
  259. package/dist/lib/node/types/index.cjs +0 -36
  260. package/dist/lib/node/types/index.cjs.map +0 -7
  261. package/dist/lib/node-esm/TranscriptContainer-G75LDEO5.mjs +0 -12
  262. package/dist/lib/node-esm/chunk-56QHOX4A.mjs +0 -765
  263. package/dist/lib/node-esm/chunk-56QHOX4A.mjs.map +0 -7
  264. package/dist/lib/node-esm/chunk-FH75X4JC.mjs +0 -56
  265. package/dist/lib/node-esm/chunk-FH75X4JC.mjs.map +0 -7
  266. package/dist/lib/node-esm/chunk-MFJO7EKD.mjs +0 -10
  267. package/dist/lib/node-esm/chunk-MFJO7EKD.mjs.map +0 -7
  268. package/dist/lib/node-esm/chunk-MFJPEIVB.mjs +0 -17
  269. package/dist/lib/node-esm/chunk-MFJPEIVB.mjs.map +0 -7
  270. package/dist/lib/node-esm/chunk-PRL64SRU.mjs +0 -496
  271. package/dist/lib/node-esm/chunk-PRL64SRU.mjs.map +0 -7
  272. package/dist/lib/node-esm/chunk-TXMDLLBR.mjs +0 -21
  273. package/dist/lib/node-esm/chunk-TXMDLLBR.mjs.map +0 -7
  274. package/dist/lib/node-esm/intent-resolver-5LVCYSJS.mjs +0 -31
  275. package/dist/lib/node-esm/intent-resolver-5LVCYSJS.mjs.map +0 -7
  276. package/dist/lib/node-esm/react-surface-JGQSYGRR.mjs +0 -35
  277. package/dist/lib/node-esm/react-surface-JGQSYGRR.mjs.map +0 -7
  278. package/dist/lib/node-esm/transcriber-TVG2MBBN.mjs +0 -57
  279. package/dist/lib/node-esm/transcriber-TVG2MBBN.mjs.map +0 -7
  280. package/dist/types/src/capabilities/capabilities.d.ts +0 -17
  281. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  282. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  283. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  284. package/dist/types/src/components/Transcript/Transcript.d.ts +0 -20
  285. package/dist/types/src/components/Transcript/Transcript.d.ts.map +0 -1
  286. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +0 -24
  287. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +0 -1
  288. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +0 -28
  289. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +0 -1
  290. package/dist/types/src/components/Transcript/index.d.ts +0 -2
  291. package/dist/types/src/components/Transcript/index.d.ts.map +0 -1
  292. package/dist/types/src/components/Transcript/transcript-extension.d.ts +0 -19
  293. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +0 -1
  294. package/dist/types/src/components/TranscriptContainer.d.ts +0 -8
  295. package/dist/types/src/components/TranscriptContainer.d.ts.map +0 -1
  296. package/dist/types/src/components/stories/FileTranscription.stories.d.ts +0 -17
  297. package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +0 -1
  298. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +0 -10
  299. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +0 -1
  300. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts +0 -19
  301. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +0 -1
  302. package/dist/types/src/components/stories/TranscriptionStory.d.ts +0 -11
  303. package/dist/types/src/components/stories/TranscriptionStory.d.ts.map +0 -1
  304. package/dist/types/src/components/stories/useIsSpeaking.d.ts.map +0 -1
  305. package/dist/types/src/segments-normalization/index.d.ts.map +0 -1
  306. package/dist/types/src/segments-normalization/message-normalizer.d.ts.map +0 -1
  307. package/dist/types/src/segments-normalization/normalization.d.ts +0 -329
  308. package/dist/types/src/segments-normalization/normalization.d.ts.map +0 -1
  309. package/dist/types/src/segments-normalization/normalization.test.d.ts.map +0 -1
  310. package/dist/types/src/segments-normalization/utils.d.ts +0 -3
  311. package/dist/types/src/segments-normalization/utils.d.ts.map +0 -1
  312. package/dist/types/src/types/schema.d.ts +0 -41
  313. package/dist/types/src/types/schema.d.ts.map +0 -1
  314. package/dist/types/src/types/types.d.ts +0 -31
  315. package/dist/types/src/types/types.d.ts.map +0 -1
  316. package/src/capabilities/capabilities.ts +0 -33
  317. package/src/capabilities/intent-resolver.ts +0 -24
  318. package/src/components/Transcript/Transcript.tsx +0 -100
  319. package/src/components/Transcript/deprecated/OldTranscript.tsx +0 -260
  320. package/src/components/TranscriptContainer.tsx +0 -30
  321. package/src/components/stories/TranscriptionStory.tsx +0 -41
  322. package/src/segments-normalization/normalization.ts +0 -105
  323. package/src/types/schema.ts +0 -41
  324. package/src/types/types.ts +0 -32
  325. package/dist/lib/browser/{TranscriptContainer-H3AZQ4CQ.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
  326. package/dist/lib/{node-esm/TranscriptContainer-G75LDEO5.mjs.map → browser/hooks/index.mjs.map} +0 -0
  327. package/dist/types/src/{segments-normalization → normalization}/normalization.test.d.ts +0 -0
  328. package/dist/types/src/{components/stories → stories}/useIsSpeaking.d.ts +0 -0
  329. package/src/{segments-normalization → normalization}/index.ts +1 -1
  330. /package/src/{components/stories → stories}/useIsSpeaking.tsx +0 -0
@@ -2,24 +2,22 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
  import { useEffect, useMemo, useState } from 'react';
7
7
 
8
- import { EdgeAiServiceClient } from '@dxos/ai';
9
- import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
10
- import { extractionAnthropicFn, processTranscriptMessage } from '@dxos/assistant';
8
+ import { extractionAnthropicFunction, processTranscriptMessage } from '@dxos/assistant/extraction';
11
9
  import { scheduleTaskInterval } from '@dxos/async';
12
10
  import { Filter, type Queue } from '@dxos/client/echo';
13
11
  import { Context } from '@dxos/context';
14
12
  import { type Key, Obj, Ref, Type } from '@dxos/echo';
15
- import { createQueueDXN } from '@dxos/echo-schema';
16
- import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
13
+ import { createQueueDXN } from '@dxos/echo/internal';
17
14
  import { IdentityDid } from '@dxos/keys';
18
15
  import { log } from '@dxos/log';
19
- import { faker } from '@dxos/random';
20
- import { useQueue, type Space } from '@dxos/react-client/echo';
21
- import { DataType } from '@dxos/schema';
22
- import { Testing, seedTestData } from '@dxos/schema/testing';
16
+ import { random } from '@dxos/random';
17
+ import { type Space, useQueue } from '@dxos/react-client/echo';
18
+ import { TestSchema } from '@dxos/schema/testing';
19
+ import { type ContentBlock, Message, Organization, Person } from '@dxos/types';
20
+ import { seedTestData } from '@dxos/types/testing';
23
21
 
24
22
  // TODO(burdon): Reconcile with plugin-markdown. Move to @dxos/schema/testing.
25
23
  export const TestItem = Schema.Struct({
@@ -32,15 +30,15 @@ export const TestItem = Schema.Struct({
32
30
  description: 'Product description',
33
31
  }),
34
32
  }).pipe(
35
- Type.Obj({
36
- typename: 'dxos.org/type/Test',
33
+ Type.object({
34
+ typename: 'org.dxos.type.test',
37
35
  version: '0.1.0',
38
36
  }),
39
37
  );
40
38
 
41
39
  // TODO(wittjosiah): Make builder generic and reuse for all message types.
42
40
  abstract class AbstractMessageBuilder {
43
- abstract createMessage(numSegments?: number): Promise<DataType.Message>;
41
+ abstract createMessage(numSegments?: number): Promise<Message.Message>;
44
42
  }
45
43
 
46
44
  /**
@@ -51,8 +49,8 @@ export class MessageBuilder extends AbstractMessageBuilder {
51
49
 
52
50
  users = Array.from({ length: 5 }, () => ({
53
51
  identityDid: IdentityDid.random().toString(),
54
- name: faker.person.fullName(),
55
- email: faker.internet.email(),
52
+ name: random.person.fullName(),
53
+ email: random.internet.email(),
56
54
  }));
57
55
 
58
56
  start = new Date(Date.now() - 24 * 60 * 60 * 10_000);
@@ -61,27 +59,32 @@ export class MessageBuilder extends AbstractMessageBuilder {
61
59
  super();
62
60
  }
63
61
 
64
- override async createMessage(numSegments = 1): Promise<DataType.Message> {
65
- return Obj.make(DataType.Message, {
62
+ override async createMessage(numSegments = 1): Promise<Message.Message> {
63
+ return Obj.make(Message.Message, {
66
64
  created: this.next().toISOString(),
67
- sender: faker.helpers.arrayElement(this.users),
65
+ sender: random.helpers.arrayElement(this.users),
68
66
  blocks: Array.from({ length: numSegments }).map(() => this.createBlock()),
69
67
  });
70
68
  }
71
69
 
72
- createBlock(): DataType.MessageBlock.Transcription {
73
- let text = faker.lorem.paragraph();
70
+ createBlock(): ContentBlock.Transcript {
71
+ let text = random.lorem.paragraph();
74
72
  if (this._space) {
75
- const label = faker.commerce.productName();
76
- const obj = this._space.db.add(Obj.make(TestItem, { title: label, description: faker.lorem.paragraph() }));
73
+ const label = random.commerce.productName();
74
+ const obj = this._space.db.add(
75
+ Obj.make(TestItem, {
76
+ title: label,
77
+ description: random.lorem.paragraph(),
78
+ }),
79
+ );
77
80
  const dxn = Ref.make(obj).dxn.toString();
78
81
  const words = text.split(' ');
79
- words.splice(Math.floor(Math.random() * words.length), 0, `[${label}][${dxn}]`);
82
+ words.splice(Math.floor(Math.random() * words.length), 0, `[${label}](${dxn})`);
80
83
  text = words.join(' ');
81
84
  }
82
85
 
83
86
  return {
84
- type: 'transcription',
87
+ _tag: 'transcript',
85
88
  started: this.next().toISOString(),
86
89
  text,
87
90
  };
@@ -95,15 +98,9 @@ export class MessageBuilder extends AbstractMessageBuilder {
95
98
 
96
99
  // TODO(burdon): Reconcile with BlockBuilder.
97
100
  class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
98
- AiService = new EdgeAiServiceClient({
99
- endpoint: AI_SERVICE_ENDPOINT.REMOTE,
100
- });
101
-
102
- executor = new FunctionExecutor(new ServiceContainer().setServices({ ai: { client: this.AiService } }));
103
-
104
101
  space: Space | undefined;
105
102
  currentMessage: number = 0;
106
- transcriptMessages: DataType.Message[] = [];
103
+ transcriptMessages: Message.Message[] = [];
107
104
 
108
105
  async connect(space: Space): Promise<void> {
109
106
  this.space = space;
@@ -111,14 +108,18 @@ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
111
108
  this.transcriptMessages = transcriptMessages;
112
109
  }
113
110
 
114
- override async createMessage(): Promise<DataType.Message> {
111
+ override async createMessage(): Promise<Message.Message> {
115
112
  if (!this.space) {
116
113
  throw new Error('Space not connected');
117
114
  }
118
115
 
119
- const { objects } = await this.space.db
116
+ const objects = await this.space.db
120
117
  .query(
121
- Filter.or(Filter.type(DataType.Person), Filter.type(DataType.Organization), Filter.type(Testing.DocumentType)),
118
+ Filter.or(
119
+ Filter.type(Person.Person),
120
+ Filter.type(Organization.Organization),
121
+ Filter.type(TestSchema.DocumentType),
122
+ ),
122
123
  )
123
124
  .run();
124
125
 
@@ -129,8 +130,7 @@ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
129
130
 
130
131
  const { message: enhancedMessage } = await processTranscriptMessage({
131
132
  input: { message },
132
- executor: this.executor,
133
- function: extractionAnthropicFn,
133
+ function: extractionAnthropicFunction,
134
134
  });
135
135
 
136
136
  return enhancedMessage;
@@ -142,7 +142,7 @@ type UseTestTranscriptionQueue = (
142
142
  queueId?: Key.ObjectId,
143
143
  running?: boolean,
144
144
  interval?: number,
145
- ) => Queue<DataType.Message> | undefined;
145
+ ) => Queue<Message.Message> | undefined;
146
146
 
147
147
  /**
148
148
  * Test transcriptionqueue.
@@ -155,7 +155,7 @@ export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
155
155
  ) => {
156
156
  // TODO(dmaretskyi): Use space.queues.create() instead.
157
157
  const queueDxn = useMemo(() => (space ? createQueueDXN(space.id, queueId) : undefined), [space, queueId]);
158
- const queue = useQueue<DataType.Message>(queueDxn);
158
+ const queue = useQueue<Message.Message>(queueDxn);
159
159
  const builder = useMemo(() => new MessageBuilder(space), [space]);
160
160
 
161
161
  useEffect(() => {
@@ -165,7 +165,7 @@ export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
165
165
 
166
166
  const i = setInterval(() => {
167
167
  void builder.createMessage(Math.ceil(Math.random() * 3)).then(async (message) => {
168
- await queue.append([Obj.make(DataType.Message, message)]);
168
+ await queue.append([Obj.make(Message.Message, message)]);
169
169
  });
170
170
  }, interval);
171
171
  return () => clearInterval(i);
@@ -186,7 +186,7 @@ export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscription
186
186
  ) => {
187
187
  // TODO(dmaretskyi): Use space.queues.create() instead.
188
188
  const queueDxn = useMemo(() => (space ? createQueueDXN(space.id, queueId) : undefined), [space, queueId]);
189
- const queue = useQueue<DataType.Message>(queueDxn);
189
+ const queue = useQueue<Message.Message>(queueDxn);
190
190
  const [builder] = useState(() => new EntityExtractionMessageBuilder());
191
191
 
192
192
  useEffect(() => {
@@ -203,7 +203,7 @@ export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscription
203
203
  ctx,
204
204
  async () => {
205
205
  const message = await builder.createMessage();
206
- void queue.append([Obj.make(DataType.Message, message)]);
206
+ void queue.append([Obj.make(Message.Message, message)]);
207
207
  },
208
208
  interval,
209
209
  );
@@ -10,7 +10,7 @@ import { invariant } from '@dxos/invariant';
10
10
  import { log } from '@dxos/log';
11
11
  import { trace } from '@dxos/tracing';
12
12
 
13
- import { type WavConfig, type AudioChunk, type AudioRecorder } from './audio-recorder';
13
+ import { type AudioChunk, type AudioRecorder, type WavConfig } from './audio-recorder';
14
14
 
15
15
  let initializingPromise: Promise<void> | undefined;
16
16
 
@@ -19,7 +19,7 @@ const initializeExtendableMediaRecorder = async () => {
19
19
  await register(await connect());
20
20
  };
21
21
 
22
- export type MediaStreamRecorderParams = {
22
+ export type MediaStreamRecorderProps = {
23
23
  mediaStreamTrack: MediaStreamTrack;
24
24
  config: {
25
25
  interval: number;
@@ -37,12 +37,12 @@ export class MediaStreamRecorder implements AudioRecorder {
37
37
  /**
38
38
  * Default MediaRecorder implementation do not support wav encoding.
39
39
  */
40
- private readonly _config: MediaStreamRecorderParams['config'];
40
+ private readonly _config: MediaStreamRecorderProps['config'];
41
41
  private _mediaRecorder?: IMediaRecorder = undefined;
42
42
  private _header?: Uint8Array = undefined;
43
43
  private _onChunk?: (chunk: AudioChunk) => void;
44
44
 
45
- constructor({ mediaStreamTrack, config }: MediaStreamRecorderParams) {
45
+ constructor({ mediaStreamTrack, config }: MediaStreamRecorderProps) {
46
46
  this._mediaStreamTrack = mediaStreamTrack;
47
47
  this._config = config;
48
48
  }
@@ -9,9 +9,9 @@ import { WaveFile } from 'wavefile';
9
9
 
10
10
  import { Trigger } from '@dxos/async';
11
11
  import { log } from '@dxos/log';
12
- import { type DataType } from '@dxos/schema';
13
12
  import { openAndClose } from '@dxos/test-utils';
14
- import { trace, TRACE_PROCESSOR } from '@dxos/tracing';
13
+ import { TRACE_PROCESSOR, trace } from '@dxos/tracing';
14
+ import { type ContentBlock } from '@dxos/types';
15
15
 
16
16
  import { type AudioChunk, type AudioRecorder, Transcriber } from '../transcriber';
17
17
  import { mergeFloat64Arrays } from '../util';
@@ -93,7 +93,7 @@ describe.skip('Transcriber', () => {
93
93
  });
94
94
 
95
95
  test.skip('transcription of audio recording', { timeout: 10_000 }, async () => {
96
- const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
96
+ const trigger = new Trigger<ContentBlock.Transcript[]>({ autoReset: true });
97
97
  const recorder = new MockAudioRecorder({
98
98
  buffer: await readFile('test.wav'),
99
99
  chunkDuration: 3_000,
@@ -127,7 +127,7 @@ describe.skip('Transcriber', () => {
127
127
  });
128
128
 
129
129
  test.skip('transcription of audio recording with overlapping chunks', { timeout: 20_000 }, async () => {
130
- const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
130
+ const trigger = new Trigger<ContentBlock.Transcript[]>({ autoReset: true });
131
131
  const recorder = new MockAudioRecorder({
132
132
  buffer: await readFile('test.wav'),
133
133
  chunkDuration: 3_000,
@@ -212,7 +212,7 @@ class MockAudioRecorder implements AudioRecorder {
212
212
 
213
213
  @trace.span()
214
214
  async start(): Promise<void> {
215
- log.info('start mock audio recorder', { wavParams: this.wav.fmt });
215
+ log.info('start mock audio recorder', { wavProps: this.wav.fmt });
216
216
  const now = Date.now();
217
217
  const samples = this.wav.getSamples();
218
218
  const sampleRate = this.wavConfig.sampleRate;
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { type AudioChunk } from './audio-recorder';
8
+ import { alignWhisperSegments, type WhisperSegment } from './transcriber';
9
+
10
+ const T0 = Date.parse('2026-01-01T00:00:00.000Z');
11
+
12
+ const chunk = (offsetMs = 0, samples = 16): AudioChunk => ({
13
+ timestamp: T0 + offsetMs,
14
+ data: new Float64Array(samples),
15
+ });
16
+
17
+ const segment = (overrides: Partial<WhisperSegment>): WhisperSegment => ({
18
+ text: '',
19
+ start: 0,
20
+ end: 0,
21
+ no_speech_prob: 0,
22
+ words: [],
23
+ ...overrides,
24
+ });
25
+
26
+ describe('alignWhisperSegments', () => {
27
+ test('returns empty when no chunks were transcribed', ({ expect }) => {
28
+ const result = alignWhisperSegments(
29
+ [segment({ text: 'x', end: 1, words: [{ word: 'x', start: 0, end: 1 }] })],
30
+ [],
31
+ 0,
32
+ );
33
+ expect(result.transcripts).toEqual([]);
34
+ expect(result.lastUsedTimestamp).toBeUndefined();
35
+ });
36
+
37
+ test('absolute-timestamps and deduplicates segments against lastUsedTimestamp', ({ expect }) => {
38
+ const segments: WhisperSegment[] = [
39
+ segment({
40
+ text: 'hello world',
41
+ start: 0,
42
+ end: 1,
43
+ words: [
44
+ { word: 'hello ', start: 0, end: 0.5 },
45
+ { word: 'world', start: 0.5, end: 1 },
46
+ ],
47
+ }),
48
+ ];
49
+
50
+ const first = alignWhisperSegments(segments, [chunk(0)], 0);
51
+ expect(first.transcripts).toHaveLength(1);
52
+ expect(first.transcripts[0]._tag).toBe('transcript');
53
+ expect(first.transcripts[0].text).toBe('hello world');
54
+ expect(new Date(first.transcripts[0].started).getTime()).toBe(T0);
55
+ expect(first.lastUsedTimestamp).toBe(T0 + 1_000);
56
+
57
+ // Same segments, same chunk start: every word's absolute start is < lastUsedTimestamp -> filtered out.
58
+ const second = alignWhisperSegments(segments, [chunk(0)], first.lastUsedTimestamp!);
59
+ expect(second.transcripts).toEqual([]);
60
+ expect(second.lastUsedTimestamp).toBeUndefined();
61
+ });
62
+
63
+ test('keeps later words when earlier words fall before lastUsedTimestamp', ({ expect }) => {
64
+ const segments: WhisperSegment[] = [
65
+ segment({
66
+ text: 'old new',
67
+ start: 0,
68
+ end: 2,
69
+ words: [
70
+ { word: 'old ', start: 0, end: 1 },
71
+ { word: 'new', start: 1.5, end: 2 },
72
+ ],
73
+ }),
74
+ ];
75
+
76
+ // First word starts at T0, second at T0+1500 — only the second survives a cursor at T0+1000.
77
+ const result = alignWhisperSegments(segments, [chunk(0)], T0 + 1_000);
78
+ expect(result.transcripts).toHaveLength(1);
79
+ expect(result.transcripts[0].text).toBe('new');
80
+ expect(new Date(result.transcripts[0].started).getTime()).toBe(T0 + 1_500);
81
+ expect(result.lastUsedTimestamp).toBe(T0 + 2_000);
82
+ });
83
+
84
+ test('drops segments that end before lastUsedTimestamp', ({ expect }) => {
85
+ const segments: WhisperSegment[] = [
86
+ segment({
87
+ text: 'stale',
88
+ start: 0,
89
+ end: 0.5,
90
+ words: [{ word: 'stale', start: 0, end: 0.5 }],
91
+ }),
92
+ segment({
93
+ text: 'fresh',
94
+ start: 1,
95
+ end: 2,
96
+ words: [{ word: 'fresh', start: 1.5, end: 2 }],
97
+ }),
98
+ ];
99
+
100
+ const result = alignWhisperSegments(segments, [chunk(0)], T0 + 1_000);
101
+ expect(result.transcripts).toHaveLength(1);
102
+ expect(result.transcripts[0].text).toBe('fresh');
103
+ });
104
+ });
@@ -7,14 +7,15 @@ import { WaveFile } from 'wavefile';
7
7
  import { DeferredTask, Trigger } from '@dxos/async';
8
8
  import { type Context, LifecycleState, Resource } from '@dxos/context';
9
9
  import { log } from '@dxos/log';
10
- import { type DataType } from '@dxos/schema';
11
10
  import { trace } from '@dxos/tracing';
11
+ import { type ContentBlock } from '@dxos/types';
12
+
13
+ import { TRANSCRIPTION_URL } from '#types';
12
14
 
13
- import { type AudioRecorder, type AudioChunk } from './audio-recorder';
14
- import { TRANSCRIPTION_URL } from '../types';
15
15
  import { mergeFloat64Arrays } from '../util';
16
+ import { type AudioChunk, type AudioRecorder } from './audio-recorder';
16
17
 
17
- type WhisperWord = {
18
+ export type WhisperWord = {
18
19
  word: string;
19
20
 
20
21
  /**
@@ -28,7 +29,7 @@ type WhisperWord = {
28
29
  end: number;
29
30
  };
30
31
 
31
- type WhisperSegment = {
32
+ export type WhisperSegment = {
32
33
  text: string;
33
34
 
34
35
  /**
@@ -60,16 +61,32 @@ export type TranscribeConfig = {
60
61
  * This is needed to catch the beginning of the user's speech.
61
62
  */
62
63
  prefixBufferChunksAmount: number;
64
+
65
+ /**
66
+ * Override the transcription endpoint base URL.
67
+ * Defaults to the DXOS calls service.
68
+ */
69
+ endpoint?: string;
63
70
  };
64
71
 
65
- export type TranscriberParams = {
72
+ /**
73
+ * Function that converts a base64-encoded WAV payload into Whisper segments.
74
+ * Allows callers to swap in alternative providers or mock the transport.
75
+ */
76
+ export type TranscribeFn = (audio: string) => Promise<WhisperSegment[]>;
77
+
78
+ export type TranscriberProps = {
66
79
  config: TranscribeConfig;
67
80
  recorder: AudioRecorder;
68
81
  /**
69
82
  * Callback to handle the transcribed segments, after all segment transformers are applied.
70
83
  * @param segments - The transcribed segments.
71
84
  */
72
- onSegments: (segments: DataType.MessageBlock.Transcription[]) => Promise<void>;
85
+ onSegments: (segments: ContentBlock.Transcript[]) => Promise<void>;
86
+ /**
87
+ * Optional override of the transcription transport. When provided, supersedes `config.endpoint`.
88
+ */
89
+ transcribe?: TranscribeFn;
73
90
  };
74
91
 
75
92
  /**
@@ -85,16 +102,18 @@ export class Transcriber extends Resource {
85
102
 
86
103
  private readonly _config: TranscribeConfig;
87
104
  private readonly _recorder: AudioRecorder;
88
- private readonly _onSegments: TranscriberParams['onSegments'];
105
+ private readonly _onSegments: TranscriberProps['onSegments'];
106
+ private readonly _transcribeFn?: TranscribeFn;
89
107
 
90
108
  private _recording = false;
91
109
  private _transcribeTask?: DeferredTask = undefined;
92
110
 
93
- constructor({ config, recorder, onSegments }: TranscriberParams) {
111
+ constructor({ config, recorder, onSegments, transcribe }: TranscriberProps) {
94
112
  super();
95
113
  this._config = config;
96
114
  this._recorder = recorder;
97
115
  this._onSegments = onSegments;
116
+ this._transcribeFn = transcribe;
98
117
  }
99
118
 
100
119
  protected override async _open(ctx: Context): Promise<void> {
@@ -191,70 +210,90 @@ export class Transcriber extends Resource {
191
210
  @trace.span({ showInBrowserTimeline: true })
192
211
  private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {
193
212
  if (audio.length === 0) {
194
- this._audioChunks = [];
195
213
  throw new Error('No audio to send for transcribing');
196
214
  }
197
215
 
198
- const response = await fetch(`${TRANSCRIPTION_URL}/transcribe`, {
199
- method: 'POST',
200
- body: JSON.stringify({ audio }),
201
- headers: {
202
- 'Content-Type': 'application/json',
203
- },
204
- });
205
-
206
- if (!response.ok) {
207
- this._audioChunks = [];
208
- throw new Error(`Transcription failed: ${response.statusText}`);
216
+ let segments: unknown;
217
+ if (this._transcribeFn) {
218
+ segments = await this._transcribeFn(audio);
219
+ } else {
220
+ const endpoint = this._config.endpoint ?? TRANSCRIPTION_URL;
221
+ const response = await fetch(`${endpoint}/transcribe`, {
222
+ method: 'POST',
223
+ body: JSON.stringify({ audio }),
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ },
227
+ });
228
+
229
+ if (!response.ok) {
230
+ throw new Error(`Transcription failed: ${response.statusText}`);
231
+ }
232
+
233
+ ({ segments } = (await response.json()) as { segments?: unknown });
209
234
  }
210
235
 
211
- const { segments } = (await response.json()) as {
212
- segments: WhisperSegment[];
213
- };
236
+ if (!Array.isArray(segments)) {
237
+ throw new Error('Transcription response payload is invalid');
238
+ }
214
239
 
215
- log.info('transcription response', {
216
- segments: segments.length,
217
- string: segments.map((segments) => segments.text).join(' '),
218
- });
240
+ log.info('transcription response', { segments: segments.length });
219
241
 
220
- return segments;
242
+ return segments as WhisperSegment[];
221
243
  }
222
244
 
223
- private _alignSegments(
224
- segments: WhisperSegment[],
225
- originalChunks: AudioChunk[],
226
- ): DataType.MessageBlock.Transcription[] {
227
- // Absolute zero for all relative timestamps in the segments.
228
- const zeroTimestamp = originalChunks.at(0)!.timestamp;
229
-
230
- const filteredSegments = segments
231
- // Use segments that end after the last used timestamp.
232
- // Segments could overlap, so we need to filter words that starts after the last used timestamp.
233
- .filter((segment) => zeroTimestamp + segment.end * 1_000 >= this._lastUsedTimestamp)
234
- // Use words that starts after the last used timestamp.
235
- .map((segment) => {
236
- const words = segment.words.filter((word) => zeroTimestamp + word.start * 1_000 >= this._lastUsedTimestamp);
237
- return {
238
- ...segment,
239
- words,
240
- text: words.map((word) => word.word).join(''),
241
- start: words.at(0)?.start ?? segment.end,
242
- };
243
- })
244
- .filter((segment) => segment.words.length > 0);
245
-
246
- if (filteredSegments.length === 0) {
247
- return [];
245
+ private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): ContentBlock.Transcript[] {
246
+ const result = alignWhisperSegments(segments, originalChunks, this._lastUsedTimestamp);
247
+ if (result.lastUsedTimestamp !== undefined) {
248
+ this._lastUsedTimestamp = result.lastUsedTimestamp;
248
249
  }
250
+ return result.transcripts;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Pure helper that filters and absolute-timestamps Whisper segments against a chunk timeline.
256
+ * Exported for testing.
257
+ *
258
+ * @param segments - Raw segments returned by the Whisper service for `originalChunks`.
259
+ * @param originalChunks - Chunks fed into the transcription request (used for the zero timestamp).
260
+ * @param lastUsedTimestamp - Most recent absolute timestamp already emitted; segments whose end
261
+ * falls strictly before this point are deduped out, as are individual words whose start does.
262
+ */
263
+ export const alignWhisperSegments = (
264
+ segments: WhisperSegment[],
265
+ originalChunks: AudioChunk[],
266
+ lastUsedTimestamp: number,
267
+ ): { transcripts: ContentBlock.Transcript[]; lastUsedTimestamp?: number } => {
268
+ if (originalChunks.length === 0) {
269
+ return { transcripts: [] };
270
+ }
249
271
 
250
- // Update last timestamp.
251
- this._lastUsedTimestamp = zeroTimestamp + filteredSegments.at(-1)!.end * 1_000;
272
+ const zeroTimestamp = originalChunks[0].timestamp;
273
+
274
+ const filteredSegments = segments
275
+ .filter((segment) => zeroTimestamp + segment.end * 1_000 >= lastUsedTimestamp)
276
+ .map((segment) => {
277
+ const words = segment.words.filter((word) => zeroTimestamp + word.start * 1_000 >= lastUsedTimestamp);
278
+ return {
279
+ ...segment,
280
+ words,
281
+ text: words.map((word) => word.word).join(''),
282
+ start: words.at(0)?.start ?? segment.end,
283
+ };
284
+ })
285
+ .filter((segment) => segment.words.length > 0);
286
+
287
+ if (filteredSegments.length === 0) {
288
+ return { transcripts: [] };
289
+ }
252
290
 
253
- // Add absolute timestamp to each segment.
254
- return filteredSegments.map((segment) => ({
255
- type: 'transcription',
291
+ return {
292
+ transcripts: filteredSegments.map((segment) => ({
293
+ _tag: 'transcript',
256
294
  started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
257
295
  text: segment.text.trim(),
258
- }));
259
- }
260
- }
296
+ })),
297
+ lastUsedTimestamp: zeroTimestamp + filteredSegments.at(-1)!.end * 1_000,
298
+ };
299
+ };