@dxos/plugin-transcription 0.8.1 → 0.8.2-main.10c050d
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-PFKLKRPQ.mjs +11 -0
- package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-4KCUYW2F.mjs} +139 -95
- package/dist/lib/browser/chunk-4KCUYW2F.mjs.map +7 -0
- package/dist/lib/browser/{chunk-6ZR5MDK3.mjs → chunk-GOF7MJNV.mjs} +3 -3
- package/dist/lib/browser/{chunk-6ZR5MDK3.mjs.map → chunk-GOF7MJNV.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-K7FOYAXO.mjs → chunk-IGEKO7ZU.mjs} +2 -2
- package/dist/lib/browser/chunk-NODIXNQP.mjs +54 -0
- package/dist/lib/browser/chunk-NODIXNQP.mjs.map +7 -0
- package/dist/lib/browser/chunk-TJPJBE45.mjs +764 -0
- package/dist/lib/browser/chunk-TJPJBE45.mjs.map +7 -0
- package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-W2KNYJD6.mjs} +4 -4
- package/dist/lib/browser/chunk-W2KNYJD6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +30 -216
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-I2LJSQDR.mjs} +7 -11
- package/dist/lib/browser/intent-resolver-I2LJSQDR.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-ID2MLJHZ.mjs} +11 -9
- package/dist/lib/browser/react-surface-ID2MLJHZ.mjs.map +7 -0
- package/dist/lib/browser/transcriber-NA7CIFIT.mjs +56 -0
- package/dist/lib/browser/transcriber-NA7CIFIT.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +4 -8
- package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-LHMC7553.cjs} +9 -8
- package/dist/lib/node/TranscriptContainer-LHMC7553.cjs.map +7 -0
- package/dist/lib/node/chunk-3GS5UNO5.cjs +778 -0
- package/dist/lib/node/chunk-3GS5UNO5.cjs.map +7 -0
- package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-5J3FBCON.cjs} +5 -5
- package/dist/lib/node/{chunk-NVZWGFJL.cjs → chunk-AFUZI63A.cjs} +6 -6
- package/dist/lib/node/{chunk-NVZWGFJL.cjs.map → chunk-AFUZI63A.cjs.map} +1 -1
- package/dist/lib/node/chunk-EIMUQYCZ.cjs +74 -0
- package/dist/lib/node/chunk-EIMUQYCZ.cjs.map +7 -0
- package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-FKDJNJ57.cjs} +8 -8
- package/dist/lib/node/chunk-FKDJNJ57.cjs.map +7 -0
- package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-ZFTXJZXH.cjs} +138 -95
- package/dist/lib/node/chunk-ZFTXJZXH.cjs.map +7 -0
- package/dist/lib/node/index.cjs +59 -251
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-LLMGF5I5.cjs} +10 -11
- package/dist/lib/node/intent-resolver-LLMGF5I5.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-SEQLEL4K.cjs} +13 -11
- package/dist/lib/node/react-surface-SEQLEL4K.cjs.map +7 -0
- package/dist/lib/node/transcriber-RKKN7LDU.cjs +68 -0
- package/dist/lib/node/transcriber-RKKN7LDU.cjs.map +7 -0
- package/dist/lib/node/types/index.cjs +7 -11
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-DLNRFVTA.mjs} +4 -3
- package/dist/lib/node-esm/chunk-BRPFVRAD.mjs +55 -0
- package/dist/lib/node-esm/chunk-BRPFVRAD.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-EFIWBQDF.mjs} +139 -95
- package/dist/lib/node-esm/chunk-EFIWBQDF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs → chunk-KUWWHMOA.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-MFJPEIVB.mjs} +4 -4
- package/dist/lib/node-esm/chunk-MFJPEIVB.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PR7EYODX.mjs +765 -0
- package/dist/lib/node-esm/chunk-PR7EYODX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-X4DDJIVL.mjs → chunk-TXMDLLBR.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-X4DDJIVL.mjs.map → chunk-TXMDLLBR.mjs.map} +1 -1
- package/dist/lib/node-esm/index.mjs +30 -216
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-WOZKOEBW.mjs} +7 -11
- package/dist/lib/node-esm/intent-resolver-WOZKOEBW.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-7MXUUTAL.mjs} +11 -9
- package/dist/lib/node-esm/react-surface-7MXUUTAL.mjs.map +7 -0
- package/dist/lib/node-esm/transcriber-5WSI6DYK.mjs +57 -0
- package/dist/lib/node-esm/transcriber-5WSI6DYK.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +4 -8
- package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +8 -10
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +2 -183
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/transcriber.d.ts +2 -1
- package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.d.ts +14 -4
- package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts +19 -4
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +27 -0
- package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -0
- package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
- package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
- package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
- package/dist/types/src/components/stories/FileTranscription.stories.d.ts +17 -0
- package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +1 -0
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +10 -0
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +1 -0
- package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts +19 -0
- package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +1 -0
- package/dist/types/src/components/stories/TranscriptionStory.d.ts +11 -0
- package/dist/types/src/components/stories/TranscriptionStory.d.ts.map +1 -0
- package/dist/types/src/components/stories/useIsSpeaking.d.ts +3 -0
- package/dist/types/src/components/stories/useIsSpeaking.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAudioFile.d.ts +1 -1
- package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
- package/dist/types/src/hooks/useAudioTrack.d.ts +1 -1
- package/dist/types/src/hooks/useAudioTrack.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
- package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
- package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
- package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
- package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
- package/dist/types/src/model/index.d.ts +2 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/model.d.ts +68 -0
- package/dist/types/src/model/model.d.ts.map +1 -0
- package/dist/types/src/model/model.test.d.ts +2 -0
- package/dist/types/src/model/model.test.d.ts.map +1 -0
- package/dist/types/src/segments-normalization/index.d.ts +4 -0
- package/dist/types/src/segments-normalization/index.d.ts.map +1 -0
- package/dist/types/src/segments-normalization/message-normalizer.d.ts +26 -0
- package/dist/types/src/segments-normalization/message-normalizer.d.ts.map +1 -0
- package/dist/types/src/segments-normalization/normalization.d.ts +520 -0
- package/dist/types/src/segments-normalization/normalization.d.ts.map +1 -0
- package/dist/types/src/segments-normalization/normalization.test.d.ts +2 -0
- package/dist/types/src/segments-normalization/normalization.test.d.ts.map +1 -0
- package/dist/types/src/segments-normalization/utils.d.ts +3 -0
- package/dist/types/src/segments-normalization/utils.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/testing.d.ts +40 -0
- package/dist/types/src/testing/testing.d.ts.map +1 -0
- package/dist/types/src/transcriber/media-stream-recorder.d.ts +8 -5
- package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
- package/dist/types/src/transcriber/transcriber.d.ts +6 -2
- package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
- package/dist/types/src/transcriber/transcription-manager.d.ts +19 -15
- package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -20
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +23 -40
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +15 -8
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util/array.d.ts.map +1 -1
- package/dist/types/src/util/get-time-str.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/monitor-audio-level.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +62 -41
- package/src/TranscriptionPlugin.tsx +13 -18
- package/src/capabilities/capabilities.ts +15 -8
- package/src/capabilities/index.ts +0 -2
- package/src/capabilities/intent-resolver.ts +7 -8
- package/src/capabilities/react-surface.tsx +5 -4
- package/src/capabilities/transcriber.ts +34 -6
- package/src/components/Transcript/Transcript.stories.tsx +201 -43
- package/src/components/Transcript/Transcript.tsx +83 -208
- package/src/components/Transcript/deprecated/OldTranscript.tsx +260 -0
- package/src/components/Transcript/transcript-extension.ts +236 -0
- package/src/components/TranscriptContainer.tsx +11 -6
- package/src/components/stories/FileTranscription.stories.tsx +240 -0
- package/src/components/stories/KeyWordRecognition.stories.tsx +128 -0
- package/src/components/stories/MicrophoneTranscription.stories.tsx +274 -0
- package/src/components/stories/TranscriptionStory.tsx +41 -0
- package/src/components/stories/useIsSpeaking.tsx +40 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAudioFile.ts +39 -9
- package/src/hooks/useAudioTrack.ts +2 -2
- package/src/hooks/useQueueModelAdapter.ts +65 -0
- package/src/hooks/useTranscriber.ts +4 -2
- package/src/meta.ts +2 -2
- package/src/model/index.ts +5 -0
- package/src/model/model.test.ts +169 -0
- package/src/model/model.ts +229 -0
- package/src/segments-normalization/index.ts +7 -0
- package/src/segments-normalization/message-normalizer.ts +108 -0
- package/src/segments-normalization/normalization.test.ts +142 -0
- package/src/segments-normalization/normalization.ts +103 -0
- package/src/segments-normalization/utils.ts +9 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/testing.ts +216 -0
- package/src/transcriber/media-stream-recorder.ts +14 -7
- package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +7 -7
- package/src/transcriber/transcriber.ts +33 -20
- package/src/transcriber/transcription-manager.ts +87 -71
- package/src/translations.ts +2 -3
- package/src/types/schema.ts +11 -33
- package/src/types/types.ts +8 -12
- package/src/util/index.ts +0 -1
- package/dist/lib/browser/TranscriptContainer-JQ3HFHLQ.mjs +0 -10
- package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs +0 -191
- package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
- package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
- package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
- package/dist/lib/browser/chunk-GU2BNYKZ.mjs +0 -35
- package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
- package/dist/lib/browser/chunk-OGNXNGJB.mjs +0 -73
- package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
- package/dist/lib/browser/chunk-YCZISX63.mjs.map +0 -7
- package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
- package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs +0 -18
- package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
- package/dist/lib/browser/react-surface-FQUHU5G7.mjs.map +0 -7
- package/dist/lib/browser/transcriber-LGMIWDYI.mjs +0 -36
- package/dist/lib/browser/transcriber-LGMIWDYI.mjs.map +0 -7
- package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
- package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs +0 -200
- package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
- package/dist/lib/node/chunk-3KHUSQNW.cjs +0 -55
- package/dist/lib/node/chunk-3KHUSQNW.cjs.map +0 -7
- package/dist/lib/node/chunk-CZMLKKWT.cjs.map +0 -7
- package/dist/lib/node/chunk-FCCJALOT.cjs.map +0 -7
- package/dist/lib/node/chunk-P4VVD6ZO.cjs +0 -95
- package/dist/lib/node/chunk-P4VVD6ZO.cjs.map +0 -7
- package/dist/lib/node/chunk-UOFHT4GZ.cjs +0 -280
- package/dist/lib/node/chunk-UOFHT4GZ.cjs.map +0 -7
- package/dist/lib/node/intent-resolver-WMMCOUEH.cjs.map +0 -7
- package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs +0 -34
- package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs.map +0 -7
- package/dist/lib/node/react-surface-WI2MGWCC.cjs.map +0 -7
- package/dist/lib/node/transcriber-QG5WPKX4.cjs +0 -49
- package/dist/lib/node/transcriber-QG5WPKX4.cjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs +0 -192
- package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
- package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GIO6J6FE.mjs +0 -74
- package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HKATMD34.mjs +0 -37
- package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-YQYBLFNI.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
- package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs +0 -19
- package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-PCH6NTVB.mjs.map +0 -7
- package/dist/lib/node-esm/transcriber-TMPYZ5L4.mjs +0 -37
- package/dist/lib/node-esm/transcriber-TMPYZ5L4.mjs.map +0 -7
- package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -181
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
- package/dist/types/src/capabilities/meeting-transcription-state.d.ts +0 -6
- package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +0 -1
- package/dist/types/src/components/transcription.stories.d.ts +0 -15
- package/dist/types/src/components/transcription.stories.d.ts.map +0 -1
- package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
- package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
- package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
- package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
- package/src/capabilities/app-graph-builder.ts +0 -152
- package/src/capabilities/meeting-transcription-state.ts +0 -16
- package/src/components/transcription.stories.tsx +0 -196
- package/src/util/random-queue-dxn.ts +0 -9
- /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-PFKLKRPQ.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-K7FOYAXO.mjs.map → chunk-IGEKO7ZU.mjs.map} +0 -0
- /package/dist/lib/node/{chunk-LP4FG423.cjs.map → chunk-5J3FBCON.cjs.map} +0 -0
- /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-DLNRFVTA.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs.map → chunk-KUWWHMOA.mjs.map} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { DEFAULT_EDGE_MODEL } from '@dxos/ai';
|
|
8
|
+
import { AISession } from '@dxos/assistant';
|
|
9
|
+
import { AiService, defineFunction } from '@dxos/functions';
|
|
10
|
+
import { ObjectId } from '@dxos/keys';
|
|
11
|
+
import { log } from '@dxos/log';
|
|
12
|
+
import { DataType } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
const MessageWithRangeId = Schema.extend(
|
|
15
|
+
DataType.Message,
|
|
16
|
+
Schema.Struct({
|
|
17
|
+
// TODO(mykola): Move to meta in DataType.Message. Use `DataType.Message` instead.
|
|
18
|
+
rangeId: Schema.optional(Schema.Array(Schema.String)).annotations({
|
|
19
|
+
description: 'The IDs of the messages that contain the sentences.',
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export interface MessageWithRangeId extends Schema.Schema.Type<typeof MessageWithRangeId> {}
|
|
25
|
+
|
|
26
|
+
export const NormalizationInput = Schema.Struct({
|
|
27
|
+
// TODO(mykola): Move to meta in DataType.Message. Use `DataType.Message` instead.
|
|
28
|
+
messages: Schema.Array(MessageWithRangeId).annotations({
|
|
29
|
+
description: 'Messages to normalize into sentences.',
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const NormalizationOutput = Schema.Struct({
|
|
34
|
+
sentences: Schema.Array(MessageWithRangeId.pipe(Schema.mutable)).pipe(Schema.mutable).annotations({
|
|
35
|
+
description: 'The sentences of the transcript.',
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const prompt = `
|
|
40
|
+
You are observing a real-time transcript of a single person speaking.
|
|
41
|
+
The transcription is delivered in chunks of 10 seconds or less. As a result, individual sentences may be split across multiple messages, or multiple sentences may appear within a single message. Additionally, because this is real-time transcription, punctuation and capitalization may be incorrect or missing.
|
|
42
|
+
|
|
43
|
+
# Task Description:
|
|
44
|
+
- Your task is to detect and reconstruct broken or incomplete sentences by merging segments into coherent, grammatically correct sentences where appropriate.
|
|
45
|
+
|
|
46
|
+
# Input Format:
|
|
47
|
+
- The input is an array of messages that contain transcription blocks.
|
|
48
|
+
- Each message is a DataType.Message, as described in the output format.
|
|
49
|
+
|
|
50
|
+
# Output Format:
|
|
51
|
+
- You have been provided with the tool that defines the output format; make sure to query it.
|
|
52
|
+
- Do not output anything other than the expected format.
|
|
53
|
+
|
|
54
|
+
# Segment Handling Rules:
|
|
55
|
+
- Sort messages by timestamp.
|
|
56
|
+
- Leave ID of the first message in a sentence.
|
|
57
|
+
- Each divided sentence should be added to the output as a separate message or block within existing messages, preserving the original order.
|
|
58
|
+
- If one message contains multiple sentences, you should not split them.
|
|
59
|
+
- The last sentence may be incomplete; it should still be output as a separate block or message.
|
|
60
|
+
- Keep track of the original timestamps of the messages and blocks and, use the timestamp of the first message/block of a sentence as the 'started' field of the merged message.
|
|
61
|
+
- The 'rangeId' field of the merged message should include 'messageId'-s and 'rangeId'-s of all messages that have been used to construct this message.
|
|
62
|
+
|
|
63
|
+
# Punctuation and Capitalization:
|
|
64
|
+
- Do not rely on the original punctuation and capitalization—they may be incorrect.
|
|
65
|
+
- Use logical reasoning to apply appropriate punctuation (e.g., period, comma, question mark, exclamation mark) and capitalization.
|
|
66
|
+
|
|
67
|
+
# Restrictions:
|
|
68
|
+
- Do not alter the order of the original messages; maintain the natural flow of speech.
|
|
69
|
+
- Do not interpret or infer meaning beyond reconstructing sentences.
|
|
70
|
+
- Do not add or remove any words or phrases.
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
export const sentenceNormalization = defineFunction({
|
|
74
|
+
description: 'Post process of transcription for sentence normalization',
|
|
75
|
+
inputSchema: NormalizationInput,
|
|
76
|
+
outputSchema: NormalizationOutput,
|
|
77
|
+
handler: async ({ data: { messages }, context }) => {
|
|
78
|
+
log.info('input', { messages });
|
|
79
|
+
const ai = context.getService(AiService);
|
|
80
|
+
const session = new AISession({ operationModel: 'configured' });
|
|
81
|
+
|
|
82
|
+
const response = await session.runStructured(NormalizationOutput, {
|
|
83
|
+
generationOptions: { model: DEFAULT_EDGE_MODEL },
|
|
84
|
+
client: ai.client,
|
|
85
|
+
tools: [],
|
|
86
|
+
artifacts: [],
|
|
87
|
+
history: [
|
|
88
|
+
{
|
|
89
|
+
id: ObjectId.random(),
|
|
90
|
+
role: 'user',
|
|
91
|
+
content: messages.map((message) => ({ type: 'text', text: JSON.stringify(message) })),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
prompt,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
response.sentences.forEach((sentence) => {
|
|
98
|
+
sentence.id = ObjectId.random();
|
|
99
|
+
});
|
|
100
|
+
log.info('response', { response });
|
|
101
|
+
return response;
|
|
102
|
+
},
|
|
103
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { AIServiceEdgeClient } from '@dxos/ai';
|
|
9
|
+
import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
|
|
10
|
+
import { extractionAnthropicFn, processTranscriptMessage } from '@dxos/assistant';
|
|
11
|
+
import { scheduleTaskInterval } from '@dxos/async';
|
|
12
|
+
import { Filter, type Queue } from '@dxos/client/echo';
|
|
13
|
+
import { Context } from '@dxos/context';
|
|
14
|
+
import { Type } from '@dxos/echo';
|
|
15
|
+
import { create, createQueueDxn, ObjectId } from '@dxos/echo-schema';
|
|
16
|
+
import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
|
|
17
|
+
import { IdentityDid } from '@dxos/keys';
|
|
18
|
+
import { log } from '@dxos/log';
|
|
19
|
+
import { faker } from '@dxos/random';
|
|
20
|
+
import { live, makeRef, useQueue, type Space } from '@dxos/react-client/echo';
|
|
21
|
+
import { DataType } from '@dxos/schema';
|
|
22
|
+
import { Testing, seedTestData } from '@dxos/schema/testing';
|
|
23
|
+
|
|
24
|
+
// TODO(burdon): Reconcile with plugin-markdown. Move to @dxos/schema/testing.
|
|
25
|
+
export const TestItem = Schema.Struct({
|
|
26
|
+
title: Schema.String.annotations({
|
|
27
|
+
title: 'Title',
|
|
28
|
+
description: 'Product title',
|
|
29
|
+
}),
|
|
30
|
+
description: Schema.String.annotations({
|
|
31
|
+
title: 'Description',
|
|
32
|
+
description: 'Product description',
|
|
33
|
+
}),
|
|
34
|
+
}).pipe(
|
|
35
|
+
Type.Obj({
|
|
36
|
+
typename: 'dxos.org/type/Test',
|
|
37
|
+
version: '0.1.0',
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// TODO(wittjosiah): Make builder generic and reuse for all message types.
|
|
42
|
+
abstract class AbstractMessageBuilder {
|
|
43
|
+
abstract createMessage(numSegments?: number): Promise<DataType.Message>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generator of transcript messages.
|
|
48
|
+
*/
|
|
49
|
+
export class MessageBuilder extends AbstractMessageBuilder {
|
|
50
|
+
static readonly singleton = new MessageBuilder();
|
|
51
|
+
|
|
52
|
+
users = Array.from({ length: 5 }, () => ({
|
|
53
|
+
identityDid: IdentityDid.random().toString(),
|
|
54
|
+
name: faker.person.fullName(),
|
|
55
|
+
email: faker.internet.email(),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
start = new Date(Date.now() - 24 * 60 * 60 * 10_000);
|
|
59
|
+
|
|
60
|
+
constructor(private readonly _space?: Space) {
|
|
61
|
+
super();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override async createMessage(numSegments = 1): Promise<DataType.Message> {
|
|
65
|
+
return {
|
|
66
|
+
id: ObjectId.random().toString(),
|
|
67
|
+
created: this.next().toISOString(),
|
|
68
|
+
sender: faker.helpers.arrayElement(this.users),
|
|
69
|
+
blocks: Array.from({ length: numSegments }).map(() => this.createBlock()),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createBlock(): DataType.MessageBlock.Transcription {
|
|
74
|
+
let text = faker.lorem.paragraph();
|
|
75
|
+
if (this._space) {
|
|
76
|
+
const label = faker.commerce.productName();
|
|
77
|
+
const obj = this._space.db.add(live(TestItem, { title: label, description: faker.lorem.paragraph() }));
|
|
78
|
+
const dxn = makeRef(obj).dxn.toString();
|
|
79
|
+
const words = text.split(' ');
|
|
80
|
+
words.splice(Math.floor(Math.random() * words.length), 0, `[${label}][${dxn}]`);
|
|
81
|
+
text = words.join(' ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type: 'transcription',
|
|
86
|
+
started: this.next().toISOString(),
|
|
87
|
+
text,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
next(): Date {
|
|
92
|
+
this.start = new Date(this.start.getTime() + Math.random() * 10_000);
|
|
93
|
+
return this.start;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// TODO(burdon): Reconcile with BlockBuilder.
|
|
98
|
+
class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
|
|
99
|
+
aiService = new AIServiceEdgeClient({
|
|
100
|
+
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
executor = new FunctionExecutor(new ServiceContainer().setServices({ ai: { client: this.aiService } }));
|
|
104
|
+
|
|
105
|
+
space: Space | undefined;
|
|
106
|
+
currentMessage: number = 0;
|
|
107
|
+
transcriptMessages: DataType.Message[] = [];
|
|
108
|
+
|
|
109
|
+
async connect(space: Space): Promise<void> {
|
|
110
|
+
this.space = space;
|
|
111
|
+
const { transcriptMessages } = await seedTestData(space);
|
|
112
|
+
this.transcriptMessages = transcriptMessages;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override async createMessage(): Promise<DataType.Message> {
|
|
116
|
+
if (!this.space) {
|
|
117
|
+
throw new Error('Space not connected');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { objects } = await this.space.db
|
|
121
|
+
.query(
|
|
122
|
+
Filter.or(Filter.type(DataType.Person), Filter.type(DataType.Organization), Filter.type(Testing.DocumentType)),
|
|
123
|
+
)
|
|
124
|
+
.run();
|
|
125
|
+
|
|
126
|
+
log.info('context', { objects });
|
|
127
|
+
const message = this.transcriptMessages[this.currentMessage];
|
|
128
|
+
this.currentMessage++;
|
|
129
|
+
this.currentMessage = this.currentMessage % this.transcriptMessages.length;
|
|
130
|
+
|
|
131
|
+
const { message: enhancedMessage } = await processTranscriptMessage({
|
|
132
|
+
input: { message },
|
|
133
|
+
executor: this.executor,
|
|
134
|
+
function: extractionAnthropicFn,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return enhancedMessage;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type UseTestTranscriptionQueue = (
|
|
142
|
+
space: Space | undefined,
|
|
143
|
+
queueId?: ObjectId,
|
|
144
|
+
running?: boolean,
|
|
145
|
+
interval?: number,
|
|
146
|
+
) => Queue<DataType.Message> | undefined;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Test transcriptionqueue.
|
|
150
|
+
*/
|
|
151
|
+
export const useTestTranscriptionQueue: UseTestTranscriptionQueue = (
|
|
152
|
+
space: Space | undefined,
|
|
153
|
+
queueId?: ObjectId,
|
|
154
|
+
running = true,
|
|
155
|
+
interval = 1_000,
|
|
156
|
+
) => {
|
|
157
|
+
const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
|
|
158
|
+
const queue = useQueue<DataType.Message>(queueDxn);
|
|
159
|
+
const builder = useMemo(() => new MessageBuilder(space), [space]);
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!queue || !running) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const i = setInterval(() => {
|
|
167
|
+
void builder.createMessage(Math.ceil(Math.random() * 3)).then((message) => {
|
|
168
|
+
queue.append([create(DataType.Message, message)]);
|
|
169
|
+
});
|
|
170
|
+
}, interval);
|
|
171
|
+
return () => clearInterval(i);
|
|
172
|
+
}, [queue, running, interval]);
|
|
173
|
+
|
|
174
|
+
return queue;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Test transcription queue.
|
|
179
|
+
*/
|
|
180
|
+
// TODO(burdon): Reconcile with useTestTranscriptionQueue.
|
|
181
|
+
export const useTestTranscriptionQueueWithEntityExtraction: UseTestTranscriptionQueue = (
|
|
182
|
+
space: Space | undefined,
|
|
183
|
+
queueId?: ObjectId,
|
|
184
|
+
running = true,
|
|
185
|
+
interval = 1_000,
|
|
186
|
+
) => {
|
|
187
|
+
const queueDxn = useMemo(() => (space ? createQueueDxn(space.id, queueId) : undefined), [space, queueId]);
|
|
188
|
+
const queue = useQueue<DataType.Message>(queueDxn);
|
|
189
|
+
const [builder] = useState(() => new EntityExtractionMessageBuilder());
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!queue || !running) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (space) {
|
|
197
|
+
void builder.connect(space);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const ctx = new Context();
|
|
201
|
+
scheduleTaskInterval(
|
|
202
|
+
ctx,
|
|
203
|
+
async () => {
|
|
204
|
+
const message = await builder.createMessage();
|
|
205
|
+
queue.append([create(DataType.Message, message)]);
|
|
206
|
+
},
|
|
207
|
+
interval,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return () => {
|
|
211
|
+
void ctx.dispose();
|
|
212
|
+
};
|
|
213
|
+
}, [space, queue, running, interval]);
|
|
214
|
+
|
|
215
|
+
return queue;
|
|
216
|
+
};
|
|
@@ -19,6 +19,13 @@ const initializeExtendableMediaRecorder = async () => {
|
|
|
19
19
|
await register(await connect());
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
export type MediaStreamRecorderParams = {
|
|
23
|
+
mediaStreamTrack: MediaStreamTrack;
|
|
24
|
+
config: {
|
|
25
|
+
interval: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* Recorder that uses the MediaContext API and AudioNode API to record audio.
|
|
24
31
|
*
|
|
@@ -30,14 +37,14 @@ export class MediaStreamRecorder implements AudioRecorder {
|
|
|
30
37
|
/**
|
|
31
38
|
* Default MediaRecorder implementation do not support wav encoding.
|
|
32
39
|
*/
|
|
33
|
-
private readonly
|
|
40
|
+
private readonly _config: MediaStreamRecorderParams['config'];
|
|
34
41
|
private _mediaRecorder?: IMediaRecorder = undefined;
|
|
35
42
|
private _header?: Uint8Array = undefined;
|
|
36
43
|
private _onChunk?: (chunk: AudioChunk) => void;
|
|
37
44
|
|
|
38
|
-
constructor({ mediaStreamTrack,
|
|
45
|
+
constructor({ mediaStreamTrack, config }: MediaStreamRecorderParams) {
|
|
39
46
|
this._mediaStreamTrack = mediaStreamTrack;
|
|
40
|
-
this.
|
|
47
|
+
this._config = config;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
get wavConfig(): WavConfig {
|
|
@@ -57,7 +64,7 @@ export class MediaStreamRecorder implements AudioRecorder {
|
|
|
57
64
|
this._onChunk = onChunk;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
async start() {
|
|
67
|
+
async start(): Promise<void> {
|
|
61
68
|
await (initializingPromise ??= initializeExtendableMediaRecorder()).catch((err) =>
|
|
62
69
|
log.info('initializeExtendableMediaRecorder', { err }),
|
|
63
70
|
);
|
|
@@ -72,10 +79,10 @@ export class MediaStreamRecorder implements AudioRecorder {
|
|
|
72
79
|
}
|
|
73
80
|
invariant(this._onChunk, 'MediaStreamRecorder: onChunk is not set');
|
|
74
81
|
this._mediaRecorder.ondataavailable = (event) => this._ondataavailable(event);
|
|
75
|
-
this._mediaRecorder.start(this.
|
|
82
|
+
this._mediaRecorder.start(this._config.interval);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
async stop() {
|
|
85
|
+
async stop(): Promise<void> {
|
|
79
86
|
if (this._mediaRecorder?.state !== 'recording') {
|
|
80
87
|
return;
|
|
81
88
|
}
|
|
@@ -83,7 +90,7 @@ export class MediaStreamRecorder implements AudioRecorder {
|
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
@synchronized
|
|
86
|
-
private async _ondataavailable(event: IBlobEvent) {
|
|
93
|
+
private async _ondataavailable(event: IBlobEvent): Promise<void> {
|
|
87
94
|
const blob = event.data;
|
|
88
95
|
const uint8Array = new Uint8Array(await blob.arrayBuffer());
|
|
89
96
|
|
|
@@ -9,11 +9,11 @@ import { WaveFile } from 'wavefile';
|
|
|
9
9
|
|
|
10
10
|
import { Trigger } from '@dxos/async';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
|
+
import { type DataType } from '@dxos/schema';
|
|
12
13
|
import { openAndClose } from '@dxos/test-utils';
|
|
13
14
|
import { trace, TRACE_PROCESSOR } from '@dxos/tracing';
|
|
14
15
|
|
|
15
16
|
import { type AudioChunk, type AudioRecorder, Transcriber } from '../transcriber';
|
|
16
|
-
import { type TranscriptSegment } from '../types';
|
|
17
17
|
import { mergeFloat64Arrays } from '../util';
|
|
18
18
|
|
|
19
19
|
// TODO(burdon): ReferenceError: Worker is not defined (Can only run with web workers).
|
|
@@ -93,7 +93,7 @@ describe.skip('Transcriber', () => {
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
test.skip('transcription of audio recording', { timeout: 10_000 }, async () => {
|
|
96
|
-
const trigger = new Trigger<
|
|
96
|
+
const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
|
|
97
97
|
const recorder = new MockAudioRecorder({
|
|
98
98
|
buffer: await readFile('test.wav'),
|
|
99
99
|
chunkDuration: 3_000,
|
|
@@ -127,7 +127,7 @@ describe.skip('Transcriber', () => {
|
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
test.skip('transcription of audio recording with overlapping chunks', { timeout: 20_000 }, async () => {
|
|
130
|
-
const trigger = new Trigger<
|
|
130
|
+
const trigger = new Trigger<DataType.MessageBlock.Transcription[]>({ autoReset: true });
|
|
131
131
|
const recorder = new MockAudioRecorder({
|
|
132
132
|
buffer: await readFile('test.wav'),
|
|
133
133
|
chunkDuration: 3_000,
|
|
@@ -194,14 +194,14 @@ class MockAudioRecorder implements AudioRecorder {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
setOnChunk(onChunk: (chunk: AudioChunk) => void) {
|
|
197
|
+
setOnChunk(onChunk: (chunk: AudioChunk) => void): void {
|
|
198
198
|
this._onChunk = onChunk;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
202
|
* Emit a chunk in controlled manner for testing purposes.
|
|
203
203
|
*/
|
|
204
|
-
emitChunk() {
|
|
204
|
+
emitChunk(): void {
|
|
205
205
|
const chunk = this.chunks.shift();
|
|
206
206
|
if (!chunk) {
|
|
207
207
|
return;
|
|
@@ -211,7 +211,7 @@ class MockAudioRecorder implements AudioRecorder {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
@trace.span()
|
|
214
|
-
async start() {
|
|
214
|
+
async start(): Promise<void> {
|
|
215
215
|
log.info('start mock audio recorder', { wavParams: this.wav.fmt });
|
|
216
216
|
const now = Date.now();
|
|
217
217
|
const samples = this.wav.getSamples();
|
|
@@ -226,5 +226,5 @@ class MockAudioRecorder implements AudioRecorder {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
async stop() {}
|
|
229
|
+
async stop(): Promise<void> {}
|
|
230
230
|
}
|
|
@@ -7,10 +7,11 @@ import { WaveFile } from 'wavefile';
|
|
|
7
7
|
import { DeferredTask, Trigger } from '@dxos/async';
|
|
8
8
|
import { type Context, LifecycleState, Resource } from '@dxos/context';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
|
+
import { type DataType } from '@dxos/schema';
|
|
10
11
|
import { trace } from '@dxos/tracing';
|
|
11
12
|
|
|
12
13
|
import { type AudioRecorder, type AudioChunk } from './audio-recorder';
|
|
13
|
-
import { TRANSCRIPTION_URL
|
|
14
|
+
import { TRANSCRIPTION_URL } from '../types';
|
|
14
15
|
import { mergeFloat64Arrays } from '../util';
|
|
15
16
|
|
|
16
17
|
type WhisperWord = {
|
|
@@ -64,7 +65,11 @@ export type TranscribeConfig = {
|
|
|
64
65
|
export type TranscriberParams = {
|
|
65
66
|
config: TranscribeConfig;
|
|
66
67
|
recorder: AudioRecorder;
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Callback to handle the transcribed segments, after all segment transformers are applied.
|
|
70
|
+
* @param segments - The transcribed segments.
|
|
71
|
+
*/
|
|
72
|
+
onSegments: (segments: DataType.MessageBlock.Transcription[]) => Promise<void>;
|
|
68
73
|
};
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -92,35 +97,41 @@ export class Transcriber extends Resource {
|
|
|
92
97
|
this._onSegments = onSegments;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
protected override async _open(ctx: Context) {
|
|
100
|
+
protected override async _open(ctx: Context): Promise<void> {
|
|
101
|
+
log.info('opening');
|
|
96
102
|
this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));
|
|
97
103
|
await this._recorder.start();
|
|
98
104
|
this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());
|
|
99
105
|
this._openTrigger.wake();
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
protected override async _close() {
|
|
108
|
+
protected override async _close(): Promise<void> {
|
|
109
|
+
log.info('closing');
|
|
103
110
|
this._recording = false;
|
|
104
111
|
this._transcribeTask = undefined;
|
|
105
112
|
await this._recorder.stop();
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
startChunksRecording() {
|
|
115
|
+
startChunksRecording(): void {
|
|
116
|
+
log.info('starting');
|
|
109
117
|
if (this._lifecycleState !== LifecycleState.OPEN) {
|
|
110
118
|
return;
|
|
111
119
|
}
|
|
120
|
+
|
|
112
121
|
this._recording = true;
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
stopChunksRecording() {
|
|
124
|
+
stopChunksRecording(): void {
|
|
116
125
|
if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {
|
|
117
126
|
return;
|
|
118
127
|
}
|
|
128
|
+
|
|
119
129
|
this._recording = false;
|
|
120
130
|
this._transcribeTask?.schedule();
|
|
131
|
+
log.info('stopped');
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
private _saveAudioChunk(chunk: AudioChunk) {
|
|
134
|
+
private _saveAudioChunk(chunk: AudioChunk): void {
|
|
124
135
|
log('saving audio chunk', { chunk });
|
|
125
136
|
this._audioChunks.push(chunk);
|
|
126
137
|
|
|
@@ -136,7 +147,7 @@ export class Transcriber extends Resource {
|
|
|
136
147
|
}
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
private async _transcribe() {
|
|
150
|
+
private async _transcribe(): Promise<void> {
|
|
140
151
|
log('transcribing', { chunks: this._audioChunks.length });
|
|
141
152
|
const chunks = this._audioChunks;
|
|
142
153
|
this._audioChunks = this._dropOldChunks();
|
|
@@ -156,14 +167,14 @@ export class Transcriber extends Resource {
|
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
|
|
159
|
-
private _dropOldChunks() {
|
|
170
|
+
private _dropOldChunks(): AudioChunk[] {
|
|
160
171
|
return this._config.prefixBufferChunksAmount > 0
|
|
161
172
|
? this._audioChunks.slice(-this._config.prefixBufferChunksAmount)
|
|
162
173
|
: [];
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
@trace.span({ showInBrowserTimeline: true })
|
|
166
|
-
private async _mergeAudioChunks(chunks: AudioChunk[]) {
|
|
177
|
+
private async _mergeAudioChunks(chunks: AudioChunk[]): Promise<string> {
|
|
167
178
|
const file = new WaveFile();
|
|
168
179
|
const wavConfig = this._recorder.wavConfig;
|
|
169
180
|
|
|
@@ -178,7 +189,7 @@ export class Transcriber extends Resource {
|
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
@trace.span({ showInBrowserTimeline: true })
|
|
181
|
-
private async _fetchTranscription(audio: string) {
|
|
192
|
+
private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {
|
|
182
193
|
if (audio.length === 0) {
|
|
183
194
|
this._audioChunks = [];
|
|
184
195
|
throw new Error('No audio to send for transcribing');
|
|
@@ -194,24 +205,25 @@ export class Transcriber extends Resource {
|
|
|
194
205
|
|
|
195
206
|
if (!response.ok) {
|
|
196
207
|
this._audioChunks = [];
|
|
197
|
-
throw new Error(
|
|
208
|
+
throw new Error(`Transcription failed: ${response.statusText}`);
|
|
198
209
|
}
|
|
199
210
|
|
|
200
|
-
const {
|
|
201
|
-
segments,
|
|
202
|
-
}: {
|
|
211
|
+
const { segments } = (await response.json()) as {
|
|
203
212
|
segments: WhisperSegment[];
|
|
204
|
-
}
|
|
213
|
+
};
|
|
205
214
|
|
|
206
215
|
log.info('transcription response', {
|
|
207
|
-
segments,
|
|
216
|
+
segments: segments.length,
|
|
208
217
|
string: segments.map((segments) => segments.text).join(' '),
|
|
209
218
|
});
|
|
210
219
|
|
|
211
220
|
return segments;
|
|
212
221
|
}
|
|
213
222
|
|
|
214
|
-
private _alignSegments(
|
|
223
|
+
private _alignSegments(
|
|
224
|
+
segments: WhisperSegment[],
|
|
225
|
+
originalChunks: AudioChunk[],
|
|
226
|
+
): DataType.MessageBlock.Transcription[] {
|
|
215
227
|
// Absolute zero for all relative timestamps in the segments.
|
|
216
228
|
const zeroTimestamp = originalChunks.at(0)!.timestamp;
|
|
217
229
|
|
|
@@ -240,8 +252,9 @@ export class Transcriber extends Resource {
|
|
|
240
252
|
|
|
241
253
|
// Add absolute timestamp to each segment.
|
|
242
254
|
return filteredSegments.map((segment) => ({
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
type: 'transcription',
|
|
256
|
+
started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
|
|
257
|
+
text: segment.text.trim(),
|
|
245
258
|
}));
|
|
246
259
|
}
|
|
247
260
|
}
|