@dxos/plugin-transcription 0.8.2-main.12df754 → 0.8.2-main.36232bc

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 (193) hide show
  1. package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs → TranscriptContainer-2VFHUHLE.mjs} +3 -3
  2. package/dist/lib/browser/app-graph-builder-TDP6PYV6.mjs +350 -0
  3. package/dist/lib/browser/app-graph-builder-TDP6PYV6.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-DR3NBV6Q.mjs → chunk-KSUYWWIU.mjs} +105 -78
  5. package/dist/lib/browser/chunk-KSUYWWIU.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-5BE6RTNC.mjs → chunk-LTIQFX4W.mjs} +1 -1
  7. package/dist/lib/browser/{chunk-5BE6RTNC.mjs.map → chunk-LTIQFX4W.mjs.map} +1 -1
  8. package/dist/lib/browser/{chunk-RHHJZQPM.mjs → chunk-NFVDICSJ.mjs} +133 -116
  9. package/dist/lib/browser/chunk-NFVDICSJ.mjs.map +7 -0
  10. package/dist/lib/browser/chunk-O6RY7FNK.mjs +27 -0
  11. package/dist/lib/browser/chunk-O6RY7FNK.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-5Q3R53U3.mjs → chunk-YRWBY6UK.mjs} +9 -21
  13. package/dist/lib/browser/chunk-YRWBY6UK.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-ZWKYRFE7.mjs} +4 -4
  17. package/dist/lib/browser/{intent-resolver-ZQQEH27F.mjs.map → intent-resolver-ZWKYRFE7.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-F32UNCKZ.mjs} +18 -7
  21. package/dist/lib/browser/react-surface-F32UNCKZ.mjs.map +7 -0
  22. package/dist/lib/browser/settings-6YAHKVKX.mjs +24 -0
  23. package/dist/lib/browser/settings-6YAHKVKX.mjs.map +7 -0
  24. package/dist/lib/browser/{transcriber-SMRLU2KY.mjs → transcriber-MSBS4VBI.mjs} +4 -4
  25. package/dist/lib/browser/types/index.mjs +3 -3
  26. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs → TranscriptContainer-E2D7PKO3.cjs} +8 -8
  27. package/dist/lib/node/{TranscriptContainer-ZWP6UKY6.cjs.map → TranscriptContainer-E2D7PKO3.cjs.map} +2 -2
  28. package/dist/lib/node/app-graph-builder-W4VDMVGV.cjs +351 -0
  29. package/dist/lib/node/app-graph-builder-W4VDMVGV.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-LA73IHCB.cjs → chunk-7XIIUVN3.cjs} +108 -80
  31. package/dist/lib/node/chunk-7XIIUVN3.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-3QEJ5EBO.cjs → chunk-IXU57SCA.cjs} +10 -22
  33. package/dist/lib/node/chunk-IXU57SCA.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-KO6MKCED.cjs → chunk-NBC6V3M2.cjs} +136 -118
  37. package/dist/lib/node/chunk-NBC6V3M2.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-WXG4Z2EZ.cjs → chunk-TJMZ26GS.cjs} +23 -8
  39. package/dist/lib/node/chunk-TJMZ26GS.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-RM4JOCDV.cjs} +8 -8
  43. package/dist/lib/node/{intent-resolver-VY3XWYAA.cjs.map → intent-resolver-RM4JOCDV.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-YQHBGPOC.cjs} +20 -11
  47. package/dist/lib/node/react-surface-YQHBGPOC.cjs.map +7 -0
  48. package/dist/lib/node/settings-ZMB4XRWS.cjs +38 -0
  49. package/dist/lib/node/settings-ZMB4XRWS.cjs.map +7 -0
  50. package/dist/lib/node/{transcriber-E6AAB4SU.cjs → transcriber-55OE4X6I.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-5N3BSK2S.mjs} +3 -3
  54. package/dist/lib/node-esm/app-graph-builder-DDDVKUHS.mjs +351 -0
  55. package/dist/lib/node-esm/app-graph-builder-DDDVKUHS.mjs.map +7 -0
  56. package/dist/lib/node-esm/{chunk-SDMGJZLN.mjs → chunk-4RMJO6QQ.mjs} +133 -116
  57. package/dist/lib/node-esm/chunk-4RMJO6QQ.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-HZBSWUJH.mjs +28 -0
  61. package/dist/lib/node-esm/chunk-HZBSWUJH.mjs.map +7 -0
  62. package/dist/lib/node-esm/{chunk-54HDN33F.mjs → chunk-OS2ITZUL.mjs} +9 -21
  63. package/dist/lib/node-esm/chunk-OS2ITZUL.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-K3R3YK3S.mjs → chunk-UWF6LM27.mjs} +105 -78
  65. package/dist/lib/node-esm/chunk-UWF6LM27.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-HDRFXMKP.mjs} +4 -4
  69. package/dist/lib/node-esm/{intent-resolver-FCTNJPWY.mjs.map → intent-resolver-HDRFXMKP.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-DRDNXTFZ.mjs} +18 -7
  73. package/dist/lib/node-esm/react-surface-DRDNXTFZ.mjs.map +7 -0
  74. package/dist/lib/node-esm/settings-I35TVDUG.mjs +25 -0
  75. package/dist/lib/node-esm/settings-I35TVDUG.mjs.map +7 -0
  76. package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs → transcriber-HH7EMCGH.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/react-surface.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/settings.d.ts +4 -0
  86. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  87. package/dist/types/src/components/Transcript/Transcript.d.ts +6 -4
  88. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  89. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +8 -4
  90. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  91. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
  92. package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
  93. package/dist/types/src/components/Transcript/transcript-extension.d.ts +4 -4
  94. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -1
  95. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  96. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts +6 -0
  97. package/dist/types/src/components/TranscriptionSettings/TranscriptionSettings.d.ts.map +1 -0
  98. package/dist/types/src/components/TranscriptionSettings/index.d.ts +2 -0
  99. package/dist/types/src/components/TranscriptionSettings/index.d.ts.map +1 -0
  100. package/dist/types/src/components/index.d.ts +1 -0
  101. package/dist/types/src/components/index.d.ts.map +1 -1
  102. package/dist/types/src/components/transcription.stories.d.ts +6 -3
  103. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  104. package/dist/types/src/entity-extraction/entity-extraction.d.ts +33 -7
  105. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -1
  106. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +2 -2
  107. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -1
  108. package/dist/types/src/model/model.d.ts +23 -23
  109. package/dist/types/src/model/model.d.ts.map +1 -1
  110. package/dist/types/src/testing/index.d.ts +2 -0
  111. package/dist/types/src/testing/index.d.ts.map +1 -0
  112. package/dist/types/src/testing/testing.d.ts +39 -0
  113. package/dist/types/src/testing/testing.d.ts.map +1 -0
  114. package/dist/types/src/transcriber/transcriber.d.ts +2 -2
  115. package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
  116. package/dist/types/src/transcriber/transcription-manager.d.ts +17 -15
  117. package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
  118. package/dist/types/src/translations.d.ts +3 -0
  119. package/dist/types/src/translations.d.ts.map +1 -1
  120. package/dist/types/src/types/schema.d.ts +0 -21
  121. package/dist/types/src/types/schema.d.ts.map +1 -1
  122. package/dist/types/src/types/types.d.ts +5 -1
  123. package/dist/types/src/types/types.d.ts.map +1 -1
  124. package/package.json +48 -45
  125. package/src/TranscriptionPlugin.tsx +29 -12
  126. package/src/capabilities/app-graph-builder.ts +99 -29
  127. package/src/capabilities/capabilities.ts +1 -1
  128. package/src/capabilities/index.ts +1 -0
  129. package/src/capabilities/intent-resolver.ts +2 -2
  130. package/src/capabilities/react-surface.tsx +12 -4
  131. package/src/capabilities/settings.ts +20 -0
  132. package/src/components/Transcript/Transcript.stories.tsx +71 -35
  133. package/src/components/Transcript/Transcript.tsx +30 -29
  134. package/src/components/Transcript/deprecated/{Transcript.tsx → OldTranscript.tsx} +24 -1
  135. package/src/components/Transcript/transcript-extension.ts +20 -17
  136. package/src/components/TranscriptContainer.tsx +6 -4
  137. package/src/components/TranscriptionSettings/TranscriptionSettings.tsx +26 -0
  138. package/src/components/TranscriptionSettings/index.ts +5 -0
  139. package/src/components/index.ts +1 -0
  140. package/src/components/transcription.stories.tsx +77 -29
  141. package/src/entity-extraction/entity-extraction.test.ts +55 -9
  142. package/src/entity-extraction/entity-extraction.ts +105 -55
  143. package/src/entity-extraction/system-prompt.tpl +38 -9
  144. package/src/hooks/useQueueModelAdapter.ts +44 -11
  145. package/src/model/model.test.ts +85 -43
  146. package/src/model/model.ts +59 -59
  147. package/src/testing/index.ts +5 -0
  148. package/src/testing/testing.ts +208 -0
  149. package/src/transcriber/transcriber.browser.test.ts +3 -3
  150. package/src/transcriber/transcriber.ts +16 -10
  151. package/src/transcriber/transcription-manager.ts +66 -57
  152. package/src/translations.ts +2 -0
  153. package/src/types/schema.ts +1 -24
  154. package/src/types/types.ts +11 -3
  155. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs +0 -168
  156. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +0 -7
  158. package/dist/lib/browser/chunk-DR3NBV6Q.mjs.map +0 -7
  159. package/dist/lib/browser/chunk-GI5LZ4SV.mjs +0 -8
  160. package/dist/lib/browser/chunk-GI5LZ4SV.mjs.map +0 -7
  161. package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +0 -7
  162. package/dist/lib/browser/react-surface-HETJ5XY6.mjs.map +0 -7
  163. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs +0 -177
  164. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +0 -7
  165. package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +0 -7
  166. package/dist/lib/node/chunk-KO6MKCED.cjs.map +0 -7
  167. package/dist/lib/node/chunk-LA73IHCB.cjs.map +0 -7
  168. package/dist/lib/node/chunk-WXG4Z2EZ.cjs.map +0 -7
  169. package/dist/lib/node/react-surface-M2JQIIDY.cjs.map +0 -7
  170. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs +0 -169
  171. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +0 -7
  172. package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-K3R3YK3S.mjs.map +0 -7
  174. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +0 -7
  175. package/dist/lib/node-esm/chunk-XY22OP6H.mjs +0 -10
  176. package/dist/lib/node-esm/chunk-XY22OP6H.mjs.map +0 -7
  177. package/dist/lib/node-esm/react-surface-PXFSIM6V.mjs.map +0 -7
  178. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +0 -13
  179. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +0 -1
  180. package/dist/types/src/components/Transcript/testings.d.ts +0 -51
  181. package/dist/types/src/components/Transcript/testings.d.ts.map +0 -1
  182. package/dist/types/src/testing/test-data.d.ts +0 -73
  183. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  184. package/src/components/Transcript/testings.ts +0 -182
  185. package/src/testing/test-data.ts +0 -314
  186. /package/dist/lib/browser/{TranscriptContainer-4D4BHE7G.mjs.map → TranscriptContainer-2VFHUHLE.mjs.map} +0 -0
  187. /package/dist/lib/browser/{meeting-transcription-state-5D52VTHU.mjs.map → meeting-transcription-state-MNA2XROE.mjs.map} +0 -0
  188. /package/dist/lib/browser/{transcriber-SMRLU2KY.mjs.map → transcriber-MSBS4VBI.mjs.map} +0 -0
  189. /package/dist/lib/node/{meeting-transcription-state-OYWCY5XE.cjs.map → meeting-transcription-state-5XLTMWPA.cjs.map} +0 -0
  190. /package/dist/lib/node/{transcriber-E6AAB4SU.cjs.map → transcriber-55OE4X6I.cjs.map} +0 -0
  191. /package/dist/lib/node-esm/{TranscriptContainer-3HJ752CP.mjs.map → TranscriptContainer-5N3BSK2S.mjs.map} +0 -0
  192. /package/dist/lib/node-esm/{meeting-transcription-state-XMZJ45P4.mjs.map → meeting-transcription-state-3WOZ52BQ.mjs.map} +0 -0
  193. /package/dist/lib/node-esm/{transcriber-REAGRCPY.mjs.map → transcriber-HH7EMCGH.mjs.map} +0 -0
