@ephia/dova-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/README.md +89 -0
  2. package/dist/EphiaBinding-BvRmlqqC.d.ts +36 -0
  3. package/dist/EphiaFloatingButton-CxiF86VW.d.ts +65 -0
  4. package/dist/EphiaTextarea-B4_CAVUg.d.ts +183 -0
  5. package/dist/NativeBinding-ChG0GeSz.d.ts +53 -0
  6. package/dist/TargetBinding-BKGQwUMc.d.ts +89 -0
  7. package/dist/TiptapBinding-B-agfV2H.d.ts +45 -0
  8. package/dist/Transport-zdeA4Pou.d.ts +63 -0
  9. package/dist/audio-state-kZ3KSvux.d.ts +39 -0
  10. package/dist/chunk-35AJK2IO.js +1 -0
  11. package/dist/chunk-35AJK2IO.js.map +1 -0
  12. package/dist/chunk-3LXZODL4.js +886 -0
  13. package/dist/chunk-3LXZODL4.js.map +1 -0
  14. package/dist/chunk-5IK5TLSK.js +67 -0
  15. package/dist/chunk-5IK5TLSK.js.map +1 -0
  16. package/dist/chunk-7E43RY75.js +9 -0
  17. package/dist/chunk-7E43RY75.js.map +1 -0
  18. package/dist/chunk-A5UEXJ5R.js +183 -0
  19. package/dist/chunk-A5UEXJ5R.js.map +1 -0
  20. package/dist/chunk-AEE554FT.js +51 -0
  21. package/dist/chunk-AEE554FT.js.map +1 -0
  22. package/dist/chunk-DIEWY3IT.js +1332 -0
  23. package/dist/chunk-DIEWY3IT.js.map +1 -0
  24. package/dist/chunk-EGIAN7FH.js +18 -0
  25. package/dist/chunk-EGIAN7FH.js.map +1 -0
  26. package/dist/chunk-EMOEAPVU.js +486 -0
  27. package/dist/chunk-EMOEAPVU.js.map +1 -0
  28. package/dist/chunk-IDC7FHIZ.js +40 -0
  29. package/dist/chunk-IDC7FHIZ.js.map +1 -0
  30. package/dist/chunk-ITJFN3VM.js +601 -0
  31. package/dist/chunk-ITJFN3VM.js.map +1 -0
  32. package/dist/chunk-K24GNU27.js +22 -0
  33. package/dist/chunk-K24GNU27.js.map +1 -0
  34. package/dist/chunk-LXMCRXXF.js +778 -0
  35. package/dist/chunk-LXMCRXXF.js.map +1 -0
  36. package/dist/chunk-MJCEOOLW.js +122 -0
  37. package/dist/chunk-MJCEOOLW.js.map +1 -0
  38. package/dist/chunk-N7U5M3VZ.js +33 -0
  39. package/dist/chunk-N7U5M3VZ.js.map +1 -0
  40. package/dist/chunk-PSYX674B.js +27 -0
  41. package/dist/chunk-PSYX674B.js.map +1 -0
  42. package/dist/chunk-RFQRV7ML.js +33 -0
  43. package/dist/chunk-RFQRV7ML.js.map +1 -0
  44. package/dist/chunk-THNHRV2B.js +18 -0
  45. package/dist/chunk-THNHRV2B.js.map +1 -0
  46. package/dist/chunk-VSLGR64U.js +62 -0
  47. package/dist/chunk-VSLGR64U.js.map +1 -0
  48. package/dist/chunk-W2ZP674X.js +346 -0
  49. package/dist/chunk-W2ZP674X.js.map +1 -0
  50. package/dist/chunk-YWZUMUYE.js +695 -0
  51. package/dist/chunk-YWZUMUYE.js.map +1 -0
  52. package/dist/client-options-Uo6jXO8k.d.ts +64 -0
  53. package/dist/connection-state-Bk33YprE.d.ts +32 -0
  54. package/dist/core/bindings/index.d.ts +24 -0
  55. package/dist/core/bindings/index.js +1025 -0
  56. package/dist/core/bindings/index.js.map +1 -0
  57. package/dist/core/index.d.ts +383 -0
  58. package/dist/core/index.js +1284 -0
  59. package/dist/core/index.js.map +1 -0
  60. package/dist/createEphiaClient-BhdZ183V.d.ts +69 -0
  61. package/dist/devices/speechmike/index.d.ts +148 -0
  62. package/dist/devices/speechmike/index.js +40 -0
  63. package/dist/devices/speechmike/index.js.map +1 -0
  64. package/dist/headless/index.d.ts +10 -0
  65. package/dist/headless/index.js +25 -0
  66. package/dist/headless/index.js.map +1 -0
  67. package/dist/index.d.ts +18 -0
  68. package/dist/index.js +119 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/react/index.d.ts +38 -0
  71. package/dist/react/index.js +70 -0
  72. package/dist/react/index.js.map +1 -0
  73. package/dist/rich-editor/index.d.ts +46 -0
  74. package/dist/rich-editor/index.js +13 -0
  75. package/dist/rich-editor/index.js.map +1 -0
  76. package/dist/schema-B2ycPlNB.d.ts +87 -0
  77. package/dist/session-APaXR48R.d.ts +12 -0
  78. package/dist/shared/index.d.ts +16 -0
  79. package/dist/shared/index.js +30 -0
  80. package/dist/shared/index.js.map +1 -0
  81. package/dist/style.css +1093 -0
  82. package/dist/testing/index.d.ts +84 -0
  83. package/dist/testing/index.js +36 -0
  84. package/dist/testing/index.js.map +1 -0
  85. package/dist/types-D5SXPSwR.d.ts +32 -0
  86. package/dist/ui/index.d.ts +30 -0
  87. package/dist/ui/index.js +34 -0
  88. package/dist/ui/index.js.map +1 -0
  89. package/dist/useEphiaSpeechMike-CjD7DWnh.d.ts +64 -0
  90. package/package.json +110 -0
  91. package/src/core/audio/audio-worklet-source.ts +30 -0
  92. package/src/core/audio/index.ts +3 -0
  93. package/src/core/audio/voice-level-meter.test.ts +27 -0
  94. package/src/core/audio/voice-level-meter.ts +270 -0
  95. package/src/core/bindings/EphiaBinding.ts +41 -0
  96. package/src/core/bindings/SegmentBindingBridge.test.ts +422 -0
  97. package/src/core/bindings/SegmentBindingBridge.ts +377 -0
  98. package/src/core/bindings/TargetBinding.ts +142 -0
  99. package/src/core/bindings/adapters/NativeAdapter.test.ts +85 -0
  100. package/src/core/bindings/adapters/NativeAdapter.ts +216 -0
  101. package/src/core/bindings/adapters/ProseMirrorAdapter.ts +231 -0
  102. package/src/core/bindings/adapters/index.ts +2 -0
  103. package/src/core/bindings/binding-factory.ts +78 -0
  104. package/src/core/bindings/detect-editor-type.ts +87 -0
  105. package/src/core/bindings/index.ts +13 -0
  106. package/src/core/bindings/insertion-boundary.test.ts +38 -0
  107. package/src/core/bindings/insertion-boundary.ts +26 -0
  108. package/src/core/bindings/native/NativeBinding.test.ts +277 -0
  109. package/src/core/bindings/native/NativeBinding.ts +239 -0
  110. package/src/core/bindings/resolver.ts +18 -0
  111. package/src/core/bindings/targets/codemirror.binding.ts +293 -0
  112. package/src/core/bindings/targets/contenteditable.binding.ts +452 -0
  113. package/src/core/bindings/targets/index.ts +10 -0
  114. package/src/core/bindings/targets/monaco.binding.ts +315 -0
  115. package/src/core/bindings/targets/tiptap.binding.test.ts +417 -0
  116. package/src/core/bindings/targets/tiptap.binding.ts +1192 -0
  117. package/src/core/bindings/tiptap/TiptapBinding.test.ts +63 -0
  118. package/src/core/bindings/tiptap/TiptapBinding.ts +464 -0
  119. package/src/core/bindings/types.ts +41 -0
  120. package/src/core/client/EphiaAudioClient.ts +654 -0
  121. package/src/core/client/audio-capture.ts +263 -0
  122. package/src/core/client/client-options.ts +39 -0
  123. package/src/core/client/client-state.ts +18 -0
  124. package/src/core/client/constants.ts +23 -0
  125. package/src/core/client/session-api.ts +415 -0
  126. package/src/core/connection/connection-state.ts +78 -0
  127. package/src/core/connection/index.ts +6 -0
  128. package/src/core/index.ts +47 -0
  129. package/src/core/operations/textToDocumentOperations.test.ts +69 -0
  130. package/src/core/operations/textToDocumentOperations.ts +92 -0
  131. package/src/core/runtime/DictationRuntime.test.ts +578 -0
  132. package/src/core/runtime/DictationRuntime.ts +434 -0
  133. package/src/core/runtime/TranscriptApplier.test.ts +355 -0
  134. package/src/core/runtime/TranscriptApplier.ts +229 -0
  135. package/src/core/runtime/index.ts +18 -0
  136. package/src/core/session/index.ts +2 -0
  137. package/src/core/session/session-machine.test.ts +16 -0
  138. package/src/core/session/session-machine.ts +59 -0
  139. package/src/core/targets/EditorContextCollector.ts +71 -0
  140. package/src/core/targets/TargetManager.test.ts +194 -0
  141. package/src/core/targets/TargetManager.ts +194 -0
  142. package/src/core/targets/index.ts +10 -0
  143. package/src/core/text-processing/index.ts +11 -0
  144. package/src/core/text-processing/overlap.test.ts +35 -0
  145. package/src/core/text-processing/overlap.ts +101 -0
  146. package/src/core/text-processing/voice-formatting.normalizer.test.ts +132 -0
  147. package/src/core/text-processing/voice-formatting.normalizer.ts +284 -0
  148. package/src/core/transcript/client-transcript.reducer.ts +366 -0
  149. package/src/core/transcript/client-transcript.state.ts +25 -0
  150. package/src/core/transcript/index.ts +19 -0
  151. package/src/core/transcript/transcript.assembler.test.ts +205 -0
  152. package/src/core/transcript/transcript.assembler.ts +152 -0
  153. package/src/core/transcript/transcript.reducer.test.ts +199 -0
  154. package/src/core/transcript/transcript.reducer.ts +771 -0
  155. package/src/core/transcript/transcript.state.ts +123 -0
  156. package/src/core/transport/LiveKitTransport.publish.test.ts +226 -0
  157. package/src/core/transport/LiveKitTransport.ts +459 -0
  158. package/src/core/transport/MockTransport.ts +231 -0
  159. package/src/core/transport/Transport.ts +82 -0
  160. package/src/debug/sdk-debug-collector.ts +79 -0
  161. package/src/devices/index.ts +2 -0
  162. package/src/devices/speechmike/__tests__/EphiaSpeechMikeProvider.test.tsx +99 -0
  163. package/src/devices/speechmike/__tests__/speechmike-audio-resolver.test.ts +96 -0
  164. package/src/devices/speechmike/__tests__/speechmike-button-router.test.ts +66 -0
  165. package/src/devices/speechmike/__tests__/speechmike-device-manager.test.ts +201 -0
  166. package/src/devices/speechmike/__tests__/speechmike-led-controller.test.ts +68 -0
  167. package/src/devices/speechmike/browser.ts +80 -0
  168. package/src/devices/speechmike/constants.ts +74 -0
  169. package/src/devices/speechmike/dictation-support-loader.ts +81 -0
  170. package/src/devices/speechmike/index.ts +11 -0
  171. package/src/devices/speechmike/react/EphiaSpeechMikeContext.ts +34 -0
  172. package/src/devices/speechmike/react/EphiaSpeechMikeProvider.tsx +287 -0
  173. package/src/devices/speechmike/react/useEphiaSpeechMike.ts +26 -0
  174. package/src/devices/speechmike/speechmike-audio-resolver.ts +58 -0
  175. package/src/devices/speechmike/speechmike-button-router.ts +73 -0
  176. package/src/devices/speechmike/speechmike-device-manager.ts +461 -0
  177. package/src/devices/speechmike/speechmike-led-controller.ts +78 -0
  178. package/src/devices/speechmike/types.ts +96 -0
  179. package/src/dictation_support.d.ts +31 -0
  180. package/src/global.d.ts +10 -0
  181. package/src/headless/createEphiaClient.ts +220 -0
  182. package/src/headless/index.ts +18 -0
  183. package/src/index.ts +89 -0
  184. package/src/react/EphiaAuto.tsx +87 -0
  185. package/src/react/components/EphiaDictationButton.tsx +88 -0
  186. package/src/react/components/EphiaStatusBar.tsx +59 -0
  187. package/src/react/components/EphiaTextarea.tsx +295 -0
  188. package/src/react/ephia-react.css +318 -0
  189. package/src/react/hooks/targets/index.ts +3 -0
  190. package/src/react/hooks/targets/useEphiaCodemirror.ts +35 -0
  191. package/src/react/hooks/targets/useEphiaMonaco.ts +35 -0
  192. package/src/react/hooks/targets/useEphiaTiptap.ts +23 -0
  193. package/src/react/hooks/useEphia.lifecycle.test.tsx +389 -0
  194. package/src/react/hooks/useEphia.ts +367 -0
  195. package/src/react/hooks/useEphiaDiscardTarget.ts +53 -0
  196. package/src/react/hooks/useEphiaServerEvent.ts +33 -0
  197. package/src/react/hooks/useEphiaTarget.ts +47 -0
  198. package/src/react/hooks/useEphiaTranscript.ts +22 -0
  199. package/src/react/index.ts +58 -0
  200. package/src/react/provider/EphiaContext.ts +63 -0
  201. package/src/react/provider/EphiaInternalContext.ts +32 -0
  202. package/src/react/provider/EphiaProvider.tsx +373 -0
  203. package/src/react/registry/binding-factory.ts +7 -0
  204. package/src/react/registry/detect-editor-type.ts +2 -0
  205. package/src/react/registry/events.ts +37 -0
  206. package/src/react/registry/registries/CodeMirrorInstanceRegistry.ts +24 -0
  207. package/src/react/registry/registries/MonacoInstanceRegistry.ts +23 -0
  208. package/src/react/registry/registries/TargetRegistry.ts +327 -0
  209. package/src/react/registry/registries/TiptapInstanceRegistry.ts +43 -0
  210. package/src/react/registry/registries/index.ts +5 -0
  211. package/src/react/store/create-ephia-store.ts +36 -0
  212. package/src/react/store/types.ts +41 -0
  213. package/src/react/utils/flash-range.ts +24 -0
  214. package/src/react/utils/index.ts +1 -0
  215. package/src/rich-editor/adapters/tiptap.test.ts +86 -0
  216. package/src/rich-editor/adapters/tiptap.ts +23 -0
  217. package/src/rich-editor/index.ts +3 -0
  218. package/src/rich-editor/types.ts +24 -0
  219. package/src/rich-editor/use-ephia-rich-editor.test.tsx +202 -0
  220. package/src/rich-editor/use-ephia-rich-editor.ts +47 -0
  221. package/src/shared/config/endpoint.test.ts +45 -0
  222. package/src/shared/config/endpoint.ts +39 -0
  223. package/src/shared/config/schema.ts +32 -0
  224. package/src/shared/effective-text.ts +13 -0
  225. package/src/shared/errors/EphiaSdkError.ts +54 -0
  226. package/src/shared/errors/messages.ts +40 -0
  227. package/src/shared/index.ts +27 -0
  228. package/src/shared/state/audio-state.ts +45 -0
  229. package/src/shared/state/index.ts +2 -0
  230. package/src/shared/store/document-store.ts +32 -0
  231. package/src/shared/store/index.ts +2 -0
  232. package/src/shared/types/editors.ts +28 -0
  233. package/src/shared/types/session.ts +12 -0
  234. package/src/style.css +2 -0
  235. package/src/testing/index.tsx +60 -0
  236. package/src/ui/assets/ephia-logo.svg +4 -0
  237. package/src/ui/components/EphiaLogo.tsx +77 -0
  238. package/src/ui/index.ts +24 -0
  239. package/src/ui/primitives/Button.tsx +53 -0
  240. package/src/ui/primitives/Spinner.tsx +21 -0
  241. package/src/ui/primitives/index.ts +5 -0
  242. package/src/ui/recorder/EphiaFloatingButton.tsx +489 -0
  243. package/src/ui/recorder/MinimalProcessingBars.tsx +122 -0
  244. package/src/ui/recorder/StandardIntensityVisualizer.tsx +148 -0
  245. package/src/ui/recorder/appearance.ts +9 -0
  246. package/src/ui/recorder/index.ts +8 -0
  247. package/src/ui/theme.css +775 -0
