@athenaintel/react 0.23.2 → 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 +40 -1
  2. package/dist/chat/AthenaChat.cjs +129 -29
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.d.ts +32 -1
  5. package/dist/chat/AthenaChat.js +130 -30
  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,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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"}
@@ -0,0 +1,47 @@
1
+ import { type DictationEngine, type DictationEngineError } from './dictation-engine';
2
+ import { type DictationStatus, type DictationTranscript } from './dictation-state';
3
+ export interface UseDictationOptions {
4
+ /** Speech engine. Defaults to the browser's Web Speech API. */
5
+ engine?: DictationEngine;
6
+ /** BCP-47 language tag. Defaults to the document language, else `en-US`. */
7
+ lang?: string;
8
+ /**
9
+ * Open the microphone through `getUserMedia` so callers get a `stream` for
10
+ * a waveform. Web Speech opens its own capture; this second capture is
11
+ * what the level meter reads. Defaults to true.
12
+ */
13
+ captureStream?: boolean;
14
+ /**
15
+ * The engine ended on its own (silence window, recognizer timeout) while
16
+ * listening, without `stop()` or `cancel()`. Receives the spoken text so
17
+ * the words are not lost; the composer puts them into the draft.
18
+ */
19
+ onAutoEnd?: (spoken: string) => void;
20
+ }
21
+ export interface DictationController {
22
+ status: DictationStatus;
23
+ /** True while starting, listening or stopping. */
24
+ isActive: boolean;
25
+ transcript: DictationTranscript;
26
+ /** `final` plus the interim tail, ready to hand to the composer. */
27
+ text: string;
28
+ error: DictationEngineError | null;
29
+ /** The microphone stream while active (for a waveform), else null. */
30
+ stream: MediaStream | null;
31
+ /** Whether the engine can run here. Hide the control when false. */
32
+ isSupported: boolean;
33
+ start: () => void;
34
+ /**
35
+ * Finish recording. Resolves with the spoken text once the engine has
36
+ * flushed (bounded wait), so trailing words are not lost.
37
+ */
38
+ stop: () => Promise<string>;
39
+ /** Discard the session and its transcript. */
40
+ cancel: () => void;
41
+ }
42
+ /**
43
+ * Drive a dictation session: microphone, speech engine, and the transcript
44
+ * state machine. The composer renders the waveform from `stream` and the
45
+ * live caption from `transcript`; on `stop()` it appends `text` to the draft.
46
+ */
47
+ export declare function useDictation(options?: UseDictationOptions): DictationController;
@@ -0,0 +1,204 @@
1
+ import { useMemo, useReducer, useState, useRef, useEffect, useCallback } from "react";
2
+ import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
3
+ import { createWebSpeechDictationEngine, DICTATION_ENGINE_ERROR_KINDS } from "./dictation-engine.js";
4
+ import { dictationReducer, INITIAL_DICTATION_STATE, DICTATION_STATUSES, spokenText, joinSpeech } from "./dictation-state.js";
5
+ const STOP_FLUSH_TIMEOUT_MS = 1500;
6
+ const EMPTY_TRANSCRIPT = { final: "", interim: "" };
7
+ function documentLanguage() {
8
+ if (typeof document !== "undefined" && document.documentElement.lang) {
9
+ return document.documentElement.lang;
10
+ }
11
+ if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
12
+ return "en-US";
13
+ }
14
+ async function openMicrophone() {
15
+ var _a;
16
+ if (typeof navigator === "undefined" || !((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia)) return null;
17
+ return navigator.mediaDevices.getUserMedia({ audio: true });
18
+ }
19
+ function releaseStream(stream) {
20
+ if (!stream) return;
21
+ for (const track of stream.getTracks()) track.stop();
22
+ }
23
+ function useDictation(options = {}) {
24
+ const engine = useMemo(() => options.engine ?? createWebSpeechDictationEngine(), [options.engine]);
25
+ const { lang, captureStream = true, onAutoEnd } = options;
26
+ const [state, dispatch] = useReducer(dictationReducer, INITIAL_DICTATION_STATE);
27
+ const [stream, setStream] = useState(null);
28
+ const [isSupported, setIsSupported] = useState(false);
29
+ const sessionRef = useRef(null);
30
+ const streamRef = useRef(null);
31
+ const stateRef = useRef(state);
32
+ stateRef.current = state;
33
+ const transcriptRef = useRef(EMPTY_TRANSCRIPT);
34
+ const onAutoEndRef = useRef(onAutoEnd);
35
+ onAutoEndRef.current = onAutoEnd;
36
+ const stopWaitersRef = useRef([]);
37
+ const generationRef = useRef(0);
38
+ useEffect(() => {
39
+ setIsSupported(engine.isSupported());
40
+ }, [engine]);
41
+ const settleWaiters = useCallback((spoken) => {
42
+ const waiters = stopWaitersRef.current;
43
+ stopWaitersRef.current = [];
44
+ for (const resolve of waiters) resolve(spoken);
45
+ }, []);
46
+ const releaseSession = useCallback(() => {
47
+ sessionRef.current = null;
48
+ releaseStream(streamRef.current);
49
+ streamRef.current = null;
50
+ setStream(null);
51
+ }, []);
52
+ const teardown = useCallback(() => {
53
+ var _a;
54
+ generationRef.current += 1;
55
+ (_a = sessionRef.current) == null ? void 0 : _a.abort();
56
+ releaseSession();
57
+ transcriptRef.current = EMPTY_TRANSCRIPT;
58
+ settleWaiters("");
59
+ }, [releaseSession, settleWaiters]);
60
+ const reportEngineError = useCallback(
61
+ (engineError) => {
62
+ athenaDiagnostics.emit("composer.dictation", {
63
+ level: "warn",
64
+ data: { phase: "error", engine: engine.id, kind: engineError.kind }
65
+ });
66
+ dispatch({ type: "error", error: engineError });
67
+ },
68
+ [engine.id]
69
+ );
70
+ const start = useCallback(() => {
71
+ if (sessionRef.current) return;
72
+ const generation = generationRef.current + 1;
73
+ generationRef.current = generation;
74
+ transcriptRef.current = EMPTY_TRANSCRIPT;
75
+ dispatch({ type: "start" });
76
+ athenaDiagnostics.emit("composer.dictation", {
77
+ level: "debug",
78
+ data: { phase: "start", engine: engine.id }
79
+ });
80
+ const run = async () => {
81
+ let microphone = null;
82
+ if (captureStream) {
83
+ try {
84
+ microphone = await openMicrophone();
85
+ } catch (error) {
86
+ if (generationRef.current !== generation) {
87
+ releaseStream(microphone);
88
+ return;
89
+ }
90
+ dispatch({
91
+ type: "error",
92
+ error: {
93
+ kind: DICTATION_ENGINE_ERROR_KINDS.permission,
94
+ message: error instanceof DOMException && error.name === "NotFoundError" ? "No microphone was found." : "Microphone access was denied. Allow the microphone for this site and try again."
95
+ }
96
+ });
97
+ return;
98
+ }
99
+ }
100
+ if (generationRef.current !== generation) {
101
+ releaseStream(microphone);
102
+ return;
103
+ }
104
+ streamRef.current = microphone;
105
+ setStream(microphone);
106
+ let terminated = false;
107
+ const endSession = () => {
108
+ var _a;
109
+ terminated = true;
110
+ const current = stateRef.current;
111
+ const spoken = spokenText(transcriptRef.current);
112
+ const endedOnItsOwn = stopWaitersRef.current.length === 0 && current.status === DICTATION_STATUSES.listening;
113
+ dispatch({ type: "ended" });
114
+ releaseSession();
115
+ settleWaiters(spoken);
116
+ if (endedOnItsOwn) (_a = onAutoEndRef.current) == null ? void 0 : _a.call(onAutoEndRef, spoken);
117
+ };
118
+ let session;
119
+ try {
120
+ session = engine.start({
121
+ stream: microphone,
122
+ lang: lang ?? documentLanguage(),
123
+ callbacks: {
124
+ onTranscript: (update) => {
125
+ if (generationRef.current !== generation) return;
126
+ transcriptRef.current = {
127
+ final: joinSpeech(transcriptRef.current.final, update.final),
128
+ interim: update.interim
129
+ };
130
+ dispatch({ type: "transcript", update });
131
+ },
132
+ onError: (engineError) => {
133
+ if (generationRef.current !== generation) return;
134
+ if (engineError.kind === DICTATION_ENGINE_ERROR_KINDS.noSpeech) return;
135
+ reportEngineError(engineError);
136
+ },
137
+ onEnd: () => {
138
+ if (generationRef.current !== generation) return;
139
+ endSession();
140
+ }
141
+ }
142
+ });
143
+ } catch (error) {
144
+ releaseSession();
145
+ reportEngineError({
146
+ kind: DICTATION_ENGINE_ERROR_KINDS.connection,
147
+ message: error instanceof Error ? error.message : "Dictation could not start."
148
+ });
149
+ return;
150
+ }
151
+ if (terminated) return;
152
+ sessionRef.current = session;
153
+ dispatch({ type: "listening" });
154
+ };
155
+ void run();
156
+ }, [captureStream, engine, lang, releaseSession, reportEngineError, settleWaiters]);
157
+ const stop = useCallback(() => {
158
+ const session = sessionRef.current;
159
+ if (!session) {
160
+ if (stateRef.current.status === DICTATION_STATUSES.starting) {
161
+ teardown();
162
+ dispatch({ type: "cancel" });
163
+ return Promise.resolve("");
164
+ }
165
+ return Promise.resolve(spokenText(transcriptRef.current));
166
+ }
167
+ dispatch({ type: "stop" });
168
+ const flushed = new Promise((resolve) => {
169
+ stopWaitersRef.current.push(resolve);
170
+ window.setTimeout(() => {
171
+ if (sessionRef.current === session) {
172
+ session.abort();
173
+ dispatch({ type: "ended" });
174
+ releaseSession();
175
+ settleWaiters(spokenText(transcriptRef.current));
176
+ }
177
+ }, STOP_FLUSH_TIMEOUT_MS);
178
+ });
179
+ session.stop();
180
+ return flushed;
181
+ }, [releaseSession, settleWaiters, teardown]);
182
+ const cancel = useCallback(() => {
183
+ teardown();
184
+ dispatch({ type: "cancel" });
185
+ }, [teardown]);
186
+ useEffect(() => () => teardown(), [teardown]);
187
+ const isActive = state.status === DICTATION_STATUSES.starting || state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.stopping;
188
+ return {
189
+ status: state.status,
190
+ isActive,
191
+ transcript: state.transcript,
192
+ text: spokenText(state.transcript),
193
+ error: state.error,
194
+ stream,
195
+ isSupported,
196
+ start,
197
+ stop,
198
+ cancel
199
+ };
200
+ }
201
+ export {
202
+ useDictation
203
+ };
204
+ //# sourceMappingURL=use-dictation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dictation.js","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":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -88,7 +88,12 @@ const threadPrefetcher = require("./threads/thread-prefetcher.cjs");
88
88
  const useRestConversationHistory = require("./threads/use-rest-conversation-history.cjs");
89
89
  const useStatewireConversationHistory = require("./threads/use-statewire-conversation-history.cjs");
90
90
  const useAthenaThreadManager = require("./threads/useAthenaThreadManager.cjs");
91
+ const useDictation = require("./dictation/use-dictation.cjs");
92
+ const DictationStrip = require("./dictation/DictationStrip.cjs");
93
+ const dictationEngine = require("./dictation/dictation-engine.cjs");
94
+ const dictationState = require("./dictation/dictation-state.cjs");
91
95
  const useAppendToComposer = require("./composer/useAppendToComposer.cjs");
96
+ const composerHandoff = require("./composer/composer-handoff.cjs");
92
97
  const useSendMessage = require("./composer/useSendMessage.cjs");
93
98
  const useComposerAttachment = require("./composer/useComposerAttachment.cjs");
94
99
  const useFileUpload = require("./composer/useFileUpload.cjs");
@@ -300,7 +305,15 @@ exports.useRestConversationHistory = useRestConversationHistory.useRestConversat
300
305
  exports.conversationSummaryFromIndexEntry = useStatewireConversationHistory.conversationSummaryFromIndexEntry;
301
306
  exports.useStatewireConversationHistory = useStatewireConversationHistory.useStatewireConversationHistory;
302
307
  exports.useAthenaThreadManager = useAthenaThreadManager.useAthenaThreadManager;
308
+ exports.useDictation = useDictation.useDictation;
309
+ exports.DictationStrip = DictationStrip.DictationStrip;
310
+ exports.DictationWaveform = DictationStrip.DictationWaveform;
311
+ exports.DICTATION_ENGINE_ERROR_KINDS = dictationEngine.DICTATION_ENGINE_ERROR_KINDS;
312
+ exports.createWebSpeechDictationEngine = dictationEngine.createWebSpeechDictationEngine;
313
+ exports.DICTATION_STATUSES = dictationState.DICTATION_STATUSES;
314
+ exports.composeDictationDraft = dictationState.composeDictationDraft;
303
315
  exports.useAppendToComposer = useAppendToComposer.useAppendToComposer;
316
+ exports.useComposerReady = composerHandoff.useComposerReady;
304
317
  exports.useSendMessage = useSendMessage.useSendMessage;
305
318
  exports.useComposerAttachment = useComposerAttachment.useComposerAttachment;
306
319
  exports.useFileUpload = useFileUpload.useFileUpload;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -114,7 +114,18 @@ export { conversationDisplayTitle, useRestConversationHistory, type RestConversa
114
114
  export { conversationSummaryFromIndexEntry, useStatewireConversationHistory, type UseStatewireConversationHistoryOptions, } from './threads/use-statewire-conversation-history';
115
115
  export { useAthenaThreadManager } from './threads/useAthenaThreadManager';
116
116
  export type { AthenaThreadManagerState } from './threads/useAthenaThreadManager';
117
+ export { useDictation } from './dictation/use-dictation';
118
+ export type { DictationController, UseDictationOptions } from './dictation/use-dictation';
119
+ export { DictationStrip, DictationWaveform } from './dictation/DictationStrip';
120
+ export type { DictationStripProps } from './dictation/DictationStrip';
121
+ export { createWebSpeechDictationEngine, DICTATION_ENGINE_ERROR_KINDS, } from './dictation/dictation-engine';
122
+ export type { DictationEngine, DictationEngineCallbacks, DictationEngineError, DictationEngineErrorKind, DictationEngineSession, DictationTranscriptUpdate, } from './dictation/dictation-engine';
123
+ export { composeDictationDraft, DICTATION_STATUSES } from './dictation/dictation-state';
124
+ export type { DictationStatus, DictationTranscript } from './dictation/dictation-state';
125
+ export type { AthenaChatComposerSlots, AthenaChatDictationOptions } from './chat/AthenaChat';
117
126
  export { useAppendToComposer } from './composer/useAppendToComposer';
127
+ export type { AppendToComposerOptions } from './composer/useAppendToComposer';
128
+ export { useComposerReady } from './composer/composer-handoff';
118
129
  export { useSendMessage } from './composer/useSendMessage';
119
130
  export type { SendMessageOptions } from './composer/useSendMessage';
120
131
  export { useComposerAttachment } from './composer/useComposerAttachment';
package/dist/index.js CHANGED
@@ -86,7 +86,12 @@ import { THREAD_PREFETCH_HOVER_DELAY_MS, THREAD_PREFETCH_MAX_CONCURRENT } from "
86
86
  import { conversationDisplayTitle, useRestConversationHistory } from "./threads/use-rest-conversation-history.js";
87
87
  import { conversationSummaryFromIndexEntry, useStatewireConversationHistory } from "./threads/use-statewire-conversation-history.js";
88
88
  import { useAthenaThreadManager } from "./threads/useAthenaThreadManager.js";
89
+ import { useDictation } from "./dictation/use-dictation.js";
90
+ import { DictationStrip, DictationWaveform } from "./dictation/DictationStrip.js";
91
+ import { DICTATION_ENGINE_ERROR_KINDS, createWebSpeechDictationEngine } from "./dictation/dictation-engine.js";
92
+ import { DICTATION_STATUSES, composeDictationDraft } from "./dictation/dictation-state.js";
89
93
  import { useAppendToComposer } from "./composer/useAppendToComposer.js";
94
+ import { useComposerReady } from "./composer/composer-handoff.js";
90
95
  import { useSendMessage } from "./composer/useSendMessage.js";
91
96
  import { useComposerAttachment } from "./composer/useComposerAttachment.js";
92
97
  import { useFileUpload } from "./composer/useFileUpload.js";
@@ -142,7 +147,11 @@ export {
142
147
  DEFAULT_STATEWIRE_MODEL,
143
148
  DEFAULT_STATEWIRE_SYNC_URL,
144
149
  DEFAULT_TOOLKIT_THEME,
150
+ DICTATION_ENGINE_ERROR_KINDS,
151
+ DICTATION_STATUSES,
145
152
  DescribeDatabaseToolUI,
153
+ DictationStrip,
154
+ DictationWaveform,
146
155
  EditFileToolUI,
147
156
  EmailSearchToolUI,
148
157
  ExpandableSection,
@@ -223,6 +232,7 @@ export {
223
232
  clearAutoOpenedAssets,
224
233
  cn,
225
234
  collectStatewireClientTools,
235
+ composeDictationDraft,
226
236
  conversationDisplayTitle,
227
237
  conversationSummaryFromIndexEntry,
228
238
  countActivityTools,
@@ -230,6 +240,7 @@ export {
230
240
  createAthenaSpacesUrl,
231
241
  createThread,
232
242
  createToolBasedGroupingFunction,
243
+ createWebSpeechDictationEngine,
233
244
  defaultGetToolGroupKey,
234
245
  deriveAthenaApiUrl,
235
246
  deriveAthenaAppUrl,
@@ -315,6 +326,8 @@ export {
315
326
  useAthenaThreadId,
316
327
  useAthenaThreadManager,
317
328
  useComposerAttachment,
329
+ useComposerReady,
330
+ useDictation,
318
331
  useFileUpload,
319
332
  useLatestTodoPlan,
320
333
  useNestedPtcComponents,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const version = "0.23.2";
3
+ const version = "0.23.3";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.23.2";
1
+ const version = "0.23.3";
2
2
  const packageJson = {
3
3
  version
4
4
  };