@dxos/plugin-transcription 0.9.0 → 0.9.1-main.c7dcc2e112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/PLUGIN.mdl +20 -0
  2. package/dist/lib/neutral/{TranscriptionArticle-L6XANBH5.mjs → TranscriptionArticle-S56VVOSF.mjs} +3 -3
  3. package/dist/lib/neutral/{TranscriptionArticle-L6XANBH5.mjs.map → TranscriptionArticle-S56VVOSF.mjs.map} +3 -3
  4. package/dist/lib/neutral/TranscriptionPlugin.mjs +6 -4
  5. package/dist/lib/neutral/TranscriptionPlugin.mjs.map +4 -4
  6. package/dist/lib/neutral/capabilities/index.mjs +5 -3
  7. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  8. package/dist/lib/neutral/{chunk-KMT3BMMQ.mjs → chunk-2URVUJT4.mjs} +57 -22
  9. package/dist/lib/neutral/chunk-2URVUJT4.mjs.map +7 -0
  10. package/dist/lib/neutral/chunk-J4ONEYPJ.mjs +8 -0
  11. package/dist/lib/neutral/{chunk-KXJZEYS7.mjs.map → chunk-J4ONEYPJ.mjs.map} +2 -2
  12. package/dist/lib/neutral/{chunk-TO77KAQK.mjs → chunk-XMHH34NI.mjs} +25 -5
  13. package/dist/lib/neutral/{chunk-TO77KAQK.mjs.map → chunk-XMHH34NI.mjs.map} +3 -3
  14. package/dist/lib/neutral/{chunk-KAEXRX3Y.mjs → chunk-XOXHWMXR.mjs} +1 -1
  15. package/dist/lib/neutral/{chunk-TX3NQISZ.mjs → chunk-XWC5BBOY.mjs} +3 -3
  16. package/dist/lib/neutral/{chunk-TX3NQISZ.mjs.map → chunk-XWC5BBOY.mjs.map} +3 -3
  17. package/dist/lib/neutral/chunk-XZVDAYM6.mjs +54 -0
  18. package/dist/lib/neutral/chunk-XZVDAYM6.mjs.map +7 -0
  19. package/dist/lib/neutral/containers/index.mjs +1 -1
  20. package/dist/lib/neutral/{create-RVR7VC33.mjs → create-7NGOP7JP.mjs} +2 -2
  21. package/dist/lib/neutral/enrich-message-F2AOM53F.mjs +24 -0
  22. package/dist/lib/neutral/enrich-message-F2AOM53F.mjs.map +7 -0
  23. package/dist/lib/neutral/hooks/index.mjs +1 -1
  24. package/dist/lib/neutral/index.mjs +7 -5
  25. package/dist/lib/neutral/meta.json +1 -1
  26. package/dist/lib/neutral/meta.mjs +1 -1
  27. package/dist/lib/neutral/{open-A5KXKRH4.mjs → open-42H72C4G.mjs} +3 -3
  28. package/dist/lib/neutral/operations/index.mjs +1 -1
  29. package/dist/lib/neutral/plugin.mjs +2 -2
  30. package/dist/lib/neutral/settings-DUS32HOM.mjs +30 -0
  31. package/dist/lib/neutral/settings-DUS32HOM.mjs.map +7 -0
  32. package/dist/lib/neutral/{summarize-5XPPMVTE.mjs → summarize-45WVUPED.mjs} +3 -3
  33. package/dist/lib/neutral/{summarize-5XPPMVTE.mjs.map → summarize-45WVUPED.mjs.map} +1 -1
  34. package/dist/lib/neutral/{text-content-KBB37HSM.mjs → text-content-XZ3HK4OT.mjs} +9 -7
  35. package/dist/lib/neutral/text-content-XZ3HK4OT.mjs.map +7 -0
  36. package/dist/lib/neutral/{transcriber-OGSF3XO5.mjs → transcriber-KYV6NWI5.mjs} +3 -3
  37. package/dist/lib/neutral/translations.mjs +1 -1
  38. package/dist/lib/neutral/translations.mjs.map +3 -3
  39. package/dist/lib/neutral/types/index.mjs +3 -1
  40. package/dist/types/dx.config.d.ts +28 -0
  41. package/dist/types/dx.config.d.ts.map +1 -0
  42. package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
  43. package/dist/types/src/capabilities/index.d.ts +5 -8
  44. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  45. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  46. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  47. package/dist/types/src/capabilities/settings.d.ts +7 -0
  48. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  49. package/dist/types/src/hooks/useTranscriptionRecording.d.ts.map +1 -1
  50. package/dist/types/src/meta.d.ts +28 -2
  51. package/dist/types/src/meta.d.ts.map +1 -1
  52. package/dist/types/src/operations/enrich-message.d.ts +9 -0
  53. package/dist/types/src/operations/enrich-message.d.ts.map +1 -0
  54. package/dist/types/src/operations/index.d.ts.map +1 -1
  55. package/dist/types/src/stories/LiveTranscription.stories.d.ts +1 -2
  56. package/dist/types/src/stories/LiveTranscription.stories.d.ts.map +1 -1
  57. package/dist/types/src/stories/ProperNounExtraction.stories.d.ts +13 -0
  58. package/dist/types/src/stories/ProperNounExtraction.stories.d.ts.map +1 -0
  59. package/dist/types/src/stories/common.d.ts.map +1 -1
  60. package/dist/types/src/testing/testing.d.ts +0 -4
  61. package/dist/types/src/testing/testing.d.ts.map +1 -1
  62. package/dist/types/src/types/Settings.d.ts +7 -0
  63. package/dist/types/src/types/Settings.d.ts.map +1 -0
  64. package/dist/types/src/types/TranscriptOperation.d.ts +407 -0
  65. package/dist/types/src/types/TranscriptOperation.d.ts.map +1 -1
  66. package/dist/types/src/types/TranscriptionCapabilities.d.ts +3 -0
  67. package/dist/types/src/types/TranscriptionCapabilities.d.ts.map +1 -1
  68. package/dist/types/src/types/index.d.ts +1 -0
  69. package/dist/types/src/types/index.d.ts.map +1 -1
  70. package/dist/types/src/util/index.d.ts +0 -1
  71. package/dist/types/src/util/index.d.ts.map +1 -1
  72. package/dist/types/tsconfig.tsbuildinfo +1 -1
  73. package/dx.config.ts +42 -0
  74. package/package.json +43 -42
  75. package/src/TranscriptionPlugin.test.ts +1 -1
  76. package/src/TranscriptionPlugin.tsx +10 -2
  77. package/src/capabilities/index.ts +2 -7
  78. package/src/capabilities/settings.ts +33 -0
  79. package/src/capabilities/text-content.ts +9 -5
  80. package/src/containers/TranscriptionArticle/TranscriptionArticle.tsx +1 -1
  81. package/src/enrichment/operations.ts +1 -1
  82. package/src/hooks/useTranscriptionRecording.ts +26 -4
  83. package/src/meta.ts +2 -35
  84. package/src/operations/enrich-message.ts +27 -0
  85. package/src/operations/index.ts +1 -0
  86. package/src/operations/summarize.ts +1 -1
  87. package/src/stories/LiveTranscription.stories.tsx +13 -67
  88. package/src/stories/ProperNounExtraction.stories.tsx +99 -0
  89. package/src/stories/common.ts +2 -1
  90. package/src/testing/testing.ts +4 -89
  91. package/src/transcriber/transcription-manager.ts +1 -1
  92. package/src/translations.ts +1 -1
  93. package/src/types/Settings.ts +21 -0
  94. package/src/types/TranscriptOperation.ts +17 -1
  95. package/src/types/TranscriptionCapabilities.ts +7 -2
  96. package/src/types/index.ts +1 -0
  97. package/src/util/index.ts +0 -1
  98. package/dist/lib/neutral/chunk-FHNI26QW.mjs +0 -45
  99. package/dist/lib/neutral/chunk-FHNI26QW.mjs.map +0 -7
  100. package/dist/lib/neutral/chunk-KMT3BMMQ.mjs.map +0 -7
  101. package/dist/lib/neutral/chunk-KXJZEYS7.mjs +0 -8
  102. package/dist/lib/neutral/text-content-KBB37HSM.mjs.map +0 -7
  103. package/dist/types/src/util/monitor-audio-level.d.ts +0 -6
  104. package/dist/types/src/util/monitor-audio-level.d.ts.map +0 -1
  105. package/src/util/monitor-audio-level.ts +0 -58
  106. /package/dist/lib/neutral/{chunk-KAEXRX3Y.mjs.map → chunk-XOXHWMXR.mjs.map} +0 -0
  107. /package/dist/lib/neutral/{create-RVR7VC33.mjs.map → create-7NGOP7JP.mjs.map} +0 -0
  108. /package/dist/lib/neutral/{open-A5KXKRH4.mjs.map → open-42H72C4G.mjs.map} +0 -0
  109. /package/dist/lib/neutral/{transcriber-OGSF3XO5.mjs.map → transcriber-KYV6NWI5.mjs.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/useAudioFile.ts", "../../../src/hooks/useAudioTrack.ts", "../../../src/hooks/useFeedModelAdapter.ts", "../../../src/hooks/useTranscriber.ts", "../../../src/hooks/useTranscriptionRecording.ts", "../../../src/hooks/useVoiceInput.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useState } from 'react';\n\nimport { scheduleTask } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { log } from '@dxos/log';\n\nexport type UseAudioState = {\n audio?: HTMLAudioElement;\n stream?: MediaStream;\n track?: MediaStreamTrack;\n};\n\nexport const useAudioFile = (audioUrl: string, constraints?: MediaTrackConstraints): UseAudioState => {\n const [{ audio, stream, track }, setStream] = useState<UseAudioState>({});\n useEffect(() => {\n const ctx = new Context();\n\n scheduleTask(ctx, async () => {\n // TODO(wittjosiah): Fetch to external url fails in headless storybook test.\n try {\n const response = await fetch(audioUrl);\n const blob = await response.blob();\n const objectUrl = URL.createObjectURL(blob);\n ctx.onDispose(() => {\n URL.revokeObjectURL(objectUrl);\n });\n\n const audio = new Audio();\n audio.src = objectUrl;\n\n await new Promise<void>((resolve, reject) => {\n audio.addEventListener(\n 'error',\n (err) => {\n log.error('error', { err });\n reject(err);\n },\n { once: true },\n );\n audio.addEventListener(\n 'canplay',\n async () => {\n resolve();\n },\n { once: true },\n );\n audio.load();\n });\n\n const audioCtx = new AudioContext();\n\n // Resume AudioContext if it's suspended.\n if (audioCtx.state === 'suspended') {\n await audioCtx.resume();\n }\n\n const destination = audioCtx.createMediaStreamDestination();\n destination.channelCount = 1;\n\n const source = audioCtx.createMediaElementSource(audio);\n source.connect(destination);\n const track = destination.stream.getAudioTracks()[0];\n if (constraints) {\n await track.applyConstraints(constraints).catch((err) => log.catch(err));\n }\n\n // Also connect to speakers so audio is audible.\n setStream({\n audio,\n stream: destination.stream,\n track: destination.stream.getAudioTracks()[0],\n });\n } catch (err) {\n log.catch(err);\n }\n });\n\n return () => {\n void ctx.dispose();\n };\n }, [audioUrl]);\n\n return { audio, stream, track };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useState } from 'react';\n\n/**\n *\n */\n// TODO(burdon): Reconcile with react-ui-experimental and plugin-calls.\nexport const useAudioTrack = (active?: boolean, constraints?: MediaTrackConstraints): MediaStreamTrack | undefined => {\n const [track, setTrack] = useState<MediaStreamTrack>();\n useEffect(() => {\n if (!active) {\n track?.stop();\n setTrack(undefined);\n return;\n }\n\n const initAudio = async () => {\n const audio = new Audio();\n audio.srcObject = await navigator.mediaDevices.getUserMedia({ audio: constraints ?? true });\n const [track] = audio.srcObject.getAudioTracks();\n if (track) {\n setTrack(track);\n }\n };\n\n if (active) {\n void initAudio();\n }\n }, [active]);\n\n return track;\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { Obj } from '@dxos/echo';\n\nimport { type ChunkRenderer, SerializationModel } from '../model';\n\n/**\n * Feeds an array of objects (typically from `useQuery(db, Query.from(feed))`) into a\n * `SerializationModel`, appending each new tail item as the array grows.\n *\n * @param renderer - chunk renderer.\n * @param objects - current snapshot of feed items.\n * @param initialChunks - chunks to seed the model with on first render.\n */\nexport const useFeedModelAdapter = <T extends Obj.Unknown>(\n renderer: ChunkRenderer<T>,\n objects: readonly T[],\n initialChunks: T[] = [],\n): SerializationModel<T> => {\n const model = useMemo(() => new SerializationModel<T>(renderer, initialChunks), [renderer]);\n\n // Seed the model with the current snapshot on first render and whenever the\n // identity of `objects` changes (e.g. feed swapped).\n useEffect(() => {\n for (const chunk of objects) {\n model.appendChunk(chunk);\n }\n }, [model, objects]);\n\n // Append new tail items as the array grows.\n useEffect(() => {\n if (model.chunks.length === objects.length) {\n return;\n }\n const chunk = objects.at(-1);\n if (chunk) {\n model.appendChunk(chunk);\n }\n }, [model, objects.length]);\n\n return model;\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useMemo } from 'react';\n\nimport { useCapabilities } from '@dxos/app-framework/ui';\n\nimport { TranscriptionCapabilities } from '#types';\n\nimport { type Transcriber } from '../transcriber';\n\n/**\n * Records audio while user is speaking and transcribes it after user is done speaking.\n */\nexport const useTranscriber = ({\n audioStreamTrack,\n recorderConfig,\n transcriberConfig,\n onSegments,\n transcribe,\n}: Partial<TranscriptionCapabilities.TranscriberProviderProps>) => {\n const [transcriberProvider] = useCapabilities(TranscriptionCapabilities.TranscriberProvider);\n\n // Initialize audio transcription.\n const transcriber = useMemo<Transcriber | undefined>(() => {\n if (!transcriberProvider || !audioStreamTrack || !onSegments) {\n return undefined;\n }\n\n return transcriberProvider({\n audioStreamTrack,\n recorderConfig,\n transcriberConfig,\n onSegments,\n transcribe,\n });\n }, [transcriberProvider, audioStreamTrack, recorderConfig, transcriberConfig, onSegments, transcribe]);\n\n useEffect(() => {\n return () => {\n void transcriber?.close();\n };\n }, [transcriber]);\n\n return transcriber;\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { createFeedServiceLayer } from '@dxos/client/echo';\nimport { Feed, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { log } from '@dxos/log';\nimport { getSpace } from '@dxos/react-client/echo';\nimport { useIdentity } from '@dxos/react-client/halo';\nimport { Message, type Transcript } from '@dxos/types';\n\nimport { useAudioTrack } from './useAudioTrack';\nimport { useTranscriber } from './useTranscriber';\n\nexport type TranscriptionRecording = {\n recording: boolean;\n toggleRecording: () => void;\n};\n\n/**\n * Drives the recording lifecycle for a transcript: opens an audio track, feeds it through the\n * transcriber, and appends transcribed message batches to the transcript's feed. Returns the\n * recording flag and a toggle callback for the caller to wire to UI.\n */\nexport const useTranscriptionRecording = (transcript: Transcript.Transcript): TranscriptionRecording => {\n const space = getSpace(transcript);\n const identity = useIdentity();\n const feed = transcript.feed.target;\n\n const [recording, setRecording] = useState(false);\n const track = useAudioTrack(recording);\n\n // Append a transcribed message batch to the transcript's feed.\n const handleSegments = useCallback(\n async (blocks: Message.Message['blocks']) => {\n if (!space || !feed || blocks.length === 0) {\n return;\n }\n const sender = identity ? { identityDid: identity.did, name: identity.profile?.displayName } : {};\n const message = Obj.make(Message.Message, {\n sender,\n created: new Date().toISOString(),\n blocks,\n });\n const feedServiceLayer = createFeedServiceLayer(space.queues);\n await Feed.append(feed, [message]).pipe(Effect.provide(feedServiceLayer), EffectEx.runAndForwardErrors);\n },\n [space, feed, identity],\n );\n\n // Drive the transcriber lifecycle off the recording flag + audio track.\n const transcriber = useTranscriber({ audioStreamTrack: track, onSegments: handleSegments });\n useEffect(() => {\n if (!transcriber) {\n return;\n }\n let cancelled = false;\n // If `open()` rejects (e.g. mic permission denied, device init failure) we must\n // clear `recording` — otherwise the toolbar button reports \"stop\" forever while\n // nothing is actually being captured.\n void transcriber.open().then(\n () => {\n if (!cancelled && recording) {\n transcriber.startChunksRecording();\n }\n },\n (err) => {\n if (!cancelled) {\n log.catch(err);\n setRecording(false);\n }\n },\n );\n return () => {\n cancelled = true;\n transcriber.stopChunksRecording();\n };\n }, [transcriber, recording]);\n\n const toggleRecording = useCallback(() => setRecording((value) => !value), []);\n\n return { recording, toggleRecording };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { scheduleMicroTask } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { log } from '@dxos/log';\nimport { useSoundEffect } from '@dxos/react-ui-audio';\n\nimport { type TranscriberProps } from '../transcriber';\nimport { useAudioTrack } from './useAudioTrack';\nimport { useTranscriber } from './useTranscriber';\n\nexport type UseVoiceInputProps = {\n active?: boolean;\n onUpdate: (text: string) => void;\n};\n\n/**\n * Hook for voice input.\n */\nexport const useVoiceInput = ({ active, onUpdate }: UseVoiceInputProps) => {\n const soundStart = useSoundEffect('StartRecording');\n const soundStop = useSoundEffect('StopRecording');\n\n const handleSegments = useCallback<TranscriberProps['onSegments']>(async (segments) => {\n const text = segments.map((str) => str.text.trim().replace(/[^\\w\\s]/g, '')).join(' ');\n onUpdate(text);\n }, []);\n\n // Audio/transcription.\n const [recording, setRecording] = useState(false);\n const track = useAudioTrack(active);\n const transcriber = useTranscriber({\n audioStreamTrack: track,\n onSegments: handleSegments,\n });\n\n // Start/stop transcription.\n useEffect(() => {\n const ctx = new Context();\n scheduleMicroTask(ctx, async () => {\n if (active && transcriber) {\n await transcriber.open();\n log.info('starting...');\n setRecording(true);\n void soundStart.play();\n transcriber.startChunksRecording();\n } else if (!active && transcriber?.isOpen) {\n transcriber?.stopChunksRecording();\n await transcriber?.close();\n log.info('stopped');\n setRecording(false);\n void soundStop.play();\n }\n });\n\n return () => {\n void ctx.dispose();\n };\n }, [active, transcriber]);\n\n return { recording };\n};\n"],
5
- "mappings": "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",
6
- "names": ["useEffect", "useState", "scheduleTask", "Context", "log", "useAudioFile", "objectUrl", "createObjectURL", "ctx", "onDispose", "URL", "revokeObjectURL", "audio", "Audio", "Promise", "resolve", "addEventListener", "err", "reject", "once", "audioCtx", "state", "destination", "source", "createMediaElementSource", "connect", "track", "constraints", "applyConstraints", "catch", "setStream", "stream", "audioUrl", "useEffect", "useState", "useAudioTrack", "active", "constraints", "track", "setTrack", "stop", "undefined", "initAudio", "audio", "Audio", "srcObject", "navigator", "mediaDevices", "getUserMedia", "getAudioTracks", "useEffect", "useMemo", "useFeedModelAdapter", "renderer", "objects", "initialChunks", "model", "useMemo", "SerializationModel", "useEffect", "chunk", "appendChunk", "chunks", "length", "at", "useEffect", "useMemo", "useCapabilities", "TranscriptionCapabilities", "useTranscriber", "audioStreamTrack", "recorderConfig", "transcriberConfig", "onSegments", "transcribe", "transcriberProvider", "TranscriberProvider", "transcriber", "undefined", "close", "Effect", "useCallback", "useEffect", "useState", "createFeedServiceLayer", "Feed", "Obj", "EffectEx", "log", "getSpace", "useIdentity", "Message", "__dxlog_file", "identity", "useIdentity", "feed", "transcript", "target", "recording", "setRecording", "track", "useAudioTrack", "useState", "handleSegments", "useCallback", "name", "message", "Obj", "Message", "sender", "created", "Date", "toISOString", "blocks", "pipe", "Effect", "provide", "feedServiceLayer", "space", "onSegments", "useEffect", "cancelled", "transcriber", "startChunksRecording", "err", "log", "toggleRecording", "useCallback", "useEffect", "useState", "scheduleMicroTask", "Context", "log", "useSoundEffect", "__dxlog_file", "soundStop", "useSoundEffect", "handleSegments", "useCallback", "segments", "str", "trim", "onUpdate", "text", "transcriber", "useTranscriber", "audioStreamTrack", "track", "onSegments", "useEffect", "scheduleMicroTask", "active", "log", "setRecording", "soundStart", "startChunksRecording", "stopChunksRecording", "close"]
4
+ "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useState } from 'react';\n\nimport { scheduleTask } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { log } from '@dxos/log';\n\nexport type UseAudioState = {\n audio?: HTMLAudioElement;\n stream?: MediaStream;\n track?: MediaStreamTrack;\n};\n\nexport const useAudioFile = (audioUrl: string, constraints?: MediaTrackConstraints): UseAudioState => {\n const [{ audio, stream, track }, setStream] = useState<UseAudioState>({});\n useEffect(() => {\n const ctx = new Context();\n\n scheduleTask(ctx, async () => {\n // TODO(wittjosiah): Fetch to external url fails in headless storybook test.\n try {\n const response = await fetch(audioUrl);\n const blob = await response.blob();\n const objectUrl = URL.createObjectURL(blob);\n ctx.onDispose(() => {\n URL.revokeObjectURL(objectUrl);\n });\n\n const audio = new Audio();\n audio.src = objectUrl;\n\n await new Promise<void>((resolve, reject) => {\n audio.addEventListener(\n 'error',\n (err) => {\n log.error('error', { err });\n reject(err);\n },\n { once: true },\n );\n audio.addEventListener(\n 'canplay',\n async () => {\n resolve();\n },\n { once: true },\n );\n audio.load();\n });\n\n const audioCtx = new AudioContext();\n\n // Resume AudioContext if it's suspended.\n if (audioCtx.state === 'suspended') {\n await audioCtx.resume();\n }\n\n const destination = audioCtx.createMediaStreamDestination();\n destination.channelCount = 1;\n\n const source = audioCtx.createMediaElementSource(audio);\n source.connect(destination);\n const track = destination.stream.getAudioTracks()[0];\n if (constraints) {\n await track.applyConstraints(constraints).catch((err) => log.catch(err));\n }\n\n // Also connect to speakers so audio is audible.\n setStream({\n audio,\n stream: destination.stream,\n track: destination.stream.getAudioTracks()[0],\n });\n } catch (err) {\n log.catch(err);\n }\n });\n\n return () => {\n void ctx.dispose();\n };\n }, [audioUrl]);\n\n return { audio, stream, track };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useState } from 'react';\n\n/**\n *\n */\n// TODO(burdon): Reconcile with react-ui-experimental and plugin-calls.\nexport const useAudioTrack = (active?: boolean, constraints?: MediaTrackConstraints): MediaStreamTrack | undefined => {\n const [track, setTrack] = useState<MediaStreamTrack>();\n useEffect(() => {\n if (!active) {\n track?.stop();\n setTrack(undefined);\n return;\n }\n\n const initAudio = async () => {\n const audio = new Audio();\n audio.srcObject = await navigator.mediaDevices.getUserMedia({ audio: constraints ?? true });\n const [track] = audio.srcObject.getAudioTracks();\n if (track) {\n setTrack(track);\n }\n };\n\n if (active) {\n void initAudio();\n }\n }, [active]);\n\n return track;\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { Obj } from '@dxos/echo';\n\nimport { type ChunkRenderer, SerializationModel } from '../model';\n\n/**\n * Feeds an array of objects (typically from `useQuery(db, Query.from(feed))`) into a\n * `SerializationModel`, appending each new tail item as the array grows.\n *\n * @param renderer - chunk renderer.\n * @param objects - current snapshot of feed items.\n * @param initialChunks - chunks to seed the model with on first render.\n */\nexport const useFeedModelAdapter = <T extends Obj.Unknown>(\n renderer: ChunkRenderer<T>,\n objects: readonly T[],\n initialChunks: T[] = [],\n): SerializationModel<T> => {\n const model = useMemo(() => new SerializationModel<T>(renderer, initialChunks), [renderer]);\n\n // Seed the model with the current snapshot on first render and whenever the\n // identity of `objects` changes (e.g. feed swapped).\n useEffect(() => {\n for (const chunk of objects) {\n model.appendChunk(chunk);\n }\n }, [model, objects]);\n\n // Append new tail items as the array grows.\n useEffect(() => {\n if (model.chunks.length === objects.length) {\n return;\n }\n const chunk = objects.at(-1);\n if (chunk) {\n model.appendChunk(chunk);\n }\n }, [model, objects.length]);\n\n return model;\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useEffect, useMemo } from 'react';\n\nimport { useCapabilities } from '@dxos/app-framework/ui';\n\nimport { TranscriptionCapabilities } from '#types';\n\nimport { type Transcriber } from '../transcriber';\n\n/**\n * Records audio while user is speaking and transcribes it after user is done speaking.\n */\nexport const useTranscriber = ({\n audioStreamTrack,\n recorderConfig,\n transcriberConfig,\n onSegments,\n transcribe,\n}: Partial<TranscriptionCapabilities.TranscriberProviderProps>) => {\n const [transcriberProvider] = useCapabilities(TranscriptionCapabilities.TranscriberProvider);\n\n // Initialize audio transcription.\n const transcriber = useMemo<Transcriber | undefined>(() => {\n if (!transcriberProvider || !audioStreamTrack || !onSegments) {\n return undefined;\n }\n\n return transcriberProvider({\n audioStreamTrack,\n recorderConfig,\n transcriberConfig,\n onSegments,\n transcribe,\n });\n }, [transcriberProvider, audioStreamTrack, recorderConfig, transcriberConfig, onSegments, transcribe]);\n\n useEffect(() => {\n return () => {\n void transcriber?.close();\n };\n }, [transcriber]);\n\n return transcriber;\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { useAtomCapability, useOperationInvoker } from '@dxos/app-framework/ui';\nimport { createFeedServiceLayer } from '@dxos/client/echo';\nimport { Feed, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { log } from '@dxos/log';\nimport { getSpace } from '@dxos/react-client/echo';\nimport { useIdentity } from '@dxos/react-client/halo';\nimport { Message, type Transcript } from '@dxos/types';\n\nimport { TranscriptOperation, TranscriptionCapabilities } from '#types';\n\nimport { useAudioTrack } from './useAudioTrack';\nimport { useTranscriber } from './useTranscriber';\n\nexport type TranscriptionRecording = {\n recording: boolean;\n toggleRecording: () => void;\n};\n\n/**\n * Drives the recording lifecycle for a transcript: opens an audio track, feeds it through the\n * transcriber, and appends transcribed message batches to the transcript's feed. Returns the\n * recording flag and a toggle callback for the caller to wire to UI.\n */\nexport const useTranscriptionRecording = (transcript: Transcript.Transcript): TranscriptionRecording => {\n const space = getSpace(transcript);\n const identity = useIdentity();\n const feed = transcript.feed.target;\n\n const [recording, setRecording] = useState(false);\n const track = useAudioTrack(recording);\n const { invokePromise } = useOperationInvoker();\n const settings = useAtomCapability(TranscriptionCapabilities.Settings);\n\n // Append a transcribed message batch to the transcript's feed, enriching it first (when entity\n // extraction is enabled) with references to known entities mentioned in the transcript.\n const handleSegments = useCallback(\n async (blocks: Message.Message['blocks']) => {\n if (!space || !feed || blocks.length === 0) {\n return;\n }\n const sender = identity ? { identityDid: identity.did, name: identity.profile?.displayName } : {};\n let message = Obj.make(Message.Message, {\n sender,\n created: new Date().toISOString(),\n blocks,\n });\n\n // `spaceId` provides the space context required by the process-affinity AiService/Database.\n // Fall back to the raw message if extraction fails (e.g. AI service unavailable).\n if (settings?.entityExtraction ?? true) {\n const { data, error } = await invokePromise(\n TranscriptOperation.EnrichMessage,\n { message },\n { spaceId: space.id },\n );\n if (error) {\n log.warn('entity extraction failed; using raw transcript', { error });\n } else if (data?.message) {\n message = data.message;\n }\n }\n\n const feedServiceLayer = createFeedServiceLayer(space.db);\n await Feed.append(feed, [message]).pipe(Effect.provide(feedServiceLayer), EffectEx.runAndForwardErrors);\n },\n [space, feed, identity, invokePromise, settings],\n );\n\n // Drive the transcriber lifecycle off the recording flag + audio track.\n const transcriber = useTranscriber({ audioStreamTrack: track, onSegments: handleSegments });\n useEffect(() => {\n if (!transcriber) {\n return;\n }\n let cancelled = false;\n // If `open()` rejects (e.g. mic permission denied, device init failure) we must\n // clear `recording` — otherwise the toolbar button reports \"stop\" forever while\n // nothing is actually being captured.\n void transcriber.open().then(\n () => {\n if (!cancelled && recording) {\n transcriber.startChunksRecording();\n }\n },\n (err) => {\n if (!cancelled) {\n log.catch(err);\n setRecording(false);\n }\n },\n );\n return () => {\n cancelled = true;\n transcriber.stopChunksRecording();\n };\n }, [transcriber, recording]);\n\n const toggleRecording = useCallback(() => setRecording((value) => !value), []);\n\n return { recording, toggleRecording };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { scheduleMicroTask } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { log } from '@dxos/log';\nimport { useSoundEffect } from '@dxos/react-ui-audio';\n\nimport { type TranscriberProps } from '../transcriber';\nimport { useAudioTrack } from './useAudioTrack';\nimport { useTranscriber } from './useTranscriber';\n\nexport type UseVoiceInputProps = {\n active?: boolean;\n onUpdate: (text: string) => void;\n};\n\n/**\n * Hook for voice input.\n */\nexport const useVoiceInput = ({ active, onUpdate }: UseVoiceInputProps) => {\n const soundStart = useSoundEffect('StartRecording');\n const soundStop = useSoundEffect('StopRecording');\n\n const handleSegments = useCallback<TranscriberProps['onSegments']>(async (segments) => {\n const text = segments.map((str) => str.text.trim().replace(/[^\\w\\s]/g, '')).join(' ');\n onUpdate(text);\n }, []);\n\n // Audio/transcription.\n const [recording, setRecording] = useState(false);\n const track = useAudioTrack(active);\n const transcriber = useTranscriber({\n audioStreamTrack: track,\n onSegments: handleSegments,\n });\n\n // Start/stop transcription.\n useEffect(() => {\n const ctx = new Context();\n scheduleMicroTask(ctx, async () => {\n if (active && transcriber) {\n await transcriber.open();\n log.info('starting...');\n setRecording(true);\n void soundStart.play();\n transcriber.startChunksRecording();\n } else if (!active && transcriber?.isOpen) {\n transcriber?.stopChunksRecording();\n await transcriber?.close();\n log.info('stopped');\n setRecording(false);\n void soundStop.play();\n }\n });\n\n return () => {\n void ctx.dispose();\n };\n }, [active, transcriber]);\n\n return { recording };\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["useEffect", "useState", "scheduleTask", "Context", "log", "useAudioFile", "objectUrl", "createObjectURL", "ctx", "onDispose", "URL", "revokeObjectURL", "audio", "Audio", "Promise", "resolve", "addEventListener", "err", "reject", "once", "audioCtx", "state", "destination", "source", "createMediaElementSource", "connect", "track", "constraints", "applyConstraints", "catch", "setStream", "stream", "audioUrl", "useEffect", "useState", "useAudioTrack", "active", "constraints", "track", "setTrack", "stop", "undefined", "initAudio", "audio", "Audio", "srcObject", "navigator", "mediaDevices", "getUserMedia", "getAudioTracks", "useEffect", "useMemo", "useFeedModelAdapter", "renderer", "objects", "initialChunks", "model", "useMemo", "SerializationModel", "useEffect", "chunk", "appendChunk", "chunks", "length", "at", "useEffect", "useMemo", "useCapabilities", "TranscriptionCapabilities", "useTranscriber", "audioStreamTrack", "recorderConfig", "transcriberConfig", "onSegments", "transcribe", "transcriberProvider", "TranscriberProvider", "transcriber", "undefined", "close", "Effect", "useCallback", "useEffect", "useState", "useAtomCapability", "useOperationInvoker", "createFeedServiceLayer", "Feed", "Obj", "EffectEx", "log", "getSpace", "useIdentity", "Message", "TranscriptOperation", "TranscriptionCapabilities", "__dxlog_file", "identity", "useIdentity", "feed", "transcript", "target", "recording", "setRecording", "track", "useAudioTrack", "invokePromise", "useOperationInvoker", "settings", "useAtomCapability", "TranscriptionCapabilities", "name", "message", "make", "Message", "sender", "created", "Date", "toISOString", "blocks", "spaceId", "error", "log", "data", "pipe", "Effect", "provide", "feedServiceLayer", "space", "onSegments", "handleSegments", "useEffect", "cancelled", "transcriber", "startChunksRecording", "err", "toggleRecording", "useCallback", "useEffect", "useState", "scheduleMicroTask", "Context", "log", "useSoundEffect", "__dxlog_file", "soundStop", "useSoundEffect", "handleSegments", "useCallback", "segments", "str", "trim", "onUpdate", "text", "transcriber", "useTranscriber", "audioStreamTrack", "track", "onSegments", "useEffect", "scheduleMicroTask", "active", "log", "setRecording", "soundStart", "startChunksRecording", "stopChunksRecording", "close"]
7
7
  }
@@ -32,4 +32,4 @@ export {
32
32
  mergeFloat64Arrays,
33
33
  renderByline
34
34
  };
35
- //# sourceMappingURL=chunk-KAEXRX3Y.mjs.map
35
+ //# sourceMappingURL=chunk-XOXHWMXR.mjs.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  mergeFloat64Arrays
3
- } from "./chunk-KAEXRX3Y.mjs";
3
+ } from "./chunk-XOXHWMXR.mjs";
4
4
 
5
5
  // src/transcriber/media-stream-recorder.ts
6
6
  import { MediaRecorder, register } from "extendable-media-recorder";
@@ -329,7 +329,7 @@ var TranscriptionManager = class extends Resource2 {
329
329
  }
330
330
  setFeed(space, feed) {
331
331
  this._feed = feed;
332
- this._feedServiceLayer = createFeedServiceLayer(space.queues);
332
+ this._feedServiceLayer = createFeedServiceLayer(space.db);
333
333
  return this;
334
334
  }
335
335
  setIdentityDid(did) {
@@ -432,4 +432,4 @@ export {
432
432
  alignWhisperSegments,
433
433
  TranscriptionManager
434
434
  };
435
- //# sourceMappingURL=chunk-TX3NQISZ.mjs.map
435
+ //# sourceMappingURL=chunk-XWC5BBOY.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/transcriber/media-stream-recorder.ts", "../../../src/transcriber/transcriber.ts", "../../../src/transcriber/transcription-manager.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type IBlobEvent, type IMediaRecorder, MediaRecorder, register } from 'extendable-media-recorder';\nimport { WaveFile } from 'wavefile';\n\nimport { synchronized } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { trace } from '@dxos/tracing';\n\nimport { type AudioChunk, type AudioRecorder, type WavConfig } from './audio-recorder';\n\nlet initializingPromise: Promise<void> | undefined;\n\nconst initializeExtendableMediaRecorder = async () => {\n const { connect } = await import('extendable-media-recorder-wav-encoder');\n await register(await connect());\n};\n\nexport type MediaStreamRecorderProps = {\n mediaStreamTrack: MediaStreamTrack;\n config: {\n interval: number;\n };\n};\n\n/**\n * Recorder that uses the MediaContext API and AudioNode API to record audio.\n *\n * It records MediaStream using https://www.npmjs.com/package/extendable-media-recorder.\n */\n@trace.resource()\nexport class MediaStreamRecorder implements AudioRecorder {\n private readonly _mediaStreamTrack: MediaStreamTrack;\n /**\n * Default MediaRecorder implementation do not support wav encoding.\n */\n private readonly _config: MediaStreamRecorderProps['config'];\n private _mediaRecorder?: IMediaRecorder = undefined;\n private _header?: Uint8Array = undefined;\n private _onChunk?: (chunk: AudioChunk) => void;\n\n constructor({ mediaStreamTrack, config }: MediaStreamRecorderProps) {\n this._mediaStreamTrack = mediaStreamTrack;\n this._config = config;\n }\n\n get wavConfig(): WavConfig {\n const settings = this._mediaStreamTrack.getSettings();\n return {\n channels: settings.channelCount ?? 1,\n sampleRate: settings.sampleRate ?? 16000,\n bitDepthCode: settings.sampleSize ? String(settings.sampleSize) : '16',\n };\n }\n\n get mediaStreamTrack() {\n return this._mediaStreamTrack;\n }\n\n setOnChunk(onChunk: (chunk: AudioChunk) => void): void {\n this._onChunk = onChunk;\n }\n\n async start(): Promise<void> {\n await (initializingPromise ??= initializeExtendableMediaRecorder()).catch((err) =>\n log.info('initializeExtendableMediaRecorder', { err }),\n );\n\n if (!this._mediaRecorder) {\n const stream = new MediaStream([this._mediaStreamTrack]);\n this._mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' });\n }\n\n if (this._mediaRecorder.state === 'recording') {\n return;\n }\n invariant(this._onChunk, 'MediaStreamRecorder: onChunk is not set');\n this._mediaRecorder.ondataavailable = (event) => this._ondataavailable(event);\n this._mediaRecorder.start(this._config.interval);\n }\n\n async stop(): Promise<void> {\n if (this._mediaRecorder?.state !== 'recording') {\n return;\n }\n this._mediaRecorder.stop();\n }\n\n @synchronized\n private async _ondataavailable(event: IBlobEvent): Promise<void> {\n const blob = event.data;\n const uint8Array = new Uint8Array(await blob.arrayBuffer());\n\n // First chunk from the MediaRecorder has a header.\n const isHeader =\n uint8Array[0] === 0x52 && // R\n uint8Array[1] === 0x49 && // I\n uint8Array[2] === 0x46 && // F\n uint8Array[3] === 0x46; // F\n let wav: WaveFile;\n\n if (isHeader) {\n wav = new WaveFile(uint8Array);\n this._header = uint8Array.slice(0, 44);\n } else if (this._header) {\n wav = new WaveFile(new Uint8Array([...this._header, ...uint8Array]));\n } else {\n log.warn('MediaStreamRecorder: no header');\n return;\n }\n\n this._onChunk!({\n timestamp: Date.now(),\n data: wav.getSamples(),\n });\n }\n}\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { WaveFile } from 'wavefile';\n\nimport { DeferredTask, Trigger } from '@dxos/async';\nimport { type Context, LifecycleState, Resource } from '@dxos/context';\nimport { log } from '@dxos/log';\nimport { trace } from '@dxos/tracing';\nimport { type ContentBlock } from '@dxos/types';\n\nimport { mergeFloat64Arrays } from '../util';\nimport { type AudioChunk, type AudioRecorder } from './audio-recorder';\n\n/**\n * Endpoint to the calls service.\n */\nconst DEFAULT_TRANSCRIPTION_URL = 'https://calls-service.dxos.workers.dev';\n\nexport type WhisperWord = {\n word: string;\n\n /**\n * Time in seconds from the start of the audio.\n */\n start: number;\n\n /**\n * Time in seconds from the start of the audio.\n */\n end: number;\n};\n\nexport type WhisperSegment = {\n text: string;\n\n /**\n * Time in seconds from the start of the audio.\n */\n start: number;\n\n /**\n * Time in seconds from the start of the audio.\n */\n end: number;\n\n /**\n * Probability of no speech in the segment.\n */\n no_speech_prob: number;\n\n words: WhisperWord[];\n};\n\nexport type TranscribeConfig = {\n /**\n * Number of chunks to transcribe automatically after.\n */\n transcribeAfterChunksAmount: number;\n\n /**\n * Number of chunks to save before the user starts speaking.\n * This is needed to catch the beginning of the user's speech.\n */\n prefixBufferChunksAmount: number;\n\n /**\n * Override the transcription endpoint base URL.\n * Defaults to the DXOS calls service.\n */\n endpoint?: string;\n};\n\n/**\n * Function that converts a base64-encoded WAV payload into Whisper segments.\n * Allows callers to swap in alternative providers or mock the transport.\n */\nexport type TranscribeFn = (audio: string) => Promise<WhisperSegment[]>;\n\nexport type TranscriberProps = {\n config: TranscribeConfig;\n recorder: AudioRecorder;\n /**\n * Callback to handle the transcribed segments, after all segment transformers are applied.\n * @param segments - The transcribed segments.\n */\n onSegments: (segments: ContentBlock.Transcript[]) => Promise<void>;\n /**\n * Optional override of the transcription transport. When provided, supersedes `config.endpoint`.\n */\n transcribe?: TranscribeFn;\n};\n\n/**\n * Handles transcription of audio chunks.\n * If user is not speaking, the last `minChunksAmount` chunks are saved and transcribed.\n * If user is speaking, the chunks are added to the buffer until the user is done talking.\n */\nexport class Transcriber extends Resource {\n private _audioChunks: AudioChunk[] = [];\n private _lastTimestamp = 0;\n\n private readonly _openTrigger = new Trigger({ autoReset: true });\n\n private readonly _config: TranscribeConfig;\n private readonly _recorder: AudioRecorder;\n private readonly _onSegments: TranscriberProps['onSegments'];\n private readonly _transcribeFn?: TranscribeFn;\n\n private _recording = false;\n private _transcribeTask?: DeferredTask = undefined;\n\n constructor({ config, recorder, onSegments, transcribe }: TranscriberProps) {\n super();\n this._config = config;\n this._recorder = recorder;\n this._onSegments = onSegments;\n this._transcribeFn = transcribe;\n }\n\n protected override async _open(ctx: Context): Promise<void> {\n log.info('opening');\n this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));\n await this._recorder.start();\n this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());\n this._openTrigger.wake();\n }\n\n protected override async _close(): Promise<void> {\n log.info('closing');\n this._recording = false;\n this._transcribeTask = undefined;\n await this._recorder.stop();\n }\n\n startChunksRecording(): void {\n log.info('starting');\n if (this._lifecycleState !== LifecycleState.OPEN) {\n return;\n }\n\n this._recording = true;\n }\n\n stopChunksRecording(): void {\n if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {\n return;\n }\n\n this._recording = false;\n this._transcribeTask?.schedule();\n log.info('stopped');\n }\n\n private _saveAudioChunk(chunk: AudioChunk): void {\n log('saving audio chunk', { chunk });\n this._audioChunks.push(chunk);\n\n // Clean the buffer if the user is not speaking and the transcription task is not scheduled.\n if (\n !this._recording &&\n (!this._transcribeTask || !this._transcribeTask.scheduled) &&\n this._audioChunks.length >= this._config.prefixBufferChunksAmount\n ) {\n this._audioChunks = this._dropOldChunks();\n } else if (this._audioChunks.length >= this._config.transcribeAfterChunksAmount && this._recording) {\n this._transcribeTask?.schedule();\n }\n }\n\n private async _transcribe(): Promise<void> {\n log('transcribing', { chunks: this._audioChunks.length });\n const chunks = this._audioChunks;\n this._audioChunks = this._dropOldChunks();\n if (chunks.length === 0) {\n return;\n }\n\n const audio = await this._mergeAudioChunks(chunks);\n const segments = await this._fetchTranscription(audio);\n if (!Array.isArray(segments) || segments.length === 0) {\n return;\n }\n\n const alignedSegments = this._alignSegments(segments, chunks);\n if (alignedSegments.length > 0) {\n await this._onSegments(alignedSegments);\n }\n }\n\n private _dropOldChunks(): AudioChunk[] {\n return this._config.prefixBufferChunksAmount > 0\n ? this._audioChunks.slice(-this._config.prefixBufferChunksAmount)\n : [];\n }\n\n @trace.span({ showInBrowserTimeline: true })\n private async _mergeAudioChunks(chunks: AudioChunk[]): Promise<string> {\n const file = new WaveFile();\n const wavConfig = this._recorder.wavConfig;\n\n file.fromScratch(\n wavConfig.channels,\n wavConfig.sampleRate,\n wavConfig.bitDepthCode,\n mergeFloat64Arrays(chunks.map(({ data }) => data)),\n );\n\n return file.toBase64();\n }\n\n @trace.span({ showInBrowserTimeline: true })\n private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {\n if (audio.length === 0) {\n throw new Error('No audio to send for transcribing');\n }\n\n let segments: unknown;\n if (this._transcribeFn) {\n segments = await this._transcribeFn(audio);\n } else {\n // TODO(burdon): Create separate endpoint?\n const endpoint = this._config.endpoint ?? DEFAULT_TRANSCRIPTION_URL;\n const response = await fetch(`${endpoint}/transcribe`, {\n method: 'POST',\n body: JSON.stringify({ audio }),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Transcription failed: ${response.statusText}`);\n }\n\n ({ segments } = (await response.json()) as { segments?: unknown });\n }\n\n if (!Array.isArray(segments)) {\n throw new Error('Transcription response payload is invalid');\n }\n\n log.info('transcription response', { segments: segments.length });\n return segments as WhisperSegment[];\n }\n\n private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): ContentBlock.Transcript[] {\n const result = alignWhisperSegments(segments, originalChunks, this._lastTimestamp);\n if (result.lastTimestamp !== undefined) {\n this._lastTimestamp = result.lastTimestamp;\n }\n\n return result.transcripts;\n }\n}\n\n/**\n * Pure helper that filters and absolute-timestamps Whisper segments against a chunk timeline.\n * Exported for testing.\n *\n * @param segments - Raw segments returned by the Whisper service for `originalChunks`.\n * @param originalChunks - Chunks fed into the transcription request (used for the zero timestamp).\n * @param lastTimestamp - Most recent absolute timestamp already emitted; segments whose end\n * falls strictly before this point are deduped out, as are individual words whose start does.\n */\nexport const alignWhisperSegments = (\n segments: WhisperSegment[],\n originalChunks: AudioChunk[],\n lastTimestamp: number,\n): { transcripts: ContentBlock.Transcript[]; lastTimestamp?: number } => {\n if (originalChunks.length === 0) {\n return { transcripts: [] };\n }\n\n const zeroTimestamp = originalChunks[0].timestamp;\n\n const filteredSegments = segments\n .filter((segment) => zeroTimestamp + segment.end * 1_000 >= lastTimestamp)\n .map((segment) => {\n const words = segment.words.filter((word) => zeroTimestamp + word.start * 1_000 >= lastTimestamp);\n return {\n ...segment,\n words,\n text: words.map((word) => word.word).join(''),\n start: words.at(0)?.start ?? segment.end,\n };\n })\n .filter((segment) => segment.words.length > 0);\n\n if (filteredSegments.length === 0) {\n return { transcripts: [] };\n }\n\n return {\n lastTimestamp: zeroTimestamp + filteredSegments.at(-1)!.end * 1_000,\n transcripts: filteredSegments.map((segment) => ({\n _tag: 'transcript',\n started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),\n text: segment.text.trim(),\n })),\n };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom, type Registry } from '@effect-atom/atom-react';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { synchronized } from '@dxos/async';\nimport { createFeedServiceLayer, type Space } from '@dxos/client/echo';\nimport { Resource } from '@dxos/context';\nimport { Feed, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { type EdgeHttpClient } from '@dxos/react-edge-client';\nimport { type ContentBlock, Message } from '@dxos/types';\n\nimport { MediaStreamRecorder } from './media-stream-recorder';\nimport { Transcriber } from './transcriber';\n\n/**\n * Length of the chunk in ms.\n */\nconst RECORD_INTERVAL = 200;\n\n/**\n * Number of chunks to save before the user starts speaking.\n */\nconst PREFIXED_CHUNKS_AMOUNT = 10;\n\n/**\n * Number of chunks to transcribe automatically after.\n * Combined should be mess than 25MB or whisper would fail.\n */\nconst TRANSCRIBE_AFTER_CHUNKS_AMOUNT = 50;\n\nexport type TranscriptMessageEnricher = (message: Message.Message) => Promise<Message.Message>;\n\nexport type TranscriptionManagerOptions = {\n edgeClient: EdgeHttpClient;\n registry: Registry.Registry;\n\n /**\n * Enrich the message before it is written to the transcription feed.\n */\n messageEnricher?: TranscriptMessageEnricher;\n};\n\n/**\n * Manages transcription state for a meeting.\n */\nexport class TranscriptionManager extends Resource {\n private readonly _edgeClient: EdgeHttpClient;\n private readonly _messageEnricher?: TranscriptMessageEnricher;\n private readonly _registry: Registry.Registry;\n private _audioStreamTrack?: MediaStreamTrack = undefined;\n private _identityDid?: string = undefined;\n private _mediaRecorder?: MediaStreamRecorder = undefined;\n private _transcriber?: Transcriber = undefined;\n private _feed?: Feed.Feed = undefined;\n private _feedServiceLayer?: Layer.Layer<Feed.FeedService> = undefined;\n private _enabledAtom = Atom.make(false);\n\n constructor(options: TranscriptionManagerOptions) {\n super();\n this._edgeClient = options.edgeClient;\n this._messageEnricher = options.messageEnricher;\n this._registry = options.registry;\n }\n\n get enabled(): Atom.Atom<boolean> {\n return this._enabledAtom;\n }\n\n getEnabled(): boolean {\n return this._registry.get(this._enabledAtom);\n }\n\n setFeed(space: Space, feed: Feed.Feed): this {\n this._feed = feed;\n this._feedServiceLayer = createFeedServiceLayer(space.queues);\n return this;\n }\n\n setIdentityDid(did: string): this {\n if (this._identityDid !== did) {\n this._identityDid = did;\n }\n return this;\n }\n\n setRecording(recording?: boolean): this {\n if (!this.isOpen || !this.getEnabled()) {\n return this;\n }\n\n if (recording) {\n this._transcriber?.startChunksRecording();\n } else {\n this._transcriber?.stopChunksRecording();\n }\n return this;\n }\n\n async setEnabled(enabled: boolean): Promise<void> {\n if (this.getEnabled() === enabled) {\n return;\n }\n\n this._registry.set(this._enabledAtom, enabled ?? false);\n if (enabled) {\n await this._maybeRestartTranscriber();\n } else {\n await this._stopTranscriber();\n }\n }\n\n @synchronized\n async setAudioTrack(track?: MediaStreamTrack): Promise<void> {\n if (this._audioStreamTrack === track) {\n return;\n }\n\n this._audioStreamTrack = track;\n await this._maybeRestartTranscriber();\n }\n\n protected override async _open(): Promise<void> {\n await this._maybeRestartTranscriber();\n }\n\n protected override async _close(): Promise<void> {\n await this._stopTranscriber();\n }\n\n private async _maybeRestartTranscriber(): Promise<void> {\n if (!this._audioStreamTrack || !this.getEnabled() || !this.isOpen) {\n return;\n }\n\n // Reinitialize transcriber if feed or media stream track has changed.\n let needReinit = false;\n if (this._audioStreamTrack !== this._mediaRecorder?.mediaStreamTrack) {\n this._mediaRecorder = new MediaStreamRecorder({\n mediaStreamTrack: this._audioStreamTrack,\n config: { interval: RECORD_INTERVAL },\n });\n needReinit = true;\n }\n if (needReinit) {\n await this._transcriber?.close();\n this._transcriber = new Transcriber({\n config: {\n transcribeAfterChunksAmount: TRANSCRIBE_AFTER_CHUNKS_AMOUNT,\n prefixBufferChunksAmount: PREFIXED_CHUNKS_AMOUNT,\n },\n recorder: this._mediaRecorder,\n onSegments: (segments) => this._onSegments(segments),\n });\n\n await this._transcriber?.open();\n }\n }\n\n private async _stopTranscriber(): Promise<void> {\n await this._transcriber?.close();\n }\n\n private async _onSegments(segments: ContentBlock.Transcript[]): Promise<void> {\n if (!this.isOpen || !this._feed || !this._feedServiceLayer) {\n return;\n }\n\n let block = Obj.make(Message.Message, {\n created: new Date().toISOString(),\n blocks: segments,\n sender: { identityDid: this._identityDid },\n });\n\n if (this._messageEnricher) {\n block = await this._messageEnricher(block);\n }\n\n await Feed.append(this._feed, [block]).pipe(Effect.provide(this._feedServiceLayer), EffectEx.runAndForwardErrors);\n }\n}\n"],
5
- "mappings": ";;;;;AAKA,SAASA,eAAQ,gBAAmB;AAEpC,SAASC,gBAAY;AACrB,SAASC,oBAAiB;AAC1B,SAASC,iBAAW;AACpB,SAASC,WAAK;AAId,SAAIC,aAAAA;;;;;;;AAdJ;AAgBA,IAAA;IACE,oCAAiC,YAAA;AACjC,QAAMC,EAAAA,QAAS,IAAA,MAAMC,OAAAA,uCAAAA;AACvB,QAAA,SAAA,MAAA,QAAA,CAAA;AAeA;AACmBC,IAAAA,sBAAAA,MAAoC;EACrD;;;;EAIQC;EACAC,iBAAuBC;EACvBC,UAAuC;EAE/C;cACOJ,EAAAA,kBAAoBK,OAAAA,GAAAA;AACzB,SAAKC,oBAAUC;AACjB,SAAA,UAAA;EAEA;MACE,YAAMC;AACN,UAAA,WAAO,KAAA,kBAAA,YAAA;WACLC;MACAC,UAAAA,SAAYF,gBAAuB;MACnCG,YAAAA,SAAcH,cAAmB;MACnC,cAAA,SAAA,aAAA,OAAA,SAAA,UAAA,IAAA;IACF;EAEA;MACE,mBAAYR;AACd,WAAA,KAAA;EAEAY;aACOR,SAAWS;AAClB,SAAA,WAAA;EAEA;QACE,QAAOhB;mCAC2CiB,kCAAAA,GAAAA,MAAAA,CAAAA,QAAAA,IAAAA,KAAAA,qCAAAA;MAAI;IAGlD,GAAC,EAAA,YAAKb,YAAgB,GAAA,cAAA,GAAA,IAAA,GAAA,KAAA,CAAA,CAAA;QACxB,CAAA,KAAMc,gBAAaC;YAAa,SAAKhB,IAAAA,YAAiB;QAAC,KAAA;MACvD,CAAA;WAAkDiB,iBAAU,IAAA,cAAA,QAAA;QAAY,UAAA;MAC1E,CAAA;IAEA;QACE,KAAA,eAAA,UAAA,aAAA;AACF;IACAvB;AACA,cAAKO,KAAAA,UAAeiB,2CAAkCC,EAAgB,YAACC,YAAAA,GAAAA,cAAAA,GAAAA,IAAAA,GAAAA,MAAAA,GAAAA,CAAAA,iBAAAA,2CAAAA,EAAAA,CAAAA;AACvE,SAAKnB,eAAeoB,kBAAkB,CAACC,UAAQ,KAAA,iBAAA,KAAA;AACjD,SAAA,eAAA,MAAA,KAAA,QAAA,QAAA;EAEA;QACE,OAASrB;QACP,KAAA,gBAAA,UAAA,aAAA;AACF;IACA;AACF,SAAA,eAAA,KAAA;EAEA;QAEE,iBAAmBsB,OAAI;AACvB,UAAMC,OAAAA,MAAa;AAEnB,UAAA,aAAA,IAAA,WAAA,MAAA,KAAA,YAAmD,CAAA;AAGjDA,UAAAA,WAAa,WAAa,CAAA,MAAI;IAC9BA,WAAW,CAAA,MAAO;IAClBA,WAAW,CAAA,MAAO;IACpB,WAAIC,CAAAA,MAAAA;AAEJ,QAAIC;QACFD,UAAUjC;AACV,YAAKU,IAAAA,SAAUsB,UAAWG;AAC5B,WAAO,UAASzB,WAAS,MAAA,GAAA,EAAA;eACjB,KAAIV,SAAS;gBAAmB,SAAY,IAAA,WAAA;WAAKgC,KAAAA;QAAW,GAAA;MACpE,CAAA,CAAA;WACE7B;AACA,UAAA,KAAA,kCAAA,QAAA,EAAA,YAAA,YAAA,GAAA,cAAA,GAAA,IAAA,GAAA,KAAA,CAAA;AACF;IAEA;SACEiC,SAAAA;MACAL,WAAUM,KAAAA,IAAU;MACtB,MAAA,IAAA,WAAA;IACF,CAAA;EACF;;;;;sBAtFOC,aAAAA;;;;;AC3BP,SAASC,YAAAA,iBAAcC;AACvB,SAAuBC,cAAc,eAAU;AAC/C,SAASC,gBAAW,gBAAY;AAChC,SAASC,OAAAA,YAAK;AAGd,SAASC,SAAAA,cAAAA;;;;;;;AAZT;AA8FA,IAAA,4BAAA;AAMUC,IAAAA,cAAAA,cAAgC,SAAA;EAChCC,eAAAA,CAAAA;EAESC,iBAAe;iBAAyB,IAAA,QAAA;IAAQ,WAAA;EAEhDC,CAAAA;EACAC;EACAC;EACAC;EAETC;EACAC,aAAAA;EAER,kBAAcC;cACP,EAAA,QAAA,UAAA,YAAA,WAAA,GAAA;AACL,UAAKN;AACL,SAAKC,UAAS;AACd,SAAKC,YAAW;AAChB,SAAKC,cAAa;AACpB,SAAA,gBAAA;EAEA;QACEI,MAAQ,KAAC;AACT,IAAAA,KAAI,KAACN,WAAUO,QAAW,EAACC,YAAU,YAAKC,GAAAA,eAAgBD,GAAAA,IAAAA,GAAAA,KAAAA,CAAAA;AAC1D,SAAA,UAAWR,WAAe,CAAA,UAAA,KAAA,gBAAA,KAAA,CAAA;AAC1B,UAAKI,KAAAA,UAAe,MAAOM;AAC3B,SAAKZ,kBAAiB,IAAA,aAAA,KAAA,YAAA,KAAA,YAAA,CAAA;AACxB,SAAA,aAAA,KAAA;EAEA;QACEQ,SAAS;AACT,IAAAA,KAAI,KAACH,WAAa,QAAA,EAAA,YAAA,YAAA,GAAAQ,eAAA,GAAA,IAAA,GAAA,KAAA,CAAA;AAClB,SAAKP,aAAAA;AACL,SAAA,kBAAqBQ;AACvB,UAAA,KAAA,UAAA,KAAA;EAEAC;yBACW;AACT,IAAAP,KAAI,KAAKQ,YAAAA,QAAe,EAAA,YAAKC,YAAqB,GAAAJ,eAAA,GAAA,IAAA,GAAA,KAAA,CAAA;QAChD,KAAA,oBAAA,eAAA,MAAA;AACF;IAEA;AACF,SAAA,aAAA;EAEAK;wBACWF;QACP,KAAA,oBAAA,eAAA,QAAA,CAAA,KAAA,YAAA;AACF;IAEA;AACA,SAAKV,aAAAA;AACLE,SAAIW,iBAAK,SAAA;AACX,IAAAX,KAAA,KAAA,WAAA,QAAA,EAAA,YAAA,YAAA,GAAAK,eAAA,GAAA,IAAA,GAAA,KAAA,CAAA;EAEQF;kBACF,OAAA;SAAwBD,sBAAAA;MAAM;IAClC,GAAA,EAAA,YAAKZ,YAAkBY,GAAAA,eAAAA,GAAAA,IAAAA,GAAAA,KAAAA,CAAAA;AAEvB,SAAA,aAAA,KAAA,KAAA;QAME,CAAA,KAAKZ,eAAe,CAAA,KAAKsB,mBAAc,CAAA,KAAA,gBAAA,cAAA,KAAA,aAAA,UAAA,KAAA,QAAA,0BAAA;AACzC,WAAO,eAAStB,KAAauB,eAAepB;eACrCK,KAAAA,aAAiBgB,UAAAA,KAAAA,QAAAA,+BAAAA,KAAAA,YAAAA;AACxB,WAAA,iBAAA,SAAA;IACF;EAEA;QACEd,cAAI;SAAkBe,gBAAazB;MAAoB,QAAA,KAAA,aAAA;IACvD,GAAA,EAAA,YAAMyB,YAAczB,GAAAA,eAAY,GAAA,IAAA,GAAA,KAAA,CAAA;AAChC,UAAKA,SAAAA,KAAe;AACpB,SAAIyB,eAAa,KAAQ,eAAA;QACvB,OAAA,WAAA,GAAA;AACF;IAEA;AACA,UAAMC,QAAAA,MAAW,KAAM,kBAAKC,MAAoBC;AAChD,UAAKC,WAAMC,MAAQJ,KAAAA,oBAA4B,KAAK;QAClD,CAAA,MAAA,QAAA,QAAA,KAAA,SAAA,WAAA,GAAA;AACF;IAEA;AACA,UAAIK,kBAAgBR,KAAS,eAAG,UAAA,MAAA;QAC9B,gBAAWlB,SAAY0B,GAAAA;AACzB,YAAA,KAAA,YAAA,eAAA;IACF;EAEQT;mBACMnB;AAGd,WAAA,KAAA,QAAA,2BAAA,IAAA,KAAA,aAAA,MAAA,CAAA,KAAA,QAAA,wBAAA,IAAA,CAAA;EAEA;QAEE,kBAAiB6B,QAAAA;AACjB,UAAMC,OAAAA,IAAAA,UAAiB7B;AAEvB8B,UAAKC,YACHF,KAAAA,UAAUG;AAMZ,SAAA,YAAYC,UAAQ,UAAA,UAAA,YAAA,UAAA,cAAA,mBAAA,OAAA,IAAA,CAAA,EAAA,KAAA,MAAA,IAAA,CAAA,CAAA;AACtB,WAAA,KAAA,SAAA;EAEA;QAEE,oBAAqB,OAAG;QACtB,MAAM,WAAU,GAAA;AAClB,YAAA,IAAA,MAAA,mCAAA;IAEA;AACA,QAAI;QACFX,KAAAA,eAAiB;AACnB,iBAAO,MAAA,KAAA,cAAA,KAAA;WACL;AAEA,YAAMY,WAAW,KAAA,QAAY,YAAY;YACvCC,WAAQ,MAAA,MAAA,GAAA,QAAA,eAAA;QACRC,QAAMC;cAAiBb,KAAAA,UAAAA;UAAM;QAC7Bc,CAAAA;iBACE;UACF,gBAAA;QACF;MAEA,CAAA;UACE,CAAA,SAAUC,IAAAA;AACZ,cAAA,IAAA,MAAA,yBAAA,SAAA,UAAA,EAAA;MAEC;AACH,OAAA,EAAA,SAAA,IAAA,MAAA,SAAA,KAAA;IAEA;QACE,CAAA,MAAM,QAAIA,QAAM,GAAA;AAClB,YAAA,IAAA,MAAA,2CAAA;IAEAjC;SAAqCgB,KAAAA,0BAAyB;MAAC,UAAA,SAAA;IAC/D,GAAA,EAAA,YAAOA,YAAAA,GAAAA,eAAAA,GAAAA,KAAAA,GAAAA,KAAAA,CAAAA;AACT,WAAA;EAEQkB;iBACAC,UAASC,gBAAqBpB;AACpC,UAAImB,SAAOE,qBAAkBC,UAAW,gBAAA,KAAA,cAAA;QACtC,OAAK/C,kBAAiB4C,QAAOE;AAC/B,WAAA,iBAAA,OAAA;IAEA;AACF,WAAA,OAAA;EACF;;cA1DSE;SAAOC,KAAAA;;;;cAePD;SAAOC,KAAAA;;;AA6ChB,GAAA,YAAA,WAAA,uBAAA,IAAA;AAcMC,IAAAA,uBAA6B,CAAA,UAAA,gBAAA,kBAAA;MAC/B,eAAO,WAAA,GAAA;WAAEC;MAAgB,aAAA,CAAA;IAC3B;EAEA;AAEA,QAAMC,gBAAAA,eACHC,CAAAA,EAAM;QAEL,mBAAcC,SAAcD,OAAQE,CAAAA,YAASC,gBAAqBC,QAAQ,MAAA,OAASX,aAAAA,EAAAA,IAAAA,CAAAA,YAAAA;AACnF,UAAA,QAAO,QAAA,MAAA,OAAA,CAAA,SAAA,gBAAA,KAAA,QAAA,OAAA,aAAA;WACFQ;MACHI,GAAAA;MACAC;MACAF,MAAAA,MAAOC,IAAS,CAAA,SAAID,KAASH,IAAAA,EAAAA,KAAW,EAAA;MAC1C,OAAA,MAAA,GAAA,CAAA,GAAA,SAAA,QAAA;IAEDD;EAEH,CAAA,EAAA,OAAID,CAAAA,YAAiB9B,QAAM,MAAQ,SAAA,CAAA;MACjC,iBAAO,WAAA,GAAA;WAAE6B;MAAgB,aAAA,CAAA;IAC3B;EAEA;SACEL;IACAK,eAAaC,gBAAiBQ,iBAAkB,GAAA,EAAA,EAAA,MAAA;iBAC9CC,iBAAM,IAAA,CAAA,aAAA;MACNC,MAAAA;MACAH,SAAML,IAAAA,KAAY,gBAAK,QAAA,QAAA,GAAA,EAAA,YAAA;MACzB,MAAA,QAAA,KAAA,KAAA;IACF,EAAA;EACA;;;;AC1SF,SAASS,YAA2B;AACpC,YAAYC,YAAY;AAGxB,SAASC,gBAAAA,qBAAoB;AAC7B,SAASC,8BAA0C;AACnD,SAASC,YAAAA,iBAAgB;AACzB,SAASC,MAAMC,WAAW;AAC1B,SAASC,gBAAgB;AAEzB,SAA4BC,eAAe;;;;;;;AAQ3C,IAAMC,kBAAkB;AAKxB,IAAMC,yBAAyB;AAM/B,IAAMC,iCAAiC;AAiBhC,IAAMC,uBAAN,cAAmCC,UAAAA;EACvBC;EACAC;EACAC;EACTC,oBAAuCC;EACvCC,eAAwBD;EACxBE,iBAAuCF;EACvCG,eAA6BH;EAC7BI,QAAoBJ;EACpBK,oBAAoDL;EACpDM,eAAeC,KAAKC,KAAK,KAAA;EAEjC,YAAYC,SAAsC;AAChD,UAAK;AACL,SAAKb,cAAca,QAAQC;AAC3B,SAAKb,mBAAmBY,QAAQE;AAChC,SAAKb,YAAYW,QAAQG;EAC3B;EAEA,IAAIC,UAA8B;AAChC,WAAO,KAAKP;EACd;EAEAQ,aAAsB;AACpB,WAAO,KAAKhB,UAAUiB,IAAI,KAAKT,YAAY;EAC7C;EAEAU,QAAQC,OAAcC,MAAuB;AAC3C,SAAKd,QAAQc;AACb,SAAKb,oBAAoBc,uBAAuBF,MAAMG,MAAM;AAC5D,WAAO;EACT;EAEAC,eAAeC,KAAmB;AAChC,QAAI,KAAKrB,iBAAiBqB,KAAK;AAC7B,WAAKrB,eAAeqB;IACtB;AACA,WAAO;EACT;EAEAC,aAAaC,WAA2B;AACtC,QAAI,CAAC,KAAKC,UAAU,CAAC,KAAKX,WAAU,GAAI;AACtC,aAAO;IACT;AAEA,QAAIU,WAAW;AACb,WAAKrB,cAAcuB,qBAAAA;IACrB,OAAO;AACL,WAAKvB,cAAcwB,oBAAAA;IACrB;AACA,WAAO;EACT;EAEA,MAAMC,WAAWf,SAAiC;AAChD,QAAI,KAAKC,WAAU,MAAOD,SAAS;AACjC;IACF;AAEA,SAAKf,UAAU+B,IAAI,KAAKvB,cAAcO,WAAW,KAAA;AACjD,QAAIA,SAAS;AACX,YAAM,KAAKiB,yBAAwB;IACrC,OAAO;AACL,YAAM,KAAKC,iBAAgB;IAC7B;EACF;EAEA,MACMC,cAAcC,OAAyC;AAC3D,QAAI,KAAKlC,sBAAsBkC,OAAO;AACpC;IACF;AAEA,SAAKlC,oBAAoBkC;AACzB,UAAM,KAAKH,yBAAwB;EACrC;EAEA,MAAyBI,QAAuB;AAC9C,UAAM,KAAKJ,yBAAwB;EACrC;EAEA,MAAyBK,SAAwB;AAC/C,UAAM,KAAKJ,iBAAgB;EAC7B;EAEA,MAAcD,2BAA0C;AACtD,QAAI,CAAC,KAAK/B,qBAAqB,CAAC,KAAKe,WAAU,KAAM,CAAC,KAAKW,QAAQ;AACjE;IACF;AAGA,QAAIW,aAAa;AACjB,QAAI,KAAKrC,sBAAsB,KAAKG,gBAAgBmC,kBAAkB;AACpE,WAAKnC,iBAAiB,IAAIoC,oBAAoB;QAC5CD,kBAAkB,KAAKtC;QACvBwC,QAAQ;UAAEC,UAAUjD;QAAgB;MACtC,CAAA;AACA6C,mBAAa;IACf;AACA,QAAIA,YAAY;AACd,YAAM,KAAKjC,cAAcsC,MAAAA;AACzB,WAAKtC,eAAe,IAAIuC,YAAY;QAClCH,QAAQ;UACNI,6BAA6BlD;UAC7BmD,0BAA0BpD;QAC5B;QACAqD,UAAU,KAAK3C;QACf4C,YAAY,CAACC,aAAa,KAAKC,YAAYD,QAAAA;MAC7C,CAAA;AAEA,YAAM,KAAK5C,cAAc8C,KAAAA;IAC3B;EACF;EAEA,MAAclB,mBAAkC;AAC9C,UAAM,KAAK5B,cAAcsC,MAAAA;EAC3B;EAEA,MAAcO,YAAYD,UAAoD;AAC5E,QAAI,CAAC,KAAKtB,UAAU,CAAC,KAAKrB,SAAS,CAAC,KAAKC,mBAAmB;AAC1D;IACF;AAEA,QAAI6C,QAAQC,IAAI3C,KAAK4C,QAAQA,SAAS;MACpCC,UAAS,oBAAIC,KAAAA,GAAOC,YAAW;MAC/BC,QAAQT;MACRU,QAAQ;QAAEC,aAAa,KAAKzD;MAAa;IAC3C,CAAA;AAEA,QAAI,KAAKJ,kBAAkB;AACzBqD,cAAQ,MAAM,KAAKrD,iBAAiBqD,KAAAA;IACtC;AAEA,UAAMS,KAAKC,OAAO,KAAKxD,OAAO;MAAC8C;KAAM,EAAEW,KAAYC,eAAQ,KAAKzD,iBAAiB,GAAG0D,SAASC,mBAAmB;EAClH;AACF;;;;",
6
- "names": ["WaveFile", "synchronized", "invariant", "log", "trace", "initializingPromise", "register", "connect", "_mediaStreamTrack", "_mediaRecorder", "_header", "undefined", "_onChunk", "mediaStreamTrack", "_config", "config", "settings", "channels", "sampleRate", "bitDepthCode", "setOnChunk", "onChunk", "err", "stream", "MediaStream", "mimeType", "ondataavailable", "_ondataavailable", "event", "start", "interval", "data", "uint8Array", "wav", "isHeader", "slice", "timestamp", "getSamples", "resource", "DeferredTask", "Trigger", "LifecycleState", "log", "trace", "mergeFloat64Arrays", "_audioChunks", "_lastTimestamp", "_openTrigger", "_config", "_recorder", "_onSegments", "_transcribeFn", "_recording", "_transcribeTask", "config", "log", "setOnChunk", "chunk", "_saveAudioChunk", "DeferredTask", "__dxlog_file", "stop", "startChunksRecording", "_lifecycleState", "LifecycleState", "stopChunksRecording", "info", "_dropOldChunks", "length", "schedule", "chunks", "segments", "_fetchTranscription", "audio", "Array", "isArray", "alignedSegments", "WaveFile", "wavConfig", "file", "fromScratch", "channels", "toBase64", "response", "method", "body", "JSON", "headers", "Error", "_alignSegments", "result", "alignWhisperSegments", "lastTimestamp", "undefined", "span", "showInBrowserTimeline", "originalChunks", "transcripts", "filteredSegments", "filter", "segment", "word", "zeroTimestamp", "start", "words", "text", "map", "_tag", "started", "Atom", "Effect", "synchronized", "createFeedServiceLayer", "Resource", "Feed", "Obj", "EffectEx", "Message", "RECORD_INTERVAL", "PREFIXED_CHUNKS_AMOUNT", "TRANSCRIBE_AFTER_CHUNKS_AMOUNT", "TranscriptionManager", "Resource", "_edgeClient", "_messageEnricher", "_registry", "_audioStreamTrack", "undefined", "_identityDid", "_mediaRecorder", "_transcriber", "_feed", "_feedServiceLayer", "_enabledAtom", "Atom", "make", "options", "edgeClient", "messageEnricher", "registry", "enabled", "getEnabled", "get", "setFeed", "space", "feed", "createFeedServiceLayer", "queues", "setIdentityDid", "did", "setRecording", "recording", "isOpen", "startChunksRecording", "stopChunksRecording", "setEnabled", "set", "_maybeRestartTranscriber", "_stopTranscriber", "setAudioTrack", "track", "_open", "_close", "needReinit", "mediaStreamTrack", "MediaStreamRecorder", "config", "interval", "close", "Transcriber", "transcribeAfterChunksAmount", "prefixBufferChunksAmount", "recorder", "onSegments", "segments", "_onSegments", "open", "block", "Obj", "Message", "created", "Date", "toISOString", "blocks", "sender", "identityDid", "Feed", "append", "pipe", "provide", "EffectEx", "runAndForwardErrors"]
4
+ "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type IBlobEvent, type IMediaRecorder, MediaRecorder, register } from 'extendable-media-recorder';\nimport { WaveFile } from 'wavefile';\n\nimport { synchronized } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { trace } from '@dxos/tracing';\n\nimport { type AudioChunk, type AudioRecorder, type WavConfig } from './audio-recorder';\n\nlet initializingPromise: Promise<void> | undefined;\n\nconst initializeExtendableMediaRecorder = async () => {\n const { connect } = await import('extendable-media-recorder-wav-encoder');\n await register(await connect());\n};\n\nexport type MediaStreamRecorderProps = {\n mediaStreamTrack: MediaStreamTrack;\n config: {\n interval: number;\n };\n};\n\n/**\n * Recorder that uses the MediaContext API and AudioNode API to record audio.\n *\n * It records MediaStream using https://www.npmjs.com/package/extendable-media-recorder.\n */\n@trace.resource()\nexport class MediaStreamRecorder implements AudioRecorder {\n private readonly _mediaStreamTrack: MediaStreamTrack;\n /**\n * Default MediaRecorder implementation do not support wav encoding.\n */\n private readonly _config: MediaStreamRecorderProps['config'];\n private _mediaRecorder?: IMediaRecorder = undefined;\n private _header?: Uint8Array = undefined;\n private _onChunk?: (chunk: AudioChunk) => void;\n\n constructor({ mediaStreamTrack, config }: MediaStreamRecorderProps) {\n this._mediaStreamTrack = mediaStreamTrack;\n this._config = config;\n }\n\n get wavConfig(): WavConfig {\n const settings = this._mediaStreamTrack.getSettings();\n return {\n channels: settings.channelCount ?? 1,\n sampleRate: settings.sampleRate ?? 16000,\n bitDepthCode: settings.sampleSize ? String(settings.sampleSize) : '16',\n };\n }\n\n get mediaStreamTrack() {\n return this._mediaStreamTrack;\n }\n\n setOnChunk(onChunk: (chunk: AudioChunk) => void): void {\n this._onChunk = onChunk;\n }\n\n async start(): Promise<void> {\n await (initializingPromise ??= initializeExtendableMediaRecorder()).catch((err) =>\n log.info('initializeExtendableMediaRecorder', { err }),\n );\n\n if (!this._mediaRecorder) {\n const stream = new MediaStream([this._mediaStreamTrack]);\n this._mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' });\n }\n\n if (this._mediaRecorder.state === 'recording') {\n return;\n }\n invariant(this._onChunk, 'MediaStreamRecorder: onChunk is not set');\n this._mediaRecorder.ondataavailable = (event) => this._ondataavailable(event);\n this._mediaRecorder.start(this._config.interval);\n }\n\n async stop(): Promise<void> {\n if (this._mediaRecorder?.state !== 'recording') {\n return;\n }\n this._mediaRecorder.stop();\n }\n\n @synchronized\n private async _ondataavailable(event: IBlobEvent): Promise<void> {\n const blob = event.data;\n const uint8Array = new Uint8Array(await blob.arrayBuffer());\n\n // First chunk from the MediaRecorder has a header.\n const isHeader =\n uint8Array[0] === 0x52 && // R\n uint8Array[1] === 0x49 && // I\n uint8Array[2] === 0x46 && // F\n uint8Array[3] === 0x46; // F\n let wav: WaveFile;\n\n if (isHeader) {\n wav = new WaveFile(uint8Array);\n this._header = uint8Array.slice(0, 44);\n } else if (this._header) {\n wav = new WaveFile(new Uint8Array([...this._header, ...uint8Array]));\n } else {\n log.warn('MediaStreamRecorder: no header');\n return;\n }\n\n this._onChunk!({\n timestamp: Date.now(),\n data: wav.getSamples(),\n });\n }\n}\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { WaveFile } from 'wavefile';\n\nimport { DeferredTask, Trigger } from '@dxos/async';\nimport { type Context, LifecycleState, Resource } from '@dxos/context';\nimport { log } from '@dxos/log';\nimport { trace } from '@dxos/tracing';\nimport { type ContentBlock } from '@dxos/types';\n\nimport { mergeFloat64Arrays } from '../util';\nimport { type AudioChunk, type AudioRecorder } from './audio-recorder';\n\n/**\n * Endpoint to the calls service.\n */\nconst DEFAULT_TRANSCRIPTION_URL = 'https://calls-service.dxos.workers.dev';\n\nexport type WhisperWord = {\n word: string;\n\n /**\n * Time in seconds from the start of the audio.\n */\n start: number;\n\n /**\n * Time in seconds from the start of the audio.\n */\n end: number;\n};\n\nexport type WhisperSegment = {\n text: string;\n\n /**\n * Time in seconds from the start of the audio.\n */\n start: number;\n\n /**\n * Time in seconds from the start of the audio.\n */\n end: number;\n\n /**\n * Probability of no speech in the segment.\n */\n no_speech_prob: number;\n\n words: WhisperWord[];\n};\n\nexport type TranscribeConfig = {\n /**\n * Number of chunks to transcribe automatically after.\n */\n transcribeAfterChunksAmount: number;\n\n /**\n * Number of chunks to save before the user starts speaking.\n * This is needed to catch the beginning of the user's speech.\n */\n prefixBufferChunksAmount: number;\n\n /**\n * Override the transcription endpoint base URL.\n * Defaults to the DXOS calls service.\n */\n endpoint?: string;\n};\n\n/**\n * Function that converts a base64-encoded WAV payload into Whisper segments.\n * Allows callers to swap in alternative providers or mock the transport.\n */\nexport type TranscribeFn = (audio: string) => Promise<WhisperSegment[]>;\n\nexport type TranscriberProps = {\n config: TranscribeConfig;\n recorder: AudioRecorder;\n /**\n * Callback to handle the transcribed segments, after all segment transformers are applied.\n * @param segments - The transcribed segments.\n */\n onSegments: (segments: ContentBlock.Transcript[]) => Promise<void>;\n /**\n * Optional override of the transcription transport. When provided, supersedes `config.endpoint`.\n */\n transcribe?: TranscribeFn;\n};\n\n/**\n * Handles transcription of audio chunks.\n * If user is not speaking, the last `minChunksAmount` chunks are saved and transcribed.\n * If user is speaking, the chunks are added to the buffer until the user is done talking.\n */\nexport class Transcriber extends Resource {\n private _audioChunks: AudioChunk[] = [];\n private _lastTimestamp = 0;\n\n private readonly _openTrigger = new Trigger({ autoReset: true });\n\n private readonly _config: TranscribeConfig;\n private readonly _recorder: AudioRecorder;\n private readonly _onSegments: TranscriberProps['onSegments'];\n private readonly _transcribeFn?: TranscribeFn;\n\n private _recording = false;\n private _transcribeTask?: DeferredTask = undefined;\n\n constructor({ config, recorder, onSegments, transcribe }: TranscriberProps) {\n super();\n this._config = config;\n this._recorder = recorder;\n this._onSegments = onSegments;\n this._transcribeFn = transcribe;\n }\n\n protected override async _open(ctx: Context): Promise<void> {\n log.info('opening');\n this._recorder.setOnChunk((chunk) => this._saveAudioChunk(chunk));\n await this._recorder.start();\n this._transcribeTask = new DeferredTask(ctx, async () => this._transcribe());\n this._openTrigger.wake();\n }\n\n protected override async _close(): Promise<void> {\n log.info('closing');\n this._recording = false;\n this._transcribeTask = undefined;\n await this._recorder.stop();\n }\n\n startChunksRecording(): void {\n log.info('starting');\n if (this._lifecycleState !== LifecycleState.OPEN) {\n return;\n }\n\n this._recording = true;\n }\n\n stopChunksRecording(): void {\n if (this._lifecycleState !== LifecycleState.OPEN || !this._recording) {\n return;\n }\n\n this._recording = false;\n this._transcribeTask?.schedule();\n log.info('stopped');\n }\n\n private _saveAudioChunk(chunk: AudioChunk): void {\n log('saving audio chunk', { chunk });\n this._audioChunks.push(chunk);\n\n // Clean the buffer if the user is not speaking and the transcription task is not scheduled.\n if (\n !this._recording &&\n (!this._transcribeTask || !this._transcribeTask.scheduled) &&\n this._audioChunks.length >= this._config.prefixBufferChunksAmount\n ) {\n this._audioChunks = this._dropOldChunks();\n } else if (this._audioChunks.length >= this._config.transcribeAfterChunksAmount && this._recording) {\n this._transcribeTask?.schedule();\n }\n }\n\n private async _transcribe(): Promise<void> {\n log('transcribing', { chunks: this._audioChunks.length });\n const chunks = this._audioChunks;\n this._audioChunks = this._dropOldChunks();\n if (chunks.length === 0) {\n return;\n }\n\n const audio = await this._mergeAudioChunks(chunks);\n const segments = await this._fetchTranscription(audio);\n if (!Array.isArray(segments) || segments.length === 0) {\n return;\n }\n\n const alignedSegments = this._alignSegments(segments, chunks);\n if (alignedSegments.length > 0) {\n await this._onSegments(alignedSegments);\n }\n }\n\n private _dropOldChunks(): AudioChunk[] {\n return this._config.prefixBufferChunksAmount > 0\n ? this._audioChunks.slice(-this._config.prefixBufferChunksAmount)\n : [];\n }\n\n @trace.span({ showInBrowserTimeline: true })\n private async _mergeAudioChunks(chunks: AudioChunk[]): Promise<string> {\n const file = new WaveFile();\n const wavConfig = this._recorder.wavConfig;\n\n file.fromScratch(\n wavConfig.channels,\n wavConfig.sampleRate,\n wavConfig.bitDepthCode,\n mergeFloat64Arrays(chunks.map(({ data }) => data)),\n );\n\n return file.toBase64();\n }\n\n @trace.span({ showInBrowserTimeline: true })\n private async _fetchTranscription(audio: string): Promise<WhisperSegment[]> {\n if (audio.length === 0) {\n throw new Error('No audio to send for transcribing');\n }\n\n let segments: unknown;\n if (this._transcribeFn) {\n segments = await this._transcribeFn(audio);\n } else {\n // TODO(burdon): Create separate endpoint?\n const endpoint = this._config.endpoint ?? DEFAULT_TRANSCRIPTION_URL;\n const response = await fetch(`${endpoint}/transcribe`, {\n method: 'POST',\n body: JSON.stringify({ audio }),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Transcription failed: ${response.statusText}`);\n }\n\n ({ segments } = (await response.json()) as { segments?: unknown });\n }\n\n if (!Array.isArray(segments)) {\n throw new Error('Transcription response payload is invalid');\n }\n\n log.info('transcription response', { segments: segments.length });\n return segments as WhisperSegment[];\n }\n\n private _alignSegments(segments: WhisperSegment[], originalChunks: AudioChunk[]): ContentBlock.Transcript[] {\n const result = alignWhisperSegments(segments, originalChunks, this._lastTimestamp);\n if (result.lastTimestamp !== undefined) {\n this._lastTimestamp = result.lastTimestamp;\n }\n\n return result.transcripts;\n }\n}\n\n/**\n * Pure helper that filters and absolute-timestamps Whisper segments against a chunk timeline.\n * Exported for testing.\n *\n * @param segments - Raw segments returned by the Whisper service for `originalChunks`.\n * @param originalChunks - Chunks fed into the transcription request (used for the zero timestamp).\n * @param lastTimestamp - Most recent absolute timestamp already emitted; segments whose end\n * falls strictly before this point are deduped out, as are individual words whose start does.\n */\nexport const alignWhisperSegments = (\n segments: WhisperSegment[],\n originalChunks: AudioChunk[],\n lastTimestamp: number,\n): { transcripts: ContentBlock.Transcript[]; lastTimestamp?: number } => {\n if (originalChunks.length === 0) {\n return { transcripts: [] };\n }\n\n const zeroTimestamp = originalChunks[0].timestamp;\n\n const filteredSegments = segments\n .filter((segment) => zeroTimestamp + segment.end * 1_000 >= lastTimestamp)\n .map((segment) => {\n const words = segment.words.filter((word) => zeroTimestamp + word.start * 1_000 >= lastTimestamp);\n return {\n ...segment,\n words,\n text: words.map((word) => word.word).join(''),\n start: words.at(0)?.start ?? segment.end,\n };\n })\n .filter((segment) => segment.words.length > 0);\n\n if (filteredSegments.length === 0) {\n return { transcripts: [] };\n }\n\n return {\n lastTimestamp: zeroTimestamp + filteredSegments.at(-1)!.end * 1_000,\n transcripts: filteredSegments.map((segment) => ({\n _tag: 'transcript',\n started: new Date(zeroTimestamp + segment.start * 1_000).toISOString(),\n text: segment.text.trim(),\n })),\n };\n};\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom, type Registry } from '@effect-atom/atom-react';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { synchronized } from '@dxos/async';\nimport { createFeedServiceLayer, type Space } from '@dxos/client/echo';\nimport { Resource } from '@dxos/context';\nimport { Feed, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { type EdgeHttpClient } from '@dxos/react-edge-client';\nimport { type ContentBlock, Message } from '@dxos/types';\n\nimport { MediaStreamRecorder } from './media-stream-recorder';\nimport { Transcriber } from './transcriber';\n\n/**\n * Length of the chunk in ms.\n */\nconst RECORD_INTERVAL = 200;\n\n/**\n * Number of chunks to save before the user starts speaking.\n */\nconst PREFIXED_CHUNKS_AMOUNT = 10;\n\n/**\n * Number of chunks to transcribe automatically after.\n * Combined should be mess than 25MB or whisper would fail.\n */\nconst TRANSCRIBE_AFTER_CHUNKS_AMOUNT = 50;\n\nexport type TranscriptMessageEnricher = (message: Message.Message) => Promise<Message.Message>;\n\nexport type TranscriptionManagerOptions = {\n edgeClient: EdgeHttpClient;\n registry: Registry.Registry;\n\n /**\n * Enrich the message before it is written to the transcription feed.\n */\n messageEnricher?: TranscriptMessageEnricher;\n};\n\n/**\n * Manages transcription state for a meeting.\n */\nexport class TranscriptionManager extends Resource {\n private readonly _edgeClient: EdgeHttpClient;\n private readonly _messageEnricher?: TranscriptMessageEnricher;\n private readonly _registry: Registry.Registry;\n private _audioStreamTrack?: MediaStreamTrack = undefined;\n private _identityDid?: string = undefined;\n private _mediaRecorder?: MediaStreamRecorder = undefined;\n private _transcriber?: Transcriber = undefined;\n private _feed?: Feed.Feed = undefined;\n private _feedServiceLayer?: Layer.Layer<Feed.FeedService> = undefined;\n private _enabledAtom = Atom.make(false);\n\n constructor(options: TranscriptionManagerOptions) {\n super();\n this._edgeClient = options.edgeClient;\n this._messageEnricher = options.messageEnricher;\n this._registry = options.registry;\n }\n\n get enabled(): Atom.Atom<boolean> {\n return this._enabledAtom;\n }\n\n getEnabled(): boolean {\n return this._registry.get(this._enabledAtom);\n }\n\n setFeed(space: Space, feed: Feed.Feed): this {\n this._feed = feed;\n this._feedServiceLayer = createFeedServiceLayer(space.db);\n return this;\n }\n\n setIdentityDid(did: string): this {\n if (this._identityDid !== did) {\n this._identityDid = did;\n }\n return this;\n }\n\n setRecording(recording?: boolean): this {\n if (!this.isOpen || !this.getEnabled()) {\n return this;\n }\n\n if (recording) {\n this._transcriber?.startChunksRecording();\n } else {\n this._transcriber?.stopChunksRecording();\n }\n return this;\n }\n\n async setEnabled(enabled: boolean): Promise<void> {\n if (this.getEnabled() === enabled) {\n return;\n }\n\n this._registry.set(this._enabledAtom, enabled ?? false);\n if (enabled) {\n await this._maybeRestartTranscriber();\n } else {\n await this._stopTranscriber();\n }\n }\n\n @synchronized\n async setAudioTrack(track?: MediaStreamTrack): Promise<void> {\n if (this._audioStreamTrack === track) {\n return;\n }\n\n this._audioStreamTrack = track;\n await this._maybeRestartTranscriber();\n }\n\n protected override async _open(): Promise<void> {\n await this._maybeRestartTranscriber();\n }\n\n protected override async _close(): Promise<void> {\n await this._stopTranscriber();\n }\n\n private async _maybeRestartTranscriber(): Promise<void> {\n if (!this._audioStreamTrack || !this.getEnabled() || !this.isOpen) {\n return;\n }\n\n // Reinitialize transcriber if feed or media stream track has changed.\n let needReinit = false;\n if (this._audioStreamTrack !== this._mediaRecorder?.mediaStreamTrack) {\n this._mediaRecorder = new MediaStreamRecorder({\n mediaStreamTrack: this._audioStreamTrack,\n config: { interval: RECORD_INTERVAL },\n });\n needReinit = true;\n }\n if (needReinit) {\n await this._transcriber?.close();\n this._transcriber = new Transcriber({\n config: {\n transcribeAfterChunksAmount: TRANSCRIBE_AFTER_CHUNKS_AMOUNT,\n prefixBufferChunksAmount: PREFIXED_CHUNKS_AMOUNT,\n },\n recorder: this._mediaRecorder,\n onSegments: (segments) => this._onSegments(segments),\n });\n\n await this._transcriber?.open();\n }\n }\n\n private async _stopTranscriber(): Promise<void> {\n await this._transcriber?.close();\n }\n\n private async _onSegments(segments: ContentBlock.Transcript[]): Promise<void> {\n if (!this.isOpen || !this._feed || !this._feedServiceLayer) {\n return;\n }\n\n let block = Obj.make(Message.Message, {\n created: new Date().toISOString(),\n blocks: segments,\n sender: { identityDid: this._identityDid },\n });\n\n if (this._messageEnricher) {\n block = await this._messageEnricher(block);\n }\n\n await Feed.append(this._feed, [block]).pipe(Effect.provide(this._feedServiceLayer), EffectEx.runAndForwardErrors);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["WaveFile", "synchronized", "invariant", "log", "trace", "initializingPromise", "register", "connect", "_mediaStreamTrack", "_mediaRecorder", "_header", "undefined", "_onChunk", "mediaStreamTrack", "_config", "config", "settings", "channels", "sampleRate", "bitDepthCode", "setOnChunk", "onChunk", "err", "stream", "MediaStream", "mimeType", "ondataavailable", "_ondataavailable", "event", "start", "interval", "data", "uint8Array", "wav", "isHeader", "slice", "timestamp", "getSamples", "resource", "DeferredTask", "Trigger", "LifecycleState", "log", "trace", "mergeFloat64Arrays", "_audioChunks", "_lastTimestamp", "_openTrigger", "_config", "_recorder", "_onSegments", "_transcribeFn", "_recording", "_transcribeTask", "config", "log", "setOnChunk", "chunk", "_saveAudioChunk", "DeferredTask", "__dxlog_file", "stop", "startChunksRecording", "_lifecycleState", "LifecycleState", "stopChunksRecording", "info", "_dropOldChunks", "length", "schedule", "chunks", "segments", "_fetchTranscription", "audio", "Array", "isArray", "alignedSegments", "WaveFile", "wavConfig", "file", "fromScratch", "channels", "toBase64", "response", "method", "body", "JSON", "headers", "Error", "_alignSegments", "result", "alignWhisperSegments", "lastTimestamp", "undefined", "span", "showInBrowserTimeline", "originalChunks", "transcripts", "filteredSegments", "filter", "segment", "word", "zeroTimestamp", "start", "words", "text", "map", "_tag", "started", "Atom", "Effect", "synchronized", "createFeedServiceLayer", "Resource", "Feed", "Obj", "EffectEx", "Message", "RECORD_INTERVAL", "PREFIXED_CHUNKS_AMOUNT", "TRANSCRIBE_AFTER_CHUNKS_AMOUNT", "TranscriptionManager", "Resource", "_edgeClient", "_messageEnricher", "_registry", "_audioStreamTrack", "undefined", "_identityDid", "_mediaRecorder", "_transcriber", "_feed", "_feedServiceLayer", "_enabledAtom", "Atom", "make", "options", "edgeClient", "messageEnricher", "registry", "enabled", "getEnabled", "get", "setFeed", "space", "feed", "createFeedServiceLayer", "db", "setIdentityDid", "did", "setRecording", "recording", "isOpen", "startChunksRecording", "stopChunksRecording", "setEnabled", "set", "_maybeRestartTranscriber", "_stopTranscriber", "setAudioTrack", "track", "_open", "_close", "needReinit", "mediaStreamTrack", "MediaStreamRecorder", "config", "interval", "close", "Transcriber", "transcribeAfterChunksAmount", "prefixBufferChunksAmount", "recorder", "onSegments", "segments", "_onSegments", "open", "block", "Obj", "Message", "created", "Date", "toISOString", "blocks", "sender", "identityDid", "Feed", "append", "pipe", "provide", "EffectEx", "runAndForwardErrors"]
7
7
  }
@@ -0,0 +1,54 @@
1
+ // src/meta.ts
2
+ import { Plugin } from "@dxos/app-framework";
3
+
4
+ // dx.config.ts
5
+ import { Config2 } from "@dxos/app-framework/config";
6
+ import { trim } from "@dxos/util";
7
+ var dx_config_default = Config2.make({
8
+ plugin: {
9
+ key: "org.dxos.plugin.transcription",
10
+ name: "Transcription",
11
+ author: "DXOS",
12
+ description: trim`
13
+ Real-time voice-to-text transcription for DXOS Composer.
14
+ Captures microphone audio in short PCM chunks, batches them into WAV payloads, and streams
15
+ the results to a Whisper-compatible speech-recognition endpoint.
16
+ Transcribed segments are timestamped, deduplicated across chunk boundaries, and persisted
17
+ as Message objects in an ECHO Feed so that every participant in a shared space sees the
18
+ live transcript without any manual sync step.
19
+
20
+ Each recording session is represented as a Transcript object linked to its Feed.
21
+ The read-only editor renders messages with speaker bylines and wall-clock timestamps,
22
+ supporting markdown decoration and document preview.
23
+ An optional message enricher pipeline lets plugins annotate messages before they are
24
+ committed — for example, to attach speaker identity from a calls service.
25
+
26
+ The plugin ships two blueprint-compatible operations: Open (loads and formats a transcript
27
+ for reading by an AI assistant) and Summarize (sends the transcript text to an LLM and
28
+ returns a structured Markdown summary with key points, verbatim quotes, and actionable
29
+ tasks extracted from the conversation).
30
+
31
+ A sentence-normalization pass merges raw word-level tokens from the recognizer into
32
+ grammatically complete sentences, improving readability of the stored transcript without
33
+ altering the underlying audio timeline.
34
+ `,
35
+ source: "https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-transcription",
36
+ icon: {
37
+ key: "ph--microphone--regular",
38
+ hue: "sky"
39
+ },
40
+ spec: "PLUGIN.mdl",
41
+ tags: [
42
+ "labs"
43
+ ],
44
+ screenshots: []
45
+ }
46
+ });
47
+
48
+ // src/meta.ts
49
+ var meta = Plugin.getMetaFromConfig(dx_config_default);
50
+
51
+ export {
52
+ meta
53
+ };
54
+ //# sourceMappingURL=chunk-XZVDAYM6.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/meta.ts", "../../../dx.config.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport config from '../dx.config';\n\nexport const meta = Plugin.getMetaFromConfig(config);\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { Config2 } from '@dxos/app-framework/config';\nimport { trim } from '@dxos/util';\n\nexport default Config2.make({\n plugin: {\n key: 'org.dxos.plugin.transcription',\n name: 'Transcription',\n author: 'DXOS',\n description: trim`\n Real-time voice-to-text transcription for DXOS Composer.\n Captures microphone audio in short PCM chunks, batches them into WAV payloads, and streams\n the results to a Whisper-compatible speech-recognition endpoint.\n Transcribed segments are timestamped, deduplicated across chunk boundaries, and persisted\n as Message objects in an ECHO Feed so that every participant in a shared space sees the\n live transcript without any manual sync step.\n\n Each recording session is represented as a Transcript object linked to its Feed.\n The read-only editor renders messages with speaker bylines and wall-clock timestamps,\n supporting markdown decoration and document preview.\n An optional message enricher pipeline lets plugins annotate messages before they are\n committed — for example, to attach speaker identity from a calls service.\n\n The plugin ships two blueprint-compatible operations: Open (loads and formats a transcript\n for reading by an AI assistant) and Summarize (sends the transcript text to an LLM and\n returns a structured Markdown summary with key points, verbatim quotes, and actionable\n tasks extracted from the conversation).\n\n A sentence-normalization pass merges raw word-level tokens from the recognizer into\n grammatically complete sentences, improving readability of the stored transcript without\n altering the underlying audio timeline.\n `,\n source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-transcription',\n icon: { key: 'ph--microphone--regular', hue: 'sky' },\n spec: 'PLUGIN.mdl',\n tags: ['labs'],\n screenshots: [],\n },\n});\n"],
5
+ "mappings": ";AAIA,SAASA,cAAc;;;ACAvB,SAASC,eAAe;AACxB,SAASC,YAAY;AAErB,IAAA,oBAAeD,QAAQE,KAAK;EAC1BC,QAAQ;IACNC,KAAK;IACLC,MAAM;IACNC,QAAQ;IACRC,aAAaN;;;;;;;;;;;;;;;;;;;;;;;IAuBbO,QAAQ;IACRC,MAAM;MAAEL,KAAK;MAA2BM,KAAK;IAAM;IACnDC,MAAM;IACNC,MAAM;MAAC;;IACPC,aAAa,CAAA;EACf;AACF,CAAA;;;ADjCO,IAAMC,OAAOC,OAAOC,kBAAkBC,iBAAAA;",
6
+ "names": ["Plugin", "Config2", "trim", "make", "plugin", "key", "name", "author", "description", "source", "icon", "hue", "spec", "tags", "screenshots", "meta", "Plugin", "getMetaFromConfig", "config"]
7
+ }
@@ -2,7 +2,7 @@ import "../chunk-J5LGTIGS.mjs";
2
2
 
3
3
  // src/containers/index.ts
4
4
  import { lazy } from "react";
5
- var TranscriptionArticle = lazy(() => import("../TranscriptionArticle-L6XANBH5.mjs"));
5
+ var TranscriptionArticle = lazy(() => import("../TranscriptionArticle-S56VVOSF.mjs"));
6
6
  export {
7
7
  TranscriptionArticle
8
8
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TranscriptOperation_exports
3
- } from "./chunk-KMT3BMMQ.mjs";
3
+ } from "./chunk-2URVUJT4.mjs";
4
4
  import "./chunk-J5LGTIGS.mjs";
