@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
@@ -2,24 +2,31 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import type { Schema } from 'effect';
6
+
5
7
  import { Capabilities, contributes, createIntent, type PluginsContext } from '@dxos/app-framework';
6
- import { fullyQualifiedId, getSpace, makeRef, type Space } from '@dxos/client/echo';
7
- import { generateName } from '@dxos/display-name';
8
- import { getSchemaTypename, isInstanceOf } from '@dxos/echo-schema';
8
+ import { AIServiceEdgeClient, type AIServiceClient } from '@dxos/assistant';
9
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
10
+ import { Filter, fullyQualifiedId, getSpace, makeRef, type Space } from '@dxos/client/echo';
11
+ import { getSchemaTypename, isInstanceOf, type BaseEchoObject } from '@dxos/echo-schema';
9
12
  import { invariant } from '@dxos/invariant';
13
+ import { DXN } from '@dxos/keys';
14
+ import { log } from '@dxos/log';
10
15
  import { ClientCapabilities } from '@dxos/plugin-client';
11
16
  import { PLANK_COMPANION_TYPE, ATTENDABLE_PATH_SEPARATOR } from '@dxos/plugin-deck/types';
12
17
  import { createExtension, type Node } from '@dxos/plugin-graph';
18
+ import { DocumentType } from '@dxos/plugin-markdown/types';
13
19
  import { MeetingCapabilities, type CallState, type MediaState } from '@dxos/plugin-meeting';
14
- import { MeetingType } from '@dxos/plugin-meeting/types';
20
+ import { MeetingType, type MeetingCallProperties } from '@dxos/plugin-meeting/types';
15
21
  import { type buf } from '@dxos/protocols/buf';
16
22
  import { type TranscriptionPayloadSchema } from '@dxos/protocols/buf/dxos/edge/calls_pb';
17
- import { keyToFallback } from '@dxos/util';
23
+ import { DataType } from '@dxos/schema';
18
24
 
19
25
  import { TranscriptionCapabilities } from './capabilities';
26
+ import { processTranscriptMessage } from '../entity-extraction';
20
27
  import { TRANSCRIPTION_PLUGIN } from '../meta';
21
28
  import { TranscriptionManager } from '../transcriber';
22
- import { TranscriptionAction, TranscriptType } from '../types';
29
+ import { TranscriptionAction, type TranscriptionSettingsProps, TranscriptType } from '../types';
23
30
 
24
31
  // TODO(wittjosiah): Factor out.
25
32
  // TODO(wittjosiah): Can we stop using protobuf for this?
