@dxos/plugin-transcription 0.8.1 → 0.8.2-main.10c050d

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 (258) hide show
  1. package/dist/lib/browser/TranscriptContainer-PFKLKRPQ.mjs +11 -0
  2. package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-4KCUYW2F.mjs} +139 -95
  3. package/dist/lib/browser/chunk-4KCUYW2F.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-6ZR5MDK3.mjs → chunk-GOF7MJNV.mjs} +3 -3
  5. package/dist/lib/browser/{chunk-6ZR5MDK3.mjs.map → chunk-GOF7MJNV.mjs.map} +1 -1
  6. package/dist/lib/browser/{chunk-K7FOYAXO.mjs → chunk-IGEKO7ZU.mjs} +2 -2
  7. package/dist/lib/browser/chunk-NODIXNQP.mjs +54 -0
  8. package/dist/lib/browser/chunk-NODIXNQP.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-TJPJBE45.mjs +764 -0
  10. package/dist/lib/browser/chunk-TJPJBE45.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-W2KNYJD6.mjs} +4 -4
  12. package/dist/lib/browser/chunk-W2KNYJD6.mjs.map +7 -0
  13. package/dist/lib/browser/index.mjs +30 -216
  14. package/dist/lib/browser/index.mjs.map +4 -4
  15. package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-I2LJSQDR.mjs} +7 -11
  16. package/dist/lib/browser/intent-resolver-I2LJSQDR.mjs.map +7 -0
  17. package/dist/lib/browser/meta.json +1 -1
  18. package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-ID2MLJHZ.mjs} +11 -9
  19. package/dist/lib/browser/react-surface-ID2MLJHZ.mjs.map +7 -0
  20. package/dist/lib/browser/transcriber-NA7CIFIT.mjs +56 -0
  21. package/dist/lib/browser/transcriber-NA7CIFIT.mjs.map +7 -0
  22. package/dist/lib/browser/types/index.mjs +4 -8
  23. package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-LHMC7553.cjs} +9 -8
  24. package/dist/lib/node/TranscriptContainer-LHMC7553.cjs.map +7 -0
  25. package/dist/lib/node/chunk-3GS5UNO5.cjs +778 -0
  26. package/dist/lib/node/chunk-3GS5UNO5.cjs.map +7 -0
  27. package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-5J3FBCON.cjs} +5 -5
  28. package/dist/lib/node/{chunk-NVZWGFJL.cjs → chunk-AFUZI63A.cjs} +6 -6
  29. package/dist/lib/node/{chunk-NVZWGFJL.cjs.map → chunk-AFUZI63A.cjs.map} +1 -1
  30. package/dist/lib/node/chunk-EIMUQYCZ.cjs +74 -0
  31. package/dist/lib/node/chunk-EIMUQYCZ.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-FKDJNJ57.cjs} +8 -8
  33. package/dist/lib/node/chunk-FKDJNJ57.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-ZFTXJZXH.cjs} +138 -95
  35. package/dist/lib/node/chunk-ZFTXJZXH.cjs.map +7 -0
  36. package/dist/lib/node/index.cjs +59 -251
  37. package/dist/lib/node/index.cjs.map +4 -4
  38. package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-LLMGF5I5.cjs} +10 -11
  39. package/dist/lib/node/intent-resolver-LLMGF5I5.cjs.map +7 -0
  40. package/dist/lib/node/meta.json +1 -1
  41. package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-SEQLEL4K.cjs} +13 -11
  42. package/dist/lib/node/react-surface-SEQLEL4K.cjs.map +7 -0
  43. package/dist/lib/node/transcriber-RKKN7LDU.cjs +68 -0
  44. package/dist/lib/node/transcriber-RKKN7LDU.cjs.map +7 -0
  45. package/dist/lib/node/types/index.cjs +7 -11
  46. package/dist/lib/node/types/index.cjs.map +2 -2
  47. package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-DLNRFVTA.mjs} +4 -3
  48. package/dist/lib/node-esm/chunk-BRPFVRAD.mjs +55 -0
  49. package/dist/lib/node-esm/chunk-BRPFVRAD.mjs.map +7 -0
  50. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-EFIWBQDF.mjs} +139 -95
  51. package/dist/lib/node-esm/chunk-EFIWBQDF.mjs.map +7 -0
  52. package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs → chunk-KUWWHMOA.mjs} +2 -2
  53. package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-MFJPEIVB.mjs} +4 -4
  54. package/dist/lib/node-esm/chunk-MFJPEIVB.mjs.map +7 -0
  55. package/dist/lib/node-esm/chunk-PR7EYODX.mjs +765 -0
  56. package/dist/lib/node-esm/chunk-PR7EYODX.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-X4DDJIVL.mjs → chunk-TXMDLLBR.mjs} +3 -3
  58. package/dist/lib/node-esm/{chunk-X4DDJIVL.mjs.map → chunk-TXMDLLBR.mjs.map} +1 -1
  59. package/dist/lib/node-esm/index.mjs +30 -216
  60. package/dist/lib/node-esm/index.mjs.map +4 -4
  61. package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-WOZKOEBW.mjs} +7 -11
  62. package/dist/lib/node-esm/intent-resolver-WOZKOEBW.mjs.map +7 -0
  63. package/dist/lib/node-esm/meta.json +1 -1
  64. package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-7MXUUTAL.mjs} +11 -9
  65. package/dist/lib/node-esm/react-surface-7MXUUTAL.mjs.map +7 -0
  66. package/dist/lib/node-esm/transcriber-5WSI6DYK.mjs +57 -0
  67. package/dist/lib/node-esm/transcriber-5WSI6DYK.mjs.map +7 -0
  68. package/dist/lib/node-esm/types/index.mjs +4 -8
  69. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  70. package/dist/types/src/capabilities/capabilities.d.ts +8 -10
  71. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  72. package/dist/types/src/capabilities/index.d.ts +2 -183
  73. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  74. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  75. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  76. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/transcriber.d.ts +2 -1
  78. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  79. package/dist/types/src/components/Transcript/Transcript.d.ts +14 -4
  80. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  81. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +19 -4
  82. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  83. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
  84. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
  85. package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
  86. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
  87. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  88. package/dist/types/src/components/stories/FileTranscription.stories.d.ts +17 -0
  89. package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +1 -0
  90. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +10 -0
  91. package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +1 -0
  92. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts +19 -0
  93. package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +1 -0
  94. package/dist/types/src/components/stories/TranscriptionStory.d.ts +11 -0
  95. package/dist/types/src/components/stories/TranscriptionStory.d.ts.map +1 -0
  96. package/dist/types/src/components/stories/useIsSpeaking.d.ts +3 -0
  97. package/dist/types/src/components/stories/useIsSpeaking.d.ts.map +1 -0
  98. package/dist/types/src/hooks/index.d.ts +1 -0
  99. package/dist/types/src/hooks/index.d.ts.map +1 -1
  100. package/dist/types/src/hooks/useAudioFile.d.ts +1 -1
  101. package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
  102. package/dist/types/src/hooks/useAudioTrack.d.ts +1 -1
  103. package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
  104. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
  105. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
  106. package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
  107. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  108. package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
  109. package/dist/types/src/model/index.d.ts +2 -0
  110. package/dist/types/src/model/index.d.ts.map +1 -0
  111. package/dist/types/src/model/model.d.ts +68 -0
  112. package/dist/types/src/model/model.d.ts.map +1 -0
  113. package/dist/types/src/model/model.test.d.ts +2 -0
  114. package/dist/types/src/model/model.test.d.ts.map +1 -0
  115. package/dist/types/src/segments-normalization/index.d.ts +4 -0
  116. package/dist/types/src/segments-normalization/index.d.ts.map +1 -0
  117. package/dist/types/src/segments-normalization/message-normalizer.d.ts +26 -0
  118. package/dist/types/src/segments-normalization/message-normalizer.d.ts.map +1 -0
  119. package/dist/types/src/segments-normalization/normalization.d.ts +520 -0
  120. package/dist/types/src/segments-normalization/normalization.d.ts.map +1 -0
  121. package/dist/types/src/segments-normalization/normalization.test.d.ts +2 -0
  122. package/dist/types/src/segments-normalization/normalization.test.d.ts.map +1 -0
  123. package/dist/types/src/segments-normalization/utils.d.ts +3 -0
  124. package/dist/types/src/segments-normalization/utils.d.ts.map +1 -0
  125. package/dist/types/src/testing/index.d.ts +2 -0
  126. package/dist/types/src/testing/index.d.ts.map +1 -0
  127. package/dist/types/src/testing/testing.d.ts +40 -0
  128. package/dist/types/src/testing/testing.d.ts.map +1 -0
  129. package/dist/types/src/transcriber/media-stream-recorder.d.ts +8 -5
  130. package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
  131. package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
  132. package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
  133. package/dist/types/src/transcriber/transcriber.d.ts +6 -2
  134. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  135. package/dist/types/src/transcriber/transcription-manager.d.ts +19 -15
  136. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  137. package/dist/types/src/translations.d.ts +3 -20
  138. package/dist/types/src/translations.d.ts.map +1 -1
  139. package/dist/types/src/types/schema.d.ts +23 -40
  140. package/dist/types/src/types/schema.d.ts.map +1 -1
  141. package/dist/types/src/types/types.d.ts +15 -8
  142. package/dist/types/src/types/types.d.ts.map +1 -1
  143. package/dist/types/src/util/array.d.ts.map +1 -1
  144. package/dist/types/src/util/get-time-str.d.ts.map +1 -1
  145. package/dist/types/src/util/index.d.ts +0 -1
  146. package/dist/types/src/util/index.d.ts.map +1 -1
  147. package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
  148. package/dist/types/tsconfig.tsbuildinfo +1 -1
  149. package/package.json +62 -41
  150. package/src/TranscriptionPlugin.tsx +13 -18
  151. package/src/capabilities/capabilities.ts +15 -8
  152. package/src/capabilities/index.ts +0 -2
  153. package/src/capabilities/intent-resolver.ts +7 -8
  154. package/src/capabilities/react-surface.tsx +5 -4
  155. package/src/capabilities/transcriber.ts +34 -6
  156. package/src/components/Transcript/Transcript.stories.tsx +201 -43
  157. package/src/components/Transcript/Transcript.tsx +83 -208
  158. package/src/components/Transcript/deprecated/OldTranscript.tsx +260 -0
  159. package/src/components/Transcript/transcript-extension.ts +236 -0
  160. package/src/components/TranscriptContainer.tsx +11 -6
  161. package/src/components/stories/FileTranscription.stories.tsx +240 -0
  162. package/src/components/stories/KeyWordRecognition.stories.tsx +128 -0
  163. package/src/components/stories/MicrophoneTranscription.stories.tsx +274 -0
  164. package/src/components/stories/TranscriptionStory.tsx +41 -0
  165. package/src/components/stories/useIsSpeaking.tsx +40 -0
  166. package/src/hooks/index.ts +1 -0
  167. package/src/hooks/useAudioFile.ts +39 -9
  168. package/src/hooks/useAudioTrack.ts +2 -2
  169. package/src/hooks/useQueueModelAdapter.ts +65 -0
  170. package/src/hooks/useTranscriber.ts +4 -2
  171. package/src/meta.ts +2 -2
  172. package/src/model/index.ts +5 -0
  173. package/src/model/model.test.ts +169 -0
  174. package/src/model/model.ts +229 -0
  175. package/src/segments-normalization/index.ts +7 -0
  176. package/src/segments-normalization/message-normalizer.ts +108 -0
  177. package/src/segments-normalization/normalization.test.ts +142 -0
  178. package/src/segments-normalization/normalization.ts +103 -0
  179. package/src/segments-normalization/utils.ts +9 -0
  180. package/src/testing/index.ts +5 -0
  181. package/src/testing/testing.ts +216 -0
  182. package/src/transcriber/media-stream-recorder.ts +14 -7
  183. package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +7 -7
  184. package/src/transcriber/transcriber.ts +33 -20
  185. package/src/transcriber/transcription-manager.ts +87 -71
  186. package/src/translations.ts +2 -3
  187. package/src/types/schema.ts +11 -33
  188. package/src/types/types.ts +8 -12
  189. package/src/util/index.ts +0 -1
  190. package/dist/lib/browser/TranscriptContainer-JQ3HFHLQ.mjs +0 -10
  191. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs +0 -191
  192. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
  193. package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
  194. package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
  195. package/dist/lib/browser/chunk-GU2BNYKZ.mjs +0 -35
  196. package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
  197. package/dist/lib/browser/chunk-OGNXNGJB.mjs +0 -73
  198. package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
  199. package/dist/lib/browser/chunk-YCZISX63.mjs.map +0 -7
  200. package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
  201. package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
  202. package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs +0 -18
  203. package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
  204. package/dist/lib/browser/react-surface-FQUHU5G7.mjs.map +0 -7
  205. package/dist/lib/browser/transcriber-LGMIWDYI.mjs +0 -36
  206. package/dist/lib/browser/transcriber-LGMIWDYI.mjs.map +0 -7
  207. package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
  208. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs +0 -200
  209. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
  210. package/dist/lib/node/chunk-3KHUSQNW.cjs +0 -55
  211. package/dist/lib/node/chunk-3KHUSQNW.cjs.map +0 -7
  212. package/dist/lib/node/chunk-CZMLKKWT.cjs.map +0 -7
  213. package/dist/lib/node/chunk-FCCJALOT.cjs.map +0 -7
  214. package/dist/lib/node/chunk-P4VVD6ZO.cjs +0 -95
  215. package/dist/lib/node/chunk-P4VVD6ZO.cjs.map +0 -7
  216. package/dist/lib/node/chunk-UOFHT4GZ.cjs +0 -280
  217. package/dist/lib/node/chunk-UOFHT4GZ.cjs.map +0 -7
  218. package/dist/lib/node/intent-resolver-WMMCOUEH.cjs.map +0 -7
  219. package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs +0 -34
  220. package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs.map +0 -7
  221. package/dist/lib/node/react-surface-WI2MGWCC.cjs.map +0 -7
  222. package/dist/lib/node/transcriber-QG5WPKX4.cjs +0 -49
  223. package/dist/lib/node/transcriber-QG5WPKX4.cjs.map +0 -7
  224. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs +0 -192
  225. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
  227. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
  228. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs +0 -74
  229. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
  230. package/dist/lib/node-esm/chunk-HKATMD34.mjs +0 -37
  231. package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
  232. package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
  233. package/dist/lib/node-esm/chunk-YQYBLFNI.mjs.map +0 -7
  234. package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
  235. package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs +0 -19
  236. package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
  237. package/dist/lib/node-esm/react-surface-PCH6NTVB.mjs.map +0 -7
  238. package/dist/lib/node-esm/transcriber-TMPYZ5L4.mjs +0 -37
  239. package/dist/lib/node-esm/transcriber-TMPYZ5L4.mjs.map +0 -7
  240. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -181
  241. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  242. package/dist/types/src/capabilities/meeting-transcription-state.d.ts +0 -6
  243. package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +0 -1
  244. package/dist/types/src/components/transcription.stories.d.ts +0 -15
  245. package/dist/types/src/components/transcription.stories.d.ts.map +0 -1
  246. package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
  247. package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
  248. package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
  249. package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
  250. package/src/capabilities/app-graph-builder.ts +0 -152
  251. package/src/capabilities/meeting-transcription-state.ts +0 -16
  252. package/src/components/transcription.stories.tsx +0 -196
  253. package/src/util/random-queue-dxn.ts +0 -9
  254. /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-PFKLKRPQ.mjs.map} +0 -0
  255. /package/dist/lib/browser/{chunk-K7FOYAXO.mjs.map → chunk-IGEKO7ZU.mjs.map} +0 -0
  256. /package/dist/lib/node/{chunk-LP4FG423.cjs.map → chunk-5J3FBCON.cjs.map} +0 -0
  257. /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-DLNRFVTA.mjs.map} +0 -0
  258. /package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs.map → chunk-KUWWHMOA.mjs.map} +0 -0
