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

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 (208) hide show
  1. package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs → TranscriptContainer-CXA4ZBHW.mjs} +3 -3
  2. package/dist/lib/browser/app-graph-builder-REQZ5SC5.mjs +351 -0
  3. package/dist/lib/browser/app-graph-builder-REQZ5SC5.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-DR3NBV6Q.mjs → chunk-3TKXI6TQ.mjs} +105 -78
  5. package/dist/lib/browser/chunk-3TKXI6TQ.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-JHWEZRWC.mjs +63 -0
  7. package/dist/lib/browser/chunk-JHWEZRWC.mjs.map +7 -0
  8. package/dist/lib/browser/{chunk-RHHJZQPM.mjs → chunk-K2LS2QOM.mjs} +135 -117
  9. package/dist/lib/browser/chunk-K2LS2QOM.mjs.map +7 -0
  10. package/dist/lib/browser/chunk-LCTMCTYY.mjs +27 -0
  11. package/dist/lib/browser/chunk-LCTMCTYY.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-5BE6RTNC.mjs → chunk-LTIQFX4W.mjs} +1 -1
  13. package/dist/lib/browser/{chunk-5BE6RTNC.mjs.map → chunk-LTIQFX4W.mjs.map} +1 -1
  14. package/dist/lib/browser/index.mjs +30 -18
  15. package/dist/lib/browser/index.mjs.map +3 -3
  16. package/dist/lib/browser/{intent-resolver-ZQQEH27F.mjs → intent-resolver-DZ62CJPC.mjs} +4 -4
  17. package/dist/lib/browser/intent-resolver-DZ62CJPC.mjs.map +7 -0
  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-PUXZNAZL.mjs} +18 -7
  21. package/dist/lib/browser/react-surface-PUXZNAZL.mjs.map +7 -0
  22. package/dist/lib/browser/settings-JWDS4J3F.mjs +24 -0
  23. package/dist/lib/browser/settings-JWDS4J3F.mjs.map +7 -0
  24. package/dist/lib/browser/{transcriber-SMRLU2KY.mjs → transcriber-YWWIV6KP.mjs} +4 -4
  25. package/dist/lib/browser/types/index.mjs +3 -3
  26. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs → TranscriptContainer-SSJVLRM6.cjs} +8 -8
  27. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs.map → TranscriptContainer-SSJVLRM6.cjs.map} +2 -2
  28. package/dist/lib/node/app-graph-builder-OAPU4GDH.cjs +352 -0
  29. package/dist/lib/node/app-graph-builder-OAPU4GDH.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-3QEJ5EBO.cjs → chunk-EJWB53WK.cjs} +22 -32
  31. package/dist/lib/node/chunk-EJWB53WK.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-KO6MKCED.cjs → chunk-EVE3H46F.cjs} +138 -119
  33. package/dist/lib/node/chunk-EVE3H46F.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-L27AK6FQ.cjs} +23 -8
  37. package/dist/lib/node/chunk-L27AK6FQ.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-LA73IHCB.cjs → chunk-WKZLGVEJ.cjs} +108 -80
  39. package/dist/lib/node/chunk-WKZLGVEJ.cjs.map +7 -0
  40. package/dist/lib/node/index.cjs +47 -35
  41. package/dist/lib/node/index.cjs.map +3 -3
  42. package/dist/lib/node/{intent-resolver-VY3XWYAA.cjs → intent-resolver-TRBKI24D.cjs} +9 -9
  43. package/dist/lib/node/intent-resolver-TRBKI24D.cjs.map +7 -0
  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-SZAMHCX7.cjs} +20 -11
  47. package/dist/lib/node/react-surface-SZAMHCX7.cjs.map +7 -0
  48. package/dist/lib/node/settings-AWWMOSCV.cjs +38 -0
  49. package/dist/lib/node/settings-AWWMOSCV.cjs.map +7 -0
  50. package/dist/lib/node/{transcriber-E6AAB4SU.cjs → transcriber-AFUXFHCR.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-M53LI2TX.mjs} +3 -3
  54. package/dist/lib/node-esm/app-graph-builder-UBP6EOBR.mjs +352 -0
  55. package/dist/lib/node-esm/app-graph-builder-UBP6EOBR.mjs.map +7 -0
  56. package/dist/lib/node-esm/{chunk-SDMGJZLN.mjs → chunk-4KC3E5T4.mjs} +135 -117
  57. package/dist/lib/node-esm/chunk-4KC3E5T4.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-MHQP2PAF.mjs +28 -0
  61. package/dist/lib/node-esm/chunk-MHQP2PAF.mjs.map +7 -0
  62. package/dist/lib/node-esm/{chunk-54HDN33F.mjs → chunk-QYG2FOXA.mjs} +22 -32
  63. package/dist/lib/node-esm/chunk-QYG2FOXA.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-K3R3YK3S.mjs → chunk-V5IWBPYO.mjs} +105 -78
  65. package/dist/lib/node-esm/chunk-V5IWBPYO.mjs.map +7 -0
  66. package/dist/lib/node-esm/index.mjs +30 -18
  67. package/dist/lib/node-esm/index.mjs.map +3 -3
  68. package/dist/lib/node-esm/{intent-resolver-FCTNJPWY.mjs → intent-resolver-HFVAL274.mjs} +4 -4
  69. package/dist/lib/node-esm/intent-resolver-HFVAL274.mjs.map +7 -0
  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-UEJELL7I.mjs} +18 -7
  73. package/dist/lib/node-esm/react-surface-UEJELL7I.mjs.map +7 -0
  74. package/dist/lib/node-esm/settings-QFNFGFJ5.mjs +25 -0
  75. package/dist/lib/node-esm/settings-QFNFGFJ5.mjs.map +7 -0
  76. package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs → transcriber-7RJEFPDI.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 +2 -179
  80. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/capabilities.d.ts +1 -1
  82. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  83. package/dist/types/src/capabilities/index.d.ts +3 -178
  84. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  86. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  88. package/dist/types/src/capabilities/settings.d.ts +4 -0
  89. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  90. package/dist/types/src/components/Transcript/Transcript.d.ts +6 -4
  91. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  92. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +8 -4
  93. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  94. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
  95. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
  96. package/dist/types/src/components/Transcript/transcript-extension.d.ts +4 -4
  97. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -1
  98. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  99. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts +6 -0
  100. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts.map +1 -0
  101. package/dist/types/src/components/TranscriptionSettings/index.d.ts +2 -0
  102. package/dist/types/src/components/TranscriptionSettings/index.d.ts.map +1 -0
  103. package/dist/types/src/components/index.d.ts +1 -0
  104. package/dist/types/src/components/index.d.ts.map +1 -1
  105. package/dist/types/src/components/transcription.stories.d.ts +6 -3
  106. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  107. package/dist/types/src/entity-extraction/entity-extraction.d.ts +33 -7
  108. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -1
  109. package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
  110. package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
  111. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +2 -2
  112. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -1
  113. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  114. package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
  115. package/dist/types/src/model/model.d.ts +23 -23
  116. package/dist/types/src/model/model.d.ts.map +1 -1
  117. package/dist/types/src/testing/index.d.ts +2 -0
  118. package/dist/types/src/testing/index.d.ts.map +1 -0
  119. package/dist/types/src/testing/testing.d.ts +40 -0
  120. package/dist/types/src/testing/testing.d.ts.map +1 -0
  121. package/dist/types/src/transcriber/transcriber.d.ts +2 -2
  122. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  123. package/dist/types/src/transcriber/transcription-manager.d.ts +17 -15
  124. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  125. package/dist/types/src/translations.d.ts +3 -18
  126. package/dist/types/src/translations.d.ts.map +1 -1
  127. package/dist/types/src/types/schema.d.ts +26 -40
  128. package/dist/types/src/types/schema.d.ts.map +1 -1
  129. package/dist/types/src/types/types.d.ts +18 -7
  130. package/dist/types/src/types/types.d.ts.map +1 -1
  131. package/dist/types/src/util/array.d.ts.map +1 -1
  132. package/dist/types/src/util/get-time-str.d.ts.map +1 -1
  133. package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
  134. package/dist/types/tsconfig.tsbuildinfo +1 -1
  135. package/package.json +55 -46
  136. package/src/TranscriptionPlugin.tsx +30 -13
  137. package/src/capabilities/app-graph-builder.ts +190 -98
  138. package/src/capabilities/capabilities.ts +1 -1
  139. package/src/capabilities/index.ts +1 -0
  140. package/src/capabilities/intent-resolver.ts +5 -4
  141. package/src/capabilities/react-surface.tsx +12 -4
  142. package/src/capabilities/settings.ts +20 -0
  143. package/src/components/Transcript/Transcript.stories.tsx +72 -36
  144. package/src/components/Transcript/Transcript.tsx +37 -30
  145. package/src/components/Transcript/deprecated/{Transcript.tsx → OldTranscript.tsx} +30 -1
  146. package/src/components/Transcript/transcript-extension.ts +20 -17
  147. package/src/components/TranscriptContainer.tsx +6 -4
  148. package/src/components/TranscriptionSettings/TranscriptionSettings.tsx +26 -0
  149. package/src/components/TranscriptionSettings/index.ts +5 -0
  150. package/src/components/index.ts +1 -0
  151. package/src/components/transcription.stories.tsx +85 -35
  152. package/src/entity-extraction/entity-extraction.test.ts +75 -10
  153. package/src/entity-extraction/entity-extraction.ts +110 -55
  154. package/src/entity-extraction/system-prompt.tpl +38 -9
  155. package/src/hooks/useQueueModelAdapter.ts +44 -11
  156. package/src/model/model.test.ts +90 -44
  157. package/src/model/model.ts +59 -59
  158. package/src/testing/index.ts +5 -0
  159. package/src/testing/testing.ts +216 -0
  160. package/src/transcriber/transcriber.browser.test.ts +3 -3
  161. package/src/transcriber/transcriber.ts +19 -10
  162. package/src/transcriber/transcription-manager.ts +66 -57
  163. package/src/translations.ts +2 -0
  164. package/src/types/schema.ts +10 -31
  165. package/src/types/types.ts +18 -8
  166. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs +0 -168
  167. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +0 -7
  168. package/dist/lib/browser/chunk-5Q3R53U3.mjs +0 -73
  169. package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +0 -7
  170. package/dist/lib/browser/chunk-DR3NBV6Q.mjs.map +0 -7
  171. package/dist/lib/browser/chunk-GI5LZ4SV.mjs +0 -8
  172. package/dist/lib/browser/chunk-GI5LZ4SV.mjs.map +0 -7
  173. package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +0 -7
  174. package/dist/lib/browser/intent-resolver-ZQQEH27F.mjs.map +0 -7
  175. package/dist/lib/browser/react-surface-HETJ5XY6.mjs.map +0 -7
  176. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs +0 -177
  177. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +0 -7
  178. package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +0 -7
  179. package/dist/lib/node/chunk-KO6MKCED.cjs.map +0 -7
  180. package/dist/lib/node/chunk-LA73IHCB.cjs.map +0 -7
  181. package/dist/lib/node/chunk-WXG4Z2EZ.cjs.map +0 -7
  182. package/dist/lib/node/intent-resolver-VY3XWYAA.cjs.map +0 -7
  183. package/dist/lib/node/react-surface-M2JQIIDY.cjs.map +0 -7
  184. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs +0 -169
  185. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +0 -7
  186. package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +0 -7
  187. package/dist/lib/node-esm/chunk-K3R3YK3S.mjs.map +0 -7
  188. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +0 -7
  189. package/dist/lib/node-esm/chunk-XY22OP6H.mjs +0 -10
  190. package/dist/lib/node-esm/chunk-XY22OP6H.mjs.map +0 -7
  191. package/dist/lib/node-esm/intent-resolver-FCTNJPWY.mjs.map +0 -7
  192. package/dist/lib/node-esm/react-surface-PXFSIM6V.mjs.map +0 -7
  193. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +0 -13
  194. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +0 -1
  195. package/dist/types/src/components/Transcript/testings.d.ts +0 -51
  196. package/dist/types/src/components/Transcript/testings.d.ts.map +0 -1
  197. package/dist/types/src/testing/test-data.d.ts +0 -73
  198. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  199. package/src/components/Transcript/testings.ts +0 -182
  200. package/src/testing/test-data.ts +0 -314
  201. /package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs.map → TranscriptContainer-CXA4ZBHW.mjs.map} +0 -0
  202. /package/dist/lib/browser/{meeting-transcription-state-5D52VTHU.mjs.map → meeting-transcription-state-MNA2XROE.mjs.map} +0 -0
  203. /package/dist/lib/browser/{transcriber-SMRLU2KY.mjs.map → transcriber-YWWIV6KP.mjs.map} +0 -0
  204. /package/dist/lib/node/{meeting-transcription-state-OYWCY5XE.cjs.map → meeting-transcription-state-5XLTMWPA.cjs.map} +0 -0
  205. /package/dist/lib/node/{transcriber-E6AAB4SU.cjs.map → transcriber-AFUXFHCR.cjs.map} +0 -0
  206. /package/dist/lib/node-esm/{TranscriptContainer-3HJ752CP.mjs.map → TranscriptContainer-M53LI2TX.mjs.map} +0 -0
  207. /package/dist/lib/node-esm/{meeting-transcription-state-XMZJ45P4.mjs.map → meeting-transcription-state-3WOZ52BQ.mjs.map} +0 -0
  208. /package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs.map → transcriber-7RJEFPDI.mjs.map} +0 -0