@@ -49,8 +56,7 @@ export default (context: PluginsContext) =>
49
56
  contributes(Capabilities.AppGraphBuilder, [
50
57
  createExtension({
51
58
  id: `${TRANSCRIPTION_PLUGIN}/meeting-transcript`,
52
- filter: (node): node is Node<MeetingType> =>
53
- isInstanceOf(MeetingType, node.data) && node.type !== PLANK_COMPANION_TYPE,
59
+ filter: (node): node is Node<MeetingType> => isInstanceOf(MeetingType, node.data),
54
60
  actions: ({ node }) => {
55
61
  const meeting = node.data;
56
62
  const state = context.requestCapability(TranscriptionCapabilities.MeetingTranscriptionState);
@@ -61,14 +67,13 @@ export default (context: PluginsContext) =>
61
67
  // NOTE: We are not saving the state of the transcription manager here.
62
68
  // We expect the state to be updated through `onCallStateUpdated` once it is propagated through Swarm.
63
69
  // This is done to avoid race conditions and to not handle optimistic updates.
64
-
65
70
  const transcript = await getMeetingTranscript(context, meeting);
66
71
  invariant(transcript, 'Failed to create transcript');
67
72
 
68
- const call = context.requestCapability(MeetingCapabilities.CallManager);
69
- call.setActivity(getSchemaTypename(TranscriptType)!, {
70
- enabled: !state.enabled,
73
+ const callManager = context.requestCapability(MeetingCapabilities.CallManager);
74
+ callManager.setActivity(getSchemaTypename(TranscriptType)!, {
71
75
  queueDxn: transcript.queue.dxn.toString(),
76
+ enabled: !state.enabled,
72
77
  });
73
78
  },
74
79
  properties: {
@@ -86,6 +91,14 @@ export default (context: PluginsContext) =>
86
91
  const meeting = node.data;
87
92
  const client = context.requestCapability(ClientCapabilities.Client);
88
93
  const state = context.requestCapability(TranscriptionCapabilities.MeetingTranscriptionState);
94
+ const settings = context
95
+ .requestCapability(Capabilities.SettingsStore)
96
+ .getStore<TranscriptionSettingsProps>(TRANSCRIPTION_PLUGIN)!.value;
97
+
98
+ // TODO(dmaretskyi): Request via capability.
99
+ const aiClient: AIServiceClient | undefined = new AIServiceEdgeClient({
100
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
101
+ });
89
102
 
90
103
  return [
91
104
  {
@@ -95,29 +108,45 @@ export default (context: PluginsContext) =>
95
108
  properties: {
96
109
  label: ['transcript companion label', { ns: TRANSCRIPTION_PLUGIN }],
97
110
  icon: 'ph--subtitles--regular',
111
+ position: 'hoist',
98
112
  disposition: 'hidden',
99
113
  schema: TranscriptType,
100
114
  getIntent: ({ space }: { space: Space }) =>
101
115
  createIntent(TranscriptionAction.Create, { spaceId: space.id }),
102
- onJoin: () => {
103
- const transcriptionManager = new TranscriptionManager(client.edge);
116
+
117
+ // TODO(burdon): Is this the right place to inisitalize the state? Change to a capability?
118
+ onJoin: async ({ meeting }: { meeting: MeetingType; roomId: string }) => {
104
119
  const identity = client.halo.identity.get();
105
120
  invariant(identity);
106
- transcriptionManager.setName(
107
- identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
108
- );
109
- const fallbackValue = keyToFallback(identity!.identityKey);
110
- const userHue = identity!.profile?.data?.hue || fallbackValue.hue;
111
- transcriptionManager.setHue(userHue);
112
- void transcriptionManager.open();
113
- state.transcriptionManager = transcriptionManager;
121
+ const space = getSpace(meeting);
122
+ invariant(space);
123
+
124
+ let messageEnricher;
125
+ if (aiClient && settings.entityExtraction) {
126
+ messageEnricher = createEntityExtractionEnricher({
127
+ aiClient,
128
+ // TODO(dmaretskyi): Have those be discovered from the schema graph or contributed by capabilities?
129
+ // This forced me to add a dependency on markdown plugin.
130
+ // This will be replaced with a vector search index anyway, so its not a big deal.
131
+ contextTypes: [DocumentType, DataType.Person, DataType.Organization],
132
+ space,
133
+ });
134
+ }
135
+
136
+ // TODO(burdon): The TranscriptionManager singleton is part of the state and should just be updated here.
137
+ state.transcriptionManager = await new TranscriptionManager({
138
+ edgeClient: client.edge,
139
+ messageEnricher,
140
+ })
141
+ .setIdentityDid(identity.did)
142
+ .open();
114
143
  },
115
- onLeave: () => {
116
- void state.transcriptionManager?.close();
144
+ onLeave: async () => {
145
+ await state.transcriptionManager?.close();
117
146
  state.transcriptionManager = undefined;
118
147
  state.enabled = false;
119
148
  },
120
- onCallStateUpdated: (callState: CallState) => {
149
+ onCallStateUpdated: async (callState: CallState) => {
121
150
  const typename = getSchemaTypename(TranscriptType);
122
151
  const transcription = typename ? callState.activities?.[typename] : undefined;
123
152
  if (!transcription?.payload) {
@@ -126,16 +155,57 @@ export default (context: PluginsContext) =>
126
155
 
127
156
  const payload: TranscriptionPayload = transcription.payload;
128
157
  state.enabled = !!payload.enabled;
129
- void state.transcriptionManager?.setEnabled(payload.enabled);
130
- void state.transcriptionManager?.setQueue(payload.queueDxn);
158
+ if (payload.queueDxn) {
159
+ // NOTE: Must set queue before enabling transcription.
160
+ state.transcriptionManager?.setQueue(DXN.parse(payload.queueDxn));
161
+ }
162
+ await state.transcriptionManager?.setEnabled(payload.enabled);
131
163
  },
132
- onMediaStateUpdated: ([mediaState, isSpeaking]: [MediaState, boolean]) => {
164
+ onMediaStateUpdated: async ([mediaState, isSpeaking]: [MediaState, boolean]) => {
133
165
  void state.transcriptionManager?.setAudioTrack(mediaState.audioTrack);
134
166
  void state.transcriptionManager?.setRecording(isSpeaking);
135
167
  },
136
- },
168
+ } satisfies MeetingCallProperties,
137
169
  },
138
170
  ];
139
171
  },
140
172
  }),
141
173
  ]);
174
+
175
+ type EntityExtractionEnricherFactoryOptions = {
176
+ aiClient: AIServiceClient;
177
+ contextTypes: Schema.Schema.AnyNoContext[];
178
+ space: Space;
179
+ };
180
+
181
+ const createEntityExtractionEnricher = ({ aiClient, contextTypes, space }: EntityExtractionEnricherFactoryOptions) => {
182
+ return async (message: DataType.Message) => {
183
+ const { objects } = await space.db.query(Filter.or(...contextTypes.map((s) => Filter.schema(s)))).run();
184
+ log.info('context', { objects });
185
+
186
+ const { message: enhancedMessage, timeElapsed } = await processTranscriptMessage({
187
+ aiService: aiClient,
188
+ message,
189
+ context: { objects: await Promise.all(objects.map(processContextObject)) },
190
+ options: { timeout: ENTITY_EXTRACTOR_TIMEOUT, fallbackToRaw: true },
191
+ });
192
+
193
+ log.info('entity extraction time', { timeElapsed });
194
+ return enhancedMessage;
195
+ };
196
+ };
197
+
198
+ const processContextObject = async (object: BaseEchoObject): Promise<any> => {
199
+ // TODO(dmaretskyi): Documents need special processing is the content is behind a ref.
200
+ // TODO(dmaretskyi): Think about a way to handle this serialization with a decorator.
201
+ // if (isInstanceOf(DocumentType, object)) {
202
+ // return {
203
+ // ...object,
204
+ // content: await object.content.load(),
205
+ // };
206
+ // }
207
+
208
+ return object;
209
+ };
210
+
211
+ const ENTITY_EXTRACTOR_TIMEOUT = 25_000;
@@ -18,8 +18,8 @@ export namespace TranscriptionCapabilities {
18
18
  export const Transcriber = defineCapability<GetTranscriber>(`${TRANSCRIPTION_PLUGIN}/capability/transcriber`);
19
19
 
20
20
  export type MeetingTranscriptionState = Live<{
21
- enabled: boolean;
22
21
  transcriptionManager?: TranscriptionManager;
22
+ enabled: boolean; // TODO(burdon): Move state into TranscriptionManager.
23
23
  }>;
24
24
  export const MeetingTranscriptionState = defineCapability<MeetingTranscriptionState>(
25
25
  `${TRANSCRIPTION_PLUGIN}/capability/meeting-transcription-state`,
@@ -9,5 +9,6 @@ export const IntentResolver = lazy(() => import('./intent-resolver'));
9
9
  export const MeetingTranscriptionState = lazy(() => import('./meeting-transcription-state'));
10
10
  export const ReactSurface = lazy(() => import('./react-surface'));
11
11
  export const Transcriber = lazy(() => import('./transcriber'));
12
+ export const Settings = lazy(() => import('./settings'));
12
13
 
13
14
  export * from './capabilities';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Capabilities, contributes, createResolver, type PluginsContext } from '@dxos/app-framework';
6
6
  import { live, refFromDXN } from '@dxos/live-object';
7
- import { type SpaceId, randomQueueDxn } from '@dxos/react-client/echo';
7
+ import { type SpaceId, createQueueDxn } from '@dxos/react-client/echo';
8
8
 
9
9
  import { TranscriptionAction, TranscriptType } from '../types';
10
10
  import { getTimeStr } from '../util';
@@ -15,7 +15,7 @@ export default (context: PluginsContext) =>
15
15
  intent: TranscriptionAction.Create,
16
16
  resolve: ({ name, spaceId }) => {
17
17
  const transcript = live(TranscriptType, {
18
- queue: refFromDXN(randomQueueDxn(spaceId as SpaceId)),
18
+ queue: refFromDXN(createQueueDxn(spaceId as SpaceId)),
19
19
  name: name ?? `Transcript ${getTimeStr(Date.now())}`,
20
20
  });
21
21
 
@@ -5,15 +5,23 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
8
+ import { SettingsStore } from '@dxos/local-storage';
8
9
 
9
- import { TranscriptionContainer } from '../components';
10
- import { TRANSCRIPTION_PLUGIN } from '../meta';
11
- import { isTranscript, type TranscriptType } from '../types';
10
+ import { TranscriptionContainer, TranscriptionSettings } from '../components';
11
+ import { meta } from '../meta';
12
+ import { isTranscript, type TranscriptionSettingsProps, type TranscriptType } from '../types';
12
13
 
13
14
  export default () =>
14
15
  contributes(Capabilities.ReactSurface, [
15
16
  createSurface({
16
- id: `${TRANSCRIPTION_PLUGIN}/article/transcript`,
17
+ id: `${meta.id}/plugin-settings`,
18
+ role: 'article',
19
+ filter: (data): data is { subject: SettingsStore<TranscriptionSettingsProps> } =>
20
+ data.subject instanceof SettingsStore && data.subject.prefix === meta.id,
21
+ component: ({ data: { subject } }) => <TranscriptionSettings settings={subject.value} />,
22
+ }),
23
+ createSurface({
24
+ id: `${meta.id}/article/transcript`,
17
25
  role: ['article', 'section'],
18
26
  filter: (data): data is { subject: TranscriptType } => isTranscript(data.subject),
19
27
  component: ({ data, role }) => <TranscriptionContainer transcript={data.subject} role={role} />,
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes } from '@dxos/app-framework';
6
+ import { live } from '@dxos/live-object';
7
+
8
+ import { TRANSCRIPTION_PLUGIN } from '../meta';
9
+ import { TranscriptionSettingsSchema } from '../types';
10
+
11
+ export default () => {
12
+ // TODO(wittjosiah): `live` currently doesn't handle schema default values.
13
+ const settings = live(TranscriptionSettingsSchema, { entityExtraction: true });
14
+
15
+ return contributes(Capabilities.Settings, {
16
+ schema: TranscriptionSettingsSchema,
17
+ prefix: TRANSCRIPTION_PLUGIN,
18
+ value: settings,
19
+ });
20
+ };
@@ -5,33 +5,35 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
- import React, { useEffect, useMemo, useState, type FC } from 'react';
8
+ import React, { type FC, useEffect, useMemo, useState } from 'react';
9
9
 
10
10
  import { IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
11
11
  import { withPluginManager } from '@dxos/app-framework/testing';
12
+ import { ObjectId } from '@dxos/echo-schema';
12
13
  import { ClientPlugin } from '@dxos/plugin-client';
13
14
  import { PreviewPlugin } from '@dxos/plugin-preview';
14
15
  import { SpacePlugin } from '@dxos/plugin-space';
15
16
  import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
16
17
  import { ThemePlugin } from '@dxos/plugin-theme';
17
18
  import { faker } from '@dxos/random';
18
- import { useSpace } from '@dxos/react-client/echo';
19
+ import { useMembers, useSpace } from '@dxos/react-client/echo';
19
20
  import { IconButton, Toolbar } from '@dxos/react-ui';
20
21
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
21
22
  import { defaultTx } from '@dxos/react-ui-theme';
23
+ import { DataType } from '@dxos/schema';
24
+ import { Testing } from '@dxos/schema/testing';
22
25
  import { withLayout } from '@dxos/storybook-utils';
23
26
 
24
27
  import { renderMarkdown, Transcript, type TranscriptProps } from './Transcript';
28
+ import { useQueueModelAdapter } from '../../hooks';
29
+ import { SerializationModel } from '../../model';
25
30
  import {
26
- BlockBuilder,
31
+ MessageBuilder,
27
32
  TestItem,
28
33
  useTestTranscriptionQueue,
29
34
  useTestTranscriptionQueueWithEntityExtraction,
30
- } from './testings';
31
- import { useQueueModelAdapter } from '../../hooks';
32
- import { BlockModel } from '../../model';
35
+ } from '../../testing';
33
36
  import translations from '../../translations';
34
- import { type TranscriptBlock } from '../../types';
35
37
 
36
38
  faker.seed(1);
37
39
 
@@ -67,45 +69,49 @@ const TranscriptContainer: FC<
67
69
  );
68
70
  };
69
71
 
70
- type StoryProps = { blocks?: TranscriptBlock[] } & Pick<TranscriptProps, 'ignoreAttention' | 'attendableId'>;
72
+ type StoryProps = { messages?: DataType.Message[] } & Pick<TranscriptProps, 'ignoreAttention' | 'attendableId'>;
71
73
 
72
74
  /**
73
- * Basic story mutates array of blocks.
75
+ * Basic story mutates array of messages.
74
76
  */
75
- const BasicStory = ({ blocks: initialBlocks = [], ...props }: StoryProps) => {
77
+ const BasicStory = ({ messages: initialMessages = [], ...props }: StoryProps) => {
76
78
  const [reset, setReset] = useState({});
77
- const builder = useMemo(() => new BlockBuilder(), []);
78
- const model = useMemo(() => new BlockModel<TranscriptBlock>(renderMarkdown, initialBlocks), [initialBlocks, reset]);
79
+ const builder = useMemo(() => new MessageBuilder(), []);
80
+ const model = useMemo(
81
+ () => new SerializationModel<DataType.Message>(renderMarkdown([]), initialMessages),
82
+ [initialMessages, reset],
83
+ );
79
84
  const [running, setRunning] = useState(true);
80
- const [currentBlock, setCurrentBlock] = useState<TranscriptBlock | null>(null);
85
+ const [currentMessage, setCurrentMessage] = useState<DataType.Message | null>(null);
81
86
  useEffect(() => {
82
87
  if (!running) {
83
88
  return;
84
89
  }
85
90
 
86
- if (!currentBlock) {
87
- const block = builder.createBlock();
88
- model.appendBlock(block);
89
- setCurrentBlock(block);
91
+ if (!currentMessage) {
92
+ void builder.createMessage().then((message) => {
93
+ model.appendChunk(message);
94
+ setCurrentMessage(message);
95
+ });
90
96
  return;
91
97
  }
92
98
 
93
99
  const i = setInterval(() => {
94
- if (currentBlock?.segments.length && currentBlock.segments.length >= 3) {
95
- setCurrentBlock(null);
100
+ if (currentMessage?.blocks.length && currentMessage.blocks.length >= 3) {
101
+ setCurrentMessage(null);
96
102
  clearInterval(i);
97
103
  return;
98
104
  }
99
105
 
100
- currentBlock.segments.push(builder.createSegment());
101
- model.updateBlock(currentBlock);
106
+ currentMessage.blocks.push(builder.createBlock());
107
+ model.updateChunk(currentMessage);
102
108
  }, 3_000);
103
109
 
104
110
  return () => clearInterval(i);
105
- }, [model, currentBlock, running]);
111
+ }, [model, currentMessage, running]);
106
112
 
107
113
  const handleReset = () => {
108
- setCurrentBlock(null);
114
+ setCurrentMessage(null);
109
115
  setRunning(false);
110
116
  setReset({});
111
117
  };
@@ -124,24 +130,54 @@ const BasicStory = ({ blocks: initialBlocks = [], ...props }: StoryProps) => {
124
130
  /**
125
131
  * Queue story mutates queue with model adapter.
126
132
  */
127
- const QueueStory = ({ blocks: initialBlocks = [], ...props }: StoryProps) => {
133
+ const QueueStory = ({
134
+ messages: initialMessages = [],
135
+ queueId,
136
+ onReset,
137
+ ...props
138
+ }: StoryProps & { queueId: ObjectId; onReset: () => void }) => {
128
139
  const [running, setRunning] = useState(true);
129
140
  const space = useSpace();
130
- const queue = useTestTranscriptionQueue(space, running, 2_000);
131
- const model = useQueueModelAdapter(renderMarkdown, queue, initialBlocks);
141
+ const members = useMembers(space?.key).map((member) => member.identity);
142
+ const queue = useTestTranscriptionQueue(space, queueId, running, 2_000);
143
+ const model = useQueueModelAdapter(renderMarkdown(members), queue, initialMessages);
132
144
 
133
- return <TranscriptContainer space={space} model={model} running={running} onRunningChange={setRunning} {...props} />;
145
+ return (
146
+ <TranscriptContainer
147
+ space={space}
148
+ model={model}
149
+ running={running}
150
+ onRunningChange={setRunning}
151
+ onReset={onReset}
152
+ {...props}
153
+ />
154
+ );
134
155
  };
135
156
 
157
+ // TODO(burdon): Reconcile with QueueStory.
136
158
  const EntityExtractionQueueStory = () => {
137
159
  const [running, setRunning] = useState(true);
138
160
  const space = useSpace();
139
- const queue = useTestTranscriptionQueueWithEntityExtraction(space, running, 2_000);
140
- const model = useQueueModelAdapter(renderMarkdown, queue, []);
161
+ const members = useMembers(space?.key).map((member) => member.identity);
162
+ const queue = useTestTranscriptionQueueWithEntityExtraction(space, undefined, running, 2_000);
163
+ const model = useQueueModelAdapter(renderMarkdown(members), queue, []);
141
164
 
142
165
  return <TranscriptContainer space={space} model={model} running={running} onRunningChange={setRunning} />;
143
166
  };
144
167
 
168
+ /**
169
+ * Wrapper remounts on refresh to reload queue.
170
+ */
171
+ const QueueStoryWrapper = () => {
172
+ const [queueId] = useState(ObjectId.random());
173
+ const [key, setKey] = useState(ObjectId.random().toString());
174
+ const handleReset = () => {
175
+ setKey(ObjectId.random().toString());
176
+ };
177
+
178
+ return <QueueStory key={key} queueId={queueId} onReset={handleReset} />;
179
+ };
180
+
145
181
  const meta: Meta<typeof QueueStory> = {
146
182
  title: 'plugins/plugin-transcription/Transcript',
147
183
  decorators: [
@@ -150,7 +186,7 @@ const meta: Meta<typeof QueueStory> = {
150
186
  ThemePlugin({ tx: defaultTx }),
151
187
  StorybookLayoutPlugin(),
152
188
  ClientPlugin({
153
- types: [TestItem],
189
+ types: [TestItem, Testing.DocumentType, DataType.Person, DataType.Organization],
154
190
  onClientInitialized: async (_, client) => {
155
191
  await client.halo.createIdentity();
156
192
  },
@@ -161,7 +197,7 @@ const meta: Meta<typeof QueueStory> = {
161
197
  IntentPlugin(),
162
198
  ],
163
199
  }),
164
- withLayout({ tooltips: true, fullscreen: true }),
200
+ withLayout({ fullscreen: true }),
165
201
  ],
166
202
  parameters: {
167
203
  translations,
@@ -170,14 +206,14 @@ const meta: Meta<typeof QueueStory> = {
170
206
 
171
207
  export default meta;
172
208
 
173
- type Story = StoryObj<StoryProps>;
209
+ type Story = StoryObj<typeof QueueStory>;
174
210
 
175
211
  export const Default: Story = {
176
212
  render: BasicStory,
177
213
  args: {
178
214
  ignoreAttention: true,
179
215
  attendableId: 'story',
180
- blocks: Array.from({ length: 10 }, () => BlockBuilder.singleton.createBlock()),
216
+ messages: await Promise.all(Array.from({ length: 10 }, () => MessageBuilder.singleton.createMessage())),
181
217
  },
182
218
  };
183
219
 
@@ -190,7 +226,7 @@ export const Empty: Story = {
190
226
  };
191
227
 
192
228
  export const WithQueue: Story = {
193
- render: QueueStory,
229
+ render: QueueStoryWrapper,
194
230
  args: {
195
231
  ignoreAttention: true,
196
232
  attendableId: 'story',
@@ -200,7 +236,7 @@ export const WithQueue: Story = {
200
236
  export const WithEntityExtractionQueue: Story = {
201
237
  render: EntityExtractionQueueStory,
202
238
  args: {
203
- // ignoreAttention: true,
204
- // attendableId: 'story',
239
+ ignoreAttention: true,
240
+ attendableId: 'story',
205
241
  },
206
242
  };
@@ -4,10 +4,8 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { DXN } from '@dxos/keys';
8
- import { log } from '@dxos/log';
9
- import { resolveRef, useClient } from '@dxos/react-client';
10
7
  import { type Space } from '@dxos/react-client/echo';
8
+ import { type Identity } from '@dxos/react-client/halo';
11
9
  import { IconButton, type ThemedClassName, useThemeContext } from '@dxos/react-ui';
12
10
  import {
13
11
  createBasicExtensions,
@@ -20,26 +18,42 @@ import {
20
18
  useTextEditor,
21
19
  } from '@dxos/react-ui-editor';
22
20
  import { mx } from '@dxos/react-ui-theme';
21
+ import { type DataType } from '@dxos/schema';
23
22
  import { isNotFalsy } from '@dxos/util';
24
23
 
25
24
  import { transcript } from './transcript-extension';
26
- import { type BlockModel } from '../../model';
27
- import { type TranscriptType, type TranscriptBlock } from '../../types';
25
+ import { type SerializationModel } from '../../model';
26
+ import { type TranscriptType } from '../../types';
28
27
 
29
- export const renderMarkdown = (block: TranscriptBlock, debug = false): string[] => {
30
- // TODO(burdon): Use link/reference markup for users (with popover).
31
- // TODO(burdon): Color and avatar.
32
- return [
33
- `###### ${block.authorName}` + (debug ? ` (${block.id})` : ''),
34
- block.segments.map((segment) => segment.text.trim()).join(' '),
35
- '',
36
- ];
37
- };
28
+ export const renderMarkdown =
29
+ (identities: Identity[]) =>
30
+ (message: DataType.Message, index: number, debug = false): string[] => {
31
+ if (message.sender.role === 'assistant') {
32
+ // Start/stop block.
33
+ return [message.blocks.find((block) => block.type === 'transcription')?.text ?? '', ''];
34
+ }
35
+
36
+ // TODO(burdon): Use link/reference markup for users (with popover).
37
+ // TODO(burdon): Color and avatar.
38
+ const identity = identities.find((identity) => identity.did === message.sender.identityDid);
39
+ const name =
40
+ identity?.profile?.displayName ??
41
+ message.sender.contact?.target?.fullName ??
42
+ message.sender.name ??
43
+ message.sender.email ??
44
+ message.sender.identityDid;
45
+ const blocks = message.blocks.filter((block) => block.type === 'transcription');
46
+ return [
47
+ `###### ${name}` + (debug ? ` [${index}]:${message.id}` : ''),
48
+ blocks.map((block) => block.text.trim()).join(' '),
49
+ '',
50
+ ];
51
+ };
38
52
 
39
53
  export type TranscriptProps = ThemedClassName<{
40
54
  transcript?: TranscriptType;
41
55
  space?: Space;
42
- model: BlockModel<TranscriptBlock>;
56
+ model: SerializationModel<DataType.Message>;
43
57
  // TODO(wittjosiah): Move to container.
44
58
  attendableId?: string;
45
59
  ignoreAttention?: boolean;
@@ -56,7 +70,6 @@ export const Transcript = ({
56
70
  attendableId,
57
71
  ignoreAttention,
58
72
  }: TranscriptProps) => {
59
- const client = useClient();
60
73
  const { themeMode } = useThemeContext();
61
74
  const { parentRef } = useTextEditor(() => {
62
75
  return {
@@ -65,19 +78,7 @@ export const Transcript = ({
65
78
  createMarkdownExtensions({ themeMode }),
66
79
  createThemeExtensions({ themeMode }),
67
80
  decorateMarkdown(),
68
- space &&
69
- preview({
70
- onLookup: async ({ label, ref }) => {
71
- log.info('onLookup', { label, ref });
72
- const dxn = DXN.parse(ref);
73
- if (!dxn) {
74
- return null;
75
- }
76
-
77
- const object = await resolveRef(client, dxn, space);
78
- return { label, object };
79
- },
80
- }),
81
+ space && preview(),
81
82
  transcript({
82
83
  model,
83
84
  started: object?.started ? new Date(object.started) : undefined,
@@ -89,5 +90,11 @@ export const Transcript = ({
89
90
  };
90
91
  }, [space, model]);
91
92
 
92
- return <div ref={parentRef} className={mx('flex grow overflow-hidden', editorWidth, classNames)} />;
93
+ return (
94
+ <div
95
+ ref={parentRef}
96
+ className={mx('flex grow overflow-hidden', editorWidth, classNames)}
97
+ data-popover-collision-boundary={true}
98
+ />
99
+ );
93
100
  };
@@ -3,10 +3,12 @@
3
3
  //
4
4
 
5
5
  import { intervalToDuration } from 'date-fns/intervalToDuration';
6
+ import { Schema } from 'effect';
6
7
  import { yieldOrContinue } from 'main-thread-scheduling';
7
8
  import React, { type FC, useCallback, useEffect, useMemo, useRef, useState, type WheelEvent } from 'react';
8
9
  import { type OnResizeCallback, useResizeDetector } from 'react-resize-detector';
9
10
 
11
+ import { Type } from '@dxos/echo';
10
12
  import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
11
13
  import { useAttention } from '@dxos/react-ui-attention';
12
14
  import {
@@ -20,7 +22,34 @@ import {
20
22
  import { mx } from '@dxos/react-ui-theme';
21
23
 
22
24
  import { TRANSCRIPTION_PLUGIN } from '../../../meta';
23
- import { type TranscriptBlock } from '../../../types';
25
+
26
+ /**
27
+ * Transcription fragment.
28
+ */
29
+ const TranscriptSegment = Schema.Struct({
30
+ // TODO(burdon): TS from service is not Unix TS (x1000).
31
+ started: Schema.String,
32
+ text: Schema.String,
33
+ });
34
+
35
+ type TranscriptSegment = Schema.Schema.Type<typeof TranscriptSegment>;
36
+
37
+ /**
38
+ * Transcription block (from single speaker).
39
+ */
40
+ const TranscriptBlock = Schema.Struct({
41
+ id: Schema.String,
42
+ authorName: Schema.optional(Schema.String), // TODO(burdon): Replace with identityDid.
43
+ authorHue: Schema.optional(Schema.String), // TOOD(burdon): Remove.
44
+ segments: Schema.Array(TranscriptSegment),
45
+ }).pipe(
46
+ Type.def({
47
+ typename: 'dxos.org/type/TranscriptBlock',
48
+ version: '0.1.0',
49
+ }),
50
+ );
51
+
52
+ type TranscriptBlock = Schema.Schema.Type<typeof TranscriptBlock>;
24
53
 
25
54
  // TODO(burdon): Actions (e.g., mark, summarize, translate, label, delete).
26
55