@dxos/plugin-transcription 0.8.1 → 0.8.2-main.f081794

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 (191) hide show
  1. package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs → TranscriptContainer-4D4BHE7G.mjs} +3 -2
  2. package/dist/lib/browser/{app-graph-builder-VDQOVX7P.mjs → app-graph-builder-I4SSIQMW.mjs} +18 -41
  3. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-5BE6RTNC.mjs} +1 -1
  5. package/dist/lib/browser/chunk-5BE6RTNC.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-OGNXNGJB.mjs → chunk-5Q3R53U3.mjs} +8 -8
  7. package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +7 -0
  8. package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-DR3NBV6Q.mjs} +6 -6
  9. package/dist/lib/browser/{chunk-YCZISX63.mjs.map → chunk-DR3NBV6Q.mjs.map} +3 -3
  10. package/dist/lib/browser/{chunk-K7FOYAXO.mjs → chunk-GI5LZ4SV.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-GU2BNYKZ.mjs → chunk-KLD7O5GJ.mjs} +2 -12
  12. package/dist/lib/browser/chunk-KLD7O5GJ.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-RHHJZQPM.mjs +702 -0
  14. package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +22 -197
  16. package/dist/lib/browser/index.mjs.map +4 -4
  17. package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-ZQQEH27F.mjs} +7 -7
  18. package/dist/lib/browser/intent-resolver-ZQQEH27F.mjs.map +7 -0
  19. package/dist/lib/browser/{meeting-transcription-state-DLFHQK72.mjs → meeting-transcription-state-5D52VTHU.mjs} +4 -4
  20. package/dist/lib/browser/meeting-transcription-state-5D52VTHU.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-HETJ5XY6.mjs} +5 -4
  23. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs → transcriber-SMRLU2KY.mjs} +8 -7
  24. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs.map → transcriber-SMRLU2KY.mjs.map} +3 -3
  25. package/dist/lib/browser/types/index.mjs +1 -1
  26. package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-ZWP6UKY6.cjs} +8 -7
  27. package/dist/lib/node/TranscriptContainer-ZWP6UKY6.cjs.map +7 -0
  28. package/dist/lib/node/{app-graph-builder-IFSIVNUR.cjs → app-graph-builder-WWC4WNTG.cjs} +31 -54
  29. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-P4VVD6ZO.cjs → chunk-3QEJ5EBO.cjs} +10 -10
  31. package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-HYGDHS6X.cjs} +4 -4
  33. package/dist/lib/node/chunk-HYGDHS6X.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-3KHUSQNW.cjs → chunk-J72PUQ3C.cjs} +6 -15
  35. package/dist/lib/node/chunk-J72PUQ3C.cjs.map +7 -0
  36. package/dist/lib/node/chunk-KO6MKCED.cjs +718 -0
  37. package/dist/lib/node/chunk-KO6MKCED.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-LA73IHCB.cjs} +10 -10
  39. package/dist/lib/node/{chunk-FCCJALOT.cjs.map → chunk-LA73IHCB.cjs.map} +3 -3
  40. package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-WXG4Z2EZ.cjs} +5 -5
  41. package/dist/lib/node/index.cjs +49 -233
  42. package/dist/lib/node/index.cjs.map +4 -4
  43. package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-VY3XWYAA.cjs} +11 -10
  44. package/dist/lib/node/intent-resolver-VY3XWYAA.cjs.map +7 -0
  45. package/dist/lib/node/{meeting-transcription-state-URIYILEQ.cjs → meeting-transcription-state-OYWCY5XE.cjs} +7 -7
  46. package/dist/lib/node/meeting-transcription-state-OYWCY5XE.cjs.map +7 -0
  47. package/dist/lib/node/meta.json +1 -1
  48. package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-M2JQIIDY.cjs} +10 -9
  49. package/dist/lib/node/{react-surface-WI2MGWCC.cjs.map → react-surface-M2JQIIDY.cjs.map} +1 -1
  50. package/dist/lib/node/{transcriber-QG5WPKX4.cjs → transcriber-E6AAB4SU.cjs} +14 -13
  51. package/dist/lib/node/{transcriber-QG5WPKX4.cjs.map → transcriber-E6AAB4SU.cjs.map} +3 -3
  52. package/dist/lib/node/types/index.cjs +7 -7
  53. package/dist/lib/node/types/index.cjs.map +1 -1
  54. package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-3HJ752CP.mjs} +3 -2
  55. package/dist/lib/node-esm/{app-graph-builder-UL6XLIZY.mjs → app-graph-builder-PPJQAFSI.mjs} +18 -41
  56. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-GIO6J6FE.mjs → chunk-54HDN33F.mjs} +8 -8
  58. package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-K3R3YK3S.mjs} +6 -6
  60. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs.map → chunk-K3R3YK3S.mjs.map} +3 -3
  61. package/dist/lib/node-esm/{chunk-HKATMD34.mjs → chunk-QMHMEKB3.mjs} +2 -12
  62. package/dist/lib/node-esm/chunk-QMHMEKB3.mjs.map +7 -0
  63. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs +703 -0
  64. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs → chunk-XY22OP6H.mjs} +2 -2
  66. package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-YXZOLOFT.mjs} +1 -1
  67. package/dist/lib/node-esm/chunk-YXZOLOFT.mjs.map +7 -0
  68. package/dist/lib/node-esm/index.mjs +22 -197
  69. package/dist/lib/node-esm/index.mjs.map +4 -4
  70. package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-FCTNJPWY.mjs} +7 -7
  71. package/dist/lib/node-esm/intent-resolver-FCTNJPWY.mjs.map +7 -0
  72. package/dist/lib/node-esm/{meeting-transcription-state-ZYVRAW6Y.mjs → meeting-transcription-state-XMZJ45P4.mjs} +4 -4
  73. package/dist/lib/node-esm/meeting-transcription-state-XMZJ45P4.mjs.map +7 -0
  74. package/dist/lib/node-esm/meta.json +1 -1
  75. package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-PXFSIM6V.mjs} +5 -4
  76. package/dist/lib/{browser/react-surface-FQUHU5G7.mjs.map → node-esm/react-surface-PXFSIM6V.mjs.map} +1 -1
  77. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs → transcriber-REAGRCPY.mjs} +8 -7
  78. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs.map → transcriber-REAGRCPY.mjs.map} +3 -3
  79. package/dist/lib/node-esm/types/index.mjs +1 -1
  80. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/capabilities.d.ts +4 -6
  82. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  83. package/dist/types/src/capabilities/index.d.ts +1 -4
  84. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/meeting-transcription-state.d.ts +2 -4
  86. package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  88. package/dist/types/src/components/Transcript/Transcript.d.ts +12 -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 +15 -4
  91. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  92. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +13 -0
  93. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +1 -0
  94. package/dist/types/src/components/Transcript/testings.d.ts +51 -0
  95. package/dist/types/src/components/Transcript/testings.d.ts.map +1 -0
  96. package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
  97. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
  98. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  99. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  100. package/dist/types/src/entity-extraction/entity-extraction.d.ts +16 -0
  101. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -0
  102. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts +2 -0
  103. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts.map +1 -0
  104. package/dist/types/src/entity-extraction/index.d.ts +2 -0
  105. package/dist/types/src/entity-extraction/index.d.ts.map +1 -0
  106. package/dist/types/src/hooks/index.d.ts +1 -0
  107. package/dist/types/src/hooks/index.d.ts.map +1 -1
  108. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
  109. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
  111. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  112. package/dist/types/src/model/index.d.ts +2 -0
  113. package/dist/types/src/model/index.d.ts.map +1 -0
  114. package/dist/types/src/model/model.d.ts +68 -0
  115. package/dist/types/src/model/model.d.ts.map +1 -0
  116. package/dist/types/src/model/model.test.d.ts +2 -0
  117. package/dist/types/src/model/model.test.d.ts.map +1 -0
  118. package/dist/types/src/testing/test-data.d.ts +73 -0
  119. package/dist/types/src/testing/test-data.d.ts.map +1 -0
  120. package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
  121. package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
  122. package/dist/types/src/types/schema.d.ts +9 -9
  123. package/dist/types/src/util/index.d.ts +0 -1
  124. package/dist/types/src/util/index.d.ts.map +1 -1
  125. package/package.json +47 -39
  126. package/src/capabilities/app-graph-builder.ts +15 -26
  127. package/src/capabilities/capabilities.ts +3 -2
  128. package/src/capabilities/intent-resolver.ts +4 -4
  129. package/src/capabilities/meeting-transcription-state.ts +2 -2
  130. package/src/capabilities/transcriber.ts +2 -1
  131. package/src/components/Transcript/Transcript.stories.tsx +165 -43
  132. package/src/components/Transcript/Transcript.tsx +78 -210
  133. package/src/components/Transcript/deprecated/Transcript.tsx +231 -0
  134. package/src/components/Transcript/testings.ts +182 -0
  135. package/src/components/Transcript/transcript-extension.ts +233 -0
  136. package/src/components/TranscriptContainer.tsx +6 -3
  137. package/src/components/transcription.stories.tsx +97 -48
  138. package/src/entity-extraction/entity-extraction.test.ts +32 -0
  139. package/src/entity-extraction/entity-extraction.ts +94 -0
  140. package/src/entity-extraction/index.ts +5 -0
  141. package/src/entity-extraction/system-prompt.tpl +9 -0
  142. package/src/entity-extraction/types.d.ts +8 -0
  143. package/src/hooks/index.ts +1 -0
  144. package/src/hooks/useQueueModelAdapter.ts +30 -0
  145. package/src/hooks/useTranscriber.ts +3 -2
  146. package/src/model/index.ts +5 -0
  147. package/src/model/model.test.ts +123 -0
  148. package/src/model/model.ts +229 -0
  149. package/src/testing/test-data.ts +314 -0
  150. package/src/transcriber/transcriber.ts +1 -1
  151. package/src/transcriber/transcription-manager.ts +2 -2
  152. package/src/types/schema.ts +7 -7
  153. package/src/types/types.ts +2 -2
  154. package/src/util/index.ts +0 -1
  155. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
  157. package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
  158. package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
  159. package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
  160. package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
  161. package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
  162. package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
  163. package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
  164. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
  165. package/dist/lib/node/chunk-3KHUSQNW.cjs.map +0 -7
  166. package/dist/lib/node/chunk-CZMLKKWT.cjs.map +0 -7
  167. package/dist/lib/node/chunk-P4VVD6ZO.cjs.map +0 -7
  168. package/dist/lib/node/chunk-UOFHT4GZ.cjs +0 -280
  169. package/dist/lib/node/chunk-UOFHT4GZ.cjs.map +0 -7
  170. package/dist/lib/node/intent-resolver-WMMCOUEH.cjs.map +0 -7
  171. package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs.map +0 -7
  172. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
  174. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
  175. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
  176. package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
  177. package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
  178. package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
  179. package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
  180. package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
  181. package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
  182. package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
  183. package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
  184. package/src/util/random-queue-dxn.ts +0 -9
  185. /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-4D4BHE7G.mjs.map} +0 -0
  186. /package/dist/lib/browser/{chunk-K7FOYAXO.mjs.map → chunk-GI5LZ4SV.mjs.map} +0 -0
  187. /package/dist/lib/{node-esm/react-surface-PCH6NTVB.mjs.map → browser/react-surface-HETJ5XY6.mjs.map} +0 -0
  188. /package/dist/lib/node/{chunk-LP4FG423.cjs.map → chunk-WXG4Z2EZ.cjs.map} +0 -0
  189. /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-3HJ752CP.mjs.map} +0 -0
  190. /package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs.map → chunk-XY22OP6H.mjs.map} +0 -0
  191. /package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +0 -0
