@dxos/plugin-transcription 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d
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.
- package/dist/lib/browser/{TranscriptContainer-E5NBJPAY.mjs → TranscriptContainer-FH7VAWYA.mjs} +3 -3
- package/dist/lib/browser/{chunk-F2WY3YTD.mjs → chunk-BJWYA2BU.mjs} +420 -402
- package/dist/lib/browser/chunk-BJWYA2BU.mjs.map +7 -0
- package/dist/lib/browser/{chunk-VDKDOZRR.mjs → chunk-KJIQQHJB.mjs} +38 -27
- package/dist/lib/browser/chunk-KJIQQHJB.mjs.map +7 -0
- package/dist/lib/browser/{chunk-R7INNNUQ.mjs → chunk-KWOOVS5G.mjs} +1 -1
- package/dist/lib/browser/{chunk-R7INNNUQ.mjs.map → chunk-KWOOVS5G.mjs.map} +2 -2
- package/dist/lib/browser/{chunk-U6QBOGTN.mjs → chunk-SREIAQSP.mjs} +1 -1
- package/dist/lib/browser/{chunk-U6QBOGTN.mjs.map → chunk-SREIAQSP.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-EQBICGOD.mjs → chunk-UKUSJTQW.mjs} +2 -2
- package/dist/lib/browser/index.mjs +5 -5
- package/dist/lib/browser/{intent-resolver-3O33MMP5.mjs → intent-resolver-AOVQ7JI4.mjs} +2 -2
- package/dist/lib/{node-esm/intent-resolver-YMN5SXNC.mjs.map → browser/intent-resolver-AOVQ7JI4.mjs.map} +2 -2
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-FJTMNF3K.mjs → react-surface-4MEJMKYQ.mjs} +5 -5
- package/dist/lib/browser/{transcriber-YVOTUBKQ.mjs → transcriber-V77CN7XX.mjs} +4 -4
- package/dist/lib/browser/transcriber-V77CN7XX.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node-esm/{TranscriptContainer-UOS7JG72.mjs → TranscriptContainer-GEUOOYHX.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-HSCG4XPR.mjs → chunk-A33GNR44.mjs} +38 -27
- package/dist/lib/node-esm/chunk-A33GNR44.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-U65ZWK3L.mjs → chunk-IKQMDRY6.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-U65ZWK3L.mjs.map → chunk-IKQMDRY6.mjs.map} +2 -2
- package/dist/lib/node-esm/{chunk-DN522SAP.mjs → chunk-PHP7MCAY.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-RHF2ALML.mjs → chunk-TM2ASOZA.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-RHF2ALML.mjs.map → chunk-TM2ASOZA.mjs.map} +1 -1
- package/dist/lib/node-esm/{chunk-Q3JKLPTW.mjs → chunk-Y7PCJ34K.mjs} +420 -402
- package/dist/lib/node-esm/chunk-Y7PCJ34K.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +5 -5
- package/dist/lib/node-esm/{intent-resolver-YMN5SXNC.mjs → intent-resolver-P4XPIKQF.mjs} +2 -2
- package/dist/lib/{browser/intent-resolver-3O33MMP5.mjs.map → node-esm/intent-resolver-P4XPIKQF.mjs.map} +2 -2
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-57TSIDR2.mjs → react-surface-EZUPNHUC.mjs} +5 -5
- package/dist/lib/node-esm/{transcriber-GPBV52ZR.mjs → transcriber-3SX3DNLV.mjs} +4 -4
- package/dist/lib/node-esm/transcriber-3SX3DNLV.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
- package/dist/types/src/components/TranscriptContainer.d.ts +1 -1
- package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +1 -1
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +1 -1
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +1 -1
- package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
- package/dist/types/src/segments-normalization/normalization.d.ts +159 -66
- package/dist/types/src/segments-normalization/normalization.d.ts.map +1 -1
- package/dist/types/src/testing/testing.d.ts +1 -1
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/transcriber/index.d.ts +1 -1
- package/dist/types/src/transcriber/index.d.ts.map +1 -1
- package/dist/types/src/transcriber/media-stream-recorder.d.ts +1 -1
- package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
- package/dist/types/src/transcriber/transcriber.d.ts +1 -1
- package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +55 -53
- package/src/capabilities/capabilities.ts +3 -3
- package/src/capabilities/intent-resolver.ts +2 -2
- package/src/capabilities/transcriber.ts +3 -2
- package/src/components/Transcript/Transcript.stories.tsx +3 -2
- package/src/components/Transcript/Transcript.tsx +5 -4
- package/src/components/Transcript/deprecated/OldTranscript.tsx +2 -2
- package/src/components/Transcript/transcript-extension.ts +2 -2
- package/src/components/TranscriptContainer.tsx +4 -3
- package/src/components/stories/FileTranscription.stories.tsx +22 -18
- package/src/components/stories/KeyWordRecognition.stories.tsx +1 -1
- package/src/components/stories/MicrophoneTranscription.stories.tsx +17 -14
- package/src/hooks/useVoiceInput.ts +3 -2
- package/src/model/model.test.ts +9 -9
- package/src/segments-normalization/message-normalizer.ts +1 -1
- package/src/segments-normalization/normalization.test.ts +19 -21
- package/src/segments-normalization/normalization.ts +54 -45
- package/src/testing/testing.ts +11 -11
- package/src/transcriber/index.ts +1 -1
- package/src/transcriber/media-stream-recorder.ts +1 -1
- package/src/transcriber/transcriber.browser.test.ts +4 -3
- package/src/transcriber/transcriber.ts +5 -7
- package/src/transcriber/transcription-manager.ts +3 -3
- package/src/types/types.ts +2 -1
- package/dist/lib/browser/chunk-F2WY3YTD.mjs.map +0 -7
- package/dist/lib/browser/chunk-VDKDOZRR.mjs.map +0 -7
- package/dist/lib/browser/transcriber-YVOTUBKQ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSCG4XPR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Q3JKLPTW.mjs.map +0 -7
- package/dist/lib/node-esm/transcriber-GPBV52ZR.mjs.map +0 -7
- /package/dist/lib/browser/{TranscriptContainer-E5NBJPAY.mjs.map → TranscriptContainer-FH7VAWYA.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-EQBICGOD.mjs.map → chunk-UKUSJTQW.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-FJTMNF3K.mjs.map → react-surface-4MEJMKYQ.mjs.map} +0 -0
- /package/dist/lib/node-esm/{TranscriptContainer-UOS7JG72.mjs.map → TranscriptContainer-GEUOOYHX.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-DN522SAP.mjs.map → chunk-PHP7MCAY.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-surface-57TSIDR2.mjs.map → react-surface-EZUPNHUC.mjs.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TranscriptionCapabilities
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SREIAQSP.mjs";
|
|
4
4
|
|
|
5
5
|
// src/components/TranscriptContainer.tsx
|
|
6
6
|
import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
|
|
@@ -9,28 +9,144 @@ import { fullyQualifiedId, getSpace } from "@dxos/client/echo";
|
|
|
9
9
|
import { useMembers, useQueue } from "@dxos/react-client/echo";
|
|
10
10
|
import { StackItem } from "@dxos/react-ui-stack";
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// src/hooks/useAudioFile.ts
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
import { scheduleTask } from "@dxos/async";
|
|
15
|
+
import { Context } from "@dxos/context";
|
|
16
|
+
import { log } from "@dxos/log";
|
|
17
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useAudioFile.ts";
|
|
18
|
+
var useAudioFile = (audioUrl, constraints) => {
|
|
19
|
+
const [{ audio, stream, track }, setStream] = useState({});
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const ctx = new Context(void 0, {
|
|
22
|
+
F: __dxlog_file,
|
|
23
|
+
L: 20
|
|
24
|
+
});
|
|
25
|
+
scheduleTask(ctx, async () => {
|
|
26
|
+
const response = await fetch(audioUrl);
|
|
27
|
+
const blob = await response.blob();
|
|
28
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
29
|
+
ctx.onDispose(() => {
|
|
30
|
+
URL.revokeObjectURL(objectUrl);
|
|
31
|
+
});
|
|
32
|
+
const audio2 = new Audio();
|
|
33
|
+
audio2.src = objectUrl;
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
audio2.addEventListener("error", (err) => {
|
|
36
|
+
log.error("error", {
|
|
37
|
+
err
|
|
38
|
+
}, {
|
|
39
|
+
F: __dxlog_file,
|
|
40
|
+
L: 37,
|
|
41
|
+
S: void 0,
|
|
42
|
+
C: (f, a) => f(...a)
|
|
43
|
+
});
|
|
44
|
+
reject(err);
|
|
45
|
+
}, {
|
|
46
|
+
once: true
|
|
47
|
+
});
|
|
48
|
+
audio2.addEventListener("canplay", async () => {
|
|
49
|
+
log.info("starting...", void 0, {
|
|
50
|
+
F: __dxlog_file,
|
|
51
|
+
L: 45,
|
|
52
|
+
S: void 0,
|
|
53
|
+
C: (f, a) => f(...a)
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
await audio2.play();
|
|
57
|
+
resolve();
|
|
58
|
+
} catch (playError) {
|
|
59
|
+
log.error("Play failed", {
|
|
60
|
+
playError
|
|
61
|
+
}, {
|
|
62
|
+
F: __dxlog_file,
|
|
63
|
+
L: 51,
|
|
64
|
+
S: void 0,
|
|
65
|
+
C: (f, a) => f(...a)
|
|
66
|
+
});
|
|
67
|
+
resolve();
|
|
68
|
+
}
|
|
69
|
+
}, {
|
|
70
|
+
once: true
|
|
71
|
+
});
|
|
72
|
+
audio2.load();
|
|
73
|
+
});
|
|
74
|
+
const audioCtx = new AudioContext();
|
|
75
|
+
if (audioCtx.state === "suspended") {
|
|
76
|
+
await audioCtx.resume();
|
|
77
|
+
}
|
|
78
|
+
const destination = audioCtx.createMediaStreamDestination();
|
|
79
|
+
destination.channelCount = 1;
|
|
80
|
+
const source = audioCtx.createMediaElementSource(audio2);
|
|
81
|
+
source.connect(destination);
|
|
82
|
+
const track2 = destination.stream.getAudioTracks()[0];
|
|
83
|
+
if (constraints) {
|
|
84
|
+
await track2.applyConstraints(constraints).catch((err) => log.catch(err, void 0, {
|
|
85
|
+
F: __dxlog_file,
|
|
86
|
+
L: 75,
|
|
87
|
+
S: void 0,
|
|
88
|
+
C: (f, a) => f(...a)
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
setStream({
|
|
92
|
+
audio: audio2,
|
|
93
|
+
stream: destination.stream,
|
|
94
|
+
track: destination.stream.getAudioTracks()[0]
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return () => {
|
|
98
|
+
void ctx.dispose();
|
|
99
|
+
};
|
|
100
|
+
}, [
|
|
101
|
+
audioUrl
|
|
102
|
+
]);
|
|
103
|
+
return {
|
|
104
|
+
audio,
|
|
105
|
+
stream,
|
|
106
|
+
track
|
|
107
|
+
};
|
|
108
|
+
};
|
|
19
109
|
|
|
20
|
-
// src/
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
110
|
+
// src/hooks/useAudioTrack.ts
|
|
111
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
112
|
+
var useAudioTrack = (active, constraints) => {
|
|
113
|
+
const [track, setTrack] = useState2();
|
|
114
|
+
useEffect2(() => {
|
|
115
|
+
if (!active) {
|
|
116
|
+
track?.stop();
|
|
117
|
+
setTrack(void 0);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const initAudio = async () => {
|
|
121
|
+
const audio = new Audio();
|
|
122
|
+
audio.srcObject = await navigator.mediaDevices.getUserMedia({
|
|
123
|
+
audio: constraints ?? true
|
|
124
|
+
});
|
|
125
|
+
const [track2] = audio.srcObject.getAudioTracks();
|
|
126
|
+
if (track2) {
|
|
127
|
+
setTrack(track2);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
if (active) {
|
|
131
|
+
void initAudio();
|
|
132
|
+
}
|
|
133
|
+
}, [
|
|
134
|
+
active
|
|
135
|
+
]);
|
|
136
|
+
return track;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/hooks/useQueueModelAdapter.ts
|
|
140
|
+
import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
|
|
26
141
|
|
|
27
142
|
// src/model/model.ts
|
|
28
143
|
import { Text } from "@codemirror/state";
|
|
29
144
|
import { Event } from "@dxos/async";
|
|
30
145
|
import { invariant } from "@dxos/invariant";
|
|
31
|
-
import { log } from "@dxos/log";
|
|
32
|
-
var
|
|
146
|
+
import { log as log2 } from "@dxos/log";
|
|
147
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/model/model.ts";
|
|
33
148
|
var DocumentAdapter = class {
|
|
149
|
+
_view;
|
|
34
150
|
constructor(_view) {
|
|
35
151
|
this._view = _view;
|
|
36
152
|
}
|
|
@@ -39,13 +155,13 @@ var DocumentAdapter = class {
|
|
|
39
155
|
return this._view.state.doc.lines - 1;
|
|
40
156
|
}
|
|
41
157
|
replaceLines(from, remove, insert) {
|
|
42
|
-
|
|
158
|
+
log2("replaceLines", {
|
|
43
159
|
count: this._view.state.doc.lines,
|
|
44
160
|
from,
|
|
45
161
|
remove,
|
|
46
162
|
insert
|
|
47
163
|
}, {
|
|
48
|
-
F:
|
|
164
|
+
F: __dxlog_file2,
|
|
49
165
|
L: 39,
|
|
50
166
|
S: this,
|
|
51
167
|
C: (f, a) => f(...a)
|
|
@@ -67,6 +183,17 @@ var DocumentAdapter = class {
|
|
|
67
183
|
}
|
|
68
184
|
};
|
|
69
185
|
var SerializationModel = class {
|
|
186
|
+
_renderer;
|
|
187
|
+
/** Ordered set of chunks. */
|
|
188
|
+
_chunks;
|
|
189
|
+
/** Map of chunk IDs to their current line counts. */
|
|
190
|
+
_chunkLineCounts;
|
|
191
|
+
/** Map of line numbers to chunk IDs. */
|
|
192
|
+
_lineToChunk;
|
|
193
|
+
/** Track chunk changes since last sync. */
|
|
194
|
+
_changes;
|
|
195
|
+
/** Emits when the document is updated. */
|
|
196
|
+
update;
|
|
70
197
|
constructor(_renderer, initialChunks = []) {
|
|
71
198
|
this._renderer = _renderer;
|
|
72
199
|
this._chunks = [];
|
|
@@ -120,7 +247,7 @@ var SerializationModel = class {
|
|
|
120
247
|
updateChunk(chunk) {
|
|
121
248
|
const index = this._chunks.findIndex((c) => c.id === chunk.id);
|
|
122
249
|
invariant(index !== -1, void 0, {
|
|
123
|
-
F:
|
|
250
|
+
F: __dxlog_file2,
|
|
124
251
|
L: 147,
|
|
125
252
|
S: this,
|
|
126
253
|
A: [
|
|
@@ -140,7 +267,7 @@ var SerializationModel = class {
|
|
|
140
267
|
deleteBlock(id) {
|
|
141
268
|
const index = this._chunks.findIndex((c) => c.id === id);
|
|
142
269
|
invariant(index !== -1, void 0, {
|
|
143
|
-
F:
|
|
270
|
+
F: __dxlog_file2,
|
|
144
271
|
L: 156,
|
|
145
272
|
S: this,
|
|
146
273
|
A: [
|
|
@@ -161,10 +288,10 @@ var SerializationModel = class {
|
|
|
161
288
|
* Syncs the tracked changes with the ChunkDocument.
|
|
162
289
|
*/
|
|
163
290
|
sync(document2) {
|
|
164
|
-
|
|
291
|
+
log2("sync", {
|
|
165
292
|
changes: this._changes
|
|
166
293
|
}, {
|
|
167
|
-
F:
|
|
294
|
+
F: __dxlog_file2,
|
|
168
295
|
L: 167,
|
|
169
296
|
S: this,
|
|
170
297
|
C: (f, a) => f(...a)
|
|
@@ -217,103 +344,269 @@ var SerializationModel = class {
|
|
|
217
344
|
}
|
|
218
345
|
};
|
|
219
346
|
|
|
220
|
-
// src/
|
|
221
|
-
var
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const block = model.getChunkAtLine(line.number)?.blocks[0];
|
|
234
|
-
const timestamp = block?.type === "transcription" && block.started;
|
|
235
|
-
if (timestamp) {
|
|
236
|
-
builder.add(line.from, line.from, new TimestampMarker(line.number, new Date(timestamp), start));
|
|
237
|
-
}
|
|
238
|
-
if (line.to + 1 > view.state.doc.length) {
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
line = view.state.doc.lineAt(line.to + 1);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return builder.finish();
|
|
245
|
-
}
|
|
246
|
-
}),
|
|
247
|
-
// Listen for model updates.
|
|
248
|
-
ViewPlugin.fromClass(class {
|
|
249
|
-
constructor(view) {
|
|
250
|
-
this._initialized = false;
|
|
251
|
-
this._adapter = new DocumentAdapter(view);
|
|
252
|
-
const scroller = view.scrollDOM;
|
|
253
|
-
let isAutoScrolling = false;
|
|
254
|
-
let hasScrolled = false;
|
|
255
|
-
let timeout;
|
|
256
|
-
const scrollToBottom = (smooth = false) => {
|
|
257
|
-
scroller.style.scrollBehavior = smooth ? "smooth" : "";
|
|
258
|
-
scroller.classList.add("cm-hide-scrollbar");
|
|
259
|
-
isAutoScrolling = true;
|
|
260
|
-
clearTimeout(timeout);
|
|
261
|
-
timeout = setTimeout(() => {
|
|
262
|
-
this._controls?.classList.add("opacity-0");
|
|
263
|
-
scroller.classList.remove("cm-hide-scrollbar");
|
|
264
|
-
isAutoScrolling = false;
|
|
265
|
-
}, 500);
|
|
266
|
-
view.dispatch({
|
|
267
|
-
effects: EditorView.scrollIntoView(view.state.doc.length, {
|
|
268
|
-
y: "end"
|
|
269
|
-
})
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
if (renderButton) {
|
|
273
|
-
this._controls = document.createElement("div");
|
|
274
|
-
this._controls.classList.add("cm-controls", "transition-opacity", "duration-300", "opacity-0");
|
|
275
|
-
view.dom.appendChild(this._controls);
|
|
276
|
-
renderButton(this._controls, {
|
|
277
|
-
onClick: () => {
|
|
278
|
-
scrollToBottom(false);
|
|
279
|
-
}
|
|
280
|
-
}, view);
|
|
281
|
-
}
|
|
282
|
-
this._cleanup = combine(addEventListener(view.scrollDOM, "scroll", () => {
|
|
283
|
-
if (!isAutoScrolling) {
|
|
284
|
-
hasScrolled = true;
|
|
285
|
-
this._controls?.classList.remove("opacity-0");
|
|
286
|
-
}
|
|
287
|
-
}), model.update.on(() => {
|
|
288
|
-
const autoScroll = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight === 0 || !hasScrolled;
|
|
289
|
-
model.sync(this._adapter);
|
|
290
|
-
if (autoScroll) {
|
|
291
|
-
scrollToBottom(true);
|
|
292
|
-
}
|
|
293
|
-
}));
|
|
347
|
+
// src/hooks/useQueueModelAdapter.ts
|
|
348
|
+
var useQueueModelAdapter = (renderer, queue, initialChunks = []) => {
|
|
349
|
+
const model = useMemo(() => new SerializationModel(renderer, initialChunks), [
|
|
350
|
+
queue
|
|
351
|
+
]);
|
|
352
|
+
const [loaded, setLoaded] = useState3(false);
|
|
353
|
+
useEffect3(() => {
|
|
354
|
+
if (!queue) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const update = () => {
|
|
358
|
+
for (const block of queue?.objects ?? []) {
|
|
359
|
+
model.appendChunk(block);
|
|
294
360
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
361
|
+
setLoaded(true);
|
|
362
|
+
};
|
|
363
|
+
let i;
|
|
364
|
+
if (queue.isLoading) {
|
|
365
|
+
i = setInterval(() => {
|
|
366
|
+
if (!queue?.isLoading) {
|
|
367
|
+
clearInterval(i);
|
|
368
|
+
update();
|
|
301
369
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
370
|
+
}, 1e3);
|
|
371
|
+
} else {
|
|
372
|
+
update();
|
|
373
|
+
}
|
|
374
|
+
return () => clearTimeout(i);
|
|
375
|
+
}, [
|
|
376
|
+
model,
|
|
377
|
+
queue
|
|
378
|
+
]);
|
|
379
|
+
useEffect3(() => {
|
|
380
|
+
if (!loaded || !queue || model.chunks.length === queue.objects.length) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const chunk = queue.objects.at(-1);
|
|
384
|
+
if (chunk) {
|
|
385
|
+
model.appendChunk(chunk);
|
|
386
|
+
}
|
|
387
|
+
}, [
|
|
388
|
+
model,
|
|
389
|
+
loaded,
|
|
390
|
+
queue?.objects.length
|
|
391
|
+
]);
|
|
392
|
+
return model;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/hooks/useTranscriber.ts
|
|
396
|
+
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
397
|
+
import { useCapabilities } from "@dxos/app-framework";
|
|
398
|
+
|
|
399
|
+
// src/capabilities/index.ts
|
|
400
|
+
import { lazy } from "@dxos/app-framework";
|
|
401
|
+
var IntentResolver = lazy(() => import("./intent-resolver-AOVQ7JI4.mjs"));
|
|
402
|
+
var ReactSurface = lazy(() => import("./react-surface-4MEJMKYQ.mjs"));
|
|
403
|
+
var Transcriber = lazy(() => import("./transcriber-V77CN7XX.mjs"));
|
|
404
|
+
|
|
405
|
+
// src/hooks/useTranscriber.ts
|
|
406
|
+
var useTranscriber = ({ audioStreamTrack, recorderConfig, transcriberConfig, onSegments }) => {
|
|
407
|
+
const [getTranscriber] = useCapabilities(TranscriptionCapabilities.Transcriber);
|
|
408
|
+
const transcriber = useMemo2(() => {
|
|
409
|
+
if (!getTranscriber || !audioStreamTrack || !onSegments) {
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
return getTranscriber({
|
|
413
|
+
audioStreamTrack,
|
|
414
|
+
recorderConfig,
|
|
415
|
+
transcriberConfig,
|
|
416
|
+
onSegments
|
|
417
|
+
});
|
|
418
|
+
}, [
|
|
419
|
+
getTranscriber,
|
|
420
|
+
audioStreamTrack,
|
|
421
|
+
recorderConfig,
|
|
422
|
+
transcriberConfig,
|
|
423
|
+
onSegments
|
|
424
|
+
]);
|
|
425
|
+
useEffect4(() => {
|
|
426
|
+
return () => {
|
|
427
|
+
void transcriber?.close();
|
|
428
|
+
};
|
|
429
|
+
}, [
|
|
430
|
+
transcriber
|
|
431
|
+
]);
|
|
432
|
+
return transcriber;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/hooks/useVoiceInput.ts
|
|
436
|
+
import { useCallback, useEffect as useEffect5, useState as useState4 } from "react";
|
|
437
|
+
import { scheduleMicroTask } from "@dxos/async";
|
|
438
|
+
import { Context as Context2 } from "@dxos/context";
|
|
439
|
+
import { log as log3 } from "@dxos/log";
|
|
440
|
+
import { useSoundEffect } from "@dxos/react-ui-sfx";
|
|
441
|
+
var __dxlog_file3 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useVoiceInput.ts";
|
|
442
|
+
var useVoiceInput = ({ active, onUpdate }) => {
|
|
443
|
+
const soundStart = useSoundEffect("StartRecording");
|
|
444
|
+
const soundStop = useSoundEffect("StopRecording");
|
|
445
|
+
const handleSegments = useCallback(async (segments) => {
|
|
446
|
+
const text = segments.map((str) => str.text.trim().replace(/[^\w\s]/g, "")).join(" ");
|
|
447
|
+
onUpdate(text);
|
|
448
|
+
}, []);
|
|
449
|
+
const [recording, setRecording] = useState4(false);
|
|
450
|
+
const track = useAudioTrack(active);
|
|
451
|
+
const transcriber = useTranscriber({
|
|
452
|
+
audioStreamTrack: track,
|
|
453
|
+
onSegments: handleSegments
|
|
454
|
+
});
|
|
455
|
+
useEffect5(() => {
|
|
456
|
+
const ctx = new Context2(void 0, {
|
|
457
|
+
F: __dxlog_file3,
|
|
458
|
+
L: 44
|
|
459
|
+
});
|
|
460
|
+
scheduleMicroTask(ctx, async () => {
|
|
461
|
+
if (active && transcriber) {
|
|
462
|
+
await transcriber.open();
|
|
463
|
+
log3.info("starting...", void 0, {
|
|
464
|
+
F: __dxlog_file3,
|
|
465
|
+
L: 48,
|
|
466
|
+
S: void 0,
|
|
467
|
+
C: (f, a) => f(...a)
|
|
468
|
+
});
|
|
469
|
+
setRecording(true);
|
|
470
|
+
void soundStart.play();
|
|
471
|
+
transcriber.startChunksRecording();
|
|
472
|
+
} else if (!active && transcriber?.isOpen) {
|
|
473
|
+
transcriber?.stopChunksRecording();
|
|
474
|
+
await transcriber?.close();
|
|
475
|
+
log3.info("stopped", void 0, {
|
|
476
|
+
F: __dxlog_file3,
|
|
477
|
+
L: 55,
|
|
478
|
+
S: void 0,
|
|
479
|
+
C: (f, a) => f(...a)
|
|
480
|
+
});
|
|
481
|
+
setRecording(false);
|
|
482
|
+
void soundStop.play();
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
return () => {
|
|
486
|
+
void ctx.dispose();
|
|
487
|
+
};
|
|
488
|
+
}, [
|
|
489
|
+
active,
|
|
490
|
+
transcriber
|
|
491
|
+
]);
|
|
492
|
+
return {
|
|
493
|
+
recording
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/components/Transcript/Transcript.tsx
|
|
498
|
+
import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
|
|
499
|
+
import React from "react";
|
|
500
|
+
import { IconButton, useThemeContext } from "@dxos/react-ui";
|
|
501
|
+
import { createBasicExtensions, createMarkdownExtensions, createRenderer, createThemeExtensions, decorateMarkdown, editorWidth, preview, useTextEditor } from "@dxos/react-ui-editor";
|
|
502
|
+
import { mx } from "@dxos/react-ui-theme";
|
|
503
|
+
import { isNotFalsy } from "@dxos/util";
|
|
504
|
+
|
|
505
|
+
// src/components/Transcript/transcript-extension.ts
|
|
506
|
+
import { RangeSetBuilder } from "@codemirror/state";
|
|
507
|
+
import { EditorView, GutterMarker, ViewPlugin, gutter } from "@codemirror/view";
|
|
508
|
+
import { format } from "date-fns/format";
|
|
509
|
+
import { intervalToDuration } from "date-fns/intervalToDuration";
|
|
510
|
+
import { addEventListener, combine } from "@dxos/async";
|
|
511
|
+
var transcript = ({ model, started, renderButton }) => {
|
|
512
|
+
return [
|
|
513
|
+
// Show timestamps in the gutter.
|
|
514
|
+
gutter({
|
|
515
|
+
class: "cm-timestamp-gutter",
|
|
516
|
+
lineMarkerChange: (update) => update.docChanged || update.viewportChanged,
|
|
517
|
+
markers: (view) => {
|
|
518
|
+
const start = getStartTime(started, model.chunks[0]);
|
|
519
|
+
const builder = new RangeSetBuilder();
|
|
520
|
+
for (const { from, to } of view.visibleRanges) {
|
|
521
|
+
let line = view.state.doc.lineAt(from);
|
|
522
|
+
while (line.from <= to) {
|
|
523
|
+
const block = model.getChunkAtLine(line.number)?.blocks[0];
|
|
524
|
+
const timestamp = block?._tag === "transcript" && block.started;
|
|
525
|
+
if (timestamp) {
|
|
526
|
+
builder.add(line.from, line.from, new TimestampMarker(line.number, new Date(timestamp), start));
|
|
527
|
+
}
|
|
528
|
+
if (line.to + 1 > view.state.doc.length) {
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
line = view.state.doc.lineAt(line.to + 1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return builder.finish();
|
|
535
|
+
}
|
|
536
|
+
}),
|
|
537
|
+
// Listen for model updates.
|
|
538
|
+
ViewPlugin.fromClass(class {
|
|
539
|
+
_controls;
|
|
540
|
+
_cleanup;
|
|
541
|
+
_adapter;
|
|
542
|
+
_initialized = false;
|
|
543
|
+
constructor(view) {
|
|
544
|
+
this._adapter = new DocumentAdapter(view);
|
|
545
|
+
const scroller = view.scrollDOM;
|
|
546
|
+
let isAutoScrolling = false;
|
|
547
|
+
let hasScrolled = false;
|
|
548
|
+
let timeout;
|
|
549
|
+
const scrollToBottom = (smooth = false) => {
|
|
550
|
+
scroller.style.scrollBehavior = smooth ? "smooth" : "";
|
|
551
|
+
scroller.classList.add("cm-hide-scrollbar");
|
|
552
|
+
isAutoScrolling = true;
|
|
553
|
+
clearTimeout(timeout);
|
|
554
|
+
timeout = setTimeout(() => {
|
|
555
|
+
this._controls?.classList.add("opacity-0");
|
|
556
|
+
scroller.classList.remove("cm-hide-scrollbar");
|
|
557
|
+
isAutoScrolling = false;
|
|
558
|
+
}, 500);
|
|
559
|
+
view.dispatch({
|
|
560
|
+
effects: EditorView.scrollIntoView(view.state.doc.length, {
|
|
561
|
+
y: "end"
|
|
562
|
+
})
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
if (renderButton) {
|
|
566
|
+
this._controls = document.createElement("div");
|
|
567
|
+
this._controls.classList.add("cm-controls", "transition-opacity", "duration-300", "opacity-0");
|
|
568
|
+
view.dom.appendChild(this._controls);
|
|
569
|
+
renderButton(this._controls, {
|
|
570
|
+
onClick: () => {
|
|
571
|
+
scrollToBottom(false);
|
|
572
|
+
}
|
|
573
|
+
}, view);
|
|
574
|
+
}
|
|
575
|
+
this._cleanup = combine(addEventListener(view.scrollDOM, "scroll", () => {
|
|
576
|
+
if (!isAutoScrolling) {
|
|
577
|
+
hasScrolled = true;
|
|
578
|
+
this._controls?.classList.remove("opacity-0");
|
|
579
|
+
}
|
|
580
|
+
}), model.update.on(() => {
|
|
581
|
+
const autoScroll = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight === 0 || !hasScrolled;
|
|
582
|
+
model.sync(this._adapter);
|
|
583
|
+
if (autoScroll) {
|
|
584
|
+
scrollToBottom(true);
|
|
585
|
+
}
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
update(update) {
|
|
589
|
+
if (!this._initialized) {
|
|
590
|
+
this._initialized = true;
|
|
591
|
+
setTimeout(() => {
|
|
592
|
+
model.sync(this._adapter);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
destroy() {
|
|
597
|
+
this._controls?.remove();
|
|
598
|
+
this._cleanup();
|
|
599
|
+
}
|
|
600
|
+
}),
|
|
601
|
+
EditorView.theme({
|
|
602
|
+
".cm-scroller": {
|
|
603
|
+
overflowY: "scroll"
|
|
604
|
+
},
|
|
605
|
+
".cm-hide-scrollbar": {
|
|
606
|
+
scrollbarWidth: "none",
|
|
607
|
+
"-ms-overflow-style": "none"
|
|
608
|
+
},
|
|
609
|
+
".cm-hide-scrollbar::-webkit-scrollbar": {
|
|
317
610
|
display: "none"
|
|
318
611
|
},
|
|
319
612
|
".cm-controls": {
|
|
@@ -338,6 +631,9 @@ var transcript = ({ model, started, renderButton }) => {
|
|
|
338
631
|
];
|
|
339
632
|
};
|
|
340
633
|
var TimestampMarker = class extends GutterMarker {
|
|
634
|
+
_line;
|
|
635
|
+
_timestamp;
|
|
636
|
+
_started;
|
|
341
637
|
constructor(_line, _timestamp, _started) {
|
|
342
638
|
super(), this._line = _line, this._timestamp = _timestamp, this._started = _started;
|
|
343
639
|
}
|
|
@@ -363,7 +659,7 @@ var getStartTime = (started, message) => {
|
|
|
363
659
|
if (started) {
|
|
364
660
|
return started;
|
|
365
661
|
}
|
|
366
|
-
if (message?.blocks[0]?.
|
|
662
|
+
if (message?.blocks[0]?._tag === "transcript" && message.blocks[0].started) {
|
|
367
663
|
return new Date(message.blocks[0].started);
|
|
368
664
|
}
|
|
369
665
|
return void 0;
|
|
@@ -385,13 +681,13 @@ var formatTimestamp = (timestamp, relative) => {
|
|
|
385
681
|
var renderMarkdown = (identities) => (message, index, debug = false) => {
|
|
386
682
|
if (message.sender.role === "assistant") {
|
|
387
683
|
return [
|
|
388
|
-
message.blocks.find((block) => block.
|
|
684
|
+
message.blocks.find((block) => block._tag === "transcript")?.text ?? "",
|
|
389
685
|
""
|
|
390
686
|
];
|
|
391
687
|
}
|
|
392
688
|
const identity = identities.find((identity2) => identity2.did === message.sender.identityDid);
|
|
393
689
|
const name = identity?.profile?.displayName ?? message.sender.contact?.target?.fullName ?? message.sender.name ?? message.sender.email ?? message.sender.identityDid;
|
|
394
|
-
const blocks = message.blocks.filter((block) => block.
|
|
690
|
+
const blocks = message.blocks.filter((block) => block._tag === "transcript");
|
|
395
691
|
return [
|
|
396
692
|
`###### ${name}` + (debug ? ` [${index}]:${message.id}` : ""),
|
|
397
693
|
blocks.map((block) => block.text.trim()).join(" "),
|
|
@@ -407,7 +703,8 @@ var Transcript = ({ classNames, transcript: object, space, model, attendableId,
|
|
|
407
703
|
extensions: [
|
|
408
704
|
createBasicExtensions({
|
|
409
705
|
readOnly: true,
|
|
410
|
-
lineWrapping: true
|
|
706
|
+
lineWrapping: true,
|
|
707
|
+
search: true
|
|
411
708
|
}),
|
|
412
709
|
createMarkdownExtensions({
|
|
413
710
|
themeMode
|
|
@@ -443,286 +740,8 @@ var Transcript = ({ classNames, transcript: object, space, model, attendableId,
|
|
|
443
740
|
}
|
|
444
741
|
};
|
|
445
742
|
|
|
446
|
-
// src/hooks/useAudioFile.ts
|
|
447
|
-
import { useEffect, useState } from "react";
|
|
448
|
-
import { scheduleTask } from "@dxos/async";
|
|
449
|
-
import { Context } from "@dxos/context";
|
|
450
|
-
import { log as log2 } from "@dxos/log";
|
|
451
|
-
var __dxlog_file2 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useAudioFile.ts";
|
|
452
|
-
var useAudioFile = (audioUrl, constraints) => {
|
|
453
|
-
const [{ audio, stream, track }, setStream] = useState({});
|
|
454
|
-
useEffect(() => {
|
|
455
|
-
const ctx = new Context(void 0, {
|
|
456
|
-
F: __dxlog_file2,
|
|
457
|
-
L: 20
|
|
458
|
-
});
|
|
459
|
-
scheduleTask(ctx, async () => {
|
|
460
|
-
const response = await fetch(audioUrl);
|
|
461
|
-
const blob = await response.blob();
|
|
462
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
463
|
-
ctx.onDispose(() => {
|
|
464
|
-
URL.revokeObjectURL(objectUrl);
|
|
465
|
-
});
|
|
466
|
-
const audio2 = new Audio();
|
|
467
|
-
audio2.src = objectUrl;
|
|
468
|
-
await new Promise((resolve, reject) => {
|
|
469
|
-
audio2.addEventListener("error", (err) => {
|
|
470
|
-
log2.error("error", {
|
|
471
|
-
err
|
|
472
|
-
}, {
|
|
473
|
-
F: __dxlog_file2,
|
|
474
|
-
L: 37,
|
|
475
|
-
S: void 0,
|
|
476
|
-
C: (f, a) => f(...a)
|
|
477
|
-
});
|
|
478
|
-
reject(err);
|
|
479
|
-
}, {
|
|
480
|
-
once: true
|
|
481
|
-
});
|
|
482
|
-
audio2.addEventListener("canplay", async () => {
|
|
483
|
-
log2.info("starting...", void 0, {
|
|
484
|
-
F: __dxlog_file2,
|
|
485
|
-
L: 45,
|
|
486
|
-
S: void 0,
|
|
487
|
-
C: (f, a) => f(...a)
|
|
488
|
-
});
|
|
489
|
-
try {
|
|
490
|
-
await audio2.play();
|
|
491
|
-
resolve();
|
|
492
|
-
} catch (playError) {
|
|
493
|
-
log2.error("Play failed", {
|
|
494
|
-
playError
|
|
495
|
-
}, {
|
|
496
|
-
F: __dxlog_file2,
|
|
497
|
-
L: 51,
|
|
498
|
-
S: void 0,
|
|
499
|
-
C: (f, a) => f(...a)
|
|
500
|
-
});
|
|
501
|
-
resolve();
|
|
502
|
-
}
|
|
503
|
-
}, {
|
|
504
|
-
once: true
|
|
505
|
-
});
|
|
506
|
-
audio2.load();
|
|
507
|
-
});
|
|
508
|
-
const audioCtx = new AudioContext();
|
|
509
|
-
if (audioCtx.state === "suspended") {
|
|
510
|
-
await audioCtx.resume();
|
|
511
|
-
}
|
|
512
|
-
const destination = audioCtx.createMediaStreamDestination();
|
|
513
|
-
destination.channelCount = 1;
|
|
514
|
-
const source = audioCtx.createMediaElementSource(audio2);
|
|
515
|
-
source.connect(destination);
|
|
516
|
-
const track2 = destination.stream.getAudioTracks()[0];
|
|
517
|
-
if (constraints) {
|
|
518
|
-
await track2.applyConstraints(constraints).catch((err) => log2.catch(err, void 0, {
|
|
519
|
-
F: __dxlog_file2,
|
|
520
|
-
L: 75,
|
|
521
|
-
S: void 0,
|
|
522
|
-
C: (f, a) => f(...a)
|
|
523
|
-
}));
|
|
524
|
-
}
|
|
525
|
-
setStream({
|
|
526
|
-
audio: audio2,
|
|
527
|
-
stream: destination.stream,
|
|
528
|
-
track: destination.stream.getAudioTracks()[0]
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
return () => {
|
|
532
|
-
void ctx.dispose();
|
|
533
|
-
};
|
|
534
|
-
}, [
|
|
535
|
-
audioUrl
|
|
536
|
-
]);
|
|
537
|
-
return {
|
|
538
|
-
audio,
|
|
539
|
-
stream,
|
|
540
|
-
track
|
|
541
|
-
};
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
// src/hooks/useAudioTrack.ts
|
|
545
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
546
|
-
var useAudioTrack = (active, constraints) => {
|
|
547
|
-
const [track, setTrack] = useState2();
|
|
548
|
-
useEffect2(() => {
|
|
549
|
-
if (!active) {
|
|
550
|
-
track?.stop();
|
|
551
|
-
setTrack(void 0);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
const initAudio = async () => {
|
|
555
|
-
const audio = new Audio();
|
|
556
|
-
audio.srcObject = await navigator.mediaDevices.getUserMedia({
|
|
557
|
-
audio: constraints ?? true
|
|
558
|
-
});
|
|
559
|
-
const [track2] = audio.srcObject.getAudioTracks();
|
|
560
|
-
if (track2) {
|
|
561
|
-
setTrack(track2);
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
if (active) {
|
|
565
|
-
void initAudio();
|
|
566
|
-
}
|
|
567
|
-
}, [
|
|
568
|
-
active
|
|
569
|
-
]);
|
|
570
|
-
return track;
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
// src/hooks/useQueueModelAdapter.ts
|
|
574
|
-
import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
|
|
575
|
-
var useQueueModelAdapter = (renderer, queue, initialChunks = []) => {
|
|
576
|
-
const model = useMemo(() => new SerializationModel(renderer, initialChunks), [
|
|
577
|
-
queue
|
|
578
|
-
]);
|
|
579
|
-
const [loaded, setLoaded] = useState3(false);
|
|
580
|
-
useEffect3(() => {
|
|
581
|
-
if (!queue) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
const update = () => {
|
|
585
|
-
for (const block of queue?.objects ?? []) {
|
|
586
|
-
model.appendChunk(block);
|
|
587
|
-
}
|
|
588
|
-
setLoaded(true);
|
|
589
|
-
};
|
|
590
|
-
let i;
|
|
591
|
-
if (queue.isLoading) {
|
|
592
|
-
i = setInterval(() => {
|
|
593
|
-
if (!queue?.isLoading) {
|
|
594
|
-
clearInterval(i);
|
|
595
|
-
update();
|
|
596
|
-
}
|
|
597
|
-
}, 1e3);
|
|
598
|
-
} else {
|
|
599
|
-
update();
|
|
600
|
-
}
|
|
601
|
-
return () => clearTimeout(i);
|
|
602
|
-
}, [
|
|
603
|
-
model,
|
|
604
|
-
queue
|
|
605
|
-
]);
|
|
606
|
-
useEffect3(() => {
|
|
607
|
-
if (!loaded || !queue || model.chunks.length === queue.objects.length) {
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
const chunk = queue.objects.at(-1);
|
|
611
|
-
if (chunk) {
|
|
612
|
-
model.appendChunk(chunk);
|
|
613
|
-
}
|
|
614
|
-
}, [
|
|
615
|
-
model,
|
|
616
|
-
loaded,
|
|
617
|
-
queue?.objects.length
|
|
618
|
-
]);
|
|
619
|
-
return model;
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
// src/hooks/useTranscriber.ts
|
|
623
|
-
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
624
|
-
import { useCapabilities } from "@dxos/app-framework";
|
|
625
|
-
|
|
626
|
-
// src/capabilities/index.ts
|
|
627
|
-
import { lazy } from "@dxos/app-framework";
|
|
628
|
-
var IntentResolver = lazy(() => import("./intent-resolver-3O33MMP5.mjs"));
|
|
629
|
-
var ReactSurface = lazy(() => import("./react-surface-FJTMNF3K.mjs"));
|
|
630
|
-
var Transcriber = lazy(() => import("./transcriber-YVOTUBKQ.mjs"));
|
|
631
|
-
|
|
632
|
-
// src/hooks/useTranscriber.ts
|
|
633
|
-
var useTranscriber = ({ audioStreamTrack, recorderConfig, transcriberConfig, onSegments }) => {
|
|
634
|
-
const [getTranscriber] = useCapabilities(TranscriptionCapabilities.Transcriber);
|
|
635
|
-
const transcriber = useMemo2(() => {
|
|
636
|
-
if (!getTranscriber || !audioStreamTrack || !onSegments) {
|
|
637
|
-
return void 0;
|
|
638
|
-
}
|
|
639
|
-
return getTranscriber({
|
|
640
|
-
audioStreamTrack,
|
|
641
|
-
recorderConfig,
|
|
642
|
-
transcriberConfig,
|
|
643
|
-
onSegments
|
|
644
|
-
});
|
|
645
|
-
}, [
|
|
646
|
-
getTranscriber,
|
|
647
|
-
audioStreamTrack,
|
|
648
|
-
recorderConfig,
|
|
649
|
-
transcriberConfig,
|
|
650
|
-
onSegments
|
|
651
|
-
]);
|
|
652
|
-
useEffect4(() => {
|
|
653
|
-
return () => {
|
|
654
|
-
void transcriber?.close();
|
|
655
|
-
};
|
|
656
|
-
}, [
|
|
657
|
-
transcriber
|
|
658
|
-
]);
|
|
659
|
-
return transcriber;
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
// src/hooks/useVoiceInput.ts
|
|
663
|
-
import { useState as useState4, useEffect as useEffect5, useCallback } from "react";
|
|
664
|
-
import { scheduleMicroTask } from "@dxos/async";
|
|
665
|
-
import { Context as Context2 } from "@dxos/context";
|
|
666
|
-
import { log as log3 } from "@dxos/log";
|
|
667
|
-
import { useSoundEffect } from "@dxos/react-ui-sfx";
|
|
668
|
-
var __dxlog_file3 = "/__w/dxos/dxos/packages/plugins/plugin-transcription/src/hooks/useVoiceInput.ts";
|
|
669
|
-
var useVoiceInput = ({ active, onUpdate }) => {
|
|
670
|
-
const soundStart = useSoundEffect("StartRecording");
|
|
671
|
-
const soundStop = useSoundEffect("StopRecording");
|
|
672
|
-
const handleSegments = useCallback(async (segments) => {
|
|
673
|
-
const text = segments.map((str) => str.text.trim().replace(/[^\w\s]/g, "")).join(" ");
|
|
674
|
-
onUpdate(text);
|
|
675
|
-
}, []);
|
|
676
|
-
const [recording, setRecording] = useState4(false);
|
|
677
|
-
const track = useAudioTrack(active);
|
|
678
|
-
const transcriber = useTranscriber({
|
|
679
|
-
audioStreamTrack: track,
|
|
680
|
-
onSegments: handleSegments
|
|
681
|
-
});
|
|
682
|
-
useEffect5(() => {
|
|
683
|
-
const ctx = new Context2(void 0, {
|
|
684
|
-
F: __dxlog_file3,
|
|
685
|
-
L: 43
|
|
686
|
-
});
|
|
687
|
-
scheduleMicroTask(ctx, async () => {
|
|
688
|
-
if (active && transcriber) {
|
|
689
|
-
await transcriber.open();
|
|
690
|
-
log3.info("starting...", void 0, {
|
|
691
|
-
F: __dxlog_file3,
|
|
692
|
-
L: 47,
|
|
693
|
-
S: void 0,
|
|
694
|
-
C: (f, a) => f(...a)
|
|
695
|
-
});
|
|
696
|
-
setRecording(true);
|
|
697
|
-
void soundStart.play();
|
|
698
|
-
transcriber.startChunksRecording();
|
|
699
|
-
} else if (!active && transcriber?.isOpen) {
|
|
700
|
-
transcriber?.stopChunksRecording();
|
|
701
|
-
await transcriber?.close();
|
|
702
|
-
log3.info("stopped", void 0, {
|
|
703
|
-
F: __dxlog_file3,
|
|
704
|
-
L: 54,
|
|
705
|
-
S: void 0,
|
|
706
|
-
C: (f, a) => f(...a)
|
|
707
|
-
});
|
|
708
|
-
setRecording(false);
|
|
709
|
-
void soundStop.play();
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
return () => {
|
|
713
|
-
void ctx.dispose();
|
|
714
|
-
};
|
|
715
|
-
}, [
|
|
716
|
-
active,
|
|
717
|
-
transcriber
|
|
718
|
-
]);
|
|
719
|
-
return {
|
|
720
|
-
recording
|
|
721
|
-
};
|
|
722
|
-
};
|
|
723
|
-
|
|
724
743
|
// src/components/TranscriptContainer.tsx
|
|
725
|
-
var TranscriptionContainer = ({
|
|
744
|
+
var TranscriptionContainer = ({ transcript: transcript2 }) => {
|
|
726
745
|
var _effect = _useSignals2();
|
|
727
746
|
try {
|
|
728
747
|
const attendableId = fullyQualifiedId(transcript2);
|
|
@@ -733,7 +752,6 @@ var TranscriptionContainer = ({ role, transcript: transcript2 }) => {
|
|
|
733
752
|
});
|
|
734
753
|
const model = useQueueModelAdapter(renderMarkdown(members), queue);
|
|
735
754
|
return /* @__PURE__ */ React2.createElement(StackItem.Content, {
|
|
736
|
-
role,
|
|
737
755
|
classNames: "container-max-width pli-2"
|
|
738
756
|
}, /* @__PURE__ */ React2.createElement(Transcript, {
|
|
739
757
|
attendableId,
|
|
@@ -761,4 +779,4 @@ export {
|
|
|
761
779
|
ReactSurface,
|
|
762
780
|
Transcriber
|
|
763
781
|
};
|
|
764
|
-
//# sourceMappingURL=chunk-
|
|
782
|
+
//# sourceMappingURL=chunk-BJWYA2BU.mjs.map
|