@dxos/plugin-transcription 0.8.2-main.12df754 → 0.8.2-main.30e4dbb

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 (202) hide show
  1. package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs → TranscriptContainer-3LU3XBEB.mjs} +3 -3
  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-DR3NBV6Q.mjs → chunk-GWY2Q2NE.mjs} +105 -78
  5. package/dist/lib/browser/chunk-GWY2Q2NE.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-RHHJZQPM.mjs → chunk-HNIKXVCG.mjs} +135 -117
  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-5BE6RTNC.mjs → chunk-LTIQFX4W.mjs} +1 -1
  11. package/dist/lib/browser/{chunk-5BE6RTNC.mjs.map → chunk-LTIQFX4W.mjs.map} +1 -1
  12. package/dist/lib/browser/{chunk-5Q3R53U3.mjs → chunk-TGOKX32G.mjs} +21 -31
  13. package/dist/lib/browser/chunk-TGOKX32G.mjs.map +7 -0
  14. package/dist/lib/browser/index.mjs +29 -17
  15. package/dist/lib/browser/index.mjs.map +3 -3
  16. package/dist/lib/browser/{intent-resolver-ZQQEH27F.mjs → intent-resolver-BBB3DWV4.mjs} +4 -4
  17. package/dist/lib/browser/{intent-resolver-ZQQEH27F.mjs.map → intent-resolver-BBB3DWV4.mjs.map} +2 -2
  18. package/dist/lib/browser/{meeting-transcription-state-5D52VTHU.mjs → meeting-transcription-state-MNA2XROE.mjs} +2 -2
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/{react-surface-HETJ5XY6.mjs → react-surface-R277DACJ.mjs} +18 -7
  21. package/dist/lib/browser/react-surface-R277DACJ.mjs.map +7 -0
  22. package/dist/lib/browser/settings-I226DZ27.mjs +24 -0
  23. package/dist/lib/browser/settings-I226DZ27.mjs.map +7 -0
  24. package/dist/lib/browser/{transcriber-SMRLU2KY.mjs → transcriber-QRSS6X6S.mjs} +4 -4
  25. package/dist/lib/browser/types/index.mjs +3 -3
  26. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs → TranscriptContainer-DICOMZLM.cjs} +8 -8
  27. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs.map → TranscriptContainer-DICOMZLM.cjs.map} +2 -2
  28. package/dist/lib/node/app-graph-builder-I6LYVIYY.cjs +350 -0
  29. package/dist/lib/node/app-graph-builder-I6LYVIYY.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-3QEJ5EBO.cjs → chunk-34WFY7M7.cjs} +22 -32
  31. package/dist/lib/node/chunk-34WFY7M7.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-KO6MKCED.cjs → chunk-4JOJCZPJ.cjs} +138 -119
  33. package/dist/lib/node/chunk-4JOJCZPJ.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-HYGDHS6X.cjs → chunk-JUGOXWES.cjs} +4 -4
  35. package/dist/lib/node/{chunk-HYGDHS6X.cjs.map → chunk-JUGOXWES.cjs.map} +1 -1
  36. package/dist/lib/node/{chunk-WXG4Z2EZ.cjs → chunk-K553L7CZ.cjs} +23 -8
  37. package/dist/lib/node/chunk-K553L7CZ.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-LA73IHCB.cjs → chunk-RK4BEN5E.cjs} +108 -80
  39. package/dist/lib/node/chunk-RK4BEN5E.cjs.map +7 -0
  40. package/dist/lib/node/index.cjs +46 -34
  41. package/dist/lib/node/index.cjs.map +3 -3
  42. package/dist/lib/node/{intent-resolver-VY3XWYAA.cjs → intent-resolver-7XY3WITN.cjs} +8 -8
  43. package/dist/lib/node/{intent-resolver-VY3XWYAA.cjs.map → intent-resolver-7XY3WITN.cjs.map} +2 -2
  44. package/dist/lib/node/{meeting-transcription-state-OYWCY5XE.cjs → meeting-transcription-state-5XLTMWPA.cjs} +6 -6
  45. package/dist/lib/node/meta.json +1 -1
  46. package/dist/lib/node/{react-surface-M2JQIIDY.cjs → react-surface-HUSWAORU.cjs} +20 -11
  47. package/dist/lib/node/react-surface-HUSWAORU.cjs.map +7 -0
  48. package/dist/lib/node/settings-NDMU3EWQ.cjs +38 -0
  49. package/dist/lib/node/settings-NDMU3EWQ.cjs.map +7 -0
  50. package/dist/lib/node/{transcriber-E6AAB4SU.cjs → transcriber-UZJMG44C.cjs} +10 -10
  51. package/dist/lib/node/types/index.cjs +8 -8
  52. package/dist/lib/node/types/index.cjs.map +1 -1
  53. package/dist/lib/node-esm/{TranscriptContainer-3HJ752CP.mjs → TranscriptContainer-K4E4P5T6.mjs} +3 -3
  54. package/dist/lib/node-esm/app-graph-builder-6FY2UQOH.mjs +350 -0
  55. package/dist/lib/node-esm/app-graph-builder-6FY2UQOH.mjs.map +7 -0
  56. package/dist/lib/node-esm/chunk-3I7AC3D3.mjs +28 -0
  57. package/dist/lib/node-esm/chunk-3I7AC3D3.mjs.map +7 -0
  58. package/dist/lib/node-esm/{chunk-YXZOLOFT.mjs → chunk-5E2MR5XK.mjs} +1 -1
  59. package/dist/lib/node-esm/{chunk-YXZOLOFT.mjs.map → chunk-5E2MR5XK.mjs.map} +1 -1
  60. package/dist/lib/node-esm/{chunk-SDMGJZLN.mjs → chunk-6C34FO4C.mjs} +135 -117
  61. package/dist/lib/node-esm/chunk-6C34FO4C.mjs.map +7 -0
  62. package/dist/lib/node-esm/{chunk-K3R3YK3S.mjs → chunk-CKANLRS4.mjs} +105 -78
  63. package/dist/lib/node-esm/chunk-CKANLRS4.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-54HDN33F.mjs → chunk-KUQ3ZTRX.mjs} +21 -31
  65. package/dist/lib/node-esm/chunk-KUQ3ZTRX.mjs.map +7 -0
  66. package/dist/lib/node-esm/index.mjs +29 -17
  67. package/dist/lib/node-esm/index.mjs.map +3 -3
  68. package/dist/lib/node-esm/{intent-resolver-FCTNJPWY.mjs → intent-resolver-ACGAPLDZ.mjs} +4 -4
  69. package/dist/lib/node-esm/{intent-resolver-FCTNJPWY.mjs.map → intent-resolver-ACGAPLDZ.mjs.map} +2 -2
  70. package/dist/lib/node-esm/{meeting-transcription-state-XMZJ45P4.mjs → meeting-transcription-state-3WOZ52BQ.mjs} +2 -2
  71. package/dist/lib/node-esm/meta.json +1 -1
  72. package/dist/lib/node-esm/{react-surface-PXFSIM6V.mjs → react-surface-G2OY4MWL.mjs} +18 -7
  73. package/dist/lib/node-esm/react-surface-G2OY4MWL.mjs.map +7 -0
  74. package/dist/lib/node-esm/settings-O7X2UQQG.mjs +25 -0
  75. package/dist/lib/node-esm/settings-O7X2UQQG.mjs.map +7 -0
  76. package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs → transcriber-NVVFH7IF.mjs} +4 -4
  77. package/dist/lib/node-esm/types/index.mjs +3 -3
  78. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  79. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/capabilities.d.ts +1 -1
  81. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/index.d.ts +1 -0
  83. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  84. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  86. package/dist/types/src/capabilities/settings.d.ts +4 -0
  87. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  88. package/dist/types/src/components/Transcript/Transcript.d.ts +6 -4
  89. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  90. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +8 -4
  91. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  92. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
  93. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
  94. package/dist/types/src/components/Transcript/transcript-extension.d.ts +4 -4
  95. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -1
  96. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  97. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts +6 -0
  98. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts.map +1 -0
  99. package/dist/types/src/components/TranscriptionSettings/index.d.ts +2 -0
  100. package/dist/types/src/components/TranscriptionSettings/index.d.ts.map +1 -0
  101. package/dist/types/src/components/index.d.ts +1 -0
  102. package/dist/types/src/components/index.d.ts.map +1 -1
  103. package/dist/types/src/components/transcription.stories.d.ts +6 -3
  104. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  105. package/dist/types/src/entity-extraction/entity-extraction.d.ts +33 -7
  106. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -1
  107. package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
  108. package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
  109. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +2 -2
  110. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -1
  111. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  112. package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
  113. package/dist/types/src/model/model.d.ts +23 -23
  114. package/dist/types/src/model/model.d.ts.map +1 -1
  115. package/dist/types/src/testing/index.d.ts +2 -0
  116. package/dist/types/src/testing/index.d.ts.map +1 -0
  117. package/dist/types/src/testing/testing.d.ts +40 -0
  118. package/dist/types/src/testing/testing.d.ts.map +1 -0
  119. package/dist/types/src/transcriber/transcriber.d.ts +2 -2
  120. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  121. package/dist/types/src/transcriber/transcription-manager.d.ts +17 -15
  122. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  123. package/dist/types/src/translations.d.ts +3 -18
  124. package/dist/types/src/translations.d.ts.map +1 -1
  125. package/dist/types/src/types/schema.d.ts +26 -40
  126. package/dist/types/src/types/schema.d.ts.map +1 -1
  127. package/dist/types/src/types/types.d.ts +11 -7
  128. package/dist/types/src/types/types.d.ts.map +1 -1
  129. package/dist/types/src/util/array.d.ts.map +1 -1
  130. package/dist/types/src/util/get-time-str.d.ts.map +1 -1
  131. package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
  132. package/dist/types/tsconfig.tsbuildinfo +1 -1
  133. package/package.json +51 -46
  134. package/src/TranscriptionPlugin.tsx +29 -12
  135. package/src/capabilities/app-graph-builder.ts +99 -29
  136. package/src/capabilities/capabilities.ts +1 -1
  137. package/src/capabilities/index.ts +1 -0
  138. package/src/capabilities/intent-resolver.ts +2 -2
  139. package/src/capabilities/react-surface.tsx +12 -4
  140. package/src/capabilities/settings.ts +20 -0
  141. package/src/components/Transcript/Transcript.stories.tsx +72 -36
  142. package/src/components/Transcript/Transcript.tsx +37 -30
  143. package/src/components/Transcript/deprecated/{Transcript.tsx → OldTranscript.tsx} +30 -1
  144. package/src/components/Transcript/transcript-extension.ts +20 -17
  145. package/src/components/TranscriptContainer.tsx +6 -4
  146. package/src/components/TranscriptionSettings/TranscriptionSettings.tsx +26 -0
  147. package/src/components/TranscriptionSettings/index.ts +5 -0
  148. package/src/components/index.ts +1 -0
  149. package/src/components/transcription.stories.tsx +84 -34
  150. package/src/entity-extraction/entity-extraction.test.ts +75 -10
  151. package/src/entity-extraction/entity-extraction.ts +110 -55
  152. package/src/entity-extraction/system-prompt.tpl +38 -9
  153. package/src/hooks/useQueueModelAdapter.ts +44 -11
  154. package/src/model/model.test.ts +90 -44
  155. package/src/model/model.ts +59 -59
  156. package/src/testing/index.ts +5 -0
  157. package/src/testing/testing.ts +221 -0
  158. package/src/transcriber/transcriber.browser.test.ts +3 -3
  159. package/src/transcriber/transcriber.ts +19 -10
  160. package/src/transcriber/transcription-manager.ts +66 -57
  161. package/src/translations.ts +2 -0
  162. package/src/types/schema.ts +10 -31
  163. package/src/types/types.ts +17 -7
  164. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs +0 -168
  165. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +0 -7
  166. package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +0 -7
  167. package/dist/lib/browser/chunk-DR3NBV6Q.mjs.map +0 -7
  168. package/dist/lib/browser/chunk-GI5LZ4SV.mjs +0 -8
  169. package/dist/lib/browser/chunk-GI5LZ4SV.mjs.map +0 -7
  170. package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +0 -7
  171. package/dist/lib/browser/react-surface-HETJ5XY6.mjs.map +0 -7
  172. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs +0 -177
  173. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +0 -7
  174. package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +0 -7
  175. package/dist/lib/node/chunk-KO6MKCED.cjs.map +0 -7
  176. package/dist/lib/node/chunk-LA73IHCB.cjs.map +0 -7
  177. package/dist/lib/node/chunk-WXG4Z2EZ.cjs.map +0 -7
  178. package/dist/lib/node/react-surface-M2JQIIDY.cjs.map +0 -7
  179. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs +0 -169
  180. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +0 -7
  181. package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +0 -7
  182. package/dist/lib/node-esm/chunk-K3R3YK3S.mjs.map +0 -7
  183. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +0 -7
  184. package/dist/lib/node-esm/chunk-XY22OP6H.mjs +0 -10
  185. package/dist/lib/node-esm/chunk-XY22OP6H.mjs.map +0 -7
  186. package/dist/lib/node-esm/react-surface-PXFSIM6V.mjs.map +0 -7
  187. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +0 -13
  188. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +0 -1
  189. package/dist/types/src/components/Transcript/testings.d.ts +0 -51
  190. package/dist/types/src/components/Transcript/testings.d.ts.map +0 -1
  191. package/dist/types/src/testing/test-data.d.ts +0 -73
  192. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  193. package/src/components/Transcript/testings.ts +0 -182
  194. package/src/testing/test-data.ts +0 -314
  195. /package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs.map → TranscriptContainer-3LU3XBEB.mjs.map} +0 -0
  196. /package/dist/lib/browser/{meeting-transcription-state-5D52VTHU.mjs.map → meeting-transcription-state-MNA2XROE.mjs.map} +0 -0
  197. /package/dist/lib/browser/{transcriber-SMRLU2KY.mjs.map → transcriber-QRSS6X6S.mjs.map} +0 -0
  198. /package/dist/lib/node/{meeting-transcription-state-OYWCY5XE.cjs.map → meeting-transcription-state-5XLTMWPA.cjs.map} +0 -0
  199. /package/dist/lib/node/{transcriber-E6AAB4SU.cjs.map → transcriber-UZJMG44C.cjs.map} +0 -0
  200. /package/dist/lib/node-esm/{TranscriptContainer-3HJ752CP.mjs.map → TranscriptContainer-K4E4P5T6.mjs.map} +0 -0
  201. /package/dist/lib/node-esm/{meeting-transcription-state-XMZJ45P4.mjs.map → meeting-transcription-state-3WOZ52BQ.mjs.map} +0 -0
  202. /package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs.map → transcriber-NVVFH7IF.mjs.map} +0 -0
