@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,260 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { intervalToDuration } from 'date-fns/intervalToDuration';
6
+ import { Schema } from 'effect';
7
+ import { yieldOrContinue } from 'main-thread-scheduling';
8
+ import React, { type FC, useCallback, useEffect, useMemo, useRef, useState, type WheelEvent } from 'react';
9
+ import { type OnResizeCallback, useResizeDetector } from 'react-resize-detector';
10
+
11
+ import { Type } from '@dxos/echo';
12
+ import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
13
+ import { useAttention } from '@dxos/react-ui-attention';
14
+ import {
15
+ type DxGridCellValue,
16
+ type DxGridElement,
17
+ type DxGridAxisMeta,
18
+ type DxGridPlaneCells,
19
+ Grid,
20
+ toPlaneCellIndex,
21
+ } from '@dxos/react-ui-grid';
22
+ import { mx } from '@dxos/react-ui-theme';
23
+
24
+ import { TRANSCRIPTION_PLUGIN } from '../../../meta';
25
+
26
+ /**
27
+ * Transcription fragment.
28
+ */
29
+ const TranscriptSegment = Schema.Struct({
30
+ // TODO(burdon): TS from service is not Unix TS (x1000).
31
+ started: Schema.String,
32
+ text: Schema.String,
33
+ });
34
+
35
+ type TranscriptSegment = Schema.Schema.Type<typeof TranscriptSegment>;
36
+
37
+ /**
38
+ * Transcription block (from single speaker).
39
+ */
40
+ const TranscriptBlock = Schema.Struct({
41
+ id: Schema.String,
42
+ authorName: Schema.optional(Schema.String), // TODO(burdon): Replace with identityDid.
43
+ authorHue: Schema.optional(Schema.String), // TOOD(burdon): Remove.
44
+ segments: Schema.Array(TranscriptSegment),
45
+ }).pipe(
46
+ Type.def({
47
+ typename: 'dxos.org/type/TranscriptBlock',
48
+ version: '0.1.0',
49
+ }),
50
+ );
51
+
52
+ type TranscriptBlock = Schema.Schema.Type<typeof TranscriptBlock>;
53
+
54
+ // TODO(burdon): Actions (e.g., mark, summarize, translate, label, delete).
55
+
56
+ const lineHeight = 20;
57
+ const cellSpacing = 8 + 2;
58
+ const timestampColumnWidth = 68;
59
+
60
+ const authorClasses = 'font-medium text-base leading-[20px]';
61
+ const timestampClasses = 'text-xs leading-[20px] text-description pie-0 tabular-nums';
62
+ const segmentTextClasses = 'text-sm whitespace-normal hyphens-auto';
63
+ const measureClasses = mx(
64
+ // NOTE(thure): The `inline-start` value must equal `timestampColumnWidth` plus grid’s gap (1px)
65
+ 'absolute inline-start-[69px] inline-end-0 invisible z-[-1] border',
66
+ 'pli-[--dx-grid-cell-padding-inline] plb-[--dx-grid-cell-padding-block] leading-[20px]',
67
+ segmentTextClasses,
68
+ );
69
+
70
+ const rowDefault = {
71
+ grid: { size: lineHeight + cellSpacing },
72
+ };
73
+
74
+ type QueueRows = [number, number][];
75
+
76
+ const mapTranscriptQueue = (blocks?: TranscriptBlock[]): QueueRows => {
77
+ if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {
78
+ return [];
79
+ } else {
80
+ return blocks.flatMap((block, blockIndex) => {
81
+ return [
82
+ [blockIndex, -1],
83
+ ...block.segments.map((segment, segmentIndex) => {
84
+ return [blockIndex, segmentIndex] as [number, number];
85
+ }),
86
+ ];
87
+ });
88
+ }
89
+ };
90
+
91
+ const pad = (n = 0) => String(n).padStart(2, '0');
92
+
93
+ const formatTimestamp = (start: Date, end: Date) => {
94
+ const { hours, minutes, seconds } = intervalToDuration({ start, end });
95
+ return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
96
+ };
97
+
98
+ const measureRows = async (
99
+ host: HTMLDivElement,
100
+ blocks: TranscriptBlock[],
101
+ queueMap: QueueRows,
102
+ signal: AbortSignal,
103
+ ) => {
104
+ const result: DxGridAxisMeta = { grid: {} };
105
+ for (let row = 0; row < queueMap.length; row++) {
106
+ await yieldOrContinue('smooth', signal);
107
+ const [blockIndex, segmentIndex] = queueMap[row];
108
+ if (segmentIndex < 0) {
109
+ result.grid[row] = { size: lineHeight + cellSpacing };
110
+ } else {
111
+ host.textContent = blocks[blockIndex]!.segments[segmentIndex]!.text;
112
+ result.grid[row] = { size: host.offsetHeight };
113
+ }
114
+ }
115
+ return result;
116
+ };
117
+
118
+ export type TranscriptProps = ThemedClassName<{
119
+ blocks?: TranscriptBlock[];
120
+ attendableId?: string;
121
+ ignoreAttention?: boolean;
122
+ }>;
123
+
124
+ /**
125
+ * @@deprecated
126
+ */
127
+ export const Transcript: FC<TranscriptProps> = ({ classNames, blocks, attendableId, ignoreAttention }) => {
128
+ const { t } = useTranslation(TRANSCRIPTION_PLUGIN);
129
+ const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
130
+ const [rows, setRows] = useState<DxGridAxisMeta | undefined>(undefined);
131
+ const [columns, setColumns] = useState<DxGridAxisMeta | undefined>(undefined);
132
+
133
+ const queueMap = useMemo(() => mapTranscriptQueue(blocks), [blocks]);
134
+
135
+ const { hasAttention } = useAttention(attendableId);
136
+ const [autoScroll, setAutoScroll] = useState(true);
137
+ const handleWheel = useCallback(
138
+ (event: WheelEvent) => {
139
+ setAutoScroll(false);
140
+ if (!ignoreAttention && !hasAttention) {
141
+ event.stopPropagation();
142
+ }
143
+ },
144
+ [hasAttention, ignoreAttention],
145
+ );
146
+
147
+ const abortControllerRef = useRef<AbortController>();
148
+
149
+ const handleResize = useCallback(
150
+ async ({ entry }: { entry: { target: HTMLDivElement } | null }) => {
151
+ if (entry?.target && Array.isArray(blocks) && blocks[0] && queueMap) {
152
+ abortControllerRef.current?.abort();
153
+ abortControllerRef.current = new AbortController();
154
+ setColumns({ grid: { 0: { size: timestampColumnWidth }, 1: { size: entry.target.offsetWidth } } });
155
+ return measureRows(entry.target, blocks, queueMap, abortControllerRef.current.signal)
156
+ .then(setRows)
157
+ .catch(() => {
158
+ // Aborted mid-measurement by new size.
159
+ });
160
+ }
161
+ },
162
+ [blocks, queueMap],
163
+ );
164
+
165
+ const { width, ref: measureRef } = useResizeDetector({
166
+ onResize: handleResize as OnResizeCallback,
167
+ refreshOptions: { leading: true },
168
+ });
169
+
170
+ useEffect(() => {
171
+ if (queueMap.length !== Object.keys(rows?.grid ?? {}).length) {
172
+ void handleResize({ entry: { target: measureRef.current } }).then(() => {
173
+ if (autoScroll) {
174
+ // TODO(thure): Implement a deterministic way to do this when `rows` has fully settled and grid has a new `maxPosBlock`.
175
+ setTimeout(() => dxGrid?.scrollToEndRow(), 50);
176
+ }
177
+ });
178
+ }
179
+ }, [blocks, queueMap, width, autoScroll]);
180
+
181
+ useEffect(() => {
182
+ if (dxGrid && Array.isArray(blocks) && blocks[0]) {
183
+ const transcriptStart = blocks[0]!.segments[0]!.started;
184
+ dxGrid.getCells = (range, plane) => {
185
+ switch (plane) {
186
+ case 'grid': {
187
+ const cells: DxGridPlaneCells = {};
188
+ for (let row = range.start.row; row <= range.end.row && row < queueMap.length; row++) {
189
+ const [blockIndex, segmentIndex] = queueMap[row];
190
+
191
+ cells[toPlaneCellIndex({ col: 0, row })] = {
192
+ readonly: true,
193
+ value:
194
+ segmentIndex < 0
195
+ ? formatTimestamp(
196
+ new Date(transcriptStart),
197
+ new Date(blocks[blockIndex]!.segments[Math.max(0, segmentIndex)]!.started),
198
+ )
199
+ : '',
200
+ className: timestampClasses,
201
+ } satisfies DxGridCellValue;
202
+
203
+ cells[toPlaneCellIndex({ col: 1, row })] = (
204
+ segmentIndex < 0
205
+ ? {
206
+ readonly: true,
207
+ accessoryHtml: `<span data-hue="${blocks[blockIndex]!.authorHue}" class="dx-text-hue">${blocks[blockIndex]!.authorName}</span>`,
208
+ className: authorClasses,
209
+ }
210
+ : {
211
+ readonly: true,
212
+ value: blocks[blockIndex]!.segments[segmentIndex]!.text,
213
+ className: segmentTextClasses,
214
+ }
215
+ ) satisfies DxGridCellValue;
216
+ }
217
+ return cells;
218
+ }
219
+ default:
220
+ return {};
221
+ }
222
+ };
223
+ }
224
+ }, [dxGrid, blocks]);
225
+
226
+ const handleScrollToEnd = useCallback(() => {
227
+ setAutoScroll(true);
228
+ dxGrid?.scrollToEndRow();
229
+ }, [dxGrid, autoScroll]);
230
+
231
+ return (
232
+ <div role='none' className={mx('relative min-bs-0', classNames)}>
233
+ <Grid.Root id={`${attendableId}--transcript`}>
234
+ <Grid.Content
235
+ limitColumns={2}
236
+ limitRows={queueMap.length}
237
+ columns={columns}
238
+ rows={rows}
239
+ rowDefault={rowDefault}
240
+ onWheel={handleWheel}
241
+ className='[--dx-grid-base:var(--dx-baseSurface)] [--dx-grid-lines:var(--dx-baseSurface)] [&_.dx-grid]:min-bs-0 [&_.dx-grid]:min-is-0 [&_.dx-grid]:select-auto'
242
+ ref={setDxGrid}
243
+ />
244
+ </Grid.Root>
245
+ <div role='none' {...{ inert: '' }} aria-hidden className={measureClasses} ref={measureRef} />
246
+ <IconButton
247
+ icon='ph--arrow-line-down--regular'
248
+ iconOnly
249
+ label={t('scroll to end label')}
250
+ tooltipSide='left'
251
+ data-state={autoScroll ? 'invisible' : 'visible'}
252
+ classNames={[
253
+ 'absolute inline-end-2 block-end-2 opacity-0 pointer-events-none',
254
+ 'data-[state="visible"]:pointer-events-auto data-[state="visible"]:opacity-100 transition-opacity',
255
+ ]}
256
+ onClick={handleScrollToEnd}
257
+ />
258
+ </div>
259
+ );
260
+ };
@@ -0,0 +1,236 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Extension, RangeSetBuilder } from '@codemirror/state';
6
+ import { EditorView, GutterMarker, ViewPlugin, type ViewUpdate, gutter } from '@codemirror/view';
7
+ import { format } from 'date-fns/format';
8
+ import { intervalToDuration } from 'date-fns/intervalToDuration';
9
+
10
+ import { type CleanupFn, addEventListener, combine } from '@dxos/async';
11
+ import { type RenderCallback } from '@dxos/react-ui-editor';
12
+ import { type DataType } from '@dxos/schema';
13
+
14
+ import { DocumentAdapter, type SerializationModel } from '../../model';
15
+
16
+ /**
17
+ * Data structure that maps Chunks queue to lines with transcript state.
18
+ */
19
+ export type TranscriptOptions = {
20
+ model: SerializationModel<DataType.Message>;
21
+ started?: Date;
22
+ renderButton?: RenderCallback<{ onClick: () => void }>;
23
+ };
24
+
25
+ /**
26
+ * Scrolling transcript with timestamps.
27
+ */
28
+ export const transcript = ({ model, started, renderButton }: TranscriptOptions): Extension => {
29
+ return [
30
+ // Show timestamps in the gutter.
31
+ gutter({
32
+ class: 'cm-timestamp-gutter',
33
+ lineMarkerChange: (update) => update.docChanged || update.viewportChanged,
34
+ markers: (view) => {
35
+ const start = getStartTime(started, model.chunks[0]);
36
+ const builder = new RangeSetBuilder<GutterMarker>();
37
+ for (const { from, to } of view.visibleRanges) {
38
+ let line = view.state.doc.lineAt(from);
39
+ while (line.from <= to) {
40
+ const block = model.getChunkAtLine(line.number)?.blocks[0];
41
+ const timestamp = block?.type === 'transcription' && block.started;
42
+ if (timestamp) {
43
+ builder.add(line.from, line.from, new TimestampMarker(line.number, new Date(timestamp), start));
44
+ }
45
+
46
+ if (line.to + 1 > view.state.doc.length) {
47
+ break;
48
+ }
49
+
50
+ line = view.state.doc.lineAt(line.to + 1);
51
+ }
52
+ }
53
+
54
+ return builder.finish();
55
+ },
56
+ }),
57
+
58
+ // Listen for model updates.
59
+ ViewPlugin.fromClass(
60
+ class {
61
+ private readonly _controls?: HTMLDivElement;
62
+ private readonly _cleanup: CleanupFn;
63
+ private readonly _adapter: DocumentAdapter;
64
+ private _initialized = false;
65
+
66
+ constructor(view: EditorView) {
67
+ this._adapter = new DocumentAdapter(view);
68
+
69
+ const scroller = view.scrollDOM;
70
+ let isAutoScrolling = false;
71
+ let hasScrolled = false;
72
+
73
+ let timeout: NodeJS.Timeout | undefined;
74
+ const scrollToBottom = (smooth = false) => {
75
+ scroller.style.scrollBehavior = smooth ? 'smooth' : '';
76
+
77
+ // Temporarily hide scrollbar to prevent flicker.
78
+ scroller.classList.add('cm-hide-scrollbar');
79
+ isAutoScrolling = true;
80
+ clearTimeout(timeout);
81
+ timeout = setTimeout(() => {
82
+ this._controls?.classList.add('opacity-0');
83
+ scroller.classList.remove('cm-hide-scrollbar');
84
+ isAutoScrolling = false;
85
+ }, 500);
86
+
87
+ // Scroll to bottom.
88
+ view.dispatch({
89
+ effects: EditorView.scrollIntoView(view.state.doc.length, { y: 'end' }),
90
+ });
91
+ };
92
+
93
+ // Scroll button.
94
+ if (renderButton) {
95
+ this._controls = document.createElement('div');
96
+ this._controls.classList.add('cm-controls', 'transition-opacity', 'duration-300', 'opacity-0');
97
+ view.dom.appendChild(this._controls);
98
+ renderButton(
99
+ this._controls,
100
+ {
101
+ onClick: () => {
102
+ scrollToBottom(false);
103
+ },
104
+ },
105
+ view,
106
+ );
107
+ }
108
+
109
+ // Event listeners.
110
+ this._cleanup = combine(
111
+ addEventListener(view.scrollDOM, 'scroll', () => {
112
+ if (!isAutoScrolling) {
113
+ hasScrolled = true;
114
+ this._controls?.classList.remove('opacity-0');
115
+ }
116
+ }),
117
+
118
+ model.update.on(() => {
119
+ // Check if clamped to bottom.
120
+ const autoScroll =
121
+ scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight === 0 || !hasScrolled;
122
+
123
+ // Sync.
124
+ model.sync(this._adapter);
125
+
126
+ // Scroll.
127
+ if (autoScroll) {
128
+ scrollToBottom(true);
129
+ }
130
+ }),
131
+ );
132
+ }
133
+
134
+ update(update: ViewUpdate) {
135
+ // Initial sync.
136
+ if (!this._initialized) {
137
+ this._initialized = true;
138
+ setTimeout(() => {
139
+ model.sync(this._adapter);
140
+ });
141
+ }
142
+ }
143
+
144
+ destroy() {
145
+ this._controls?.remove();
146
+ this._cleanup();
147
+ }
148
+ },
149
+ ),
150
+
151
+ EditorView.theme({
152
+ '.cm-scroller': {
153
+ overflowY: 'scroll',
154
+ },
155
+ '.cm-hide-scrollbar': {
156
+ scrollbarWidth: 'none',
157
+ '-ms-overflow-style': 'none',
158
+ },
159
+ '.cm-hide-scrollbar::-webkit-scrollbar': {
160
+ display: 'none',
161
+ },
162
+ '.cm-controls': {
163
+ position: 'absolute',
164
+ bottom: '0.5rem',
165
+ right: '0.5rem',
166
+ zIndex: 1000,
167
+ },
168
+ '.cm-line': {
169
+ paddingRight: '1rem',
170
+ },
171
+ '.cm-timestamp-gutter': {
172
+ width: '6rem',
173
+ paddingRight: '1rem',
174
+ },
175
+ '.cm-timestamp-gutter > .cm-gutterElement > div': {
176
+ display: 'inline-flex',
177
+ textAlign: 'right',
178
+ padding: '3px',
179
+ },
180
+ }),
181
+ ];
182
+ };
183
+
184
+ /**
185
+ * Gutter marker that displays a timestamp.
186
+ */
187
+ class TimestampMarker extends GutterMarker {
188
+ constructor(
189
+ readonly _line: number,
190
+ readonly _timestamp: Date,
191
+ readonly _started?: Date,
192
+ ) {
193
+ super();
194
+ }
195
+
196
+ override eq(other: this) {
197
+ return other._timestamp === this._timestamp;
198
+ }
199
+
200
+ override toDOM(view: EditorView) {
201
+ const el = document.createElement('div');
202
+ el.className = 'text-sm text-subdued hover:bg-hoverSurface cursor-pointer';
203
+ el.textContent = formatTimestamp(this._timestamp, this._started);
204
+ // TODO(burdon): Click to bookmark or copy hyperlink.
205
+ el.onclick = () => {
206
+ const pos = view.state.doc.line(this._line).from;
207
+ view.dispatch({
208
+ effects: EditorView.scrollIntoView(pos, { y: 'start' }),
209
+ });
210
+ };
211
+
212
+ return el;
213
+ }
214
+ }
215
+
216
+ const getStartTime = (started?: Date, message?: DataType.Message): Date | undefined => {
217
+ if (started) {
218
+ return started;
219
+ }
220
+
221
+ if (message?.blocks[0]?.type === 'transcription' && message.blocks[0].started) {
222
+ return new Date(message.blocks[0].started);
223
+ }
224
+
225
+ return undefined;
226
+ };
227
+
228
+ const formatTimestamp = (timestamp: Date, relative?: Date) => {
229
+ if (relative) {
230
+ const pad = (n = 0) => String(n).padStart(2, '0');
231
+ const { hours, minutes, seconds } = intervalToDuration({ start: relative, end: timestamp });
232
+ return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
233
+ } else {
234
+ return format(timestamp, 'HH:mm:ss');
235
+ }
236
+ };
@@ -4,20 +4,25 @@
4
4
 
