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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs → TranscriptContainer-4D4BHE7G.mjs} +3 -2
  2. package/dist/lib/browser/{app-graph-builder-VDQOVX7P.mjs → app-graph-builder-I4SSIQMW.mjs} +18 -41
  3. package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-5BE6RTNC.mjs} +1 -1
  5. package/dist/lib/browser/chunk-5BE6RTNC.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-OGNXNGJB.mjs → chunk-5Q3R53U3.mjs} +8 -8
  7. package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +7 -0
  8. package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-DR3NBV6Q.mjs} +6 -6
  9. package/dist/lib/browser/{chunk-YCZISX63.mjs.map → chunk-DR3NBV6Q.mjs.map} +3 -3
  10. package/dist/lib/browser/{chunk-K7FOYAXO.mjs → chunk-GI5LZ4SV.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-GU2BNYKZ.mjs → chunk-KLD7O5GJ.mjs} +2 -12
  12. package/dist/lib/browser/chunk-KLD7O5GJ.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-RHHJZQPM.mjs +702 -0
  14. package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +22 -197
  16. package/dist/lib/browser/index.mjs.map +4 -4
  17. package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-ZQQEH27F.mjs} +7 -7
  18. package/dist/lib/browser/intent-resolver-ZQQEH27F.mjs.map +7 -0
  19. package/dist/lib/browser/{meeting-transcription-state-DLFHQK72.mjs → meeting-transcription-state-5D52VTHU.mjs} +4 -4
  20. package/dist/lib/browser/meeting-transcription-state-5D52VTHU.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-HETJ5XY6.mjs} +5 -4
  23. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs → transcriber-SMRLU2KY.mjs} +8 -7
  24. package/dist/lib/browser/{transcriber-LGMIWDYI.mjs.map → transcriber-SMRLU2KY.mjs.map} +3 -3
  25. package/dist/lib/browser/types/index.mjs +1 -1
  26. package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-ZWP6UKY6.cjs} +8 -7
  27. package/dist/lib/node/TranscriptContainer-ZWP6UKY6.cjs.map +7 -0
  28. package/dist/lib/node/{app-graph-builder-IFSIVNUR.cjs → app-graph-builder-WWC4WNTG.cjs} +31 -54
  29. package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-P4VVD6ZO.cjs → chunk-3QEJ5EBO.cjs} +10 -10
  31. package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-HYGDHS6X.cjs} +4 -4
  33. package/dist/lib/node/chunk-HYGDHS6X.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-3KHUSQNW.cjs → chunk-J72PUQ3C.cjs} +6 -15
  35. package/dist/lib/node/chunk-J72PUQ3C.cjs.map +7 -0
  36. package/dist/lib/node/chunk-KO6MKCED.cjs +718 -0
  37. package/dist/lib/node/chunk-KO6MKCED.cjs.map +7 -0
  38. package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-LA73IHCB.cjs} +10 -10
  39. package/dist/lib/node/{chunk-FCCJALOT.cjs.map → chunk-LA73IHCB.cjs.map} +3 -3
  40. package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-WXG4Z2EZ.cjs} +5 -5
  41. package/dist/lib/node/index.cjs +49 -233
  42. package/dist/lib/node/index.cjs.map +4 -4
  43. package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-VY3XWYAA.cjs} +11 -10
  44. package/dist/lib/node/intent-resolver-VY3XWYAA.cjs.map +7 -0
  45. package/dist/lib/node/{meeting-transcription-state-URIYILEQ.cjs → meeting-transcription-state-OYWCY5XE.cjs} +7 -7
  46. package/dist/lib/node/meeting-transcription-state-OYWCY5XE.cjs.map +7 -0
  47. package/dist/lib/node/meta.json +1 -1
  48. package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-M2JQIIDY.cjs} +10 -9
  49. package/dist/lib/node/{react-surface-WI2MGWCC.cjs.map → react-surface-M2JQIIDY.cjs.map} +1 -1
  50. package/dist/lib/node/{transcriber-QG5WPKX4.cjs → transcriber-E6AAB4SU.cjs} +14 -13
  51. package/dist/lib/node/{transcriber-QG5WPKX4.cjs.map → transcriber-E6AAB4SU.cjs.map} +3 -3
  52. package/dist/lib/node/types/index.cjs +7 -7
  53. package/dist/lib/node/types/index.cjs.map +1 -1
  54. package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-3HJ752CP.mjs} +3 -2
  55. package/dist/lib/node-esm/{app-graph-builder-UL6XLIZY.mjs → app-graph-builder-PPJQAFSI.mjs} +18 -41
  56. package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-GIO6J6FE.mjs → chunk-54HDN33F.mjs} +8 -8
  58. package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-K3R3YK3S.mjs} +6 -6
  60. package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs.map → chunk-K3R3YK3S.mjs.map} +3 -3
  61. package/dist/lib/node-esm/{chunk-HKATMD34.mjs → chunk-QMHMEKB3.mjs} +2 -12
  62. package/dist/lib/node-esm/chunk-QMHMEKB3.mjs.map +7 -0
  63. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs +703 -0
  64. package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs → chunk-XY22OP6H.mjs} +2 -2
  66. package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-YXZOLOFT.mjs} +1 -1
  67. package/dist/lib/node-esm/chunk-YXZOLOFT.mjs.map +7 -0
  68. package/dist/lib/node-esm/index.mjs +22 -197
  69. package/dist/lib/node-esm/index.mjs.map +4 -4
  70. package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-FCTNJPWY.mjs} +7 -7
  71. package/dist/lib/node-esm/intent-resolver-FCTNJPWY.mjs.map +7 -0
  72. package/dist/lib/node-esm/{meeting-transcription-state-ZYVRAW6Y.mjs → meeting-transcription-state-XMZJ45P4.mjs} +4 -4
  73. package/dist/lib/node-esm/meeting-transcription-state-XMZJ45P4.mjs.map +7 -0
  74. package/dist/lib/node-esm/meta.json +1 -1
  75. package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-PXFSIM6V.mjs} +5 -4
  76. package/dist/lib/{browser/react-surface-FQUHU5G7.mjs.map → node-esm/react-surface-PXFSIM6V.mjs.map} +1 -1
  77. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs → transcriber-REAGRCPY.mjs} +8 -7
  78. package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs.map → transcriber-REAGRCPY.mjs.map} +3 -3
  79. package/dist/lib/node-esm/types/index.mjs +1 -1
  80. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/capabilities.d.ts +4 -6
  82. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  83. package/dist/types/src/capabilities/index.d.ts +1 -4
  84. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/meeting-transcription-state.d.ts +2 -4
  86. package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
  88. package/dist/types/src/components/Transcript/Transcript.d.ts +12 -4
  89. package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
  90. package/dist/types/src/components/Transcript/Transcript.stories.d.ts +15 -4
  91. package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
  92. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +13 -0
  93. package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +1 -0
  94. package/dist/types/src/components/Transcript/testings.d.ts +51 -0
  95. package/dist/types/src/components/Transcript/testings.d.ts.map +1 -0
  96. package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
  97. package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
  98. package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
  99. package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
  100. package/dist/types/src/entity-extraction/entity-extraction.d.ts +16 -0
  101. package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -0
  102. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts +2 -0
  103. package/dist/types/src/entity-extraction/entity-extraction.test.d.ts.map +1 -0
  104. package/dist/types/src/entity-extraction/index.d.ts +2 -0
  105. package/dist/types/src/entity-extraction/index.d.ts.map +1 -0
  106. package/dist/types/src/hooks/index.d.ts +1 -0
  107. package/dist/types/src/hooks/index.d.ts.map +1 -1
  108. package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
  109. package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
  111. package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
  112. package/dist/types/src/model/index.d.ts +2 -0
  113. package/dist/types/src/model/index.d.ts.map +1 -0
  114. package/dist/types/src/model/model.d.ts +68 -0
  115. package/dist/types/src/model/model.d.ts.map +1 -0
  116. package/dist/types/src/model/model.test.d.ts +2 -0
  117. package/dist/types/src/model/model.test.d.ts.map +1 -0
  118. package/dist/types/src/testing/test-data.d.ts +73 -0
  119. package/dist/types/src/testing/test-data.d.ts.map +1 -0
  120. package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
  121. package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
  122. package/dist/types/src/types/schema.d.ts +9 -9
  123. package/dist/types/src/util/index.d.ts +0 -1
  124. package/dist/types/src/util/index.d.ts.map +1 -1
  125. package/package.json +47 -39
  126. package/src/capabilities/app-graph-builder.ts +15 -26
  127. package/src/capabilities/capabilities.ts +3 -2
  128. package/src/capabilities/intent-resolver.ts +4 -4
  129. package/src/capabilities/meeting-transcription-state.ts +2 -2
  130. package/src/capabilities/transcriber.ts +2 -1
  131. package/src/components/Transcript/Transcript.stories.tsx +165 -43
  132. package/src/components/Transcript/Transcript.tsx +78 -210
  133. package/src/components/Transcript/deprecated/Transcript.tsx +231 -0
  134. package/src/components/Transcript/testings.ts +182 -0
  135. package/src/components/Transcript/transcript-extension.ts +233 -0
  136. package/src/components/TranscriptContainer.tsx +6 -3
  137. package/src/components/transcription.stories.tsx +97 -48
  138. package/src/entity-extraction/entity-extraction.test.ts +32 -0
  139. package/src/entity-extraction/entity-extraction.ts +94 -0
  140. package/src/entity-extraction/index.ts +5 -0
  141. package/src/entity-extraction/system-prompt.tpl +9 -0
  142. package/src/entity-extraction/types.d.ts +8 -0
  143. package/src/hooks/index.ts +1 -0
  144. package/src/hooks/useQueueModelAdapter.ts +30 -0
  145. package/src/hooks/useTranscriber.ts +3 -2
  146. package/src/model/index.ts +5 -0
  147. package/src/model/model.test.ts +123 -0
  148. package/src/model/model.ts +229 -0
  149. package/src/testing/test-data.ts +314 -0
  150. package/src/transcriber/transcriber.ts +1 -1
  151. package/src/transcriber/transcription-manager.ts +2 -2
  152. package/src/types/schema.ts +7 -7
  153. package/src/types/types.ts +2 -2
  154. package/src/util/index.ts +0 -1
  155. package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
  157. package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
  158. package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
  159. package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
  160. package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
  161. package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
  162. package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
  163. package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
  164. package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
  165. package/dist/lib/node/chunk-3KHUSQNW.cjs.map +0 -7
  166. package/dist/lib/node/chunk-CZMLKKWT.cjs.map +0 -7
  167. package/dist/lib/node/chunk-P4VVD6ZO.cjs.map +0 -7
  168. package/dist/lib/node/chunk-UOFHT4GZ.cjs +0 -280
  169. package/dist/lib/node/chunk-UOFHT4GZ.cjs.map +0 -7
  170. package/dist/lib/node/intent-resolver-WMMCOUEH.cjs.map +0 -7
  171. package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs.map +0 -7
  172. package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
  174. package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
  175. package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
  176. package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
  177. package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
  178. package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
  179. package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
  180. package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
  181. package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
  182. package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
  183. package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
  184. package/src/util/random-queue-dxn.ts +0 -9
  185. /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-4D4BHE7G.mjs.map} +0 -0
  186. /package/dist/lib/browser/{chunk-K7FOYAXO.mjs.map → chunk-GI5LZ4SV.mjs.map} +0 -0
  187. /package/dist/lib/{node-esm/react-surface-PCH6NTVB.mjs.map → browser/react-surface-HETJ5XY6.mjs.map} +0 -0
  188. /package/dist/lib/node/{chunk-LP4FG423.cjs.map → chunk-WXG4Z2EZ.cjs.map} +0 -0
  189. /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-3HJ752CP.mjs.map} +0 -0
  190. /package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs.map → chunk-XY22OP6H.mjs.map} +0 -0
  191. /package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +0 -0