@@ -9,15 +9,15 @@ import { intervalToDuration } from 'date-fns/intervalToDuration';
9
9
 
10
10
  import { type CleanupFn, addEventListener, combine } from '@dxos/async';
11
11
  import { type RenderCallback } from '@dxos/react-ui-editor';
12
+ import { type DataType } from '@dxos/schema';
12
13
 
13
- import { DocumentAdapter, type BlockModel } from '../../model';
14
- import { type TranscriptBlock } from '../../types';
14
+ import { DocumentAdapter, type SerializationModel } from '../../model';
15
15
 
16
16
  /**
17
- * Data structure that maps Blocks queue to lines with transcript state.
17
+ * Data structure that maps Chunks queue to lines with transcript state.
18
18
  */
19
19
  export type TranscriptOptions = {
20
- model: BlockModel<TranscriptBlock>;
20
+ model: SerializationModel<DataType.Message>;
21
21
  started?: Date;
22
22
  renderButton?: RenderCallback<{ onClick: () => void }>;
23
23
  };
@@ -32,12 +32,13 @@ export const transcript = ({ model, started, renderButton }: TranscriptOptions):
32
32
  class: 'cm-timestamp-gutter',
33
33
  lineMarkerChange: (update) => update.docChanged || update.viewportChanged,