@@ -16,84 +16,107 @@ import React, {
16
16
  useState,
17
17
  } from 'react';
18
18
 
19
- import { createStatic } from '@dxos/echo-schema';
19
+ import { Events, IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
20
+ import { withPluginManager } from '@dxos/app-framework/testing';
21
+ import { MemoryQueue } from '@dxos/echo-db';
22
+ import { create } from '@dxos/echo-schema';
20
23
  import { type DXN } from '@dxos/keys';
21
24
  import { log } from '@dxos/log';
22
- import { Config } from '@dxos/react-client';
23
- import { useQueue } from '@dxos/react-client/echo';
24
- import { withClientProvider } from '@dxos/react-client/testing';
25
+ import { ClientPlugin } from '@dxos/plugin-client';
26
+ import { SpacePlugin } from '@dxos/plugin-space';
27
+ import { ThemePlugin } from '@dxos/plugin-theme';
28
+ import { randomQueueDxn, useQueue } from '@dxos/react-client/echo';
25
29
  import { IconButton, Toolbar } from '@dxos/react-ui';
26
30
  import { ScrollContainer } from '@dxos/react-ui-components';
31
+ import { defaultTx } from '@dxos/react-ui-theme';
27
32
  import { withLayout, withTheme } from '@dxos/storybook-utils';
28
33
 
29
- import { Transcript } from './Transcript';
30
- import { useAudioFile, useAudioTrack, useTranscriber } from '../hooks';
34
+ import { renderMarkdown, Transcript } from './Transcript';
35
+ import { TestItem } from './Transcript/testings';
36
+ import { TranscriptionPlugin } from '../TranscriptionPlugin';
37
+ import { useAudioFile, useAudioTrack, useQueueModelAdapter, useTranscriber } from '../hooks';
38
+ import { type BlockModel } from '../model';
31
39
  import { type TranscriberParams } from '../transcriber';
32
40
  import { TranscriptBlock } from '../types';
33
- import { randomQueueDxn } from '../util';
34
41
 
35
42
  const TranscriptionStory: FC<{
36
- playing: boolean;
37
- setPlaying: Dispatch<SetStateAction<boolean>>;
38
- blocks?: TranscriptBlock[];
39
- }> = ({ playing, setPlaying, blocks }) => {
43
+ model: BlockModel<TranscriptBlock>;
44
+ running: boolean;
45
+ onRunningChange: Dispatch<SetStateAction<boolean>>;
46
+ }> = ({ model, running, onRunningChange }) => {
40
47
  return (
41
48
  <div className='flex flex-col w-[30rem]'>
42
49
  <Toolbar.Root>
43
50
  <IconButton
44
51
  iconOnly
45
- icon={playing ? 'ph--pause--regular' : 'ph--play--regular'}
46
- label={playing ? 'Pause' : 'Play'}
47
- onClick={() => setPlaying((playing) => !playing)}
52
+ icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
53
+ label={running ? 'Pause' : 'Play'}
54
+ onClick={() => onRunningChange((running) => !running)}
48
55
  />
49
56
  </Toolbar.Root>
50
57
  <ScrollContainer>
51
- <Transcript blocks={blocks} attendableId='story' />
58
+ <Transcript model={model} attendableId='story' />
52
59
  </ScrollContainer>
53
60
  </div>
54
61
  );
55
62
  };
56
63
 
57
64
  const Microphone = () => {
58
- const [playing, setPlaying] = useState(false);
65
+ const [running, setRunning] = useState(false);
59
66
 
60
67
  // Audio.
61
- const track = useAudioTrack(playing);
68
+ const track = useAudioTrack(running);
62
69
 
63
70
  // Queue.
64
71
  const queueDxn = useMemo(() => randomQueueDxn(), []);
65
- const queue = useQueue<TranscriptBlock>(queueDxn, { pollInterval: 500 });
72
+ const queue = useMemo(() => new MemoryQueue<TranscriptBlock>(queueDxn), [queueDxn]);
73
+ const model = useQueueModelAdapter(renderMarkdown, queue);
66
74
 
67
75
  // Transcriber.
68
76
  const handleSegments = useCallback<TranscriberParams['onSegments']>(
69
77
  async (segments) => {
70
- const block = createStatic(TranscriptBlock, { segments });
71
- queue?.append([block]);
78
+ const block = create(TranscriptBlock, { segments });
79
+ void queue.append([block]);
72
80
  },
73
81
  [queue],
74
82
  );
75
- const transcriber = useTranscriber({ audioStreamTrack: track, onSegments: handleSegments });
83
+
84
+ const config = useRef<TranscriberParams['config']>({
85
+ transcribeAfterChunksAmount: 20,
86
+ prefixBufferChunksAmount: 5,
87
+ });
88
+
89
+ const transcriber = useTranscriber({
90
+ audioStreamTrack: track,
91
+ onSegments: handleSegments,
92
+ config: config.current,
93
+ });
94
+ const [isOpen, setIsOpen] = useState(false);
76
95
  useEffect(() => {
77
- void transcriber?.open();
96
+ void transcriber?.open().then(() => setIsOpen(true));
97
+ log.info('transcriber open');
78
98
  return () => {
79
- void transcriber?.close();
99
+ void transcriber?.close().then(() => setIsOpen(false));
100
+ log.info('transcriber close');
80
101
  };
81
102
  }, [transcriber]);
82
103
 
83
104
  // Manage transcription state.
84
105
  useEffect(() => {
85
- if (playing && transcriber?.isOpen) {
86
- void transcriber?.startChunksRecording();
87
- } else if (!playing) {
88
- void transcriber?.stopChunksRecording();
106
+ if (running && transcriber?.isOpen) {
107
+ transcriber?.startChunksRecording();
108
+ log.info('transcriber startChunksRecording');
109
+ } else if (!running) {
110
+ transcriber?.stopChunksRecording();
111
+ log.info('transcriber stopChunksRecording');
89
112
  }
90
- }, [transcriber, playing, transcriber?.isOpen]);
113
+ }, [transcriber, running, isOpen]);
91
114
 
92
- return <TranscriptionStory playing={playing} setPlaying={setPlaying} blocks={queue?.items} />;
115
+ return <TranscriptionStory model={model} running={running} onRunningChange={setRunning} />;
93
116
  };