@@ -0,0 +1,229 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Text } from '@codemirror/state';
6
+ import { type EditorView } from '@codemirror/view';
7
+
8
+ import { Event } from '@dxos/async';
9
+ import { invariant } from '@dxos/invariant';
10
+ import { log } from '@dxos/log';
11
+
12
+ export type Block = { id: string };
13
+
14
+ /**
15
+ * Covert block to a set of markdown lines.
16
+ */
17
+ export type BlockRenderer<T extends Block> = (block: T, debug?: boolean) => string[];
18
+
19
+ /**
20
+ * Abstract representation of the document model.
21
+ */
22
+ export interface BlockDocument {
23
+ lineCount(): number;
24
+ replaceLines(from: number, remove: number, insert: string[]): void;
25
+ }
26
+
27
+ /**
28
+ * Codemirror document adapter.
29
+ */
30
+ export class DocumentAdapter implements BlockDocument {
31
+ constructor(private readonly _view: EditorView) {}
32
+
33
+ /** The document must always have at least one line. */
34
+ lineCount(): number {
35
+ return this._view.state.doc.lines - 1;
36
+ }
37
+
38
+ replaceLines(from: number, remove: number, insert: string[]): void {
39
+ log('replaceLines', { count: this._view.state.doc.lines, from, remove, insert });
40
+
41
+ const numLines = this._view.state.doc.lines;
42
+ const posFrom = this._view.state.doc.line(from).from;
43
+ const posTo = remove === 0 ? posFrom : this._view.state.doc.line(Math.min(numLines, from + remove)).from;
44
+
45
+ // Don't remove the last line.
46
+ let text = insert.join('\n');
47
+ if (posTo === this._view.state.doc.length) {
48
+ text += '\n';
49
+ }
50
+
51
+ this._view.dispatch({
52
+ changes: { from: posFrom, to: posTo, insert: text },
53
+ });
54
+ }
55
+ }
56
+
57
+ type BlockChange<T extends Block> =
58
+ | {
59
+ type: 'append';
60
+ index: number;
61
+ block: T;
62
+ }
63
+ | {
64
+ type: 'update';
65
+ index: number;
66
+ block: T;
67
+ }
68
+ | {
69
+ type: 'delete';
70
+ index: number;
71
+ id: string;
72
+ };
73
+
74
+ /**
75
+ * Each Block can be converted into a set of lines.
76
+ * Blocks can be added, updated or deleted from the Queue.
77
+ * A Document is an immutable projection that represents a flat list of lines corresponding to the Queue.
78
+ *
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.
81
+ */
82
+ export class BlockModel<T extends Block> {
83
+ /** Ordered set of blocks. */
84
+ private readonly _blocks: T[] = [];
85
+
86
+ /** Map of block IDs to their current line counts. */
87
+ private readonly _blockLineCounts = new Map<string, number>();
88
+
89
+ /** Map of line numbers to block IDs. */
90
+ private readonly _lineToBlock = new Map<number, T>();
91
+
92
+ /** Track block changes since last sync. */
93
+ private readonly _changes: Array<BlockChange<T>> = [];
94
+
95
+ /** Emits when the document is updated. */
96
+ public readonly update = new Event<void>();
97
+
98
+ constructor(
99
+ private readonly _renderer: BlockRenderer<T>,
100
+ initialBlocks: T[] = [],
101
+ ) {
102
+ initialBlocks.forEach((block) => {
103
+ this.appendBlock(block);
104
+ });
105
+ }
106
+
107
+ toJSON() {
108
+ return {
109
+ blocks: this._blocks.length,
110
+ changes: this._changes.length,
111
+ };
112
+ }
113
+
114
+ get doc() {
115
+ return Text.of([...this._blocks.flatMap((block) => this._renderer(block)), '']);
116
+ }
117
+
118
+ get blocks() {
119
+ return this._blocks;
120
+ }
121
+
122
+ /**
123
+ * Get the block ID at the given line number.
124
+ */
125
+ getBlockAtLine(line: number): T | undefined {
126
+ return this._lineToBlock.get(line);
127
+ }
128
+
129
+ reset(): this {
130
+ this._blocks.length = 0;
131
+ this._blockLineCounts.clear();
132
+ this._lineToBlock.clear();
133
+ this.update.emit();
134
+ return this;
135
+ }
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 });
141
+ this.update.emit();
142
+ return this;
143
+ }
144
+
145
+ updateBlock(block: T): this {
146
+ const index = this._blocks.findIndex((b) => b.id === block.id);
147
+ invariant(index !== -1);
148
+ this._blocks[index] = block;
149
+ this._changes.push({ type: 'update', index, block });
150
+ this.update.emit();
151
+ return this;
152
+ }
153
+
154
+ deleteBlock(id: string): this {
155
+ const index = this._blocks.findIndex((b) => b.id === id);
156
+ invariant(index !== -1);
157
+ this._blocks.splice(index, 1);
158
+ this._changes.push({ type: 'delete', index, id });
159
+ this.update.emit();
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Syncs the tracked changes with the BlockDocument.
165
+ */
166
+ sync(document: BlockDocument): this {
167
+ log('sync', { changes: this._changes });
168
+
169
+ // Process each change in order.
170
+ for (const change of this._changes) {
171
+ switch (change.type) {
172
+ case 'append': {
173
+ // For appends, convert block to lines and add at the end.
174
+ const lines = this._renderer(change.block);
175
+ const lastLine = document.lineCount();
176
+ document.replaceLines(lastLine + 1, 0, lines);
177
+ break;
178
+ }
179
+
180
+ case 'update': {
181
+ // For updates, find the block's line range and replace with new lines.
182
+ let lineStart = 1;
183
+ for (let i = 0; i < change.index; i++) {
184
+ lineStart += this._blockLineCounts.get(this._blocks[i].id)!;
185
+ }
186
+ const previousLineCount = this._blockLineCounts.get(change.block.id)!;
187
+ const newLines = this._renderer(change.block);
188
+ document.replaceLines(lineStart, previousLineCount, newLines);
189
+ break;
190
+ }
191
+
192
+ case 'delete': {
193
+ // For deletes, find the block's line range and remove those lines.
194
+ let lineStart = 1;
195
+ for (let i = 0; i < change.index; i++) {
196
+ lineStart += this._blockLineCounts.get(this._blocks[i].id)!;
197
+ }
198
+ const previousLineCount = this._blockLineCounts.get(change.id)!;
199
+ document.replaceLines(lineStart, previousLineCount, []);
200
+ break;
201
+ }
202
+ }
203
+ }
204
+
205
+ // Clear the line-to-block mapping.
206
+ this._lineToBlock.clear();
207
+
208
+ // Update all block line counts and line-to-block mapping after processing changes.
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);
214
+ currentLine += lineCount;
215
+ }
216
+
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);
222
+ }
223
+ }
224
+
225
+ // Clear changes after sync.
226
+ this._changes.length = 0;
227
+ return this;
228
+ }
229
+ }
@@ -0,0 +1,314 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+
7
+ import { create, EchoObject, ObjectId } from '@dxos/echo-schema';
8
+ import { ContactType } from '@dxos/schema';
9
+
10
+ import { TranscriptBlock } from '../types';
11
+
12
+ const createContact = (name: string, email: string): ContactType => {
13
+ return create(ContactType, {
14
+ name,
15
+ identifiers: [{ type: 'email', value: email }],
16
+ });
17
+ };
18
+
19
+ /**
20
+ * Helper to create dates in reverse chronological order
21
+ */
22
+ const getDate = (daysAgo: number): string => {
23
+ const date = new Date();
24
+ date.setDate(date.getDate() - daysAgo);
25
+ return date.toISOString();
26
+ };
27
+
28
+ export const DocumentType = Schema.Struct({
29
+ id: ObjectId,
30
+ name: Schema.String,
31
+ content: Schema.String,
32
+ }).pipe(EchoObject({ typename: 'dxos.org/example/Document', version: '0.1.0' }));
33
+ export type DocumentType = typeof DocumentType.Type;
34
+
35
+ const createDocument = (name: string, content: string): DocumentType => {
36
+ return create(DocumentType, {
37
+ name,
38
+ content,
39
+ });
40
+ };
41
+
42
+ export const contacts = {
43
+ john: createContact('John Doe', 'john.doe@example.com'),
44
+ sarah: createContact('Sarah Johnson', 'sarah.johnson@techvision.com'),
45
+ michael: createContact('Michael Chen', 'michael.chen@techvision.com'),
46
+ emma: createContact('Emma Rodriguez', 'e.rodriguez@investors.com'),
47
+ david: createContact('David Williams', 'david@accountingfirm.com'),
48
+ unknown1: createContact('Bitcoin Support', 'support@btc-wallet-verify.com'),
49
+ unknown2: createContact('HR Department', 'hr-department@techvison-company.co'),
50
+ };
51
+
52
+ export const documents = [
53
+ createDocument(
54
+ 'Q3 Financial Report',
55
+ 'Q3 Financial Summary\n\nRevenue: $12.4M (↑8% YoY)\nExpenses: $8.7M (↑15% YoY)\nMargin: 29.8% (↓4.5% YoY)\n\nConcerns:\n- Marketing budget exceeded allocation by 23%\n- Customer acquisition cost increased to $142 (↑18%)\n- New product line underperforming projections by 35%\n\nRecommendations:\n1. Reallocate Q4 marketing budget to high-performing channels\n2. Implement cost-saving measures in non-essential operations\n3. Review pricing strategy for new product line',
56
+ ),
57
+ createDocument(
58
+ 'Testing Protocol',
59
+ 'Beta Testing Protocol v2.3\n\nPhase 1: Internal QA (2 weeks)\n- Unit testing coverage must exceed 85%\n- Integration testing across all supported platforms\n- Performance benchmarking against v4.2\n\nPhase 2: Closed Beta (3 weeks)\n- 250 selected customers from premium tier\n- Daily feedback collection and triage\n- Weekly build updates based on critical issues\n\nPhase 3: Open Beta (2 weeks)\n- Gradual rollout to 10,000 users\n- A/B testing of UI variations\n- Stress testing with simulated peak loads\n\nSuccess Criteria:\n- Crash rate below 0.1%\n- User satisfaction score >4.2/5\n- Performance degradation <5% on target devices',
60
+ ),
61
+ createDocument(
62
+ 'Asia Market Expansion',
63
+ 'Asia Market Expansion Strategy\n\nTarget Markets:\n1. Japan - $4.2B TAM, 22% CAGR\n2. South Korea - $2.8B TAM, 19% CAGR\n3. Singapore - $1.5B TAM, 24% CAGR\n\nCompetitive Landscape:\n- Local players dominate 65% of market share\n- Western competitors struggling with localization\n- Regulatory environment favors local partnerships\n\nEntry Strategy:\n- Phase 1: Strategic partnerships with established distributors\n- Phase 2: Local office establishment in Singapore (regional hub)\n- Phase 3: Acquisition of complementary local technology firms\n\nProjected ROI: 3.2x investment over 5 years with break-even in Year 3',
64
+ ),
65
+ createDocument(
66
+ 'Strategic Partnership Proposal',
67
+ 'Partnership Framework: TechVision & Example Corp\n\nProposed Collaboration Areas:\n\n1. Technology Integration\n - API access to respective platforms\n - Joint development of middleware solutions\n - Shared data insights with privacy controls\n\n2. Market Expansion\n - Co-branded offerings for enterprise segment\n - Channel partner access in respective regions\n - Joint participation in industry events\n\n3. Investment Structure\n - Initial pilot project ($250K shared investment)\n - Success-based scaling model\n - Equity considerations for Phase 2\n\nAnticipated Outcomes:\n- $8-12M incremental revenue within 24 months\n- 15% reduction in customer acquisition costs\n- Accelerated product roadmap by ~6 months',
68
+ ),
69
+ createDocument(
70
+ 'Annual Audit Checklist',
71
+ 'FY2023 Audit Preparation Checklist\n\nFinance Department:\n- General ledger and trial balance\n- Bank reconciliations and statements (all accounts)\n- Accounts receivable aging report with bad debt analysis\n- Fixed asset register with depreciation schedules\n- Loan agreements and covenant compliance documentation\n\nOperations:\n- Inventory count procedures and results\n- Major contracts and purchase agreements\n- Vendor master list with annual spend analysis\n- Warehouse security and valuation procedures\n\nHR:\n- Payroll reconciliations and tax filings\n- Benefits administration documentation\n- Employee handbook and policy updates\n- Executive compensation agreements\n\nIT:\n- System access controls documentation\n- Backup and disaster recovery testing results\n- Security incident reports and resolution documentation\n- Software license compliance report',
72
+ ),
73
+ createDocument(
74
+ 'Product Roadmap 2023-2024',
75
+ 'Product Roadmap: July 2023 - December 2024\n\nQ3 2023:\n- Core platform v5.0 release\n • Redesigned user interface\n • Performance improvements (50% faster loading)\n • Enhanced security features\n\nQ4 2023:\n- Mobile application redesign\n- Enterprise SSO integration\n- Custom reporting engine\n\nQ1 2024:\n- AI-powered recommendation system\n- Advanced analytics dashboard\n- API v3 with expanded capabilities\n\nQ2 2024:\n- Multi-tenant architecture\n- Compliance module for regulated industries\n- White-labeling capabilities\n\nQ3-Q4 2024:\n- International expansion features\n- Blockchain integration for document verification\n- Industry-specific vertical solutions\n\nResource Allocation:\n- Engineering: 65% of budget\n- Design: 15% of budget\n- QA: 12% of budget\n- Documentation/Support: 8% of budget',
76
+ ),
77
+ createDocument(
78
+ 'TechGiant Acquisition Offer',
79
+ 'CONFIDENTIAL: TechGiant Acquisition Offer Analysis\n\nOffer Details:\n- $450M all-cash transaction\n- 30-day exclusivity period requested\n- Retention packages for key executives (3-year vesting)\n- Product integration roadmap provided\n\nValuation Analysis:\n- 8.2x trailing twelve-month revenue\n- 6.4x forward revenue (based on projections)\n- 22x EBITDA (industry average: 18-24x)\n\nComparable Transactions:\n- CompetitorX acquisition: 7.5x revenue (2022)\n- StartupY acquisition: 9.1x revenue (2023)\n\nPreliminary Board Considerations:\n- Offer represents 35% premium to last funding round valuation\n- Potential regulatory concerns in EU market\n- IP ownership transition requires careful structuring\n- Alternative paths to liquidity should be evaluated\n\nNext Steps:\n- Engage outside counsel for preliminary review\n- Prepare counter-proposal scenarios\n- Develop communication strategy for employees and customers',
80
+ ),
81
+ // Unrelated documents
82
+ createDocument(
83
+ 'Employee Handbook',
84
+ 'TechVision Employee Handbook v3.2\n\nSection 1: Company Overview\n- Mission and values\n- Organizational structure\n- Company history\n\nSection 2: Employment Policies\n- Equal opportunity statement\n- At-will employment explanation\n- Code of conduct\n- Confidentiality requirements\n\nSection 3: Compensation & Benefits\n- Pay periods and methods\n- Health insurance options\n- 401(k) plan details\n- Stock option program\n- Professional development allowance\n\nSection 4: Time Off & Leave Policies\n- Vacation accrual schedule\n- Sick leave policy\n- Parental leave benefits\n- Bereavement and jury duty\n\nSection 5: Workplace Guidelines\n- Office hours and flexible work\n- Remote work policies\n- Equipment usage\n- Expense reimbursement procedure\n\nSection 6: Health & Safety\n- Emergency procedures\n- Workplace safety guidelines\n- Incident reporting process',
85
+ ),
86
+ createDocument(
87
+ 'Project Moonshot',
88
+ 'Project Moonshot: Preliminary Research\n\nMarket Opportunity:\n- $35B global addressable market by 2026\n- 42% CAGR in emerging economies\n- Limited competition with established solutions\n\nTechnical Feasibility:\n- Core technology proven in lab environment (TRL 4)\n- Key patents identified for licensing or acquisition\n- Estimated 18-24 months to minimum viable product\n\nResource Requirements:\n- Core team of 8-10 engineers (3 specialized roles)\n- Initial funding need: $4.2M for Phase 1\n- Strategic partnerships required for manufacturing\n\nRisk Assessment:\n- Regulatory approval pathway unclear in target markets\n- Two competing technologies in early development\n- Supply chain dependencies for critical components\n\nNext Decision Point:\n- Go/no-go for prototype development by Q4',
89
+ ),
90
+ createDocument(
91
+ 'Office Relocation Plan',
92
+ 'Headquarters Relocation Plan: Downtown Campus\n\nTimeline:\n- Phase 1: Planning & Design (3 months)\n- Phase 2: Construction & Buildout (5 months)\n- Phase 3: Move Execution (1 month)\n- Target completion: December 15\n\nSpace Details:\n- 45,000 sq ft across 3 floors\n- 320 workstations (30% hot-desking)\n- 18 conference rooms of varying sizes\n- 5 specialized collaboration spaces\n- Expanded lab and testing facilities\n\nBudget Summary:\n- Lease: $42/sq ft annually ($1.89M/year)\n- Buildout: $185/sq ft ($8.3M total)\n- Furniture & Equipment: $2.4M\n- Moving & Logistics: $350K\n- Technology Infrastructure: $1.2M\n\nDepartment Migration Schedule:\n- Engineering: December 3-4\n- Product & Design: December 5-6\n- Sales & Marketing: December 10-11\n- Executive & Operations: December 12-13',
93
+ ),
94
+ ];
95
+
96
+ export const transcriptBlocks: TranscriptBlock[] = [
97
+ create(TranscriptBlock, {
98
+ authorName: contacts.john.name,
99
+ segments: [
100
+ {
101
+ started: getDate(0),
102
+ text: 'Good morning everyone. Thanks for joining the quarterly strategy meeting. I see Sarah and Emma are here. Are we still waiting for David?',
103
+ },
104
+ {
105
+ started: getDate(30),
106
+ text: 'While we wait, let me pull up the Q3 Financial Report that we need to review today.',
107
+ },
108
+ ],
109
+ }),
110
+ create(TranscriptBlock, {
111
+ authorName: contacts.sarah.name,
112
+ segments: [
113
+ {
114
+ started: getDate(45),
115
+ text: "Morning John. David texted me that he's running about 5 minutes late. Something about finalizing some numbers for us.",
116
+ },
117
+ ],
118
+ }),
119
+ create(TranscriptBlock, {
120
+ authorName: contacts.emma.name,
121
+ segments: [
122
+ {
123
+ started: getDate(60),
124
+ text: "Hi everyone. I've already reviewed the Q3 report. I'm particularly concerned about the marketing budget overrun. 23% over allocation is significant.",
125
+ },
126
+ ],
127
+ }),
128
+ create(TranscriptBlock, {
129
+ authorName: contacts.john.name,
130
+ segments: [
131
+ {
132
+ started: getDate(90),
133
+ text: "I agree, Emma. That's definitely one of our top concerns. We also need to address the underperforming new product line. It's 35% below projections.",
134
+ },
135
+ ],
136
+ }),
137
+ create(TranscriptBlock, {
138
+ authorName: contacts.david.name,
139
+ segments: [
140
+ {
141
+ started: getDate(120),
142
+ text: "Sorry I'm late everyone. I was just finalizing the numbers for our Asia Market Expansion strategy to discuss today.",
143
+ },
144
+ {
145
+ started: getDate(135),
146
+ text: "I see you're already discussing the Q3 report. The margin decrease is concerning, but I think it's related to our investment in the new markets.",
147
+ },
148
+ ],
149
+ }),
150
+ create(TranscriptBlock, {
151
+ authorName: contacts.sarah.name,
152
+ segments: [
153
+ {
154
+ started: getDate(160),
155
+ text: "David, regarding the Asia expansion, I've been working with Michael from the Singapore office. The regulatory environment there definitely favors local partnerships as mentioned in our strategy document.",
156
+ },
157
+ ],
158
+ }),
159
+ create(TranscriptBlock, {
160
+ authorName: contacts.emma.name,
161
+ segments: [
162
+ {
163
+ started: getDate(190),
164
+ text: 'I think we should also consider the TechGiant acquisition offer in light of our expansion plans. The $450M all-cash transaction would give us significant resources for the Asia push.',
165
+ },
166
+ {
167
+ started: getDate(210),
168
+ text: 'Has anyone spoken with Alex about this? As our CFO, his input would be valuable.',
169
+ },
170
+ ],
171
+ }),
172
+ create(TranscriptBlock, {
173
+ authorName: contacts.john.name,
174
+ segments: [
175
+ {
176
+ started: getDate(240),
177
+ text: "I had lunch with Alex yesterday. He's running the numbers, but his initial reaction was positive. The 35% premium over our last funding round is attractive.",
178
+ },
179
+ {
180
+ started: getDate(260),
181
+ text: "Let's shift gears to the Product Roadmap for 2023-2024. Sarah, can you give us an update on the Q3 deliverables?",
182
+ },
183
+ ],
184
+ }),
185
+ create(TranscriptBlock, {
186
+ authorName: contacts.sarah.name,
187
+ segments: [
188
+ {
189
+ started: getDate(290),
190
+ text: 'Sure. The core platform v5.0 is on track for release next month. The redesigned UI has tested well with focus groups, and performance improvements are exceeding our 50% faster loading target.',
191
+ },
192
+ {
193
+ started: getDate(320),
194
+ text: "I've been working closely with Jennifer's team on the enhanced security features. They've done an excellent job.",
195
+ },
196
+ ],
197
+ }),
198
+ create(TranscriptBlock, {
199
+ authorName: contacts.david.name,
200
+ segments: [
201
+ {
202
+ started: getDate(350),
203
+ text: 'Speaking of security, I received an email from someone claiming to be from Bitcoin Support. It looked suspicious, so I forwarded it to our IT security team.',
204
+ },
205
+ ],
206
+ }),
207
+ create(TranscriptBlock, {
208
+ authorName: contacts.john.name,
209
+ segments: [
210
+ {
211
+ started: getDate(380),
212
+ text: "Good call, David. There's been an increase in phishing attempts lately. Everyone should remain vigilant.",
213
+ },
214
+ {
215
+ started: getDate(400),
216
+ text: 'Now, regarding our Strategic Partnership Proposal with Example Corp, I had a productive call with their CEO yesterday.',
217
+ },
218
+ ],
219
+ }),
220
+ create(TranscriptBlock, {
221
+ authorName: contacts.emma.name,
222
+ segments: [
223
+ {
224
+ started: getDate(430),
225
+ text: "That's great to hear. The partnership framework looks solid. I particularly like the co-branded offerings for the enterprise segment. That aligns well with our Q4 goals.",
226
+ },
227
+ ],
228
+ }),
229
+ create(TranscriptBlock, {
230
+ authorName: contacts.sarah.name,
231
+ segments: [
232
+ {
233
+ started: getDate(460),
234
+ text: 'By the way, has everyone completed their items for the Annual Audit Checklist? The HR Department sent a reminder yesterday about the payroll reconciliations and tax filings.',
235
+ },
236
+ ],
237
+ }),
238
+ create(TranscriptBlock, {
239
+ authorName: contacts.david.name,
240
+ segments: [
241
+ {
242
+ started: getDate(490),
243
+ text: "I've submitted all the finance department items. The general ledger, trial balance, and bank reconciliations are all ready for review.",
244
+ },
245
+ {
246
+ started: getDate(510),
247
+ text: "I'm still working on the accounts receivable aging report with bad debt analysis. Should have that done by tomorrow.",
248
+ },
249
+ ],
250
+ }),
251
+ create(TranscriptBlock, {
252
+ authorName: contacts.john.name,
253
+ segments: [
254
+ {
255
+ started: getDate(540),
256
+ text: "Excellent. Let's wrap up with action items. Emma, can you lead the marketing budget reallocation for Q4?",
257
+ },
258
+ ],
259
+ }),
260
+ create(TranscriptBlock, {
261
+ authorName: contacts.emma.name,
262
+ segments: [
263
+ {
264
+ started: getDate(570),
265
+ text: "Yes, I'll work with the marketing team to focus on high-performing channels as recommended in the Q3 report.",
266
+ },
267
+ ],
268
+ }),
269
+ create(TranscriptBlock, {
270
+ authorName: contacts.john.name,
271
+ segments: [
272
+ {
273
+ started: getDate(600),
274
+ text: 'Sarah, please continue leading the product release and coordinate with Jennifer on the security features.',
275
+ },
276
+ ],
277
+ }),
278
+ create(TranscriptBlock, {
279
+ authorName: contacts.sarah.name,
280
+ segments: [
281
+ {
282
+ started: getDate(630),
283
+ text: "Will do. I'll also follow up with Michael about the Singapore regulatory requirements for our Asia expansion.",
284
+ },
285
+ ],
286
+ }),
287
+ create(TranscriptBlock, {
288
+ authorName: contacts.john.name,
289
+ segments: [
290
+ {
291
+ started: getDate(660),
292
+ text: 'And David, please finalize the accounts receivable report and continue your analysis of the TechGiant offer with Alex.',
293
+ },
294
+ ],
295
+ }),
296
+ create(TranscriptBlock, {
297
+ authorName: contacts.david.name,
298
+ segments: [
299
+ {
300
+ started: getDate(690),
301
+ text: "I'm on it. I'll have a detailed analysis ready for our next meeting.",
302
+ },
303
+ ],
304
+ }),
305
+ create(TranscriptBlock, {
306
+ authorName: contacts.john.name,
307
+ segments: [
308
+ {
309
+ started: getDate(720),
310
+ text: "Great. Thanks everyone for a productive meeting. Let's reconvene next week to follow up on these items.",
311
+ },
312
+ ],
313
+ }),
314
+ ];
@@ -240,7 +240,7 @@ export class Transcriber extends Resource {
240
240
 
241
241
  // Add absolute timestamp to each segment.
242
242
  return filteredSegments.map((segment) => ({
243
- started: new Date(zeroTimestamp + segment.start * 1_000),
243
+ started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
244
244
  text: segment.text,
245
245
  }));
246
246
  }