@@ -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';
22
- import { create } from '@dxos/echo-schema';
21
+ import { AIServiceEdgeClient } from '@dxos/assistant';
22
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
23
+ import { Filter, MemoryQueue } from '@dxos/echo-db';
24
+ import { create, createQueueDxn } 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 { 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.type(DataType.Person),
103
+ Filter.type(DataType.Organization),
104
+ Filter.type(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
+ });
@@ -7,23 +7,39 @@ import { Schema } from 'effect';
7
7
  import { createTemplate, Message, structuredOutputParser } from '@dxos/artifact';
8
8
  import type { AIServiceClient } from '@dxos/assistant';
9
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';
10
+ import { asyncTimeout } from '@dxos/async';
11
+ import { type BaseEchoObject, create, ObjectId } from '@dxos/echo-schema';
12
+ import { DXN } from '@dxos/keys';
13
+ import { log } from '@dxos/log';
14
+ import { DataType } from '@dxos/schema';
13
15
 
14
16
  import SYSTEM_PROMPT from './system-prompt.tpl?raw';
15
- import type { TranscriptBlock } from '../types';
16
17
 
17
- type ProcessTranscriptBlockParams = {
18
- block: TranscriptBlock;
18
+ type ProcessTranscriptMessageParams = {
19
+ message: DataType.Message;
19
20
  aiService: AIServiceClient;
20
21
  context: {
21
22
  objects?: BaseEchoObject[];
22
23
  };
24
+
25
+ options?: {
26
+ /**
27
+ * Timeout for the entity extraction.
28
+ */
29
+ timeout?: number;
30
+
31
+ /**
32
+ * Fallback to raw text if the entity extraction fails.
33
+ * Otherwise the function will throw an error.
34
+ * @default false
35
+ */
36
+ fallbackToRaw?: boolean;
37
+ };
23
38
  };
24
39
 
25
- type ProcessTranscriptBlockResult = {
26
- block: TranscriptBlock;
40
+ type ProcessTranscriptMessageResult = {
41
+ message: DataType.Message;
42
+ timeElapsed: number;
27
43
  };
28
44
 
29
45
  /**
@@ -34,61 +50,100 @@ const createSystemPrompt = (): string => {
34
50
  return template({});
35
51
  };
36
52
 
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(
53
+ const ReferencedQuotes = Schema.Struct({
54
+ references: Schema.Array(
48
55
  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
- }),
56
+ quote: Schema.String,
57
+ id: Schema.String,
52
58
  }),
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
- },
59
+ ).annotations({
60
+ description:
61
+ 'The references to the context objects that are mentioned in the transcript. quote should match the original transcript text exactly, while id is the id of the context object.',
62
+ }),
63
+ });
64
+ interface ReferencedQuotes extends Schema.Schema.Type<typeof ReferencedQuotes> {}
65
+
66
+ // TODO(dmaretskyi): Move context to a vector search index.
67
+ export const processTranscriptMessage = async (
68
+ params: ProcessTranscriptMessageParams,
69
+ ): Promise<ProcessTranscriptMessageResult> => {
70
+ try {
71
+ const outputParser = structuredOutputParser(ReferencedQuotes);
72
+ const runParser = async (): Promise<ProcessTranscriptMessageResult> => {
73
+ const startTime = performance.now();
74
+ const result = outputParser.getResult(
75
+ await new MixedStreamParser().parse(
76
+ await params.aiService.execStream({
77
+ model: '@anthropic/claude-3-5-haiku-20241022',
78
+ systemPrompt: createSystemPrompt(),
79
+ history: [
80
+ create(Message, {
81
+ role: 'user',
82
+ content: [
83
+ {
84
+ type: 'text',
85
+ text: `<context>${JSON.stringify(params.context.objects)}</context>`,
86
+ },
87
+ {
88
+ type: 'text',
89
+ text: `<transcript>${JSON.stringify(params.message.blocks)}</transcript>`,
90
+ },
91
+ ],
92
+ }),
67
93
  ],
94
+ tools: [outputParser.tool],
68
95
  }),
69
- ],
70
- tools: [outputParser.tool],
71
- }),
72
- ),
73
- );
96
+ ),
97
+ );
98
+ log.info('entity extraction result', { refs: result.references });
74
99
 
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
- };
100
+ return {
101
+ message: create(DataType.Message, {
102
+ ...params.message,
103
+ blocks: params.message.blocks.map((block, i) =>
104
+ block.type !== 'transcription'
105
+ ? block
106
+ : {
107
+ ...block,
108
+ text: postprocessText(block.text, result),
109
+ },
110
+ ),
111
+ }),
112
+ timeElapsed: performance.now() - startTime,
113
+ };
114
+ };
115
+
116
+ if (params.options?.timeout && params.options.timeout > 0) {
117
+ return await asyncTimeout(runParser(), params.options.timeout);
118
+ } else {
119
+ return await runParser();
120
+ }
121
+ } catch (error) {
122
+ if (params.options?.fallbackToRaw) {
123
+ log.warn('failed to process transcript message, falling back to raw text', { error });
124
+ return {
125
+ message: params.message,
126
+ timeElapsed: 0,
127
+ };
128
+ } else {
129
+ throw error;
130
+ }
131
+ }
84
132
  };
85
133
 
86
134
  /**
87
- * Finds and replaces all inline references with DXNs references.
135
+ * Finds and replaces all quotes with DXNs references.
88
136
  */
89
137
  // 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
- });
138
+ export const postprocessText = (text: string, quotes: ReferencedQuotes) => {
139
+ for (const quote of quotes.references) {
140
+ if (!ObjectId.isValid(quote.id)) {
141
+ continue;
142
+ }
143
+
144
+ // Use a case-insensitive regular expression to replace the quote
145
+ const regex = new RegExp(quote.quote.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
146
+ text = text.replace(regex, `[${quote.quote}][${DXN.fromLocalObjectId(quote.id).toString()}]`);
147
+ }
148
+ return text;
94
149
  };
@@ -1,9 +1,38 @@
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]
1
+ You are an AI assistant specialized in processing and enhancing transcripts with contextual information.
2
+ Your task is to analyze a given transcript and extract references to the context objects.
3
+
4
+ The context and transcript are provided in the user message in `context` and `transcript` XML tags.
5
+
6
+ Here are your instructions:
7
+
8
+ 1. Read the transcript and context information provided above.
9
+
10
+ 2. Process each segment of the transcript separately.
11
+
12
+ 3. For each segment:
13
+ a. Analyze the content and identify relevant information from the context.
14
+ b. Extract a number of references to the context objects that are mentioned in the segment.
15
+ c. Output the references in the format `{ "quote": "earnings report", "id": "01JT0JP9AX0XKGZX4MV4B69VT6" }`.
16
+ d. If there are no references, just output an empty array.
17
+
18
+ 4. Maintain the exact order and structure of the original transcript segments.
19
+
20
+ Output Format:
21
+ The final output should be a JSON array of references.
22
+
23
+ Example output structure:
24
+ ```json
25
+ {
26
+ "references": [
27
+ { "quote": "earnings report", "id": "01JT0JP9AX0XKGZX4MV4B69VT6" },
28
+ { "quote": "John", "id": "01JT0VM0VN3ZKN7GDBM8CV9BA7" }
29
+ ]
30
+ }
31
+ ```
32
+
33
+ Remember:
34
+ - Only quote the text that is in the transcript.
35
+ - Analyze each segment carefully before processing it.
36
+ - Only reference objects that appear in the context and exactly by their id, If the object is not in the context, do not insert a reference.
37
+
38
+ Now, process the transcript and context provided below according to these instructions.
@@ -2,29 +2,62 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useMemo, useEffect } from 'react';
5
+ import { useMemo, useEffect, useState } from 'react';
6
6
 
