@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,778 @@
1
+ import {
2
+ EphiaSdkError
3
+ } from "./chunk-N7U5M3VZ.js";
4
+
5
+ // src/core/transport/LiveKitTransport.ts
6
+ import {
7
+ Room,
8
+ RoomEvent,
9
+ DataPacket_Kind,
10
+ LocalAudioTrack,
11
+ ConnectionState,
12
+ ConnectionQuality,
13
+ MediaDeviceFailure,
14
+ AudioPresets,
15
+ Track
16
+ } from "livekit-client";
17
+ import { ephiaWireServerEventSchema, PROTOCOL_VERSION } from "ephia-protocol";
18
+
19
+ // src/debug/sdk-debug-collector.ts
20
+ function isDev() {
21
+ if (typeof window === "undefined") return false;
22
+ try {
23
+ return process.env.NODE_ENV === "development";
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ var collector = null;
29
+ function getSdkDebugCollector() {
30
+ if (!isDev()) return null;
31
+ if (!collector) {
32
+ collector = {
33
+ events: [],
34
+ rawEvents: [],
35
+ states: {},
36
+ pushEvent(source, type, payload) {
37
+ const entry = { ts: performance.now(), source, type, payload };
38
+ this.events.push(entry);
39
+ if (this.events.length > 3e3) this.events.shift();
40
+ },
41
+ pushRawEvent(source, type, payload) {
42
+ const entry = { ts: performance.now(), source, type, payload };
43
+ this.rawEvents.push(entry);
44
+ if (this.rawEvents.length > 3e3) this.rawEvents.shift();
45
+ },
46
+ setState(source, state) {
47
+ this.states[source] = { ts: performance.now(), source, state };
48
+ }
49
+ };
50
+ window.__ephia_sdk_debug__ = collector;
51
+ }
52
+ return collector;
53
+ }
54
+ function dbgRawEvent(source, type, payload) {
55
+ getSdkDebugCollector()?.pushRawEvent(source, type, payload);
56
+ }
57
+ function dbgState(source, state) {
58
+ getSdkDebugCollector()?.setState(source, state);
59
+ }
60
+
61
+ // src/core/transport/LiveKitTransport.ts
62
+ var TOPIC_CLIENT_CONTROL = "ephia.client.control";
63
+ var TOPIC_SERVER_EVENT = "ephia.server.event";
64
+ var TRACK_MIC = "ephia-microphone";
65
+ var textDecoder = new TextDecoder();
66
+ var textEncoder = new TextEncoder();
67
+ function asString(value) {
68
+ return typeof value === "string" ? value : void 0;
69
+ }
70
+ var RECONNECT_POLICY = {
71
+ nextRetryDelayInMs(ctx) {
72
+ if (ctx.retryCount >= 10) return null;
73
+ const base = Math.min(300 * 2 ** ctx.retryCount, 3e4);
74
+ const jitter = Math.floor(Math.random() * 1e3);
75
+ return base + jitter;
76
+ }
77
+ };
78
+ function uuid() {
79
+ const c = globalThis.crypto;
80
+ if (c?.randomUUID) return c.randomUUID();
81
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
82
+ }
83
+ var LiveKitTransport = class {
84
+ room = null;
85
+ _eventCallbacks = /* @__PURE__ */ new Set();
86
+ _serverEventCallbacks = /* @__PURE__ */ new Set();
87
+ _stateCallbacks = /* @__PURE__ */ new Set();
88
+ _errorCallbacks = /* @__PURE__ */ new Set();
89
+ _state = {
90
+ status: "idle",
91
+ localAudioPublished: false,
92
+ localAudioMuted: false,
93
+ reconnectCount: 0
94
+ };
95
+ _localAudioTrack = null;
96
+ _krispProcessor = null;
97
+ _sessionId = "";
98
+ _clientSeq = 0;
99
+ async connect(params) {
100
+ if (!this.room) {
101
+ this.room = new Room(this._roomOptions());
102
+ } else if (this.room.state === ConnectionState.Connected || this.room.state === ConnectionState.Reconnecting || this.room.state === ConnectionState.Connecting) {
103
+ await this.disconnect("reconnect");
104
+ this.room = new Room(this._roomOptions());
105
+ } else {
106
+ this.room.removeAllListeners();
107
+ }
108
+ this._sessionId = params.sessionId ?? params.roomName;
109
+ this._clientSeq = 0;
110
+ this._setState({ status: "connecting", roomName: params.roomName });
111
+ this._attachRoomListeners(params);
112
+ await this.room.connect(params.livekitUrl, params.token);
113
+ if (this.room.state !== ConnectionState.Connected) {
114
+ throw new EphiaSdkError(
115
+ "transport.connect_failed",
116
+ `LiveKit room not connected: ${this.room.state}`
117
+ );
118
+ }
119
+ this._setState({
120
+ status: "connected",
121
+ roomName: this.room.name,
122
+ participantIdentity: this.room.localParticipant?.identity
123
+ });
124
+ }
125
+ async prepareConnection(url, token) {
126
+ if (!this.room) {
127
+ this.room = new Room(this._roomOptions());
128
+ }
129
+ await this.room.prepareConnection(url, token);
130
+ }
131
+ async disconnect(_reason) {
132
+ if (this.room) {
133
+ const room = this.room;
134
+ await room.disconnect();
135
+ room.removeAllListeners();
136
+ this.room = null;
137
+ }
138
+ this._localAudioTrack = null;
139
+ this._setState({
140
+ status: "disconnected",
141
+ localAudioPublished: false,
142
+ localAudioMuted: false
143
+ });
144
+ }
145
+ async sendMessage(message) {
146
+ if (!this.room) {
147
+ throw new EphiaSdkError("transport.not_connected", "Cannot send message: room is null");
148
+ }
149
+ if (this.room.state !== ConnectionState.Connected) {
150
+ throw new EphiaSdkError("transport.not_connected", `Cannot send message: room state is ${this.room.state}`);
151
+ }
152
+ const wire = this._toWireClientEvent(message);
153
+ await this.room.localParticipant.publishData(textEncoder.encode(JSON.stringify(wire)), {
154
+ reliable: true,
155
+ topic: TOPIC_CLIENT_CONTROL
156
+ });
157
+ }
158
+ onEvent(callback) {
159
+ this._eventCallbacks.add(callback);
160
+ return () => this._eventCallbacks.delete(callback);
161
+ }
162
+ onServerEvent(callback) {
163
+ this._serverEventCallbacks.add(callback);
164
+ return () => this._serverEventCallbacks.delete(callback);
165
+ }
166
+ onTransportState(callback) {
167
+ this._stateCallbacks.add(callback);
168
+ return () => this._stateCallbacks.delete(callback);
169
+ }
170
+ onError(callback) {
171
+ this._errorCallbacks.add(callback);
172
+ return () => this._errorCallbacks.delete(callback);
173
+ }
174
+ getState() {
175
+ return { ...this._state };
176
+ }
177
+ getLocalAudioPublication() {
178
+ return this._localAudioTrack;
179
+ }
180
+ async publishAudio(track, options) {
181
+ if (this._localAudioTrack) {
182
+ await this.unpublishAudio();
183
+ }
184
+ if (!this.room) {
185
+ throw new EphiaSdkError("transport.connect_failed", "Cannot publish audio: room is null");
186
+ }
187
+ if (this.room.state !== ConnectionState.Connected) {
188
+ throw new EphiaSdkError("transport.connect_failed", `Room not connected: ${this.room.state}`);
189
+ }
190
+ this._localAudioTrack = await this.room.localParticipant.publishTrack(track, {
191
+ name: TRACK_MIC,
192
+ source: Track.Source.Microphone,
193
+ audioPreset: AudioPresets.speech
194
+ });
195
+ this._setState({ localAudioMuted: false });
196
+ const tryKrisp = options?.enableNoiseFilter === true;
197
+ if (tryKrisp && this._localAudioTrack?.track instanceof LocalAudioTrack) {
198
+ try {
199
+ const { KrispNoiseFilter, isKrispNoiseFilterSupported } = await import("@livekit/krisp-noise-filter");
200
+ if (isKrispNoiseFilterSupported()) {
201
+ const processor = KrispNoiseFilter();
202
+ await this._localAudioTrack.track.setProcessor(processor);
203
+ this._krispProcessor = processor;
204
+ await track.applyConstraints({
205
+ echoCancellation: false,
206
+ noiseSuppression: false,
207
+ autoGainControl: false
208
+ });
209
+ this._setState({ krispActive: true });
210
+ }
211
+ } catch (err) {
212
+ console.warn("[LiveKitTransport] Krisp initialization failed \u2014 fallback WebRTC natif", err);
213
+ }
214
+ }
215
+ }
216
+ async unpublishAudio() {
217
+ if (this._localAudioTrack && this.room) {
218
+ if (this._krispProcessor && this._localAudioTrack.track instanceof LocalAudioTrack) {
219
+ try {
220
+ await this._localAudioTrack.track.stopProcessor();
221
+ } catch {
222
+ }
223
+ this._krispProcessor = null;
224
+ }
225
+ await this.room.localParticipant.unpublishTrack(this._localAudioTrack.track);
226
+ this._localAudioTrack = null;
227
+ this._setState({ krispActive: false, localAudioMuted: false });
228
+ }
229
+ }
230
+ async performRpc(method, payload, timeout = 3e3) {
231
+ if (!this.room) {
232
+ throw new EphiaSdkError("transport.not_connected", "Cannot perform RPC: room not available");
233
+ }
234
+ return this.room.localParticipant.performRpc({
235
+ destinationIdentity: "ephia-transcribe-agent",
236
+ method,
237
+ payload: JSON.stringify(payload),
238
+ responseTimeout: timeout
239
+ });
240
+ }
241
+ async switchActiveDevice(kind, deviceId) {
242
+ if (!this.room) {
243
+ throw new EphiaSdkError("transport.not_connected", "Cannot switch device: room not available");
244
+ }
245
+ return this.room.switchActiveDevice(kind, deviceId);
246
+ }
247
+ // ------------------------------------------------------------------
248
+ // Internals
249
+ // ------------------------------------------------------------------
250
+ _toWireClientEvent(message) {
251
+ const msgType = message.type;
252
+ return {
253
+ protocolVersion: PROTOCOL_VERSION,
254
+ type: msgType,
255
+ eventId: uuid(),
256
+ sessionId: this._sessionId,
257
+ clientSeq: ++this._clientSeq,
258
+ sentAt: (/* @__PURE__ */ new Date()).toISOString(),
259
+ payload: this._toWireClientPayload(message)
260
+ };
261
+ }
262
+ _toWireClientPayload(message) {
263
+ const msgType = message.type;
264
+ const payload = message.payload;
265
+ const raw = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
266
+ if (msgType === "session.reset") {
267
+ const scope = raw.scope === "target" ? "target" : "global";
268
+ const targetId = asString(raw.targetId) ?? asString(raw.target_id);
269
+ const reason = asString(raw.reason) ?? "user_explicit";
270
+ if (scope === "target" && !targetId) {
271
+ throw new EphiaSdkError(
272
+ "protocol.invalid_event",
273
+ "session.reset target scope requires targetId"
274
+ );
275
+ }
276
+ return scope === "target" && targetId ? { scope: "target", targetId, reason } : { scope: "global", reason };
277
+ }
278
+ if (msgType === "session.target.changed") {
279
+ return { targetId: asString(raw.targetId) ?? asString(raw.target_id) ?? "" };
280
+ }
281
+ if (msgType === "session.editor_context.update") {
282
+ return {
283
+ targetId: asString(raw.targetId) ?? asString(raw.target_id) ?? "",
284
+ documentEmpty: raw.documentEmpty ?? raw.document_empty ?? false,
285
+ insertionMode: raw.insertionMode ?? raw.insertion_mode ?? "append",
286
+ leftContext: raw.leftContext ?? raw.left_context ?? "",
287
+ rightContext: raw.rightContext ?? raw.right_context ?? "",
288
+ selectedText: raw.selectedText ?? raw.selected_text,
289
+ cursorOffset: raw.cursorOffset ?? raw.cursor_offset
290
+ };
291
+ }
292
+ return raw;
293
+ }
294
+ _attachRoomListeners(_params) {
295
+ if (!this.room) return;
296
+ this.room.on(RoomEvent.ConnectionStateChanged, (lkState) => {
297
+ dbgRawEvent("livekit", "RoomEvent.ConnectionStateChanged", { state: lkState });
298
+ switch (lkState) {
299
+ case ConnectionState.Connected:
300
+ this._setState({ status: "connected" });
301
+ break;
302
+ case ConnectionState.Disconnected:
303
+ this._setState({ status: "disconnected" });
304
+ break;
305
+ case ConnectionState.Reconnecting:
306
+ this._setState({
307
+ status: "reconnecting",
308
+ reconnectCount: this._state.reconnectCount + 1
309
+ });
310
+ break;
311
+ }
312
+ dbgState("livekit.transport", this._state);
313
+ });
314
+ this.room.on(RoomEvent.Reconnecting, () => {
315
+ dbgRawEvent("livekit", "RoomEvent.Reconnecting", {});
316
+ this._setState({
317
+ status: "reconnecting",
318
+ reconnectCount: this._state.reconnectCount + 1
319
+ });
320
+ dbgState("livekit.transport", this._state);
321
+ });
322
+ this.room.on(RoomEvent.Reconnected, () => {
323
+ dbgRawEvent("livekit", "RoomEvent.Reconnected", {});
324
+ this._setState({ status: "reconnected" });
325
+ dbgState("livekit.transport", this._state);
326
+ });
327
+ this.room.on(RoomEvent.ConnectionQualityChanged, (quality) => {
328
+ dbgRawEvent("livekit", "RoomEvent.ConnectionQualityChanged", { quality });
329
+ const mapped = quality === ConnectionQuality.Excellent ? "excellent" : quality === ConnectionQuality.Good ? "good" : quality === ConnectionQuality.Poor ? "poor" : "unknown";
330
+ this._setState({ connectionQuality: mapped });
331
+ dbgState("livekit.transport", this._state);
332
+ });
333
+ this.room.on(RoomEvent.LocalTrackPublished, () => {
334
+ dbgRawEvent("livekit", "RoomEvent.LocalTrackPublished", {});
335
+ this._setState({ localAudioPublished: true });
336
+ dbgState("livekit.transport", this._state);
337
+ });
338
+ this.room.on(RoomEvent.LocalTrackUnpublished, () => {
339
+ dbgRawEvent("livekit", "RoomEvent.LocalTrackUnpublished", {});
340
+ this._setState({ localAudioPublished: false });
341
+ dbgState("livekit.transport", this._state);
342
+ });
343
+ this.room.on(RoomEvent.LocalAudioSilenceDetected, () => {
344
+ dbgRawEvent("livekit", "RoomEvent.LocalAudioSilenceDetected", {});
345
+ this._setState({ localAudioMuted: true });
346
+ dbgState("livekit.transport", this._state);
347
+ });
348
+ this.room.on(RoomEvent.MediaDevicesError, (err) => {
349
+ dbgRawEvent("livekit", "RoomEvent.MediaDevicesError", { error: err instanceof Error ? err.message : String(err) });
350
+ const failure = MediaDeviceFailure.getFailure(err);
351
+ switch (failure) {
352
+ case MediaDeviceFailure.PermissionDenied:
353
+ this._emitError("audio.permission_denied", "Autorisation micro refus\xE9e \u2014 v\xE9rifier les param\xE8tres du navigateur");
354
+ break;
355
+ case MediaDeviceFailure.NotFound:
356
+ this._emitError("audio.no_input_device", "Aucun microphone d\xE9tect\xE9");
357
+ break;
358
+ case MediaDeviceFailure.DeviceInUse:
359
+ this._emitError("audio.device_in_use", "Micro occup\xE9 par une autre application");
360
+ break;
361
+ default:
362
+ this._emitError("audio.no_input_device", err instanceof Error ? err.message : String(err));
363
+ }
364
+ });
365
+ this.room.on(RoomEvent.DataReceived, (payload, _participant, kind, topic) => {
366
+ const isReliable = kind === DataPacket_Kind.RELIABLE;
367
+ const isLossy = kind === DataPacket_Kind.LOSSY;
368
+ if (topic !== TOPIC_SERVER_EVENT) return;
369
+ if (!isReliable && !isLossy) return;
370
+ try {
371
+ const data = JSON.parse(textDecoder.decode(payload));
372
+ dbgRawEvent("livekit", "RoomEvent.DataReceived", { topic, kind, data });
373
+ const parsed = ephiaWireServerEventSchema.safeParse(data);
374
+ if (!parsed.success) {
375
+ console.warn("[LiveKitTransport] invalid server event schema (ignored):", parsed.error.issues);
376
+ return;
377
+ }
378
+ this._handleWireServerEvent(parsed.data);
379
+ } catch (err) {
380
+ console.warn("[LiveKitTransport] failed to parse data channel payload", err);
381
+ }
382
+ });
383
+ }
384
+ _handleWireServerEvent(event) {
385
+ this._serverEventCallbacks.forEach((cb) => cb(event));
386
+ }
387
+ _roomOptions() {
388
+ return {
389
+ adaptiveStream: false,
390
+ dynacast: false,
391
+ disconnectOnPageLeave: false,
392
+ reconnectPolicy: RECONNECT_POLICY,
393
+ audioCaptureDefaults: {
394
+ echoCancellation: true,
395
+ noiseSuppression: true,
396
+ autoGainControl: true
397
+ }
398
+ };
399
+ }
400
+ _setState(partial) {
401
+ const next = { ...this._state, ...partial };
402
+ let changed = false;
403
+ for (const key in partial) {
404
+ if (next[key] !== this._state[key]) {
405
+ changed = true;
406
+ break;
407
+ }
408
+ }
409
+ if (!changed) return;
410
+ this._state = next;
411
+ this._stateCallbacks.forEach((cb) => cb(this._state));
412
+ }
413
+ _emitError(code, message) {
414
+ const err = new EphiaSdkError(code, message);
415
+ this._errorCallbacks.forEach((cb) => cb(err));
416
+ }
417
+ };
418
+
419
+ // src/core/client/client-state.ts
420
+ var initialClientState = {
421
+ status: "idle",
422
+ sessionId: null,
423
+ roomName: null,
424
+ error: null,
425
+ isMicEnabled: false
426
+ };
427
+
428
+ // src/core/session/session-machine.ts
429
+ var ALLOWED_TRANSITIONS = {
430
+ idle: ["creating_session", "disposed"],
431
+ creating_session: ["connecting_transport", "error", "disposed"],
432
+ connecting_transport: ["ready", "error", "disposed"],
433
+ ready: ["recording", "finalizing", "error", "disposed"],
434
+ recording: ["paused", "finalizing", "error", "disposed"],
435
+ paused: ["recording", "finalizing", "error", "disposed"],
436
+ // "paused" : stop() passe par finalizing pendant le drain backend, puis retombe en paused
437
+ finalizing: ["paused", "ended", "error", "disposed"],
438
+ ended: ["idle", "disposed"],
439
+ error: ["idle", "disposed"],
440
+ disposed: []
441
+ };
442
+ function transitionSessionStatus(current, next) {
443
+ if (current === next) return current;
444
+ const allowed = ALLOWED_TRANSITIONS[current] ?? [];
445
+ if (!allowed.includes(next)) {
446
+ throw new EphiaSdkError(
447
+ "client.invalid_state",
448
+ `Transition interdite: ${current} -> ${next}`,
449
+ { current, next, allowed }
450
+ );
451
+ }
452
+ return next;
453
+ }
454
+ function canTransition(current, next) {
455
+ if (current === next) return true;
456
+ const allowed = ALLOWED_TRANSITIONS[current] ?? [];
457
+ return allowed.includes(next);
458
+ }
459
+
460
+ // src/core/runtime/TranscriptApplier.ts
461
+ var MAX_SEEN_EVENTS = 1e3;
462
+ var MAX_PENDING_EVENTS = 50;
463
+ var TranscriptApplier = class {
464
+ targetManager;
465
+ warn;
466
+ debug;
467
+ /** eventId → déjà traité (idempotence). */
468
+ seenEventIds = /* @__PURE__ */ new Set();
469
+ seenEventQueue = [];
470
+ /** segmentId → dernière segmentRevision appliquée. */
471
+ latestRevisionBySegment = /* @__PURE__ */ new Map();
472
+ /** segmentId → targetId (cache défensif). */
473
+ segmentTargetMap = /* @__PURE__ */ new Map();
474
+ /** segmentIds pour lesquels un canonical a été appliqué avec succès. */
475
+ canonicalSegments = /* @__PURE__ */ new Set();
476
+ /** Events en attente de montage de leur cible (targetId → events[]). */
477
+ pendingEvents = /* @__PURE__ */ new Map();
478
+ constructor(options) {
479
+ this.targetManager = options.targetManager;
480
+ this.warn = options.warn ?? ((message, details) => console.warn(message, details));
481
+ this.debug = options.debug ?? (() => {
482
+ });
483
+ }
484
+ handleEvent(event) {
485
+ if (this.seenEventIds.has(event.eventId)) {
486
+ this.debug("duplicate event ignored", { eventId: event.eventId, type: event.type });
487
+ return;
488
+ }
489
+ if (event.type === "segment.preview") {
490
+ if (this.canonicalSegments.has(event.payload.segmentId)) {
491
+ this.debug("late preview ignored after canonical", {
492
+ segmentId: event.payload.segmentId
493
+ });
494
+ this._markEventSeen(event.eventId);
495
+ return;
496
+ }
497
+ const binding = this.resolveBinding(event.payload.targetId, event.payload.segmentId);
498
+ if (!binding) {
499
+ this._enqueuePendingEvent(event, event.payload.targetId);
500
+ return;
501
+ }
502
+ this._markEventSeen(event.eventId);
503
+ binding.previewSegment?.({ id: event.payload.segmentId, text: event.payload.text });
504
+ return;
505
+ }
506
+ if (event.type === "segment.operation") {
507
+ const applied = this.applyOperation(event.payload, event);
508
+ if (applied) this._markEventSeen(event.eventId);
509
+ }
510
+ }
511
+ /**
512
+ * Rejoue les events en attente pour une cible donnée.
513
+ * À appeler après l'enregistrement d'une nouvelle target.
514
+ */
515
+ flushPendingEvents(targetId) {
516
+ const pending = this.pendingEvents.get(targetId);
517
+ if (!pending || pending.length === 0) return;
518
+ this.debug("flushing pending events", { targetId, count: pending.length });
519
+ const toRetry = [...pending];
520
+ this.pendingEvents.delete(targetId);
521
+ for (const event of toRetry) {
522
+ this.handleEvent(event);
523
+ }
524
+ }
525
+ applyOperation(payload, event) {
526
+ const binding = this.resolveBinding(payload.targetId, payload.segmentId);
527
+ if (!binding) {
528
+ if (event && payload.targetId) {
529
+ this._enqueuePendingEvent(event, payload.targetId);
530
+ }
531
+ return false;
532
+ }
533
+ if (!this.shouldApplySegmentRevision(payload)) {
534
+ return true;
535
+ }
536
+ if (payload.operation === "noop") return true;
537
+ if (payload.operation === "delete") {
538
+ binding.removeSegment(payload.segmentId);
539
+ if (payload.absorbedSegmentIds?.length) {
540
+ binding.removeSegments(payload.absorbedSegmentIds);
541
+ }
542
+ return true;
543
+ }
544
+ binding.upsertSegment({
545
+ id: payload.segmentId,
546
+ text: payload.textForInsertion || payload.text,
547
+ stage: payload.stage,
548
+ source: payload.source
549
+ });
550
+ if (payload.stage === "canonical") {
551
+ this.canonicalSegments.add(payload.segmentId);
552
+ }
553
+ if (payload.absorbedSegmentIds?.length) {
554
+ binding.removeSegments(payload.absorbedSegmentIds);
555
+ }
556
+ return true;
557
+ }
558
+ shouldApplySegmentRevision(payload) {
559
+ const previous = this.latestRevisionBySegment.get(payload.segmentId);
560
+ const current = payload.segmentRevision;
561
+ if (previous !== void 0 && current <= previous) {
562
+ this.debug("stale segment revision ignored", {
563
+ segmentId: payload.segmentId,
564
+ previous,
565
+ current
566
+ });
567
+ return false;
568
+ }
569
+ this.latestRevisionBySegment.set(payload.segmentId, current);
570
+ return true;
571
+ }
572
+ resolveBinding(targetId, segmentId) {
573
+ if (targetId) {
574
+ const target = this.targetManager.get(targetId);
575
+ const binding = this.asEphiaBinding(target?.binding);
576
+ if (!binding) {
577
+ this.warn("[ephia] target not found; transcript event ignored", {
578
+ targetId,
579
+ segmentId
580
+ });
581
+ return null;
582
+ }
583
+ this.segmentTargetMap.set(segmentId, targetId);
584
+ return binding;
585
+ }
586
+ const mappedTargetId = this.segmentTargetMap.get(segmentId);
587
+ if (mappedTargetId) {
588
+ return this.asEphiaBinding(this.targetManager.get(mappedTargetId)?.binding);
589
+ }
590
+ this.warn("[ephia] segment.operation missing targetId; event ignored", {
591
+ segmentId
592
+ });
593
+ return null;
594
+ }
595
+ _markEventSeen(eventId) {
596
+ this.seenEventIds.add(eventId);
597
+ this.seenEventQueue.push(eventId);
598
+ if (this.seenEventQueue.length > MAX_SEEN_EVENTS) {
599
+ const oldest = this.seenEventQueue.shift();
600
+ if (oldest) this.seenEventIds.delete(oldest);
601
+ }
602
+ }
603
+ asEphiaBinding(binding) {
604
+ if (!binding) return null;
605
+ if (typeof binding === "object" && "upsertSegment" in binding) return binding;
606
+ return null;
607
+ }
608
+ _enqueuePendingEvent(event, targetId) {
609
+ if (!targetId) return;
610
+ const queue = this.pendingEvents.get(targetId) ?? [];
611
+ if (queue.length >= MAX_PENDING_EVENTS) {
612
+ this.debug("pending events queue full, dropping oldest", { targetId });
613
+ queue.shift();
614
+ }
615
+ queue.push(event);
616
+ this.pendingEvents.set(targetId, queue);
617
+ this.debug("event enqueued for target", {
618
+ targetId,
619
+ eventId: event.eventId,
620
+ type: event.type,
621
+ queueSize: queue.length
622
+ });
623
+ }
624
+ };
625
+
626
+ // src/core/targets/TargetManager.ts
627
+ var TargetManager = class {
628
+ targets = /* @__PURE__ */ new Map();
629
+ sendMessage;
630
+ flushDelayMs;
631
+ onActiveTargetChange;
632
+ onTargetRegistered;
633
+ activeTargetId = null;
634
+ flushTimers = /* @__PURE__ */ new Map();
635
+ constructor(options) {
636
+ this.sendMessage = options.sendMessage;
637
+ this.flushDelayMs = options.flushDelayMs ?? 80;
638
+ this.onActiveTargetChange = options.onActiveTargetChange;
639
+ this.onTargetRegistered = options.onTargetRegistered;
640
+ }
641
+ /**
642
+ * Register a target and return its registration token.
643
+ * Pass the token to unregister() to prevent stale cleanups from accidentally
644
+ * removing a newer registration with the same id (React StrictMode / remounts).
645
+ */
646
+ register(target) {
647
+ const token = Symbol(target.id);
648
+ this.targets.set(target.id, { ...target, token });
649
+ if (this.activeTargetId === null && target.mode !== "read") {
650
+ this.setActive(target.id);
651
+ }
652
+ this.onTargetRegistered?.(target.id);
653
+ return token;
654
+ }
655
+ /**
656
+ * Unregister a target.
657
+ * When `token` is provided, only unregisters if the stored token matches
658
+ * (prevents a stale cleanup from removing a newer registration).
659
+ * Returns true if the target was removed, false if skipped.
660
+ */
661
+ unregister(targetId, token) {
662
+ const current = this.targets.get(targetId);
663
+ if (!current) return false;
664
+ if (token !== void 0 && current.token !== token) {
665
+ return false;
666
+ }
667
+ this.targets.delete(targetId);
668
+ this.clearFlush(targetId);
669
+ if (this.activeTargetId === targetId) {
670
+ const next = this.firstWritableTargetId();
671
+ this.activeTargetId = null;
672
+ if (next) {
673
+ this.setActive(next);
674
+ } else {
675
+ this.onActiveTargetChange?.(null);
676
+ }
677
+ }
678
+ return true;
679
+ }
680
+ get(targetId) {
681
+ return this.targets.get(targetId);
682
+ }
683
+ getActiveTargetId() {
684
+ return this.activeTargetId;
685
+ }
686
+ getActiveTarget() {
687
+ return this.activeTargetId ? this.targets.get(this.activeTargetId) : void 0;
688
+ }
689
+ setActive(targetId) {
690
+ if (!this.targets.has(targetId)) return;
691
+ if (this.activeTargetId === targetId) {
692
+ this.scheduleEditorContextFlush(targetId);
693
+ return;
694
+ }
695
+ this.activeTargetId = targetId;
696
+ this.onActiveTargetChange?.(targetId);
697
+ this.send({
698
+ type: "session.target.changed",
699
+ payload: { targetId }
700
+ });
701
+ this.scheduleEditorContextFlush(targetId);
702
+ }
703
+ syncActiveTarget() {
704
+ if (!this.activeTargetId) return;
705
+ this.send({
706
+ type: "session.target.changed",
707
+ payload: { targetId: this.activeTargetId }
708
+ });
709
+ this.flushEditorContext(this.activeTargetId);
710
+ }
711
+ /** Resync native bindings from current DOM before dictation (manual edits between sessions). */
712
+ prepareForDictation(targetId = this.activeTargetId) {
713
+ if (!targetId) return;
714
+ const binding = this.targets.get(targetId)?.binding;
715
+ if (binding?.kind === "native" && "prepareForDictation" in binding) {
716
+ binding.prepareForDictation();
717
+ }
718
+ }
719
+ flushEditorContext(targetId = this.activeTargetId) {
720
+ if (!targetId) return;
721
+ this.clearFlush(targetId);
722
+ const target = this.targets.get(targetId);
723
+ if (!target?.binding) return;
724
+ const context = target.binding.getEditorContext(target.id);
725
+ this.send({
726
+ type: "session.editor_context.update",
727
+ payload: context
728
+ });
729
+ }
730
+ scheduleEditorContextFlush(targetId = this.activeTargetId) {
731
+ if (!targetId) return;
732
+ this.clearFlush(targetId);
733
+ const timer = setTimeout(() => {
734
+ this.flushTimers.delete(targetId);
735
+ this.flushEditorContext(targetId);
736
+ }, this.flushDelayMs);
737
+ this.flushTimers.set(targetId, timer);
738
+ }
739
+ reset(scope, targetId = this.activeTargetId, reason = "user_explicit") {
740
+ this.send({
741
+ type: "session.reset",
742
+ payload: scope === "target" && targetId ? { scope: "target", targetId, reason } : { scope: "global", reason }
743
+ });
744
+ }
745
+ dispose() {
746
+ for (const targetId of this.flushTimers.keys()) {
747
+ this.clearFlush(targetId);
748
+ }
749
+ this.targets.clear();
750
+ this.activeTargetId = null;
751
+ }
752
+ firstWritableTargetId() {
753
+ for (const target of this.targets.values()) {
754
+ if (target.mode !== "read") return target.id;
755
+ }
756
+ return null;
757
+ }
758
+ clearFlush(targetId) {
759
+ const timer = this.flushTimers.get(targetId);
760
+ if (timer) clearTimeout(timer);
761
+ this.flushTimers.delete(targetId);
762
+ }
763
+ send(message) {
764
+ this.sendMessage(message).catch(() => {
765
+ });
766
+ }
767
+ };
768
+
769
+ export {
770
+ dbgState,
771
+ LiveKitTransport,
772
+ initialClientState,
773
+ transitionSessionStatus,
774
+ canTransition,
775
+ TranscriptApplier,
776
+ TargetManager
777
+ };
778
+ //# sourceMappingURL=chunk-LXMCRXXF.js.map