@@ -5,7 +5,7 @@
5
5
  import { synchronized } from '@dxos/async';
6
6
  import { Resource } from '@dxos/context';
7
7
  import { QueueImpl, type Queue } from '@dxos/echo-db';
8
- import { createStatic } from '@dxos/echo-schema';
8
+ import { create } from '@dxos/echo-schema';
9
9
  import { invariant } from '@dxos/invariant';
10
10
  import { DXN } from '@dxos/keys';
11
11
  import { type EdgeHttpClient } from '@dxos/react-edge-client';
@@ -178,7 +178,7 @@ export class TranscriptionManager extends Resource {
178
178
  return;
179
179
  }
180
180
 
181
- const block = createStatic(TranscriptBlock, { authorName: this._name, authorHue: this._hue, segments });
181
+ const block = create(TranscriptBlock, { authorName: this._name, authorHue: this._hue, segments });
182
182
  this._queue.append([block]);
183
183
  }
184
184
  }
@@ -10,9 +10,9 @@ import { EchoObject, Expando, Ref, S, TypedObject } from '@dxos/echo-schema';
10
10
  export const TranscriptSchema = S.Struct({
11
11
  name: S.String,
12
12
 
13
- // TODO(burdon): Use string for dates?
14
- started: S.optional(S.Date),
15
- ended: S.optional(S.Date),
13
+ // TODO(burdon): Use Date or string?
14
+ started: S.optional(S.String),
15
+ ended: S.optional(S.String),
16
16
 
17
17
  /**
18
18
  * Queue containing TranscriptBlock objects.
@@ -33,7 +33,7 @@ export class TranscriptType extends TypedObject({
33
33
  * Contains metadata for the recording and transcript.
34
34
  */
35
35
  const TranscriptHeader = S.Struct({
36
- started: S.optional(S.Date),
36
+ started: S.optional(S.String),
37
37
  });
38
38
 
39
39
  export type TranscriptHeader = S.Schema.Type<typeof TranscriptHeader>;
@@ -43,7 +43,7 @@ export type TranscriptHeader = S.Schema.Type<typeof TranscriptHeader>;
43
43
  */
44
44
  const TranscriptSegment = S.Struct({
45
45
  // TODO(burdon): TS from service is not Unix TS (x1000).
46
- started: S.Date,
46
+ started: S.String,
47
47
  text: S.String,
48
48
  });
49
49
 
@@ -54,8 +54,8 @@ export type TranscriptSegment = S.Schema.Type<typeof TranscriptSegment>;
54
54
  */
55
55
  export const TranscriptBlock = S.Struct({
56
56
  id: S.String,
57
- authorName: S.optional(S.String), // TODO(burdon): IdentityDid.
58
- authorHue: S.optional(S.String),
57
+ authorName: S.optional(S.String), // TODO(burdon): Replace with identityDid.
58
+ authorHue: S.optional(S.String), // TOOD(burdon): Remove.
59
59
  segments: S.Array(TranscriptSegment),
60
60
  }).pipe(EchoObject({ typename: 'dxos.org/type/TranscriptBlock', version: '0.1.0' }));
61
61
 
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { S, isInstanceOf } from '@dxos/echo-schema';
6
- import { isReactiveObject } from '@dxos/react-client/echo';
6
+ import { isLiveObject } from '@dxos/react-client/echo';
7
7
 
8
8
  import { TranscriptType } from './schema';
9
9
  import { TRANSCRIPTION_PLUGIN } from '../meta';
@@ -32,5 +32,5 @@ export namespace TranscriptionAction {
32
32
  }
33
33
 
34
34
  export const isTranscript = (object: unknown): object is typeof TranscriptType => {
35
- return isReactiveObject(object) && isInstanceOf(TranscriptType, object);
35
+ return isLiveObject(object) && isInstanceOf(TranscriptType, object);
36
36
  };
package/src/util/index.ts CHANGED
@@ -5,4 +5,3 @@
5
5
  export * from './array';
6
6
  export * from './get-time-str';
7
7
  export * from './monitor-audio-level';
8
- export * from './random-queue-dxn';
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/capabilities/app-graph-builder.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Capabilities, contributes, createIntent, type PluginsContext } from '@dxos/app-framework';\nimport { fullyQualifiedId, getSpace, makeRef, type Space } from '@dxos/client/echo';\nimport { generateName } from '@dxos/display-name';\nimport { getSchemaTypename, isInstanceOf } from '@dxos/echo-schema';\nimport { invariant } from '@dxos/invariant';\nimport { ClientCapabilities } from '@dxos/plugin-client';\nimport { PLANK_COMPANION_TYPE, ATTENDABLE_PATH_SEPARATOR } from '@dxos/plugin-deck/types';\nimport { createExtension, type Node } from '@dxos/plugin-graph';\nimport { MeetingCapabilities, type CallState, type MediaState } from '@dxos/plugin-meeting';\nimport { MeetingType } from '@dxos/plugin-meeting/types';\nimport { buf } from '@dxos/protocols/buf';\nimport {\n TranscriptionPayloadSchema,\n type TranscriptionPayload as TranscriptionPayloadProto,\n} from '@dxos/protocols/buf/dxos/edge/calls_pb';\nimport { keyToFallback } from '@dxos/util';\n\nimport { TranscriptionCapabilities } from './capabilities';\nimport { TRANSCRIPTION_PLUGIN } from '../meta';\nimport { TranscriptionManager } from '../transcriber';\nimport { TranscriptionAction, TranscriptType } from '../types';\n\n// TODO(wittjosiah): Factor out.\n// TODO(wittjosiah): Can we stop using protobuf for this?\ntype TranscriptionPayload = buf.MessageInitShape<typeof TranscriptionPayloadSchema>;\nconst codec = {\n encode: (message: TranscriptionPayload): Uint8Array => {\n return buf.toBinary(TranscriptionPayloadSchema, buf.create(TranscriptionPayloadSchema, message));\n },\n decode: (message: Uint8Array): TranscriptionPayloadProto => {\n return buf.fromBinary(TranscriptionPayloadSchema, message);\n },\n};\n\nconst getMeetingTranscript = async (\n context: PluginsContext,\n meeting: MeetingType,\n): Promise<TranscriptType | undefined> => {\n const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);\n\n const typename = getSchemaTypename(TranscriptType)!;\n const existing = await meeting.artifacts[typename]?.load();\n if (existing instanceof TranscriptType) {\n return existing;\n }\n\n const space = getSpace(meeting);\n invariant(space);\n const { data } = await dispatch(createIntent(TranscriptionAction.Create, { spaceId: space.id }));\n meeting.artifacts[typename] = makeRef(data!.object);\n return data?.object;\n};\n\n// TODO(wittjosiah): Introduce a sound that plays when transcription is activated/deactivated.\nexport default (context: PluginsContext) =>\n contributes(Capabilities.AppGraphBuilder, [\n createExtension({\n id: `${TRANSCRIPTION_PLUGIN}/meeting-transcript`,\n filter: (node): node is Node<MeetingType> =>\n isInstanceOf(MeetingType, node.data) && node.type !== PLANK_COMPANION_TYPE,\n actions: ({ node }) => {\n const meeting = node.data;\n const state = context.requestCapability(TranscriptionCapabilities.MeetingTranscriptionState);\n return [\n {\n id: `${fullyQualifiedId(meeting)}/action/start-stop-transcription`,\n data: async () => {\n const call = context.requestCapability(MeetingCapabilities.CallManager);\n invariant(state.transcriptionManager);\n state.enabled = !state.enabled;\n void state.transcriptionManager.setEnabled(state.enabled);\n const transcript = await getMeetingTranscript(context, meeting);\n invariant(transcript, 'Failed to create transcript');\n call.setActivity(getSchemaTypename(TranscriptType)!, {\n value: codec.encode({\n enabled: state.enabled,\n queueDxn: transcript.queue.dxn.toString(),\n }),\n });\n },\n properties: {\n label: state.enabled\n ? ['stop transcription label', { ns: TRANSCRIPTION_PLUGIN }]\n : ['start transcription label', { ns: TRANSCRIPTION_PLUGIN }],\n icon: 'ph--record--regular',\n disposition: 'toolbar',\n classNames: state.enabled ? 'text-activeInCall' : '',\n },\n },\n ];\n },\n connector: ({ node }) => {\n const meeting = node.data;\n const client = context.requestCapability(ClientCapabilities.Client);\n const state = context.requestCapability(TranscriptionCapabilities.MeetingTranscriptionState);\n\n return [\n {\n id: `${fullyQualifiedId(meeting)}${ATTENDABLE_PATH_SEPARATOR}${getSchemaTypename(TranscriptType)}`,\n type: PLANK_COMPANION_TYPE,\n data: meeting.artifacts[getSchemaTypename(TranscriptType)!]?.target,\n properties: {\n label: ['transcript companion label', { ns: TRANSCRIPTION_PLUGIN }],\n icon: 'ph--subtitles--regular',\n disposition: 'hidden',\n schema: TranscriptType,\n getIntent: ({ space }: { space: Space }) =>\n createIntent(TranscriptionAction.Create, { spaceId: space.id }),\n onJoin: () => {\n const transcriptionManager = new TranscriptionManager(client.edge);\n const identity = client.halo.identity.get();\n invariant(identity);\n transcriptionManager.setName(\n identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),\n );\n const fallbackValue = keyToFallback(identity!.identityKey);\n const userHue = identity!.profile?.data?.hue || fallbackValue.hue;\n transcriptionManager.setHue(userHue);\n void transcriptionManager.open();\n state.transcriptionManager = transcriptionManager;\n },\n onLeave: () => {\n void state.transcriptionManager?.close();\n state.transcriptionManager = undefined;\n state.enabled = false;\n },\n onCallStateUpdated: (callState: CallState) => {\n const typename = getSchemaTypename(TranscriptType);\n const transcription = typename ? callState.activities?.[typename] : undefined;\n if (!transcription?.payload?.value) {\n return;\n }\n\n const payload = codec.decode(transcription.payload.value);\n state.enabled = payload.enabled;\n void state.transcriptionManager?.setEnabled(payload.enabled);\n void state.transcriptionManager?.setQueue(payload.queueDxn);\n },\n onMediaStateUpdated: ([mediaState, isSpeaking]: [MediaState, boolean]) => {\n void state.transcriptionManager?.setAudioTrack(mediaState.audioTrack);\n void state.transcriptionManager?.setRecording(isSpeaking);\n },\n },\n },\n ];\n },\n }),\n ]);\n"],
5
- "mappings": "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",
6
- "names": ["Capabilities", "contributes", "createIntent", "fullyQualifiedId", "getSpace", "makeRef", "generateName", "getSchemaTypename", "isInstanceOf", "invariant", "ClientCapabilities", "PLANK_COMPANION_TYPE", "ATTENDABLE_PATH_SEPARATOR", "createExtension", "MeetingCapabilities", "MeetingType", "buf", "TranscriptionPayloadSchema", "keyToFallback", "codec", "encode", "message", "buf", "toBinary", "TranscriptionPayloadSchema", "create", "decode", "fromBinary", "getMeetingTranscript", "context", "meeting", "dispatchPromise", "dispatch", "requestCapability", "Capabilities", "IntentDispatcher", "typename", "getSchemaTypename", "TranscriptType", "existing", "artifacts", "load", "space", "getSpace", "invariant", "data", "createIntent", "TranscriptionAction", "Create", "spaceId", "id", "makeRef", "object", "contributes", "AppGraphBuilder", "createExtension", "TRANSCRIPTION_PLUGIN", "filter", "node", "isInstanceOf", "MeetingType", "type", "PLANK_COMPANION_TYPE", "actions", "state", "TranscriptionCapabilities", "MeetingTranscriptionState", "fullyQualifiedId", "call", "MeetingCapabilities", "CallManager", "transcriptionManager", "enabled", "setEnabled", "transcript", "setActivity", "value", "queueDxn", "queue", "dxn", "toString", "properties", "label", "ns", "icon", "disposition", "classNames", "connector", "client", "ClientCapabilities", "Client", "ATTENDABLE_PATH_SEPARATOR", "target", "schema", "getIntent", "onJoin", "TranscriptionManager", "edge", "identity", "halo", "get", "setName", "profile", "displayName", "generateName", "identityKey", "toHex", "fallbackValue", "keyToFallback", "userHue", "hue", "setHue", "open", "onLeave", "close", "undefined", "onCallStateUpdated", "callState", "transcription", "activities", "payload", "setQueue", "onMediaStateUpdated", "mediaState", "isSpeaking", "setAudioTrack", "audioTrack", "setRecording"]
7
- }