94
117
 
95
118
  const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; transcriptUrl: string }) => {
96
- const [playing, setPlaying] = useState(false);
119
+ const [running, setRunning] = useState(false);
97
120
 
98
121
  // Audio.
99
122
  const audioElement = useRef<HTMLAudioElement>(null);
@@ -110,18 +133,19 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
110
133
  return;
111
134
  }
112
135
 
113
- if (playing) {
136
+ if (running) {
114
137
  void audio.play();
115
138
  } else {
116
139
  void audio.pause();
117
140
  }
118
- }, [audio, playing]);
141
+ }, [audio, running]);
119
142
 
120
143
  // Transcriber.
121
144
  const queue = useQueue<TranscriptBlock>(queueDxn, { pollInterval: 500 });
145
+ const model = useQueueModelAdapter(renderMarkdown, queue);
122
146
  const handleSegments = useCallback<TranscriberParams['onSegments']>(
123
147
  async (segments) => {
124
- const block = createStatic(TranscriptBlock, { authorName: 'test', authorHue: 'cyan', segments });
148
+ const block = create(TranscriptBlock, { authorName: 'test', authorHue: 'cyan', segments });
125
149
  queue?.append([block]);
126
150
  },
127
151
  [queue],
@@ -133,15 +157,15 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
133
157
  });
