@dxos/plugin-transcription 0.8.1 → 0.8.2-main.2f9c567

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 (241) hide show
  1. package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs → TranscriptContainer-3LU3XBEB.mjs} +3 -2
  2. package/dist/lib/browser/app-graph-builder-YXELPN6F.mjs +349 -0
  3. package/dist/lib/browser/app-graph-builder-YXELPN6F.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-GWY2Q2NE.mjs} +108 -81
  5. package/dist/lib/browser/chunk-GWY2Q2NE.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-HNIKXVCG.mjs +720 -0
  7. package/dist/lib/browser/chunk-HNIKXVCG.mjs.map +7 -0
  8. package/dist/lib/browser/chunk-HRDAILQX.mjs +27 -0
  9. package/dist/lib/browser/chunk-HRDAILQX.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-GU2BNYKZ.mjs → chunk-KLD7O5GJ.mjs} +2 -12
  11. package/dist/lib/browser/chunk-KLD7O5GJ.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-LTIQFX4W.mjs} +1 -1
  13. package/dist/lib/browser/chunk-LTIQFX4W.mjs.map +7 -0
  14. package/dist/lib/browser/chunk-TGOKX32G.mjs +63 -0
  15. package/dist/lib/browser/chunk-TGOKX32G.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +46 -209
  17. package/dist/lib/browser/index.mjs.map +4 -4
  18. package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-BBB3DWV4.mjs} +8 -8
  19. package/dist/lib/browser/intent-resolver-BBB3DWV4.mjs.map +7 -0
  20. package/dist/lib/browser/{meeting-transcription-state-DLFHQK72.mjs → meeting-transcription-state-MNA2XROE.mjs} +4 -4
  21. package/dist/lib/browser/meeting-transcription-state-MNA2XROE.mjs.map +7 -0
  22. package/dist/lib/browser/meta.json +1 -1
  23. package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-R277DACJ.mjs} +18 -6
  24. package/dist/lib/browser/react-surface-R277DACJ.mjs.map +7 -0
  25. package/dist/lib/browser/settings-I226DZ27.mjs +24 -0
  26. package/dist/lib/browser/settings-I226DZ27.mjs.map +7 -0
  27. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs → transcriber-QRSS6X6S.mjs} +8 -7
  28. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs.map → transcriber-QRSS6X6S.mjs.map} +3 -3
  29. package/dist/lib/browser/types/index.mjs +3 -3
  30. package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-DICOMZLM.cjs} +8 -7
  31. package/dist/lib/node/TranscriptContainer-DICOMZLM.cjs.map +7 -0
  32. package/dist/lib/node/app-graph-builder-I6LYVIYY.cjs +350 -0
  33. package/dist/lib/node/app-graph-builder-I6LYVIYY.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-P4VVD6ZO.cjs → chunk-34WFY7M7.cjs} +24 -34
  35. package/dist/lib/node/chunk-34WFY7M7.cjs.map +7 -0
  36. package/dist/lib/node/chunk-4JOJCZPJ.cjs +737 -0
  37. package/dist/lib/node/chunk-4JOJCZPJ.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-3KHUSQNW.cjs → chunk-J72PUQ3C.cjs} +6 -15
  39. package/dist/lib/node/chunk-J72PUQ3C.cjs.map +7 -0
  40. package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-JUGOXWES.cjs} +4 -4
  41. package/dist/lib/node/chunk-JUGOXWES.cjs.map +7 -0
  42. package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-K553L7CZ.cjs} +23 -8
  43. package/dist/lib/node/chunk-K553L7CZ.cjs.map +7 -0
  44. package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-RK4BEN5E.cjs} +111 -83
  45. package/dist/lib/node/chunk-RK4BEN5E.cjs.map +7 -0
  46. package/dist/lib/node/index.cjs +65 -237
  47. package/dist/lib/node/index.cjs.map +4 -4
  48. package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-7XY3WITN.cjs} +11 -10
  49. package/dist/lib/node/intent-resolver-7XY3WITN.cjs.map +7 -0
  50. package/dist/lib/node/{meeting-transcription-state-URIYILEQ.cjs → meeting-transcription-state-5XLTMWPA.cjs} +7 -7
  51. package/dist/lib/node/meeting-transcription-state-5XLTMWPA.cjs.map +7 -0
  52. package/dist/lib/node/meta.json +1 -1
  53. package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-HUSWAORU.cjs} +20 -10
  54. package/dist/lib/node/react-surface-HUSWAORU.cjs.map +7 -0
  55. package/dist/lib/node/settings-NDMU3EWQ.cjs +38 -0
  56. package/dist/lib/node/settings-NDMU3EWQ.cjs.map +7 -0
  57. package/dist/lib/node/{transcriber-QG5WPKX4.cjs → transcriber-UZJMG44C.cjs} +14 -13
  58. package/dist/lib/node/{transcriber-QG5WPKX4.cjs.map → transcriber-UZJMG44C.cjs.map} +3 -3
  59. package/dist/lib/node/types/index.cjs +8 -8
  60. package/dist/lib/node/types/index.cjs.map +1 -1
  61. package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-K4E4P5T6.mjs} +3 -2
  62. package/dist/lib/node-esm/app-graph-builder-6FY2UQOH.mjs +350 -0
  63. package/dist/lib/node-esm/app-graph-builder-6FY2UQOH.mjs.map +7 -0
  64. package/dist/lib/node-esm/chunk-3I7AC3D3.mjs +28 -0
  65. package/dist/lib/node-esm/chunk-3I7AC3D3.mjs.map +7 -0
  66. package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-5E2MR5XK.mjs} +1 -1
  67. package/dist/lib/node-esm/chunk-5E2MR5XK.mjs.map +7 -0
  68. package/dist/lib/node-esm/chunk-6C34FO4C.mjs +721 -0
  69. package/dist/lib/node-esm/chunk-6C34FO4C.mjs.map +7 -0
  70. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-CKANLRS4.mjs} +108 -81
  71. package/dist/lib/node-esm/chunk-CKANLRS4.mjs.map +7 -0
  72. package/dist/lib/node-esm/chunk-KUQ3ZTRX.mjs +64 -0
  73. package/dist/lib/node-esm/chunk-KUQ3ZTRX.mjs.map +7 -0
  74. package/dist/lib/node-esm/{chunk-HKATMD34.mjs → chunk-QMHMEKB3.mjs} +2 -12
  75. package/dist/lib/node-esm/chunk-QMHMEKB3.mjs.map +7 -0
  76. package/dist/lib/node-esm/index.mjs +46 -209
  77. package/dist/lib/node-esm/index.mjs.map +4 -4
  78. package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-ACGAPLDZ.mjs} +8 -8
  79. package/dist/lib/node-esm/intent-resolver-ACGAPLDZ.mjs.map +7 -0
  80. package/dist/lib/node-esm/{meeting-transcription-state-ZYVRAW6Y.mjs → meeting-transcription-state-3WOZ52BQ.mjs} +4 -4
  81. package/dist/lib/node-esm/meeting-transcription-state-3WOZ52BQ.mjs.map +7 -0
  82. package/dist/lib/node-esm/meta.json +1 -1
  83. package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-G2OY4MWL.mjs} +18 -6
  84. package/dist/lib/node-esm/react-surface-G2OY4MWL.mjs.map +7 -0
  85. package/dist/lib/node-esm/settings-O7X2UQQG.mjs +25 -0
  86. package/dist/lib/node-esm/settings-O7X2UQQG.mjs.map +7 -0
  87. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs → transcriber-NVVFH7IF.mjs} +8 -7
  88. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs.map → transcriber-NVVFH7IF.mjs.map} +3 -3
  89. package/dist/lib/node-esm/types/index.mjs +3 -3
  90. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  91. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  92. package/dist/types/src/capabilities/capabilities.d.ts +5 -7
  93. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  94. package/dist/types/src/capabilities/index.d.ts +2 -4
  95. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  96. package/dist/types/src/capabilities/meeting-transcription-state.d.ts +2 -4
  97. package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +1 -1
  98. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  99. package/dist/types/src/capabilities/settings.d.ts +4 -0
  100. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  101. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  102. package/dist/types/src/components/Transcript/Transcript.d.ts +14 -4
  103. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  104. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +19 -4
  105. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  106. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
  107. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
  108. package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
  109. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
  110. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  111. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts +6 -0
  112. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts.map +1 -0
  113. package/dist/types/src/components/TranscriptionSettings/index.d.ts +2 -0
  114. package/dist/types/src/components/TranscriptionSettings/index.d.ts.map +1 -0
  115. package/dist/types/src/components/index.d.ts +1 -0
  116. package/dist/types/src/components/index.d.ts.map +1 -1
  117. package/dist/types/src/components/transcription.stories.d.ts +6 -3
  118. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  119. package/dist/types/src/entity-extraction/entity-extraction.d.ts +42 -0
  120. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -0
  121. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts +2 -0
  122. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts.map +1 -0
  123. package/dist/types/src/entity-extraction/index.d.ts +2 -0
  124. package/dist/types/src/entity-extraction/index.d.ts.map +1 -0
  125. package/dist/types/src/hooks/index.d.ts +1 -0
  126. package/dist/types/src/hooks/index.d.ts.map +1 -1
  127. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
  128. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
  129. package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
  130. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  131. package/dist/types/src/model/index.d.ts +2 -0
  132. package/dist/types/src/model/index.d.ts.map +1 -0
  133. package/dist/types/src/model/model.d.ts +68 -0
  134. package/dist/types/src/model/model.d.ts.map +1 -0
  135. package/dist/types/src/model/model.test.d.ts +2 -0
  136. package/dist/types/src/model/model.test.d.ts.map +1 -0
  137. package/dist/types/src/testing/index.d.ts +2 -0
  138. package/dist/types/src/testing/index.d.ts.map +1 -0
  139. package/dist/types/src/testing/testing.d.ts +40 -0
  140. package/dist/types/src/testing/testing.d.ts.map +1 -0
  141. package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
  142. package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
  143. package/dist/types/src/transcriber/transcriber.d.ts +2 -2
  144. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  145. package/dist/types/src/transcriber/transcription-manager.d.ts +17 -15
  146. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  147. package/dist/types/src/translations.d.ts +3 -0
  148. package/dist/types/src/translations.d.ts.map +1 -1
  149. package/dist/types/src/types/schema.d.ts +26 -40
  150. package/dist/types/src/types/schema.d.ts.map +1 -1
  151. package/dist/types/src/types/types.d.ts +11 -7
  152. package/dist/types/src/types/types.d.ts.map +1 -1
  153. package/dist/types/src/util/index.d.ts +0 -1
  154. package/dist/types/src/util/index.d.ts.map +1 -1
  155. package/package.json +53 -40
  156. package/src/TranscriptionPlugin.tsx +29 -12
  157. package/src/capabilities/app-graph-builder.ts +111 -52
  158. package/src/capabilities/capabilities.ts +4 -3
  159. package/src/capabilities/index.ts +1 -0
  160. package/src/capabilities/intent-resolver.ts +5 -5
  161. package/src/capabilities/meeting-transcription-state.ts +2 -2
  162. package/src/capabilities/react-surface.tsx +12 -4
  163. package/src/capabilities/settings.ts +20 -0
  164. package/src/capabilities/transcriber.ts +2 -1
  165. package/src/components/Transcript/Transcript.stories.tsx +201 -43
  166. package/src/components/Transcript/Transcript.tsx +83 -208
  167. package/src/components/Transcript/deprecated/OldTranscript.tsx +260 -0
  168. package/src/components/Transcript/transcript-extension.ts +236 -0
  169. package/src/components/TranscriptContainer.tsx +11 -6
  170. package/src/components/TranscriptionSettings/TranscriptionSettings.tsx +26 -0
  171. package/src/components/TranscriptionSettings/index.ts +5 -0
  172. package/src/components/index.ts +1 -0
  173. package/src/components/transcription.stories.tsx +163 -60
  174. package/src/entity-extraction/entity-extraction.test.ts +97 -0
  175. package/src/entity-extraction/entity-extraction.ts +149 -0
  176. package/src/entity-extraction/index.ts +5 -0
  177. package/src/entity-extraction/system-prompt.tpl +38 -0
  178. package/src/entity-extraction/types.d.ts +8 -0
  179. package/src/hooks/index.ts +1 -0
  180. package/src/hooks/useQueueModelAdapter.ts +63 -0
  181. package/src/hooks/useTranscriber.ts +3 -2
  182. package/src/model/index.ts +5 -0
  183. package/src/model/model.test.ts +169 -0
  184. package/src/model/model.ts +229 -0
  185. package/src/testing/index.ts +5 -0
  186. package/src/testing/testing.ts +221 -0
  187. package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +3 -3
  188. package/src/transcriber/transcriber.ts +20 -11
  189. package/src/transcriber/transcription-manager.ts +67 -58
  190. package/src/translations.ts +2 -0
  191. package/src/types/schema.ts +11 -32
  192. package/src/types/types.ts +19 -9
  193. package/src/util/index.ts +0 -1
  194. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs +0 -191
  195. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
  196. package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
  197. package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
  198. package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
  199. package/dist/lib/browser/chunk-K7FOYAXO.mjs +0 -8
  200. package/dist/lib/browser/chunk-K7FOYAXO.mjs.map +0 -7
  201. package/dist/lib/browser/chunk-OGNXNGJB.mjs +0 -73
  202. package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-YCZISX63.mjs.map +0 -7
  204. package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
  205. package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
  206. package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
  207. package/dist/lib/browser/react-surface-FQUHU5G7.mjs.map +0 -7
  208. package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
  209. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs +0 -200
  210. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
  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-LP4FG423.cjs.map +0 -7
  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.map +0 -7
  220. package/dist/lib/node/react-surface-WI2MGWCC.cjs.map +0 -7
  221. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs +0 -192
  222. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
  223. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
  224. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
  225. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs +0 -74
  226. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
  227. package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
  228. package/dist/lib/node-esm/chunk-J4D2RBBH.mjs +0 -10
  229. package/dist/lib/node-esm/chunk-J4D2RBBH.mjs.map +0 -7
  230. package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
  231. package/dist/lib/node-esm/chunk-YQYBLFNI.mjs.map +0 -7
  232. package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
  233. package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
  234. package/dist/lib/node-esm/react-surface-PCH6NTVB.mjs.map +0 -7
  235. package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
  236. package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
  237. package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
  238. package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
  239. package/src/util/random-queue-dxn.ts +0 -9
  240. /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-3LU3XBEB.mjs.map} +0 -0
  241. /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-K4E4P5T6.mjs.map} +0 -0
