@dxos/plugin-transcription 0.8.4-main.f9ba587 → 0.8.4-main.fcc0d83b33

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 (284) 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/create-ULR5DPJZ.mjs +21 -0
  11. package/dist/lib/browser/create-ULR5DPJZ.mjs.map +7 -0
  12. package/dist/lib/browser/index.mjs +1085 -98
  13. package/dist/lib/browser/index.mjs.map +4 -4
  14. package/dist/lib/browser/meta.json +1 -1
  15. package/dist/lib/browser/open-SFMPLZVW.mjs +27 -0
  16. package/dist/lib/browser/open-SFMPLZVW.mjs.map +7 -0
  17. package/dist/lib/browser/operations/index.mjs +13 -0
  18. package/dist/lib/browser/operations/index.mjs.map +7 -0
  19. package/dist/lib/browser/summarize-I3POXUK3.mjs +79 -0
  20. package/dist/lib/browser/summarize-I3POXUK3.mjs.map +7 -0
  21. package/dist/lib/browser/translations.mjs +22 -0
  22. package/dist/lib/browser/translations.mjs.map +7 -0
  23. package/dist/lib/browser/types/index.mjs +4 -8
  24. package/dist/lib/node-esm/blueprints/index.mjs +32 -0
  25. package/dist/lib/node-esm/blueprints/index.mjs.map +7 -0
  26. package/dist/lib/node-esm/chunk-7UIZWCD4.mjs +112 -0
  27. package/dist/lib/node-esm/chunk-7UIZWCD4.mjs.map +7 -0
  28. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  29. package/dist/lib/node-esm/chunk-KO3IOD2O.mjs +19 -0
  30. package/dist/lib/node-esm/chunk-KO3IOD2O.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-O533NBMK.mjs +37 -0
  32. package/dist/lib/node-esm/chunk-O533NBMK.mjs.map +7 -0
  33. package/dist/lib/node-esm/create-A5QHNWRF.mjs +22 -0
  34. package/dist/lib/node-esm/create-A5QHNWRF.mjs.map +7 -0
  35. package/dist/lib/node-esm/index.mjs +1085 -98
  36. package/dist/lib/node-esm/index.mjs.map +4 -4
  37. package/dist/lib/node-esm/meta.json +1 -1
  38. package/dist/lib/node-esm/open-OI24QNTO.mjs +28 -0
  39. package/dist/lib/node-esm/open-OI24QNTO.mjs.map +7 -0
  40. package/dist/lib/node-esm/operations/index.mjs +14 -0
  41. package/dist/lib/node-esm/operations/index.mjs.map +7 -0
  42. package/dist/lib/node-esm/summarize-F3LLJ45P.mjs +80 -0
  43. package/dist/lib/node-esm/summarize-F3LLJ45P.mjs.map +7 -0
  44. package/dist/lib/node-esm/translations.mjs +23 -0
  45. package/dist/lib/node-esm/translations.mjs.map +7 -0
  46. package/dist/lib/node-esm/types/index.mjs +4 -8
  47. package/dist/types/src/TranscriptionPlugin.d.ts +2 -1
  48. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  49. package/dist/types/src/blueprints/index.d.ts +2 -0
  50. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  51. package/dist/types/src/blueprints/transcription-blueprint.d.ts +4 -0
  52. package/dist/types/src/blueprints/transcription-blueprint.d.ts.map +1 -0
  53. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -0
  54. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
  55. package/dist/types/src/capabilities/index.d.ts +6 -4
  56. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  57. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  58. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  59. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  60. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  61. package/dist/types/src/capabilities/transcriber.d.ts +4 -3
  62. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  63. package/dist/types/src/components/Transcription/Transcription.d.ts +18 -0
  64. package/dist/types/src/components/Transcription/Transcription.d.ts.map +1 -0
  65. package/dist/types/src/components/Transcription/Transcription.stories.d.ts +40 -0
  66. package/dist/types/src/components/Transcription/Transcription.stories.d.ts.map +1 -0
  67. package/dist/types/src/components/Transcription/index.d.ts +2 -0
  68. package/dist/types/src/components/Transcription/index.d.ts.map +1 -0
  69. package/dist/types/src/components/Transcription/transcription-extension.d.ts +15 -0
  70. package/dist/types/src/components/Transcription/transcription-extension.d.ts.map +1 -0
  71. package/dist/types/src/components/index.d.ts +1 -3
  72. package/dist/types/src/components/index.d.ts.map +1 -1
  73. package/dist/types/src/containers/TranscriptionContainer/TranscriptionContainer.d.ts +6 -0
  74. package/dist/types/src/containers/TranscriptionContainer/TranscriptionContainer.d.ts.map +1 -0
  75. package/dist/types/src/containers/TranscriptionContainer/index.d.ts +2 -0
  76. package/dist/types/src/containers/TranscriptionContainer/index.d.ts.map +1 -0
  77. package/dist/types/src/containers/index.d.ts +3 -0
  78. package/dist/types/src/containers/index.d.ts.map +1 -0
  79. package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
  80. package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
  81. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +1 -1
  82. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -1
  83. package/dist/types/src/hooks/useTranscriber.d.ts +2 -2
  84. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  85. package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
  86. package/dist/types/src/index.d.ts +1 -1
  87. package/dist/types/src/index.d.ts.map +1 -1
  88. package/dist/types/src/meta.d.ts +2 -3
  89. package/dist/types/src/meta.d.ts.map +1 -1
  90. package/dist/types/src/model/model.d.ts +2 -2
  91. package/dist/types/src/model/model.d.ts.map +1 -1
  92. package/dist/types/src/{segments-normalization → normalization}/index.d.ts +1 -1
  93. package/dist/types/src/normalization/index.d.ts.map +1 -0
  94. package/dist/types/src/{segments-normalization → normalization}/message-normalizer.d.ts +5 -5
  95. package/dist/types/src/normalization/message-normalizer.d.ts.map +1 -0
  96. package/dist/types/src/normalization/normalization.d.ts +364 -0
  97. package/dist/types/src/normalization/normalization.d.ts.map +1 -0
  98. package/dist/types/src/normalization/normalization.test.d.ts.map +1 -0
  99. package/dist/types/src/normalization/utils.d.ts +3 -0
  100. package/dist/types/src/normalization/utils.d.ts.map +1 -0
  101. package/dist/types/src/operations/create.d.ts +5 -0
  102. package/dist/types/src/operations/create.d.ts.map +1 -0
  103. package/dist/types/src/operations/definitions.d.ts +917 -0
  104. package/dist/types/src/operations/definitions.d.ts.map +1 -0
  105. package/dist/types/src/operations/index.d.ts +4 -0
  106. package/dist/types/src/operations/index.d.ts.map +1 -0
  107. package/dist/types/src/operations/open.d.ts +5 -0
  108. package/dist/types/src/operations/open.d.ts.map +1 -0
  109. package/dist/types/src/operations/summarize.d.ts +8 -0
  110. package/dist/types/src/operations/summarize.d.ts.map +1 -0
  111. package/dist/types/src/stories/FileTranscription.stories.d.ts +18 -0
  112. package/dist/types/src/stories/FileTranscription.stories.d.ts.map +1 -0
  113. package/dist/types/src/stories/KeyWordRecognition.stories.d.ts +14 -0
  114. package/dist/types/src/stories/KeyWordRecognition.stories.d.ts.map +1 -0
  115. package/dist/types/src/stories/MicrophoneTranscription.stories.d.ts +20 -0
  116. package/dist/types/src/stories/MicrophoneTranscription.stories.d.ts.map +1 -0
  117. package/dist/types/src/stories/TranscriptionStory.d.ts +23 -0
  118. package/dist/types/src/stories/TranscriptionStory.d.ts.map +1 -0
  119. package/dist/types/src/stories/useIsSpeaking.d.ts.map +1 -0
  120. package/dist/types/src/testing/testing.d.ts +10 -10
  121. package/dist/types/src/testing/testing.d.ts.map +1 -1
  122. package/dist/types/src/transcriber/media-stream-recorder.d.ts +3 -3
  123. package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
  124. package/dist/types/src/transcriber/transcriber.d.ts +60 -5
  125. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  126. package/dist/types/src/transcriber/transcriber.test.d.ts +2 -0
  127. package/dist/types/src/transcriber/transcriber.test.d.ts.map +1 -0
  128. package/dist/types/src/transcriber/transcription-manager.d.ts +12 -13
  129. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  130. package/dist/types/src/translations.d.ts +7 -8
  131. package/dist/types/src/translations.d.ts.map +1 -1
  132. package/dist/types/src/types/capabilities.d.ts +19 -0
  133. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  134. package/dist/types/src/types/defs.d.ts +5 -0
  135. package/dist/types/src/types/defs.d.ts.map +1 -0
  136. package/dist/types/src/types/index.d.ts +2 -2
  137. package/dist/types/src/types/index.d.ts.map +1 -1
  138. package/dist/types/src/util/array.d.ts.map +1 -1
  139. package/dist/types/src/util/get-time-str.d.ts.map +1 -1
  140. package/dist/types/src/util/index.d.ts +1 -0
  141. package/dist/types/src/util/index.d.ts.map +1 -1
  142. package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
  143. package/dist/types/src/util/render-byline.d.ts +4 -0
  144. package/dist/types/src/util/render-byline.d.ts.map +1 -0
  145. package/dist/types/tsconfig.tsbuildinfo +1 -1
  146. package/package.json +108 -80
  147. package/src/TranscriptionPlugin.tsx +47 -60
  148. package/src/blueprints/index.ts +5 -0
  149. package/src/blueprints/transcription-blueprint.ts +29 -0
  150. package/src/capabilities/blueprint-definition.ts +17 -0
  151. package/src/capabilities/index.ts +9 -6
  152. package/src/capabilities/operation-handler.ts +16 -0
  153. package/src/capabilities/react-surface.tsx +23 -14
  154. package/src/capabilities/transcriber.ts +54 -42
  155. package/src/components/{Transcript/Transcript.stories.tsx → Transcription/Transcription.stories.tsx} +77 -72
  156. package/src/components/Transcription/Transcription.tsx +64 -0
  157. package/src/components/{Transcript → Transcription}/index.ts +1 -1
  158. package/src/components/{Transcript/transcript-extension.ts → Transcription/transcription-extension.ts} +15 -48
  159. package/src/components/index.ts +1 -6
  160. package/src/containers/TranscriptionContainer/TranscriptionContainer.tsx +35 -0
  161. package/src/containers/TranscriptionContainer/index.ts +5 -0
  162. package/src/containers/index.ts +7 -0
  163. package/src/hooks/useAudioFile.ts +49 -53
  164. package/src/hooks/useQueueModelAdapter.ts +1 -1
  165. package/src/hooks/useTranscriber.ts +12 -4
  166. package/src/hooks/useVoiceInput.ts +3 -3
  167. package/src/index.ts +1 -1
  168. package/src/meta.ts +9 -6
  169. package/src/model/model.test.ts +27 -21
  170. package/src/model/model.ts +3 -3
  171. package/src/{segments-normalization → normalization}/message-normalizer.ts +20 -19
  172. package/src/{segments-normalization → normalization}/normalization.test.ts +26 -33
  173. package/src/normalization/normalization.ts +77 -0
  174. package/src/{segments-normalization → normalization}/utils.ts +2 -2
  175. package/src/operations/create.ts +24 -0
  176. package/src/operations/definitions.ts +101 -0
  177. package/src/operations/index.ts +13 -0
  178. package/src/operations/open.ts +28 -0
  179. package/src/operations/summarize.ts +102 -0
  180. package/src/{components/stories → stories}/FileTranscription.stories.tsx +98 -71
  181. package/src/{components/stories → stories}/KeyWordRecognition.stories.tsx +46 -40
  182. package/src/{components/stories → stories}/MicrophoneTranscription.stories.tsx +84 -100
  183. package/src/stories/TranscriptionStory.tsx +93 -0
  184. package/src/testing/testing.ts +41 -41
  185. package/src/transcriber/media-stream-recorder.ts +4 -4
  186. package/src/transcriber/transcriber.browser.test.ts +5 -5
  187. package/src/transcriber/transcriber.test.ts +104 -0
  188. package/src/transcriber/transcriber.ts +101 -62
  189. package/src/transcriber/transcription-manager.ts +37 -52
  190. package/src/translations.ts +7 -7
  191. package/src/types/capabilities.ts +39 -0
  192. package/src/types/defs.ts +9 -0
  193. package/src/types/index.ts +2 -2
  194. package/src/util/index.ts +1 -0
  195. package/src/util/render-byline.ts +33 -0
  196. package/dist/lib/browser/TranscriptContainer-E5NBJPAY.mjs +0 -11
  197. package/dist/lib/browser/chunk-5WH45KBY.mjs +0 -19
  198. package/dist/lib/browser/chunk-5WH45KBY.mjs.map +0 -7
  199. package/dist/lib/browser/chunk-EQBICGOD.mjs +0 -8
  200. package/dist/lib/browser/chunk-EQBICGOD.mjs.map +0 -7
  201. package/dist/lib/browser/chunk-F2WY3YTD.mjs +0 -764
  202. package/dist/lib/browser/chunk-F2WY3YTD.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-R7INNNUQ.mjs +0 -55
  204. package/dist/lib/browser/chunk-R7INNNUQ.mjs.map +0 -7
  205. package/dist/lib/browser/chunk-U6QBOGTN.mjs +0 -16
  206. package/dist/lib/browser/chunk-U6QBOGTN.mjs.map +0 -7
  207. package/dist/lib/browser/chunk-VDKDOZRR.mjs +0 -495
  208. package/dist/lib/browser/chunk-VDKDOZRR.mjs.map +0 -7
  209. package/dist/lib/browser/intent-resolver-3O33MMP5.mjs +0 -30
  210. package/dist/lib/browser/intent-resolver-3O33MMP5.mjs.map +0 -7
  211. package/dist/lib/browser/react-surface-FJTMNF3K.mjs +0 -34
  212. package/dist/lib/browser/react-surface-FJTMNF3K.mjs.map +0 -7
  213. package/dist/lib/browser/transcriber-YVOTUBKQ.mjs +0 -56
  214. package/dist/lib/browser/transcriber-YVOTUBKQ.mjs.map +0 -7
  215. package/dist/lib/node-esm/TranscriptContainer-UOS7JG72.mjs +0 -12
  216. package/dist/lib/node-esm/chunk-DN522SAP.mjs +0 -10
  217. package/dist/lib/node-esm/chunk-DN522SAP.mjs.map +0 -7
  218. package/dist/lib/node-esm/chunk-HSCG4XPR.mjs +0 -496
  219. package/dist/lib/node-esm/chunk-HSCG4XPR.mjs.map +0 -7
  220. package/dist/lib/node-esm/chunk-Q3JKLPTW.mjs +0 -765
  221. package/dist/lib/node-esm/chunk-Q3JKLPTW.mjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-RHF2ALML.mjs +0 -17
  223. package/dist/lib/node-esm/chunk-RHF2ALML.mjs.map +0 -7
  224. package/dist/lib/node-esm/chunk-U65ZWK3L.mjs +0 -56
  225. package/dist/lib/node-esm/chunk-U65ZWK3L.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-XMUCZBP3.mjs +0 -21
  227. package/dist/lib/node-esm/chunk-XMUCZBP3.mjs.map +0 -7
  228. package/dist/lib/node-esm/intent-resolver-YMN5SXNC.mjs +0 -31
  229. package/dist/lib/node-esm/intent-resolver-YMN5SXNC.mjs.map +0 -7
  230. package/dist/lib/node-esm/react-surface-57TSIDR2.mjs +0 -35
  231. package/dist/lib/node-esm/react-surface-57TSIDR2.mjs.map +0 -7
  232. package/dist/lib/node-esm/transcriber-GPBV52ZR.mjs +0 -57
  233. package/dist/lib/node-esm/transcriber-GPBV52ZR.mjs.map +0 -7
  234. package/dist/types/src/capabilities/capabilities.d.ts +0 -17
  235. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  236. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  237. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  238. package/dist/types/src/components/Transcript/Transcript.d.ts +0 -20
  239. package/dist/types/src/components/Transcript/Transcript.d.ts.map +0 -1
  240. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +0 -24
  241. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +0 -1
  242. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +0 -28
  243. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +0 -1
  244. package/dist/types/src/components/Transcript/index.d.ts +0 -2
  245. package/dist/types/src/components/Transcript/index.d.ts.map +0 -1
  246. package/dist/types/src/components/Transcript/transcript-extension.d.ts +0 -19
  247. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +0 -1
  248. package/dist/types/src/components/TranscriptContainer.d.ts +0 -9
  249. package/dist/types/src/components/TranscriptContainer.d.ts.map +0 -1
  250. package/dist/types/src/components/stories/FileTranscription.stories.d.ts +0 -17
  251. package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +0 -1
  252. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +0 -10
  253. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +0 -1
  254. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts +0 -19
  255. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +0 -1
  256. package/dist/types/src/components/stories/TranscriptionStory.d.ts +0 -11
  257. package/dist/types/src/components/stories/TranscriptionStory.d.ts.map +0 -1
  258. package/dist/types/src/components/stories/useIsSpeaking.d.ts.map +0 -1
  259. package/dist/types/src/segments-normalization/index.d.ts.map +0 -1
  260. package/dist/types/src/segments-normalization/message-normalizer.d.ts.map +0 -1
  261. package/dist/types/src/segments-normalization/normalization.d.ts +0 -329
  262. package/dist/types/src/segments-normalization/normalization.d.ts.map +0 -1
  263. package/dist/types/src/segments-normalization/normalization.test.d.ts.map +0 -1
  264. package/dist/types/src/segments-normalization/utils.d.ts +0 -3
  265. package/dist/types/src/segments-normalization/utils.d.ts.map +0 -1
  266. package/dist/types/src/types/schema.d.ts +0 -41
  267. package/dist/types/src/types/schema.d.ts.map +0 -1
  268. package/dist/types/src/types/types.d.ts +0 -31
  269. package/dist/types/src/types/types.d.ts.map +0 -1
  270. package/src/capabilities/capabilities.ts +0 -33
  271. package/src/capabilities/intent-resolver.ts +0 -24
  272. package/src/components/Transcript/Transcript.tsx +0 -100
  273. package/src/components/Transcript/deprecated/OldTranscript.tsx +0 -260
  274. package/src/components/TranscriptContainer.tsx +0 -35
  275. package/src/components/stories/TranscriptionStory.tsx +0 -41
  276. package/src/segments-normalization/normalization.ts +0 -106
  277. package/src/types/schema.ts +0 -41
  278. package/src/types/types.ts +0 -32
  279. package/dist/lib/browser/{TranscriptContainer-E5NBJPAY.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
  280. package/dist/lib/node-esm/{TranscriptContainer-UOS7JG72.mjs.map → chunk-HSLMI22Q.mjs.map} +0 -0
  281. package/dist/types/src/{segments-normalization → normalization}/normalization.test.d.ts +0 -0
  282. package/dist/types/src/{components/stories → stories}/useIsSpeaking.d.ts +0 -0
  283. package/src/{segments-normalization → normalization}/index.ts +1 -1
  284. /package/src/{components/stories → stories}/useIsSpeaking.tsx +0 -0
@@ -1,121 +1,1108 @@
1
- import {
2
- TranscriptContainer
3
- } from "./chunk-EQBICGOD.mjs";
4
- import {
5
- IntentResolver,
6
- ReactSurface,
7
- Transcriber as Transcriber2,
8
- Transcript,
9
- TranscriptionContainer,
10
- renderMarkdown,
11
- useAudioFile,
12
- useAudioTrack,
13
- useQueueModelAdapter,
14
- useTranscriber,
15
- useVoiceInput
16
- } from "./chunk-F2WY3YTD.mjs";
17
- import {
18
- MediaStreamRecorder,
19
- Transcriber,
20
- TranscriptionManager
21
- } from "./chunk-VDKDOZRR.mjs";
22
- import {
23
- TranscriptType
24
- } from "./chunk-R7INNNUQ.mjs";
25
1
  import {
26
2
  TranscriptionCapabilities
27
- } from "./chunk-U6QBOGTN.mjs";
3
+ } from "./chunk-3L623EVL.mjs";
28
4
  import {
29
- TRANSCRIPTION_PLUGIN,
30
- meta
31
- } from "./chunk-5WH45KBY.mjs";
5
+ mergeFloat64Arrays,
6
+ renderByline
7
+ } from "./chunk-KAEXRX3Y.mjs";
8
+ import "./chunk-J5LGTIGS.mjs";
32
9
 