134
158
 
135
159
  useEffect(() => {
136
- if (transcriber && playing) {
137
- void transcriber.open();
138
- } else if (!playing) {
160
+ if (transcriber && running) {
161
+ void transcriber.open().then(manageChunkRecording);
162
+ } else if (!running) {
139
163
  transcriber?.stopChunksRecording();
140
164
  void transcriber?.close();
141
165
  }
142
- }, [transcriber, playing]);
166
+ }, [transcriber, running]);
143
167
 
144
- useEffect(() => {
168
+ const manageChunkRecording = () => {
145
169
  if (track?.readyState === 'live' && transcriber?.isOpen) {
146
170
  log.info('starting transcription');
147
171
  transcriber.startChunksRecording();
@@ -149,24 +173,49 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
149
173
  log.info('stopping transcription');
150
174
  transcriber.stopChunksRecording();
151
175
  }
176
+ };
177
+ useEffect(() => {
178
+ manageChunkRecording();
152
179
  }, [transcriber, track?.readyState, transcriber?.isOpen]);
153
180
 
154
- return <TranscriptionStory playing={playing} setPlaying={setPlaying} blocks={queue?.items} />;
181
+ return <TranscriptionStory model={model} running={running} onRunningChange={setRunning} />;
155
182
  };
156
183
 
157
184
  const meta: Meta<typeof AudioFile> = {
158
185
  title: 'plugins/plugin-transcription/transcription',
159
186
  decorators: [
160
- withClientProvider({
161
- config: new Config({
162
- runtime: {
163
- client: { edgeFeatures: { signaling: true } },
164
- services: {
165
- edge: { url: 'https://edge.dxos.workers.dev/' },
166
- iceProviders: [{ urls: 'https://edge.dxos.workers.dev/ice' }],
187
+ withPluginManager({
188
+ plugins: [
189
+ ThemePlugin({ tx: defaultTx }),
190
+ ClientPlugin({
191
+ types: [TestItem],
192
+ onClientInitialized: async (_, client) => {
193
+ await client.halo.createIdentity();
167
194
  },
168
- },
169
- }),
195
+ }),
196
+ SpacePlugin(),
197
+ SettingsPlugin(),
198
+ IntentPlugin(),
199
+ TranscriptionPlugin(),
200
+ ],
201
+ fireEvents: [Events.SetupAppGraph],
202
+ // capabilities: [
203
+ // contributes(
204
+ // Capabilities.ReactSurface,
205
+ // createSurface({
206
+ // id: 'preview-test',
207
+ // role: 'preview',
208
+ // component: ({ data }) => {
209
+ // const schema = getSchema(data);
210
+ // if (!schema) {
211
+ // return null;
212
+ // }
213
+
214
+ // return <Form schema={schema} values={data} />;
215
+ // },
216
+ // }),
217
+ // ),
218
+ // ],
170
219
  }),
171
220
  withTheme,
172
221
  withLayout({
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { AIServiceEdgeClient } from '@dxos/assistant';
8
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
9
+ import { log } from '@dxos/log';
10
+
11
+ import { processTranscriptBlock } from './entity-extraction';
12
+ import * as TestData from '../testing/test-data';
13
+
14
+ const aiService = new AIServiceEdgeClient({
15
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
16
+ });
17
+
18
+ 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,
24
+ aiService,
25
+ context: {
26
+ objects: [...TestData.documents, ...Object.values(TestData.contacts)],
27
+ },
28
+ });
29
+ log.info('output', enhancedBlock);
30
+ }
31
+ });
32
+ });
@@ -0,0 +1,94 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+
7
+ import { createTemplate, Message, structuredOutputParser } from '@dxos/artifact';
8
+ import type { AIServiceClient } from '@dxos/assistant';
9
+ import { MixedStreamParser } from '@dxos/assistant';
10
+ import { raise } from '@dxos/debug';
11
+ import { create } from '@dxos/echo-schema';
12
+ import type { BaseEchoObject } from '@dxos/echo-schema';
13
+
14
+ import SYSTEM_PROMPT from './system-prompt.tpl?raw';
15
+ import type { TranscriptBlock } from '../types';
16
+
17
+ type ProcessTranscriptBlockParams = {
18
+ block: TranscriptBlock;
19
+ aiService: AIServiceClient;
20
+ context: {
21
+ objects?: BaseEchoObject[];
22
+ };
23
+ };
24
+
25
+ type ProcessTranscriptBlockResult = {
26
+ block: TranscriptBlock;
27
+ };
28
+
29
+ /**
30
+ * Process Handlebars template.
31
+ */
32
+ const createSystemPrompt = (): string => {
33
+ const template = createTemplate(SYSTEM_PROMPT);
34
+ return template({});
35
+ };
36
+
37
+ export const processTranscriptBlock = async (
38
+ params: ProcessTranscriptBlockParams,
39
+ ): Promise<ProcessTranscriptBlockResult> => {
40
+ // TODO(dmaretskyi): Move context to a vector search index.
41
+ const systemPrompt = `
42
+ ${createSystemPrompt()}
43
+ Context:
44
+ ${JSON.stringify(params.context.objects)}
45
+ `;
46
+
47
+ const outputParser = structuredOutputParser(
48
+ Schema.Struct({
49
+ segments: Schema.Array(Schema.String).annotations({
50
+ description: 'The enhanced text of the transcript segments, keep the order and structure exactly as is',
51
+ }),
52
+ }),
53
+ );
54
+ const result = outputParser.getResult(
55
+ await new MixedStreamParser().parse(
56
+ await params.aiService.execStream({
57
+ model: '@anthropic/claude-3-5-haiku-20241022',
58
+ systemPrompt,
59
+ history: [
60
+ create(Message, {
61
+ role: 'user',
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: JSON.stringify(params.block),
66
+ },
67
+ ],
68
+ }),
69
+ ],
70
+ tools: [outputParser.tool],
71
+ }),
72
+ ),
73
+ );
74
+
75
+ return {
76
+ block: {
77
+ ...params.block,
78
+ segments: params.block.segments.map((segment, i) => ({
79
+ ...segment,
80
+ text: postprocessText(result?.segments[i] ?? raise(new Error('failed to process email'))),
81
+ })),
82
+ },
83
+ };
84
+ };
85
+
86
+ /**
87
+ * Finds and replaces all inline references with DXNs references.
88
+ */
89
+ // TODO(dmaretskyi): Lookup and verifiy ids from provided context.
90
+ const postprocessText = (text: string) => {
91
+ return text.replace(/\n/g, ' ').replace(/\[([^\]]+)\]\[([A-Z0-9]+)\]/g, (match, name, id) => {
92
+ return `[${name}][dxn:echo:@:${id}]`;
93
+ });
94
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './entity-extraction';
@@ -0,0 +1,9 @@
1
+ You are a helpful assistant that processes transcripts.
2
+ Your goal is to enhance the transcript with entity references.
3
+ Insert inline references to the entities in the transcript.
4
+ Keep the transcript structure and text as is.
5
+ Call the submit_result tool to submit your result.
6
+
7
+ The inline reference syntax is as follows:
8
+ - [<optional name>][<ID>] or just [<ID>]
9
+ - Example: [Earnings Report][01JT0JP9AX0XKGZX4MV4B69VT6]
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ declare module '*.tpl?raw' {
6
+ const content: string;
7
+ export default content;
8
+ }
@@ -4,5 +4,6 @@
4
4
 