5
5
 
6
6
  // src/operations/create.ts
@@ -18,4 +18,4 @@ var create_default = handler;
18
18
  export {
19
19
  create_default as default
20
20
  };
21
- //# sourceMappingURL=create-RVR7VC33.mjs.map
21
+ //# sourceMappingURL=create-7NGOP7JP.mjs.map
@@ -0,0 +1,24 @@
1
+ import {
2
+ TranscriptOperation_exports
3
+ } from "./chunk-2URVUJT4.mjs";
4
+ import "./chunk-J5LGTIGS.mjs";
5
+
6
+ // src/operations/enrich-message.ts
7
+ import * as Effect from "effect/Effect";
8
+ import { enrichTranscriptMessage } from "@dxos/assistant/extraction";
9
+ import { Operation } from "@dxos/compute";
10
+ import { Database } from "@dxos/echo";
11
+ var handler = TranscriptOperation_exports.EnrichMessage.pipe(Operation.withHandler(Effect.fnUntraced(function* ({ message }) {
12
+ const { db } = yield* Database.Service;
13
+ const enriched = yield* enrichTranscriptMessage(message, {
14
+ db
15
+ });
16
+ return {
17
+ message: enriched
18
+ };
19
+ })));
20
+ var enrich_message_default = handler;
21
+ export {
22
+ enrich_message_default as default
23
+ };
24
+ //# sourceMappingURL=enrich-message-F2AOM53F.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/operations/enrich-message.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { enrichTranscriptMessage } from '@dxos/assistant/extraction';\nimport { Operation } from '@dxos/compute';\nimport { Database } from '@dxos/echo';\n\nimport { TranscriptOperation } from '../types';\n\n/**\n * Extracts proper nouns from a transcript message and links them to objects in the space via\n * full-text search, rewriting the text with reference links.\n */\nconst handler: Operation.WithHandler<typeof TranscriptOperation.EnrichMessage> = TranscriptOperation.EnrichMessage.pipe(\n Operation.withHandler(\n Effect.fnUntraced(function* ({ message }) {\n const { db } = yield* Database.Service;\n const enriched = yield* enrichTranscriptMessage(message, { db });\n return { message: enriched };\n }),\n ),\n);\n\nexport default handler;\n"],
5
+ "mappings": ";;;;;;AAIA,YAAYA,YAAY;AAExB,SAASC,+BAA+B;AACxC,SAASC,iBAAiB;AAC1B,SAASC,gBAAgB;AAQzB,IAAMC,UAA2EC,4BAAoBC,cAAcC,KACjHC,UAAUC,YACDC,kBAAW,WAAW,EAAEC,QAAO,GAAE;AACtC,QAAM,EAAEC,GAAE,IAAK,OAAOC,SAASC;AAC/B,QAAMC,WAAW,OAAOC,wBAAwBL,SAAS;IAAEC;EAAG,CAAA;AAC9D,SAAO;IAAED,SAASI;EAAS;AAC7B,CAAA,CAAA,CAAA;AAIJ,IAAA,yBAAeX;",
6
+ "names": ["Effect", "enrichTranscriptMessage", "Operation", "Database", "handler", "TranscriptOperation", "EnrichMessage", "pipe", "Operation", "withHandler", "fnUntraced", "message", "db", "Database", "Service", "enriched", "enrichTranscriptMessage"]
7
+ }
@@ -5,7 +5,7 @@ import {
5
5
  useTranscriber,
6
6
  useTranscriptionRecording,
7
7
  useVoiceInput
8
- } from "../chunk-TO77KAQK.mjs";
8
+ } from "../chunk-XMHH34NI.mjs";
9
9
  import "../chunk-MLRJWAQH.mjs";