@@ -0,0 +1,886 @@
1
+ import {
2
+ useEphiaInternal
3
+ } from "./chunk-THNHRV2B.js";
4
+
5
+ // src/devices/speechmike/constants.ts
6
+ var SPEECHMIKE_VENDOR_IDS = {
7
+ PHILIPS: 2321
8
+ };
9
+ var SPEECHMIKE_CONFIG = {
10
+ BUTTON_DEBOUNCE_MS: 150,
11
+ WATCHDOG_INTERVAL_MS: 1e4,
12
+ DEVICE_OPERATION_TIMEOUT_MS: 2e3,
13
+ REQUEST_DEVICE_TIMEOUT_MS: 15e3,
14
+ LED_OPERATION_TIMEOUT_MS: 500
15
+ };
16
+ var LED_MODE = {
17
+ OFF: 0,
18
+ ON: 1,
19
+ ON_BRIGHT: 3
20
+ };
21
+ var DEFAULT_BUTTON_MAPPING = {
22
+ RECORD: "record.toggle",
23
+ PLAY: "play.toggle",
24
+ FORWARD: "seek.forward",
25
+ REWIND: "seek.backward",
26
+ F1: "audio.discard",
27
+ F1_A: "audio.discard",
28
+ F2: "audio.discard",
29
+ F2_B: "audio.discard",
30
+ F3: "audio.submit",
31
+ F3_C: "audio.submit",
32
+ F4: "audio.submit",
33
+ F4_D: "audio.submit",
34
+ INS: "agent.open",
35
+ OVR: "exam.new",
36
+ INS_OVR: "exam.new",
37
+ EOL: "resources.toggle",
38
+ EOL_PRIO: "resources.toggle"
39
+ };
40
+ var ALL_LEDS_OFF = {
41
+ 0: 0,
42
+ 1: 0,
43
+ 2: 0,
44
+ 3: 0,
45
+ 4: 0,
46
+ 5: 0,
47
+ 6: 0,
48
+ 7: 0,
49
+ 8: 0,
50
+ 9: 0
51
+ };
52
+ var DEFAULT_LED_INTENTS = {
53
+ off: { ...ALL_LEDS_OFF },
54
+ idle: { ...ALL_LEDS_OFF, 2: LED_MODE.ON_BRIGHT },
55
+ recording: { ...ALL_LEDS_OFF, 1: LED_MODE.ON_BRIGHT },
56
+ processing: { ...ALL_LEDS_OFF, 2: LED_MODE.ON, 4: LED_MODE.ON },
57
+ audio_available: {
58
+ ...ALL_LEDS_OFF,
59
+ 6: LED_MODE.ON_BRIGHT,
60
+ 7: LED_MODE.ON_BRIGHT,
61
+ 8: LED_MODE.ON_BRIGHT,
62
+ 9: LED_MODE.ON_BRIGHT
63
+ },
64
+ success: { ...ALL_LEDS_OFF, 2: 2 },
65
+ error: { ...ALL_LEDS_OFF, 3: 2 },
66
+ cancelled: { ...ALL_LEDS_OFF, 3: LED_MODE.ON_BRIGHT }
67
+ };
68
+
69
+ // src/devices/speechmike/dictation-support-loader.ts
70
+ var runtimePromise = null;
71
+ function resetDictationSupportLoaderForTests() {
72
+ runtimePromise = null;
73
+ }
74
+ function loadDictationSupport() {
75
+ if (runtimePromise) return runtimePromise;
76
+ const specifier = "dictation_support";
77
+ runtimePromise = import(
78
+ /* @vite-ignore */
79
+ specifier
80
+ ).then((mod) => {
81
+ if (!mod.DictationDeviceManager || !mod.ButtonEvent) {
82
+ throw new Error("dictation_support exports are incomplete");
83
+ }
84
+ return {
85
+ DictationDeviceManager: mod.DictationDeviceManager,
86
+ ButtonEvent: mod.ButtonEvent,
87
+ MotionEvent: mod.MotionEvent
88
+ };
89
+ }).catch((error) => {
90
+ runtimePromise = null;
91
+ throw Object.assign(new Error("dictation_support unavailable"), {
92
+ code: "speechmike.driver_missing",
93
+ cause: error
94
+ });
95
+ });
96
+ return runtimePromise;
97
+ }
98
+
99
+ // src/devices/speechmike/speechmike-audio-resolver.ts
100
+ function scoreSpeechMikeAudioDevice(device) {
101
+ if (device.kind !== "audioinput") return 0;
102
+ const label = (device.label ?? "").toLowerCase();
103
+ let score = 0;
104
+ if (label.includes("speechmike")) score += 100;
105
+ if (label.includes("philips")) score += 60;
106
+ if (label.includes("dictation")) score += 40;
107
+ if (label.includes("microphone")) score += 10;
108
+ return score;
109
+ }
110
+ async function listAudioInputDevices() {
111
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) {
112
+ return [];
113
+ }
114
+ const devices = await navigator.mediaDevices.enumerateDevices();
115
+ return devices.filter((device) => device.kind === "audioinput");
116
+ }
117
+ async function resolveSpeechMikeAudioInput(options) {
118
+ if (typeof navigator === "undefined" || !navigator.mediaDevices) return null;
119
+ let permissionStream = null;
120
+ if (options?.requestPermission) {
121
+ permissionStream = await navigator.mediaDevices.getUserMedia({ audio: true });
122
+ }
123
+ try {
124
+ const inputs = await listAudioInputDevices();
125
+ const matches = inputs.map((device) => ({
126
+ deviceId: device.deviceId,
127
+ label: device.label,
128
+ score: scoreSpeechMikeAudioDevice(device),
129
+ raw: device
130
+ })).filter((match) => match.score > 0).sort((a, b) => b.score - a.score);
131
+ return matches[0] ?? null;
132
+ } finally {
133
+ permissionStream?.getTracks().forEach((track) => track.stop());
134
+ }
135
+ }
136
+
137
+ // src/devices/speechmike/speechmike-button-router.ts
138
+ var SpeechMikeButtonRouter = class {
139
+ constructor(buttonEnum, mapping = {}, debounceMs = SPEECHMIKE_CONFIG.BUTTON_DEBOUNCE_MS) {
140
+ this.buttonEnum = buttonEnum;
141
+ this.debounceMs = debounceMs;
142
+ this.mapping = mergeButtonMapping(mapping);
143
+ }
144
+ buttonEnum;
145
+ debounceMs;
146
+ lastEventTime = 0;
147
+ processedEvents = /* @__PURE__ */ new Set();
148
+ mapping;
149
+ setMapping(mapping) {
150
+ this.mapping = mergeButtonMapping(mapping);
151
+ }
152
+ parse(bitMask) {
153
+ const now = Date.now();
154
+ const buttons = /* @__PURE__ */ new Set();
155
+ for (const value of Object.values(this.buttonEnum)) {
156
+ const numeric = Number(value);
157
+ if (Number.isNaN(numeric)) continue;
158
+ if (bitMask & numeric) {
159
+ const name = this.buttonEnum[numeric];
160
+ if (name) buttons.add(name);
161
+ }
162
+ }
163
+ if (buttons.size === 0) {
164
+ return { buttons, actions: [], rawBitMask: bitMask };
165
+ }
166
+ if (now - this.lastEventTime < this.debounceMs) return null;
167
+ const eventKey = `${[...buttons].join(",")}_${Math.floor(now / this.debounceMs)}`;
168
+ if (this.processedEvents.has(eventKey)) return null;
169
+ this.processedEvents.add(eventKey);
170
+ if (typeof window !== "undefined") {
171
+ window.setTimeout(() => this.processedEvents.delete(eventKey), 1e3);
172
+ }
173
+ this.lastEventTime = now;
174
+ const actions = [...buttons].map((button) => this.mapping[button] ?? "none").filter((action) => action !== "none");
175
+ return { buttons, actions, rawBitMask: bitMask };
176
+ }
177
+ };
178
+ function mergeButtonMapping(mapping) {
179
+ const merged = { ...DEFAULT_BUTTON_MAPPING };
180
+ for (const [button, action] of Object.entries(mapping)) {
181
+ if (action !== void 0) merged[button] = action;
182
+ }
183
+ return merged;
184
+ }
185
+
186
+ // src/devices/speechmike/browser.ts
187
+ function isBrowser() {
188
+ return typeof window !== "undefined" && typeof navigator !== "undefined";
189
+ }
190
+ function getNavigatorHid() {
191
+ if (!isBrowser()) return null;
192
+ return navigator.hid ?? null;
193
+ }
194
+ function createTimeoutError(operation, timeoutMs) {
195
+ return new Error(`${operation} timeout after ${timeoutMs}ms`);
196
+ }
197
+ function withTimeout(promise, timeoutMs, operation) {
198
+ if (typeof window === "undefined") return promise;
199
+ let timeoutId = null;
200
+ return Promise.race([
201
+ promise.finally(() => {
202
+ if (timeoutId !== null) window.clearTimeout(timeoutId);
203
+ }),
204
+ new Promise((_, reject) => {
205
+ timeoutId = window.setTimeout(() => {
206
+ reject(createTimeoutError(operation, timeoutMs));
207
+ }, timeoutMs);
208
+ })
209
+ ]);
210
+ }
211
+ function matchesSpeechMikeIdentity(device) {
212
+ const productName = device.productName?.toLowerCase() ?? "";
213
+ const manufacturerName = device.manufacturerName?.toLowerCase() ?? "";
214
+ return productName.includes("speechmike") || productName.includes("philips") || manufacturerName.includes("philips");
215
+ }
216
+
217
+ // src/devices/speechmike/speechmike-led-controller.ts
218
+ function isLedCapableDevice(device) {
219
+ if (!device || typeof device !== "object") return false;
220
+ return "setLed" in device || "setLeds" in device;
221
+ }
222
+ var SpeechMikeLedController = class {
223
+ constructor(getDevice) {
224
+ this.getDevice = getDevice;
225
+ }
226
+ getDevice;
227
+ queue = Promise.resolve();
228
+ setIntent(intent) {
229
+ this.setBatchQueued(DEFAULT_LED_INTENTS[intent]);
230
+ }
231
+ setBatchQueued(states) {
232
+ this.queue = this.queue.catch(() => void 0).then(() => this.setBatch(states)).catch(() => void 0);
233
+ }
234
+ async setBatch(states) {
235
+ const device = this.getLedDevice();
236
+ if (!device) return;
237
+ if (typeof device.setLeds === "function") {
238
+ try {
239
+ await withTimeout(
240
+ device.setLeds(states),
241
+ SPEECHMIKE_CONFIG.LED_OPERATION_TIMEOUT_MS,
242
+ "setLeds"
243
+ );
244
+ return;
245
+ } catch {
246
+ }
247
+ }
248
+ await Promise.allSettled(
249
+ Object.entries(states).map(([index, mode]) => this.setLed(Number(index), mode))
250
+ );
251
+ }
252
+ async setLed(index, mode) {
253
+ const device = this.getLedDevice();
254
+ if (!device?.setLed) return false;
255
+ try {
256
+ await withTimeout(
257
+ device.setLed(index, mode),
258
+ SPEECHMIKE_CONFIG.LED_OPERATION_TIMEOUT_MS,
259
+ `setLed(${index})`
260
+ );
261
+ return true;
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+ clear() {
267
+ this.setIntent("off");
268
+ }
269
+ getLedDevice() {
270
+ const device = this.getDevice();
271
+ return isLedCapableDevice(device) ? device : null;
272
+ }
273
+ };
274
+
275
+ // src/devices/speechmike/speechmike-device-manager.ts
276
+ function toError(error, fallbackCode) {
277
+ if (error && typeof error === "object" && "code" in error) {
278
+ const code = String(error.code ?? fallbackCode);
279
+ const message = error instanceof Error ? error.message : String(error);
280
+ return { code, message };
281
+ }
282
+ return {
283
+ code: fallbackCode,
284
+ message: error instanceof Error ? error.message : String(error)
285
+ };
286
+ }
287
+ function toRecord(value) {
288
+ return value && typeof value === "object" ? value : null;
289
+ }
290
+ function getDeviceHidDevice(device) {
291
+ const maybeDevice = toRecord(device);
292
+ return maybeDevice?.hidDevice ?? null;
293
+ }
294
+ function sameHidDevice(a, b) {
295
+ if (!a || !b) return false;
296
+ return a === b;
297
+ }
298
+ function extractDeviceInfo(value) {
299
+ const record = toRecord(value);
300
+ if (!record) return null;
301
+ const productName = typeof record.productName === "string" ? record.productName : void 0;
302
+ const manufacturerName = typeof record.manufacturerName === "string" ? record.manufacturerName : void 0;
303
+ const vendorId = typeof record.vendorId === "number" ? record.vendorId : void 0;
304
+ const productId = typeof record.productId === "number" ? record.productId : void 0;
305
+ if (productName === void 0 && manufacturerName === void 0 && vendorId === void 0 && productId === void 0) {
306
+ return null;
307
+ }
308
+ return { productName, manufacturerName, vendorId, productId };
309
+ }
310
+ var SpeechMikeDeviceManager = class {
311
+ listeners = {
312
+ status: /* @__PURE__ */ new Set(),
313
+ device: /* @__PURE__ */ new Set(),
314
+ deviceInfo: /* @__PURE__ */ new Set(),
315
+ presence: /* @__PURE__ */ new Set(),
316
+ button: /* @__PURE__ */ new Set(),
317
+ error: /* @__PURE__ */ new Set()
318
+ };
319
+ loadRuntime;
320
+ initialButtonMapping;
321
+ manager = null;
322
+ router = null;
323
+ currentDevice = null;
324
+ currentDeviceInfo = null;
325
+ status = "idle";
326
+ physicallyPresent = false;
327
+ initialized = false;
328
+ disposed = false;
329
+ reconnectLock = false;
330
+ watchdogInterval = null;
331
+ onButtonEvent = (_device, bitMask) => {
332
+ const event = this.router?.parse(bitMask);
333
+ if (event) this.emit("button", event);
334
+ };
335
+ onDeviceConnected = (device) => {
336
+ void this.attachDevice(device, "sdk-connect");
337
+ };
338
+ onDeviceDisconnected = () => {
339
+ this.detachDevice("sdk-disconnect");
340
+ };
341
+ onMotionEvent = () => {
342
+ };
343
+ onHidConnect = (event) => {
344
+ void this.handleHidConnect(event);
345
+ };
346
+ onHidDisconnect = (event) => {
347
+ void this.handleHidDisconnect(event);
348
+ };
349
+ constructor(options = {}) {
350
+ this.loadRuntime = options.loadRuntime ?? loadDictationSupport;
351
+ this.initialButtonMapping = options.buttonMapping ?? {};
352
+ }
353
+ async initialize() {
354
+ if (this.disposed) return;
355
+ if (this.initialized) return;
356
+ this.initialized = true;
357
+ const hid = getNavigatorHid();
358
+ if (!hid) {
359
+ this.setStatus("unsupported");
360
+ return;
361
+ }
362
+ this.setStatus("initializing");
363
+ try {
364
+ const runtime = await this.loadRuntime();
365
+ if (this.disposed) return;
366
+ this.router = new SpeechMikeButtonRouter(runtime.ButtonEvent, this.initialButtonMapping);
367
+ const manager = new runtime.DictationDeviceManager();
368
+ this.manager = manager;
369
+ manager.addButtonEventListener?.(this.onButtonEvent);
370
+ manager.addDeviceConnectedEventListener?.(this.onDeviceConnected);
371
+ manager.addDeviceDisconnectedEventListener?.(this.onDeviceDisconnected);
372
+ manager.addMotionEventListener?.(this.onMotionEvent);
373
+ hid.addEventListener("connect", this.onHidConnect);
374
+ hid.addEventListener("disconnect", this.onHidDisconnect);
375
+ if (manager.init) {
376
+ await withTimeout(
377
+ manager.init(),
378
+ SPEECHMIKE_CONFIG.DEVICE_OPERATION_TIMEOUT_MS,
379
+ "speechmike.init"
380
+ );
381
+ }
382
+ await this.reconnect();
383
+ this.startWatchdog();
384
+ } catch (error) {
385
+ const err = toError(error, "speechmike.initialize_failed");
386
+ this.emit("error", err);
387
+ this.setStatus(err.code === "speechmike.driver_missing" ? "driver_missing" : "error");
388
+ }
389
+ }
390
+ async requestAuthorization() {
391
+ await this.initialize();
392
+ if (this.disposed) return;
393
+ if (!this.manager) {
394
+ const error = {
395
+ code: "speechmike.manager_unavailable",
396
+ message: "SpeechMike manager is not initialized"
397
+ };
398
+ this.emit("error", error);
399
+ throw new Error(error.message);
400
+ }
401
+ this.setStatus("authorization_required");
402
+ try {
403
+ await withTimeout(
404
+ this.manager.requestDevice(),
405
+ SPEECHMIKE_CONFIG.REQUEST_DEVICE_TIMEOUT_MS,
406
+ "speechmike.requestDevice"
407
+ );
408
+ const device = this.getManagerDevices()[0] ?? null;
409
+ if (!device) {
410
+ this.setStatus("error");
411
+ throw new Error("No SpeechMike device returned after authorization");
412
+ }
413
+ await this.attachDevice(device, "user-grant");
414
+ } catch (error) {
415
+ const err = toError(error, "speechmike.authorization_failed");
416
+ this.emit("error", err);
417
+ this.setStatus("present_unauthorized");
418
+ throw error;
419
+ }
420
+ }
421
+ async reconnect() {
422
+ if (this.reconnectLock || this.disposed) return;
423
+ this.reconnectLock = true;
424
+ this.setStatus(this.currentDevice ? "recovering" : "restoring");
425
+ try {
426
+ const device = this.getManagerDevices()[0] ?? null;
427
+ if (device) {
428
+ await this.attachDevice(device, "reconnect");
429
+ return;
430
+ }
431
+ const present = await this.detectPhysicalPresence();
432
+ this.setPhysicallyPresent(present);
433
+ this.detachDevice(present ? "present-unauthorized" : "not-present", {
434
+ status: present ? "present_unauthorized" : "not_present"
435
+ });
436
+ } catch (error) {
437
+ const err = toError(error, "speechmike.reconnect_failed");
438
+ this.emit("error", err);
439
+ this.setStatus("error");
440
+ } finally {
441
+ this.reconnectLock = false;
442
+ }
443
+ }
444
+ dispose() {
445
+ this.disposed = true;
446
+ if (this.watchdogInterval !== null && typeof window !== "undefined") {
447
+ window.clearInterval(this.watchdogInterval);
448
+ this.watchdogInterval = null;
449
+ }
450
+ const hid = getNavigatorHid();
451
+ hid?.removeEventListener("connect", this.onHidConnect);
452
+ hid?.removeEventListener("disconnect", this.onHidDisconnect);
453
+ this.manager?.removeButtonEventListener?.(this.onButtonEvent);
454
+ this.manager?.removeDeviceConnectedEventListener?.(this.onDeviceConnected);
455
+ this.manager?.removeDeviceDisconnectedEventListener?.(this.onDeviceDisconnected);
456
+ this.manager?.removeMotionEventListener?.(this.onMotionEvent);
457
+ this.manager?.disconnect?.();
458
+ this.currentDevice = null;
459
+ this.currentDeviceInfo = null;
460
+ this.manager = null;
461
+ this.router = null;
462
+ this.initialized = false;
463
+ this.emit("device", null);
464
+ this.emit("deviceInfo", null);
465
+ }
466
+ getCurrentDevice() {
467
+ return this.currentDevice;
468
+ }
469
+ getStatus() {
470
+ return this.status;
471
+ }
472
+ getPhysicallyPresent() {
473
+ return this.physicallyPresent;
474
+ }
475
+ getDeviceInfo() {
476
+ return this.currentDeviceInfo;
477
+ }
478
+ setButtonMapping(mapping) {
479
+ this.router?.setMapping(mapping);
480
+ }
481
+ on(event, cb) {
482
+ this.listeners[event].add(cb);
483
+ return () => {
484
+ this.listeners[event].delete(cb);
485
+ };
486
+ }
487
+ async attachDevice(device, _cause) {
488
+ if (this.disposed) return;
489
+ this.currentDevice = device;
490
+ this.setPhysicallyPresent(true);
491
+ this.currentDeviceInfo = await this.resolveDeviceInfo(device);
492
+ this.emit("device", device);
493
+ this.emit("deviceInfo", this.currentDeviceInfo);
494
+ this.setStatus("connected");
495
+ }
496
+ detachDevice(_cause, options = {}) {
497
+ this.currentDevice = null;
498
+ this.currentDeviceInfo = null;
499
+ this.emit("device", null);
500
+ this.emit("deviceInfo", null);
501
+ this.setStatus(options.status ?? "disconnected");
502
+ }
503
+ async handleHidConnect(event) {
504
+ if (this.disposed) return;
505
+ const present = await this.detectPhysicalPresence();
506
+ this.setPhysicallyPresent(present);
507
+ if (!present) return;
508
+ const devices = this.getManagerDevices();
509
+ const connectedHidDevice = event.device;
510
+ const match = devices.find((device) => sameHidDevice(getDeviceHidDevice(device), connectedHidDevice)) ?? devices[0] ?? null;
511
+ if (match) {
512
+ await this.attachDevice(match, "hid-connect");
513
+ } else {
514
+ this.setStatus("present_unauthorized");
515
+ }
516
+ }
517
+ async handleHidDisconnect(event) {
518
+ if (this.disposed) return;
519
+ const currentHidDevice = getDeviceHidDevice(this.currentDevice);
520
+ if (sameHidDevice(currentHidDevice, event.device)) {
521
+ this.detachDevice("hid-disconnect");
522
+ }
523
+ this.setPhysicallyPresent(await this.detectPhysicalPresence());
524
+ }
525
+ startWatchdog() {
526
+ if (this.watchdogInterval !== null || typeof window === "undefined") return;
527
+ this.watchdogInterval = window.setInterval(() => {
528
+ void this.watchdogTick();
529
+ }, SPEECHMIKE_CONFIG.WATCHDOG_INTERVAL_MS);
530
+ }
531
+ async watchdogTick() {
532
+ if (this.reconnectLock || this.disposed) return;
533
+ this.reconnectLock = true;
534
+ try {
535
+ const present = await this.detectPhysicalPresence();
536
+ this.setPhysicallyPresent(present);
537
+ if (!this.currentDevice && present) {
538
+ const device = this.getManagerDevices()[0] ?? null;
539
+ if (device) {
540
+ await this.attachDevice(device, "watchdog-restore");
541
+ } else {
542
+ this.setStatus("present_unauthorized");
543
+ }
544
+ } else if (this.currentDevice && !present) {
545
+ this.detachDevice("watchdog-absent", { status: "disconnected" });
546
+ }
547
+ } finally {
548
+ this.reconnectLock = false;
549
+ }
550
+ }
551
+ async detectPhysicalPresence() {
552
+ const hid = getNavigatorHid();
553
+ if (hid) {
554
+ try {
555
+ const devices = await hid.getDevices();
556
+ if (devices.some(matchesSpeechMikeIdentity)) return true;
557
+ } catch {
558
+ }
559
+ }
560
+ try {
561
+ const devices = await navigator.mediaDevices?.enumerateDevices?.();
562
+ return !!devices?.some((device) => {
563
+ const label = (device.label ?? "").toLowerCase();
564
+ return device.kind === "audioinput" && (label.includes("speechmike") || label.includes("philips") || label.includes("dictation"));
565
+ });
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ async resolveDeviceInfo(device) {
571
+ const fromDevice = extractDeviceInfo(device);
572
+ if (fromDevice) return fromDevice;
573
+ const hidDevice = getDeviceHidDevice(device);
574
+ const fromHid = extractDeviceInfo(hidDevice);
575
+ if (fromHid) return fromHid;
576
+ try {
577
+ const info = await this.manager?.getDeviceInfo?.(device);
578
+ return extractDeviceInfo(info);
579
+ } catch {
580
+ return null;
581
+ }
582
+ }
583
+ getManagerDevices() {
584
+ const devices = this.manager?.getDevices?.() ?? [];
585
+ return Array.isArray(devices) ? devices : [];
586
+ }
587
+ setStatus(status) {
588
+ if (this.status === status) return;
589
+ this.status = status;
590
+ this.emit("status", status);
591
+ }
592
+ setPhysicallyPresent(present) {
593
+ if (this.physicallyPresent === present) return;
594
+ this.physicallyPresent = present;
595
+ this.emit("presence", present);
596
+ }
597
+ emit(event, payload) {
598
+ for (const listener of this.listeners[event]) {
599
+ listener(payload);
600
+ }
601
+ }
602
+ };
603
+
604
+ // src/devices/speechmike/react/EphiaSpeechMikeProvider.tsx
605
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
606
+
607
+ // src/devices/speechmike/react/EphiaSpeechMikeContext.ts
608
+ import { createContext } from "react";
609
+ var FALLBACK_EPHIA_SPEECHMIKE_STATE = {
610
+ status: { hid: "unsupported", audio: "idle" },
611
+ device: null,
612
+ deviceInfo: null,
613
+ physicallyPresent: false,
614
+ audioInputDeviceId: null,
615
+ audioInputDeviceLabel: null,
616
+ pressedButtons: /* @__PURE__ */ new Set(),
617
+ lastButton: null,
618
+ lastAction: null,
619
+ error: null
620
+ };
621
+ var EphiaSpeechMikeContext = createContext(null);
622
+
623
+ // src/devices/speechmike/react/EphiaSpeechMikeProvider.tsx
624
+ import { jsx } from "react/jsx-runtime";
625
+ var INITIAL_STATE = {
626
+ status: { hid: "idle", audio: "idle" },
627
+ device: null,
628
+ deviceInfo: null,
629
+ physicallyPresent: false,
630
+ audioInputDeviceId: null,
631
+ audioInputDeviceLabel: null,
632
+ pressedButtons: /* @__PURE__ */ new Set(),
633
+ lastButton: null,
634
+ lastAction: null,
635
+ error: null
636
+ };
637
+ function EphiaSpeechMikeProvider({
638
+ children,
639
+ enabled = true,
640
+ autoConnect = true,
641
+ preferAudioInput = true,
642
+ requestAudioPermissionOnInit = false,
643
+ ledSync = true,
644
+ initialLedIntent = "idle",
645
+ buttonMapping,
646
+ onAction,
647
+ onError,
648
+ onStatusChange
649
+ }) {
650
+ const internal = useEphiaInternal();
651
+ const managerRef = useRef(null);
652
+ const ledRef = useRef(null);
653
+ const onActionRef = useRef(onAction);
654
+ const onErrorRef = useRef(onError);
655
+ const onStatusChangeRef = useRef(onStatusChange);
656
+ const ledSyncRef = useRef(ledSync);
657
+ const initialLedIntentRef = useRef(initialLedIntent);
658
+ const buttonMappingRef = useRef(buttonMapping);
659
+ const [state, setState] = useState(INITIAL_STATE);
660
+ onActionRef.current = onAction;
661
+ onErrorRef.current = onError;
662
+ onStatusChangeRef.current = onStatusChange;
663
+ ledSyncRef.current = ledSync;
664
+ initialLedIntentRef.current = initialLedIntent;
665
+ buttonMappingRef.current = buttonMapping;
666
+ const patchState = useCallback((patch) => {
667
+ setState((prev) => {
668
+ const next = typeof patch === "function" ? patch(prev) : {
669
+ ...prev,
670
+ ...patch
671
+ };
672
+ onStatusChangeRef.current?.(next);
673
+ return next;
674
+ });
675
+ }, []);
676
+ const setAudioStatus = useCallback(
677
+ (audio) => {
678
+ patchState((prev) => ({
679
+ ...prev,
680
+ status: { ...prev.status, audio }
681
+ }));
682
+ },
683
+ [patchState]
684
+ );
685
+ const resolveAndApplyAudioInput = useCallback(async () => {
686
+ if (!preferAudioInput) return;
687
+ setAudioStatus("resolving");
688
+ try {
689
+ const match = await resolveSpeechMikeAudioInput({
690
+ requestPermission: requestAudioPermissionOnInit
691
+ });
692
+ if (!match) {
693
+ patchState((prev) => ({
694
+ ...prev,
695
+ status: { ...prev.status, audio: "not_found" },
696
+ audioInputDeviceId: null,
697
+ audioInputDeviceLabel: null
698
+ }));
699
+ return;
700
+ }
701
+ internal.clientRef.current?.setPreferredAudioInputDeviceId(match.deviceId);
702
+ patchState((prev) => ({
703
+ ...prev,
704
+ status: { ...prev.status, audio: "ready" },
705
+ audioInputDeviceId: match.deviceId,
706
+ audioInputDeviceLabel: match.label,
707
+ error: null
708
+ }));
709
+ } catch (error) {
710
+ const err = {
711
+ code: "speechmike.audio_resolve_failed",
712
+ message: error instanceof Error ? error.message : String(error)
713
+ };
714
+ patchState((prev) => ({
715
+ ...prev,
716
+ status: { ...prev.status, audio: "error" },
717
+ error: err
718
+ }));
719
+ onErrorRef.current?.(err);
720
+ }
721
+ }, [
722
+ internal.clientRef,
723
+ patchState,
724
+ preferAudioInput,
725
+ requestAudioPermissionOnInit,
726
+ setAudioStatus
727
+ ]);
728
+ useEffect(() => {
729
+ if (!enabled) {
730
+ patchState(INITIAL_STATE);
731
+ return;
732
+ }
733
+ const manager = new SpeechMikeDeviceManager({
734
+ buttonMapping: buttonMappingRef.current
735
+ });
736
+ const led = new SpeechMikeLedController(() => manager.getCurrentDevice());
737
+ managerRef.current = manager;
738
+ ledRef.current = led;
739
+ const unsubs = [
740
+ manager.on("status", (hidStatus) => {
741
+ patchState((prev) => ({
742
+ ...prev,
743
+ status: { ...prev.status, hid: hidStatus }
744
+ }));
745
+ }),
746
+ manager.on("presence", (physicallyPresent) => {
747
+ patchState({ physicallyPresent });
748
+ }),
749
+ manager.on("deviceInfo", (deviceInfo) => {
750
+ patchState({ deviceInfo });
751
+ }),
752
+ manager.on("device", (device) => {
753
+ patchState({ device });
754
+ if (device && ledSyncRef.current) {
755
+ led.setIntent(initialLedIntentRef.current);
756
+ }
757
+ if (device && preferAudioInput) {
758
+ void resolveAndApplyAudioInput();
759
+ }
760
+ }),
761
+ manager.on("button", ({ buttons, actions }) => {
762
+ const buttonList = [...buttons];
763
+ const lastButton = buttonList[buttonList.length - 1] ?? null;
764
+ const lastAction = actions[actions.length - 1] ?? null;
765
+ patchState({
766
+ pressedButtons: new Set(buttons),
767
+ lastButton,
768
+ lastAction
769
+ });
770
+ for (const action of actions) {
771
+ onActionRef.current?.(action);
772
+ }
773
+ }),
774
+ manager.on("error", (error) => {
775
+ patchState({ error });
776
+ onErrorRef.current?.(error);
777
+ })
778
+ ];
779
+ if (autoConnect) {
780
+ manager.initialize().catch((error) => {
781
+ const err = {
782
+ code: "speechmike.initialize_failed",
783
+ message: error instanceof Error ? error.message : String(error)
784
+ };
785
+ patchState({ error: err });
786
+ onErrorRef.current?.(err);
787
+ });
788
+ }
789
+ return () => {
790
+ unsubs.forEach((unsub) => unsub());
791
+ led.clear();
792
+ manager.dispose();
793
+ managerRef.current = null;
794
+ ledRef.current = null;
795
+ };
796
+ }, [
797
+ autoConnect,
798
+ enabled,
799
+ patchState,
800
+ preferAudioInput,
801
+ resolveAndApplyAudioInput
802
+ ]);
803
+ useEffect(() => {
804
+ managerRef.current?.setButtonMapping(buttonMapping ?? {});
805
+ }, [buttonMapping]);
806
+ useEffect(() => {
807
+ if (!enabled || !preferAudioInput) return;
808
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.addEventListener) return;
809
+ const handler = () => {
810
+ void resolveAndApplyAudioInput();
811
+ };
812
+ navigator.mediaDevices.addEventListener("devicechange", handler);
813
+ return () => {
814
+ navigator.mediaDevices.removeEventListener("devicechange", handler);
815
+ };
816
+ }, [enabled, preferAudioInput, resolveAndApplyAudioInput]);
817
+ const value = useMemo(
818
+ () => ({
819
+ ...state,
820
+ isConnected: state.status.hid === "connected",
821
+ isReady: state.status.hid === "connected" && state.status.audio === "ready",
822
+ requestAuthorization: async () => {
823
+ if (!enabled) {
824
+ throw new Error("EphiaSpeechMikeProvider is disabled");
825
+ }
826
+ await managerRef.current?.requestAuthorization();
827
+ await resolveAndApplyAudioInput();
828
+ },
829
+ reconnect: async () => {
830
+ if (!enabled) return;
831
+ await managerRef.current?.reconnect();
832
+ await resolveAndApplyAudioInput();
833
+ },
834
+ setLedIntent: (intent) => {
835
+ ledRef.current?.setIntent(intent);
836
+ },
837
+ setButtonMapping: (mapping) => {
838
+ managerRef.current?.setButtonMapping(mapping);
839
+ }
840
+ }),
841
+ [enabled, resolveAndApplyAudioInput, state]
842
+ );
843
+ return /* @__PURE__ */ jsx(EphiaSpeechMikeContext.Provider, { value, children });
844
+ }
845
+
846
+ // src/devices/speechmike/react/useEphiaSpeechMike.ts
847
+ import { useContext } from "react";
848
+ function useEphiaSpeechMike() {
849
+ const ctx = useContext(EphiaSpeechMikeContext);
850
+ if (ctx) return ctx;
851
+ return {
852
+ ...FALLBACK_EPHIA_SPEECHMIKE_STATE,
853
+ isConnected: false,
854
+ isReady: false,
855
+ requestAuthorization: async () => {
856
+ throw new Error("EphiaSpeechMikeProvider not mounted");
857
+ },
858
+ reconnect: async () => {
859
+ },
860
+ setLedIntent: () => {
861
+ },
862
+ setButtonMapping: () => {
863
+ }
864
+ };
865
+ }
866
+
867
+ export {
868
+ SPEECHMIKE_VENDOR_IDS,
869
+ SPEECHMIKE_CONFIG,
870
+ LED_MODE,
871
+ DEFAULT_BUTTON_MAPPING,
872
+ DEFAULT_LED_INTENTS,
873
+ resetDictationSupportLoaderForTests,
874
+ loadDictationSupport,
875
+ scoreSpeechMikeAudioDevice,
876
+ listAudioInputDevices,
877
+ resolveSpeechMikeAudioInput,
878
+ SpeechMikeButtonRouter,
879
+ SpeechMikeLedController,
880
+ SpeechMikeDeviceManager,
881
+ FALLBACK_EPHIA_SPEECHMIKE_STATE,
882
+ EphiaSpeechMikeContext,
883
+ EphiaSpeechMikeProvider,
884
+ useEphiaSpeechMike
885
+ };
886
+ //# sourceMappingURL=chunk-3LXZODL4.js.map