33
- // src/TranscriptionPlugin.tsx
34
- import { Capabilities, Events, contributes, defineModule, definePlugin } from "@dxos/app-framework";
35
- import { Obj } from "@dxos/echo";
36
- import { ClientCapabilities, ClientEvents } from "@dxos/plugin-client";
37
- import { getSpace } from "@dxos/react-client/echo";
38
- import { DataType } from "@dxos/schema";
10
+ // src/components/Transcription/Transcription.tsx
11
+ import React from "react";
12
+ import { useThemeContext } from "@dxos/react-ui";
13
+ import { useTextEditor } from "@dxos/react-ui-editor";
14
+ import { autoScroll, createBasicExtensions, createMarkdownExtensions, createThemeExtensions, decorateMarkdown, documentSlots, preview } from "@dxos/ui-editor";
15
+ import { composable, composableProps } from "@dxos/ui-theme";
16
+ import { isTruthy } from "@dxos/util";
17
+
18
+ // src/components/Transcription/transcription-extension.ts
19
+ import { RangeSetBuilder } from "@codemirror/state";
20
+ import { EditorView, GutterMarker, ViewPlugin, gutter } from "@codemirror/view";
21
+ import { format } from "date-fns/format";
22
+ import { intervalToDuration } from "date-fns/intervalToDuration";
23
+ import { addEventListener, combine } from "@dxos/async";
39
24
 
