@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,103 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+
7
+ import { DEFAULT_EDGE_MODEL } from '@dxos/ai';
8
+ import { AISession } from '@dxos/assistant';
9
+ import { AiService, defineFunction } from '@dxos/functions';
10
+ import { ObjectId } from '@dxos/keys';
11
+ import { log } from '@dxos/log';
12
+ import { DataType } from '@dxos/schema';
13
+
14
+ const MessageWithRangeId = Schema.extend(
15
+ DataType.Message,
16
+ Schema.Struct({
17
+ // TODO(mykola): Move to meta in DataType.Message. Use `DataType.Message` instead.
18
+ rangeId: Schema.optional(Schema.Array(Schema.String)).annotations({
19
+ description: 'The IDs of the messages that contain the sentences.',
20
+ }),
21
+ }),
22
+ );
23
+
24
+ export interface MessageWithRangeId extends Schema.Schema.Type<typeof MessageWithRangeId> {}
25
+
26
+ export const NormalizationInput = Schema.Struct({
27
+ // TODO(mykola): Move to meta in DataType.Message. Use `DataType.Message` instead.
28
+ messages: Schema.Array(MessageWithRangeId).annotations({
29
+ description: 'Messages to normalize into sentences.',
30
+ }),
31
+ });
32
+
33
+ export const NormalizationOutput = Schema.Struct({
34
+ sentences: Schema.Array(MessageWithRangeId.pipe(Schema.mutable)).pipe(Schema.mutable).annotations({
35
+ description: 'The sentences of the transcript.',
36
+ }),
37
+ });
38
+
39
+ const prompt = `
40
+ You are observing a real-time transcript of a single person speaking.
41
+ The transcription is delivered in chunks of 10 seconds or less. As a result, individual sentences may be split across multiple messages, or multiple sentences may appear within a single message. Additionally, because this is real-time transcription, punctuation and capitalization may be incorrect or missing.
42
+
43
+ # Task Description:
44
+ - Your task is to detect and reconstruct broken or incomplete sentences by merging segments into coherent, grammatically correct sentences where appropriate.
45
+
46
+ # Input Format:
47
+ - The input is an array of messages that contain transcription blocks.
48
+ - Each message is a DataType.Message, as described in the output format.
49
+
50
+ # Output Format:
51
+ - You have been provided with the tool that defines the output format; make sure to query it.
52
+ - Do not output anything other than the expected format.
53
+
54
+ # Segment Handling Rules:
55
+ - Sort messages by timestamp.
56
+ - Leave ID of the first message in a sentence.
57
+ - Each divided sentence should be added to the output as a separate message or block within existing messages, preserving the original order.
58
+ - If one message contains multiple sentences, you should not split them.
59
+ - The last sentence may be incomplete; it should still be output as a separate block or message.
60
+ - Keep track of the original timestamps of the messages and blocks and, use the timestamp of the first message/block of a sentence as the 'started' field of the merged message.
61
+ - The 'rangeId' field of the merged message should include 'messageId'-s and 'rangeId'-s of all messages that have been used to construct this message.
62
+
63
+ # Punctuation and Capitalization:
64
+ - Do not rely on the original punctuation and capitalization—they may be incorrect.
65
+ - Use logical reasoning to apply appropriate punctuation (e.g., period, comma, question mark, exclamation mark) and capitalization.
66
+
67
+ # Restrictions:
68
+ - Do not alter the order of the original messages; maintain the natural flow of speech.
69
+ - Do not interpret or infer meaning beyond reconstructing sentences.
70
+ - Do not add or remove any words or phrases.
71
+ `;
72
+
73
+ export const sentenceNormalization = defineFunction({
74
+ description: 'Post process of transcription for sentence normalization',
75
+ inputSchema: NormalizationInput,
76
+ outputSchema: NormalizationOutput,
77
+ handler: async ({ data: { messages }, context }) => {
78
+ log.info('input', { messages });
79
+ const ai = context.getService(AiService);
80
+ const session = new AISession({ operationModel: 'configured' });
81
+
82
+ const response = await session.runStructured(NormalizationOutput, {
83
+ generationOptions: { model: DEFAULT_EDGE_MODEL },
84
+ client: ai.client,
85
+ tools: [],
86
+ artifacts: [],
87
+ history: [
88
+ {
89
+ id: ObjectId.random(),
90
+ role: 'user',
91
+ content: messages.map((message) => ({ type: 'text', text: JSON.stringify(message) })),
92
+ },
93
+ ],
94
+ prompt,
95
+ });
96
+
97
+ response.sentences.forEach((sentence) => {
98
+ sentence.id = ObjectId.random();
99
+ });
100
+ log.info('response', { response });
101
+ return response;
102
+ },
103
+ });
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type DataType } from '@dxos/schema';
6
+
7
+ export const getActorId = (actor: DataType.Actor) => {
8
+ return actor.identityDid || actor.identityKey || actor.email || actor.name || '';
9
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './testing';
@@ -0,0 +1,216 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+ import { useEffect, useMemo, useState } from 'react';
7
+
8
+ import { AIServiceEdgeClient } from '@dxos/ai';
9
+ import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
10
+ import { extractionAnthropicFn, processTranscriptMessage } from '@dxos/assistant';
11
+ import { scheduleTaskInterval } from '@dxos/async';
12
+ import { Filter, type Queue } from '@dxos/client/echo';
13
+ import { Context } from '@dxos/context';
14
+ import { Type } from '@dxos/echo';
15
+ import { create, createQueueDxn, ObjectId } from '@dxos/echo-schema';
16
+ import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
17
+ import { IdentityDid } from '@dxos/keys';
18
+ import { log } from '@dxos/log';
19
+ import { faker } from '@dxos/random';
20
+ import { live, makeRef, useQueue, type Space } from '@dxos/react-client/echo';
21
+ import { DataType } from '@dxos/schema';
22
+ import { Testing, seedTestData } from '@dxos/schema/testing';
23
+
24
+ // TODO(burdon): Reconcile with plugin-markdown. Move to @dxos/schema/testing.
25
+ export const TestItem = Schema.Struct({
26
+ title: Schema.String.annotations({
27
+ title: 'Title',
28
+ description: 'Product title',
29
+ }),
30
+ description: Schema.String.annotations({
31
+ title: 'Description',
32
+ description: 'Product description',
33
+ }),
34
+ }).pipe(
35
+ Type.Obj({
36
+ typename: 'dxos.org/type/Test',
37
+ version: '0.1.0',
38
+ }),
39
+ );
40
+
41
+ // TODO(wittjosiah): Make builder generic and reuse for all message types.
42
+ abstract class AbstractMessageBuilder {
43
+ abstract createMessage(numSegments?: number): Promise<DataType.Message>;
44
+ }
45
+
46
+ /**
47
+ * Generator of transcript messages.
48
+ */
49
+ export class MessageBuilder extends AbstractMessageBuilder {
50
+ static readonly singleton = new MessageBuilder();
51
+
52
+ users = Array.from({ length: 5 }, () => ({
53
+ identityDid: IdentityDid.random().toString(),
54
+ name: faker.person.fullName(),
55
+ email: faker.internet.email(),
56
+ }));
57
+
58
+ start = new Date(Date.now() - 24 * 60 * 60 * 10_000);
59
+
60
+ constructor(private readonly _space?: Space) {
61
+ super();
62
+ }
63
+
64
+ override async createMessage(numSegments = 1): Promise<DataType.Message> {
65
+ return {
66
+ id: ObjectId.random().toString(),
67
+ created: this.next().toISOString(),
68
+ sender: faker.helpers.arrayElement(this.users),
69
+ blocks: Array.from({ length: numSegments }).map(() => this.createBlock()),
70
+ };
71
+ }
72
+
73
+ createBlock(): DataType.MessageBlock.Transcription {
74
+ let text = faker.lorem.paragraph();
75
+ if (this._space) {
76
+ const label = faker.commerce.productName();
77
+ const obj = this._space.db.add(live(TestItem, { title: label, description: faker.lorem.paragraph() }));
78
+ const dxn = makeRef(obj).dxn.toString();
79
+ const words = text.split(' ');
80
+ words.splice(Math.floor(Math.random() * words.length), 0, `[${label}][${dxn}]`);
81
+ text = words.join(' ');
82
+ }
83
+
84
+ return {
85
+ type: 'transcription',
86
+ started: this.next().toISOString(),
87
+ text,
88
+ };
89
+ }
90
+
91
+ next(): Date {
92
+ this.start = new Date(this.start.getTime() + Math.random() * 10_000);
93
+ return this.start;
94
+ }
95
+ }
96
+
97
+ // TODO(burdon): Reconcile with BlockBuilder.
98
+ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
99
+ aiService = new AIServiceEdgeClient({
100
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
101
+ });
102
+
103
+ executor = new FunctionExecutor(new ServiceContainer().setServices({ ai: { client: this.aiService } }));
104
+
105
+ space: Space | undefined;
106
+ currentMessage: number = 0;
107
+ transcriptMessages: DataType.Message[] = [];
108
+
109
+ async connect(space: Space): Promise<void> {
110
+ this.space = space;
111
+ const { transcriptMessages } = await seedTestData(space);
112
+ this.transcriptMessages = transcriptMessages;
113
+ }
114
+
115
+ override async createMessage(): Promise<DataType.Message> {
116
+ if (!this.space) {
117
+ throw new Error('Space not connected');
118
+ }
119
+
120
+ const { objects } = await this.space.db
121
+ .query(
122
+ Filter.or(Filter.type(DataType.Person), Filter.type(DataType.Organization), Filter.type(Testing.DocumentType)),
123
+ )
124
+ .run();
125
+
126
+ log.info('context', { objects });
127
+ const message = this.transcriptMessages[this.currentMessage];
128
+ this.currentMessage++;
129
+ this.currentMessage = this.currentMessage % this.transcriptMessages.length;
130
+
131
+ const { message: enhancedMessage } = await processTranscriptMessage({
132
+ input: { message },
133
+ executor: this.executor,
134
+ function: extractionAnthropicFn,
135
+ });
136
+
137
+ return enhancedMessage;
138
+ }
139
+ }
140
+
141
+ type UseTestTranscriptionQueue = (
142
+ space: Space | undefined,
143
+ queueId?: ObjectId,
144
+ running?: boolean,
145
+ interval?: number,
146
+ ) => Queue<DataType.Message> | undefined;
147
+
148
+ /**
149
+ * Test transcriptionqueue.
150
+ */
151
+ export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
152
+ space: Space | undefined,
153
+ queueId?: ObjectId,
154
+ running = true,
155
+ interval = 1_000,
156
+ ) => {
157
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
158
+ const queue = useQueue<DataType.Message>(queueDxn);
159
+ const builder = useMemo(() => new MessageBuilder(space), [space]);
160
+
161
+ useEffect(() => {
162
+ if (!queue || !running) {
163
+ return;
164
+ }
165
+
166
+ const i = setInterval(() => {
167
+ void builder.createMessage(Math.ceil(Math.random() * 3)).then((message) => {
168
+ queue.append([create(DataType.Message, message)]);
169
+ });
170
+ }, interval);
171
+ return () => clearInterval(i);
172
+ }, [queue, running, interval]);
173
+
174
+ return queue;
175
+ };
176
+
177
+ /**
178
+ * Test transcription queue.
179
+ */
180
+ // TODO(burdon): Reconcile with useTestTranscriptionQueue.
181
+ export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscriptionQueue = (
182
+ space: Space | undefined,
183
+ queueId?: ObjectId,
184
+ running = true,
185
+ interval = 1_000,
186
+ ) => {
187
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
188
+ const queue = useQueue<DataType.Message>(queueDxn);
189
+ const [builder] = useState(() => new EntityExtractionMessageBuilder());
190
+
191
+ useEffect(() => {
192
+ if (!queue || !running) {
193
+ return;
194
+ }
195
+
196
+ if (space) {
197
+ void builder.connect(space);
198
+ }
199
+
200
+ const ctx = new Context();
201
+ scheduleTaskInterval(
202
+ ctx,
203
+ async () => {
204
+ const message = await builder.createMessage();
205
+ queue.append([create(DataType.Message, message)]);
206
+ },
207
+ interval,
208
+ );
209
+
210
+ return () => {
211
+ void ctx.dispose();
212
+ };
213
+ }, [space, queue, running, interval]);
214
+
215
+ return queue;
216
+ };
@@ -19,6 +19,13 @@ const initializeExtendableMediaRecorder = async () => {
19
19
  await register(await connect());
20
20
  };