10
10
  import "../chunk-J5LGTIGS.mjs";
11
11
  export {
@@ -3,7 +3,7 @@ import {
3
3
  Transcriber,
4
4
  TranscriptionManager,
5
5
  alignWhisperSegments
6
- } from "./chunk-TX3NQISZ.mjs";
6
+ } from "./chunk-XWC5BBOY.mjs";
7
7
  import {
8
8
  useAudioFile,
9
9
  useAudioTrack,
@@ -11,15 +11,16 @@ import {
11
11
  useTranscriber,
12
12
  useTranscriptionRecording,
13
13
  useVoiceInput
14
- } from "./chunk-TO77KAQK.mjs";
14
+ } from "./chunk-XMHH34NI.mjs";
15
15
  import {
16
16
  meta
17
- } from "./chunk-FHNI26QW.mjs";
17
+ } from "./chunk-XZVDAYM6.mjs";
18
18
  import {
19
+ Settings_exports,
19
20
  TranscriptOperation_exports,
20
21
  TranscriptionCapabilities_exports
21
- } from "./chunk-KMT3BMMQ.mjs";
22
- import "./chunk-KAEXRX3Y.mjs";
22
+ } from "./chunk-2URVUJT4.mjs";
23
+ import "./chunk-XOXHWMXR.mjs";
23
24
  import {
24
25
  transcription_blueprint_default
25
26
  } from "./chunk-ZR4BH4QQ.mjs";
@@ -27,6 +28,7 @@ import "./chunk-MLRJWAQH.mjs";
27
28
  import "./chunk-J5LGTIGS.mjs";
28
29
  export {
29
30
  MediaStreamRecorder,
31
+ Settings_exports as Settings,
30
32
  Transcriber,
31
33
  TranscriptOperation_exports as TranscriptOperation,
32
34
  transcription_blueprint_default as TranscriptionBlueprint,