@dxos/plugin-transcription 0.9.1-main.c7dcc2e112 → 0.10.0
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/PLUGIN.mdl +5 -5
- package/dist/lib/neutral/{TranscriptionArticle-S56VVOSF.mjs → TranscriptionArticle-FEWCWG4A.mjs} +3 -6
- package/dist/lib/neutral/TranscriptionArticle-FEWCWG4A.mjs.map +7 -0
- package/dist/lib/neutral/TranscriptionPlugin.mjs +25 -4
- package/dist/lib/neutral/TranscriptionPlugin.mjs.map +3 -3
- package/dist/lib/neutral/TranscriptionPlugin.node.mjs +3 -3
- package/dist/lib/neutral/TranscriptionPlugin.node.mjs.map +3 -3
- package/dist/lib/neutral/app-graph-builder-GQRMI22E.mjs +48 -0
- package/dist/lib/neutral/app-graph-builder-GQRMI22E.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +17 -5
- package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
- package/dist/lib/neutral/{chunk-XZVDAYM6.mjs → chunk-5BVHY75B.mjs} +2 -2
- package/dist/lib/neutral/{chunk-XZVDAYM6.mjs.map → chunk-5BVHY75B.mjs.map} +1 -1
- package/dist/lib/neutral/chunk-HNXSU7HJ.mjs +8 -0
- package/dist/lib/neutral/chunk-KOXLGGSL.mjs +30 -0
- package/dist/lib/neutral/chunk-KOXLGGSL.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-2URVUJT4.mjs → chunk-MDV7NBFJ.mjs} +31 -8
- package/dist/lib/neutral/chunk-MDV7NBFJ.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +114 -258
- package/dist/lib/neutral/components/index.mjs.map +4 -4
- package/dist/lib/neutral/containers/index.mjs +1 -1
- package/dist/lib/neutral/{create-7NGOP7JP.mjs → create-HNHGCKZ4.mjs} +2 -2
- package/dist/lib/neutral/{enrich-message-F2AOM53F.mjs → enrich-message-DSJU4TXC.mjs} +2 -2
- package/dist/lib/neutral/entity-lookup-PCDW7NC4.mjs +25 -0
- package/dist/lib/neutral/entity-lookup-PCDW7NC4.mjs.map +7 -0
- package/dist/lib/neutral/hooks/index.mjs +93 -15
- package/dist/lib/neutral/hooks/index.mjs.map +4 -4
- package/dist/lib/neutral/index.mjs +6 -32
- package/dist/lib/neutral/markdown-extension-CCDXXY7E.mjs +16 -0
- package/dist/lib/neutral/markdown-extension-CCDXXY7E.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/meta.mjs +1 -1
- package/dist/lib/neutral/{open-42H72C4G.mjs → open-SNMZXXYO.mjs} +4 -6
- package/dist/lib/neutral/open-SNMZXXYO.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +1 -1
- package/dist/lib/neutral/pipeline-status-XMWMAUJY.mjs +17 -0
- package/dist/lib/neutral/pipeline-status-XMWMAUJY.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +2 -2
- package/dist/lib/neutral/recording-session-IADO7ATU.mjs +15 -0
- package/dist/lib/neutral/recording-session-IADO7ATU.mjs.map +7 -0
- package/dist/lib/neutral/{settings-DUS32HOM.mjs → settings-7KE4MABW.mjs} +3 -2
- package/dist/lib/neutral/{settings-DUS32HOM.mjs.map → settings-7KE4MABW.mjs.map} +3 -3
- package/dist/lib/neutral/skill-definition-DXNK324V.mjs +15 -0
- package/dist/lib/neutral/skill-definition-DXNK324V.mjs.map +7 -0
- package/dist/lib/neutral/skills/index.mjs +8 -0
- package/dist/lib/neutral/{summarize-45WVUPED.mjs → summarize-LBUZTOTH.mjs} +4 -4
- package/dist/lib/neutral/summarize-LBUZTOTH.mjs.map +7 -0
- package/dist/lib/neutral/{text-content-XZ3HK4OT.mjs → text-content-NOWR642C.mjs} +2 -4
- package/dist/lib/neutral/text-content-NOWR642C.mjs.map +7 -0
- package/dist/lib/neutral/transcriber-6XZHUUHK.mjs +174 -0
- package/dist/lib/neutral/transcriber-6XZHUUHK.mjs.map +7 -0
- package/dist/lib/neutral/transcription-driver-DN6ELQXZ.mjs +207 -0
- package/dist/lib/neutral/transcription-driver-DN6ELQXZ.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +7 -0
- package/dist/lib/neutral/translations.mjs.map +2 -2
- package/dist/lib/neutral/types/index.mjs +1 -1
- package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +5 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/entity-lookup.d.ts +10 -0
- package/dist/types/src/capabilities/entity-lookup.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +7 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/markdown-extension.d.ts +5 -0
- package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
- package/dist/types/src/capabilities/pipeline-status.d.ts +9 -0
- package/dist/types/src/capabilities/pipeline-status.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/recording-session.d.ts +5 -0
- package/dist/types/src/capabilities/recording-session.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +1 -3
- package/dist/types/src/capabilities/settings.d.ts.map +1 -1
- package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
- package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
- package/dist/types/src/capabilities/text-content.d.ts +1 -4
- package/dist/types/src/capabilities/transcriber.d.ts +4 -3
- package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
- package/dist/types/src/capabilities/transcription-driver.d.ts +5 -0
- package/dist/types/src/capabilities/transcription-driver.d.ts.map +1 -0
- package/dist/types/src/components/Mic/Mic.d.ts +12 -0
- package/dist/types/src/components/Mic/Mic.d.ts.map +1 -0
- package/dist/types/src/components/{Oracle/Oracle.stories.d.ts → Mic/Mic.stories.d.ts} +9 -7
- package/dist/types/src/components/Mic/Mic.stories.d.ts.map +1 -0
- package/dist/types/src/components/Mic/index.d.ts +2 -0
- package/dist/types/src/components/Mic/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/TranscriptionArticle/TranscriptionArticle.d.ts.map +1 -1
- package/dist/types/src/containers/TranscriptionArticle/TranscriptionArticle.stories.d.ts +7 -0
- package/dist/types/src/containers/TranscriptionArticle/TranscriptionArticle.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +0 -5
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTranscriptionRecording.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +4 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/normalization/message-normalizer.d.ts +2 -2
- package/dist/types/src/normalization/message-normalizer.d.ts.map +1 -1
- package/dist/types/src/normalization/normalization.d.ts +98 -733
- package/dist/types/src/normalization/normalization.d.ts.map +1 -1
- package/dist/types/src/operations/open.d.ts.map +1 -1
- package/dist/types/src/operations/summarize.d.ts.map +1 -1
- package/dist/types/src/skills/index.d.ts +2 -0
- package/dist/types/src/skills/index.d.ts.map +1 -0
- package/dist/types/src/skills/transcription-skill.d.ts +4 -0
- package/dist/types/src/skills/transcription-skill.d.ts.map +1 -0
- package/dist/types/src/stories/Keyword.stories.d.ts +51 -0
- package/dist/types/src/stories/Keyword.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Pipeline.stories.d.ts +60 -0
- package/dist/types/src/stories/Pipeline.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Transcription.stories.d.ts +23 -0
- package/dist/types/src/stories/Transcription.stories.d.ts.map +1 -0
- package/dist/types/src/testing/decorators.d.ts +21 -0
- package/dist/types/src/testing/decorators.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/testing.d.ts +3 -9
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/{transcriber/transcription-manager.d.ts → transcription-manager.d.ts} +6 -5
- package/dist/types/src/transcription-manager.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +7 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Settings.d.ts +6 -0
- package/dist/types/src/types/Settings.d.ts.map +1 -1
- package/dist/types/src/types/TranscriptOperation.d.ts +114 -1971
- package/dist/types/src/types/TranscriptOperation.d.ts.map +1 -1
- package/dist/types/src/types/TranscriptionCapabilities.d.ts +51 -13
- package/dist/types/src/types/TranscriptionCapabilities.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -2
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +1 -1
- package/package.json +68 -69
- package/src/TranscriptionPlugin.node.ts +2 -2
- package/src/TranscriptionPlugin.test.ts +1 -1
- package/src/TranscriptionPlugin.tsx +31 -2
- package/src/capabilities/app-graph-builder.tsx +49 -0
- package/src/capabilities/entity-lookup.ts +32 -0
- package/src/capabilities/index.ts +7 -1
- package/src/capabilities/markdown-extension.ts +17 -0
- package/src/capabilities/pipeline-status.ts +23 -0
- package/src/capabilities/recording-session.ts +17 -0
- package/src/capabilities/settings.ts +1 -0
- package/src/capabilities/skill-definition.ts +15 -0
- package/src/capabilities/text-content.ts +1 -2
- package/src/capabilities/transcriber.ts +5 -48
- package/src/capabilities/transcription-driver.tsx +263 -0
- package/src/components/Mic/Mic.stories.tsx +66 -0
- package/src/components/Mic/Mic.tsx +136 -0
- package/src/components/Mic/index.ts +5 -0
- package/src/components/index.ts +1 -2
- package/src/containers/TranscriptionArticle/TranscriptionArticle.stories.tsx +4 -0
- package/src/containers/TranscriptionArticle/TranscriptionArticle.tsx +4 -5
- package/src/hooks/index.ts +0 -5
- package/src/hooks/useTranscriptionRecording.ts +4 -8
- package/src/index.ts +1 -5
- package/src/normalization/message-normalizer.ts +3 -3
- package/src/normalization/normalization.ts +3 -0
- package/src/operations/open.ts +2 -2
- package/src/operations/summarize.ts +2 -2
- package/src/skills/index.ts +5 -0
- package/src/skills/transcription-skill.ts +29 -0
- package/src/stories/Keyword.stories.tsx +98 -0
- package/src/stories/Pipeline.stories.tsx +361 -0
- package/src/stories/Transcription.stories.tsx +178 -0
- package/src/testing/decorators.ts +78 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/testing.ts +13 -48
- package/src/{transcriber/transcription-manager.ts → transcription-manager.ts} +13 -12
- package/src/translations.ts +8 -0
- package/src/types/Settings.ts +38 -0
- package/src/types/TranscriptOperation.ts +6 -4
- package/src/types/TranscriptionCapabilities.ts +57 -21
- package/src/util/index.ts +0 -2
- package/dist/lib/neutral/TranscriptionArticle-S56VVOSF.mjs.map +0 -7
- package/dist/lib/neutral/blueprint-definition-DA4QFRYJ.mjs +0 -15
- package/dist/lib/neutral/blueprint-definition-DA4QFRYJ.mjs.map +0 -7
- package/dist/lib/neutral/blueprints/index.mjs +0 -8
- package/dist/lib/neutral/chunk-2URVUJT4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J4ONEYPJ.mjs +0 -8
- package/dist/lib/neutral/chunk-MLRJWAQH.mjs +0 -179
- package/dist/lib/neutral/chunk-MLRJWAQH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XMHH34NI.mjs +0 -318
- package/dist/lib/neutral/chunk-XMHH34NI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XOXHWMXR.mjs +0 -35
- package/dist/lib/neutral/chunk-XOXHWMXR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XWC5BBOY.mjs +0 -435
- package/dist/lib/neutral/chunk-XWC5BBOY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ZR4BH4QQ.mjs +0 -30
- package/dist/lib/neutral/chunk-ZR4BH4QQ.mjs.map +0 -7
- package/dist/lib/neutral/open-42H72C4G.mjs.map +0 -7
- package/dist/lib/neutral/summarize-45WVUPED.mjs.map +0 -7
- package/dist/lib/neutral/text-content-XZ3HK4OT.mjs.map +0 -7
- package/dist/lib/neutral/transcriber-KYV6NWI5.mjs +0 -59
- package/dist/lib/neutral/transcriber-KYV6NWI5.mjs.map +0 -7
- package/dist/types/src/blueprints/index.d.ts +0 -2
- package/dist/types/src/blueprints/index.d.ts.map +0 -1
- package/dist/types/src/blueprints/transcription-blueprint.d.ts +0 -4
- package/dist/types/src/blueprints/transcription-blueprint.d.ts.map +0 -1
- package/dist/types/src/capabilities/blueprint-definition.d.ts +0 -6
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +0 -1
- package/dist/types/src/components/Oracle/Oracle.d.ts +0 -24
- package/dist/types/src/components/Oracle/Oracle.d.ts.map +0 -1
- package/dist/types/src/components/Oracle/Oracle.stories.d.ts.map +0 -1
- package/dist/types/src/components/Oracle/index.d.ts +0 -2
- package/dist/types/src/components/Oracle/index.d.ts.map +0 -1
- package/dist/types/src/components/Transcription/Transcription.d.ts +0 -11
- package/dist/types/src/components/Transcription/Transcription.d.ts.map +0 -1
- package/dist/types/src/components/Transcription/Transcription.stories.d.ts +0 -37
- package/dist/types/src/components/Transcription/Transcription.stories.d.ts.map +0 -1
- package/dist/types/src/components/Transcription/index.d.ts +0 -2
- package/dist/types/src/components/Transcription/index.d.ts.map +0 -1
- package/dist/types/src/components/Transcription/transcription-extension.d.ts +0 -15
- package/dist/types/src/components/Transcription/transcription-extension.d.ts.map +0 -1
- package/dist/types/src/enrichment/conversation.test.d.ts +0 -2
- package/dist/types/src/enrichment/conversation.test.d.ts.map +0 -1
- package/dist/types/src/enrichment/operations.d.ts +0 -200
- package/dist/types/src/enrichment/operations.d.ts.map +0 -1
- package/dist/types/src/enrichment/parser.d.ts +0 -26
- package/dist/types/src/enrichment/parser.d.ts.map +0 -1
- package/dist/types/src/enrichment/stream-simulator.d.ts +0 -38
- package/dist/types/src/enrichment/stream-simulator.d.ts.map +0 -1
- package/dist/types/src/hooks/useAudioFile.d.ts +0 -7
- package/dist/types/src/hooks/useAudioFile.d.ts.map +0 -1
- package/dist/types/src/hooks/useAudioTrack.d.ts +0 -5
- package/dist/types/src/hooks/useAudioTrack.d.ts.map +0 -1
- package/dist/types/src/hooks/useFeedModelAdapter.d.ts +0 -12
- package/dist/types/src/hooks/useFeedModelAdapter.d.ts.map +0 -1
- package/dist/types/src/hooks/useTranscriber.d.ts +0 -7
- package/dist/types/src/hooks/useTranscriber.d.ts.map +0 -1
- package/dist/types/src/hooks/useVoiceInput.d.ts +0 -11
- package/dist/types/src/hooks/useVoiceInput.d.ts.map +0 -1
- package/dist/types/src/model/index.d.ts +0 -2
- package/dist/types/src/model/index.d.ts.map +0 -1
- package/dist/types/src/model/model.d.ts +0 -68
- package/dist/types/src/model/model.d.ts.map +0 -1
- package/dist/types/src/model/model.test.d.ts +0 -2
- package/dist/types/src/model/model.test.d.ts.map +0 -1
- package/dist/types/src/stories/FileTranscription.stories.d.ts +0 -20
- package/dist/types/src/stories/FileTranscription.stories.d.ts.map +0 -1
- package/dist/types/src/stories/KeyWordRecognition.stories.d.ts +0 -14
- package/dist/types/src/stories/KeyWordRecognition.stories.d.ts.map +0 -1
- package/dist/types/src/stories/LiveTranscription.stories.d.ts +0 -19
- package/dist/types/src/stories/LiveTranscription.stories.d.ts.map +0 -1
- package/dist/types/src/stories/ProperNounExtraction.stories.d.ts +0 -13
- package/dist/types/src/stories/ProperNounExtraction.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TranscriptionStory.d.ts +0 -21
- package/dist/types/src/stories/TranscriptionStory.d.ts.map +0 -1
- package/dist/types/src/stories/common.d.ts +0 -248
- package/dist/types/src/stories/common.d.ts.map +0 -1
- package/dist/types/src/transcriber/audio-recorder.d.ts +0 -47
- package/dist/types/src/transcriber/audio-recorder.d.ts.map +0 -1
- package/dist/types/src/transcriber/index.d.ts +0 -5
- package/dist/types/src/transcriber/index.d.ts.map +0 -1
- package/dist/types/src/transcriber/media-stream-recorder.d.ts +0 -30
- package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +0 -1
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts +0 -2
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +0 -1
- package/dist/types/src/transcriber/transcriber.d.ts +0 -105
- package/dist/types/src/transcriber/transcriber.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/transcriber/transcription-manager.d.ts.map +0 -1
- package/dist/types/src/util/array.d.ts +0 -2
- package/dist/types/src/util/array.d.ts.map +0 -1
- package/dist/types/src/util/render-byline.d.ts +0 -4
- package/dist/types/src/util/render-byline.d.ts.map +0 -1
- package/src/blueprints/index.ts +0 -5
- package/src/blueprints/transcription-blueprint.ts +0 -29
- package/src/capabilities/blueprint-definition.ts +0 -19
- package/src/components/Oracle/Oracle.stories.tsx +0 -147
- package/src/components/Oracle/Oracle.tsx +0 -142
- package/src/components/Oracle/index.ts +0 -5
- package/src/components/Transcription/Transcription.stories.tsx +0 -213
- package/src/components/Transcription/Transcription.tsx +0 -55
- package/src/components/Transcription/index.ts +0 -5
- package/src/components/Transcription/transcription-extension.ts +0 -203
- package/src/enrichment/conversation.test.ts +0 -162
- package/src/enrichment/conversation.txt +0 -23
- package/src/enrichment/operations.ts +0 -271
- package/src/enrichment/parser.ts +0 -101
- package/src/enrichment/stream-simulator.ts +0 -125
- package/src/hooks/useAudioFile.ts +0 -88
- package/src/hooks/useAudioTrack.ts +0 -35
- package/src/hooks/useFeedModelAdapter.ts +0 -46
- package/src/hooks/useTranscriber.ts +0 -47
- package/src/hooks/useVoiceInput.ts +0 -66
- package/src/model/index.ts +0 -5
- package/src/model/model.test.ts +0 -175
- package/src/model/model.ts +0 -229
- package/src/stories/FileTranscription.stories.tsx +0 -143
- package/src/stories/KeyWordRecognition.stories.tsx +0 -132
- package/src/stories/LiveTranscription.stories.tsx +0 -99
- package/src/stories/ProperNounExtraction.stories.tsx +0 -99
- package/src/stories/TranscriptionStory.tsx +0 -101
- package/src/stories/common.ts +0 -234
- package/src/transcriber/audio-recorder.ts +0 -56
- package/src/transcriber/index.ts +0 -8
- package/src/transcriber/media-stream-recorder.ts +0 -120
- package/src/transcriber/transcriber.browser.test.ts +0 -230
- package/src/transcriber/transcriber.test.ts +0 -104
- package/src/transcriber/transcriber.ts +0 -303
- package/src/util/array.ts +0 -14
- package/src/util/render-byline.ts +0 -33
- /package/dist/lib/neutral/{chunk-J4ONEYPJ.mjs.map → chunk-HNXSU7HJ.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-7NGOP7JP.mjs.map → create-HNHGCKZ4.mjs.map} +0 -0
- /package/dist/lib/neutral/{enrich-message-F2AOM53F.mjs.map → enrich-message-DSJU4TXC.mjs.map} +0 -0
- /package/dist/lib/neutral/{blueprints → skills}/index.mjs.map +0 -0
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import fs from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { describe, expect, test } from 'vitest';
|
|
8
|
-
import { WaveFile } from 'wavefile';
|
|
9
|
-
|
|
10
|
-
import { Trigger } from '@dxos/async';
|
|
11
|
-
import { log } from '@dxos/log';
|
|
12
|
-
import { openAndClose } from '@dxos/test-utils';
|
|
13
|
-
import { TRACE_PROCESSOR, trace } from '@dxos/tracing';
|
|
14
|
-
import { type ContentBlock } from '@dxos/types';
|
|
15
|
-
|
|
16
|
-
import { type AudioChunk, type AudioRecorder, Transcriber } from '../transcriber';
|
|
17
|
-
import { mergeFloat64Arrays } from '../util';
|
|
18
|
-
|
|
19
|
-
// TODO(burdon): ReferenceError: Worker is not defined (Can only run with web workers).
|
|
20
|
-
|
|
21
|
-
// This is a playground for testing the transcription, requires `calls-service` to be running.
|
|
22
|
-
describe.skip('Transcriber', () => {
|
|
23
|
-
const DIR_PATH = path.join(__dirname, 'assets');
|
|
24
|
-
|
|
25
|
-
const readFile = async (filename: string) => {
|
|
26
|
-
return fs.readFileSync(path.join(DIR_PATH, filename));
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const writeOutputFile = async (filename: string, data: Uint8Array) => {
|
|
30
|
-
const outputPath = path.join(DIR_PATH, 'out', filename);
|
|
31
|
-
if (!fs.existsSync(path.dirname(outputPath))) {
|
|
32
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return fs.writeFileSync(outputPath, data);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
test.skip('split wav file', async () => {
|
|
39
|
-
// Read the WAV file
|
|
40
|
-
const fileBuffer = await readFile('./testing/test.wav');
|
|
41
|
-
const wav = new WaveFile(fileBuffer);
|
|
42
|
-
|
|
43
|
-
// Get PCM samples and format info.
|
|
44
|
-
const samples = wav.getSamples();
|
|
45
|
-
const format = wav.fmt as any;
|
|
46
|
-
|
|
47
|
-
// Take second half of samples.
|
|
48
|
-
const halfSamples = samples.slice(Math.floor(samples.length / 2));
|
|
49
|
-
|
|
50
|
-
// Create new WAV file with half the samples.
|
|
51
|
-
const halfWav = new WaveFile();
|
|
52
|
-
halfWav.fromScratch(1, format.sampleRate!, '16', halfSamples);
|
|
53
|
-
|
|
54
|
-
// Save the half-length file
|
|
55
|
-
await writeOutputFile('half.wav', halfWav.toBuffer());
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test.skip('merge audio chunks', async () => {
|
|
59
|
-
const chunks: AudioChunk[] = [];
|
|
60
|
-
const trigger = new Trigger({ autoReset: true });
|
|
61
|
-
|
|
62
|
-
const recorder = new MockAudioRecorder({
|
|
63
|
-
buffer: await readFile('test.wav'),
|
|
64
|
-
chunkDuration: 3_000,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
recorder.setOnChunk((chunk) => {
|
|
68
|
-
chunks.push(chunk);
|
|
69
|
-
trigger.wake();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await recorder.start();
|
|
73
|
-
|
|
74
|
-
const chunksNumber = 3;
|
|
75
|
-
for (let i = 0; i < chunksNumber; i++) {
|
|
76
|
-
recorder.emitChunk();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
while (chunks.length < chunksNumber) {
|
|
80
|
-
await trigger.wait();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const wav = new WaveFile();
|
|
84
|
-
wav.fromScratch(
|
|
85
|
-
1, //
|
|
86
|
-
recorder.wavConfig.sampleRate,
|
|
87
|
-
'16',
|
|
88
|
-
mergeFloat64Arrays(chunks.map((chunk) => chunk.data)),
|
|
89
|
-
);
|
|
90
|
-
await writeOutputFile('merged.wav', wav.toBuffer());
|
|
91
|
-
|
|
92
|
-
await recorder.stop();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test.skip('transcription of audio recording', { timeout: 10_000 }, async () => {
|
|
96
|
-
const trigger = new Trigger<ContentBlock.Transcript[]>({ autoReset: true });
|
|
97
|
-
const recorder = new MockAudioRecorder({
|
|
98
|
-
buffer: await readFile('test.wav'),
|
|
99
|
-
chunkDuration: 3_000,
|
|
100
|
-
});
|
|
101
|
-
const transcription = new Transcriber({
|
|
102
|
-
recorder,
|
|
103
|
-
onSegments: async (segments) => {
|
|
104
|
-
log.info('transcription', { segments });
|
|
105
|
-
trigger.wake(segments);
|
|
106
|
-
},
|
|
107
|
-
config: {
|
|
108
|
-
transcribeAfterChunksAmount: 1000,
|
|
109
|
-
prefixBufferChunksAmount: 1,
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
await openAndClose(transcription);
|
|
114
|
-
|
|
115
|
-
transcription.startChunksRecording();
|
|
116
|
-
|
|
117
|
-
recorder.emitChunk();
|
|
118
|
-
recorder.emitChunk();
|
|
119
|
-
recorder.emitChunk();
|
|
120
|
-
|
|
121
|
-
transcription.stopChunksRecording();
|
|
122
|
-
|
|
123
|
-
// Could fail, not critical.
|
|
124
|
-
expect(
|
|
125
|
-
(await trigger.wait()).some((segment) => segment.text.includes('I will tell you some information about myself')),
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test.skip('transcription of audio recording with overlapping chunks', { timeout: 20_000 }, async () => {
|
|
130
|
-
const trigger = new Trigger<ContentBlock.Transcript[]>({ autoReset: true });
|
|
131
|
-
const recorder = new MockAudioRecorder({
|
|
132
|
-
buffer: await readFile('test.wav'),
|
|
133
|
-
chunkDuration: 3_000,
|
|
134
|
-
});
|
|
135
|
-
const transcription = new Transcriber({
|
|
136
|
-
recorder,
|
|
137
|
-
onSegments: async (segments) => {
|
|
138
|
-
log.info('transcription', { segments });
|
|
139
|
-
trigger.wake(segments);
|
|
140
|
-
},
|
|
141
|
-
config: {
|
|
142
|
-
transcribeAfterChunksAmount: 1000,
|
|
143
|
-
prefixBufferChunksAmount: 1,
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await openAndClose(transcription);
|
|
148
|
-
|
|
149
|
-
transcription.startChunksRecording();
|
|
150
|
-
recorder.emitChunk();
|
|
151
|
-
recorder.emitChunk();
|
|
152
|
-
transcription.stopChunksRecording();
|
|
153
|
-
expect((await trigger.wait()).some((segment) => segment.text.includes("Hello, I'm")));
|
|
154
|
-
|
|
155
|
-
transcription.startChunksRecording();
|
|
156
|
-
recorder.emitChunk();
|
|
157
|
-
recorder.emitChunk();
|
|
158
|
-
transcription.stopChunksRecording();
|
|
159
|
-
expect(
|
|
160
|
-
(await trigger.wait()).some((segment) => segment.text.includes('I will tell you some information about myself')),
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
log.info('Done', { trace: TRACE_PROCESSOR.getDiagnostics() });
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test.skip('double file', async () => {
|
|
167
|
-
const fileBuffer = await readFile('test.wav');
|
|
168
|
-
const wav = new WaveFile(fileBuffer);
|
|
169
|
-
const samples = wav.getSamples();
|
|
170
|
-
const newWav = new WaveFile();
|
|
171
|
-
newWav.fromScratch(1, 24000, '16', mergeFloat64Arrays([samples, samples]));
|
|
172
|
-
await writeOutputFile('double.wav', newWav.toBuffer());
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
@trace.resource()
|
|
177
|
-
class MockAudioRecorder implements AudioRecorder {
|
|
178
|
-
public chunks: AudioChunk[] = [];
|
|
179
|
-
public wav: WaveFile;
|
|
180
|
-
public chunkDuration: number;
|
|
181
|
-
|
|
182
|
-
private _onChunk?: (chunk: AudioChunk) => void = undefined;
|
|
183
|
-
|
|
184
|
-
constructor({ buffer, chunkDuration }: { buffer: Uint8Array; chunkDuration: number }) {
|
|
185
|
-
this.wav = new WaveFile(buffer);
|
|
186
|
-
this.chunkDuration = chunkDuration;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
get wavConfig() {
|
|
190
|
-
return {
|
|
191
|
-
channels: 1,
|
|
192
|
-
sampleRate: (this.wav.fmt as any).sampleRate,
|
|
193
|
-
bitDepthCode: '16',
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
setOnChunk(onChunk: (chunk: AudioChunk) => void): void {
|
|
198
|
-
this._onChunk = onChunk;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Emit a chunk in controlled manner for testing purposes.
|
|
203
|
-
*/
|
|
204
|
-
emitChunk(): void {
|
|
205
|
-
const chunk = this.chunks.shift();
|
|
206
|
-
if (!chunk) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
this._onChunk!(chunk);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
@trace.span()
|
|
214
|
-
async start(): Promise<void> {
|
|
215
|
-
log.info('start mock audio recorder', { wavProps: this.wav.fmt });
|
|
216
|
-
const now = Date.now();
|
|
217
|
-
const samples = this.wav.getSamples();
|
|
218
|
-
const sampleRate = this.wavConfig.sampleRate;
|
|
219
|
-
const increment = sampleRate * (this.chunkDuration / 1000);
|
|
220
|
-
for (let i = 0; i < samples.length; i += increment) {
|
|
221
|
-
const chunk = samples.slice(i, i + increment);
|
|
222
|
-
this.chunks.push({
|
|
223
|
-
timestamp: now + (i / increment) * this.chunkDuration,
|
|
224
|
-
data: chunk,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async stop(): Promise<void> {}
|
|
230
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2026 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { describe, test } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import { type AudioChunk } from './audio-recorder';
|
|
8
|
-
import { alignWhisperSegments, type WhisperSegment } from './transcriber';
|
|
9
|
-
|
|
10
|
-
const T0 = Date.parse('2026-01-01T00:00:00.000Z');
|
|
11
|
-
|
|
12
|
-
const chunk = (offsetMs = 0, samples = 16): AudioChunk => ({
|
|
13
|
-
timestamp: T0 + offsetMs,
|
|
14
|
-
data: new Float64Array(samples),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const segment = (overrides: Partial<WhisperSegment>): WhisperSegment => ({
|
|
18
|
-
text: '',
|
|
19
|
-
start: 0,
|
|
20
|
-
end: 0,
|
|
21
|
-
no_speech_prob: 0,
|
|
22
|
-
words: [],
|
|
23
|
-
...overrides,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('alignWhisperSegments', () => {
|
|
27
|
-
test('returns empty when no chunks were transcribed', ({ expect }) => {
|
|
28
|
-
const result = alignWhisperSegments(
|
|
29
|
-
[segment({ text: 'x', end: 1, words: [{ word: 'x', start: 0, end: 1 }] })],
|
|
30
|
-
[],
|
|
31
|
-
0,
|
|
32
|
-
);
|
|
33
|
-
expect(result.transcripts).toEqual([]);
|
|
34
|
-
expect(result.lastTimestamp).toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('absolute-timestamps and deduplicates segments against lastTimestamp', ({ expect }) => {
|
|
38
|
-
const segments: WhisperSegment[] = [
|
|
39
|
-
segment({
|
|
40
|
-
text: 'hello world',
|
|
41
|
-
start: 0,
|
|
42
|
-
end: 1,
|
|
43
|
-
words: [
|
|
44
|
-
{ word: 'hello ', start: 0, end: 0.5 },
|
|
45
|
-
{ word: 'world', start: 0.5, end: 1 },
|
|
46
|
-
],
|
|
47
|
-
}),
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
const first = alignWhisperSegments(segments, [chunk(0)], 0);
|
|
51
|
-
expect(first.transcripts).toHaveLength(1);
|
|
52
|
-
expect(first.transcripts[0]._tag).toBe('transcript');
|
|
53
|
-
expect(first.transcripts[0].text).toBe('hello world');
|
|
54
|
-
expect(new Date(first.transcripts[0].started).getTime()).toBe(T0);
|
|
55
|
-
expect(first.lastTimestamp).toBe(T0 + 1_000);
|
|
56
|
-
|
|
57
|
-
// Same segments, same chunk start: every word's absolute start is < lastTimestamp -> filtered out.
|
|
58
|
-
const second = alignWhisperSegments(segments, [chunk(0)], first.lastTimestamp!);
|
|
59
|
-
expect(second.transcripts).toEqual([]);
|
|
60
|
-
expect(second.lastTimestamp).toBeUndefined();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('keeps later words when earlier words fall before lastTimestamp', ({ expect }) => {
|
|
64
|
-
const segments: WhisperSegment[] = [
|
|
65
|
-
segment({
|
|
66
|
-
text: 'old new',
|
|
67
|
-
start: 0,
|
|
68
|
-
end: 2,
|
|
69
|
-
words: [
|
|
70
|
-
{ word: 'old ', start: 0, end: 1 },
|
|
71
|
-
{ word: 'new', start: 1.5, end: 2 },
|
|
72
|
-
],
|
|
73
|
-
}),
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
// First word starts at T0, second at T0+1500 — only the second survives a cursor at T0+1000.
|
|
77
|
-
const result = alignWhisperSegments(segments, [chunk(0)], T0 + 1_000);
|
|
78
|
-
expect(result.transcripts).toHaveLength(1);
|
|
79
|
-
expect(result.transcripts[0].text).toBe('new');
|
|
80
|
-
expect(new Date(result.transcripts[0].started).getTime()).toBe(T0 + 1_500);
|
|
81
|
-
expect(result.lastTimestamp).toBe(T0 + 2_000);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('drops segments that end before lastTimestamp', ({ expect }) => {
|
|
85
|
-
const segments: WhisperSegment[] = [
|
|
86
|
-
segment({
|
|
87
|
-
text: 'stale',
|
|
88
|
-
start: 0,
|
|
89
|
-
end: 0.5,
|
|
90
|
-
words: [{ word: 'stale', start: 0, end: 0.5 }],
|
|
91
|
-
}),
|
|
92
|
-
segment({
|
|
93
|
-
text: 'fresh',
|
|
94
|
-
start: 1,
|
|
95
|
-
end: 2,
|
|
96
|
-
words: [{ word: 'fresh', start: 1.5, end: 2 }],
|
|
97
|
-
}),
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
const result = alignWhisperSegments(segments, [chunk(0)], T0 + 1_000);
|
|
101
|
-
expect(result.transcripts).toHaveLength(1);
|
|
102
|
-
expect(result.transcripts[0].text).toBe('fresh');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { WaveFile } from 'wavefile';
|
|
6
|
-
|
|
7
|
-
import { DeferredTask, Trigger } from '@dxos/async';
|
|
8
|
-
import { type Context, LifecycleState, Resource } from '@dxos/context';
|
|
9
|
-
import { log } from '@dxos/log';
|
|
10
|
-
import { trace } from '@dxos/tracing';
|
|
11
|
-
import { type ContentBlock } from '@dxos/types';
|
|
12
|
-
|
|
13
|
-
import { mergeFloat64Arrays } from '../util';
|
|
14
|
-
import { type AudioChunk, type AudioRecorder } from './audio-recorder';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Endpoint to the calls service.
|
|
18
|
-
*/
|
|
19
|
-
const DEFAULT_TRANSCRIPTION_URL = 'https://calls-service.dxos.workers.dev';
|
|
20
|
-
|
|
21
|
-
export type WhisperWord = {
|
|
22
|
-
word: string;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Time in seconds from the start of the audio.
|
|
26
|
-
*/
|
|
27
|
-
start: number;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Time in seconds from the start of the audio.
|
|
31
|
-
*/
|
|
32
|
-
end: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type WhisperSegment = {
|
|
36
|
-
text: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Time in seconds from the start of the audio.
|
|
40
|
-
*/
|
|
41
|
-
start: number;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Time in seconds from the start of the audio.
|
|
45
|
-
*/
|
|
46
|
-
end: number;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Probability of no speech in the segment.
|
|
50
|
-
*/
|
|
51
|
-
no_speech_prob: number;
|
|
52
|
-
|
|
53
|
-
words: WhisperWord[];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type TranscribeConfig = {
|
|
57
|
-
/**
|
|
58
|
-
* Number of chunks to transcribe automatically after.
|
|
59
|
-
*/
|
|
60
|
-
transcribeAfterChunksAmount: number;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Number of chunks to save before the user starts speaking.
|
|
64
|
-
* This is needed to catch the beginning of the user's speech.
|
|
65
|
-
*/
|
|
66
|
-
prefixBufferChunksAmount: number;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Override the transcription endpoint base URL.
|
|
70
|
-
* Defaults to the DXOS calls service.
|
|
71
|
-
*/
|
|
72
|
-
endpoint?: string;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Function that converts a base64-encoded WAV payload into Whisper segments.
|
|
77
|
-
* Allows callers to swap in alternative providers or mock the transport.
|
|
78
|
-
*/
|
|
79
|
-
export type TranscribeFn = (audio: string) => Promise<WhisperSegment[]>;
|
|
80
|
-
|
|
81
|
-
export type TranscriberProps = {
|
|
82
|
-
config: TranscribeConfig;
|
|
83
|
-
recorder: AudioRecorder;
|
|
84
|
-
/**
|
|
85
|
-
* Callback to handle the transcribed segments, after all segment transformers are applied.
|
|
86
|
-
* @param segments - The transcribed segments.
|
|
87
|
-
*/
|
|
88
|
-
onSegments: (segments: ContentBlock.Transcript[]) => Promise<void>;
|
|
89
|
-
/**
|
|
90
|
-
* Optional override of the transcription transport. When provided, supersedes `config.endpoint`.
|
|
91
|
-
*/
|
|
92
|
-
transcribe?: TranscribeFn;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Handles transcription of audio chunks.
|
|
97
|
-
* If user is not speaking, the last `minChunksAmount` chunks are saved and transcribed.
|
|
98
|
-
* If user is speaking, the chunks are added to the buffer until the user is done talking.
|
|
99
|
-
*/
|
|
100
|
-
export class Transcriber extends Resource {
|
|
101
|
-
private _audioChunks: AudioChunk[] = [];
|
|
102
|
-
private _lastTimestamp = 0;
|
|
103
|
-
|
|
104
|
-
private readonly _openTrigger = new Trigger({ autoReset: true });
|
|
105
|
-
|
|
106
|
-
private readonly _config: TranscribeConfig;
|
|
107
|
-
private readonly _recorder: AudioRecorder;
|
|
108
|
-
private readonly _onSegments: TranscriberProps['onSegments'];
|
|
109
|
-
private readonly _transcribeFn?: TranscribeFn;
|
|
110
|
-
|
|
111
|
-
private _recording = false;
|
|
112
|
-
private _transcribeTask?: DeferredTask = undefined;
|
|
113
|
-
|
|
114
|
-
constructor({ config, recorder, onSegments, transcribe }: TranscriberProps) {
|
|
115
|
-
super();
|
|
116
|
-
this._config = config;
|
|
117
|
-
this._recorder = recorder;
|
|
118
|
-
this._onSegments = onSegments;
|
|
119
|
-
this._transcribeFn = transcribe;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
protected override async _open(ctx: Context): Promise<void> {
|
|
123
|
-
log.info('opening');
|
|
124
|
-
this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));
|
|
125
|
-
await this._recorder.start();
|
|
126
|
-
this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());
|
|
127
|
-
this._openTrigger.wake();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
protected override async _close(): Promise<void> {
|
|
131
|
-
log.info('closing');
|
|
132
|
-
this._recording = false;
|
|
133
|
-
this._transcribeTask = undefined;
|
|
134
|
-
await this._recorder.stop();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
startChunksRecording(): void {
|
|
138
|
-
log.info('starting');
|
|
139
|
-
if (this._lifecycleState !== LifecycleState.OPEN) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
this._recording = true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
stopChunksRecording(): void {
|
|
147
|
-
if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
this._recording = false;
|
|
152
|
-
this._transcribeTask?.schedule();
|
|
153
|
-
log.info('stopped');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private _saveAudioChunk(chunk: AudioChunk): void {
|
|
157
|
-
log('saving audio chunk', { chunk });
|
|
158
|
-
this._audioChunks.push(chunk);
|
|
159
|
-
|
|
160
|
-
// Clean the buffer if the user is not speaking and the transcription task is not scheduled.
|
|
161
|
-
if (
|
|
162
|
-
!this._recording &&
|
|
163
|
-
(!this._transcribeTask || !this._transcribeTask.scheduled) &&
|
|
164
|
-
this._audioChunks.length >= this._config.prefixBufferChunksAmount
|
|
165
|
-
) {
|
|
166
|
-
this._audioChunks = this._dropOldChunks();
|
|
167
|
-
} else if (this._audioChunks.length >= this._config.transcribeAfterChunksAmount && this._recording) {
|
|
168
|
-
this._transcribeTask?.schedule();
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private async _transcribe(): Promise<void> {
|
|
173
|
-
log('transcribing', { chunks: this._audioChunks.length });
|
|
174
|
-
const chunks = this._audioChunks;
|
|
175
|
-
this._audioChunks = this._dropOldChunks();
|
|
176
|
-
if (chunks.length === 0) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const audio = await this._mergeAudioChunks(chunks);
|
|
181
|
-
const segments = await this._fetchTranscription(audio);
|
|
182
|
-
if (!Array.isArray(segments) || segments.length === 0) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const alignedSegments = this._alignSegments(segments, chunks);
|
|
187
|
-
if (alignedSegments.length > 0) {
|
|
188
|
-
await this._onSegments(alignedSegments);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private _dropOldChunks(): AudioChunk[] {
|
|
193
|
-
return this._config.prefixBufferChunksAmount > 0
|
|
194
|
-
? this._audioChunks.slice(-this._config.prefixBufferChunksAmount)
|
|
195
|
-
: [];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
199
|
-
private async _mergeAudioChunks(chunks: AudioChunk[]): Promise<string> {
|
|
200
|
-
const file = new WaveFile();
|
|
201
|
-
const wavConfig = this._recorder.wavConfig;
|
|
202
|
-
|
|
203
|
-
file.fromScratch(
|
|
204
|
-
wavConfig.channels,
|
|
205
|
-
wavConfig.sampleRate,
|
|
206
|
-
wavConfig.bitDepthCode,
|
|
207
|
-
mergeFloat64Arrays(chunks.map(({ data }) => data)),
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
return file.toBase64();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
214
|
-
private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {
|
|
215
|
-
if (audio.length === 0) {
|
|
216
|
-
throw new Error('No audio to send for transcribing');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let segments: unknown;
|
|
220
|
-
if (this._transcribeFn) {
|
|
221
|
-
segments = await this._transcribeFn(audio);
|
|
222
|
-
} else {
|
|
223
|
-
// TODO(burdon): Create separate endpoint?
|
|
224
|
-
const endpoint = this._config.endpoint ?? DEFAULT_TRANSCRIPTION_URL;
|
|
225
|
-
const response = await fetch(`${endpoint}/transcribe`, {
|
|
226
|
-
method: 'POST',
|
|
227
|
-
body: JSON.stringify({ audio }),
|
|
228
|
-
headers: {
|
|
229
|
-
'Content-Type': 'application/json',
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
if (!response.ok) {
|
|
234
|
-
throw new Error(`Transcription failed: ${response.statusText}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
({ segments } = (await response.json()) as { segments?: unknown });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!Array.isArray(segments)) {
|
|
241
|
-
throw new Error('Transcription response payload is invalid');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
log.info('transcription response', { segments: segments.length });
|
|
245
|
-
return segments as WhisperSegment[];
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): ContentBlock.Transcript[] {
|
|
249
|
-
const result = alignWhisperSegments(segments, originalChunks, this._lastTimestamp);
|
|
250
|
-
if (result.lastTimestamp !== undefined) {
|
|
251
|
-
this._lastTimestamp = result.lastTimestamp;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return result.transcripts;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Pure helper that filters and absolute-timestamps Whisper segments against a chunk timeline.
|
|
260
|
-
* Exported for testing.
|
|
261
|
-
*
|
|
262
|
-
* @param segments - Raw segments returned by the Whisper service for `originalChunks`.
|
|
263
|
-
* @param originalChunks - Chunks fed into the transcription request (used for the zero timestamp).
|
|
264
|
-
* @param lastTimestamp - Most recent absolute timestamp already emitted; segments whose end
|
|
265
|
-
* falls strictly before this point are deduped out, as are individual words whose start does.
|
|
266
|
-
*/
|
|
267
|
-
export const alignWhisperSegments = (
|
|
268
|
-
segments: WhisperSegment[],
|
|
269
|
-
originalChunks: AudioChunk[],
|
|
270
|
-
lastTimestamp: number,
|
|
271
|
-
): { transcripts: ContentBlock.Transcript[]; lastTimestamp?: number } => {
|
|
272
|
-
if (originalChunks.length === 0) {
|
|
273
|
-
return { transcripts: [] };
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const zeroTimestamp = originalChunks[0].timestamp;
|
|
277
|
-
|
|
278
|
-
const filteredSegments = segments
|
|
279
|
-
.filter((segment) => zeroTimestamp + segment.end * 1_000 >= lastTimestamp)
|
|
280
|
-
.map((segment) => {
|
|
281
|
-
const words = segment.words.filter((word) => zeroTimestamp + word.start * 1_000 >= lastTimestamp);
|
|
282
|
-
return {
|
|
283
|
-
...segment,
|
|
284
|
-
words,
|
|
285
|
-
text: words.map((word) => word.word).join(''),
|
|
286
|
-
start: words.at(0)?.start ?? segment.end,
|
|
287
|
-
};
|
|
288
|
-
})
|
|
289
|
-
.filter((segment) => segment.words.length > 0);
|
|
290
|
-
|
|
291
|
-
if (filteredSegments.length === 0) {
|
|
292
|
-
return { transcripts: [] };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
lastTimestamp: zeroTimestamp + filteredSegments.at(-1)!.end * 1_000,
|
|
297
|
-
transcripts: filteredSegments.map((segment) => ({
|
|
298
|
-
_tag: 'transcript',
|
|
299
|
-
started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),
|
|
300
|
-
text: segment.text.trim(),
|
|
301
|
-
})),
|
|
302
|
-
};
|
|
303
|
-
};
|
package/src/util/array.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
export const mergeFloat64Arrays = (arrays: Float64Array[]) => {
|
|
6
|
-
const newArray = new Float64Array(arrays.reduce((acc, array) => array.length + acc, 0));
|
|
7
|
-
let offset = 0;
|
|
8
|
-
for (const array of arrays) {
|
|
9
|
-
newArray.set(array, offset);
|
|
10
|
-
offset += array.length;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return newArray;
|
|
14
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Identity } from '@dxos/react-client/halo';
|
|
6
|
-
import { type Message } from '@dxos/types';
|
|
7
|
-
|
|
8
|
-
// TODO(thure): Move this?
|
|
9
|
-
export const renderByline =
|
|
10
|
-
(identities: Identity[]) =>
|
|
11
|
-
(message: Message.Message, index: number, debug = false): string[] => {
|
|
12
|
-
if (message.sender.role === 'assistant') {
|
|
13
|
-
// Start/stop block.
|
|
14
|
-
return [message.blocks.find((block) => block._tag === 'transcript')?.text ?? '', ''];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// TODO(burdon): Use link/reference markup for users (with popover).
|
|
18
|
-
// TODO(burdon): Color and avatar.
|
|
19
|
-
const identity = identities.find((identity) => identity.did === message.sender.identityDid);
|
|
20
|
-
const name =
|
|
21
|
-
identity?.profile?.displayName ??
|
|
22
|
-
message.sender.contact?.target?.fullName ??
|
|
23
|
-
message.sender.name ??
|
|
24
|
-
message.sender.email ??
|
|
25
|
-
message.sender.identityDid;
|
|
26
|
-
const blocks = message.blocks.filter((block) => block._tag === 'transcript');
|
|
27
|
-
return [
|
|
28
|
-
// TODO(thure): Use an XML tag with the bits needed here.
|
|
29
|
-
`###### ${name}` + (debug ? ` [${index}]:${message.id}` : ''),
|
|
30
|
-
blocks.map((block) => block.text.trim()).join(' '),
|
|
31
|
-
'',
|
|
32
|
-
];
|
|
33
|
-
};
|
|
File without changes
|
|
File without changes
|
/package/dist/lib/neutral/{enrich-message-F2AOM53F.mjs.map → enrich-message-DSJU4TXC.mjs.map}
RENAMED
|
File without changes
|
|
File without changes
|