@@ -0,0 +1,274 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react';
8
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
9
+
10
+ import { AIServiceEdgeClient } from '@dxos/ai';
11
+ import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
12
+ import { Events, IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
13
+ import { withPluginManager } from '@dxos/app-framework/testing';
14
+ import {
15
+ extractionAnthropicFn,
16
+ type ExtractionFunction,
17
+ extractionNerFn,
18
+ processTranscriptMessage,
19
+ getNer,
20
+ } from '@dxos/assistant';
21
+ import { Filter, MemoryQueue } from '@dxos/echo-db';
22
+ import { create, createQueueDxn, type Expando } from '@dxos/echo-schema';
23
+ import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
24
+ import { invariant } from '@dxos/invariant';
25
+ import { log } from '@dxos/log';
26
+ import { ClientPlugin } from '@dxos/plugin-client';
27
+ import { PreviewPlugin } from '@dxos/plugin-preview';
28
+ import { SpacePlugin } from '@dxos/plugin-space';
29
+ import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
30
+ import { ThemePlugin } from '@dxos/plugin-theme';
31
+ import { IndexKind, useSpace } from '@dxos/react-client/echo';
32
+ import { defaultTx } from '@dxos/react-ui-theme';
33
+ import { DataType } from '@dxos/schema';
34
+ import { seedTestData, Testing } from '@dxos/schema/testing';
35
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
36
+
37
+ import { TranscriptionStory } from './TranscriptionStory';
38
+ import { useIsSpeaking } from './useIsSpeaking';
39
+ import { TranscriptionPlugin } from '../../TranscriptionPlugin';
40
+ import { useAudioTrack, useQueueModelAdapter, useTranscriber } from '../../hooks';
41
+ import { TestItem } from '../../testing';
42
+ import { type MediaStreamRecorderParams, type TranscriberParams } from '../../transcriber';
43
+ import { renderMarkdown } from '../Transcript';
44
+
45
+ const TRANSCRIBER_CONFIG = {
46
+ transcribeAfterChunksAmount: 50,
47
+ prefixBufferChunksAmount: 10,
48
+ };
49
+
50
+ const RECORDER_CONFIG = {
51
+ interval: 200,
52
+ };
53
+
54
+ type StoryProps = {
55
+ detectSpeaking?: boolean;
56
+ transcriberConfig: TranscriberParams['config'];
57
+ recorderConfig: MediaStreamRecorderParams['config'];
58
+ audioConstraints?: MediaTrackConstraints;
59
+ entityExtraction: 'none' | 'ner' | 'llm';
60
+ };
61
+
62
+ const DefaultStory = ({
63
+ detectSpeaking,
64
+ entityExtraction = 'none',
65
+ transcriberConfig,
66
+ recorderConfig,
67
+ audioConstraints,
68
+ }: StoryProps) => {
69
+ const [running, setRunning] = useState(false);
70
+
71
+ // Audio.
72
+ const track = useAudioTrack(running, audioConstraints);
73
+
74
+ // Speaking.
75
+ const isSpeaking = detectSpeaking ? useIsSpeaking(track) : true;
76
+
77
+ // Queue.
78
+ const queueDxn = useMemo(() => createQueueDxn(), []);
79
+ const queue = useMemo(() => new MemoryQueue<DataType.Message>(queueDxn), [queueDxn]);
80
+ const model = useQueueModelAdapter(renderMarkdown([]), queue);
81
+ const space = useSpace();
82
+
83
+ useEffect(() => {
84
+ if (!space) {
85
+ log.warn('no space');
86
+ }
87
+ }, [space]);
88
+
89
+ // Entity extraction.
90
+ const { extractionFunction, executor, objects } = useMemo(() => {
91
+ if (!space) {
92
+ log.warn('no space');
93
+ return {};
94
+ }
95
+
96
+ let executor: FunctionExecutor | undefined;
97
+ let extractionFunction: ExtractionFunction | undefined;
98
+ let objects: Promise<Expando[]> | undefined;
99
+
100
+ if (entityExtraction === 'ner') {
101
+ // Init model loading. Takes time.
102
+ void getNer();
103
+ extractionFunction = extractionNerFn;
104
+ } else if (entityExtraction === 'llm') {
105
+ extractionFunction = extractionAnthropicFn;
106
+ objects = space.db
107
+ .query(
108
+ Filter.or(
109
+ Filter.type(DataType.Person),
110
+ Filter.type(DataType.Organization),
111
+ Filter.type(Testing.DocumentType),
112
+ ),
113
+ )
114
+ .run()
115
+ .then((result) => result.objects);
116
+ }
117
+ if (entityExtraction !== 'none') {
118
+ const aiService = new AIServiceEdgeClient({
119
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
120
+ });
121
+ executor = new FunctionExecutor(
122
+ new ServiceContainer().setServices({ ai: { client: aiService }, database: { db: space!.db } }),
123
+ );
124
+ }
125
+
126
+ return { extractionFunction, executor, objects };
127
+ }, [entityExtraction, space]);
128
+
129
+ // Transcriber.
130
+ const handleSegments = useCallback<TranscriberParams['onSegments']>(
131
+ async (blocks) => {
132
+ const message = create(DataType.Message, { sender: { name: 'You' }, created: new Date().toISOString(), blocks });
133
+ if (!space) {
134
+ void queue.append([message]);
135
+ return;
136
+ }
137
+
138
+ if (entityExtraction !== 'none') {
139
+ invariant(extractionFunction, 'extractionFunction is required');
140
+ invariant(executor, 'executor is required');
141
+ const result = await processTranscriptMessage({
142
+ executor,
143
+ function: extractionFunction,
144
+ input: {
145
+ message,
146
+ objects: await objects,
147
+ },
148
+ options: {
149
+ fallbackToRaw: true,
150
+ timeout: 30_000,
151
+ },
152
+ });
153
+ void queue.append([result.message]);
154
+ } else {
155
+ void queue.append([message]);
156
+ }
157
+ },
158
+ [queue, space],
159
+ );
160
+
161
+ const transcriber = useTranscriber({
162
+ audioStreamTrack: track,
163
+ onSegments: handleSegments,
164
+ transcriberConfig,
165
+ recorderConfig,
166
+ });
167
+ const [isOpen, setIsOpen] = useState(false);
168
+ useEffect(() => {
169
+ void transcriber?.open().then(() => setIsOpen(true));
170
+ return () => {
171
+ void transcriber?.close().then(() => setIsOpen(false));
172
+ };
173
+ }, [transcriber]);
174
+
175
+ // Manage transcription state.
176
+ useEffect(() => {
177
+ if (running && transcriber?.isOpen && isSpeaking) {
178
+ transcriber?.startChunksRecording();
179
+ } else if (!running || !isSpeaking) {
180
+ transcriber?.stopChunksRecording();
181
+ }
182
+ }, [transcriber, running, isOpen, isSpeaking]);
183
+
184
+ return <TranscriptionStory model={model} running={running} onRunningChange={setRunning} />;
185
+ };
186
+
187
+ const meta: Meta<typeof DefaultStory> = {
188
+ title: 'plugins/plugin-transcription/MicrophoneTranscription',
189
+ render: DefaultStory,
190
+ decorators: [
191
+ withPluginManager({
192
+ plugins: [
193
+ ThemePlugin({ tx: defaultTx }),
194
+ StorybookLayoutPlugin(),
195
+ ClientPlugin({
196
+ types: [TestItem, DataType.Person, DataType.Organization, Testing.DocumentType],
197
+ onClientInitialized: async (_, client) => {
198
+ await client.halo.createIdentity();
199
+ await client.spaces.waitUntilReady();
200
+ await client.spaces.default.waitUntilReady();
201
+ // TODO(mykola): Make API easier to use.
202
+ // TODO(mykola): Delete after enabling vector indexing by default.
203
+ // Enable vector indexing.
204
+ await client.services.services.QueryService!.setConfig({
205
+ enabled: true,
206
+ indexes: [
207
+ //
208
+ { kind: IndexKind.Kind.SCHEMA_MATCH },
209
+ { kind: IndexKind.Kind.GRAPH },
210
+ { kind: IndexKind.Kind.VECTOR },
211
+ ],
212
+ });
213
+ await client.services.services.QueryService!.reindex();
214
+ await seedTestData(client.spaces.default);
215
+ },
216
+ }),
217
+ SpacePlugin(),
218
+ SettingsPlugin(),
219
+ PreviewPlugin(),
220
+ IntentPlugin(),
221
+ TranscriptionPlugin(),
222
+ ],
223
+ fireEvents: [Events.SetupAppGraph],
224
+ }),
225
+ withTheme,
226
+ withLayout({ fullscreen: true, classNames: 'justify-center' }),
227
+ ],
228
+ };
229
+
230
+ export default meta;
231
+
232
+ type Story = StoryObj<typeof DefaultStory>;
233
+
234
+ export const Default: Story = {
235
+ args: {
236
+ detectSpeaking: false,
237
+ entityExtraction: 'none',
238
+ transcriberConfig: TRANSCRIBER_CONFIG,
239
+ recorderConfig: RECORDER_CONFIG,
240
+ audioConstraints: {
241
+ echoCancellation: false,
242
+ noiseSuppression: false,
243
+ autoGainControl: false,
244
+ },
245
+ },
246
+ };
247
+
248
+ export const EntityExtraction: Story = {
249
+ args: {
250
+ detectSpeaking: true,
251
+ entityExtraction: 'ner',
252
+ transcriberConfig: TRANSCRIBER_CONFIG,
253
+ recorderConfig: RECORDER_CONFIG,
254
+ audioConstraints: {
255
+ echoCancellation: true,
256
+ noiseSuppression: true,
257
+ autoGainControl: true,
258
+ },
259
+ },
260
+ };
261
+
262
+ export const SpeechDetection: Story = {
263
+ args: {
264
+ detectSpeaking: true,
265
+ entityExtraction: 'none',
266
+ transcriberConfig: TRANSCRIBER_CONFIG,
267
+ recorderConfig: RECORDER_CONFIG,
268
+ audioConstraints: {
269
+ echoCancellation: true,
270
+ noiseSuppression: true,
271
+ autoGainControl: true,
272
+ },
273
+ },
274
+ };
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import React, { type Dispatch, type FC, type RefObject, type SetStateAction } from 'react';
8
+
9
+ import { useSpace } from '@dxos/react-client/echo';
10
+ import { IconButton, Toolbar } from '@dxos/react-ui';
11
+ import { ScrollContainer } from '@dxos/react-ui-components';
12
+ import { type DataType } from '@dxos/schema';
13
+
14
+ import { type SerializationModel } from '../../model';
15
+ import { Transcript } from '../Transcript';
16
+
17
+ export const TranscriptionStory: FC<{
18
+ model: SerializationModel<DataType.Message>;
19
+ running: boolean;
20
+ onRunningChange: Dispatch<SetStateAction<boolean>>;
21
+ audioRef?: RefObject<HTMLAudioElement>;
22
+ }> = ({ model, running, onRunningChange, audioRef }) => {
23
+ const space = useSpace();
24
+
25
+ return (
26
+ <div className='flex flex-col w-[30rem]'>
27
+ {audioRef && <audio ref={audioRef} autoPlay />}
28
+ <Toolbar.Root>
29
+ <IconButton
30
+ iconOnly
31
+ icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
32
+ label={running ? 'Pause' : 'Play'}
33
+ onClick={() => onRunningChange((running) => !running)}
34
+ />
35
+ </Toolbar.Root>
36
+ <ScrollContainer>
37
+ <Transcript space={space} model={model} attendableId='story' />
38
+ </ScrollContainer>
39
+ </div>
40
+ );
41
+ };
@@ -0,0 +1,40 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { useEffect, useMemo, useState } from 'react';
8
+
9
+ import { scheduleTask } from '@dxos/async';
10
+ import { Context } from '@dxos/context';
11
+ import { SpeakingMonitor } from '@dxos/plugin-thread';
12
+
13
+ export const useIsSpeaking = (track?: MediaStreamTrack) => {
14
+ const [isSpeaking, setIsSpeaking] = useState(false);
15
+ const speakingMonitor = useMemo(() => {
16
+ if (!track) {
17
+ return;
18
+ }
19
+ return new SpeakingMonitor(track);
20
+ }, [track]);
21
+
22
+ useEffect(() => {
23
+ if (!speakingMonitor) {
24
+ return;
25
+ }
26
+
27
+ const ctx = new Context();
28
+ scheduleTask(ctx, async () => {
29
+ speakingMonitor.speakingChanged.on(ctx, () => setIsSpeaking(speakingMonitor.isSpeaking));
30
+ await speakingMonitor.open();
31
+ ctx.onDispose(() => speakingMonitor.close());
32
+ });
33
+
34
+ return () => {
35
+ void ctx.dispose();
36
+ };
37
+ }, [speakingMonitor]);
38
+
39
+ return isSpeaking;
40
+ };
@@ -4,5 +4,6 @@
4
4
 
5
5
  export * from './useAudioFile';
6
6
  export * from './useAudioTrack';
7
+ export * from './useQueueModelAdapter';
7
8
  export * from './useTranscriber';
8
9
  export * from './useVoiceInput';
@@ -4,6 +4,8 @@
4
4
 
5
5
  import { useEffect, useState } from 'react';
6
6
 
7
+ import { scheduleTask } from '@dxos/async';
8
+ import { Context } from '@dxos/context';
7
9
  import { log } from '@dxos/log';
8
10
 
9
11
  export type UseAudioState = {
@@ -12,14 +14,22 @@ export type UseAudioState = {
12
14
  track?: MediaStreamTrack;
13
15
  };
14
16
 
15
- export const useAudioFile = (audioUrl: string): UseAudioState => {
17
+ export const useAudioFile = (audioUrl: string, constraints?: MediaTrackConstraints): UseAudioState => {
16
18
  const [{ audio, stream, track }, setStream] = useState<UseAudioState>({});
17
19
  useEffect(() => {
18
- const t = setTimeout(async () => {
20
+ const ctx = new Context();
21
+
22
+ scheduleTask(ctx, async () => {
19
23
  const response = await fetch(audioUrl);
20
24
  const blob = await response.blob();
25
+ const objectUrl = URL.createObjectURL(blob);
26
+ ctx.onDispose(() => {
27
+ URL.revokeObjectURL(objectUrl);
28
+ });
29
+
21
30
  const audio = new Audio();
22
- audio.src = URL.createObjectURL(blob);
31
+ audio.src = objectUrl;
32
+
23
33
  await new Promise<void>((resolve, reject) => {
24
34
  audio.addEventListener(
25
35
  'error',
@@ -31,23 +41,41 @@ export const useAudioFile = (audioUrl: string): UseAudioState => {
31
41
  );
32
42
  audio.addEventListener(
33
43
  'canplay',
34
- () => {
44
+ async () => {
35
45
  log.info('starting...');
36
- resolve();
46
+ try {
47
+ // Try to play the audio
48
+ await audio.play();
49
+ resolve();
50
+ } catch (playError) {
51
+ log.error('Play failed', { playError });
52
+ // Still resolve as the audio is ready, even if autoplay failed.
53
+ resolve();
54
+ }
37
55
  },
38
56
  { once: true },
39
57
  );
40
58
  audio.load();
41
59
  });
42
60
 
43
- const ctx = new AudioContext();
61
+ const audioCtx = new AudioContext();
62
+
63
+ // Resume AudioContext if it's suspended.
64
+ if (audioCtx.state === 'suspended') {
65
+ await audioCtx.resume();
66
+ }
44
67
 
45
- const destination = ctx.createMediaStreamDestination();
68
+ const destination = audioCtx.createMediaStreamDestination();
46
69
  destination.channelCount = 1;
47
70
 
48
- const source = ctx.createMediaElementSource(audio);
71
+ const source = audioCtx.createMediaElementSource(audio);
49
72
  source.connect(destination);
73
+ const track = destination.stream.getAudioTracks()[0];
74
+ if (constraints) {
75
+ await track.applyConstraints(constraints).catch((err) => log.catch(err));
76
+ }
50
77
 
78
+ // Also connect to speakers so audio is audible.
51
79
  setStream({
52
80
  audio,
53
81
  stream: destination.stream,
@@ -55,7 +83,9 @@ export const useAudioFile = (audioUrl: string): UseAudioState => {
55
83
  });
56
84
  });
57
85
 
58
- return () => clearTimeout(t);
86
+ return () => {
87
+ void ctx.dispose();
88
+ };
59
89
  }, [audioUrl]);
60
90
 
61
91
  return { audio, stream, track };
@@ -8,7 +8,7 @@ import { useEffect, useState } from 'react';
8
8
  *
9
9
  */
10
10
  // TODO(burdon): Reconcile with react-ui-sfx and plugin-calls.
11
- export const useAudioTrack = (active?: boolean): MediaStreamTrack | undefined => {
11
+ export const useAudioTrack = (active?: boolean, constraints?: MediaTrackConstraints): MediaStreamTrack | undefined => {
12
12
  const [track, setTrack] = useState<MediaStreamTrack>();
13
13
  useEffect(() => {
14
14
  if (!active) {
@@ -19,7 +19,7 @@ export const useAudioTrack = (active?: boolean): MediaStreamTrack | undefined =>
19
19
 
20
20
  const initAudio = async () => {
21
21
  const audio = new Audio();
22
- audio.srcObject = await navigator.mediaDevices.getUserMedia({ audio: true });
22
+ audio.srcObject = await navigator.mediaDevices.getUserMedia({ audio: constraints ?? true });
23
23
  const [track] = audio.srcObject.getAudioTracks();
24
24
  if (track) {
25
25
  setTrack(track);
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo, useEffect, useState } from 'react';
6
+
7
+ import { type Queue } from '@dxos/react-client/echo';
8
+
9
+ import { type Chunk, type ChunkRenderer, SerializationModel } from '../model';
10
+
11
+ /**
12
+ * Model adapter for a queue.
13
+ */
14
+ export const useQueueModelAdapter = <T extends Chunk>(
15
+ renderer: ChunkRenderer<T>,
16
+ queue: Queue<T> | undefined,
17
+ initialChunks: T[] = [],
18
+ ): SerializationModel<T> => {
19
+ const model = useMemo(() => new SerializationModel<T>(renderer, initialChunks), [queue]);
20
+ const [loaded, setLoaded] = useState(false);
21
+
22
+ // Set initial blocks.
23
+ useEffect(() => {
24
+ if (!queue) {
25
+ return;
26
+ }
27
+
28
+ const update = () => {
29
+ for (const block of queue?.objects ?? []) {
30
+ model.appendChunk(block);
31
+ }
32
+
33
+ setLoaded(true);
34
+ };
35
+
36
+ // TODO(burdon): This is a hack to ensure the queue is loaded.
37
+ let i: NodeJS.Timeout | undefined;
38
+ if (queue.isLoading) {
39
+ i = setInterval(() => {
40
+ if (!queue?.isLoading) {
41
+ clearInterval(i);
42
+ update();
43
+ }
44
+ }, 1_000);
45
+ } else {
46
+ update();
47
+ }
48
+
49
+ return () => clearTimeout(i);
50
+ }, [model, queue]);
51
+
52
+ // TODO(burdon): Can we listen for queue events?
53
+ useEffect(() => {
54
+ if (!loaded || !queue || model.chunks.length === queue.objects.length) {
55
+ return;
56
+ }
57
+
58
+ const chunk = queue.objects.at(-1);
59
+ if (chunk) {
60
+ model.appendChunk(chunk);
61
+ }
62
+ }, [model, loaded, queue?.objects.length]);
63
+
64
+ return model;
65
+ };
@@ -15,6 +15,8 @@ import { type Transcriber } from '../transcriber';
15
15
  export const useTranscriber = ({
16
16
  audioStreamTrack,
17
17
  onSegments,
18
+ transcriberConfig,
19
+ recorderConfig,
18
20
  }: Partial<TranscriptionCapabilities.GetTranscriberProps>) => {
19
21
  const [getTranscriber] = useCapabilities(TranscriptionCapabilities.Transcriber);
20
22
 
@@ -24,8 +26,8 @@ export const useTranscriber = ({
24
26
  return undefined;
25
27
  }
26
28
 
27
- return getTranscriber({ audioStreamTrack, onSegments });
28
- }, [audioStreamTrack, onSegments, getTranscriber]);
29
+ return getTranscriber({ audioStreamTrack, onSegments, transcriberConfig, recorderConfig });
30
+ }, [audioStreamTrack, onSegments, getTranscriber, transcriberConfig, recorderConfig]);
29
31
 
30
32
  useEffect(() => {
31
33
  return () => {
package/src/meta.ts CHANGED
@@ -11,7 +11,7 @@ export const meta: PluginMeta = {
11
11
  name: 'Transcription',
12
12
  description: 'Voice transcription.',
13
13
  icon: 'ph--microphone--regular',
14
- source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/experimental/plugin-transcription',
15
- tags: ['experimental'],
14
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-transcription',
15
+ tags: ['labs'],
16
16
  screenshots: [],
17
17
  };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './model';