7
7
  import { type Queue } from '@dxos/react-client/echo';
8
8
 
9
- import { type Block, BlockModel, type BlockRenderer } from '../model';
9
+ import { type Chunk, type ChunkRenderer, SerializationModel } from '../model';
10
10
 
11
11
  /**
12
12
  * Model adapter for a queue.
13
13
  */
14
- export const useQueueModelAdapter = <T extends Block>(
15
- renderer: BlockRenderer<T>,
14
+ export const useQueueModelAdapter = <T extends Chunk>(
15
+ renderer: ChunkRenderer<T>,
16
16
  queue: Queue<T> | undefined,
17
- initialBlocks: T[] = [],
18
- ): BlockModel<T> => {
19
- const model = useMemo(() => new BlockModel<T>(renderer, initialBlocks), [queue]);
17
+ initialChunks: T[] = [],
18
+ ): SerializationModel<T> => {
19
+ const model = useMemo(() => new SerializationModel<T>(renderer, initialChunks), [queue]);
20
+ const [loaded, setLoaded] = useState(false);
21
+
22
+ // Set initial blocks.
23
+ useEffect(() => {
24
+ if (!queue) {
25
+ return;
26
+ }
27
+
28
+ const update = () => {
29
+ for (const block of queue?.items ?? []) {
30
+ model.appendChunk(block);
31
+ }
32
+
33
+ setLoaded(true);
34
+ };
35
+
36
+ // TODO(burdon): This is a hack to ensure the queue is loaded.
37
+ let i: NodeJS.Timeout | undefined;
38
+ if (queue.isLoading) {
39
+ i = setInterval(() => {
40
+ if (!queue?.isLoading) {
41
+ clearInterval(i);
42
+ update();
43
+ }
44
+ }, 1_000);
45
+ } else {
46
+ update();
47
+ }
48
+
49
+ return () => clearTimeout(i);
50
+ }, [model, queue]);
51
+
52
+ // TODO(burdon): Can we listen for queue events?
20
53
  useEffect(() => {
21
- if (!queue?.items.length) {
54
+ if (!loaded || !queue || model.chunks.length === queue.items.length) {
22
55
  return;
23
56
  }
24
57
 
25
- const block = queue.items[queue.items.length - 1];
26
- model.appendBlock(block);
27
- }, [model, queue?.items.length]);
58
+ const chunk = queue.items[queue.items.length - 1];
59
+ model.appendChunk(chunk);
60
+ }, [model, loaded, queue?.items.length]);
28
61
 
29
62
  return model;
30
63
  };