@mieweb/ui 0.11.0-dev.50 → 0.11.0-dev.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -57,7 +57,7 @@ var chunkMGFRGPWI_cjs = require('./chunk-MGFRGPWI.cjs');
57
57
  var chunk2I4IRQQG_cjs = require('./chunk-2I4IRQQG.cjs');
58
58
  var chunkXWZEECEX_cjs = require('./chunk-XWZEECEX.cjs');
59
59
  var chunk5MXO6PEV_cjs = require('./chunk-5MXO6PEV.cjs');
60
- var chunkTLXQ2E57_cjs = require('./chunk-TLXQ2E57.cjs');
60
+ var chunkLEJHDPLM_cjs = require('./chunk-LEJHDPLM.cjs');
61
61
  var chunk2Y2O2PRZ_cjs = require('./chunk-2Y2O2PRZ.cjs');
62
62
  var chunkMQK2D6I6_cjs = require('./chunk-MQK2D6I6.cjs');
63
63
  var chunk2XHPPOXY_cjs = require('./chunk-2XHPPOXY.cjs');
@@ -45289,15 +45289,15 @@ Object.defineProperty(exports, "ReadingProgressBar", {
45289
45289
  });
45290
45290
  Object.defineProperty(exports, "DictationButton", {
45291
45291
  enumerable: true,
45292
- get: function () { return chunkTLXQ2E57_cjs.DictationButton; }
45292
+ get: function () { return chunkLEJHDPLM_cjs.DictationButton; }
45293
45293
  });
45294
45294
  Object.defineProperty(exports, "defaultDictationLabels", {
45295
45295
  enumerable: true,
45296
- get: function () { return chunkTLXQ2E57_cjs.defaultDictationLabels; }
45296
+ get: function () { return chunkLEJHDPLM_cjs.defaultDictationLabels; }
45297
45297
  });
45298
45298
  Object.defineProperty(exports, "useDictation", {
45299
45299
  enumerable: true,
45300
- get: function () { return chunkTLXQ2E57_cjs.useDictation; }
45300
+ get: function () { return chunkLEJHDPLM_cjs.useDictation; }
45301
45301
  });
