@athenaintel/react 0.23.1 → 0.23.3

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 (87) hide show
  1. package/README.md +45 -2
  2. package/dist/chat/AthenaChat.cjs +132 -39
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.d.ts +37 -4
  5. package/dist/chat/AthenaChat.js +133 -40
  6. package/dist/chat/AthenaChat.js.map +1 -1
  7. package/dist/chat/athena-composer-controls.js +1 -1
  8. package/dist/chat/statewire-client-tools.cjs +20 -0
  9. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  10. package/dist/chat/statewire-client-tools.d.ts +27 -0
  11. package/dist/chat/statewire-client-tools.js +20 -0
  12. package/dist/chat/statewire-client-tools.js.map +1 -1
  13. package/dist/chat/use-athena-run-live.cjs.map +1 -1
  14. package/dist/chat/use-athena-run-live.d.ts +7 -0
  15. package/dist/chat/use-athena-run-live.js.map +1 -1
  16. package/dist/chat-ui/composer/tiptap-composer.cjs +16 -1
  17. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  18. package/dist/chat-ui/composer/tiptap-composer.js +16 -1
  19. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  20. package/dist/composer/composer-handoff.cjs +121 -0
  21. package/dist/composer/composer-handoff.cjs.map +1 -0
  22. package/dist/composer/composer-handoff.d.ts +89 -0
  23. package/dist/composer/composer-handoff.js +121 -0
  24. package/dist/composer/composer-handoff.js.map +1 -0
  25. package/dist/composer/index.d.ts +2 -0
  26. package/dist/composer/useAppendToComposer.cjs +19 -7
  27. package/dist/composer/useAppendToComposer.cjs.map +1 -1
  28. package/dist/composer/useAppendToComposer.d.ts +22 -4
  29. package/dist/composer/useAppendToComposer.js +20 -8
  30. package/dist/composer/useAppendToComposer.js.map +1 -1
  31. package/dist/composer/useSendMessage.cjs +26 -8
  32. package/dist/composer/useSendMessage.cjs.map +1 -1
  33. package/dist/composer/useSendMessage.d.ts +8 -0
  34. package/dist/composer/useSendMessage.js +27 -9
  35. package/dist/composer/useSendMessage.js.map +1 -1
  36. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  37. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  38. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  39. package/dist/diagnostics/errors.cjs +2 -0
  40. package/dist/diagnostics/errors.cjs.map +1 -1
  41. package/dist/diagnostics/errors.d.ts +2 -0
  42. package/dist/diagnostics/errors.js +2 -0
  43. package/dist/diagnostics/errors.js.map +1 -1
  44. package/dist/dictation/DictationStrip.cjs +60 -0
  45. package/dist/dictation/DictationStrip.cjs.map +1 -0
  46. package/dist/dictation/DictationStrip.d.ts +25 -0
  47. package/dist/dictation/DictationStrip.js +60 -0
  48. package/dist/dictation/DictationStrip.js.map +1 -0
  49. package/dist/dictation/audio-level-history.cjs +68 -0
  50. package/dist/dictation/audio-level-history.cjs.map +1 -0
  51. package/dist/dictation/audio-level-history.d.ts +27 -0
  52. package/dist/dictation/audio-level-history.js +68 -0
  53. package/dist/dictation/audio-level-history.js.map +1 -0
  54. package/dist/dictation/dictation-controls.cjs +60 -0
  55. package/dist/dictation/dictation-controls.cjs.map +1 -0
  56. package/dist/dictation/dictation-controls.d.ts +15 -0
  57. package/dist/dictation/dictation-controls.js +60 -0
  58. package/dist/dictation/dictation-controls.js.map +1 -0
  59. package/dist/dictation/dictation-engine.cjs +128 -0
  60. package/dist/dictation/dictation-engine.cjs.map +1 -0
  61. package/dist/dictation/dictation-engine.d.ts +87 -0
  62. package/dist/dictation/dictation-engine.js +128 -0
  63. package/dist/dictation/dictation-engine.js.map +1 -0
  64. package/dist/dictation/dictation-state.cjs +68 -0
  65. package/dist/dictation/dictation-state.cjs.map +1 -0
  66. package/dist/dictation/dictation-state.d.ts +58 -0
  67. package/dist/dictation/dictation-state.js +68 -0
  68. package/dist/dictation/dictation-state.js.map +1 -0
  69. package/dist/dictation/use-dictation.cjs +204 -0
  70. package/dist/dictation/use-dictation.cjs.map +1 -0
  71. package/dist/dictation/use-dictation.d.ts +47 -0
  72. package/dist/dictation/use-dictation.js +204 -0
  73. package/dist/dictation/use-dictation.js.map +1 -0
  74. package/dist/index.cjs +13 -0
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.ts +11 -0
  77. package/dist/index.js +13 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/packages/react/package.json.cjs +1 -1
  80. package/dist/packages/react/package.json.js +1 -1
  81. package/dist/provider/AthenaProvider.cjs +42 -3
  82. package/dist/provider/AthenaProvider.cjs.map +1 -1
  83. package/dist/provider/AthenaProvider.d.ts +1 -1
  84. package/dist/provider/AthenaProvider.js +43 -4
  85. package/dist/provider/AthenaProvider.js.map +1 -1
  86. package/dist/styles.css +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1,128 @@