5
5
  import React, { type FC } from 'react';
6
6
 
7
- import { fullyQualifiedId } from '@dxos/client/echo';
8
- import { useQueue } from '@dxos/react-client/echo';
7
+ import { fullyQualifiedId, getSpace } from '@dxos/client/echo';
8
+ import { useMembers, useQueue } from '@dxos/react-client/echo';
9
9
  import { StackItem } from '@dxos/react-ui-stack';
10
+ import { type DataType } from '@dxos/schema';
10
11
 
11
- import { Transcript } from './Transcript';
12
- import { type TranscriptBlock, type TranscriptType } from '../types';
12
+ import { Transcript, renderMarkdown } from './Transcript';
13
+ import { useQueueModelAdapter } from '../hooks';
14
+ import { type TranscriptType } from '../types';
13
15
 
14
16
  export const TranscriptionContainer: FC<{ role: string; transcript: TranscriptType }> = ({ role, transcript }) => {
15
17
  const attendableId = fullyQualifiedId(transcript);
16
- const queue = useQueue<TranscriptBlock>(transcript.queue.dxn, { pollInterval: 1_000 });
18
+ const space = getSpace(transcript);
19
+ const members = useMembers(space?.key).map((member) => member.identity);
20
+ const queue = useQueue<DataType.Message>(transcript.queue.dxn, { pollInterval: 1_000 });
21
+ const model = useQueueModelAdapter(renderMarkdown(members), queue);
17
22
 
18
23
  return (
19
24
  <StackItem.Content role={role} classNames='container-max-width pli-2'>
20
- <Transcript attendableId={attendableId} blocks={queue?.items} />
25
+ <Transcript attendableId={attendableId} space={space} model={model} transcript={transcript} />
21
26
  </StackItem.Content>
22
27
  );
23
28
  };
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Input, useTranslation } from '@dxos/react-ui';
8
+ import { DeprecatedFormContainer, DeprecatedFormInput } from '@dxos/react-ui-form';
9
+
10
+ import { TRANSCRIPTION_PLUGIN } from '../../meta';
11
+ import { type TranscriptionSettingsProps } from '../../types';
12
+
13
+ export const TranscriptionSettings = ({ settings }: { settings: TranscriptionSettingsProps }) => {
14
+ const { t } = useTranslation(TRANSCRIPTION_PLUGIN);
15
+
16
+ return (
17
+ <DeprecatedFormContainer>
18
+ <DeprecatedFormInput label={t('settings entity extraction label')}>
19
+ <Input.Switch
20
+ checked={!!settings.entityExtraction}
21
+ onCheckedChange={(checked) => (settings.entityExtraction = checked)}
22
+ />
23
+ </DeprecatedFormInput>
24
+ </DeprecatedFormContainer>
25
+ );
26
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './TranscriptionSettings';
@@ -6,5 +6,6 @@ import { lazy } from 'react';
6
6
 
7
7
  export * from './Transcript';
8
8
  export * from './TranscriptContainer';
9
+ export * from './TranscriptionSettings';
9
10
 
10
11
  export const TranscriptContainer = lazy(() => import('./TranscriptContainer'));