45302
45302
  Object.defineProperty(exports, "RecordButton", {
45303
45303
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -15523,6 +15523,10 @@ interface DictationButtonProps extends UseDictationOptions, Omit<React$1.ButtonH
15523
15523
  variant?: RecordButtonVariant;
15524
15524
  /** Show `m:ss` while recording. */
15525
15525
  showDuration?: boolean;
15526
+ /** Live waveform bars instead of the stop icon while recording. */
15527
+ showWaveform?: boolean;
15528
+ /** Pulse rings while recording. They extend past the button, so turn off in tight rows. Default true. */
15529
+ showPulse?: boolean;
15526
15530
  /** Show the listening/transcribing/error caption. It is always announced to screen readers. */
15527
15531
  showStatus?: boolean;
15528
15532
  /** Override any user-facing string (i18n). */
package/dist/index.d.ts CHANGED
@@ -15523,6 +15523,10 @@ interface DictationButtonProps extends UseDictationOptions, Omit<React$1.ButtonH
15523
15523
  variant?: RecordButtonVariant;
15524
15524
  /** Show `m:ss` while recording. */
15525
15525
  showDuration?: boolean;
15526
+ /** Live waveform bars instead of the stop icon while recording. */
15527
+ showWaveform?: boolean;
15528
+ /** Pulse rings while recording. They extend past the button, so turn off in tight rows. Default true. */
15529
+ showPulse?: boolean;
15526
15530
  /** Show the listening/transcribing/error caption. It is always announced to screen readers. */
15527
15531
  showStatus?: boolean;
15528
15532
  /** Override any user-facing string (i18n). */
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ import { Progress } from './chunk-P4E3K3ND.js';
64
64
  export { CircularProgress, Progress, circularProgressVariants, progressBarFillVariants, progressBarTrackVariants } from './chunk-P4E3K3ND.js';
65
65
  export { QuickAction, QuickActionGroup, QuickActionIcons, quickActionIconVariants, quickActionVariants } from './chunk-TQDZLRIE.js';
66
66
  export { ReadingProgressBar } from './chunk-WKSUL2ZN.js';
67
- export { DictationButton, defaultDictationLabels, useDictation } from './chunk-I65QJMFB.js';
67
+ export { DictationButton, defaultDictationLabels, useDictation } from './chunk-EOIGNUWI.js';
68
68
  import { RecordButton } from './chunk-URHUNFFC.js';
69
69
  export { RecordButton, formatDuration, recordButtonVariants } from './chunk-URHUNFFC.js';
70
70
  export { FillerWordsModal, MediaEditor, SilenceSettingsModal, SpeedMarkerMenu, WordEditorModal } from './chunk-46ZUORUE.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mieweb/ui",
3
- "version": "0.11.0-dev.50",
3
+ "version": "0.11.0-dev.51",
4
4
  "description": "A themeable, accessible React component library built with Tailwind CSS",
5
5
  "author": "Medical Informatics Engineering, Inc.",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/DictationButton/useDictation.ts","../src/components/DictationButton/DictationButton.tsx"],"names":["React2"],"mappings":"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","file":"chunk-I65QJMFB.js","sourcesContent":["import * as React from 'react';\n\nexport type DictationStatus = 'idle' | 'recording' | 'transcribing' | 'error';\n\n/** Turns recorded audio into text. Reject to report an error; honor `signal` to support cancel. */\nexport type DictationTranscribe = (\n audio: Blob,\n signal: AbortSignal\n) => Promise<string>;\n\nexport interface UseDictationOptions {\n /** Called with the final, trimmed transcript. Not called for empty results or after cancel. */\n onText: (text: string) => void;\n /** Transcription provider. Defaults to on-device Whisper (audio never leaves the browser). */\n transcribe?: DictationTranscribe;\n /** Called with the underlying error (permission denied, provider failure, …). */\n onError?: (error: Error) => void;\n /** Auto-stop after this many seconds. `0` disables the limit. Default 300. */\n maxDurationSeconds?: number;\n /**\n * Reuse a mic stream the host already holds (e.g. a wake-word listener) instead of opening one.\n * Its tracks are never stopped by the hook. Falls back to `getUserMedia` when it returns no live audio.\n */\n getStream?: () => MediaStream | null | undefined;\n}\n\nexport interface UseDictationResult {\n status: DictationStatus;\n error: Error | null;\n /** Milliseconds recorded so far in the current session. */\n elapsedMs: number;\n start: () => Promise<void>;\n /** Stop recording and transcribe. */\n stop: () => void;\n /** Discard the recording or in-flight transcription. `onText` will not fire. */\n cancel: () => void;\n}\n\nconst onDeviceTranscribe: DictationTranscribe = async (audio, signal) => {\n // Loaded lazily so hosts using a server provider never pull in Whisper.\n const { transcribeBlob } = await import('../AI/whisperTranscribe');\n // The shared worker cannot stop a running decode, so cancel only discards its result.\n if (signal.aborted) throw new Error('Dictation cancelled');\n return transcribeBlob(audio);\n};\n\nfunction toError(value: unknown): Error {\n if (value instanceof Error) return value;\n // DOMException (e.g. NotAllowedError) is not an Error subclass in every runtime; keep its `name`.\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n 'message' in value\n )\n return value as Error;\n return new Error(String(value));\n}\n\nfunction liveAudio(stream: MediaStream | null | undefined) {\n return stream?.getAudioTracks().some((track) => track.readyState === 'live')\n ? stream\n : null;\n}\n\n/**\n * Headless speech-to-text for a single speaker: record, then transcribe to editable text.\n * Owns the mic lifecycle; every async step is tied to a session id so stale results are dropped.\n */\nexport function useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds = 300,\n getStream,\n}: UseDictationOptions): UseDictationResult {\n const [status, setStatus] = React.useState<DictationStatus>('idle');\n const [error, setError] = React.useState<Error | null>(null);\n const [elapsedMs, setElapsedMs] = React.useState(0);\n\n const callbacksRef = React.useRef({ onText, onError, transcribe, getStream });\n callbacksRef.current = { onText, onError, transcribe, getStream };\n const maxMsRef = React.useRef(maxDurationSeconds * 1000);\n maxMsRef.current = maxDurationSeconds * 1000;\n\n const sessionRef = React.useRef(0);\n const busyRef = React.useRef(false);\n const mountedRef = React.useRef(true);\n // Only streams the hook opened; a host-shared stream is never stored here, so it is never stopped.\n const streamRef = React.useRef<MediaStream | null>(null);\n const recorderRef = React.useRef<MediaRecorder | null>(null);\n const timerRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n const abortRef = React.useRef<AbortController | null>(null);\n\n const releaseMic = React.useCallback(() => {\n if (timerRef.current) clearInterval(timerRef.current);\n timerRef.current = null;\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const fail = React.useCallback(\n (err: unknown) => {\n releaseMic();\n recorderRef.current = null;\n busyRef.current = false;\n const e = toError(err);\n if (mountedRef.current) {\n setError(e);\n setStatus('error');\n }\n callbacksRef.current.onError?.(e);\n },\n [releaseMic]\n );\n\n const finish = React.useCallback(\n async (session: number, chunks: Blob[], mimeType: string) => {\n // A stale session must not touch refs that may already belong to a new one.\n if (session !== sessionRef.current) return;\n releaseMic();\n recorderRef.current = null;\n\n const audio = new Blob(chunks, { type: mimeType });\n if (audio.size === 0) {\n busyRef.current = false;\n if (mountedRef.current) setStatus('idle');\n return;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n if (mountedRef.current) setStatus('transcribing');\n try {\n const run = callbacksRef.current.transcribe ?? onDeviceTranscribe;\n const text = (await run(audio, controller.signal)).trim();\n if (session !== sessionRef.current || controller.signal.aborted) return;\n busyRef.current = false;\n abortRef.current = null;\n if (mountedRef.current) setStatus('idle');\n if (text) callbacksRef.current.onText(text);\n } catch (err) {\n if (session !== sessionRef.current || controller.signal.aborted) return;\n abortRef.current = null;\n fail(err);\n }\n },\n [releaseMic, fail]\n );\n\n const cancel = React.useCallback(() => {\n sessionRef.current += 1;\n abortRef.current?.abort();\n abortRef.current = null;\n const recorder = recorderRef.current;\n recorderRef.current = null;\n if (recorder && recorder.state !== 'inactive') recorder.stop();\n releaseMic();\n busyRef.current = false;\n if (mountedRef.current) {\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n }\n }, [releaseMic]);\n\n const stop = React.useCallback(() => {\n const recorder = recorderRef.current;\n if (recorder) {\n // Already inactive means onstop is queued; cancelling here would drop the take.\n if (recorder.state !== 'inactive') recorder.stop();\n } else if (busyRef.current && !abortRef.current) {\n // Still waiting on the permission prompt: nothing recorded yet.\n cancel();\n }\n }, [cancel]);\n\n const start = React.useCallback(async () => {\n if (busyRef.current) return;\n busyRef.current = true;\n const session = ++sessionRef.current;\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n\n let shared: MediaStream | null;\n try {\n shared = liveAudio(callbacksRef.current.getStream?.());\n } catch (err) {\n fail(err);\n return;\n }\n if (\n typeof MediaRecorder === 'undefined' ||\n (!shared &&\n (typeof navigator === 'undefined' ||\n !navigator.mediaDevices?.getUserMedia))\n ) {\n fail(new Error('Microphone is not available in this context'));\n return;\n }\n\n let stream: MediaStream;\n if (shared) {\n stream = shared;\n } else {\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n if (session === sessionRef.current) fail(err);\n return;\n }\n if (session !== sessionRef.current) {\n // Cancelled or unmounted while the permission prompt was open.\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = stream;\n }\n\n let recorder: MediaRecorder;\n try {\n recorder = new MediaRecorder(stream);\n } catch (err) {\n fail(err);\n return;\n }\n\n const chunks: Blob[] = [];\n recorder.ondataavailable = (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n };\n recorder.onstop = () => {\n void finish(session, chunks, recorder.mimeType || 'audio/webm');\n };\n recorder.onerror = (e) => {\n if (session !== sessionRef.current) return;\n sessionRef.current += 1;\n recorderRef.current = null;\n fail((e as Event & { error?: unknown }).error ?? e);\n };\n // Shared tracks live longer than one take; MediaRecorder stops by itself if they end.\n if (!shared) {\n stream.getAudioTracks().forEach((track) => {\n track.addEventListener('ended', () => {\n if (recorderRef.current === recorder && recorder.state !== 'inactive')\n recorder.stop();\n });\n });\n }\n\n recorderRef.current = recorder;\n try {\n recorder.start();\n } catch (err) {\n recorderRef.current = null;\n fail(err);\n return;\n }\n setStatus('recording');\n\n const startedAt = Date.now();\n timerRef.current = setInterval(() => {\n const elapsed = Date.now() - startedAt;\n setElapsedMs(elapsed);\n if (maxMsRef.current > 0 && elapsed >= maxMsRef.current) {\n if (recorder.state !== 'inactive') recorder.stop();\n }\n }, 250);\n }, [fail, finish]);\n\n React.useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n cancel();\n };\n }, [cancel]);\n\n return { status, error, elapsedMs, start, stop, cancel };\n}\n","import * as React from 'react';\nimport {\n RecordButton,\n formatDuration,\n type RecordButtonSize,\n type RecordButtonState,\n type RecordButtonVariant,\n} from '../RecordButton';\nimport { cn } from '../../utils/cn';\nimport {\n useDictation,\n type DictationStatus,\n type UseDictationOptions,\n} from './useDictation';\n\nexport interface DictationButtonLabels {\n start: string;\n stop: string;\n listening: string;\n transcribing: string;\n error: string;\n micBlocked: string;\n}\n\nexport const defaultDictationLabels: DictationButtonLabels = {\n start: 'Start dictation',\n stop: 'Stop dictation',\n listening: 'Listening…',\n transcribing: 'Transcribing…',\n error: 'Dictation failed. Try again.',\n micBlocked: 'Microphone access is blocked.',\n};\n\n/** Drive dictation from code, e.g. when a wake word fires. */\nexport interface DictationButtonHandle {\n /** Does nothing while `disabled` or while a take is already in progress. */\n start: () => void;\n /** Stop recording and transcribe. */\n stop: () => void;\n /** Discard the take; `onText` will not fire. */\n cancel: () => void;\n}\n\nexport interface DictationButtonProps\n extends\n UseDictationOptions,\n Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n // The ARIA attributes follow the dictation state; customise text through `labels`.\n | 'children'\n | 'onClick'\n | 'onError'\n | 'disabled'\n | 'className'\n | 'aria-label'\n | 'aria-pressed'\n | 'aria-busy'\n > {\n disabled?: boolean;\n size?: RecordButtonSize;\n variant?: RecordButtonVariant;\n /** Show `m:ss` while recording. */\n showDuration?: boolean;\n /** Show the listening/transcribing/error caption. It is always announced to screen readers. */\n showStatus?: boolean;\n /** Override any user-facing string (i18n). */\n labels?: Partial<DictationButtonLabels>;\n className?: string;\n /** Start, stop or cancel from code. `ref` stays the `<button>`. */\n handleRef?: React.Ref<DictationButtonHandle>;\n}\n\nconst buttonState: Record<DictationStatus, RecordButtonState> = {\n idle: 'idle',\n recording: 'recording',\n transcribing: 'processing',\n error: 'error',\n};\n\n/**\n * Mic button that turns speech into editable text via `onText`. Never sends anything itself.\n * Escape while recording discards the take.\n */\nexport const DictationButton = React.forwardRef<\n HTMLButtonElement,\n DictationButtonProps\n>(\n (\n {\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n disabled = false,\n size = 'sm',\n variant = 'ghost',\n showDuration = false,\n showStatus = false,\n labels: labelOverrides,\n className,\n handleRef,\n ...rest\n },\n ref\n ) => {\n const labels = { ...defaultDictationLabels, ...labelOverrides };\n const { status, error, elapsedMs, start, stop, cancel } = useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n });\n\n // Covers a pending permission prompt (status still idle) and transcribing, not just recording.\n React.useEffect(() => {\n if (disabled) cancel();\n }, [disabled, cancel]);\n\n React.useImperativeHandle(\n handleRef,\n () => ({\n start: () => {\n if (!disabled) void start();\n },\n stop,\n cancel,\n }),\n [disabled, start, stop, cancel]\n );\n\n // Document-level so Escape works while focus stays in the composer.\n React.useEffect(() => {\n if (status !== 'recording') return;\n const onEscape = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || e.defaultPrevented) return;\n e.preventDefault();\n cancel();\n };\n document.addEventListener('keydown', onEscape);\n return () => document.removeEventListener('keydown', onEscape);\n }, [status, cancel]);\n\n const errorText =\n error?.name === 'NotAllowedError' ? labels.micBlocked : labels.error;\n const statusText =\n status === 'recording'\n ? labels.listening\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : '';\n const ariaLabel =\n status === 'recording'\n ? labels.stop\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : labels.start;\n\n return (\n <div\n data-slot=\"dictation-button\"\n className={cn('inline-flex items-center gap-2', className)}\n >\n <RecordButton\n {...rest}\n ref={ref}\n state={disabled ? 'disabled' : buttonState[status]}\n disabled={disabled}\n size={size}\n variant={variant}\n aria-label={ariaLabel}\n onClick={() => {\n if (status === 'recording') stop();\n else void start();\n }}\n />\n {showDuration && status === 'recording' && (\n <span\n data-slot=\"dictation-button-duration\"\n className=\"text-destructive font-mono text-xs tabular-nums\"\n >\n {formatDuration(elapsedMs / 1000)}\n </span>\n )}\n <span\n role=\"status\"\n data-slot=\"dictation-button-status\"\n className={cn(\n 'text-xs',\n status === 'error' ? 'text-destructive' : 'text-muted-foreground',\n !showStatus && 'sr-only'\n )}\n >\n {statusText}\n </span>\n </div>\n );\n }\n);\n\nDictationButton.displayName = 'DictationButton';\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/DictationButton/useDictation.ts","../src/components/DictationButton/DictationButton.tsx"],"names":["React","React2","jsxs","cn","jsx","RecordButton","formatDuration"],"mappings":"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","file":"chunk-TLXQ2E57.cjs","sourcesContent":["import * as React from 'react';\n\nexport type DictationStatus = 'idle' | 'recording' | 'transcribing' | 'error';\n\n/** Turns recorded audio into text. Reject to report an error; honor `signal` to support cancel. */\nexport type DictationTranscribe = (\n audio: Blob,\n signal: AbortSignal\n) => Promise<string>;\n\nexport interface UseDictationOptions {\n /** Called with the final, trimmed transcript. Not called for empty results or after cancel. */\n onText: (text: string) => void;\n /** Transcription provider. Defaults to on-device Whisper (audio never leaves the browser). */\n transcribe?: DictationTranscribe;\n /** Called with the underlying error (permission denied, provider failure, …). */\n onError?: (error: Error) => void;\n /** Auto-stop after this many seconds. `0` disables the limit. Default 300. */\n maxDurationSeconds?: number;\n /**\n * Reuse a mic stream the host already holds (e.g. a wake-word listener) instead of opening one.\n * Its tracks are never stopped by the hook. Falls back to `getUserMedia` when it returns no live audio.\n */\n getStream?: () => MediaStream | null | undefined;\n}\n\nexport interface UseDictationResult {\n status: DictationStatus;\n error: Error | null;\n /** Milliseconds recorded so far in the current session. */\n elapsedMs: number;\n start: () => Promise<void>;\n /** Stop recording and transcribe. */\n stop: () => void;\n /** Discard the recording or in-flight transcription. `onText` will not fire. */\n cancel: () => void;\n}\n\nconst onDeviceTranscribe: DictationTranscribe = async (audio, signal) => {\n // Loaded lazily so hosts using a server provider never pull in Whisper.\n const { transcribeBlob } = await import('../AI/whisperTranscribe');\n // The shared worker cannot stop a running decode, so cancel only discards its result.\n if (signal.aborted) throw new Error('Dictation cancelled');\n return transcribeBlob(audio);\n};\n\nfunction toError(value: unknown): Error {\n if (value instanceof Error) return value;\n // DOMException (e.g. NotAllowedError) is not an Error subclass in every runtime; keep its `name`.\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n 'message' in value\n )\n return value as Error;\n return new Error(String(value));\n}\n\nfunction liveAudio(stream: MediaStream | null | undefined) {\n return stream?.getAudioTracks().some((track) => track.readyState === 'live')\n ? stream\n : null;\n}\n\n/**\n * Headless speech-to-text for a single speaker: record, then transcribe to editable text.\n * Owns the mic lifecycle; every async step is tied to a session id so stale results are dropped.\n */\nexport function useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds = 300,\n getStream,\n}: UseDictationOptions): UseDictationResult {\n const [status, setStatus] = React.useState<DictationStatus>('idle');\n const [error, setError] = React.useState<Error | null>(null);\n const [elapsedMs, setElapsedMs] = React.useState(0);\n\n const callbacksRef = React.useRef({ onText, onError, transcribe, getStream });\n callbacksRef.current = { onText, onError, transcribe, getStream };\n const maxMsRef = React.useRef(maxDurationSeconds * 1000);\n maxMsRef.current = maxDurationSeconds * 1000;\n\n const sessionRef = React.useRef(0);\n const busyRef = React.useRef(false);\n const mountedRef = React.useRef(true);\n // Only streams the hook opened; a host-shared stream is never stored here, so it is never stopped.\n const streamRef = React.useRef<MediaStream | null>(null);\n const recorderRef = React.useRef<MediaRecorder | null>(null);\n const timerRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n const abortRef = React.useRef<AbortController | null>(null);\n\n const releaseMic = React.useCallback(() => {\n if (timerRef.current) clearInterval(timerRef.current);\n timerRef.current = null;\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const fail = React.useCallback(\n (err: unknown) => {\n releaseMic();\n recorderRef.current = null;\n busyRef.current = false;\n const e = toError(err);\n if (mountedRef.current) {\n setError(e);\n setStatus('error');\n }\n callbacksRef.current.onError?.(e);\n },\n [releaseMic]\n );\n\n const finish = React.useCallback(\n async (session: number, chunks: Blob[], mimeType: string) => {\n // A stale session must not touch refs that may already belong to a new one.\n if (session !== sessionRef.current) return;\n releaseMic();\n recorderRef.current = null;\n\n const audio = new Blob(chunks, { type: mimeType });\n if (audio.size === 0) {\n busyRef.current = false;\n if (mountedRef.current) setStatus('idle');\n return;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n if (mountedRef.current) setStatus('transcribing');\n try {\n const run = callbacksRef.current.transcribe ?? onDeviceTranscribe;\n const text = (await run(audio, controller.signal)).trim();\n if (session !== sessionRef.current || controller.signal.aborted) return;\n busyRef.current = false;\n abortRef.current = null;\n if (mountedRef.current) setStatus('idle');\n if (text) callbacksRef.current.onText(text);\n } catch (err) {\n if (session !== sessionRef.current || controller.signal.aborted) return;\n abortRef.current = null;\n fail(err);\n }\n },\n [releaseMic, fail]\n );\n\n const cancel = React.useCallback(() => {\n sessionRef.current += 1;\n abortRef.current?.abort();\n abortRef.current = null;\n const recorder = recorderRef.current;\n recorderRef.current = null;\n if (recorder && recorder.state !== 'inactive') recorder.stop();\n releaseMic();\n busyRef.current = false;\n if (mountedRef.current) {\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n }\n }, [releaseMic]);\n\n const stop = React.useCallback(() => {\n const recorder = recorderRef.current;\n if (recorder) {\n // Already inactive means onstop is queued; cancelling here would drop the take.\n if (recorder.state !== 'inactive') recorder.stop();\n } else if (busyRef.current && !abortRef.current) {\n // Still waiting on the permission prompt: nothing recorded yet.\n cancel();\n }\n }, [cancel]);\n\n const start = React.useCallback(async () => {\n if (busyRef.current) return;\n busyRef.current = true;\n const session = ++sessionRef.current;\n setStatus('idle');\n setError(null);\n setElapsedMs(0);\n\n let shared: MediaStream | null;\n try {\n shared = liveAudio(callbacksRef.current.getStream?.());\n } catch (err) {\n fail(err);\n return;\n }\n if (\n typeof MediaRecorder === 'undefined' ||\n (!shared &&\n (typeof navigator === 'undefined' ||\n !navigator.mediaDevices?.getUserMedia))\n ) {\n fail(new Error('Microphone is not available in this context'));\n return;\n }\n\n let stream: MediaStream;\n if (shared) {\n stream = shared;\n } else {\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n if (session === sessionRef.current) fail(err);\n return;\n }\n if (session !== sessionRef.current) {\n // Cancelled or unmounted while the permission prompt was open.\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = stream;\n }\n\n let recorder: MediaRecorder;\n try {\n recorder = new MediaRecorder(stream);\n } catch (err) {\n fail(err);\n return;\n }\n\n const chunks: Blob[] = [];\n recorder.ondataavailable = (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n };\n recorder.onstop = () => {\n void finish(session, chunks, recorder.mimeType || 'audio/webm');\n };\n recorder.onerror = (e) => {\n if (session !== sessionRef.current) return;\n sessionRef.current += 1;\n recorderRef.current = null;\n fail((e as Event & { error?: unknown }).error ?? e);\n };\n // Shared tracks live longer than one take; MediaRecorder stops by itself if they end.\n if (!shared) {\n stream.getAudioTracks().forEach((track) => {\n track.addEventListener('ended', () => {\n if (recorderRef.current === recorder && recorder.state !== 'inactive')\n recorder.stop();\n });\n });\n }\n\n recorderRef.current = recorder;\n try {\n recorder.start();\n } catch (err) {\n recorderRef.current = null;\n fail(err);\n return;\n }\n setStatus('recording');\n\n const startedAt = Date.now();\n timerRef.current = setInterval(() => {\n const elapsed = Date.now() - startedAt;\n setElapsedMs(elapsed);\n if (maxMsRef.current > 0 && elapsed >= maxMsRef.current) {\n if (recorder.state !== 'inactive') recorder.stop();\n }\n }, 250);\n }, [fail, finish]);\n\n React.useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n cancel();\n };\n }, [cancel]);\n\n return { status, error, elapsedMs, start, stop, cancel };\n}\n","import * as React from 'react';\nimport {\n RecordButton,\n formatDuration,\n type RecordButtonSize,\n type RecordButtonState,\n type RecordButtonVariant,\n} from '../RecordButton';\nimport { cn } from '../../utils/cn';\nimport {\n useDictation,\n type DictationStatus,\n type UseDictationOptions,\n} from './useDictation';\n\nexport interface DictationButtonLabels {\n start: string;\n stop: string;\n listening: string;\n transcribing: string;\n error: string;\n micBlocked: string;\n}\n\nexport const defaultDictationLabels: DictationButtonLabels = {\n start: 'Start dictation',\n stop: 'Stop dictation',\n listening: 'Listening…',\n transcribing: 'Transcribing…',\n error: 'Dictation failed. Try again.',\n micBlocked: 'Microphone access is blocked.',\n};\n\n/** Drive dictation from code, e.g. when a wake word fires. */\nexport interface DictationButtonHandle {\n /** Does nothing while `disabled` or while a take is already in progress. */\n start: () => void;\n /** Stop recording and transcribe. */\n stop: () => void;\n /** Discard the take; `onText` will not fire. */\n cancel: () => void;\n}\n\nexport interface DictationButtonProps\n extends\n UseDictationOptions,\n Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n // The ARIA attributes follow the dictation state; customise text through `labels`.\n | 'children'\n | 'onClick'\n | 'onError'\n | 'disabled'\n | 'className'\n | 'aria-label'\n | 'aria-pressed'\n | 'aria-busy'\n > {\n disabled?: boolean;\n size?: RecordButtonSize;\n variant?: RecordButtonVariant;\n /** Show `m:ss` while recording. */\n showDuration?: boolean;\n /** Show the listening/transcribing/error caption. It is always announced to screen readers. */\n showStatus?: boolean;\n /** Override any user-facing string (i18n). */\n labels?: Partial<DictationButtonLabels>;\n className?: string;\n /** Start, stop or cancel from code. `ref` stays the `<button>`. */\n handleRef?: React.Ref<DictationButtonHandle>;\n}\n\nconst buttonState: Record<DictationStatus, RecordButtonState> = {\n idle: 'idle',\n recording: 'recording',\n transcribing: 'processing',\n error: 'error',\n};\n\n/**\n * Mic button that turns speech into editable text via `onText`. Never sends anything itself.\n * Escape while recording discards the take.\n */\nexport const DictationButton = React.forwardRef<\n HTMLButtonElement,\n DictationButtonProps\n>(\n (\n {\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n disabled = false,\n size = 'sm',\n variant = 'ghost',\n showDuration = false,\n showStatus = false,\n labels: labelOverrides,\n className,\n handleRef,\n ...rest\n },\n ref\n ) => {\n const labels = { ...defaultDictationLabels, ...labelOverrides };\n const { status, error, elapsedMs, start, stop, cancel } = useDictation({\n onText,\n transcribe,\n onError,\n maxDurationSeconds,\n getStream,\n });\n\n // Covers a pending permission prompt (status still idle) and transcribing, not just recording.\n React.useEffect(() => {\n if (disabled) cancel();\n }, [disabled, cancel]);\n\n React.useImperativeHandle(\n handleRef,\n () => ({\n start: () => {\n if (!disabled) void start();\n },\n stop,\n cancel,\n }),\n [disabled, start, stop, cancel]\n );\n\n // Document-level so Escape works while focus stays in the composer.\n React.useEffect(() => {\n if (status !== 'recording') return;\n const onEscape = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || e.defaultPrevented) return;\n e.preventDefault();\n cancel();\n };\n document.addEventListener('keydown', onEscape);\n return () => document.removeEventListener('keydown', onEscape);\n }, [status, cancel]);\n\n const errorText =\n error?.name === 'NotAllowedError' ? labels.micBlocked : labels.error;\n const statusText =\n status === 'recording'\n ? labels.listening\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : '';\n const ariaLabel =\n status === 'recording'\n ? labels.stop\n : status === 'transcribing'\n ? labels.transcribing\n : status === 'error'\n ? errorText\n : labels.start;\n\n return (\n <div\n data-slot=\"dictation-button\"\n className={cn('inline-flex items-center gap-2', className)}\n >\n <RecordButton\n {...rest}\n ref={ref}\n state={disabled ? 'disabled' : buttonState[status]}\n disabled={disabled}\n size={size}\n variant={variant}\n aria-label={ariaLabel}\n onClick={() => {\n if (status === 'recording') stop();\n else void start();\n }}\n />\n {showDuration && status === 'recording' && (\n <span\n data-slot=\"dictation-button-duration\"\n className=\"text-destructive font-mono text-xs tabular-nums\"\n >\n {formatDuration(elapsedMs / 1000)}\n </span>\n )}\n <span\n role=\"status\"\n data-slot=\"dictation-button-status\"\n className={cn(\n 'text-xs',\n status === 'error' ? 'text-destructive' : 'text-muted-foreground',\n !showStatus && 'sr-only'\n )}\n >\n {statusText}\n </span>\n </div>\n );\n }\n);\n\nDictationButton.displayName = 'DictationButton';\n"]}