40
- // src/translations.ts
41
- var translations = [
42
- {
43
- "en-US": {
44
- [meta.id]: {
45
- "plugin name": "Transcription",
46
- "transcript companion label": "Transcript",
47
- "delete button": "Delete",
48
- "bookmark button": "Bookmark",
49
- "scroll to end label": "Scroll to latest",
50
- "settings entity extraction label": "Entity extraction"
25
+ // src/model/model.ts
26
+ import { Text } from "@codemirror/state";
27
+ import { Event } from "@dxos/async";
28
+ import { invariant } from "@dxos/invariant";
29
+ import { log } from "@dxos/log";
30
+ var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/model/model.ts";
31
+ var DocumentAdapter = class {
32
+ _view;
33
+ constructor(_view) {
34
+ this._view = _view;
35
+ }
36
+ /** The document must always have at least one line. */
37
+ lineCount() {
38
+ return this._view.state.doc.lines - 1;
39
+ }
40
+ replaceLines(from, remove, insert) {
41
+ log("replaceLines", {
42
+ count: this._view.state.doc.lines,
43
+ from,
44
+ remove,
45
+ insert
46
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 19, S: this });
47
+ const numLines = this._view.state.doc.lines;
48
+ const posFrom = this._view.state.doc.line(from).from;
49
+ const posTo = remove === 0 ? posFrom : this._view.state.doc.line(Math.min(numLines, from + remove)).from;
50
+ let text = insert.join("\n");
51
+ if (posTo === this._view.state.doc.length) {
52
+ text += "\n";
53
+ }
54
+ this._view.dispatch({
55
+ changes: {
56
+ from: posFrom,
57
+ to: posTo,
58
+ insert: text
59
+ }
60
+ });
61
+ }
62
+ };
63
+ var SerializationModel = class {
64
+ _renderer;
65
+ /** Emits when the document is updated. */
66
+ update = new Event();
67
+ /** Ordered set of chunks. */
68
+ _chunks = [];
69
+ /** Map of chunk IDs to their current line counts. */
70
+ _chunkLineCounts = /* @__PURE__ */ new Map();
71
+ /** Map of line numbers to chunk IDs. */
72
+ _lineToChunk = /* @__PURE__ */ new Map();
73
+ /** Track chunk changes since last sync. */
74
+ _changes = [];
75
+ constructor(_renderer, initialChunks = []) {
76
+ this._renderer = _renderer;
77
+ initialChunks.forEach((chunk) => {
78
+ this.appendChunk(chunk);
79
+ });
80
+ }
81
+ toJSON() {
82
+ return {
83
+ chunks: this._chunks.length,
84
+ changes: this._changes.length
85
+ };
86
+ }
87
+ get doc() {
88
+ return Text.of([
89
+ ...this._chunks.flatMap((chunk, index) => this._renderer(chunk, index)),
90
+ ""
91
+ ]);
92
+ }
93
+ get chunks() {
94
+ return this._chunks;
95
+ }
96
+ /**
97
+ * Get the block ID at the given line number.
98
+ */
99
+ getChunkAtLine(line) {
100
+ return this._lineToChunk.get(line);
101
+ }
102
+ reset() {
103
+ this._chunks.length = 0;
104
+ this._chunkLineCounts.clear();
105
+ this._lineToChunk.clear();
106
+ this.update.emit();
107
+ return this;
108
+ }
109
+ appendChunk(chunk) {
110
+ const index = this._chunks.length;
111
+ this._chunks.push(chunk);
112
+ this._changes.push({
113
+ type: "append",
114
+ index,
115
+ chunk
116
+ });
117
+ this.update.emit();
118
+ return this;
119
+ }
120
+ updateChunk(chunk) {
121
+ const index = this._chunks.findIndex((c) => c.id === chunk.id);
122
+ invariant(index !== -1, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 102, S: this, A: ["index !== -1", ""] });
123
+ this._chunks[index] = chunk;
124
+ this._changes.push({
125
+ type: "update",
126
+ index,
127
+ chunk
128
+ });
129
+ this.update.emit();
130
+ return this;
131
+ }
132
+ deleteBlock(id) {
133
+ const index = this._chunks.findIndex((c) => c.id === id);
134
+ invariant(index !== -1, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 114, S: this, A: ["index !== -1", ""] });
135
+ this._chunks.splice(index, 1);
136
+ this._changes.push({
137
+ type: "delete",
138
+ index,
139
+ id
140
+ });
141
+ this.update.emit();
142
+ return this;
143
+ }
144
+ /**
145
+ * Syncs the tracked changes with the ChunkDocument.
146
+ */
147
+ sync(document2) {
148
+ log("sync", {
149
+ changes: this._changes
150
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 127, S: this });
151
+ for (const change of this._changes) {
152
+ switch (change.type) {
153
+ case "append": {
154
+ const lines = this._renderer(change.chunk, change.index);
155
+ const lastLine = document2.lineCount();
156
+ document2.replaceLines(lastLine + 1, 0, lines);
157
+ break;
158
+ }
159
+ case "update": {
160
+ let lineStart = 1;
161
+ for (let i = 0; i < change.index; i++) {
162
+ lineStart += this._chunkLineCounts.get(this._chunks[i].id);
163
+ }
164
+ const previousLineCount = this._chunkLineCounts.get(change.chunk.id);
165
+ const newLines = this._renderer(change.chunk, change.index);
166
+ document2.replaceLines(lineStart, previousLineCount, newLines);
167
+ break;
168
+ }
169
+ case "delete": {
170
+ let lineStart = 1;
171
+ for (let i = 0; i < change.index; i++) {
172
+ lineStart += this._chunkLineCounts.get(this._chunks[i].id);
173
+ }
174
+ const previousLineCount = this._chunkLineCounts.get(change.id);
175
+ document2.replaceLines(lineStart, previousLineCount, []);
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ this._lineToChunk.clear();
181
+ let currentLine = 1;
182
+ for (const [index, chunk] of this._chunks.entries()) {
183
+ const lineCount = this._renderer(chunk, index).length;
184
+ this._chunkLineCounts.set(chunk.id, lineCount);
185
+ this._lineToChunk.set(currentLine, chunk);
186
+ currentLine += lineCount;
187
+ }
188
+ const chunkIds = new Set(this._chunks.map((chunk) => chunk.id));
189
+ for (const id of this._chunkLineCounts.keys()) {
190
+ if (!chunkIds.has(id)) {
191
+ this._chunkLineCounts.delete(id);
51
192
  }
52
193
  }
194
+ this._changes.length = 0;
195
+ return this;
53
196
  }
54
- ];
197
+ };
55
198
 
56
- // src/TranscriptionPlugin.tsx
57
- var TranscriptionPlugin = () => definePlugin(meta, [
58
- defineModule({
59
- id: `${meta.id}/module/translations`,
60
- activatesOn: Events.SetupTranslations,
61
- activate: () => contributes(Capabilities.Translations, translations)
62
- }),
63
- defineModule({
64
- id: `${meta.id}/module/metadata`,
65
- activatesOn: Events.SetupMetadata,
66
- activate: (context) => contributes(Capabilities.Metadata, {
67
- id: TranscriptType.typename,
68
- metadata: {
69
- icon: "ph--subtitles--regular",
70
- // TODO(wittjosiah): Factor out. Artifact? Separate capability?
71
- getTextContent: async (transcript) => {
72
- const space = getSpace(transcript);
73
- const members = space?.members.get().map((member) => member.identity) ?? [];
74
- const queue = space?.queues.get(transcript.queue.dxn);
75
- await queue?.refresh();
76
- const content = queue?.objects.filter((message) => Obj.instanceOf(DataType.Message, message)).flatMap((message, index) => renderMarkdown(members)(message, index)).join("\n\n");
77
- return content;
199
+ // src/components/Transcription/transcription-extension.ts
200
+ var transcription = ({ model, started }) => {
201
+ return [
202
+ // Show timestamps in the gutter.
203
+ gutter({
204
+ class: "cm-timestamp-gutter",
205
+ lineMarkerChange: (update) => update.docChanged || update.viewportChanged,
206
+ markers: (view) => {
207
+ const start = getStartTime(started, model.chunks[0]);
208
+ const builder = new RangeSetBuilder();
209
+ for (const { from, to } of view.visibleRanges) {
210
+ let line = view.state.doc.lineAt(from);
211
+ while (line.from <= to) {
212
+ const block = model.getChunkAtLine(line.number)?.blocks[0];
213
+ const timestamp = block?._tag === "transcript" && block.started;
214
+ if (timestamp) {
215
+ builder.add(line.from, line.from, new TimestampMarker(line.number, new Date(timestamp), start));
216
+ }
217
+ if (line.to + 1 > view.state.doc.length) {
218
+ break;
219
+ }
220
+ line = view.state.doc.lineAt(line.to + 1);
221
+ }
78
222
  }
223
+ return builder.finish();
224
+ }
225
+ }),
226
+ // Listen for model updates.
227
+ ViewPlugin.fromClass(class {
228
+ _controls;
229
+ _cleanup;
230
+ _adapter;
231
+ _initialized = false;
232
+ constructor(view) {
233
+ this._adapter = new DocumentAdapter(view);
234
+ const scroller = view.scrollDOM;
235
+ let isAutoScrolling = false;
236
+ let hasScrolled = false;
237
+ let timeout;
238
+ const scrollToBottom = (smooth = false) => {
239
+ scroller.style.scrollBehavior = smooth ? "smooth" : "";
240
+ scroller.classList.add("cm-hide-scrollbar");
241
+ isAutoScrolling = true;
242
+ clearTimeout(timeout);
243
+ timeout = setTimeout(() => {
244
+ this._controls?.classList.add("opacity-0");
245
+ scroller.classList.remove("cm-hide-scrollbar");
246
+ isAutoScrolling = false;
247
+ }, 500);
248
+ view.dispatch({
249
+ effects: EditorView.scrollIntoView(view.state.doc.length, {
250
+ y: "end"
251
+ })
252
+ });
253
+ };
254
+ this._cleanup = combine(addEventListener(view.scrollDOM, "scroll", () => {
255
+ if (!isAutoScrolling) {
256
+ hasScrolled = true;
257
+ this._controls?.classList.remove("opacity-0");
258
+ }
259
+ }), model.update.on(() => {
260
+ const autoScroll2 = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight === 0 || !hasScrolled;
261
+ model.sync(this._adapter);
262
+ if (autoScroll2) {
263
+ scrollToBottom(true);
264
+ }
265
+ }));
266
+ }
267
+ update(_update) {
268
+ if (!this._initialized) {
269
+ this._initialized = true;
270
+ setTimeout(() => {
271
+ model.sync(this._adapter);
272
+ });
273
+ }
274
+ }
275
+ destroy() {
276
+ this._controls?.remove();
277
+ this._cleanup();
278
+ }
279
+ }),
280
+ EditorView.theme({
281
+ ".cm-gutters": {
282
+ backgroundColor: "var(--color-base-surface)"
283
+ },
284
+ ".cm-timestamp-gutter": {
285
+ width: "6rem",
286
+ paddingLeft: "0.5rem",
287
+ paddingRight: "1rem"
288
+ },
289
+ ".cm-timestamp-gutter > .cm-gutterElement > div": {
290
+ display: "inline-flex",
291
+ textAlign: "right",
292
+ padding: "3px"
79
293
  }
80
294
  })
81
- }),
82
- defineModule({
83
- id: `${meta.id}/module/schema`,
84
- activatesOn: ClientEvents.SetupSchema,
85
- activate: () => [
86
- contributes(ClientCapabilities.Schema, [
87
- TranscriptType
88
- ])
89
- ]
90
- }),
91
- defineModule({
92
- id: `${meta.id}/module/react-surface`,
93
- activatesOn: Events.SetupReactSurface,
94
- activate: ReactSurface
95
- }),
96
- defineModule({
97
- id: `${meta.id}/module/intent-resolver`,
98
- activatesOn: Events.SetupIntentResolver,
99
- activate: IntentResolver
100
- }),
101
- defineModule({
102
- id: `${meta.id}/module/transcription`,
103
- activatesOn: Events.SetupAppGraph,
104
- activate: Transcriber2
295
+ ];
296
+ };
297
+ var TimestampMarker = class extends GutterMarker {
298
+ _line;
299
+ _timestamp;
300
+ _started;
301
+ constructor(_line, _timestamp, _started) {
302
+ super(), this._line = _line, this._timestamp = _timestamp, this._started = _started;
303
+ }
304
+ eq(other) {
305
+ return this._timestamp === other._timestamp;
306
+ }
307
+ toDOM(view) {
308
+ const el = document.createElement("div");
309
+ el.className = "text-sm text-subdued hover:bg-hover-surface cursor-pointer";
310
+ el.textContent = formatTimestamp(this._timestamp, this._started);
311
+ el.onclick = () => {
312
+ const pos = view.state.doc.line(this._line).from;
313
+ view.dispatch({
314
+ effects: EditorView.scrollIntoView(pos, {
315
+ y: "start"
316
+ })
317
+ });
318
+ };
319
+ return el;
320
+ }
321
+ };
322
+ var getStartTime = (started, message) => {
323
+ if (started) {
324
+ return started;
325
+ }
326
+ if (message?.blocks[0]?._tag === "transcript" && message.blocks[0].started) {
327
+ return new Date(message.blocks[0].started);
328
+ }
329
+ return void 0;
330
+ };
331
+ var formatTimestamp = (timestamp, relative) => {
332
+ if (relative) {
333
+ const pad = (n = 0) => String(n).padStart(2, "0");
334
+ const { hours, minutes, seconds } = intervalToDuration({
335
+ start: relative,
336
+ end: timestamp
337
+ });
338
+ return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
339
+ } else {
340
+ return format(timestamp, "HH:mm:ss");
341
+ }
342
+ };
343
+
344
+ // src/components/Transcription/Transcription.tsx
345
+ var Transcription = composable(({ transcript: object, model, children, ...props }, forwardedRef) => {
346
+ const { themeMode } = useThemeContext();
347
+ const { parentRef } = useTextEditor(() => {
348
+ return {
349
+ extensions: [
350
+ createBasicExtensions({
351
+ readOnly: true,
352
+ lineWrapping: true,
353
+ search: true
354
+ }),
355
+ createMarkdownExtensions(),
356
+ createThemeExtensions({
357
+ themeMode,
358
+ slots: documentSlots
359
+ }),
360
+ decorateMarkdown(),
361
+ preview(),
362
+ transcription({
363
+ model,
364
+ started: object?.started ? new Date(object.started) : void 0
365
+ }),
366
+ autoScroll()
367
+ ].filter(isTruthy)
368
+ };
369
+ }, [
370
+ model
371
+ ]);
372
+ return /* @__PURE__ */ React.createElement("div", {
373
+ ...composableProps(props, {
374
+ classNames: "dx-container"
375
+ }),
376
+ ref: parentRef,
377
+ "data-popover-collision-boundary": true
378
+ });
379
+ });
380
+
381
+ // src/hooks/useAudioFile.ts
382
+ import { useEffect, useState } from "react";
383
+ import { scheduleTask } from "@dxos/async";
384
+ import { Context } from "@dxos/context";
385
+ import { log as log2 } from "@dxos/log";
386
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useAudioFile.ts";
387
+ var useAudioFile = (audioUrl, constraints) => {
388
+ const [{ audio, stream, track }, setStream] = useState({});
389
+ useEffect(() => {
390
+ const ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 11 });
391
+ scheduleTask(ctx, async () => {
392
+ try {
393
+ const response = await fetch(audioUrl);
394
+ const blob = await response.blob();
395
+ const objectUrl = URL.createObjectURL(blob);
396
+ ctx.onDispose(() => {
397
+ URL.revokeObjectURL(objectUrl);
398
+ });
399
+ const audio2 = new Audio();
400
+ audio2.src = objectUrl;
401
+ await new Promise((resolve, reject) => {
402
+ audio2.addEventListener("error", (err) => {
403
+ log2.error("error", {
404
+ err
405
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 25, S: void 0 });
406
+ reject(err);
407
+ }, {
408
+ once: true
409
+ });
410
+ audio2.addEventListener("canplay", async () => {
411
+ resolve();
412
+ }, {
413
+ once: true
414
+ });
415
+ audio2.load();
416
+ });
417
+ const audioCtx = new AudioContext();
418
+ if (audioCtx.state === "suspended") {
419
+ await audioCtx.resume();
420
+ }
421
+ const destination = audioCtx.createMediaStreamDestination();
422
+ destination.channelCount = 1;
423
+ const source = audioCtx.createMediaElementSource(audio2);
424
+ source.connect(destination);
425
+ const track2 = destination.stream.getAudioTracks()[0];
426
+ if (constraints) {
427
+ await track2.applyConstraints(constraints).catch((err) => log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 50, S: void 0 }));
428
+ }
429
+ setStream({
430
+ audio: audio2,
431
+ stream: destination.stream,
432
+ track: destination.stream.getAudioTracks()[0]
433
+ });
434
+ } catch (err) {
435
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 59, S: void 0 });
436
+ }
437
+ });
438
+ return () => {
439
+ void ctx.dispose();
440
+ };
441
+ }, [
442
+ audioUrl
443
+ ]);
444
+ return {
445
+ audio,
446
+ stream,
447
+ track
448
+ };
449
+ };
450
+
451
+ // src/hooks/useAudioTrack.ts
452
+ import { useEffect as useEffect2, useState as useState2 } from "react";
453
+ var useAudioTrack = (active, constraints) => {
454
+ const [track, setTrack] = useState2();
455
+ useEffect2(() => {
456
+ if (!active) {
457
+ track?.stop();
458
+ setTrack(void 0);
459
+ return;
460
+ }
461
+ const initAudio = async () => {
462
+ const audio = new Audio();
463
+ audio.srcObject = await navigator.mediaDevices.getUserMedia({
464
+ audio: constraints ?? true
465
+ });
466
+ const [track2] = audio.srcObject.getAudioTracks();
467
+ if (track2) {
468
+ setTrack(track2);
469
+ }
470
+ };
471
+ if (active) {
472
+ void initAudio();
473
+ }
474
+ }, [
475
+ active
476
+ ]);
477
+ return track;
478
+ };
479
+
480
+ // src/hooks/useQueueModelAdapter.ts
481
+ import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
482
+ var useQueueModelAdapter = (renderer, queue, initialChunks = []) => {
483
+ const model = useMemo(() => new SerializationModel(renderer, initialChunks), [
484
+ queue
485
+ ]);
486
+ const [loaded, setLoaded] = useState3(false);
487
+ useEffect3(() => {
488
+ if (!queue) {
489
+ return;
490
+ }
491
+ const update = () => {
492
+ for (const block of queue?.objects ?? []) {
493
+ model.appendChunk(block);
494
+ }
495
+ setLoaded(true);
496
+ };
497
+ let i;
498
+ if (queue.isLoading) {
499
+ i = setInterval(() => {
500
+ if (!queue?.isLoading) {
501
+ clearInterval(i);
502
+ update();
503
+ }
504
+ }, 1e3);
505
+ } else {
506
+ update();
507
+ }
508
+ return () => clearTimeout(i);
509
+ }, [
510
+ model,
511
+ queue
512
+ ]);
513
+ useEffect3(() => {
514
+ if (!loaded || !queue || model.chunks.length === queue.objects.length) {
515
+ return;
516
+ }
517
+ const chunk = queue.objects.at(-1);
518
+ if (chunk) {
519
+ model.appendChunk(chunk);
520
+ }
521
+ }, [
522
+ model,
523
+ loaded,
524
+ queue?.objects.length
525
+ ]);
526
+ return model;
527
+ };
528
+
529
+ // src/hooks/useTranscriber.ts
530
+ import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
531
+ import { useCapabilities } from "@dxos/app-framework/ui";
532
+ import { TranscriptionCapabilities as TranscriptionCapabilities2 } from "#types";
533
+ var useTranscriber = ({ audioStreamTrack, recorderConfig, transcriberConfig, onSegments, transcribe }) => {
534
+ const [getTranscriber] = useCapabilities(TranscriptionCapabilities2.Transcriber);
535
+ const transcriber = useMemo2(() => {
536
+ if (!getTranscriber || !audioStreamTrack || !onSegments) {
537
+ return void 0;
538
+ }
539
+ return getTranscriber({
540
+ audioStreamTrack,
541
+ recorderConfig,
542
+ transcriberConfig,
543
+ onSegments,
544
+ transcribe
545
+ });
546
+ }, [
547
+ getTranscriber,
548
+ audioStreamTrack,
549
+ recorderConfig,
550
+ transcriberConfig,
551
+ onSegments,
552
+ transcribe
553
+ ]);
554
+ useEffect4(() => {
555
+ return () => {
556
+ void transcriber?.close();
557
+ };
558
+ }, [
559
+ transcriber
560
+ ]);
561
+ return transcriber;
562
+ };
563
+
564
+ // src/hooks/useVoiceInput.ts
565
+ import { useCallback, useEffect as useEffect5, useState as useState4 } from "react";
566
+ import { scheduleMicroTask } from "@dxos/async";
567
+ import { Context as Context2 } from "@dxos/context";
568
+ import { log as log3 } from "@dxos/log";
569
+ import { useSoundEffect } from "@dxos/react-ui-sfx";
570
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useVoiceInput.ts";
571
+ var useVoiceInput = ({ active, onUpdate }) => {
572
+ const soundStart = useSoundEffect("StartRecording");
573
+ const soundStop = useSoundEffect("StopRecording");
574
+ const handleSegments = useCallback(async (segments) => {
575
+ const text = segments.map((str) => str.text.trim().replace(/[^\w\s]/g, "")).join(" ");
576
+ onUpdate(text);
577
+ }, []);
578
+ const [recording, setRecording] = useState4(false);
579
+ const track = useAudioTrack(active);
580
+ const transcriber = useTranscriber({
581
+ audioStreamTrack: track,
582
+ onSegments: handleSegments
583
+ });
584
+ useEffect5(() => {
585
+ const ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 29 });
586
+ scheduleMicroTask(ctx, async () => {
587
+ if (active && transcriber) {
588
+ await transcriber.open();
589
+ log3.info("starting...", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 33, S: void 0 });
590
+ setRecording(true);
591
+ void soundStart.play();
592
+ transcriber.startChunksRecording();
593
+ } else if (!active && transcriber?.isOpen) {
594
+ transcriber?.stopChunksRecording();
595
+ await transcriber?.close();
596
+ log3.info("stopped", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 40, S: void 0 });
597
+ setRecording(false);
598
+ void soundStop.play();
599
+ }
600
+ });
601
+ return () => {
602
+ void ctx.dispose();
603
+ };
604
+ }, [
605
+ active,
606
+ transcriber
607
+ ]);
608
+ return {
609
+ recording
610
+ };
611
+ };
612
+
613
+ // src/meta.ts
614
+ import { trim } from "@dxos/util";
615
+ var meta = {
616
+ id: "org.dxos.plugin.transcription",
617
+ name: "Transcription",
618
+ description: trim`
619
+ Real-time voice-to-text transcription service for capturing spoken content.
620
+ Convert audio input into searchable text that integrates seamlessly with notes and documents.
621
+ `,
622
+ icon: "ph--microphone--regular",
623
+ iconHue: "sky",
624
+ source: "https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-transcription",
625
+ tags: [
626
+ "labs"
627
+ ],
628
+ screenshots: []
629
+ };
630
+
631
+ // src/transcriber/media-stream-recorder.ts
632
+ import { MediaRecorder, register } from "extendable-media-recorder";
633
+ import { WaveFile } from "wavefile";
634
+ import { synchronized } from "@dxos/async";
635
+ import { invariant as invariant2 } from "@dxos/invariant";
636
+ import { log as log4 } from "@dxos/log";
637
+ import { trace } from "@dxos/tracing";
638
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/transcriber/media-stream-recorder.ts";
639
+ function _ts_decorate(decorators, target, key, desc) {
640
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
641
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
642
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
643
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
644
+ }
645
+ var initializingPromise;
646
+ var initializeExtendableMediaRecorder = async () => {
647
+ const { connect } = await import("extendable-media-recorder-wav-encoder");
648
+ await register(await connect());
649
+ };
650
+ var MediaStreamRecorder = class {
651
+ _mediaStreamTrack;
652
+ /**
653
+ * Default MediaRecorder implementation do not support wav encoding.
654
+ */
655
+ _config;
656
+ _mediaRecorder = void 0;
657
+ _header = void 0;
658
+ _onChunk;
659
+ constructor({ mediaStreamTrack, config }) {
660
+ this._mediaStreamTrack = mediaStreamTrack;
661
+ this._config = config;
662
+ }
663
+ get wavConfig() {
664
+ const settings = this._mediaStreamTrack.getSettings();
665
+ return {
666
+ channels: settings.channelCount ?? 1,
667
+ sampleRate: settings.sampleRate ?? 16e3,
668
+ bitDepthCode: settings.sampleSize ? String(settings.sampleSize) : "16"
669
+ };
670
+ }
671
+ get mediaStreamTrack() {
672
+ return this._mediaStreamTrack;
673
+ }
674
+ setOnChunk(onChunk) {
675
+ this._onChunk = onChunk;
676
+ }
677
+ async start() {
678
+ await (initializingPromise ??= initializeExtendableMediaRecorder()).catch((err) => log4.info("initializeExtendableMediaRecorder", {
679
+ err
680
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 48, S: this }));
681
+ if (!this._mediaRecorder) {
682
+ const stream = new MediaStream([
683
+ this._mediaStreamTrack
684
+ ]);
685
+ this._mediaRecorder = new MediaRecorder(stream, {
686
+ mimeType: "audio/wav"
687
+ });
688
+ }
689
+ if (this._mediaRecorder.state === "recording") {
690
+ return;
691
+ }
692
+ invariant2(this._onChunk, "MediaStreamRecorder: onChunk is not set", { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 62, S: this, A: ["this._onChunk", "'MediaStreamRecorder: onChunk is not set'"] });
693
+ this._mediaRecorder.ondataavailable = (event) => this._ondataavailable(event);
694
+ this._mediaRecorder.start(this._config.interval);
695
+ }
696
+ async stop() {
697
+ if (this._mediaRecorder?.state !== "recording") {
698
+ return;
699
+ }
700
+ this._mediaRecorder.stop();
701
+ }
702
+ async _ondataavailable(event) {
703
+ const blob = event.data;
704
+ const uint8Array = new Uint8Array(await blob.arrayBuffer());
705
+ const isHeader = uint8Array[0] === 82 && // R
706
+ uint8Array[1] === 73 && // I
707
+ uint8Array[2] === 70 && // F
708
+ uint8Array[3] === 70;
709
+ let wav;
710
+ if (isHeader) {
711
+ wav = new WaveFile(uint8Array);
712
+ this._header = uint8Array.slice(0, 44);
713
+ } else if (this._header) {
714
+ wav = new WaveFile(new Uint8Array([
715
+ ...this._header,
716
+ ...uint8Array
717
+ ]));
718
+ } else {
719
+ log4.warn("MediaStreamRecorder: no header", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 90, S: this });
720
+ return;
721
+ }
722
+ this._onChunk({
723
+ timestamp: Date.now(),
724
+ data: wav.getSamples()
725
+ });
726
+ }
727
+ };
728
+ _ts_decorate([
729
+ synchronized
730
+ ], MediaStreamRecorder.prototype, "_ondataavailable", null);
731
+ MediaStreamRecorder = _ts_decorate([
732
+ trace.resource()
733
+ ], MediaStreamRecorder);
734
+
735
+ // src/transcriber/transcriber.ts
736
+ import { WaveFile as WaveFile2 } from "wavefile";
737
+ import { DeferredTask, Trigger } from "@dxos/async";
738
+ import { LifecycleState, Resource } from "@dxos/context";
739
+ import { log as log5 } from "@dxos/log";
740
+ import { trace as trace2 } from "@dxos/tracing";
741
+ import { TRANSCRIPTION_URL } from "#types";
742
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/transcriber/transcriber.ts";
743
+ function _ts_decorate2(decorators, target, key, desc) {
744
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
745
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
746
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
747
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
748
+ }
749
+ var Transcriber = class extends Resource {
750
+ _audioChunks = [];
751
+ _lastUsedTimestamp = 0;
752
+ _openTrigger = new Trigger({
753
+ autoReset: true
754
+ });
755
+ _config;
756
+ _recorder;
757
+ _onSegments;
758
+ _transcribeFn;
759
+ _recording = false;
760
+ _transcribeTask = void 0;
761
+ constructor({ config, recorder, onSegments, transcribe }) {
762
+ super();
763
+ this._config = config;
764
+ this._recorder = recorder;
765
+ this._onSegments = onSegments;
766
+ this._transcribeFn = transcribe;
767
+ }
768
+ async _open(ctx) {
769
+ log5.info("opening", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 41, S: this });
770
+ this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));
771
+ await this._recorder.start();
772
+ this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());
773
+ this._openTrigger.wake();
774
+ }
775
+ async _close() {
776
+ log5.info("closing", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 48, S: this });
777
+ this._recording = false;
778
+ this._transcribeTask = void 0;
779
+ await this._recorder.stop();
780
+ }
781
+ startChunksRecording() {
782
+ log5.info("starting", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 54, S: this });
783
+ if (this._lifecycleState !== LifecycleState.OPEN) {
784
+ return;
785
+ }
786
+ this._recording = true;
787
+ }
788
+ stopChunksRecording() {
789
+ if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {
790
+ return;
791
+ }
792
+ this._recording = false;
793
+ this._transcribeTask?.schedule();
794
+ log5.info("stopped", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 66, S: this });
795
+ }
796
+ _saveAudioChunk(chunk) {
797
+ log5("saving audio chunk", {
798
+ chunk
799
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 69, S: this });
800
+ this._audioChunks.push(chunk);
801
+ if (!this._recording && (!this._transcribeTask || !this._transcribeTask.scheduled) && this._audioChunks.length >= this._config.prefixBufferChunksAmount) {
802
+ this._audioChunks = this._dropOldChunks();
803
+ } else if (this._audioChunks.length >= this._config.transcribeAfterChunksAmount && this._recording) {
804
+ this._transcribeTask?.schedule();
805
+ }
806
+ }
807
+ async _transcribe() {
808
+ log5("transcribing", {
809
+ chunks: this._audioChunks.length
810
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 81, S: this });
811
+ const chunks = this._audioChunks;
812
+ this._audioChunks = this._dropOldChunks();
813
+ if (chunks.length === 0) {
814
+ return;
815
+ }
816
+ const audio = await this._mergeAudioChunks(chunks);
817
+ const segments = await this._fetchTranscription(audio);
818
+ if (!Array.isArray(segments) || segments.length === 0) {
819
+ return;
820
+ }
821
+ const alignedSegments = this._alignSegments(segments, chunks);
822
+ if (alignedSegments.length > 0) {
823
+ await this._onSegments(alignedSegments);
824
+ }
825
+ }
826
+ _dropOldChunks() {
827
+ return this._config.prefixBufferChunksAmount > 0 ? this._audioChunks.slice(-this._config.prefixBufferChunksAmount) : [];
828
+ }
829
+ async _mergeAudioChunks(chunks) {
830
+ const file = new WaveFile2();
831
+ const wavConfig = this._recorder.wavConfig;
832
+ file.fromScratch(wavConfig.channels, wavConfig.sampleRate, wavConfig.bitDepthCode, mergeFloat64Arrays(chunks.map(({ data }) => data)));
833
+ return file.toBase64();
834
+ }
835
+ async _fetchTranscription(audio) {
836
+ if (audio.length === 0) {
837
+ throw new Error("No audio to send for transcribing");
838
+ }
839
+ let segments;
840
+ if (this._transcribeFn) {
841
+ segments = await this._transcribeFn(audio);
842
+ } else {
843
+ const endpoint = this._config.endpoint ?? TRANSCRIPTION_URL;
844
+ const response = await fetch(`${endpoint}/transcribe`, {
845
+ method: "POST",
846
+ body: JSON.stringify({
847
+ audio
848
+ }),
849
+ headers: {
850
+ "Content-Type": "application/json"
851
+ }
852
+ });
853
+ if (!response.ok) {
854
+ throw new Error(`Transcription failed: ${response.statusText}`);
855
+ }
856
+ ({ segments } = await response.json());
857
+ }
858
+ if (!Array.isArray(segments)) {
859
+ throw new Error("Transcription response payload is invalid");
860
+ }
861
+ log5.info("transcription response", {
862
+ segments: segments.length
863
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 134, S: this });
864
+ return segments;
865
+ }
866
+ _alignSegments(segments, originalChunks) {
867
+ const result = alignWhisperSegments(segments, originalChunks, this._lastUsedTimestamp);
868
+ if (result.lastUsedTimestamp !== void 0) {
869
+ this._lastUsedTimestamp = result.lastUsedTimestamp;
870
+ }
871
+ return result.transcripts;
872
+ }
873
+ };
874
+ _ts_decorate2([
875
+ trace2.span({
876
+ showInBrowserTimeline: true
105
877
  })
106
- ]);
878
+ ], Transcriber.prototype, "_mergeAudioChunks", null);
879
+ _ts_decorate2([
880
+ trace2.span({
881
+ showInBrowserTimeline: true
882
+ })
883
+ ], Transcriber.prototype, "_fetchTranscription", null);
884
+ var alignWhisperSegments = (segments, originalChunks, lastUsedTimestamp) => {
885
+ if (originalChunks.length === 0) {
886
+ return {
887
+ transcripts: []
888
+ };
889
+ }
890
+ const zeroTimestamp = originalChunks[0].timestamp;
891
+ const filteredSegments = segments.filter((segment) => zeroTimestamp + segment.end * 1e3 >= lastUsedTimestamp).map((segment) => {
892
+ const words = segment.words.filter((word) => zeroTimestamp + word.start * 1e3 >= lastUsedTimestamp);
893
+ return {
894
+ ...segment,
895
+ words,
896
+ text: words.map((word) => word.word).join(""),
897
+ start: words.at(0)?.start ?? segment.end
898
+ };
899
+ }).filter((segment) => segment.words.length > 0);
900
+ if (filteredSegments.length === 0) {
901
+ return {
902
+ transcripts: []
903
+ };
904
+ }
905
+ return {
906
+ transcripts: filteredSegments.map((segment) => ({
907
+ _tag: "transcript",
908
+ started: new Date(zeroTimestamp + segment.start * 1e3).toISOString(),
909
+ text: segment.text.trim()
910
+ })),
911
+ lastUsedTimestamp: zeroTimestamp + filteredSegments.at(-1).end * 1e3
912
+ };
913
+ };
914
+
915
+ // src/transcriber/transcription-manager.ts
916
+ import { Atom } from "@effect-atom/atom-react";
917
+ import { synchronized as synchronized2 } from "@dxos/async";
918
+ import { Resource as Resource2 } from "@dxos/context";
919
+ import { Obj } from "@dxos/echo";
920
+ import { Message } from "@dxos/types";
921
+ function _ts_decorate3(decorators, target, key, desc) {
922
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
923
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
924
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
925
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
926
+ }
927
+ var RECORD_INTERVAL = 200;
928
+ var PREFIXED_CHUNKS_AMOUNT = 10;
929
+ var TRANSCRIBE_AFTER_CHUNKS_AMOUNT = 50;
930
+ var TranscriptionManager = class extends Resource2 {
931
+ _edgeClient;
932
+ _messageEnricher;
933
+ _registry;
934
+ _audioStreamTrack = void 0;
935
+ _identityDid = void 0;
936
+ _mediaRecorder = void 0;
937
+ _transcriber = void 0;
938
+ _queue = void 0;
939
+ _enabledAtom = Atom.make(false);
940
+ constructor(options) {
941
+ super();
942
+ this._edgeClient = options.edgeClient;
943
+ this._messageEnricher = options.messageEnricher;
944
+ this._registry = options.registry;
945
+ }
946
+ get enabled() {
947
+ return this._enabledAtom;
948
+ }
949
+ getEnabled() {
950
+ return this._registry.get(this._enabledAtom);
951
+ }
952
+ setQueue(queue) {
953
+ this._queue = queue;
954
+ return this;
955
+ }
956
+ setIdentityDid(did) {
957
+ if (this._identityDid !== did) {
958
+ this._identityDid = did;
959
+ }
960
+ return this;
961
+ }
962
+ setRecording(recording) {
963
+ if (!this.isOpen || !this.getEnabled()) {
964
+ return this;
965
+ }
966
+ if (recording) {
967
+ this._transcriber?.startChunksRecording();
968
+ } else {
969
+ this._transcriber?.stopChunksRecording();
970
+ }
971
+ return this;
972
+ }
973
+ async setEnabled(enabled) {
974
+ if (this.getEnabled() === enabled) {
975
+ return;
976
+ }
977
+ this._registry.set(this._enabledAtom, enabled ?? false);
978
+ if (enabled) {
979
+ await this._maybeRestartTranscriber();
980
+ } else {
981
+ await this._stopTranscriber();
982
+ }
983
+ }
984
+ async setAudioTrack(track) {
985
+ if (this._audioStreamTrack === track) {
986
+ return;
987
+ }
988
+ this._audioStreamTrack = track;
989
+ await this._maybeRestartTranscriber();
990
+ }
991
+ async _open() {
992
+ await this._maybeRestartTranscriber();
993
+ }
994
+ async _close() {
995
+ await this._stopTranscriber();
996
+ }
997
+ async _maybeRestartTranscriber() {
998
+ if (!this._audioStreamTrack || !this.getEnabled() || !this.isOpen) {
999
+ return;
1000
+ }
1001
+ let needReinit = false;
1002
+ if (this._audioStreamTrack !== this._mediaRecorder?.mediaStreamTrack) {
1003
+ this._mediaRecorder = new MediaStreamRecorder({
1004
+ mediaStreamTrack: this._audioStreamTrack,
1005
+ config: {
1006
+ interval: RECORD_INTERVAL
1007
+ }
1008
+ });
1009
+ needReinit = true;
1010
+ }
1011
+ if (needReinit) {
1012
+ await this._transcriber?.close();
1013
+ this._transcriber = new Transcriber({
1014
+ config: {
1015
+ transcribeAfterChunksAmount: TRANSCRIBE_AFTER_CHUNKS_AMOUNT,
1016
+ prefixBufferChunksAmount: PREFIXED_CHUNKS_AMOUNT
1017
+ },
1018
+ recorder: this._mediaRecorder,
1019
+ onSegments: (segments) => this._onSegments(segments)
1020
+ });
1021
+ await this._transcriber?.open();
1022
+ }
1023
+ }
1024
+ async _stopTranscriber() {
1025
+ await this._transcriber?.close();
1026
+ }
1027
+ async _onSegments(segments) {
1028
+ if (!this.isOpen || !this._queue) {
1029
+ return;
1030
+ }
1031
+ let block = Obj.make(Message.Message, {
1032
+ created: (/* @__PURE__ */ new Date()).toISOString(),
1033
+ blocks: segments,
1034
+ sender: {
1035
+ identityDid: this._identityDid
1036
+ }
1037
+ });
1038
+ if (this._messageEnricher) {
1039
+ block = await this._messageEnricher(block);
1040
+ }
1041
+ await this._queue.append([
1042
+ block
1043
+ ]);
1044
+ }
1045
+ };
1046
+ _ts_decorate3([
1047
+ synchronized2
1048
+ ], TranscriptionManager.prototype, "setAudioTrack", null);
1049
+
1050
+ // src/TranscriptionPlugin.tsx
1051
+ import * as Option from "effect/Option";
1052
+ import { Plugin } from "@dxos/app-framework";
1053
+ import { AppActivationEvents, AppPlugin } from "@dxos/app-toolkit";
1054
+ import { Annotation, Feed, Obj as Obj2 } from "@dxos/echo";
1055
+ import { getSpace } from "@dxos/react-client/echo";
1056
+ import { Message as Message2, Transcript } from "@dxos/types";
1057
+ import { BlueprintDefinition, OperationHandler, ReactSurface, Transcriber as Transcriber2 } from "#capabilities";
1058
+ import { meta as meta2 } from "#meta";
1059
+ import { translations } from "#translations";
1060
+ var TranscriptionPlugin = Plugin.define(meta2).pipe(AppPlugin.addBlueprintDefinitionModule({
1061
+ activate: BlueprintDefinition
1062
+ }), AppPlugin.addMetadataModule({
1063
+ metadata: {
1064
+ id: Transcript.Transcript.typename,
1065
+ metadata: {
1066
+ icon: Annotation.IconAnnotation.get(Transcript.Transcript).pipe(Option.getOrThrow).icon,
1067
+ iconHue: Annotation.IconAnnotation.get(Transcript.Transcript).pipe(Option.getOrThrow).hue ?? "white",
1068
+ // TODO(wittjosiah): Factor out. Artifact? Separate capability?
1069
+ getTextContent: async (transcript) => {
1070
+ const space = getSpace(transcript);
1071
+ const members = space?.members.get().map((member) => member.identity) ?? [];
1072
+ const feed = await transcript.feed.load();
1073
+ const queueDxn = Feed.getQueueDxn(feed);
1074
+ const queue = queueDxn ? space?.queues.get(queueDxn) : void 0;
1075
+ await queue?.refresh();
1076
+ const content = queue?.objects.filter((message) => Obj2.instanceOf(Message2.Message, message)).flatMap((message, index) => renderByline(members)(message, index)).join("\n\n");
1077
+ return content;
1078
+ }
1079
+ }
1080
+ }
1081
+ }), AppPlugin.addOperationHandlerModule({
1082
+ activate: OperationHandler
1083
+ }), AppPlugin.addSchemaModule({
1084
+ schema: [
1085
+ Transcript.Transcript
1086
+ ]
1087
+ }), AppPlugin.addSurfaceModule({
1088
+ activate: ReactSurface
1089
+ }), AppPlugin.addTranslationsModule({
1090
+ translations
1091
+ }), Plugin.addModule({
1092
+ id: "transcription",
1093
+ activatesOn: AppActivationEvents.SetupAppGraph,
1094
+ activate: Transcriber2
1095
+ }), Plugin.make);
107
1096
  export {
108
1097
  MediaStreamRecorder,
109
- TRANSCRIPTION_PLUGIN,
110
1098
  Transcriber,
111
- Transcript,
112
- TranscriptContainer,
1099
+ Transcription,
113
1100
  TranscriptionCapabilities,
114
- TranscriptionContainer,
115
1101
  TranscriptionManager,
116
1102
  TranscriptionPlugin,
1103
+ alignWhisperSegments,
117
1104
  meta,
118
- renderMarkdown,
1105
+ renderByline,
119
1106
  useAudioFile,
120
1107
  useAudioTrack,
121
1108
  useQueueModelAdapter,