5
5
  export * from './useAudioFile';
6
6
  export * from './useAudioTrack';
7
+ export * from './useQueueModelAdapter';
7
8
  export * from './useTranscriber';
8
9
  export * from './useVoiceInput';
@@ -0,0 +1,30 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo, useEffect } from 'react';
6
+
7
+ import { type Queue } from '@dxos/react-client/echo';
8
+
9
+ import { type Block, BlockModel, type BlockRenderer } from '../model';
10
+
11
+ /**
12
+ * Model adapter for a queue.
13
+ */
14
+ export const useQueueModelAdapter = <T extends Block>(
15
+ renderer: BlockRenderer<T>,
16
+ queue: Queue<T> | undefined,
17
+ initialBlocks: T[] = [],
18
+ ): BlockModel<T> => {
19
+ const model = useMemo(() => new BlockModel<T>(renderer, initialBlocks), [queue]);
20
+ useEffect(() => {
21
+ if (!queue?.items.length) {
22
+ return;
23
+ }
24
+
25
+ const block = queue.items[queue.items.length - 1];
26
+ model.appendBlock(block);
27
+ }, [model, queue?.items.length]);
28
+
29
+ return model;
30
+ };
@@ -15,6 +15,7 @@ import { type Transcriber } from '../transcriber';
15
15
  export const useTranscriber = ({
16
16
  audioStreamTrack,
17
17
  onSegments,
18
+ config,
18
19
  }: Partial<TranscriptionCapabilities.GetTranscriberProps>) => {
19
20
  const [getTranscriber] = useCapabilities(TranscriptionCapabilities.Transcriber);
20
21
 
@@ -24,8 +25,8 @@ export const useTranscriber = ({
24
25
  return undefined;
25
26
  }
26
27
 
27
- return getTranscriber({ audioStreamTrack, onSegments });
28
- }, [audioStreamTrack, onSegments, getTranscriber]);
28
+ return getTranscriber({ audioStreamTrack, onSegments, config });
29
+ }, [audioStreamTrack, onSegments, getTranscriber, config]);
29
30
 