@@ -0,0 +1,221 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema, SchemaAST } from 'effect';
6
+ import { useEffect, useMemo, useState } from 'react';
7
+
8
+ import { AIServiceEdgeClient } from '@dxos/assistant';
9
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
10
+ import { scheduleTaskInterval } from '@dxos/async';
11
+ import { createQueueDxn, Filter, type Queue } from '@dxos/client/echo';
12
+ import { Context } from '@dxos/context';
13
+ import { Type } from '@dxos/echo';
14
+ import { create, ObjectId } from '@dxos/echo-schema';
15
+ import { IdentityDid } from '@dxos/keys';
16
+ import { log } from '@dxos/log';
17
+ import { faker } from '@dxos/random';
18
+ import { live, makeRef, useQueue, type Space } from '@dxos/react-client/echo';
19
+ import { DataType } from '@dxos/schema';
20
+ import { Testing, seedTestData } from '@dxos/schema/testing';
21
+
22
+ import { processTranscriptMessage } from '../entity-extraction';
23
+
24
+ // TODO(burdon): Reconcile with plugin-markdown. Move to @dxos/schema/testing.
25
+
26
+ export const TestItem = Schema.Struct({
27
+ title: Schema.String.annotations({
28
+ [SchemaAST.TitleAnnotationId]: 'Title',
29
+ [SchemaAST.DescriptionAnnotationId]: 'Product title',
30
+ }),
31
+ description: Schema.String.annotations({
32
+ [SchemaAST.TitleAnnotationId]: 'Description',
33
+ [SchemaAST.DescriptionAnnotationId]: 'Product description',
34
+ }),
35
+ }).pipe(
36
+ Type.def({
37
+ typename: 'dxos.org/type/Test',
38
+ version: '0.1.0',
39
+ }),
40
+ );
41
+
42
+ // TODO(wittjosiah): Make builder generic and reuse for all message types.
43
+ abstract class AbstractMessageBuilder {
44
+ abstract createMessage(numSegments?: number): Promise<DataType.Message>;
45
+ }
46
+
47
+ /**
48
+ * Generator of transcript messages.
49
+ */
50
+ export class MessageBuilder extends AbstractMessageBuilder {
51
+ static readonly singleton = new MessageBuilder();
52
+
53
+ users = Array.from({ length: 5 }, () => ({
54
+ identityDid: IdentityDid.random().toString(),
55
+ name: faker.person.fullName(),
56
+ email: faker.internet.email(),
57
+ }));
58
+
59
+ start = new Date(Date.now() - 24 * 60 * 60 * 10_000);
60
+
61
+ constructor(private readonly _space?: Space) {
62
+ super();
63
+ }
64
+
65
+ override async createMessage(numSegments = 1): Promise<DataType.Message> {
66
+ return {
67
+ id: ObjectId.random().toString(),
68
+ created: this.next().toISOString(),
69
+ sender: faker.helpers.arrayElement(this.users),
70
+ blocks: Array.from({ length: numSegments }).map(() => this.createBlock()),
71
+ };
72
+ }
73
+
74
+ createBlock(): DataType.MessageBlock.Transcription {
75
+ let text = faker.lorem.paragraph();
76
+ if (this._space) {
77
+ const label = faker.commerce.productName();
78
+ const obj = this._space.db.add(live(TestItem, { title: label, description: faker.lorem.paragraph() }));
79
+ const dxn = makeRef(obj).dxn.toString();
80
+ const words = text.split(' ');
81
+ words.splice(Math.floor(Math.random() * words.length), 0, `[${label}][${dxn}]`);
82
+ text = words.join(' ');
83
+ }
84
+
85
+ return {
86
+ type: 'transcription',
87
+ started: this.next().toISOString(),
88
+ text,
89
+ };
90
+ }
91
+
92
+ next() {
93
+ this.start = new Date(this.start.getTime() + Math.random() * 10_000);
94
+ return this.start;
95
+ }
96
+ }
97
+
98
+ // TODO(burdon): Reconcile with BlockBuilder.
99
+ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
100
+ aiService = new AIServiceEdgeClient({
101
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
102
+ });
103
+
104
+ space: Space | undefined;
105
+ currentMessage: number = 0;
106
+ transcriptMessages: DataType.Message[] = [];
107
+
108
+ async connect(space: Space) {
109
+ this.space = space;
110
+ const { transcriptMessages } = await seedTestData(space);
111
+ this.transcriptMessages = transcriptMessages;
112
+ }
113
+
114
+ override async createMessage(): Promise<DataType.Message> {
115
+ if (!this.space) {
116
+ throw new Error('Space not connected');
117
+ }
118
+
119
+ const { objects } = await this.space.db
120
+ .query(
121
+ Filter.or(
122
+ Filter.schema(DataType.Person),
123
+ Filter.schema(DataType.Organization),
124
+ Filter.schema(Testing.DocumentType),
125
+ ),
126
+ )
127
+ .run();
128
+
129
+ log.info('context', { objects });
130
+ const message = this.transcriptMessages[this.currentMessage];
131
+ this.currentMessage++;
132
+ this.currentMessage = this.currentMessage % this.transcriptMessages.length;
133
+
134
+ const { message: enhancedMessage } = await processTranscriptMessage({
135
+ message,
136
+ aiService: this.aiService,
137
+ context: {
138
+ objects,
139
+ },
140
+ });
141
+
142
+ return enhancedMessage;
143
+ }
144
+ }
145
+
146
+ type UseTestTranscriptionQueue = (
147
+ space: Space | undefined,
148
+ queueId?: ObjectId,
149
+ running?: boolean,
150
+ interval?: number,
151
+ ) => Queue<DataType.Message> | undefined;
152
+
153
+ /**
154
+ * Test transcriptionqueue.
155
+ */
156
+ export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
157
+ space: Space | undefined,
158
+ queueId?: ObjectId,
159
+ running = true,
160
+ interval = 1_000,
161
+ ) => {
162
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
163
+ const queue = useQueue<DataType.Message>(queueDxn);
164
+ const builder = useMemo(() => new MessageBuilder(space), [space]);
165
+
166
+ useEffect(() => {
167
+ if (!queue || !running) {
168
+ return;
169
+ }
170
+
171
+ const i = setInterval(() => {
172
+ void builder.createMessage(Math.ceil(Math.random() * 3)).then((message) => {
173
+ queue.append([create(DataType.Message, message)]);
174
+ });
175
+ }, interval);
176
+ return () => clearInterval(i);
177
+ }, [queue, running, interval]);
178
+
179
+ return queue;
180
+ };
181
+
182
+ /**
183
+ * Test transcription queue.
184
+ */
185
+ // TODO(burdon): Reconcile with useTestTranscriptionQueue.
186
+ export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscriptionQueue = (
187
+ space: Space | undefined,
188
+ queueId?: ObjectId,
189
+ running = true,
190
+ interval = 1_000,
191
+ ) => {
192
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
193
+ const queue = useQueue<DataType.Message>(queueDxn);
194
+ const [builder] = useState(() => new EntityExtractionMessageBuilder());
195
+
196
+ useEffect(() => {
197
+ if (!queue || !running) {
198
+ return;
199
+ }
200
+
201
+ if (space) {
202
+ void builder.connect(space);
203
+ }
204
+
205
+ const ctx = new Context();
206
+ scheduleTaskInterval(
207
+ ctx,
208
+ async () => {
209
+ const message = await builder.createMessage();
210
+ queue.append([create(DataType.Message, message)]);
211
+ },
212
+ interval,
213
+ );
214
+
215
+ return () => {
216
+ void ctx.dispose();
217
+ };
218
+ }, [space, queue, running, interval]);
219
+
220
+ return queue;
221
+ };
@@ -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,
@@ -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,7 @@ export type TranscribeConfig = {
64
65
  export type TranscriberParams = {
65
66
  config: TranscribeConfig;
66
67
  recorder: AudioRecorder;
67
- onSegments: (segments: TranscriptSegment[]) => Promise<void>;
68
+ onSegments: (segments: DataType.MessageBlock.Transcription[]) => Promise<void>;
68
69
  };
69
70
 
70
71
  /**
@@ -93,6 +94,7 @@ export class Transcriber extends Resource {
93
94
  }
94
95
 
95
96
  protected override async _open(ctx: Context) {
97
+ log.info('opening');
96
98
  this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));
97
99
  await this._recorder.start();
98
100
  this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());
@@ -100,15 +102,18 @@ export class Transcriber extends Resource {
100
102
  }
101
103
 
102
104
  protected override async _close() {
105
+ log.info('closing');
103
106
  this._recording = false;
104
107
  this._transcribeTask = undefined;
105
108
  await this._recorder.stop();
106
109
  }
107
110
 
108
111
  startChunksRecording() {
112
+ log.info('starting');
109
113
  if (this._lifecycleState !== LifecycleState.OPEN) {
110
114
  return;
111
115
  }
116
+
112
117
  this._recording = true;
113
118
  }
114
119
 
@@ -116,8 +121,10 @@ export class Transcriber extends Resource {
116
121
  if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {
117
122
  return;
118
123
  }
124
+
119
125
  this._recording = false;
120
126
  this._transcribeTask?.schedule();
127
+ log.info('stopped');
121
128
  }
122
129
 
123
130
  private _saveAudioChunk(chunk: AudioChunk) {
@@ -194,24 +201,25 @@ export class Transcriber extends Resource {
194
201
 
195
202
  if (!response.ok) {
196
203
  this._audioChunks = [];
197
- throw new Error('Failed to transcribe');
204
+ throw new Error(`Transcription failed: ${response.statusText}`);
198
205
  }
199
206
 
200
- const {
201
- segments,
202
- }: {
207
+ const { segments } = (await response.json()) as {
203
208
  segments: WhisperSegment[];
204
- } = await response.json();
209
+ };
205
210
 
206
211
  log.info('transcription response', {
207
- segments,
212
+ segments: segments.length,
208
213
  string: segments.map((segments) => segments.text).join(' '),
209
214
  });
210
215
 
211
216
  return segments;
212
217
  }
213
218
 
214
- private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): TranscriptSegment[] {
219
+ private _alignSegments(
220
+ segments: WhisperSegment[],
221
+ originalChunks: AudioChunk[],
222
+ ): DataType.MessageBlock.Transcription[] {
215
223
  // Absolute zero for all relative timestamps in the segments.
216
224
  const zeroTimestamp = originalChunks.at(0)!.timestamp;
217
225
 
@@ -240,8 +248,9 @@ export class Transcriber extends Resource {
240
248
 
241
249
  // Add absolute timestamp to each segment.
242
250
  return filteredSegments.map((segment) => ({
243
- started: new Date(zeroTimestamp + segment.start * 1_000),
244
- text: segment.text,
251
+ type: 'transcription',
252
+ started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
253
+ text: segment.text.trim(),
245
254
  }));
246
255
  }
247
256
  }
@@ -5,15 +5,14 @@
5
5
  import { synchronized } from '@dxos/async';
6
6
  import { Resource } from '@dxos/context';
7
7
  import { QueueImpl, type Queue } from '@dxos/echo-db';
8
- import { createStatic } from '@dxos/echo-schema';
9
- import { invariant } from '@dxos/invariant';
10
- import { DXN } from '@dxos/keys';
8
+ import { create } from '@dxos/echo-schema';
9
+ import { type DXN } from '@dxos/keys';
10
+ import { log } from '@dxos/log';
11
11
  import { type EdgeHttpClient } from '@dxos/react-edge-client';
12
- import { type HuePalette } from '@dxos/react-ui-theme';
12
+ import { DataType } from '@dxos/schema';
13
13
 
14
14
  import { MediaStreamRecorder } from './media-stream-recorder';
15
15
  import { Transcriber } from './transcriber';
16
- import { TranscriptBlock, type TranscriptSegment } from '../types';
17
16
 
18
17
  /**
19
18
  * Length of the chunk in ms.
@@ -31,23 +30,34 @@ const PREFIXED_CHUNKS_AMOUNT = 10;
31
30
  */
32
31
  const TRANSCRIBE_AFTER_CHUNKS_AMOUNT = 50;
33
32
 
33
+ export type TranscriptMessageEnricher = (message: DataType.Message) => Promise<DataType.Message>;
34
+
35
+ export type TranscriptionManagerOptions = {
36
+ edgeClient: EdgeHttpClient;
37
+
38
+ /**
39
+ * Enrich the message before it is written to the transcription queue.
40
+ */
41
+ messageEnricher?: TranscriptMessageEnricher;
42
+ };
43
+
34
44
  /**
35
45
  * Manages transcription state for a meeting.
36
46
  */
37
- // TODO(mykola): Reconcile with transcriber capability.
38
47
  export class TranscriptionManager extends Resource {
39
48
  private readonly _edgeClient: EdgeHttpClient;
49
+ private readonly _messageEnricher?: TranscriptMessageEnricher;
40
50
  private _audioStreamTrack?: MediaStreamTrack = undefined;
41
- private _name?: string = undefined;
42
- private _hue?: HuePalette = undefined;
51
+ private _identityDid?: string = undefined;
43
52
  private _mediaRecorder?: MediaStreamRecorder = undefined;
44
53
  private _transcriber?: Transcriber = undefined;
54
+ private _queue?: Queue<DataType.Message> = undefined;
45
55
  private _enabled = false;
46
- private _queue?: Queue<TranscriptBlock> = undefined;
47
56
 
48
- constructor(edgeClient: EdgeHttpClient) {
57
+ constructor(options: TranscriptionManagerOptions) {
49
58
  super();
50
- this._edgeClient = edgeClient;
59
+ this._edgeClient = options.edgeClient;
60
+ this._messageEnricher = options.messageEnricher;
51
61
  }
52
62
 
53
63
  protected override async _open() {
@@ -58,10 +68,24 @@ export class TranscriptionManager extends Resource {
58
68
  void this._transcriber?.close();
59
69
  }
60
70
 
61
- @synchronized
62
- setRecording(recording?: boolean) {
71
+ setQueue(queueDxn: DXN): TranscriptionManager {
72
+ if (this._queue?.dxn.toString() !== queueDxn.toString()) {
73
+ log.info('setQueue', { queueDxn: queueDxn.toString() });
74
+ this._queue = new QueueImpl<DataType.Message>(this._edgeClient, queueDxn);
75
+ }
76
+ return this;
77
+ }
78
+
79
+ setIdentityDid(did: string): TranscriptionManager {
80
+ if (this._identityDid !== did) {
81
+ this._identityDid = did;
82
+ }
83
+ return this;
84
+ }
85
+
86
+ setRecording(recording?: boolean): TranscriptionManager {
63
87
  if (!this.isOpen || !this._enabled) {
64
- return;
88
+ return this;
65
89
  }
66
90
 
67
91
  if (recording) {
@@ -69,6 +93,7 @@ export class TranscriptionManager extends Resource {
69
93
  } else {
70
94
  this._transcriber?.stopChunksRecording();
71
95
  }
96
+ return this;
72
97
  }
73
98
 
74
99
  /**
@@ -80,7 +105,9 @@ export class TranscriptionManager extends Resource {
80
105
  if (this._enabled === enabled) {
81
106
  return;
82
107
  }
108
+
83
109
  this._enabled = enabled ?? false;
110
+ // TODO(burdon): Why is toggle called here?
84
111
  this.isOpen && (await this._toggleTranscriber());
85
112
  }
86
113
 
@@ -89,60 +116,33 @@ export class TranscriptionManager extends Resource {
89
116
  if (this._audioStreamTrack === track) {
90
117
  return;
91
118
  }
119
+
92
120
  this._audioStreamTrack = track;
93
121
  this.isOpen && (await this._toggleTranscriber());
94
122
  }
95
123
 
96
- /**
97
- * Set the queue to save the transcription to.
98
- * @param queue - The queue to save the transcription to or the DXN of the queue.
99
- */
100
- @synchronized
101
- setQueue(queue?: Queue<TranscriptBlock> | string) {
102
- switch (typeof queue) {
103
- case 'string':
104
- if (this._queue?.dxn.toString() === queue) {
105
- return;
106
- }
107
- this._queue = new QueueImpl<TranscriptBlock>(this._edgeClient, DXN.parse(queue));
108
- break;
109
- case 'object':
110
- if (this._queue === queue) {
111
- return;
112
- }
113
- invariant(queue instanceof QueueImpl);
114
- this._queue = queue;
115
- break;
116
- case 'undefined':
117
- this._queue = undefined;
118
- break;
119
- }
120
- }
121
-
122
- @synchronized
123
- setName(name: string) {
124
- if (this._name === name) {
125
- return;
126
- }
127
- this._name = name;
128
- }
129
-
130
- @synchronized
131
- setHue(hue: HuePalette) {
132
- if (this._hue === hue) {
133
- return;
134
- }
135
- this._hue = hue;
136
- }
137
-
124
+ // TODO(burdon): Change this to setEnables (explicit), not toggle.
138
125
  private async _toggleTranscriber() {
139
126
  await this._maybeReinitTranscriber();
140
127
 
141
128
  // Open or close transcriber if transcription is enabled or disabled.
142
129
  if (this._enabled) {
143
130
  await this._transcriber?.open();
131
+ // TODO(burdon): Started and stopped blocks appear twice.
132
+ const block = create(DataType.Message, {
133
+ created: new Date().toISOString(),
134
+ blocks: [{ type: 'transcription', text: 'Started', started: new Date().toISOString() }],
135
+ sender: { role: 'assistant' },
136
+ });
137
+ this._queue?.append([block]);
144
138
  } else {
145
139
  await this._transcriber?.close();
140
+ const block = create(DataType.Message, {
141
+ created: new Date().toISOString(),
142
+ blocks: [{ type: 'transcription', text: 'Stopped', started: new Date().toISOString() }],
143
+ sender: { role: 'assistant' },
144
+ });
145
+ this._queue?.append([block]);
146
146
  }
147
147
  }
148
148
 
@@ -173,12 +173,21 @@ export class TranscriptionManager extends Resource {
173
173
  }
174
174
  }
175
175
 
176
- private async _onSegments(segments: TranscriptSegment[]) {
176
+ private async _onSegments(segments: DataType.MessageBlock.Transcription[]) {
177
177
  if (!this.isOpen || !this._queue) {
178
178
  return;
179
179
  }
180
180
 
181
- const block = createStatic(TranscriptBlock, { authorName: this._name, authorHue: this._hue, segments });
181
+ let block = create(DataType.Message, {
182
+ created: new Date().toISOString(),
183
+ blocks: segments,
184
+ sender: { identityDid: this._identityDid },
185
+ });
186
+
187
+ if (this._messageEnricher) {
188
+ block = await this._messageEnricher(block);
189
+ }
190
+
182
191
  this._queue.append([block]);
183
192
  }
184
193
  }
@@ -22,6 +22,8 @@ export default [
22
22
  'delete button': 'Delete',
23
23
  'bookmark button': 'Bookmark',
24
24
  'scroll to end label': 'Scroll to latest',
25
+
26
+ 'settings entity extraction label': 'Entity extraction',
25
27
  },
26
28
  },
27
29
  },
@@ -2,17 +2,19 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { EchoObject, Expando, Ref, S, TypedObject } from '@dxos/echo-schema';
5
+ import { Schema } from 'effect';
6
+
7
+ import { Expando, Ref, TypedObject } from '@dxos/echo-schema';
6
8
 
7
9
  /**
8
10
  * Root transcript object created when the user starts a transcription.
9
11
  */
10
- export const TranscriptSchema = S.Struct({
11
- name: S.String,
12
+ export const TranscriptSchema = Schema.Struct({
13
+ name: Schema.String,
12
14
 
13
- // TODO(burdon): Use string for dates?
14
- started: S.optional(S.Date),
15
- ended: S.optional(S.Date),
15
+ // TODO(burdon): Use Date or string?
16
+ started: Schema.optional(Schema.String),
17
+ ended: Schema.optional(Schema.String),
16
18
 
17
19
  /**
18
20
  * Queue containing TranscriptBlock objects.
@@ -32,31 +34,8 @@ export class TranscriptType extends TypedObject({
32
34
  * First message in queue.
33
35
  * Contains metadata for the recording and transcript.
34
36
  */
35
- const TranscriptHeader = S.Struct({
36
- started: S.optional(S.Date),
37
+ const TranscriptHeader = Schema.Struct({
38
+ started: Schema.optional(Schema.String),
37
39
  });
38
40
 
39
- export type TranscriptHeader = S.Schema.Type<typeof TranscriptHeader>;
40
-
41
- /**
42
- * Transcription fragment.
43
- */
44
- const TranscriptSegment = S.Struct({
45
- // TODO(burdon): TS from service is not Unix TS (x1000).
46
- started: S.Date,
47
- text: S.String,
48
- });
49
-
50
- export type TranscriptSegment = S.Schema.Type<typeof TranscriptSegment>;
51
-
52
- /**
53
- * Transcription block (from single speaker).
54
- */
55
- export const TranscriptBlock = S.Struct({
56
- id: S.String,
57
- authorName: S.optional(S.String), // TODO(burdon): IdentityDid.
58
- authorHue: S.optional(S.String),
59
- segments: S.Array(TranscriptSegment),
60
- }).pipe(EchoObject({ typename: 'dxos.org/type/TranscriptBlock', version: '0.1.0' }));
61
-
62
- export type TranscriptBlock = S.Schema.Type<typeof TranscriptBlock>;
41
+ export type TranscriptHeader = Schema.Schema.Type<typeof TranscriptHeader>;
@@ -2,8 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { S, isInstanceOf } from '@dxos/echo-schema';
6
- import { isReactiveObject } from '@dxos/react-client/echo';
5
+ import { Schema } from 'effect';
6
+
7
+ import { SpaceIdSchema, isInstanceOf } from '@dxos/echo-schema';
8
+ import { isLiveObject } from '@dxos/react-client/echo';
7
9
 
8
10
  import { TranscriptType } from './schema';
9
11
  import { TRANSCRIPTION_PLUGIN } from '../meta';
@@ -19,18 +21,26 @@ export const TRANSCRIPTION_URL = 'https://calls-service.dxos.workers.dev';
19
21
  export namespace TranscriptionAction {
20
22
  const TRANSCRIPTION_ACTION = `${TRANSCRIPTION_PLUGIN}/action`;
21
23
 
22
- export class Create extends S.TaggedClass<Create>()(`${TRANSCRIPTION_ACTION}/create`, {
23
- input: S.Struct({
24
- name: S.optional(S.String),
25
- // TODO(wittjosiah): SpaceId.
26
- spaceId: S.String,
24
+ export class Create extends Schema.TaggedClass<Create>()(`${TRANSCRIPTION_ACTION}/create`, {
25
+ input: Schema.Struct({
26
+ name: Schema.optional(Schema.String),
27
+ spaceId: SpaceIdSchema,
27
28
  }),
28
- output: S.Struct({
29
+ output: Schema.Struct({
29
30
  object: TranscriptType,
30
31
  }),
31
32
  }) {}
32
33
  }
33
34
 
34
35
  export const isTranscript = (object: unknown): object is typeof TranscriptType => {
35
- return isReactiveObject(object) && isInstanceOf(TranscriptType, object);
36
+ return isLiveObject(object) && isInstanceOf(TranscriptType, object);
36
37
  };
38
+
39
+ // TODO(burdon): Create with decode consistently: Schema.decodeSync(TranscriptionSettingsSchema)({}))
40
+ export const TranscriptionSettingsSchema = Schema.mutable(
41
+ Schema.Struct({
42
+ entityExtraction: Schema.optional(Schema.Boolean).pipe(Schema.withConstructorDefault(() => true)),
43
+ }),
44
+ );
45
+
46
+ export type TranscriptionSettingsProps = Schema.Schema.Type<typeof TranscriptionSettingsSchema>;
package/src/util/index.ts CHANGED
@@ -5,4 +5,3 @@
5
5
  export * from './array';
6
6
  export * from './get-time-str';
7
7
  export * from './monitor-audio-level';
8
- export * from './random-queue-dxn';