34
34
  markers: (view) => {
35
- const start = getStartTime(started, model.blocks[0]);
35
+ const start = getStartTime(started, model.chunks[0]);
36
36
  const builder = new RangeSetBuilder<GutterMarker>();
37
37
  for (const { from, to } of view.visibleRanges) {
38
38
  let line = view.state.doc.lineAt(from);
39
39
  while (line.from <= to) {
40
- const timestamp = model.getBlockAtLine(line.number)?.segments[0]?.started;
40
+ const block = model.getChunkAtLine(line.number)?.blocks[0];
41
+ const timestamp = block?.type === 'transcription' && block.started;
41
42
  if (timestamp) {
42
43
  builder.add(line.from, line.from, new TimestampMarker(line.number, new Date(timestamp), start));
43
44
  }
@@ -69,13 +70,15 @@ export const transcript = ({ model, started, renderButton }: TranscriptOptions):
69
70
  let isAutoScrolling = false;
70
71
  let hasScrolled = false;
71
72
 
72
- const scrollToBottom = () => {
73
- scroller.style.scrollBehavior = 'smooth';
73
+ let timeout: NodeJS.Timeout | undefined;
74
+ const scrollToBottom = (smooth = false) => {
75
+ scroller.style.scrollBehavior = smooth ? 'smooth' : '';
74
76
 
75
77
  // Temporarily hide scrollbar to prevent flicker.
76
78
  scroller.classList.add('cm-hide-scrollbar');
77
79
  isAutoScrolling = true;
78
- setTimeout(() => {
80
+ clearTimeout(timeout);
81
+ timeout = setTimeout(() => {
79
82
  this._controls?.classList.add('opacity-0');
80
83
  scroller.classList.remove('cm-hide-scrollbar');
81
84
  isAutoScrolling = false;
@@ -96,7 +99,7 @@ export const transcript = ({ model, started, renderButton }: TranscriptOptions):
96
99
  this._controls,
97
100
  {
98
101
  onClick: () => {
99
- scrollToBottom();
102
+ scrollToBottom(false);
100
103
  },
101
104
  },
102
105
  view,
@@ -122,7 +125,7 @@ export const transcript = ({ model, started, renderButton }: TranscriptOptions):
122
125
 
123
126
  // Scroll.
124
127
  if (autoScroll) {
125
- scrollToBottom();
128
+ scrollToBottom(true);
126
129
  }
127
130
  }),
128
131
  );
@@ -210,24 +213,24 @@ class TimestampMarker extends GutterMarker {
210
213
  }
211
214
  }
212
215
 
213
- const getStartTime = (started?: Date, block?: TranscriptBlock): Date | undefined => {
216
+ const getStartTime = (started?: Date, message?: DataType.Message): Date | undefined => {
214
217
  if (started) {
215
218
  return started;
216
219
  }
217
220
 
218
- if (block?.segments[0]?.started) {
219
- return new Date(block.segments[0].started);
221
+ if (message?.blocks[0]?.type === 'transcription' && message.blocks[0].started) {
222
+ return new Date(message.blocks[0].started);
220
223
  }
221
224
 
222
225
  return undefined;
223
226
  };
224
227
 
225
228
  const formatTimestamp = (timestamp: Date, relative?: Date) => {
226
- if (!relative) {
227
- return format(timestamp, 'HH:mm:ss');
228
- } else {
229
+ if (relative) {
229
230
  const pad = (n = 0) => String(n).padStart(2, '0');
230
231
  const { hours, minutes, seconds } = intervalToDuration({ start: relative, end: timestamp });
231
232
  return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
233
+ } else {
234
+ return format(timestamp, 'HH:mm:ss');
232
235
  }
233
236
  };
@@ -5,18 +5,20 @@
5
5
  import React, { type FC } from 'react';
6
6
 
7
7
  import { fullyQualifiedId, getSpace } from '@dxos/client/echo';
8
- import { useQueue } from '@dxos/react-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
12
  import { Transcript, renderMarkdown } from './Transcript';
12
13
  import { useQueueModelAdapter } from '../hooks';
13
- import { type TranscriptBlock, type TranscriptType } from '../types';
14
+ import { type TranscriptType } from '../types';
14
15
 
15
16
  export const TranscriptionContainer: FC<{ role: string; transcript: TranscriptType }> = ({ role, transcript }) => {
16
17
  const attendableId = fullyQualifiedId(transcript);
17
18
  const space = getSpace(transcript);
18
- const queue = useQueue<TranscriptBlock>(transcript.queue.dxn, { pollInterval: 1_000 });
19
- const model = useQueueModelAdapter(renderMarkdown, queue);
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);
20
22
 
21
23
  return (
22
24
  <StackItem.Content role={role} classNames='container-max-width pli-2'>
@@ -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'));
@@ -18,32 +18,40 @@ import React, {
18
18
 
19
19
  import { Events, IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
20
20
  import { withPluginManager } from '@dxos/app-framework/testing';
21
- import { MemoryQueue } from '@dxos/echo-db';
21
+ import { AIServiceEdgeClient } from '@dxos/assistant';
22
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
23
+ import { Filter, MemoryQueue } from '@dxos/echo-db';
22
24
  import { create } from '@dxos/echo-schema';
23
25
  import { type DXN } from '@dxos/keys';
24
26
  import { log } from '@dxos/log';
25
27
  import { ClientPlugin } from '@dxos/plugin-client';
28
+ import { PreviewPlugin } from '@dxos/plugin-preview';
26
29
  import { SpacePlugin } from '@dxos/plugin-space';
30
+ import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
27
31
  import { ThemePlugin } from '@dxos/plugin-theme';
28
- import { randomQueueDxn, useQueue } from '@dxos/react-client/echo';
32
+ import { createQueueDxn, useQueue, useSpace } from '@dxos/react-client/echo';
29
33
  import { IconButton, Toolbar } from '@dxos/react-ui';
30
34
  import { ScrollContainer } from '@dxos/react-ui-components';
31
35
  import { defaultTx } from '@dxos/react-ui-theme';
36
+ import { DataType } from '@dxos/schema';
37
+ import { seedTestData, Testing } from '@dxos/schema/testing';
32
38
  import { withLayout, withTheme } from '@dxos/storybook-utils';
33
39
 
34
40
  import { renderMarkdown, Transcript } from './Transcript';
35
- import { TestItem } from './Transcript/testings';
36
41
  import { TranscriptionPlugin } from '../TranscriptionPlugin';
42
+ import { processTranscriptMessage } from '../entity-extraction';
37
43
  import { useAudioFile, useAudioTrack, useQueueModelAdapter, useTranscriber } from '../hooks';
38
- import { type BlockModel } from '../model';
44
+ import { type SerializationModel } from '../model';
45
+ import { TestItem } from '../testing';
39
46
  import { type TranscriberParams } from '../transcriber';
40
- import { TranscriptBlock } from '../types';
41
47
 
42
48
  const TranscriptionStory: FC<{
43
- model: BlockModel<TranscriptBlock>;
49
+ model: SerializationModel<DataType.Message>;
44
50
  running: boolean;
45
51
  onRunningChange: Dispatch<SetStateAction<boolean>>;
46
52
  }> = ({ model, running, onRunningChange }) => {
53
+ const space = useSpace();
54
+
47
55
  return (
48
56
  <div className='flex flex-col w-[30rem]'>
49
57
  <Toolbar.Root>
@@ -55,30 +63,67 @@ const TranscriptionStory: FC<{
55
63
  />
56
64
  </Toolbar.Root>
57
65
  <ScrollContainer>
58
- <Transcript model={model} attendableId='story' />
66
+ <Transcript space={space} model={model} attendableId='story' />
59
67
  </ScrollContainer>
60
68
  </div>
61
69
  );
62
70
  };
63
71
 
64
- const Microphone = () => {
72
+ const aiService = new AIServiceEdgeClient({
73
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
74
+ });
75
+
76
+ const Microphone = ({ entityExtraction }: { entityExtraction?: boolean }) => {
65
77
  const [running, setRunning] = useState(false);
66
78
 
67
79
  // Audio.
68
80
  const track = useAudioTrack(running);
69
81
 
70
82
  // Queue.
71
- const queueDxn = useMemo(() => randomQueueDxn(), []);
72
- const queue = useMemo(() => new MemoryQueue<TranscriptBlock>(queueDxn), [queueDxn]);
73
- const model = useQueueModelAdapter(renderMarkdown, queue);
83
+ const queueDxn = useMemo(() => createQueueDxn(), []);
84
+ const queue = useMemo(() => new MemoryQueue<DataType.Message>(queueDxn), [queueDxn]);
85
+ const model = useQueueModelAdapter(renderMarkdown([]), queue);
86
+ const space = useSpace();
74
87
 
75
88
  // Transcriber.
76
89
  const handleSegments = useCallback<TranscriberParams['onSegments']>(
77
- async (segments) => {
78
- const block = create(TranscriptBlock, { segments });
79
- void queue.append([block]);
90
+ async (blocks) => {
91
+ const message = create(DataType.Message, { sender: { name: 'You' }, created: new Date().toISOString(), blocks });
92
+
93
+ if (entityExtraction) {
94
+ if (!space) {
95
+ log.warn('no space');
96
+ return;
97
+ }
98
+ // TODO(dmaretskyi): Move to vector search index.
99
+ const { objects } = await space.db
100
+ .query(
101
+ Filter.or(
102
+ Filter.schema(DataType.Person),
103
+ Filter.schema(DataType.Organization),
104
+ Filter.schema(Testing.DocumentType),
105
+ ),
106
+ )
107
+ .run();
108
+
109
+ log.info('context', { objects });
110
+
111
+ const result = await processTranscriptMessage({
112
+ message,
113
+ aiService,
114
+ context: {
115
+ objects,
116
+ },
117
+ options: {
118
+ fallbackToRaw: true,
119
+ },
120
+ });
121
+ void queue.append([result.message]);
122
+ } else {
123
+ void queue.append([message]);
124
+ }
80
125
  },
81
- [queue],
126
+ [queue, space],
82
127
  );
83
128
 
84
129
  const config = useRef<TranscriberParams['config']>({
@@ -94,10 +139,8 @@ const Microphone = () => {
94
139
  const [isOpen, setIsOpen] = useState(false);
95
140
  useEffect(() => {
96
141
  void transcriber?.open().then(() => setIsOpen(true));
97
- log.info('transcriber open');
98
142
  return () => {
99
143
  void transcriber?.close().then(() => setIsOpen(false));
100
- log.info('transcriber close');
101
144
  };
102
145
  }, [transcriber]);
103
146
 
@@ -105,10 +148,8 @@ const Microphone = () => {
105
148
  useEffect(() => {
106
149
  if (running && transcriber?.isOpen) {
107
150
  transcriber?.startChunksRecording();
108
- log.info('transcriber startChunksRecording');
109
151
  } else if (!running) {
110
152
  transcriber?.stopChunksRecording();
111
- log.info('transcriber stopChunksRecording');
112
153
  }
113
154
  }, [transcriber, running, isOpen]);
114
155
 
@@ -141,12 +182,12 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
141
182
  }, [audio, running]);
142
183
 
143
184
  // Transcriber.
144
- const queue = useQueue<TranscriptBlock>(queueDxn, { pollInterval: 500 });
145
- const model = useQueueModelAdapter(renderMarkdown, queue);
185
+ const queue = useQueue<DataType.Message>(queueDxn, { pollInterval: 500 });
186
+ const model = useQueueModelAdapter(renderMarkdown([]), queue);
146
187
  const handleSegments = useCallback<TranscriberParams['onSegments']>(
147
- async (segments) => {
148
- const block = create(TranscriptBlock, { authorName: 'test', authorHue: 'cyan', segments });
149
- queue?.append([block]);
188
+ async (blocks) => {
189
+ const message = create(DataType.Message, { sender: { name: 'You' }, created: new Date().toISOString(), blocks });
190
+ void queue?.append([message]);
150
191
  },
151
192
  [queue],
152
193
  );
@@ -187,14 +228,19 @@ const meta: Meta<typeof AudioFile> = {
187
228
  withPluginManager({
188
229
  plugins: [
189
230
  ThemePlugin({ tx: defaultTx }),
231
+ StorybookLayoutPlugin(),
190
232
  ClientPlugin({
191
- types: [TestItem],
233
+ types: [TestItem, DataType.Person, DataType.Organization, Testing.DocumentType],
192
234
  onClientInitialized: async (_, client) => {
193
235
  await client.halo.createIdentity();
236
+ await client.spaces.waitUntilReady();
237
+ await client.spaces.default.waitUntilReady();
238
+ await seedTestData(client.spaces.default);
194
239
  },
195
240
  }),
196
241
  SpacePlugin(),
197
242
  SettingsPlugin(),
243
+ PreviewPlugin(),
198
244
  IntentPlugin(),
199
245
  TranscriptionPlugin(),
200
246
  ],
@@ -218,26 +264,30 @@ const meta: Meta<typeof AudioFile> = {
218
264
  // ],
219
265
  }),
220
266
  withTheme,
221
- withLayout({
222
- tooltips: true,
223
- fullscreen: true,
224
- classNames: 'justify-center',
225
- }),
267
+ withLayout({ fullscreen: true, classNames: 'justify-center' }),
226
268
  ],
227
269
  };
228
270
 
229
271
  export default meta;
230
272
 
231
- type Story = StoryObj<typeof AudioFile>;
273
+ export const Default: StoryObj<typeof Microphone> = {
274
+ render: Microphone,
275
+ args: {
276
+ entityExtraction: false,
277
+ },
278
+ };
232
279
 
233
- export const Default: Story = {
280
+ export const EntityExtraction: StoryObj<typeof Microphone> = {
234
281
  render: Microphone,
282
+ args: {
283
+ entityExtraction: true,
284
+ },
235
285
  };
236
286
 
237
- export const File: Story = {
287
+ export const File: StoryObj<typeof AudioFile> = {
238
288
  render: AudioFile,
239
289
  args: {
240
- queueDxn: randomQueueDxn(),
290
+ queueDxn: createQueueDxn(),
241
291
  // https://learnenglish.britishcouncil.org/general-english/audio-zone/living-london
242
292
  transcriptUrl: 'https://dxos.network/audio-london.txt',
243
293
  audioUrl: 'https://dxos.network/audio-london.m4a',
@@ -2,31 +2,96 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { describe, test } from 'vitest';
5
+ import { describe, test, expect } from 'vitest';
6
6
 
7
7
  import { AIServiceEdgeClient } from '@dxos/assistant';
8
8
  import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
9
9
  import { log } from '@dxos/log';
10
+ import { createTestData } from '@dxos/schema/testing';
11
+ import { range } from '@dxos/util';
10
12
 
11
- import { processTranscriptBlock } from './entity-extraction';
12
- import * as TestData from '../testing/test-data';
13
+ import { processTranscriptMessage, postprocessText } from './entity-extraction';
13
14
 
14
15
  const aiService = new AIServiceEdgeClient({
15
16
  endpoint: AI_SERVICE_ENDPOINT.REMOTE,
16
17
  });
17
18
 
18
19
  describe.skip('EntityExtraction', { timeout: 180_000 }, () => {
19
- test('should process a transcript block', async () => {
20
- for (const block of TestData.transcriptBlocks) {
21
- log.info('input', block);
22
- const { block: enhancedBlock } = await processTranscriptBlock({
23
- block,
20
+ test.skip('should process a transcript block', async () => {
21
+ const { transcriptMessages, documents, contacts } = await createTestData();
22
+
23
+ log.info('context', { documents, contacts });
24
+
25
+ for (const message of transcriptMessages) {
26
+ log.info('input', message);
27
+ const { message: enhancedMessage } = await processTranscriptMessage({
28
+ message,
29
+ aiService,
30
+ context: {
31
+ objects: [...documents, ...Object.values(contacts)],
32
+ },
33
+ });
34
+ log.info('output', enhancedMessage);
35
+ }
36
+ });
37
+
38
+ test.only('computational irreducibility', async () => {
39
+ const { transcriptWoflram, documents, contacts } = await createTestData();
40
+
41
+ log.info('context', { documents, contacts });
42
+ const message = transcriptWoflram[0];
43
+ log.info('input', message);
44
+
45
+ await Promise.all(
46
+ range(10).map(async () => {
47
+ const { message: enhancedMessage, timeElapsed } = await processTranscriptMessage({
48
+ message,
49
+ aiService,
50
+ context: {
51
+ objects: [...documents, ...Object.values(contacts)],
52
+ },
53
+ });
54
+ log.info('output', { message: enhancedMessage.blocks[0], timeElapsed });
55
+ }),
56
+ );
57
+ });
58
+
59
+ test('org and document linking', async () => {
60
+ const { transcriptJosiah, documents, contacts, organizations } = await createTestData();
61
+
62
+ log.info('context', { contacts, organizations, documents });
63
+
64
+ for (const message of transcriptJosiah) {
65
+ log.info('input', message);
66
+
67
+ const { message: enhancedMessage } = await processTranscriptMessage({
68
+ message,
24
69
  aiService,
25
70
  context: {
26
- objects: [...TestData.documents, ...Object.values(TestData.contacts)],
71
+ objects: [...documents, ...Object.values(contacts), ...Object.values(organizations)],
27
72
  },
28
73
  });
29
- log.info('output', enhancedBlock);
74
+ log.info('output', enhancedMessage);
30
75
  }
31
76
  });
32
77
  });
78
+
79
+ describe('postprocessText', () => {
80
+ test('should replace quotes with DXN references', () => {
81
+ const quotes = {
82
+ references: [{ quote: 'computational irreducibility', id: '01JTG9JW11XGWJZ32AW8ET93D1' }],
83
+ };
84
+
85
+ expect(postprocessText('This is a computational irreducibility test.', quotes)).toBe(
86
+ 'This is a [computational irreducibility][dxn:echo:@:01JTG9JW11XGWJZ32AW8ET93D1] test.',
87
+ );
88
+ expect(
89
+ postprocessText(
90
+ "And what I'd like to talk today about is Steven Wolfram's concept of a computational irreducibility.",
91
+ quotes,
92
+ ),
93
+ ).toBe(
94
+ "And what I'd like to talk today about is Steven Wolfram's concept of a [computational irreducibility][dxn:echo:@:01JTG9JW11XGWJZ32AW8ET93D1].",
95
+ );
96
+ });
97
+ });