30
31
  useEffect(() => {
31
32
  return () => {
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './model';
@@ -0,0 +1,123 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { EditorView } from '@codemirror/view';
6
+ import { describe, test } from 'vitest';
7
+
8
+ import { BlockModel, DocumentAdapter, type BlockRenderer } from './model';
9
+ import { type TranscriptBlock } from '../types';
10
+
11
+ const blockToMarkdown: BlockRenderer<TranscriptBlock> = (block: TranscriptBlock, debug = true): string[] => {
12
+ return [`###### ${block.authorName}`, ...block.segments.map((segment) => segment.text), ''];
13
+ };
14
+
15
+ const createDate = () => new Date().toISOString();
16
+
17
+ describe('BlockModel', () => {
18
+ test('basic', ({ expect }) => {
19
+ const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
20
+ expect(model.blocks.length).to.eq(0);
21
+ expect(model.doc.toString()).to.eq('');
22
+
23
+ // Create block.
24
+ const block = {
25
+ id: '1',
26
+ authorName: 'Alice',
27
+ segments: [{ started: createDate(), text: 'Hello world!' }],
28
+ };
29
+ model.appendBlock(block);
30
+ {
31
+ const text = model.doc.toString();
32
+ expect(text).to.eq('###### Alice\nHello world!\n\n');
33
+ expect(model.blocks.length).to.eq(1);
34
+ }
35
+
36
+ // Update block.
37
+ block.segments.push({ started: new Date().toISOString(), text: 'Hello again!' });
38
+ model.updateBlock(block);
39
+ {
40
+ const text = model.doc.toString();
41
+ expect(text).to.eq('###### Alice\nHello world!\nHello again!\n\n');
42
+ expect(model.blocks.length).to.eq(1);
43
+ }
44
+ });
45
+
46
+ test('sync - append', ({ expect }) => {
47
+ const view = new EditorView({ extensions: [], doc: '' });
48
+ const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
49
+ const adapter = new DocumentAdapter(view);
50
+ expect(adapter.lineCount()).to.eq(0);
51
+ expect(view.state.doc.toString()).to.eq('');
52
+
53
+ // Append block.
54
+ {
55
+ const block = {
56
+ id: '1',
57
+ authorName: 'Alice',
58
+ segments: [{ started: createDate(), text: 'Hello world!' }],
59
+ };
60
+ model.appendBlock(block);
61
+ model.sync(adapter);
62
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
63
+ }
64
+
65
+ // Append block.
66
+ {
67
+ const block = {
68
+ id: '2',
69
+ authorName: 'Bob',
70
+ segments: [{ started: createDate(), text: 'Hello world!' }],
71
+ };
72
+ model.appendBlock(block);
73
+ model.sync(adapter);
74
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n###### Bob\nHello world!\n\n');
75
+ }
76
+ });
77
+
78
+ test('sync - append, update, delete', ({ expect }) => {
79
+ const view = new EditorView({ extensions: [], doc: '' });
80
+ const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
81
+ const adapter = new DocumentAdapter(view);
82
+ expect(adapter.lineCount()).to.eq(0);
83
+
84
+ // Append block.
85
+ {
86
+ const block = { id: '1', authorName: 'Alice', segments: [{ started: createDate(), text: 'Hello world!' }] };
87
+ model.appendBlock(block);
88
+ model.sync(adapter);
89
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
90
+
91
+ // Update block (add segment).
92
+ block.segments.push({ started: createDate(), text: 'Hello again!' });
93
+ model.updateBlock(block);
94
+ model.sync(adapter);
95
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\nHello again!\n\n');
96
+
97
+ // Update block (remove segment).
98
+ block.segments.splice(0, 1);
99
+ model.updateBlock(block);
100
+ model.sync(adapter);
101
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n');
102
+
103
+ expect(model.getBlockAtLine(1)).to.eq(block);
104
+ }
105
+
106
+ // Append block.
107
+ {
108
+ const block = { id: '2', authorName: 'Bob', segments: [{ started: createDate(), text: 'Hello again!' }] };
109
+ model.appendBlock(block);
110
+ model.sync(adapter);
111
+ expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n###### Bob\nHello again!\n\n');
112
+
113
+ expect(model.getBlockAtLine(4)).to.eq(block);
114
+ }
115
+
116
+ // Delete block.
117
+ {
118
+ model.deleteBlock('1');
119
+ model.sync(adapter);
120
+ expect(view.state.doc.toString()).to.eq('###### Bob\nHello again!\n\n');
121
+ }
122
+ });
123
+ });