@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Skill, Template } from '@dxos/compute';
|
|
6
|
+
import { trim } from '@dxos/util';
|
|
7
|
+
|
|
8
|
+
import { TranscriptOperation } from '#types';
|
|
9
|
+
|
|
10
|
+
const SKILL_KEY = 'org.dxos.skill.transcription';
|
|
11
|
+
|
|
12
|
+
const make = () =>
|
|
13
|
+
Skill.make({
|
|
14
|
+
key: SKILL_KEY,
|
|
15
|
+
name: 'Transcription',
|
|
16
|
+
tools: Skill.toolDefinitions({ operations: [TranscriptOperation.Open, TranscriptOperation.Summarize] }),
|
|
17
|
+
instructions: Template.make({
|
|
18
|
+
source: trim`
|
|
19
|
+
You can open and summarize a meeting transcript.
|
|
20
|
+
`,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const skill: Skill.Definition = {
|
|
25
|
+
key: SKILL_KEY,
|
|
26
|
+
make,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default skill;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Keyword-spotting demo on the browser's Web Speech API (no DXOS transcriber), driven by the real
|
|
7
|
+
* `Mic` button so the mic UX matches the rest of the suite.
|
|
8
|
+
*
|
|
9
|
+
* - `Default` lists the keywords and checks each as it is heard.
|
|
10
|
+
* - The toolbar `Mic` toggles the shared `RecordingSession`; the story observes it (`useRecordingSession`)
|
|
11
|
+
* to start/stop `window.SpeechRecognition` (continuous + interim results). The editor driver stays
|
|
12
|
+
* idle (no editor view for this session).
|
|
13
|
+
* - Matching normalizes punctuation/whitespace; matched rows show a check via `react-ui-list` `Listbox`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
17
|
+
import React, { useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import { useAtomCapabilityState } from '@dxos/app-framework/ui';
|
|
20
|
+
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
21
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
22
|
+
import { useSpeechRecognition } from '@dxos/react-ui-transcription';
|
|
23
|
+
|
|
24
|
+
import { Mic } from '#components';
|
|
25
|
+
import { translations } from '#translations';
|
|
26
|
+
import { TranscriptionCapabilities } from '#types';
|
|
27
|
+
|
|
28
|
+
import { createStoryDecorators } from '../testing';
|
|
29
|
+
|
|
30
|
+
// Stable session key for the Mic button; any non-editor id works (the editor driver ignores it).
|
|
31
|
+
const DOC_ID = 'keyword-detection';
|
|
32
|
+
|
|
33
|
+
// Reads the shared recording flag toggled by the `Mic` button (the editor driver stays idle when no
|
|
34
|
+
// editor view is registered for this session).
|
|
35
|
+
const useRecordingSession = (docId: string): boolean => {
|
|
36
|
+
const [session] = useAtomCapabilityState(TranscriptionCapabilities.RecordingSession);
|
|
37
|
+
return !!session?.recording && session.id === docId;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type StoryArgs = {
|
|
41
|
+
keywords: string[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DefaultStory = ({ keywords }: StoryArgs) => {
|
|
45
|
+
const [matched, setMatched] = useState<string | undefined>(undefined);
|
|
46
|
+
const handleTranscript = (transcript: string) => {
|
|
47
|
+
const match = keywords.find((keyword) => transcript.includes(keyword));
|
|
48
|
+
setMatched(match ?? undefined);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const recording = useRecordingSession(DOC_ID);
|
|
52
|
+
useSpeechRecognition({ active: recording, onTranscript: handleTranscript });
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Panel.Root>
|
|
56
|
+
<Panel.Toolbar asChild>
|
|
57
|
+
<Toolbar.Root>
|
|
58
|
+
<Mic docId={DOC_ID} />
|
|
59
|
+
</Toolbar.Root>
|
|
60
|
+
</Panel.Toolbar>
|
|
61
|
+
|
|
62
|
+
<Panel.Content>
|
|
63
|
+
<Listbox.Root value={matched}>
|
|
64
|
+
<Listbox.Viewport thin padding>
|
|
65
|
+
<Listbox.Content aria-label='Keywords'>
|
|
66
|
+
{keywords.map((keyword) => (
|
|
67
|
+
<Listbox.Item key={keyword} id={keyword}>
|
|
68
|
+
<Listbox.ItemLabel>{keyword}</Listbox.ItemLabel>
|
|
69
|
+
</Listbox.Item>
|
|
70
|
+
))}
|
|
71
|
+
</Listbox.Content>
|
|
72
|
+
</Listbox.Viewport>
|
|
73
|
+
</Listbox.Root>
|
|
74
|
+
</Panel.Content>
|
|
75
|
+
</Panel.Root>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const meta = {
|
|
80
|
+
title: 'plugins/plugin-transcription/stories/Keyword',
|
|
81
|
+
render: DefaultStory,
|
|
82
|
+
decorators: createStoryDecorators(),
|
|
83
|
+
parameters: {
|
|
84
|
+
layout: 'fullscreen',
|
|
85
|
+
controls: { disable: true },
|
|
86
|
+
translations,
|
|
87
|
+
},
|
|
88
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
89
|
+
|
|
90
|
+
export default meta;
|
|
91
|
+
|
|
92
|
+
type Story = StoryObj<typeof meta>;
|
|
93
|
+
|
|
94
|
+
export const Default: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
keywords: ['hello', 'hey kai', 'kai', 'computer', 'dxos', 'composer'].sort(),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* End-to-end transcription into a real plugin-markdown editor: the single editor-output integration
|
|
7
|
+
* test, unifying the former MarkdownTranscription, Pipeline and MicPipeline stories.
|
|
8
|
+
*
|
|
9
|
+
* - `Live` — live microphone via the real `TranscriptionDriver` (toolbar `Mic` button), streaming
|
|
10
|
+
* pending text into the document with inline confirm/cancel + entity linking (requires a mic).
|
|
11
|
+
* - `Recording` / `RecordingIndicator` — seed the pending-text decoration (no mic) to show the
|
|
12
|
+
* recording preview / placeholder deterministically.
|
|
13
|
+
* - `CorrectionOnly` / `WithExtraction` / `Full` — drive `PipelineRuntime` over a scripted transcript
|
|
14
|
+
* (correction → extraction → summarization), writing each stage into the doc; status + per-stage
|
|
15
|
+
* telemetry + summary render in the side panel via the shared `PipelineStatus` component.
|
|
16
|
+
* - Space is seeded with Person/Organization entities so extraction links recognized names.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { type StateEffect } from '@codemirror/state';
|
|
20
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
21
|
+
import * as Effect from 'effect/Effect';
|
|
22
|
+
import * as Stream from 'effect/Stream';
|
|
23
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
24
|
+
|
|
25
|
+
import { Capability, Plugin } from '@dxos/app-framework';
|
|
26
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
27
|
+
import { Surface, useAtomCapability, useCapabilities } from '@dxos/app-framework/ui';
|
|
28
|
+
import { AppActivationEvents, AppCapabilities, AppNode, AppPlugin, AppSpace } from '@dxos/app-toolkit';
|
|
29
|
+
import { AppSurface, useAppGraph } from '@dxos/app-toolkit/ui';
|
|
30
|
+
import { Filter, Query } from '@dxos/echo';
|
|
31
|
+
import { Doc } from '@dxos/echo-doc';
|
|
32
|
+
import { EffectEx } from '@dxos/effect';
|
|
33
|
+
import { DXN } from '@dxos/keys';
|
|
34
|
+
import { ClientCapabilities } from '@dxos/plugin-client';
|
|
35
|
+
import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
|
|
36
|
+
import { Graph, GraphBuilder, Node, NodeMatcher, qualifyId } from '@dxos/plugin-graph';
|
|
37
|
+
import { Markdown, MarkdownCapabilities, MarkdownEvents } from '@dxos/plugin-markdown';
|
|
38
|
+
import { MarkdownPlugin } from '@dxos/plugin-markdown/testing';
|
|
39
|
+
import { SpacePlugin } from '@dxos/plugin-space/testing';
|
|
40
|
+
import { corePlugins } from '@dxos/plugin-testing';
|
|
41
|
+
import { useQuery, useSpaces } from '@dxos/react-client/echo';
|
|
42
|
+
import { useAttentionAttributes } from '@dxos/react-ui-attention';
|
|
43
|
+
import { PipelineStatus } from '@dxos/react-ui-transcription';
|
|
44
|
+
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
45
|
+
import { Text } from '@dxos/schema';
|
|
46
|
+
import {
|
|
47
|
+
type CommitFn,
|
|
48
|
+
PipelineRuntime,
|
|
49
|
+
type Stage,
|
|
50
|
+
type TelemetryEvent,
|
|
51
|
+
TranscriptEvent,
|
|
52
|
+
makeCorrectionStage,
|
|
53
|
+
makeDatabaseLookup,
|
|
54
|
+
makeExtractionStage,
|
|
55
|
+
makeSummarizationStage,
|
|
56
|
+
} from '@dxos/transcription-pipeline';
|
|
57
|
+
import { type ContentBlock, Organization, Person } from '@dxos/types';
|
|
58
|
+
import { seedTestData } from '@dxos/types/testing';
|
|
59
|
+
import { appendPendingText, cancelPendingText, setPendingAnchor, setPendingInterim } from '@dxos/ui-editor';
|
|
60
|
+
import { isNonNullable, trim } from '@dxos/util';
|
|
61
|
+
|
|
62
|
+
import { translations } from '#translations';
|
|
63
|
+
import { TranscriptionCapabilities } from '#types';
|
|
64
|
+
|
|
65
|
+
import { enableQueryIndexes } from '../testing';
|
|
66
|
+
import { TranscriptionPlugin } from '../TranscriptionPlugin';
|
|
67
|
+
|
|
68
|
+
const SAMPLE_CONTENT = trim`
|
|
69
|
+
# Test
|
|
70
|
+
|
|
71
|
+
Place the cursor here, then click the microphone in the toolbar to start recording.
|
|
72
|
+
Live transcription appears inline as greyed text — confirm (✓) to insert it into the document, or cancel (✕) to discard.
|
|
73
|
+
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const TRANSCRIPT = trim`
|
|
77
|
+
So I caught up with Sarah Johnson this morning
|
|
78
|
+
We talked about the DXOS and Cyberdyne partnership
|
|
79
|
+
Amco might join the project later this year
|
|
80
|
+
I should follow up with Michael Chen next week
|
|
81
|
+
`.split('\n');
|
|
82
|
+
|
|
83
|
+
// Story-only plugin exposing Markdown documents in the personal space as direct children of the graph
|
|
84
|
+
// root, so TranscriptionPlugin's toolbar extension can attach the record action to the doc's node.
|
|
85
|
+
const StoryGraphPlugin = () =>
|
|
86
|
+
Plugin.define(
|
|
87
|
+
Plugin.makeMeta({
|
|
88
|
+
key: DXN.make('org.dxos.plugin.transcription.story.pipelineGraph'),
|
|
89
|
+
name: 'Transcription Pipeline Story Graph',
|
|
90
|
+
}),
|
|
91
|
+
).pipe(
|
|
92
|
+
AppPlugin.addAppGraphModule({
|
|
93
|
+
activate: Effect.fnUntraced(function* () {
|
|
94
|
+
const capabilities = yield* Capability.Service;
|
|
95
|
+
const extensions = yield* GraphBuilder.createExtension({
|
|
96
|
+
id: 'storyDocs',
|
|
97
|
+
match: NodeMatcher.whenRoot,
|
|
98
|
+
connector: (_, get) =>
|
|
99
|
+
Effect.gen(function* () {
|
|
100
|
+
// Tolerate the teardown window when stories swap: the Client capability may already be
|
|
101
|
+
// removed while this reactive connector recomputes once more (use `getAll`, not the
|
|
102
|
+
// throwing `get`).
|
|
103
|
+
const [client] = capabilities.getAll(ClientCapabilities.Client);
|
|
104
|
+
const space = client && AppSpace.getPersonalSpace(client);
|
|
105
|
+
if (!space) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const docs = get(space.db.query(Filter.type(Markdown.Document)).atom);
|
|
110
|
+
return docs
|
|
111
|
+
.map((object) => AppNode.makeObject({ get, db: space.db, object, droppable: false }))
|
|
112
|
+
.filter(isNonNullable);
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
return Capability.contributes(AppCapabilities.AppGraphBuilder, extensions);
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
118
|
+
Plugin.make,
|
|
119
|
+
)();
|
|
120
|
+
|
|
121
|
+
type StageId = 'correct' | 'extract' | 'summarize';
|
|
122
|
+
const STAGE_FACTORY: Record<StageId, () => Stage<any, any>> = {
|
|
123
|
+
correct: makeCorrectionStage,
|
|
124
|
+
extract: makeExtractionStage,
|
|
125
|
+
summarize: makeSummarizationStage,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type StoryArgs = {
|
|
129
|
+
/**
|
|
130
|
+
* When set, drives `PipelineRuntime` over the scripted transcript through these stages.
|
|
131
|
+
*/
|
|
132
|
+
stages?: readonly StageId[];
|
|
133
|
+
/**
|
|
134
|
+
* Seeds the pending-text decoration directly (final/interim), bypassing audio capture, to
|
|
135
|
+
* showcase the recording-state preview deterministically (no microphone).
|
|
136
|
+
*/
|
|
137
|
+
seed?: { final?: string; interim?: string };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const DefaultStory = ({ stages, seed }: StoryArgs) => {
|
|
141
|
+
const { graph } = useAppGraph();
|
|
142
|
+
const [space] = useSpaces();
|
|
143
|
+
const [doc] = useQuery(space?.db, Query.type(Markdown.Document));
|
|
144
|
+
const attendableId = doc && qualifyId(Node.RootId, doc.id);
|
|
145
|
+
// Mark the editor attended so its toolbar (and the contributed record action) are active.
|
|
146
|
+
const attentionAttrs = useAttentionAttributes(attendableId);
|
|
147
|
+
const [editorViews] = useCapabilities(MarkdownCapabilities.EditorViews);
|
|
148
|
+
const status = useAtomCapability(TranscriptionCapabilities.PipelineStatus);
|
|
149
|
+
const [telemetry, setTelemetry] = useState<TelemetryEvent[]>([]);
|
|
150
|
+
const [summary, setSummary] = useState<string>();
|
|
151
|
+
|
|
152
|
+
// The status panel is relevant for the live (driver phase) and scripted (telemetry) variants; the
|
|
153
|
+
// seeded recording-preview variants are about the inline editor affordance only.
|
|
154
|
+
const showStatus = !seed;
|
|
155
|
+
|
|
156
|
+
// Story renders the surface directly (no deck), so expand the doc node's actions.
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (attendableId) {
|
|
159
|
+
void Graph.expand(graph, attendableId, 'action');
|
|
160
|
+
}
|
|
161
|
+
}, [graph, attendableId]);
|
|
162
|
+
|
|
163
|
+
// Seed the pending-text decoration once the editor view has registered (no mic required).
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (!attendableId || !editorViews) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Clear any previously-seeded decoration when seeding stops.
|
|
169
|
+
if (!seed) {
|
|
170
|
+
editorViews.get(attendableId)?.view.dispatch({ effects: cancelPendingText.of() });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let cancelled = false;
|
|
174
|
+
const trySeed = () => {
|
|
175
|
+
const entry = editorViews.get(attendableId);
|
|
176
|
+
if (!entry) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
// Anchor the pending block at the end of the document.
|
|
180
|
+
const anchor = entry.view.state.doc.length;
|
|
181
|
+
const effects: StateEffect<unknown>[] = [setPendingAnchor.of({ anchor, placeholder: 'Recording…' })];
|
|
182
|
+
if (seed.final) {
|
|
183
|
+
effects.push(appendPendingText.of(seed.final));
|
|
184
|
+
}
|
|
185
|
+
if (seed.interim) {
|
|
186
|
+
effects.push(setPendingInterim.of(seed.interim));
|
|
187
|
+
}
|
|
188
|
+
entry.view.dispatch({ effects });
|
|
189
|
+
return true;
|
|
190
|
+
};
|
|
191
|
+
if (trySeed()) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const interval = setInterval(() => {
|
|
196
|
+
if (cancelled || trySeed()) {
|
|
197
|
+
clearInterval(interval);
|
|
198
|
+
}
|
|
199
|
+
}, 50);
|
|
200
|
+
return () => {
|
|
201
|
+
cancelled = true;
|
|
202
|
+
clearInterval(interval);
|
|
203
|
+
};
|
|
204
|
+
}, [seed, attendableId, editorViews]);
|
|
205
|
+
|
|
206
|
+
// Run the pipeline over the scripted transcript, writing each stage's output into the document.
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const target = doc?.content?.target;
|
|
209
|
+
if (!stages || !space || !target) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let cancelled = false;
|
|
213
|
+
setTelemetry([]);
|
|
214
|
+
setSummary(undefined);
|
|
215
|
+
|
|
216
|
+
const blocks: ContentBlock.Transcript[] = TRANSCRIPT.map((text, index) => ({
|
|
217
|
+
_tag: 'transcript',
|
|
218
|
+
started: new Date(index * 1000).toISOString(),
|
|
219
|
+
text,
|
|
220
|
+
}));
|
|
221
|
+
const stageList = stages.map((id) => STAGE_FACTORY[id]());
|
|
222
|
+
|
|
223
|
+
// Re-render the whole document body from the (mutated-in-place) blocks; preferring corrected text.
|
|
224
|
+
const render = () => {
|
|
225
|
+
const body = blocks.map((block) => block.corrected ?? block.text).join('\n\n');
|
|
226
|
+
Doc.updateText(target, 'content', `# Transcript\n\n${body}\n`);
|
|
227
|
+
};
|
|
228
|
+
render();
|
|
229
|
+
|
|
230
|
+
const commit: CommitFn = (write, window) =>
|
|
231
|
+
Effect.sync(() => {
|
|
232
|
+
if (cancelled) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const update of write.blockUpdates ?? []) {
|
|
236
|
+
const block = window[update.index];
|
|
237
|
+
if (block) {
|
|
238
|
+
const { index: _index, ...patch } = update;
|
|
239
|
+
Object.assign(block, patch);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (write.transcriptUpdate?.summary) {
|
|
243
|
+
setSummary(write.transcriptUpdate.summary);
|
|
244
|
+
}
|
|
245
|
+
render();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const source = Stream.fromIterable([
|
|
249
|
+
...blocks.map((block) => TranscriptEvent.block(block)),
|
|
250
|
+
TranscriptEvent.silence(5_000),
|
|
251
|
+
]);
|
|
252
|
+
void EffectEx.runPromise(
|
|
253
|
+
PipelineRuntime.run({
|
|
254
|
+
source,
|
|
255
|
+
stages: stageList,
|
|
256
|
+
lookup: makeDatabaseLookup(space.db),
|
|
257
|
+
commit,
|
|
258
|
+
onTelemetry: (event) => {
|
|
259
|
+
if (!cancelled) {
|
|
260
|
+
setTelemetry((prev) => [...prev, event]);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return () => {
|
|
267
|
+
cancelled = true;
|
|
268
|
+
};
|
|
269
|
+
}, [space, doc, stages]);
|
|
270
|
+
|
|
271
|
+
const data = useMemo(() => ({ subject: doc, attendableId: attendableId ?? 'story' }), [doc, attendableId]);
|
|
272
|
+
|
|
273
|
+
if (!doc) {
|
|
274
|
+
return <Loading data={{ doc: !!doc }} />;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!showStatus) {
|
|
278
|
+
return (
|
|
279
|
+
<div className='contents' {...attentionAttrs}>
|
|
280
|
+
<Surface.Surface type={AppSurface.Article} data={data} limit={1} />
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div role='none' className='dx-container grid grid-cols-[1fr_20rem] gap-2' {...attentionAttrs}>
|
|
287
|
+
<div role='none' className='dx-expander'>
|
|
288
|
+
<Surface.Surface type={AppSurface.Article} data={data} limit={1} />
|
|
289
|
+
</div>
|
|
290
|
+
<PipelineStatus
|
|
291
|
+
phase={stages ? 'idle' : (status?.phase ?? 'idle')}
|
|
292
|
+
stages={stages}
|
|
293
|
+
telemetry={telemetry}
|
|
294
|
+
summary={summary}
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const meta = {
|
|
301
|
+
title: 'plugins/plugin-transcription/stories/Pipeline',
|
|
302
|
+
render: DefaultStory,
|
|
303
|
+
decorators: [
|
|
304
|
+
withLayout({ layout: 'fullscreen' }),
|
|
305
|
+
withPluginManager({
|
|
306
|
+
setupEvents: [AppActivationEvents.SetupSettings, MarkdownEvents.SetupExtensions],
|
|
307
|
+
plugins: [
|
|
308
|
+
...corePlugins(),
|
|
309
|
+
ClientPlugin({
|
|
310
|
+
types: [Markdown.Document, Text.Text, Person.Person, Organization.Organization],
|
|
311
|
+
onClientInitialized: ({ client }) =>
|
|
312
|
+
Effect.gen(function* () {
|
|
313
|
+
const { personalSpace } = yield* initializeIdentity(client);
|
|
314
|
+
// Vector indexes so extraction can link recognized Person/Organization names.
|
|
315
|
+
yield* enableQueryIndexes(client.services.services);
|
|
316
|
+
yield* Effect.promise(() => seedTestData(personalSpace));
|
|
317
|
+
personalSpace.db.add(Markdown.make({ name: 'Transcript', content: SAMPLE_CONTENT }));
|
|
318
|
+
yield* Effect.promise(() => personalSpace.db.flush({ indexes: true }));
|
|
319
|
+
}),
|
|
320
|
+
}),
|
|
321
|
+
SpacePlugin({}),
|
|
322
|
+
MarkdownPlugin(),
|
|
323
|
+
StoryGraphPlugin(),
|
|
324
|
+
TranscriptionPlugin(),
|
|
325
|
+
],
|
|
326
|
+
}),
|
|
327
|
+
],
|
|
328
|
+
parameters: {
|
|
329
|
+
layout: 'fullscreen',
|
|
330
|
+
controls: { disable: true },
|
|
331
|
+
translations,
|
|
332
|
+
},
|
|
333
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
334
|
+
|
|
335
|
+
export default meta;
|
|
336
|
+
|
|
337
|
+
type Story = StoryObj<typeof meta>;
|
|
338
|
+
|
|
339
|
+
/** Live microphone via the real driver: streams transcription into the doc (requires a mic). */
|
|
340
|
+
export const Live: Story = {};
|
|
341
|
+
|
|
342
|
+
/** Scripted: correction only (punctuation / capitalization). */
|
|
343
|
+
export const WithCorrection: Story = {
|
|
344
|
+
args: {
|
|
345
|
+
stages: ['correct'],
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/** Scripted: correction + entity extraction (recognized entities become dx-anchor links). */
|
|
350
|
+
export const WithExtraction: Story = {
|
|
351
|
+
args: {
|
|
352
|
+
stages: ['correct', 'extract'],
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/** Scripted: correction + extraction + a cumulative summary (shown in the side panel). */
|
|
357
|
+
export const Full: Story = {
|
|
358
|
+
args: {
|
|
359
|
+
stages: ['correct', 'extract', 'summarize'],
|
|
360
|
+
},
|
|
361
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Streaming file transcription into a message list. Loads an audio file and streams its transcript
|
|
7
|
+
* through the recording engine (Transcriber → runLivePipeline → correction stage) **as it plays** —
|
|
8
|
+
* appending each corrected block as a message rather than waiting for playback to stop.
|
|
9
|
+
*
|
|
10
|
+
* - `Default` plays a hosted m4a; the play/pause + upload toolbar gates playback and capture.
|
|
11
|
+
* - A small chunk threshold drives incremental transcription every few seconds (the streaming stack).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
15
|
+
import * as Effect from 'effect/Effect';
|
|
16
|
+
import React, { type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
17
|
+
|
|
18
|
+
import { IconButton, Panel, ScrollContainer, Toolbar } from '@dxos/react-ui';
|
|
19
|
+
import {
|
|
20
|
+
Transcription,
|
|
21
|
+
renderByline,
|
|
22
|
+
useAudioFile,
|
|
23
|
+
useFeedModelAdapter,
|
|
24
|
+
useRecordingPipeline,
|
|
25
|
+
} from '@dxos/react-ui-transcription';
|
|
26
|
+
import { type CommitFn, type TranscribeConfig, makeCorrectionStage } from '@dxos/transcription-pipeline';
|
|
27
|
+
import { type ContentBlock, Message } from '@dxos/types';
|
|
28
|
+
|
|
29
|
+
import { createStoryDecorators } from '../testing';
|
|
30
|
+
|
|
31
|
+
// Small chunk threshold so the transcriber emits every few seconds while the file plays (streaming),
|
|
32
|
+
// instead of only flushing the whole buffer on stop.
|
|
33
|
+
const STREAMING_TRANSCRIBE_CONFIG: Partial<TranscribeConfig> = {
|
|
34
|
+
transcribeAfterChunksAmount: 25,
|
|
35
|
+
prefixBufferChunksAmount: 10,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// In-memory message buffer + model adapter (production wires up a real space-backed `Feed`).
|
|
39
|
+
const useStoryMessageModel = () => {
|
|
40
|
+
const [messages, setMessages] = useState<Message.Message[]>([]);
|
|
41
|
+
const appendMessage = useCallback((message: Message.Message) => setMessages((prev) => [...prev, message]), []);
|
|
42
|
+
const model = useFeedModelAdapter(renderByline([]), messages);
|
|
43
|
+
return { model, appendMessage };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type StoryArgs = {
|
|
47
|
+
audioUrl: string;
|
|
48
|
+
audioConstraints?: MediaTrackConstraints;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const DefaultStory = ({ audioUrl, audioConstraints }: StoryArgs) => {
|
|
52
|
+
// Decode the file/URL into a track; a play/pause button gates playback + capture.
|
|
53
|
+
const [running, setRunning] = useState(false);
|
|
54
|
+
const [uploadedUrl, setUploadedUrl] = useState<string>();
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
if (uploadedUrl) {
|
|
58
|
+
URL.revokeObjectURL(uploadedUrl);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}, [uploadedUrl]);
|
|
62
|
+
|
|
63
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
64
|
+
const handleFileChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
65
|
+
const file = event.target.files?.[0];
|
|
66
|
+
if (file) {
|
|
67
|
+
setRunning(false);
|
|
68
|
+
setUploadedUrl(URL.createObjectURL(file));
|
|
69
|
+
}
|
|
70
|
+
// Reset so the same file can be re-selected.
|
|
71
|
+
event.target.value = '';
|
|
72
|
+
}, []);
|
|
73
|
+
const { audio, stream, track } = useAudioFile(uploadedUrl ?? audioUrl, audioConstraints);
|
|
74
|
+
|
|
75
|
+
// Pipe the decoded stream into the <audio> element so the user hears playback, mirroring `running`.
|
|
76
|
+
const audioRef = useRef<HTMLAudioElement>(null);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (audioRef.current && stream) {
|
|
79
|
+
audioRef.current.srcObject = stream;
|
|
80
|
+
}
|
|
81
|
+
}, [stream]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!audio) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (running) {
|
|
87
|
+
void audio.play();
|
|
88
|
+
} else {
|
|
89
|
+
audio.pause();
|
|
90
|
+
}
|
|
91
|
+
}, [audio, running]);
|
|
92
|
+
|
|
93
|
+
// Append one message per committed block (keyed by block identity so re-commits don't duplicate).
|
|
94
|
+
const { model, appendMessage } = useStoryMessageModel();
|
|
95
|
+
const seen = useRef(new WeakSet<ContentBlock.Transcript>());
|
|
96
|
+
const commit = useCallback<CommitFn>(
|
|
97
|
+
(write, window) =>
|
|
98
|
+
Effect.sync(() => {
|
|
99
|
+
for (const update of write.blockUpdates ?? []) {
|
|
100
|
+
const block = window[update.index];
|
|
101
|
+
if (!block || seen.current.has(block)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
seen.current.add(block);
|
|
105
|
+
appendMessage(
|
|
106
|
+
Message.make({
|
|
107
|
+
sender: { name: 'You' },
|
|
108
|
+
created: new Date().toISOString(),
|
|
109
|
+
blocks: [{ ...block, text: update.corrected ?? block.text }],
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
[appendMessage],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const stages = useMemo(() => [makeCorrectionStage()], []);
|
|
118
|
+
useRecordingPipeline({
|
|
119
|
+
config: STREAMING_TRANSCRIBE_CONFIG,
|
|
120
|
+
segmentSentences: true,
|
|
121
|
+
active: running,
|
|
122
|
+
track,
|
|
123
|
+
stages,
|
|
124
|
+
commit,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
<audio ref={audioRef} autoPlay />
|
|
130
|
+
<Panel.Root>
|
|
131
|
+
<Panel.Toolbar asChild>
|
|
132
|
+
<Toolbar.Root>
|
|
133
|
+
<IconButton
|
|
134
|
+
iconOnly
|
|
135
|
+
disabled={!stream}
|
|
136
|
+
icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
|
|
137
|
+
label={running ? 'Pause' : 'Play'}
|
|
138
|
+
onClick={() => setRunning((value) => !value)}
|
|
139
|
+
/>
|
|
140
|
+
<input ref={fileInputRef} type='file' accept='audio/*' className='hidden' onChange={handleFileChange} />
|
|
141
|
+
<IconButton
|
|
142
|
+
iconOnly
|
|
143
|
+
icon='ph--upload--regular'
|
|
144
|
+
label='Upload audio'
|
|
145
|
+
onClick={() => fileInputRef.current?.click()}
|
|
146
|
+
/>
|
|
147
|
+
</Toolbar.Root>
|
|
148
|
+
</Panel.Toolbar>
|
|
149
|
+
<ScrollContainer.Root pin>
|
|
150
|
+
<Panel.Content asChild>
|
|
151
|
+
<ScrollContainer.Content>
|
|
152
|
+
<ScrollContainer.Viewport>
|
|
153
|
+
<Transcription model={model} />
|
|
154
|
+
</ScrollContainer.Viewport>
|
|
155
|
+
</ScrollContainer.Content>
|
|
156
|
+
</Panel.Content>
|
|
157
|
+
</ScrollContainer.Root>
|
|
158
|
+
</Panel.Root>
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const meta = {
|
|
164
|
+
title: 'plugins/plugin-transcription/stories/Transcription',
|
|
165
|
+
render: DefaultStory,
|
|
166
|
+
decorators: createStoryDecorators(),
|
|
167
|
+
} satisfies Meta<StoryArgs>;
|
|
168
|
+
|
|
169
|
+
export default meta;
|
|
170
|
+
|
|
171
|
+
type Story = StoryObj<typeof meta>;
|
|
172
|
+
|
|
173
|
+
export const Default: Story = {
|
|
174
|
+
args: {
|
|
175
|
+
// https://learnenglish.britishcouncil.org/general-english/audio-zone/living-london
|
|
176
|
+
audioUrl: 'https://dxos.network/audio-london.m4a',
|
|
177
|
+
},
|
|
178
|
+
};
|