1
+ const DICTATION_ENGINE_ERROR_KINDS = {
2
+ /** The user denied the microphone, or the browser blocked it. */
3
+ permission: "permission",
4
+ /** No speech engine is available in this browser. */
5
+ unsupported: "unsupported",
6
+ /** The engine could not reach its recognizer. */
7
+ connection: "connection",
8
+ /** The engine heard nothing for its silence window. */
9
+ noSpeech: "no-speech",
10
+ unknown: "unknown"
11
+ };
12
+ function resolveSpeechRecognitionCtor() {
13
+ if (typeof window === "undefined") return null;
14
+ const scope = window;
15
+ return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;
16
+ }
17
+ function classifyWebSpeechError(code) {
18
+ switch (code) {
19
+ case "not-allowed":
20
+ case "service-not-allowed":
21
+ return {
22
+ kind: DICTATION_ENGINE_ERROR_KINDS.permission,
23
+ message: "Microphone access was denied. Allow the microphone for this site and try again."
24
+ };
25
+ case "network":
26
+ return {
27
+ kind: DICTATION_ENGINE_ERROR_KINDS.connection,
28
+ message: "Speech recognition could not connect. Check your connection and try again."
29
+ };
30
+ case "no-speech":
31
+ return {
32
+ kind: DICTATION_ENGINE_ERROR_KINDS.noSpeech,
33
+ message: "No speech was heard."
34
+ };
35
+ case "audio-capture":
36
+ return {
37
+ kind: DICTATION_ENGINE_ERROR_KINDS.permission,
38
+ message: "No microphone was found."
39
+ };
40
+ case "aborted":
41
+ return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: "Dictation was interrupted." };
42
+ default:
43
+ return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: "Dictation stopped unexpectedly." };
44
+ }
45
+ }
46
+ function foldWebSpeechResults(results, resultIndex) {
47
+ var _a;
48
+ let final = "";
49
+ let interim = "";
50
+ for (let i = resultIndex; i < results.length; i += 1) {
51
+ const result = results[i];
52
+ const transcript = ((_a = result == null ? void 0 : result[0]) == null ? void 0 : _a.transcript) ?? "";
53
+ if (!transcript) continue;
54
+ if (result.isFinal) final = final ? `${final} ${transcript.trim()}` : transcript.trim();
55
+ else interim = interim ? `${interim} ${transcript.trim()}` : transcript.trim();
56
+ }
57
+ return { final, interim };
58
+ }
59
+ function createWebSpeechDictationEngine() {
60
+ return {
61
+ id: "web-speech",
62
+ isSupported: () => resolveSpeechRecognitionCtor() !== null,
63
+ start: ({ lang, callbacks }) => {
64
+ const Ctor = resolveSpeechRecognitionCtor();
65
+ if (!Ctor) {
66
+ callbacks.onError({
67
+ kind: DICTATION_ENGINE_ERROR_KINDS.unsupported,
68
+ message: "This browser has no speech recognition."
69
+ });
70
+ callbacks.onEnd();
71
+ return { stop: () => void 0, abort: () => void 0 };
72
+ }
73
+ const recognition = new Ctor();
74
+ recognition.continuous = true;
75
+ recognition.interimResults = true;
76
+ recognition.lang = lang;
77
+ let ended = false;
78
+ const end = () => {
79
+ if (ended) return;
80
+ ended = true;
81
+ recognition.onresult = null;
82
+ recognition.onerror = null;
83
+ recognition.onend = null;
84
+ callbacks.onEnd();
85
+ };
86
+ recognition.onresult = (event) => {
87
+ callbacks.onTranscript(foldWebSpeechResults(event.results, event.resultIndex));
88
+ };
89
+ recognition.onerror = (event) => {
90
+ if (event.error === "aborted") return;
91
+ callbacks.onError(classifyWebSpeechError(event.error));
92
+ };
93
+ recognition.onend = end;
94
+ try {
95
+ recognition.start();
96
+ } catch (error) {
97
+ callbacks.onError({
98
+ kind: DICTATION_ENGINE_ERROR_KINDS.unknown,
99
+ message: error instanceof Error ? error.message : "Dictation could not start."
100
+ });
101
+ end();
102
+ }
103
+ return {
104
+ stop: () => {
105
+ try {
106
+ recognition.stop();
107
+ } catch {
108
+ end();
109
+ }
110
+ },
111
+ abort: () => {
112
+ try {
113
+ recognition.abort();
114
+ } finally {
115
+ end();
116
+ }
117
+ }
118
+ };
119
+ }
120
+ };
121
+ }
122
+ export {
123
+ DICTATION_ENGINE_ERROR_KINDS,
124
+ classifyWebSpeechError,
125
+ createWebSpeechDictationEngine,
126
+ foldWebSpeechResults
127
+ };
128
+ //# sourceMappingURL=dictation-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictation-engine.js","sources":["../../src/dictation/dictation-engine.ts"],"sourcesContent":["/**\n * Speech-to-text engines behind the composer's dictation mode.\n *\n * An engine turns microphone audio into a stream of transcript updates. The\n * SDK ships the browser's own `SpeechRecognition` engine (Chrome, Edge,\n * Safari), which needs no backend. A host can plug in another engine (for\n * example a relay to a server-side recognizer) through the `engine` option\n * of `useDictation` / `<AthenaChat dictation>`; the composer UI is engine\n * agnostic.\n */\n\nexport interface DictationTranscriptUpdate {\n /** Text the engine has committed. Appended to earlier committed text. */\n final: string;\n /** Text still being recognized; replaced on the next update. */\n interim: string;\n}\n\nexport interface DictationEngineCallbacks {\n onTranscript: (update: DictationTranscriptUpdate) => void;\n /** The engine stopped on its own (silence timeout, network, permission). */\n onEnd: () => void;\n onError: (error: DictationEngineError) => void;\n}\n\nexport interface DictationEngineSession {\n /** Ask the engine to finish. `onEnd` fires once it has flushed. */\n stop: () => void;\n /** Tear the engine down without waiting for a flush. */\n abort: () => void;\n}\n\nexport interface DictationEngine {\n /** Stable identifier surfaced in diagnostics (e.g. `web-speech`). */\n readonly id: string;\n /**\n * Whether this engine can run in the current browser. When false the\n * composer hides the mic instead of rendering a control that cannot work.\n */\n isSupported: () => boolean;\n /**\n * Start recognizing. `stream` is the microphone stream the composer already\n * opened for the waveform; engines that read audio themselves (Web Speech)\n * ignore it, relay engines encode it.\n */\n start: (options: {\n stream: MediaStream | null;\n lang: string;\n callbacks: DictationEngineCallbacks;\n }) => DictationEngineSession;\n}\n\nexport const DICTATION_ENGINE_ERROR_KINDS = {\n /** The user denied the microphone, or the browser blocked it. */\n permission: 'permission',\n /** No speech engine is available in this browser. */\n unsupported: 'unsupported',\n /** The engine could not reach its recognizer. */\n connection: 'connection',\n /** The engine heard nothing for its silence window. */\n noSpeech: 'no-speech',\n unknown: 'unknown',\n} as const;\n\nexport type DictationEngineErrorKind =\n (typeof DICTATION_ENGINE_ERROR_KINDS)[keyof typeof DICTATION_ENGINE_ERROR_KINDS];\n\nexport interface DictationEngineError {\n kind: DictationEngineErrorKind;\n /** One line the composer can show the user. */\n message: string;\n}\n\n// ─── Web Speech API engine ──────────────────────────────────────────\n\n// The Web Speech API has no lib.dom entry in every TypeScript version the SDK\n// supports, and browsers still vendor-prefix the constructor.\ninterface SpeechRecognitionAlternativeLike {\n transcript: string;\n}\ninterface SpeechRecognitionResultLike {\n isFinal: boolean;\n readonly length: number;\n [index: number]: SpeechRecognitionAlternativeLike;\n}\ninterface SpeechRecognitionEventLike extends Event {\n resultIndex: number;\n results: ArrayLike<SpeechRecognitionResultLike>;\n}\ninterface SpeechRecognitionErrorEventLike extends Event {\n error: string;\n message?: string;\n}\ninterface SpeechRecognitionLike extends EventTarget {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start: () => void;\n stop: () => void;\n abort: () => void;\n onresult: ((event: SpeechRecognitionEventLike) => void) | null;\n onerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;\n onend: (() => void) | null;\n}\ntype SpeechRecognitionCtor = new () => SpeechRecognitionLike;\n\nfunction resolveSpeechRecognitionCtor(): SpeechRecognitionCtor | null {\n if (typeof window === 'undefined') return null;\n const scope = window as unknown as {\n SpeechRecognition?: SpeechRecognitionCtor;\n webkitSpeechRecognition?: SpeechRecognitionCtor;\n };\n return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;\n}\n\n/** Map a Web Speech `error` code to the engine's error vocabulary. */\nexport function classifyWebSpeechError(code: string): DictationEngineError {\n switch (code) {\n case 'not-allowed':\n case 'service-not-allowed':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.permission,\n message: 'Microphone access was denied. Allow the microphone for this site and try again.',\n };\n case 'network':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.connection,\n message: 'Speech recognition could not connect. Check your connection and try again.',\n };\n case 'no-speech':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.noSpeech,\n message: 'No speech was heard.',\n };\n case 'audio-capture':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.permission,\n message: 'No microphone was found.',\n };\n case 'aborted':\n return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: 'Dictation was interrupted.' };\n default:\n return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: 'Dictation stopped unexpectedly.' };\n }\n}\n\n/**\n * Fold one Web Speech result event into `{ final, interim }`. Finalized\n * alternatives from `resultIndex` onward are joined with a space; the rest is\n * the interim tail. Pure so it can be tested without a browser.\n */\nexport function foldWebSpeechResults(\n results: ArrayLike<SpeechRecognitionResultLike>,\n resultIndex: number,\n): DictationTranscriptUpdate {\n let final = '';\n let interim = '';\n for (let i = resultIndex; i < results.length; i += 1) {\n const result = results[i];\n const transcript = result?.[0]?.transcript ?? '';\n if (!transcript) continue;\n if (result.isFinal) final = final ? `${final} ${transcript.trim()}` : transcript.trim();\n else interim = interim ? `${interim} ${transcript.trim()}` : transcript.trim();\n }\n return { final, interim };\n}\n\n/**\n * The browser's own recognizer. Zero dependencies and zero backend; Chrome\n * and Edge stream partial results, Safari returns results in chunks, Firefox\n * has no implementation (`isSupported()` is false there).\n */\nexport function createWebSpeechDictationEngine(): DictationEngine {\n return {\n id: 'web-speech',\n isSupported: () => resolveSpeechRecognitionCtor() !== null,\n start: ({ lang, callbacks }) => {\n const Ctor = resolveSpeechRecognitionCtor();\n if (!Ctor) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unsupported,\n message: 'This browser has no speech recognition.',\n });\n callbacks.onEnd();\n return { stop: () => undefined, abort: () => undefined };\n }\n const recognition = new Ctor();\n recognition.continuous = true;\n recognition.interimResults = true;\n recognition.lang = lang;\n let ended = false;\n const end = () => {\n if (ended) return;\n ended = true;\n recognition.onresult = null;\n recognition.onerror = null;\n recognition.onend = null;\n callbacks.onEnd();\n };\n recognition.onresult = (event) => {\n callbacks.onTranscript(foldWebSpeechResults(event.results, event.resultIndex));\n };\n recognition.onerror = (event) => {\n // `aborted` follows our own abort(); the session already ended.\n if (event.error === 'aborted') return;\n callbacks.onError(classifyWebSpeechError(event.error));\n };\n recognition.onend = end;\n try {\n recognition.start();\n } catch (error) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unknown,\n message: error instanceof Error ? error.message : 'Dictation could not start.',\n });\n end();\n }\n return {\n stop: () => {\n try {\n recognition.stop();\n } catch {\n end();\n }\n },\n abort: () => {\n try {\n recognition.abort();\n } finally {\n end();\n }\n },\n };\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const DICTATION_STATUSES = {
4
+ idle: "idle",
5
+ starting: "starting",
6
+ listening: "listening",
7
+ stopping: "stopping",
8
+ error: "error"
9
+ };
10
+ const INITIAL_DICTATION_STATE = {
11
+ status: DICTATION_STATUSES.idle,
12
+ transcript: { final: "", interim: "" },
13
+ error: null
14
+ };
15
+ function joinSpeech(head, tail) {
16
+ const left = head.trim();
17
+ const right = tail.trim();
18
+ if (!left) return right;
19
+ if (!right) return left;
20
+ return `${left} ${right}`;
21
+ }
22
+ function dictationReducer(state, action) {
23
+ switch (action.type) {
24
+ case "start":
25
+ return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };
26
+ case "listening":
27
+ return state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.listening } : state;
28
+ case "transcript": {
29
+ if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {
30
+ return state;
31
+ }
32
+ return {
33
+ ...state,
34
+ transcript: {
35
+ final: joinSpeech(state.transcript.final, action.update.final),
36
+ interim: action.update.interim
37
+ }
38
+ };
39
+ }
40
+ case "stop":
41
+ return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.stopping } : state;
42
+ case "ended":
43
+ return {
44
+ ...state,
45
+ status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,
46
+ transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: "" }
47
+ };
48
+ case "cancel":
49
+ return INITIAL_DICTATION_STATE;
50
+ case "error":
51
+ return { ...state, status: DICTATION_STATUSES.error, error: action.error };
52
+ default:
53
+ return state;
54
+ }
55
+ }
56
+ function spokenText(transcript) {
57
+ return joinSpeech(transcript.final, transcript.interim);
58
+ }
59
+ function composeDictationDraft(existingDraft, spoken) {
60
+ return joinSpeech(existingDraft, spoken);
61
+ }
62
+ exports.DICTATION_STATUSES = DICTATION_STATUSES;
63
+ exports.INITIAL_DICTATION_STATE = INITIAL_DICTATION_STATE;
64
+ exports.composeDictationDraft = composeDictationDraft;
65
+ exports.dictationReducer = dictationReducer;
66
+ exports.joinSpeech = joinSpeech;
67
+ exports.spokenText = spokenText;
68
+ //# sourceMappingURL=dictation-state.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictation-state.cjs","sources":["../../src/dictation/dictation-state.ts"],"sourcesContent":["import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';\n\n/**\n * The dictation session's state machine, kept pure so the transitions are\n * testable without a microphone.\n *\n * idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle\n * ▲ │ │ │\n * └────cancel──────┴──────────────────────────┴─────────────────────┘\n *\n * `error` is a terminal state shown inline until the next start or cancel.\n */\nexport const DICTATION_STATUSES = {\n idle: 'idle',\n starting: 'starting',\n listening: 'listening',\n stopping: 'stopping',\n error: 'error',\n} as const;\n\nexport type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];\n\nexport interface DictationTranscript {\n /** Everything the engine committed this session, space-joined. */\n final: string;\n /** The in-progress tail; replaced on every update. */\n interim: string;\n}\n\nexport interface DictationState {\n status: DictationStatus;\n transcript: DictationTranscript;\n error: DictationEngineError | null;\n}\n\nexport type DictationAction =\n | { type: 'start' }\n | { type: 'listening' }\n | { type: 'transcript'; update: DictationTranscriptUpdate }\n | { type: 'stop' }\n | { type: 'ended' }\n | { type: 'cancel' }\n | { type: 'error'; error: DictationEngineError };\n\nexport const INITIAL_DICTATION_STATE: DictationState = {\n status: DICTATION_STATUSES.idle,\n transcript: { final: '', interim: '' },\n error: null,\n};\n\n/** Join two transcript fragments with one space, ignoring empty sides. */\nexport function joinSpeech(head: string, tail: string): string {\n const left = head.trim();\n const right = tail.trim();\n if (!left) return right;\n if (!right) return left;\n return `${left} ${right}`;\n}\n\nexport function dictationReducer(state: DictationState, action: DictationAction): DictationState {\n switch (action.type) {\n case 'start':\n return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };\n case 'listening':\n return state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.listening }\n : state;\n case 'transcript': {\n if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {\n return state;\n }\n return {\n ...state,\n transcript: {\n final: joinSpeech(state.transcript.final, action.update.final),\n interim: action.update.interim,\n },\n };\n }\n case 'stop':\n return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.stopping }\n : state;\n case 'ended':\n // A flush may still have an interim tail; commit it, the engine will\n // not refine it any further. An error reported before the engine closed\n // stays visible: only a start or cancel clears it.\n return {\n ...state,\n status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,\n transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: '' },\n };\n case 'cancel':\n return INITIAL_DICTATION_STATE;\n case 'error':\n return { ...state, status: DICTATION_STATUSES.error, error: action.error };\n default:\n return state;\n }\n}\n\n/** The text a dictation session hands to the composer once it ends. */\nexport function spokenText(transcript: DictationTranscript): string {\n return joinSpeech(transcript.final, transcript.interim);\n}\n\n/**\n * Merge spoken text into the draft that existed before recording started.\n * Manual edits are kept; the speech is appended after one space.\n */\nexport function composeDictationDraft(existingDraft: string, spoken: string): string {\n return joinSpeech(existingDraft, spoken);\n}\n"],"names":[],"mappings":";;AAYO,MAAM,qBAAqB;AAAA,EAChC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AACT;AA0BO,MAAM,0BAA0C;AAAA,EACrD,QAAQ,mBAAmB;AAAA,EAC3B,YAAY,EAAE,OAAO,IAAI,SAAS,GAAA;AAAA,EAClC,OAAO;AACT;AAGO,SAAS,WAAW,MAAc,MAAsB;AAC7D,QAAM,OAAO,KAAK,KAAA;AAClB,QAAM,QAAQ,KAAK,KAAA;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,IAAI,IAAI,KAAK;AACzB;AAEO,SAAS,iBAAiB,OAAuB,QAAyC;AAC/F,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,yBAAyB,QAAQ,mBAAmB,SAAA;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,WACvC,EAAE,GAAG,OAAO,QAAQ,mBAAmB,UAAA,IACvC;AAAA,IACN,KAAK,cAAc;AACjB,UAAI,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,UAAU;AACjG,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,UACV,OAAO,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,KAAK;AAAA,UAC7D,SAAS,OAAO,OAAO;AAAA,QAAA;AAAA,MACzB;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,WACxF,EAAE,GAAG,OAAO,QAAQ,mBAAmB,aACvC;AAAA,IACN,KAAK;AAIH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,mBAAmB,QAAQ,mBAAmB,QAAQ,mBAAmB;AAAA,QAClG,YAAY,EAAE,OAAO,WAAW,MAAM,WAAW,OAAO,MAAM,WAAW,OAAO,GAAG,SAAS,GAAA;AAAA,MAAG;AAAA,IAEnG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,mBAAmB,OAAO,OAAO,OAAO,MAAA;AAAA,IACrE;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,WAAW,YAAyC;AAClE,SAAO,WAAW,WAAW,OAAO,WAAW,OAAO;AACxD;AAMO,SAAS,sBAAsB,eAAuB,QAAwB;AACnF,SAAO,WAAW,eAAe,MAAM;AACzC;;;;;;;"}
@@ -0,0 +1,58 @@
1
+ import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';
2
+ /**
3
+ * The dictation session's state machine, kept pure so the transitions are
4
+ * testable without a microphone.
5
+ *
6
+ * idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle
7
+ * ▲ │ │ │
8
+ * └────cancel──────┴──────────────────────────┴─────────────────────┘
9
+ *
10
+ * `error` is a terminal state shown inline until the next start or cancel.
11
+ */
12
+ export declare const DICTATION_STATUSES: {
13
+ readonly idle: 'idle';
14
+ readonly starting: 'starting';
15
+ readonly listening: 'listening';
16
+ readonly stopping: 'stopping';
17
+ readonly error: 'error';
18
+ };
19
+ export type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];
20
+ export interface DictationTranscript {
21
+ /** Everything the engine committed this session, space-joined. */
22
+ final: string;
23
+ /** The in-progress tail; replaced on every update. */
24
+ interim: string;
25
+ }
26
+ export interface DictationState {
27
+ status: DictationStatus;
28
+ transcript: DictationTranscript;
29
+ error: DictationEngineError | null;
30
+ }
31
+ export type DictationAction = {
32
+ type: 'start';
33
+ } | {
34
+ type: 'listening';
35
+ } | {
36
+ type: 'transcript';
37
+ update: DictationTranscriptUpdate;
38
+ } | {
39
+ type: 'stop';
40
+ } | {
41
+ type: 'ended';
42
+ } | {
43
+ type: 'cancel';
44
+ } | {
45
+ type: 'error';
46
+ error: DictationEngineError;
47
+ };
48
+ export declare const INITIAL_DICTATION_STATE: DictationState;
49
+ /** Join two transcript fragments with one space, ignoring empty sides. */
50
+ export declare function joinSpeech(head: string, tail: string): string;
51
+ export declare function dictationReducer(state: DictationState, action: DictationAction): DictationState;
52
+ /** The text a dictation session hands to the composer once it ends. */
53
+ export declare function spokenText(transcript: DictationTranscript): string;
54
+ /**
55
+ * Merge spoken text into the draft that existed before recording started.
56
+ * Manual edits are kept; the speech is appended after one space.
57
+ */
58
+ export declare function composeDictationDraft(existingDraft: string, spoken: string): string;
@@ -0,0 +1,68 @@
1
+ const DICTATION_STATUSES = {
2
+ idle: "idle",
3
+ starting: "starting",
4
+ listening: "listening",
5
+ stopping: "stopping",
6
+ error: "error"
7
+ };
8
+ const INITIAL_DICTATION_STATE = {
9
+ status: DICTATION_STATUSES.idle,
10
+ transcript: { final: "", interim: "" },
11
+ error: null
12
+ };
13
+ function joinSpeech(head, tail) {
14
+ const left = head.trim();
15
+ const right = tail.trim();
16
+ if (!left) return right;
17
+ if (!right) return left;
18
+ return `${left} ${right}`;
19
+ }
20
+ function dictationReducer(state, action) {
21
+ switch (action.type) {
22
+ case "start":
23
+ return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };
24
+ case "listening":
25
+ return state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.listening } : state;
26
+ case "transcript": {
27
+ if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {
28
+ return state;
29
+ }
30
+ return {
31
+ ...state,
32
+ transcript: {
33
+ final: joinSpeech(state.transcript.final, action.update.final),
34
+ interim: action.update.interim
35
+ }
36
+ };
37
+ }
38
+ case "stop":
39
+ return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.stopping } : state;
40
+ case "ended":
41
+ return {
42
+ ...state,
43
+ status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,
44
+ transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: "" }
45
+ };
46
+ case "cancel":
47
+ return INITIAL_DICTATION_STATE;
48
+ case "error":
49
+ return { ...state, status: DICTATION_STATUSES.error, error: action.error };
50
+ default:
51
+ return state;
52
+ }
53
+ }
54
+ function spokenText(transcript) {
55
+ return joinSpeech(transcript.final, transcript.interim);
56
+ }
57
+ function composeDictationDraft(existingDraft, spoken) {
58
+ return joinSpeech(existingDraft, spoken);
59
+ }
60
+ export {
61
+ DICTATION_STATUSES,
62
+ INITIAL_DICTATION_STATE,
63
+ composeDictationDraft,
64
+ dictationReducer,
65
+ joinSpeech,
66
+ spokenText
67
+ };
68
+ //# sourceMappingURL=dictation-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictation-state.js","sources":["../../src/dictation/dictation-state.ts"],"sourcesContent":["import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';\n\n/**\n * The dictation session's state machine, kept pure so the transitions are\n * testable without a microphone.\n *\n * idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle\n * ▲ │ │ │\n * └────cancel──────┴──────────────────────────┴─────────────────────┘\n *\n * `error` is a terminal state shown inline until the next start or cancel.\n */\nexport const DICTATION_STATUSES = {\n idle: 'idle',\n starting: 'starting',\n listening: 'listening',\n stopping: 'stopping',\n error: 'error',\n} as const;\n\nexport type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];\n\nexport interface DictationTranscript {\n /** Everything the engine committed this session, space-joined. */\n final: string;\n /** The in-progress tail; replaced on every update. */\n interim: string;\n}\n\nexport interface DictationState {\n status: DictationStatus;\n transcript: DictationTranscript;\n error: DictationEngineError | null;\n}\n\nexport type DictationAction =\n | { type: 'start' }\n | { type: 'listening' }\n | { type: 'transcript'; update: DictationTranscriptUpdate }\n | { type: 'stop' }\n | { type: 'ended' }\n | { type: 'cancel' }\n | { type: 'error'; error: DictationEngineError };\n\nexport const INITIAL_DICTATION_STATE: DictationState = {\n status: DICTATION_STATUSES.idle,\n transcript: { final: '', interim: '' },\n error: null,\n};\n\n/** Join two transcript fragments with one space, ignoring empty sides. */\nexport function joinSpeech(head: string, tail: string): string {\n const left = head.trim();\n const right = tail.trim();\n if (!left) return right;\n if (!right) return left;\n return `${left} ${right}`;\n}\n\nexport function dictationReducer(state: DictationState, action: DictationAction): DictationState {\n switch (action.type) {\n case 'start':\n return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };\n case 'listening':\n return state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.listening }\n : state;\n case 'transcript': {\n if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {\n return state;\n }\n return {\n ...state,\n transcript: {\n final: joinSpeech(state.transcript.final, action.update.final),\n interim: action.update.interim,\n },\n };\n }\n case 'stop':\n return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.stopping }\n : state;\n case 'ended':\n // A flush may still have an interim tail; commit it, the engine will\n // not refine it any further. An error reported before the engine closed\n // stays visible: only a start or cancel clears it.\n return {\n ...state,\n status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,\n transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: '' },\n };\n case 'cancel':\n return INITIAL_DICTATION_STATE;\n case 'error':\n return { ...state, status: DICTATION_STATUSES.error, error: action.error };\n default:\n return state;\n }\n}\n\n/** The text a dictation session hands to the composer once it ends. */\nexport function spokenText(transcript: DictationTranscript): string {\n return joinSpeech(transcript.final, transcript.interim);\n}\n\n/**\n * Merge spoken text into the draft that existed before recording started.\n * Manual edits are kept; the speech is appended after one space.\n */\nexport function composeDictationDraft(existingDraft: string, spoken: string): string {\n return joinSpeech(existingDraft, spoken);\n}\n"],"names":[],"mappings":"AAYO,MAAM,qBAAqB;AAAA,EAChC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AACT;AA0BO,MAAM,0BAA0C;AAAA,EACrD,QAAQ,mBAAmB;AAAA,EAC3B,YAAY,EAAE,OAAO,IAAI,SAAS,GAAA;AAAA,EAClC,OAAO;AACT;AAGO,SAAS,WAAW,MAAc,MAAsB;AAC7D,QAAM,OAAO,KAAK,KAAA;AAClB,QAAM,QAAQ,KAAK,KAAA;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,IAAI,IAAI,KAAK;AACzB;AAEO,SAAS,iBAAiB,OAAuB,QAAyC;AAC/F,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,yBAAyB,QAAQ,mBAAmB,SAAA;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,WACvC,EAAE,GAAG,OAAO,QAAQ,mBAAmB,UAAA,IACvC;AAAA,IACN,KAAK,cAAc;AACjB,UAAI,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,UAAU;AACjG,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,UACV,OAAO,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,KAAK;AAAA,UAC7D,SAAS,OAAO,OAAO;AAAA,QAAA;AAAA,MACzB;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,WACxF,EAAE,GAAG,OAAO,QAAQ,mBAAmB,aACvC;AAAA,IACN,KAAK;AAIH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,mBAAmB,QAAQ,mBAAmB,QAAQ,mBAAmB;AAAA,QAClG,YAAY,EAAE,OAAO,WAAW,MAAM,WAAW,OAAO,MAAM,WAAW,OAAO,GAAG,SAAS,GAAA;AAAA,MAAG;AAAA,IAEnG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,mBAAmB,OAAO,OAAO,OAAO,MAAA;AAAA,IACrE;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,WAAW,YAAyC;AAClE,SAAO,WAAW,WAAW,OAAO,WAAW,OAAO;AACxD;AAMO,SAAS,sBAAsB,eAAuB,QAAwB;AACnF,SAAO,WAAW,eAAe,MAAM;AACzC;"}
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react = require("react");
4
+ const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
5
+ const dictationEngine = require("./dictation-engine.cjs");
6
+ const dictationState = require("./dictation-state.cjs");
7
+ const STOP_FLUSH_TIMEOUT_MS = 1500;
8
+ const EMPTY_TRANSCRIPT = { final: "", interim: "" };
9
+ function documentLanguage() {
10
+ if (typeof document !== "undefined" && document.documentElement.lang) {
11
+ return document.documentElement.lang;
12
+ }
13
+ if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
14
+ return "en-US";
15
+ }
16
+ async function openMicrophone() {
17
+ var _a;
18
+ if (typeof navigator === "undefined" || !((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia)) return null;
19
+ return navigator.mediaDevices.getUserMedia({ audio: true });
20
+ }
21
+ function releaseStream(stream) {
22
+ if (!stream) return;
23
+ for (const track of stream.getTracks()) track.stop();
24
+ }
25
+ function useDictation(options = {}) {
26
+ const engine = react.useMemo(() => options.engine ?? dictationEngine.createWebSpeechDictationEngine(), [options.engine]);
27
+ const { lang, captureStream = true, onAutoEnd } = options;
28
+ const [state, dispatch] = react.useReducer(dictationState.dictationReducer, dictationState.INITIAL_DICTATION_STATE);
29
+ const [stream, setStream] = react.useState(null);
30
+ const [isSupported, setIsSupported] = react.useState(false);
31
+ const sessionRef = react.useRef(null);
32
+ const streamRef = react.useRef(null);
33
+ const stateRef = react.useRef(state);
34
+ stateRef.current = state;
35
+ const transcriptRef = react.useRef(EMPTY_TRANSCRIPT);
36
+ const onAutoEndRef = react.useRef(onAutoEnd);
37
+ onAutoEndRef.current = onAutoEnd;
38
+ const stopWaitersRef = react.useRef([]);
39
+ const generationRef = react.useRef(0);
40
+ react.useEffect(() => {
41
+ setIsSupported(engine.isSupported());
42
+ }, [engine]);
43
+ const settleWaiters = react.useCallback((spoken) => {
44
+ const waiters = stopWaitersRef.current;
45
+ stopWaitersRef.current = [];
46
+ for (const resolve of waiters) resolve(spoken);
47
+ }, []);
48
+ const releaseSession = react.useCallback(() => {
49
+ sessionRef.current = null;
50
+ releaseStream(streamRef.current);
51
+ streamRef.current = null;
52
+ setStream(null);
53
+ }, []);
54
+ const teardown = react.useCallback(() => {
55
+ var _a;
56
+ generationRef.current += 1;
57
+ (_a = sessionRef.current) == null ? void 0 : _a.abort();
58
+ releaseSession();
59
+ transcriptRef.current = EMPTY_TRANSCRIPT;
60
+ settleWaiters("");
61
+ }, [releaseSession, settleWaiters]);
62
+ const reportEngineError = react.useCallback(
63
+ (engineError) => {
64
+ athenaDiagnostics.athenaDiagnostics.emit("composer.dictation", {
65
+ level: "warn",
66
+ data: { phase: "error", engine: engine.id, kind: engineError.kind }
67
+ });
68
+ dispatch({ type: "error", error: engineError });
69
+ },
70
+ [engine.id]
71
+ );
72
+ const start = react.useCallback(() => {
73
+ if (sessionRef.current) return;
74
+ const generation = generationRef.current + 1;
75
+ generationRef.current = generation;
76
+ transcriptRef.current = EMPTY_TRANSCRIPT;
77
+ dispatch({ type: "start" });
78
+ athenaDiagnostics.athenaDiagnostics.emit("composer.dictation", {
79
+ level: "debug",
80
+ data: { phase: "start", engine: engine.id }
81
+ });
82
+ const run = async () => {
83
+ let microphone = null;
84
+ if (captureStream) {
85
+ try {
86
+ microphone = await openMicrophone();
87
+ } catch (error) {
88
+ if (generationRef.current !== generation) {
89
+ releaseStream(microphone);
90
+ return;
91
+ }
92
+ dispatch({
93
+ type: "error",
94
+ error: {
95
+ kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.permission,
96
+ message: error instanceof DOMException && error.name === "NotFoundError" ? "No microphone was found." : "Microphone access was denied. Allow the microphone for this site and try again."
97
+ }
98
+ });
99
+ return;
100
+ }
101
+ }
102
+ if (generationRef.current !== generation) {
103
+ releaseStream(microphone);
104
+ return;
105
+ }
106
+ streamRef.current = microphone;
107
+ setStream(microphone);
108
+ let terminated = false;
109
+ const endSession = () => {
110
+ var _a;
111
+ terminated = true;
112
+ const current = stateRef.current;
113
+ const spoken = dictationState.spokenText(transcriptRef.current);
114
+ const endedOnItsOwn = stopWaitersRef.current.length === 0 && current.status === dictationState.DICTATION_STATUSES.listening;
115
+ dispatch({ type: "ended" });
116
+ releaseSession();
117
+ settleWaiters(spoken);
118
+ if (endedOnItsOwn) (_a = onAutoEndRef.current) == null ? void 0 : _a.call(onAutoEndRef, spoken);
119
+ };
120
+ let session;
121
+ try {
122
+ session = engine.start({
123
+ stream: microphone,
124
+ lang: lang ?? documentLanguage(),
125
+ callbacks: {
126
+ onTranscript: (update) => {
127
+ if (generationRef.current !== generation) return;
128
+ transcriptRef.current = {
129
+ final: dictationState.joinSpeech(transcriptRef.current.final, update.final),
130
+ interim: update.interim
131
+ };
132
+ dispatch({ type: "transcript", update });
133
+ },
134
+ onError: (engineError) => {
135
+ if (generationRef.current !== generation) return;
136
+ if (engineError.kind === dictationEngine.DICTATION_ENGINE_ERROR_KINDS.noSpeech) return;
137
+ reportEngineError(engineError);
138
+ },
139
+ onEnd: () => {
140
+ if (generationRef.current !== generation) return;
141
+ endSession();
142
+ }
143
+ }
144
+ });
145
+ } catch (error) {
146
+ releaseSession();
147
+ reportEngineError({
148
+ kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.connection,
149
+ message: error instanceof Error ? error.message : "Dictation could not start."
150
+ });
151
+ return;
152
+ }
153
+ if (terminated) return;
154
+ sessionRef.current = session;
155
+ dispatch({ type: "listening" });
156
+ };
157
+ void run();
158
+ }, [captureStream, engine, lang, releaseSession, reportEngineError, settleWaiters]);
159
+ const stop = react.useCallback(() => {
160
+ const session = sessionRef.current;
161
+ if (!session) {
162
+ if (stateRef.current.status === dictationState.DICTATION_STATUSES.starting) {
163
+ teardown();
164
+ dispatch({ type: "cancel" });
165
+ return Promise.resolve("");
166
+ }
167
+ return Promise.resolve(dictationState.spokenText(transcriptRef.current));
168
+ }
169
+ dispatch({ type: "stop" });
170
+ const flushed = new Promise((resolve) => {
171
+ stopWaitersRef.current.push(resolve);
172
+ window.setTimeout(() => {
173
+ if (sessionRef.current === session) {
174
+ session.abort();
175
+ dispatch({ type: "ended" });
176
+ releaseSession();
177
+ settleWaiters(dictationState.spokenText(transcriptRef.current));
178
+ }
179
+ }, STOP_FLUSH_TIMEOUT_MS);
180
+ });
181
+ session.stop();
182
+ return flushed;
183
+ }, [releaseSession, settleWaiters, teardown]);
184
+ const cancel = react.useCallback(() => {
185
+ teardown();
186
+ dispatch({ type: "cancel" });
187
+ }, [teardown]);
188
+ react.useEffect(() => () => teardown(), [teardown]);
189
+ const isActive = state.status === dictationState.DICTATION_STATUSES.starting || state.status === dictationState.DICTATION_STATUSES.listening || state.status === dictationState.DICTATION_STATUSES.stopping;
190
+ return {
191
+ status: state.status,
192
+ isActive,
193
+ transcript: state.transcript,
194
+ text: dictationState.spokenText(state.transcript),
195
+ error: state.error,
196
+ stream,
197
+ isSupported,
198
+ start,
199
+ stop,
200
+ cancel
201
+ };
202
+ }
203
+ exports.useDictation = useDictation;
204
+ //# sourceMappingURL=use-dictation.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dictation.cjs","sources":["../../src/dictation/use-dictation.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport {\n createWebSpeechDictationEngine,\n DICTATION_ENGINE_ERROR_KINDS,\n type DictationEngine,\n type DictationEngineError,\n type DictationEngineSession,\n} from './dictation-engine';\nimport {\n DICTATION_STATUSES,\n dictationReducer,\n INITIAL_DICTATION_STATE,\n joinSpeech,\n spokenText,\n type DictationStatus,\n type DictationTranscript,\n} from './dictation-state';\n\nexport interface UseDictationOptions {\n /** Speech engine. Defaults to the browser's Web Speech API. */\n engine?: DictationEngine;\n /** BCP-47 language tag. Defaults to the document language, else `en-US`. */\n lang?: string;\n /**\n * Open the microphone through `getUserMedia` so callers get a `stream` for\n * a waveform. Web Speech opens its own capture; this second capture is\n * what the level meter reads. Defaults to true.\n */\n captureStream?: boolean;\n /**\n * The engine ended on its own (silence window, recognizer timeout) while\n * listening, without `stop()` or `cancel()`. Receives the spoken text so\n * the words are not lost; the composer puts them into the draft.\n */\n onAutoEnd?: (spoken: string) => void;\n}\n\nexport interface DictationController {\n status: DictationStatus;\n /** True while starting, listening or stopping. */\n isActive: boolean;\n transcript: DictationTranscript;\n /** `final` plus the interim tail, ready to hand to the composer. */\n text: string;\n error: DictationEngineError | null;\n /** The microphone stream while active (for a waveform), else null. */\n stream: MediaStream | null;\n /** Whether the engine can run here. Hide the control when false. */\n isSupported: boolean;\n start: () => void;\n /**\n * Finish recording. Resolves with the spoken text once the engine has\n * flushed (bounded wait), so trailing words are not lost.\n */\n stop: () => Promise<string>;\n /** Discard the session and its transcript. */\n cancel: () => void;\n}\n\n/** How long `stop()` waits for the engine's final flush before giving up. */\nconst STOP_FLUSH_TIMEOUT_MS = 1500;\n\ntype StopResolver = (spoken: string) => void;\n\nconst EMPTY_TRANSCRIPT: DictationTranscript = { final: '', interim: '' };\n\nfunction documentLanguage(): string {\n if (typeof document !== 'undefined' && document.documentElement.lang) {\n return document.documentElement.lang;\n }\n if (typeof navigator !== 'undefined' && navigator.language) return navigator.language;\n return 'en-US';\n}\n\nasync function openMicrophone(): Promise<MediaStream | null> {\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) return null;\n return navigator.mediaDevices.getUserMedia({ audio: true });\n}\n\nfunction releaseStream(stream: MediaStream | null): void {\n if (!stream) return;\n for (const track of stream.getTracks()) track.stop();\n}\n\n/**\n * Drive a dictation session: microphone, speech engine, and the transcript\n * state machine. The composer renders the waveform from `stream` and the\n * live caption from `transcript`; on `stop()` it appends `text` to the draft.\n */\nexport function useDictation(options: UseDictationOptions = {}): DictationController {\n const engine = useMemo(() => options.engine ?? createWebSpeechDictationEngine(), [options.engine]);\n const { lang, captureStream = true, onAutoEnd } = options;\n const [state, dispatch] = useReducer(dictationReducer, INITIAL_DICTATION_STATE);\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [isSupported, setIsSupported] = useState(false);\n\n const sessionRef = useRef<DictationEngineSession | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const stateRef = useRef(state);\n stateRef.current = state;\n // The authoritative transcript, updated synchronously as the engine reports\n // it. The reducer's copy renders the caption; this one is what `stop()` and\n // `onAutoEnd` hand over, so an engine that flushes its last words and ends\n // in the same tick — before React has rendered — loses nothing.\n const transcriptRef = useRef<DictationTranscript>(EMPTY_TRANSCRIPT);\n const onAutoEndRef = useRef(onAutoEnd);\n onAutoEndRef.current = onAutoEnd;\n // stop() callers waiting for the engine's final flush.\n const stopWaitersRef = useRef<StopResolver[]>([]);\n // Distinguishes a fresh session from the callbacks of a torn-down one.\n const generationRef = useRef(0);\n\n useEffect(() => {\n setIsSupported(engine.isSupported());\n }, [engine]);\n\n const settleWaiters = useCallback((spoken: string) => {\n const waiters = stopWaitersRef.current;\n stopWaitersRef.current = [];\n for (const resolve of waiters) resolve(spoken);\n }, []);\n\n const releaseSession = useCallback(() => {\n sessionRef.current = null;\n releaseStream(streamRef.current);\n streamRef.current = null;\n setStream(null);\n }, []);\n\n const teardown = useCallback(() => {\n generationRef.current += 1;\n sessionRef.current?.abort();\n releaseSession();\n transcriptRef.current = EMPTY_TRANSCRIPT;\n settleWaiters('');\n }, [releaseSession, settleWaiters]);\n\n const reportEngineError = useCallback(\n (engineError: DictationEngineError) => {\n athenaDiagnostics.emit('composer.dictation', {\n level: 'warn',\n data: { phase: 'error', engine: engine.id, kind: engineError.kind },\n });\n dispatch({ type: 'error', error: engineError });\n },\n [engine.id],\n );\n\n const start = useCallback(() => {\n if (sessionRef.current) return;\n const generation = generationRef.current + 1;\n generationRef.current = generation;\n transcriptRef.current = EMPTY_TRANSCRIPT;\n dispatch({ type: 'start' });\n athenaDiagnostics.emit('composer.dictation', {\n level: 'debug',\n data: { phase: 'start', engine: engine.id },\n });\n\n const run = async () => {\n let microphone: MediaStream | null = null;\n if (captureStream) {\n try {\n microphone = await openMicrophone();\n } catch (error) {\n if (generationRef.current !== generation) {\n releaseStream(microphone);\n return;\n }\n dispatch({\n type: 'error',\n error: {\n kind: DICTATION_ENGINE_ERROR_KINDS.permission,\n message:\n error instanceof DOMException && error.name === 'NotFoundError'\n ? 'No microphone was found.'\n : 'Microphone access was denied. Allow the microphone for this site and try again.',\n },\n });\n return;\n }\n }\n if (generationRef.current !== generation) {\n releaseStream(microphone);\n return;\n }\n streamRef.current = microphone;\n setStream(microphone);\n\n // An engine may fail synchronously inside start() (Web Speech throws\n // when recognition.start() is refused) and run onError/onEnd before we\n // hold the session. Those callbacks flip this flag so a dead session is\n // never stored, which would make every later mic click return early.\n let terminated = false;\n const endSession = () => {\n terminated = true;\n const current = stateRef.current;\n const spoken = spokenText(transcriptRef.current);\n // No stop() is waiting: the engine gave up on its own. Hand the\n // words over so they land in the draft instead of vanishing.\n const endedOnItsOwn =\n stopWaitersRef.current.length === 0 && current.status === DICTATION_STATUSES.listening;\n // The reducer keeps an error status through 'ended', so an error\n // reported just before the engine closed stays on screen.\n dispatch({ type: 'ended' });\n releaseSession();\n settleWaiters(spoken);\n if (endedOnItsOwn) onAutoEndRef.current?.(spoken);\n };\n let session: DictationEngineSession;\n try {\n session = engine.start({\n stream: microphone,\n lang: lang ?? documentLanguage(),\n callbacks: {\n onTranscript: (update) => {\n if (generationRef.current !== generation) return;\n transcriptRef.current = {\n final: joinSpeech(transcriptRef.current.final, update.final),\n interim: update.interim,\n };\n dispatch({ type: 'transcript', update });\n },\n onError: (engineError) => {\n if (generationRef.current !== generation) return;\n // A silence timeout is the engine giving up politely; keep the\n // words we have instead of showing an error.\n if (engineError.kind === DICTATION_ENGINE_ERROR_KINDS.noSpeech) return;\n reportEngineError(engineError);\n },\n onEnd: () => {\n if (generationRef.current !== generation) return;\n endSession();\n },\n },\n });\n } catch (error) {\n // A relay engine that cannot build its session must not strand the\n // microphone or leave the composer in `starting`.\n releaseSession();\n reportEngineError({\n kind: DICTATION_ENGINE_ERROR_KINDS.connection,\n message: error instanceof Error ? error.message : 'Dictation could not start.',\n });\n return;\n }\n if (terminated) return;\n sessionRef.current = session;\n dispatch({ type: 'listening' });\n };\n void run();\n }, [captureStream, engine, lang, releaseSession, reportEngineError, settleWaiters]);\n\n const stop = useCallback((): Promise<string> => {\n const session = sessionRef.current;\n if (!session) {\n // Stopped while the microphone prompt was still open: nothing was heard\n // yet, and the pending start must not bring the engine up afterwards.\n if (stateRef.current.status === DICTATION_STATUSES.starting) {\n teardown();\n dispatch({ type: 'cancel' });\n return Promise.resolve('');\n }\n return Promise.resolve(spokenText(transcriptRef.current));\n }\n dispatch({ type: 'stop' });\n const flushed = new Promise<string>((resolve) => {\n stopWaitersRef.current.push(resolve);\n window.setTimeout(() => {\n // Bounded wait: a recognizer that never fires onend must not hold the\n // composer hostage. Force the session closed and hand over what was\n // heard so far.\n if (sessionRef.current === session) {\n session.abort();\n dispatch({ type: 'ended' });\n releaseSession();\n settleWaiters(spokenText(transcriptRef.current));\n }\n }, STOP_FLUSH_TIMEOUT_MS);\n });\n session.stop();\n return flushed;\n }, [releaseSession, settleWaiters, teardown]);\n\n const cancel = useCallback(() => {\n teardown();\n dispatch({ type: 'cancel' });\n }, [teardown]);\n\n // Release the microphone with the component.\n useEffect(() => () => teardown(), [teardown]);\n\n const isActive =\n state.status === DICTATION_STATUSES.starting ||\n state.status === DICTATION_STATUSES.listening ||\n state.status === DICTATION_STATUSES.stopping;\n\n return {\n status: state.status,\n isActive,\n transcript: state.transcript,\n text: spokenText(state.transcript),\n error: state.error,\n stream,\n isSupported,\n start,\n stop,\n cancel,\n };\n}\n"],"names":["useMemo","createWebSpeechDictationEngine","useReducer","dictationReducer","INITIAL_DICTATION_STATE","useState","useRef","useEffect","useCallback","athenaDiagnostics","DICTATION_ENGINE_ERROR_KINDS","spokenText","DICTATION_STATUSES","joinSpeech"],"mappings":"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"}