21
21
 
22
+ export type MediaStreamRecorderParams = {
23
+ mediaStreamTrack: MediaStreamTrack;
24
+ config: {
25
+ interval: number;
26
+ };
27
+ };
28
+
22
29
  /**
23
30
  * Recorder that uses the MediaContext API and AudioNode API to record audio.
24
31
  *
@@ -30,14 +37,14 @@ export class MediaStreamRecorder implements AudioRecorder {
30
37
  /**
31
38
  * Default MediaRecorder implementation do not support wav encoding.
32
39
  */
33
- private readonly _interval: number;
40
+ private readonly _config: MediaStreamRecorderParams['config'];
34
41
  private _mediaRecorder?: IMediaRecorder = undefined;
35
42
  private _header?: Uint8Array = undefined;
36
43
  private _onChunk?: (chunk: AudioChunk) => void;
37
44
 
38
- constructor({ mediaStreamTrack, interval }: { mediaStreamTrack: MediaStreamTrack; interval: number }) {
45
+ constructor({ mediaStreamTrack, config }: MediaStreamRecorderParams) {
39
46
  this._mediaStreamTrack = mediaStreamTrack;
40
- this._interval = interval;
47
+ this._config = config;
41
48
  }
42
49
 
43
50
  get wavConfig(): WavConfig {
@@ -57,7 +64,7 @@ export class MediaStreamRecorder implements AudioRecorder {
57
64
  this._onChunk = onChunk;
58
65
  }
59
66
 
60
- async start() {
67
+ async start(): Promise<void> {
61
68
  await (initializingPromise ??= initializeExtendableMediaRecorder()).catch((err) =>
62
69
  log.info('initializeExtendableMediaRecorder', { err }),
63
70
  );
@@ -72,10 +79,10 @@ export class MediaStreamRecorder implements AudioRecorder {
72
79
  }
73
80
  invariant(this._onChunk, 'MediaStreamRecorder: onChunk is not set');
74
81
  this._mediaRecorder.ondataavailable = (event) => this._ondataavailable(event);
75
- this._mediaRecorder.start(this._interval);
82
+ this._mediaRecorder.start(this._config.interval);
76
83
  }
77
84
 
78
- async stop() {
85
+ async stop(): Promise<void> {
79
86
  if (this._mediaRecorder?.state !== 'recording') {
80
87
  return;
81
88
  }
@@ -83,7 +90,7 @@ export class MediaStreamRecorder implements AudioRecorder {
83
90
  }
84
91
 
85
92
  @synchronized
86
- private async _ondataavailable(event: IBlobEvent) {
93
+ private async _ondataavailable(event: IBlobEvent): Promise<void> {
87
94
  const blob = event.data;
88
95
  const uint8Array = new Uint8Array(await blob.arrayBuffer());
89
96
 
@@ -9,11 +9,11 @@ import { WaveFile } from 'wavefile';
9
9
 
10
10
  import { Trigger } from '@dxos/async';
11
11
  import { log } from '@dxos/log';
12
+ import { type DataType } from '@dxos/schema';
12
13
  import { openAndClose } from '@dxos/test-utils';
13
14
  import { trace, TRACE_PROCESSOR } from '@dxos/tracing';
14
15
 
15
16
  import { type AudioChunk, type AudioRecorder, Transcriber } from '../transcriber';
16
- import { type TranscriptSegment } from '../types';
17
17
  import { mergeFloat64Arrays } from '../util';
18
18
 
19
19
  // TODO(burdon): ReferenceError: Worker is not defined (Can only run with web workers).
@@ -93,7 +93,7 @@ describe.skip('Transcriber', () => {
93
93
  });
94
94
 
95
95
  test.skip('transcription of audio recording', { timeout: 10_000 }, async () => {
96
- const trigger = new Trigger<TranscriptSegment[]>({ autoReset: true });
96
+ const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
97
97
  const recorder = new MockAudioRecorder({
98
98
  buffer: await readFile('test.wav'),
99
99
  chunkDuration: 3_000,
@@ -127,7 +127,7 @@ describe.skip('Transcriber', () => {
127
127
  });
128
128
 
129
129
  test.skip('transcription of audio recording with overlapping chunks', { timeout: 20_000 }, async () => {
130
- const trigger = new Trigger<TranscriptSegment[]>({ autoReset: true });
130
+ const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
131
131
  const recorder = new MockAudioRecorder({
132
132
  buffer: await readFile('test.wav'),
133
133
  chunkDuration: 3_000,
@@ -194,14 +194,14 @@ class MockAudioRecorder implements AudioRecorder {
194
194
  };
195
195
  }
196
196
 
197
- setOnChunk(onChunk: (chunk: AudioChunk) => void) {
197
+ setOnChunk(onChunk: (chunk: AudioChunk) => void): void {
198
198
  this._onChunk = onChunk;
199
199
  }
200
200
 
201
201
  /**
202
202
  * Emit a chunk in controlled manner for testing purposes.
203
203
  */
204
- emitChunk() {
204
+ emitChunk(): void {
205
205
  const chunk = this.chunks.shift();
206
206
  if (!chunk) {
207
207
  return;
@@ -211,7 +211,7 @@ class MockAudioRecorder implements AudioRecorder {
211
211
  }
212
212
 
213
213
  @trace.span()
214
- async start() {
214
+ async start(): Promise<void> {
215
215
  log.info('start mock audio recorder', { wavParams: this.wav.fmt });
216
216
  const now = Date.now();
217
217
  const samples = this.wav.getSamples();
@@ -226,5 +226,5 @@ class MockAudioRecorder implements AudioRecorder {
226
226
  }
227
227
  }
228
228
 
229
- async stop() {}
229
+ async stop(): Promise<void> {}
230
230
  }
@@ -7,10 +7,11 @@ import { WaveFile } from 'wavefile';
7
7
  import { DeferredTask, Trigger } from '@dxos/async';
8
8
  import { type Context, LifecycleState, Resource } from '@dxos/context';
9
9
  import { log } from '@dxos/log';
10
+ import { type DataType } from '@dxos/schema';
10
11
  import { trace } from '@dxos/tracing';
11
12
 
12
13
  import { type AudioRecorder, type AudioChunk } from './audio-recorder';
13
- import { TRANSCRIPTION_URL, type TranscriptSegment } from '../types';
14
+ import { TRANSCRIPTION_URL } from '../types';
14
15
  import { mergeFloat64Arrays } from '../util';
15
16
 
16
17
  type WhisperWord = {
@@ -64,7 +65,11 @@ export type TranscribeConfig = {
64
65
  export type TranscriberParams = {
65
66
  config: TranscribeConfig;
66
67
  recorder: AudioRecorder;
67
- onSegments: (segments: TranscriptSegment[]) => Promise<void>;
68
+ /**
69
+ * Callback to handle the transcribed segments, after all segment transformers are applied.
70
+ * @param segments - The transcribed segments.
71
+ */
72
+ onSegments: (segments: DataType.MessageBlock.Transcription[]) => Promise<void>;
68
73
  };
69
74
 
70
75
  /**
@@ -92,35 +97,41 @@ export class Transcriber extends Resource {
92
97
  this._onSegments = onSegments;
93
98
  }
94
99
 
95
- protected override async _open(ctx: Context) {
100
+ protected override async _open(ctx: Context): Promise<void> {
101
+ log.info('opening');
96
102
  this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));
97
103
  await this._recorder.start();
98
104
  this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());
99
105
  this._openTrigger.wake();
100
106
  }
101
107
 
102
- protected override async _close() {
108
+ protected override async _close(): Promise<void> {
109
+ log.info('closing');
103
110
  this._recording = false;
104
111
  this._transcribeTask = undefined;
105
112
  await this._recorder.stop();
106
113
  }
107
114
 
108
- startChunksRecording() {
115
+ startChunksRecording(): void {
116
+ log.info('starting');
109
117
  if (this._lifecycleState !== LifecycleState.OPEN) {
110
118
  return;
111
119
  }
120
+
112
121
  this._recording = true;
113
122
  }
114
123
 
115
- stopChunksRecording() {
124
+ stopChunksRecording(): void {
116
125
  if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {
117
126
  return;
118
127
  }
128
+
119
129
  this._recording = false;
120
130
  this._transcribeTask?.schedule();
131
+ log.info('stopped');
121
132
  }
122
133
 
123
- private _saveAudioChunk(chunk: AudioChunk) {
134
+ private _saveAudioChunk(chunk: AudioChunk): void {
124
135
  log('saving audio chunk', { chunk });
125
136
  this._audioChunks.push(chunk);
126
137
 
@@ -136,7 +147,7 @@ export class Transcriber extends Resource {
136
147
  }
137
148
  }
138
149
 
139
- private async _transcribe() {
150
+ private async _transcribe(): Promise<void> {
140
151
  log('transcribing', { chunks: this._audioChunks.length });
141
152
  const chunks = this._audioChunks;
142
153
  this._audioChunks = this._dropOldChunks();
@@ -156,14 +167,14 @@ export class Transcriber extends Resource {
156
167
  }
157
168
  }
158
169
 
159
- private _dropOldChunks() {
170
+ private _dropOldChunks(): AudioChunk[] {
160
171
  return this._config.prefixBufferChunksAmount > 0
161
172
  ? this._audioChunks.slice(-this._config.prefixBufferChunksAmount)
162
173
  : [];
163
174
  }
164
175
 
165
176
  @trace.span({ showInBrowserTimeline: true })
166
- private async _mergeAudioChunks(chunks: AudioChunk[]) {
177
+ private async _mergeAudioChunks(chunks: AudioChunk[]): Promise<string> {
167
178
  const file = new WaveFile();
168
179
  const wavConfig = this._recorder.wavConfig;
169
180
 
@@ -178,7 +189,7 @@ export class Transcriber extends Resource {
178
189
  }
179
190
 
180
191
  @trace.span({ showInBrowserTimeline: true })
181
- private async _fetchTranscription(audio: string) {
192
+ private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {
182
193
  if (audio.length === 0) {
183
194
  this._audioChunks = [];
184
195
  throw new Error('No audio to send for transcribing');
@@ -194,24 +205,25 @@ export class Transcriber extends Resource {
194
205
 
195
206
  if (!response.ok) {
196
207
  this._audioChunks = [];
197
- throw new Error('Failed to transcribe');
208
+ throw new Error(`Transcription failed: ${response.statusText}`);
198
209
  }
199
210
 
200
- const {
201
- segments,
202
- }: {
211
+ const { segments } = (await response.json()) as {
203
212
  segments: WhisperSegment[];
204
- } = await response.json();
213
+ };
205
214
 
206
215
  log.info('transcription response', {
207
- segments,
216
+ segments: segments.length,
208
217
  string: segments.map((segments) => segments.text).join(' '),
209
218
  });
210
219
 
211
220
  return segments;
212
221
  }
213
222
 
214
- private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): TranscriptSegment[] {
223
+ private _alignSegments(
224
+ segments: WhisperSegment[],
225
+ originalChunks: AudioChunk[],
226
+ ): DataType.MessageBlock.Transcription[] {
215
227
  // Absolute zero for all relative timestamps in the segments.
216
228
  const zeroTimestamp = originalChunks.at(0)!.timestamp;
217
229
 
@@ -240,8 +252,9 @@ export class Transcriber extends Resource {
240
252
 
241
253
  // Add absolute timestamp to each segment.
242
254
  return filteredSegments.map((segment) => ({
243
- started: new Date(zeroTimestamp + segment.start * 1_000),
244
- text: segment.text,
255
+ type: 'transcription',
256
+ started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
257
+ text: segment.text.trim(),
245
258
  }));
246
259
  }
247
260
  }