@@ -5,71 +5,97 @@
5
5
  import { EditorView } from '@codemirror/view';
6
6
  import { describe, test } from 'vitest';
7
7
 
8
- import { BlockModel, DocumentAdapter, type BlockRenderer } from './model';
9
- import { type TranscriptBlock } from '../types';
8
+ import { type MessageType } from '@dxos/schema';
10
9
 
11
- const blockToMarkdown: BlockRenderer<TranscriptBlock> = (block: TranscriptBlock, debug = true): string[] => {
12
- return [`###### ${block.authorName}`, ...block.segments.map((segment) => segment.text), ''];
10
+ import { SerializationModel, DocumentAdapter, type ChunkRenderer } from './model';
11
+
12
+ const blockToMarkdown: ChunkRenderer<MessageType> = (message: MessageType, index: number, debug = true): string[] => {
13
+ return [
14
+ `###### ${message.sender.name}`,
15
+ ...message.blocks.filter((block) => block.type === 'transcription').map((block) => block.text),
16
+ '',
17
+ ];
13
18
  };
14
19
 
15
20
  const createDate = () => new Date().toISOString();
16
21
 
17
- describe('BlockModel', () => {
22
+ describe('SerializationModel', () => {
18
23
  test('basic', ({ expect }) => {
19
- const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
20
- expect(model.blocks.length).to.eq(0);
24
+ const model = new SerializationModel<MessageType>(blockToMarkdown);
25
+ expect(model.chunks.length).to.eq(0);
21
26
  expect(model.doc.toString()).to.eq('');
22
27
 
23
- // Create block.
24
- const block = {
28
+ // Create message.
29
+ const message: MessageType = {
25
30
  id: '1',
26
- authorName: 'Alice',
27
- segments: [{ started: createDate(), text: 'Hello world!' }],
31
+ created: createDate(),
32
+ sender: { name: 'Alice' },
33
+ blocks: [
34
+ {
35
+ type: 'transcription',
36
+ started: createDate(),
37
+ text: 'Hello world!',
38
+ },
39
+ ],
28
40
  };
29
- model.appendBlock(block);
41
+ model.appendChunk(message);
30
42
  {
31
43
  const text = model.doc.toString();
32
44
  expect(text).to.eq('###### Alice\nHello world!\n\n');
33
- expect(model.blocks.length).to.eq(1);
45
+ expect(model.chunks.length).to.eq(1);
34
46
  }
35
47
 
36
- // Update block.
37
- block.segments.push({ started: new Date().toISOString(), text: 'Hello again!' });
38
- model.updateBlock(block);
48
+ // Update message.
49
+ message.blocks.push({ type: 'transcription', started: createDate(), text: 'Hello again!' });
50
+ model.updateChunk(message);
39
51
  {
40
52
  const text = model.doc.toString();
41
53
  expect(text).to.eq('###### Alice\nHello world!\nHello again!\n\n');
42
- expect(model.blocks.length).to.eq(1);
54
+ expect(model.chunks.length).to.eq(1);
43
55
  }
44
56
  });
45
57
 
46
58
  test('sync - append', ({ expect }) => {
47
59
  const view = new EditorView({ extensions: [], doc: '' });
48
- const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
60
+ const model = new SerializationModel<MessageType>(blockToMarkdown);
49
61
  const adapter = new DocumentAdapter(view);
50
62
  expect(adapter.lineCount()).to.eq(0);
51
63
  expect(view.state.doc.toString()).to.eq('');
52
64
 
53
- // Append block.
65
+ // Append message.
54
66
  {
55
- const block = {
67
+ const message: MessageType = {
56
68
  id: '1',
57
- authorName: 'Alice',
58
- segments: [{ started: createDate(), text: 'Hello world!' }],
69
+ created: createDate(),
70
+ sender: { name: 'Alice' },
71
+ blocks: [
72
+ {
73
+ type: 'transcription',
74
+ started: createDate(),
75
+ text: 'Hello world!',
76
+ },
77
+ ],
59
78
  };
60
- model.appendBlock(block);
79
+ model.appendChunk(message);
61
80
  model.sync(adapter);
62
81
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
63
82
  }
64
83
 
65
- // Append block.
84
+ // Append message.
66
85
  {
67
- const block = {
86
+ const message: MessageType = {
68
87
  id: '2',
69
- authorName: 'Bob',
70
- segments: [{ started: createDate(), text: 'Hello world!' }],
88
+ created: createDate(),
89
+ sender: { name: 'Bob' },
90
+ blocks: [
91
+ {
92
+ type: 'transcription',
93
+ started: createDate(),
94
+ text: 'Hello world!',
95
+ },
96
+ ],
71
97
  };
72
- model.appendBlock(block);
98
+ model.appendChunk(message);
73
99
  model.sync(adapter);
74
100
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n###### Bob\nHello world!\n\n');
75
101
  }
@@ -77,40 +103,56 @@ describe('BlockModel', () => {
77
103
 
78
104
  test('sync - append, update, delete', ({ expect }) => {
79
105
  const view = new EditorView({ extensions: [], doc: '' });
80
- const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
106
+ const model = new SerializationModel<MessageType>(blockToMarkdown);
81
107
  const adapter = new DocumentAdapter(view);
82
108
  expect(adapter.lineCount()).to.eq(0);
83
109
 
84
- // Append block.
110
+ // Append message.
85
111
  {
86
- const block = { id: '1', authorName: 'Alice', segments: [{ started: createDate(), text: 'Hello world!' }] };
87
- model.appendBlock(block);
112
+ const message: MessageType = {
113
+ id: '1',
114
+ created: createDate(),
115
+ sender: { name: 'Alice' },
116
+ blocks: [
117
+ {
118
+ type: 'transcription',
119
+ started: createDate(),
120
+ text: 'Hello world!',
121
+ },
122
+ ],
123
+ };
124
+ model.appendChunk(message);
88
125
  model.sync(adapter);
89
126
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
90
127
 
91
- // Update block (add segment).
92
- block.segments.push({ started: createDate(), text: 'Hello again!' });
93
- model.updateBlock(block);
128
+ // Update message (add block).
129
+ message.blocks.push({ type: 'transcription', started: createDate(), text: 'Hello again!' });
130
+ model.updateChunk(message);
94
131
  model.sync(adapter);
95
132
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\nHello again!\n\n');
96
133
 
97
- // Update block (remove segment).
98
- block.segments.splice(0, 1);
99
- model.updateBlock(block);
134
+ // Update message (remove block).
135
+ message.blocks.splice(0, 1);
136
+ model.updateChunk(message);
100
137
  model.sync(adapter);
101
138
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n');
102
139
 
103
- expect(model.getBlockAtLine(1)).to.eq(block);
140
+ expect(model.getChunkAtLine(1)).to.eq(message);
104
141
  }
105
142
 
106
- // Append block.
143
+ // Append message.
107
144
  {
108
- const block = { id: '2', authorName: 'Bob', segments: [{ started: createDate(), text: 'Hello again!' }] };
109
- model.appendBlock(block);
145
+ const message: MessageType = {
146
+ id: '2',
147
+ created: createDate(),
148
+ sender: { name: 'Bob' },
149
+ blocks: [{ type: 'transcription', started: createDate(), text: 'Hello again!' }],
150
+ };
151
+ model.appendChunk(message);
110
152
  model.sync(adapter);
111
153
  expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n###### Bob\nHello again!\n\n');
112
154
 
113
- expect(model.getBlockAtLine(4)).to.eq(block);
155
+ expect(model.getChunkAtLine(4)).to.eq(message);
114
156
  }
115
157
 
116
158
  // Delete block.
@@ -9,17 +9,17 @@ import { Event } from '@dxos/async';
9
9
  import { invariant } from '@dxos/invariant';
10
10
  import { log } from '@dxos/log';
11
11
 
12
- export type Block = { id: string };
12
+ export type Chunk = { id: string };
13
13
 
14
14
  /**
15
15
  * Covert block to a set of markdown lines.
16
16
  */
17
- export type BlockRenderer<T extends Block> = (block: T, debug?: boolean) => string[];
17
+ export type ChunkRenderer<T extends Chunk> = (chunk: T, index: number, debug?: boolean) => string[];
18
18
 
19
19
  /**
20
20
  * Abstract representation of the document model.
21
21
  */
22
- export interface BlockDocument {
22
+ export interface ChunkDocument {
23
23
  lineCount(): number;
24
24
  replaceLines(from: number, remove: number, insert: string[]): void;
25
25
  }
@@ -27,7 +27,7 @@ export interface BlockDocument {
27
27
  /**
28
28
  * Codemirror document adapter.
29
29
  */
30
- export class DocumentAdapter implements BlockDocument {
30
+ export class DocumentAdapter implements ChunkDocument {
31
31
  constructor(private readonly _view: EditorView) {}
32
32
 
33
33
  /** The document must always have at least one line. */
@@ -54,16 +54,16 @@ export class DocumentAdapter implements BlockDocument {
54
54
  }
55
55
  }
56
56
 
57
- type BlockChange<T extends Block> =
57
+ type ChunkChange<T extends Chunk> =
58
58
  | {
59
59
  type: 'append';
60
60
  index: number;
61
- block: T;
61
+ chunk: T;
62
62
  }
63
63
  | {
64
64
  type: 'update';
65
65
  index: number;
66
- block: T;
66
+ chunk: T;
67
67
  }
68
68
  | {
69
69
  type: 'delete';
@@ -72,98 +72,98 @@ type BlockChange<T extends Block> =
72
72
  };
73
73
 
74
74
  /**
75
- * Each Block can be converted into a set of lines.
76
- * Blocks can be added, updated or deleted from the Queue.
75
+ * Each Chunk can be converted into a set of lines.
76
+ * Chunks can be added, updated or deleted from the Queue.
77
77
  * A Document is an immutable projection that represents a flat list of lines corresponding to the Queue.
78
78
  *
79
79
  * Ideally we would implement a custom virtual Text model for the View, but this currently isn't possible in Codemirror.
80
- * Instead this model tracks changes and syncs them with the BlockDocument.
80
+ * Instead this model tracks changes and syncs them with the ChunkDocument.
81
81
  */
82
- export class BlockModel<T extends Block> {
83
- /** Ordered set of blocks. */
84
- private readonly _blocks: T[] = [];
82
+ export class SerializationModel<T extends Chunk> {
83
+ /** Ordered set of chunks. */
84
+ private readonly _chunks: T[] = [];
85
85
 
86
- /** Map of block IDs to their current line counts. */
87
- private readonly _blockLineCounts = new Map<string, number>();
86
+ /** Map of chunk IDs to their current line counts. */
87
+ private readonly _chunkLineCounts = new Map<string, number>();
88
88
 
89
- /** Map of line numbers to block IDs. */
90
- private readonly _lineToBlock = new Map<number, T>();
89
+ /** Map of line numbers to chunk IDs. */
90
+ private readonly _lineToChunk = new Map<number, T>();
91
91
 
92
- /** Track block changes since last sync. */
93
- private readonly _changes: Array<BlockChange<T>> = [];
92
+ /** Track chunk changes since last sync. */
93
+ private readonly _changes: Array<ChunkChange<T>> = [];
94
94
 
95
95
  /** Emits when the document is updated. */
96
96
  public readonly update = new Event<void>();
97
97
 
98
98
  constructor(
99
- private readonly _renderer: BlockRenderer<T>,
100
- initialBlocks: T[] = [],
99
+ private readonly _renderer: ChunkRenderer<T>,
100
+ initialChunks: T[] = [],
101
101
  ) {
102
- initialBlocks.forEach((block) => {
103
- this.appendBlock(block);
102
+ initialChunks.forEach((chunk) => {
103
+ this.appendChunk(chunk);
104
104
  });
105
105
  }
106
106
 
107
107
  toJSON() {
108
108
  return {
109
- blocks: this._blocks.length,
109
+ chunks: this._chunks.length,
110
110
  changes: this._changes.length,
111
111
  };
112
112
  }
113
113
 
114
114
  get doc() {
115
- return Text.of([...this._blocks.flatMap((block) => this._renderer(block)), '']);
115
+ return Text.of([...this._chunks.flatMap((chunk, index) => this._renderer(chunk, index)), '']);
116
116
  }
117
117
 
118
- get blocks() {
119
- return this._blocks;
118
+ get chunks() {
119
+ return this._chunks;
120
120
  }
121
121
 
122
122
  /**
123
123
  * Get the block ID at the given line number.
124
124
  */
125
- getBlockAtLine(line: number): T | undefined {
126
- return this._lineToBlock.get(line);
125
+ getChunkAtLine(line: number): T | undefined {
126
+ return this._lineToChunk.get(line);
127
127
  }
128
128
 
129
129
  reset(): this {
130
- this._blocks.length = 0;
131
- this._blockLineCounts.clear();
132
- this._lineToBlock.clear();
130
+ this._chunks.length = 0;
131
+ this._chunkLineCounts.clear();
132
+ this._lineToChunk.clear();
133
133
  this.update.emit();
134
134
  return this;
135
135
  }
136
136
 
137
- appendBlock(block: T): this {
138
- const index = this._blocks.length;
139
- this._blocks.push(block);
140
- this._changes.push({ type: 'append', index, block });
137
+ appendChunk(chunk: T): this {
138
+ const index = this._chunks.length;
139
+ this._chunks.push(chunk);
140
+ this._changes.push({ type: 'append', index, chunk });
141
141
  this.update.emit();
142
142
  return this;
143
143
  }
144
144
 
145
- updateBlock(block: T): this {
146
- const index = this._blocks.findIndex((b) => b.id === block.id);
145
+ updateChunk(chunk: T): this {
146
+ const index = this._chunks.findIndex((c) => c.id === chunk.id);
147
147
  invariant(index !== -1);
148
- this._blocks[index] = block;
149
- this._changes.push({ type: 'update', index, block });
148
+ this._chunks[index] = chunk;
149
+ this._changes.push({ type: 'update', index, chunk });
150
150
  this.update.emit();
151
151
  return this;
152
152
  }
153
153
 
154
154
  deleteBlock(id: string): this {
155
- const index = this._blocks.findIndex((b) => b.id === id);
155
+ const index = this._chunks.findIndex((c) => c.id === id);
156
156
  invariant(index !== -1);
157
- this._blocks.splice(index, 1);
157
+ this._chunks.splice(index, 1);
158
158
  this._changes.push({ type: 'delete', index, id });
159
159
  this.update.emit();
160
160
  return this;
161
161
  }
162
162
 
163
163
  /**
164
- * Syncs the tracked changes with the BlockDocument.
164
+ * Syncs the tracked changes with the ChunkDocument.
165
165
  */
166
- sync(document: BlockDocument): this {
166
+ sync(document: ChunkDocument): this {
167
167
  log('sync', { changes: this._changes });
168
168
 
169
169
  // Process each change in order.
@@ -171,7 +171,7 @@ export class BlockModel<T extends Block> {
171
171
  switch (change.type) {
172
172
  case 'append': {
173
173
  // For appends, convert block to lines and add at the end.
174
- const lines = this._renderer(change.block);
174
+ const lines = this._renderer(change.chunk, change.index);
175
175
  const lastLine = document.lineCount();
176
176
  document.replaceLines(lastLine + 1, 0, lines);
177
177
  break;
@@ -181,10 +181,10 @@ export class BlockModel<T extends Block> {
181
181
  // For updates, find the block's line range and replace with new lines.
182
182
  let lineStart = 1;
183
183
  for (let i = 0; i < change.index; i++) {
184
- lineStart += this._blockLineCounts.get(this._blocks[i].id)!;
184
+ lineStart += this._chunkLineCounts.get(this._chunks[i].id)!;
185
185
  }
186
- const previousLineCount = this._blockLineCounts.get(change.block.id)!;
187
- const newLines = this._renderer(change.block);
186
+ const previousLineCount = this._chunkLineCounts.get(change.chunk.id)!;
187
+ const newLines = this._renderer(change.chunk, change.index);
188
188
  document.replaceLines(lineStart, previousLineCount, newLines);
189
189
  break;
190
190
  }
@@ -193,9 +193,9 @@ export class BlockModel<T extends Block> {
193
193
  // For deletes, find the block's line range and remove those lines.
194
194
  let lineStart = 1;
195
195
  for (let i = 0; i < change.index; i++) {
196
- lineStart += this._blockLineCounts.get(this._blocks[i].id)!;
196
+ lineStart += this._chunkLineCounts.get(this._chunks[i].id)!;
197
197
  }
198
- const previousLineCount = this._blockLineCounts.get(change.id)!;
198
+ const previousLineCount = this._chunkLineCounts.get(change.id)!;
199
199
  document.replaceLines(lineStart, previousLineCount, []);
200
200
  break;
201
201
  }
@@ -203,22 +203,22 @@ export class BlockModel<T extends Block> {
203
203
  }
204
204
 
205
205
  // Clear the line-to-block mapping.
206
- this._lineToBlock.clear();
206
+ this._lineToChunk.clear();
207
207
 
208
208
  // Update all block line counts and line-to-block mapping after processing changes.
209
209
  let currentLine = 1;
210
- for (const block of this._blocks) {
211
- const lineCount = this._renderer(block).length;
212
- this._blockLineCounts.set(block.id, lineCount);
213
- this._lineToBlock.set(currentLine, block);
210
+ for (const [index, chunk] of this._chunks.entries()) {
211
+ const lineCount = this._renderer(chunk, index).length;
212
+ this._chunkLineCounts.set(chunk.id, lineCount);
213
+ this._lineToChunk.set(currentLine, chunk);
214
214
  currentLine += lineCount;
215
215
  }
216
216
 
217
217
  // Clean up deleted blocks.
218
- const blockIds = new Set(this._blocks.map((block) => block.id));
219
- for (const id of this._blockLineCounts.keys()) {
220
- if (!blockIds.has(id)) {
221
- this._blockLineCounts.delete(id);
218
+ const chunkIds = new Set(this._chunks.map((chunk) => chunk.id));
219
+ for (const id of this._chunkLineCounts.keys()) {
220
+ if (!chunkIds.has(id)) {
221
+ this._chunkLineCounts.delete(id);
222
222
  }
223
223
  }
224
224
 
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './testing';
@@ -0,0 +1,208 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useMemo, useState } from 'react';
6
+
7
+ import { AIServiceEdgeClient } from '@dxos/assistant';
8
+ import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
9
+ import { scheduleTaskInterval } from '@dxos/async';
10
+ import { createQueueDxn, Filter, type Queue } from '@dxos/client/echo';
11
+ import { Context } from '@dxos/context';
12
+ import { AST, create, EchoObject, ObjectId, S } from '@dxos/echo-schema';
13
+ import { IdentityDid } from '@dxos/keys';
14
+ import { log } from '@dxos/log';
15
+ import { faker } from '@dxos/random';
16
+ import { live, makeRef, useQueue, type Space } from '@dxos/react-client/echo';
17
+ import { Contact, MessageType, Organization, type TranscriptionContentBlock } from '@dxos/schema';
18
+ import { Testing, seedTestData } from '@dxos/schema/testing';
19
+
20
+ import { processTranscriptMessage } from '../entity-extraction';
21
+
22
+ // TODO(burdon): Reconcile with plugin-markdown. Move to @dxos/schema/testing.
23
+
24
+ export const TestItem = S.Struct({
25
+ title: S.String.annotations({
26
+ [AST.TitleAnnotationId]: 'Title',
27
+ [AST.DescriptionAnnotationId]: 'Product title',
28
+ }),
29
+ description: S.String.annotations({
30
+ [AST.TitleAnnotationId]: 'Description',
31
+ [AST.DescriptionAnnotationId]: 'Product description',
32
+ }),
33
+ }).pipe(EchoObject({ typename: 'dxos.org/type/Test', version: '0.1.0' }));
34
+
35
+ // TODO(wittjosiah): Make builder generic and reuse for all message types.
36
+ abstract class AbstractMessageBuilder {
37
+ abstract createMessage(numSegments?: number): Promise<MessageType>;
38
+ }
39
+
40
+ /**
41
+ * Generator of transcript messages.
42
+ */
43
+ export class MessageBuilder extends AbstractMessageBuilder {
44
+ static readonly singleton = new MessageBuilder();
45
+
46
+ users = Array.from({ length: 5 }, () => ({
47
+ identityDid: IdentityDid.random().toString(),
48
+ name: faker.person.fullName(),
49
+ email: faker.internet.email(),
50
+ }));
51
+
52
+ start = new Date(Date.now() - 24 * 60 * 60 * 10_000);
53
+
54
+ constructor(private readonly _space?: Space) {
55
+ super();
56
+ }
57
+
58
+ override async createMessage(numSegments = 1): Promise<MessageType> {
59
+ return {
60
+ id: ObjectId.random().toString(),
61
+ created: this.next().toISOString(),
62
+ sender: faker.helpers.arrayElement(this.users),
63
+ blocks: Array.from({ length: numSegments }).map(() => this.createBlock()),
64
+ };
65
+ }
66
+
67
+ createBlock(): TranscriptionContentBlock {
68
+ let text = faker.lorem.paragraph();
69
+ if (this._space) {
70
+ const label = faker.commerce.productName();
71
+ const obj = this._space.db.add(live(TestItem, { title: label, description: faker.lorem.paragraph() }));
72
+ const dxn = makeRef(obj).dxn.toString();
73
+ const words = text.split(' ');
74
+ words.splice(Math.floor(Math.random() * words.length), 0, `[${label}][${dxn}]`);
75
+ text = words.join(' ');
76
+ }
77
+
78
+ return {
79
+ type: 'transcription',
80
+ started: this.next().toISOString(),
81
+ text,
82
+ };
83
+ }
84
+
85
+ next() {
86
+ this.start = new Date(this.start.getTime() + Math.random() * 10_000);
87
+ return this.start;
88
+ }
89
+ }
90
+
91
+ // TODO(burdon): Reconcile with BlockBuilder.
92
+ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
93
+ aiService = new AIServiceEdgeClient({
94
+ endpoint: AI_SERVICE_ENDPOINT.REMOTE,
95
+ });
96
+
97
+ space: Space | undefined;
98
+ currentMessage: number = 0;
99
+ transcriptMessages: MessageType[] = [];
100
+
101
+ async connect(space: Space) {
102
+ this.space = space;
103
+ const { transcriptMessages } = await seedTestData(space);
104
+ this.transcriptMessages = transcriptMessages;
105
+ }
106
+
107
+ override async createMessage(): Promise<MessageType> {
108
+ if (!this.space) {
109
+ throw new Error('Space not connected');
110
+ }
111
+
112
+ const { objects } = await this.space.db
113
+ .query(Filter.or(Filter.schema(Contact), Filter.schema(Organization), Filter.schema(Testing.DocumentType)))
114
+ .run();
115
+
116
+ log.info('context', { objects });
117
+ const message = this.transcriptMessages[this.currentMessage];
118
+ this.currentMessage++;
119
+ this.currentMessage = this.currentMessage % this.transcriptMessages.length;
120
+
121
+ const { message: enhancedMessage } = await processTranscriptMessage({
122
+ message,
123
+ aiService: this.aiService,
124
+ context: {
125
+ objects,
126
+ },
127
+ });
128
+
129
+ return enhancedMessage;
130
+ }
131
+ }
132
+
133
+ type UseTestTranscriptionQueue = (
134
+ space: Space | undefined,
135
+ queueId?: ObjectId,
136
+ running?: boolean,
137
+ interval?: number,
138
+ ) => Queue<MessageType> | undefined;
139
+
140
+ /**
141
+ * Test transcriptionqueue.
142
+ */
143
+ export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
144
+ space: Space | undefined,
145
+ queueId?: ObjectId,
146
+ running = true,
147
+ interval = 1_000,
148
+ ) => {
149
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
150
+ const queue = useQueue<MessageType>(queueDxn);
151
+ const builder = useMemo(() => new MessageBuilder(space), [space]);
152
+
153
+ useEffect(() => {
154
+ if (!queue || !running) {
155
+ return;
156
+ }
157
+
158
+ const i = setInterval(() => {
159
+ void builder.createMessage(Math.ceil(Math.random() * 3)).then((message) => {
160
+ queue.append([create(MessageType, message)]);
161
+ });
162
+ }, interval);
163
+ return () => clearInterval(i);
164
+ }, [queue, running, interval]);
165
+
166
+ return queue;
167
+ };
168
+
169
+ /**
170
+ * Test transcription queue.
171
+ */
172
+ // TODO(burdon): Reconcile with useTestTranscriptionQueue.
173
+ export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscriptionQueue = (
174
+ space: Space | undefined,
175
+ queueId?: ObjectId,
176
+ running = true,
177
+ interval = 1_000,
178
+ ) => {
179
+ const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
180
+ const queue = useQueue<MessageType>(queueDxn);
181
+ const [builder] = useState(() => new EntityExtractionMessageBuilder());
182
+
183
+ useEffect(() => {
184
+ if (!queue || !running) {
185
+ return;
186
+ }
187
+
188
+ if (space) {
189
+ void builder.connect(space);
190
+ }
191
+
192
+ const ctx = new Context();
193
+ scheduleTaskInterval(
194
+ ctx,
195
+ async () => {
196
+ const message = await builder.createMessage();
197
+ queue.append([create(MessageType, message)]);
198
+ },
199
+ interval,
200
+ );
201
+
202
+ return () => {
203
+ void ctx.dispose();
204
+ };
205
+ }, [space, queue, running, interval]);
206
+
207
+ return queue;
208
+ };