@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 @@
1
+ {"version":3,"sources":["../../../src/core/bindings/TargetBinding.ts","../../../src/core/bindings/targets/tiptap.binding.ts","../../../src/shared/store/document-store.ts","../../../src/core/text-processing/overlap.ts"],"sourcesContent":["import type { DocumentOperation } from \"ephia-protocol\";\n\nexport interface SessionAnchor {\n /** Position où la dictée a commencé (immutable pendant la session). */\n readonly initialStart: number;\n /** Fin initiale (égale à initialStart si pas de sélection). */\n readonly initialEnd: number;\n /** True si la session a démarré avec une sélection non-vide. */\n readonly hadSelection: boolean;\n /** Position courante de la fin du texte dicté (mise à jour à chaque insert). */\n end: number;\n /**\n * True tant que la sélection initiale n'a pas encore été supprimée.\n * Le binding la supprimera atomiquement avec le premier insert de texte dicté.\n */\n selectionPendingDelete: boolean;\n}\n\nexport interface BeginSessionOptions {\n /**\n * Si true, la sélection courante (si non vide) sera supprimée au premier\n * insert et remplacée par le texte dicté. Défaut : true.\n */\n replaceSelection?: boolean;\n}\n\nexport interface CommitFinalOptions {\n absorbedSegmentIds?: string[];\n}\n\nexport interface TargetBinding {\n kind: string;\n\n attach(): void;\n detach(): void;\n\n // ─── Session lifecycle ─────────────────────────────────────────────────────\n /**\n * Verrouille un anchor au début d'une session de dictée. Tous les inserts\n * suivants se feront relativement à cet anchor, indépendamment du curseur\n * de l'utilisateur. Si l'utilisateur avait une sélection non-vide, elle\n * sera remplacée (sauf si replaceSelection: false).\n */\n beginSession?(opts?: BeginSessionOptions): SessionAnchor;\n\n /** Termine la session et relâche l'anchor. Les textes restent dans le document. */\n endSession?(): void;\n\n /** Retourne l'anchor courant, ou null si pas de session active. */\n getSessionAnchor?(): SessionAnchor | null;\n\n /** Position visuelle du point d'insertion (anchor.end pendant la session, curseur sinon). */\n getCursorRect?(): DOMRect | null;\n\n /**\n * Rectangle englobant d'une plage de texte. Utilisé pour le flash visuel\n * (revised, committed). Retourne null si non supporté ou plage hors vue.\n */\n getRangeRect?(start: number, end: number): DOMRect | null;\n\n // ─── Streaming insertion ───────────────────────────────────────────────────\n insertPartial?(segmentId: string, text: string, revision: number): void;\n commitFinal(segmentId: string, text: string, options?: CommitFinalOptions): void;\n clearPartial(segmentId: string): void;\n clearAll?(): void;\n\n /** Retourne la plage [start, end] d'un segment commis dans le document, ou null si inconnu. */\n getSegmentRange?(segmentId: string): { start: number; end: number } | null;\n\n /** Récupère le texte complet du document */\n getText(): string;\n\n /** Récupère la sélection actuelle */\n getSelection?(): {\n text: string;\n range: {\n start: number;\n end: number;\n };\n } | null;\n\n /** Récupère la position du curseur */\n getCursorOffset?(): number | null;\n\n /** Applique une opération structurée au document */\n applyOperation(operation: DocumentOperation): void;\n\n /**\n * Applique une séquence d'opérations (ex: insert_text/line_break/paragraph_break\n * issue de textToDocumentOperations()). applyOperation() seul ne traite\n * qu'une opération à la fois et n'a pas de notion de curseur implicite entre\n * deux appels — un binding qui veut positionner correctement chaque\n * opération relativement aux précédentes (ex: TipTap, cf. P4) doit fournir\n * sa propre implémentation. Sinon, applyOperationsSequentially() (ci-dessous)\n * sert de filet par défaut.\n */\n applyOperations?(operations: DocumentOperation[]): void;\n\n /** Appelé quand un segment passe en état \"reviewing\" (correction SMART en cours) */\n notifyReviewing?(): void;\n\n /** Appelé quand la review d'un segment est terminée (corrected ou confirmed) */\n notifyReviewDone?(): void;\n}\n\n/**\n * Insère un joiner intelligent entre deux morceaux de texte.\n * - Pas de joiner si un côté est déjà séparé par un whitespace ou une ponctuation\n * - Espace par défaut sinon\n */\nexport function pickJoiner(before: string, next: string): string {\n if (!before || !next) return \"\";\n const lastChar = before[before.length - 1];\n const firstChar = next[0];\n if (/\\s/.test(lastChar)) return \"\";\n if (/\\s/.test(firstChar)) return \"\";\n if (/^[,.;:!?)\\]]/.test(firstChar)) return \"\";\n if (/[(\\[]/.test(lastChar)) return \"\";\n return \" \";\n}\n\nexport function endsWithNewline(text: string): boolean {\n return text.length > 0 && text[text.length - 1] === \"\\n\";\n}\n\n/**\n * Filet par défaut pour les bindings qui ne fournissent pas leur propre\n * applyOperations() : appelle binding.applyOperations() s'il existe, sinon\n * boucle sur applyOperation() opération par opération (P3).\n */\nexport function applyOperationsSequentially(\n binding: TargetBinding,\n operations: DocumentOperation[]\n): void {\n if (binding.applyOperations) {\n binding.applyOperations(operations);\n return;\n }\n for (const operation of operations) {\n binding.applyOperation(operation);\n }\n}\n","/**\n * Binding TipTap historique mais encore actif.\n * Ne pas supprimer tant que createTiptapBinding est utilisé par les intégrations.\n * Les corrections d'insertion realtime TipTap doivent être faites ici, pas dans ProseMirrorAdapter.\n */\n\nimport type { Editor } from \"@tiptap/core\";\nimport { Mark } from \"@tiptap/core\";\nimport type { Mark as ProseMirrorMark } from \"@tiptap/pm/model\";\nimport { Plugin, PluginKey, TextSelection } from \"@tiptap/pm/state\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport type {\n BeginSessionOptions,\n CommitFinalOptions,\n SessionAnchor,\n TargetBinding,\n} from \"../TargetBinding\";\nimport { pickJoiner } from \"../TargetBinding\";\nimport type { DocumentOperation } from \"ephia-protocol\";\nimport { documentStore } from \"../../../shared/store/document-store\";\nimport { textToDocumentOperations } from \"../../operations/textToDocumentOperations\";\nimport { stripLeadingOverlapFromTextWithInfo } from \"../../text-processing/overlap\";\n\n// ---------------------------------------------------------------------------\n// ProseMirror Plugin — stocke les positions des segments audio + l'anchor de\n// session, et les mappe automatiquement à chaque transaction (typing, undo).\n// ---------------------------------------------------------------------------\n\ninterface EphiaTiptapPluginState {\n segments: Record<string, { from: number; to: number; revision: number; joinerLength?: number }>;\n anchor: {\n initialStart: number;\n initialEnd: number;\n hadSelection: boolean;\n end: number;\n selectionPendingDelete: boolean;\n } | null;\n}\n\nconst ephiaTiptapKey = new PluginKey<EphiaTiptapPluginState>(\"ephiaTiptap\");\n\ntype SegmentUpdate = Record<string, { from: number; to: number; revision: number; joinerLength?: number } | null>;\ntype AnchorUpdate =\n | { kind: \"set\"; value: EphiaTiptapPluginState[\"anchor\"] }\n | { kind: \"patch\"; end?: number }\n | { kind: \"clear\" };\n\ninterface EphiaMeta {\n segments?: SegmentUpdate;\n anchor?: AnchorUpdate;\n}\n\nfunction createEphiaTiptapPlugin(): Plugin {\n return new Plugin({\n key: ephiaTiptapKey,\n state: {\n init(): EphiaTiptapPluginState {\n return { segments: {}, anchor: null };\n },\n apply(tr, value): EphiaTiptapPluginState {\n const docSize = tr.doc.content.size;\n\n // 1. Mapper les segments existants — clamp + drop hors bornes\n const mappedSegments: EphiaTiptapPluginState[\"segments\"] = {};\n for (const [id, pos] of Object.entries(value.segments)) {\n const mappedFrom = Math.min(Math.max(tr.mapping.map(pos.from), 0), docSize);\n const mappedTo = Math.min(Math.max(tr.mapping.map(pos.to), 0), docSize);\n if (mappedFrom < 0 || mappedTo < 0 || mappedFrom > docSize || mappedTo > docSize || mappedFrom > mappedTo) {\n continue; // segment supprimé / corrompu par undo profond\n }\n mappedSegments[id] = {\n from: mappedFrom,\n to: mappedTo,\n revision: pos.revision,\n joinerLength: pos.joinerLength,\n };\n }\n\n // 2. Mapper l'anchor existant — clamp + drop hors bornes\n let mappedAnchor = value.anchor\n ? {\n initialStart: Math.min(Math.max(tr.mapping.map(value.anchor.initialStart), 0), docSize),\n initialEnd: Math.min(Math.max(tr.mapping.map(value.anchor.initialEnd), 0), docSize),\n hadSelection: value.anchor.hadSelection,\n end: Math.min(Math.max(tr.mapping.map(value.anchor.end), 0), docSize),\n selectionPendingDelete: value.anchor.selectionPendingDelete,\n }\n : null;\n if (mappedAnchor && (mappedAnchor.initialStart > docSize || mappedAnchor.initialEnd > docSize || mappedAnchor.end > docSize)) {\n mappedAnchor = null;\n }\n\n // 3. Appliquer les mises à jour explicites\n const meta = tr.getMeta(ephiaTiptapKey) as EphiaMeta | undefined;\n if (meta) {\n if (meta.segments) {\n for (const [id, pos] of Object.entries(meta.segments)) {\n if (pos === null) delete mappedSegments[id];\n else mappedSegments[id] = pos;\n }\n }\n if (meta.anchor) {\n if (meta.anchor.kind === \"clear\") {\n mappedAnchor = null;\n } else if (meta.anchor.kind === \"set\") {\n mappedAnchor = meta.anchor.value;\n } else if (meta.anchor.kind === \"patch\" && mappedAnchor) {\n mappedAnchor = {\n ...mappedAnchor,\n end: meta.anchor.end ?? mappedAnchor.end,\n };\n }\n }\n }\n\n return { segments: mappedSegments, anchor: mappedAnchor };\n },\n },\n });\n}\n\nfunction getPluginState(editor: Editor): EphiaTiptapPluginState {\n return (\n ephiaTiptapKey.getState(editor.state) ?? { segments: {}, anchor: null }\n );\n}\n\nfunction setSegmentMeta(\n tr: Transaction,\n segmentId: string,\n pos: { from: number; to: number; revision: number; joinerLength?: number } | null\n): void {\n const current = (tr.getMeta(ephiaTiptapKey) as EphiaMeta | undefined) ?? {};\n current.segments = { ...(current.segments ?? {}), [segmentId]: pos };\n tr.setMeta(ephiaTiptapKey, current);\n}\n\nfunction setAnchorMeta(tr: Transaction, update: AnchorUpdate): void {\n const current = (tr.getMeta(ephiaTiptapKey) as EphiaMeta | undefined) ?? {};\n current.anchor = update;\n tr.setMeta(ephiaTiptapKey, current);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers — diff intelligent pour éviter delete+insert inutiles\n// ---------------------------------------------------------------------------\n\nfunction getCommonPrefix(a: string, b: string): number {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return i;\n}\n\nfunction getCommonSuffix(a: string, b: string, prefixLen: number): number {\n let i = 0;\n while (\n i < a.length - prefixLen &&\n i < b.length - prefixLen &&\n a[a.length - 1 - i] === b[b.length - 1 - i]\n ) {\n i++;\n }\n return i;\n}\n\n// ---------------------------------------------------------------------------\n// Scroll helper — throttle + viewport check + user-scroll detection\n// ---------------------------------------------------------------------------\n\nconst SCROLL_THROTTLE_MS = 300;\nconst USER_SCROLL_QUIET_MS = 500;\nconst PARTIAL_DEBOUNCE_MS = 60;\nconst SKIP_STORE_SYNC_META = \"ephiaSkipStoreSync\";\n\nfunction isPosVisible(editor: Editor, pos: number): boolean {\n try {\n const coords = editor.view.coordsAtPos(pos);\n const rect = editor.view.dom.getBoundingClientRect();\n return coords.top >= rect.top && coords.bottom <= rect.bottom;\n } catch {\n return true; // par défaut, ne pas scroller\n }\n}\n\n// ---------------------------------------------------------------------------\n// Binding TipTap — session anchor + UX fluide\n// ---------------------------------------------------------------------------\n\ninterface ParsedSection {\n id: string;\n title: string;\n text: string;\n level: number;\n start: number;\n end: number;\n}\n\ntype ProseMirrorDoc = Editor[\"state\"][\"doc\"];\n\nfunction textBetweenWithHardBreaks(\n doc: ProseMirrorDoc,\n from: number,\n to: number,\n blockSeparator?: string\n): string {\n return doc.textBetween(from, to, blockSeparator, \"\\n\");\n}\n\nfunction getTextFromDoc(doc: ProseMirrorDoc): string {\n return textBetweenWithHardBreaks(doc, 0, doc.content.size, \"\\n\\n\");\n}\n\nfunction resolveInsertPosition(\n editor: Editor,\n position?: number | \"cursor\" | \"start\" | \"end\"\n): number {\n if (typeof position === \"number\") {\n return Math.min(Math.max(position, 0), editor.state.doc.content.size);\n }\n if (position === \"start\") return 1;\n if (position === \"end\") return Math.max(1, editor.state.doc.content.size - 1);\n return editor.state.selection.from;\n}\n\nfunction insertOperationsAt(\n editor: Editor,\n tr: Transaction,\n pos: number,\n operations: DocumentOperation[],\n mark?: ProseMirrorMark\n): { from: number; to: number; fallbackSteps: string[] } {\n let cursor = pos;\n const fallbackSteps: string[] = [];\n const hardBreak = editor.schema.nodes.hardBreak;\n const marks = mark ? [mark] : [];\n\n const insertText = (text: string) => {\n if (!text) return;\n const node = editor.schema.text(text, marks);\n tr.insert(cursor, node);\n cursor += node.nodeSize;\n };\n\n const insertHardBreakOrFallback = () => {\n if (hardBreak) {\n const node = hardBreak.create();\n tr.insert(cursor, node);\n cursor += node.nodeSize;\n return;\n }\n const node = editor.schema.text(\"\\n\", marks);\n tr.insert(cursor, node);\n cursor += node.nodeSize;\n fallbackSteps.push(\"fallback_plain_newline_no_hardbreak_node\");\n };\n\n for (const operation of operations) {\n if (operation.type === \"insert_text\" || operation.type === \"insert\") {\n insertText(operation.text);\n } else if (operation.type === \"line_break\") {\n insertHardBreakOrFallback();\n } else if (operation.type === \"paragraph_break\") {\n insertHardBreakOrFallback();\n insertHardBreakOrFallback();\n }\n }\n\n return { from: pos, to: cursor, fallbackSteps };\n}\n\nfunction measureOperationsSize(editor: Editor, operations: DocumentOperation[]): number {\n const hardBreak = editor.schema.nodes.hardBreak;\n let size = 0;\n for (const operation of operations) {\n if (operation.type === \"insert_text\" || operation.type === \"insert\") {\n if (operation.text) {\n size += editor.schema.text(operation.text).nodeSize;\n }\n } else if (operation.type === \"line_break\") {\n size += hardBreak ? hardBreak.create().nodeSize : editor.schema.text(\"\\n\").nodeSize;\n } else if (operation.type === \"paragraph_break\") {\n const breakSize = hardBreak ? hardBreak.create().nodeSize : editor.schema.text(\"\\n\").nodeSize;\n size += breakSize * 2;\n }\n }\n return size;\n}\n\nfunction measureTextAsOperationsSize(editor: Editor, text: string): number {\n return measureOperationsSize(editor, textToDocumentOperations(text));\n}\n\nfunction parseSegmentsFromDoc(doc: ProseMirrorDoc): ParsedSection[] {\n const segments: ParsedSection[] = [];\n let currentSection: ParsedSection | null = null;\n let offset = 0;\n\n doc.descendants((node, _pos) => {\n if (node.type.name === \"heading\") {\n if (currentSection) currentSection.end = offset;\n currentSection = {\n id: `sec_${segments.length}`,\n title: node.textContent,\n text: \"\",\n level: node.attrs.level || 1,\n start: offset,\n end: offset,\n };\n segments.push(currentSection);\n } else if (currentSection && node.isTextblock) {\n currentSection.text +=\n (currentSection.text ? \"\\n\" : \"\") + node.textContent;\n }\n offset += node.nodeSize;\n return true;\n });\n\n if (currentSection) {\n (currentSection as ParsedSection).end = offset;\n }\n return segments;\n}\n\nfunction parseSegmentsFromEditor(editor: Editor): ParsedSection[] {\n return parseSegmentsFromDoc(editor.state.doc);\n}\n\n/** Convertit l'anchor du plugin (positions PM) vers l'interface publique. */\nfunction toPublicAnchor(\n anchor: NonNullable<EphiaTiptapPluginState[\"anchor\"]>\n): SessionAnchor {\n return {\n initialStart: anchor.initialStart,\n initialEnd: anchor.initialEnd,\n hadSelection: anchor.hadSelection,\n end: anchor.end,\n selectionPendingDelete: anchor.selectionPendingDelete,\n };\n}\n\n/* ── TipTap Marks ─────────────────────────────────────────────────────────── */\n\n/** Mark affiché sur le texte en cours de streaming (preview temps réel). */\nexport const EphiaPreviewMark = Mark.create({\n name: \"ephiaPreview\",\n parseHTML() {\n return [{ tag: \"span[data-ephia-streaming]\" }];\n },\n renderHTML() {\n return [\n \"span\",\n {\n \"data-ephia-streaming\": \"true\",\n class: \"ephia-text--streaming\",\n },\n 0,\n ];\n },\n});\n\n/** Mark temporaire affiché pendant la transition committed (streaming → normal). */\nexport const EphiaCommittedMark = Mark.create({\n name: \"ephiaCommitted\",\n parseHTML() {\n return [{ tag: \"span[data-ephia-committed]\" }];\n },\n renderHTML() {\n return [\n \"span\",\n {\n \"data-ephia-committed\": \"true\",\n class: \"ephia-text--committed\",\n },\n 0,\n ];\n },\n});\n\n/** Mark affiché sur le texte corrigé par le review pipeline. */\nexport const EphiaRevisedMark = Mark.create({\n name: \"ephiaRevised\",\n parseHTML() {\n return [{ tag: \"span[data-ephia-revised]\" }];\n },\n renderHTML() {\n return [\n \"span\",\n {\n \"data-ephia-revised\": \"true\",\n class: \"ephia-text--revised\",\n },\n 0,\n ];\n },\n});\n\n/** Mark affiché sur le placeholder de démarrage (interim avant le premier partial). */\nexport const EphiaPlaceholderMark = Mark.create({\n name: \"ephiaPlaceholder\",\n parseHTML() {\n return [{ tag: \"span[data-ephia-placeholder]\" }];\n },\n renderHTML() {\n return [\n \"span\",\n {\n \"data-ephia-placeholder\": \"true\",\n class: \"ephia-text--placeholder\",\n },\n 0,\n ];\n },\n});\n\nexport function createTiptapBinding(editor: Editor, wrapperEl?: HTMLElement): TargetBinding {\n const plugin = createEphiaTiptapPlugin();\n editor.registerPlugin(plugin);\n let lastScrollTime = 0;\n let lastUserScrollTime = 0;\n let lastDictationEnd: number | null = null;\n let cachedSectionsDoc = editor.state.doc;\n let cachedSections = parseSegmentsFromEditor(editor);\n\n const markUserScroll = () => {\n lastUserScrollTime = Date.now();\n };\n\n const maybeScrollIntoView = (tr: Transaction, ed: Editor, pos: number): void => {\n const now = Date.now();\n if (now - lastUserScrollTime < USER_SCROLL_QUIET_MS) return;\n if (now - lastScrollTime < SCROLL_THROTTLE_MS) return;\n if (isPosVisible(ed, pos)) return;\n tr.scrollIntoView();\n lastScrollTime = now;\n };\n\n editor.view.dom.addEventListener(\"scroll\", markUserScroll, { passive: true });\n\n // Timeouts de transition committed à nettoyer au detach.\n const committedTimeouts = new Map<string, number>();\n const knownSegmentIds = new Set<string>();\n\n const clearCommittedTimeout = (segmentId: string): void => {\n const timeoutId = committedTimeouts.get(segmentId);\n if (timeoutId !== undefined) {\n window.clearTimeout(timeoutId);\n committedTimeouts.delete(segmentId);\n }\n };\n\n // Sync to Zustand store — debouncé via rAF pour éviter de re-parser tout le\n // doc (getText + parseSegments) à chaque transaction pendant le streaming.\n // Sur une dictée fluide, on reçoit plusieurs partials/sec : sans debounce\n // ça multiplie par N le coût des subscribers du store.\n let syncRaf: number | null = null;\n let pendingSyncDoc: ProseMirrorDoc | null = null;\n const getCachedSections = (): ParsedSection[] => cachedSections;\n\n const syncToStore = (event?: { transaction?: Transaction }) => {\n if (event?.transaction?.getMeta(SKIP_STORE_SYNC_META)) return;\n pendingSyncDoc = event?.transaction?.doc ?? editor.state.doc;\n if (syncRaf !== null) return;\n syncRaf = (typeof requestAnimationFrame !== \"undefined\"\n ? requestAnimationFrame\n : (cb: FrameRequestCallback) => setTimeout(cb, 16) as unknown as number)(\n () => {\n syncRaf = null;\n const doc = pendingSyncDoc ?? editor.state.doc;\n pendingSyncDoc = null;\n const text = getTextFromDoc(doc);\n cachedSectionsDoc = doc;\n cachedSections = parseSegmentsFromDoc(doc);\n const segments = cachedSections;\n documentStore.getState().syncFromEditor(text, segments);\n }\n );\n };\n\n const syncSelection = () => {\n const { from, to } = editor.state.selection;\n const selectedText =\n from !== to ? textBetweenWithHardBreaks(editor.state.doc, from, to) : null;\n if (cachedSectionsDoc !== editor.state.doc && cachedSections.length === 0) {\n cachedSectionsDoc = editor.state.doc;\n cachedSections = parseSegmentsFromEditor(editor);\n }\n const cursor = editor.state.selection.from;\n const section =\n getCachedSections().find((seg) => cursor >= seg.start && cursor <= seg.end)\n ?.title ?? null;\n documentStore.getState().setSelection(selectedText, section);\n };\n\n editor.on(\"update\", syncToStore);\n editor.on(\"selectionUpdate\", syncSelection);\n\n syncToStore();\n syncSelection();\n\n const _applyPartial = (segmentId: string, text: string, revision: number): void => {\n const state = getPluginState(editor);\n const existing = state.segments[segmentId];\n const isPlaceholder = segmentId === \"ephia-startup-placeholder\";\n const mark = isPlaceholder\n ? editor.schema.marks.ephiaPlaceholder?.create()\n : editor.schema.marks.ephiaPreview?.create();\n\n if (existing) {\n // Réordering réseau : rejeter une révision périmée\n if (revision <= existing.revision) return;\n\n const oldText = textBetweenWithHardBreaks(editor.state.doc, existing.from, existing.to);\n if (oldText === text) return;\n\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n\n const prefixLen = getCommonPrefix(oldText, text);\n const suffixLen = getCommonSuffix(oldText, text, prefixLen);\n\n if (prefixLen > 0 || suffixLen > 0) {\n const deleteFrom = existing.from + prefixLen;\n const deleteTo = existing.to - suffixLen;\n const insertText = text.slice(prefixLen, text.length - suffixLen);\n\n if (deleteFrom < deleteTo) tr.delete(deleteFrom, deleteTo);\n if (insertText) {\n insertOperationsAt(\n editor,\n tr,\n deleteFrom,\n textToDocumentOperations(insertText),\n mark\n );\n }\n const newEnd = existing.from + measureTextAsOperationsSize(editor, text);\n setSegmentMeta(tr, segmentId, {\n from: existing.from,\n to: newEnd,\n revision,\n joinerLength: existing.joinerLength,\n });\n } else {\n tr.delete(existing.from, existing.to);\n const inserted = insertOperationsAt(\n editor,\n tr,\n existing.from,\n textToDocumentOperations(text),\n mark\n );\n setSegmentMeta(tr, segmentId, {\n from: existing.from,\n to: inserted.to,\n revision,\n joinerLength: existing.joinerLength,\n });\n }\n\n // L'anchor.end est mappé automatiquement via tr.mapping\n maybeScrollIntoView(tr, editor, existing.to);\n editor.view.dispatch(tr);\n knownSegmentIds.add(segmentId);\n } else {\n // Nouveau segment → insérer à l'anchor (ou au curseur si pas de session)\n if (!state.anchor) {\n return; // session terminée → rejeter le partial fantôme\n }\n\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n\n // Suppression lazy de la sélection : si une sélection était en attente,\n // la supprimer atomiquement avec le premier insert dicté.\n let pos = state.anchor.end;\n if (\n state.anchor.selectionPendingDelete &&\n state.anchor.initialEnd > state.anchor.initialStart\n ) {\n tr.delete(state.anchor.initialStart, state.anchor.initialEnd);\n pos = state.anchor.initialStart;\n setAnchorMeta(tr, {\n kind: \"set\",\n value: {\n ...state.anchor,\n selectionPendingDelete: false,\n initialEnd: state.anchor.initialStart,\n end: state.anchor.initialStart,\n },\n });\n }\n\n const before = textBetweenWithHardBreaks(editor.state.doc, Math.max(0, pos - 500), pos);\n const overlap = stripLeadingOverlapFromTextWithInfo(before, text);\n text = overlap.text;\n if (!text) return;\n const joiner = overlap.partialWord ? \"\" : pickJoiner(before, text);\n\n const fullText = joiner + text;\n const inserted = insertOperationsAt(\n editor,\n tr,\n pos,\n textToDocumentOperations(fullText),\n mark\n );\n\n const segFrom = pos + measureTextAsOperationsSize(editor, joiner);\n const segTo = inserted.to;\n setSegmentMeta(tr, segmentId, { from: segFrom, to: segTo, revision, joinerLength: joiner.length });\n setAnchorMeta(tr, { kind: \"patch\", end: segTo });\n\n maybeScrollIntoView(tr, editor, segTo);\n editor.view.dispatch(tr);\n knownSegmentIds.add(segmentId);\n }\n };\n\n // ── Debounce réel des partials, par segment, pour réduire les transactions PM. ──\n const pendingPartials = new Map<\n string,\n { text: string; revision: number; timer: ReturnType<typeof setTimeout> }\n >();\n\n const flushPendingPartial = (segmentId: string) => {\n const pending = pendingPartials.get(segmentId);\n if (!pending) return;\n clearTimeout(pending.timer);\n pendingPartials.delete(segmentId);\n _applyPartial(segmentId, pending.text, pending.revision);\n };\n\n const cancelPendingPartial = (segmentId: string) => {\n const pending = pendingPartials.get(segmentId);\n if (!pending) return;\n clearTimeout(pending.timer);\n pendingPartials.delete(segmentId);\n };\n\n const cancelAllPendingPartials = () => {\n for (const { timer } of pendingPartials.values()) {\n clearTimeout(timer);\n }\n pendingPartials.clear();\n };\n\n const schedulePartial = (segmentId: string, text: string, revision: number) => {\n const existing = pendingPartials.get(segmentId);\n if (existing) {\n if (revision < existing.revision) return;\n clearTimeout(existing.timer);\n }\n const timer = setTimeout(() => flushPendingPartial(segmentId), PARTIAL_DEBOUNCE_MS);\n pendingPartials.set(segmentId, { text, revision, timer });\n };\n\n const trimOverlappingPreviewSegments = (\n tr: Transaction,\n state: EphiaTiptapPluginState,\n committedSegmentId: string,\n committedText: string\n ): void => {\n const previewMark = editor.schema.marks.ephiaPreview?.create();\n for (const [otherId, other] of Object.entries(state.segments)) {\n if (otherId === committedSegmentId || other.revision === Number.MAX_SAFE_INTEGER) {\n continue;\n }\n const previewText = textBetweenWithHardBreaks(editor.state.doc, other.from, other.to);\n const overlap = stripLeadingOverlapFromTextWithInfo(committedText, previewText);\n const trimmed = overlap.text;\n if (trimmed === previewText) continue;\n\n let from = tr.mapping.map(other.from);\n let to = tr.mapping.map(other.to);\n if (overlap.partialWord && from > 0 && /\\s/.test(textBetweenWithHardBreaks(tr.doc, from - 1, from))) {\n tr.delete(from - 1, from);\n from -= 1;\n to -= 1;\n }\n if (trimmed) {\n tr.delete(from, to);\n const inserted = insertOperationsAt(\n editor,\n tr,\n from,\n textToDocumentOperations(trimmed),\n previewMark\n );\n setSegmentMeta(tr, otherId, {\n from,\n to: inserted.to,\n revision: other.revision + 1,\n });\n } else {\n tr.delete(from, to);\n setSegmentMeta(tr, otherId, null);\n }\n }\n };\n\n return {\n kind: \"tiptap\",\n\n attach(): void {\n /* Plugin déjà enregistré */\n },\n\n detach(): void {\n editor.view.dom.removeEventListener(\"scroll\", markUserScroll);\n editor.unregisterPlugin(ephiaTiptapKey);\n editor.off(\"update\", syncToStore);\n editor.off(\"selectionUpdate\", syncSelection);\n cancelAllPendingPartials();\n if (syncRaf !== null && typeof cancelAnimationFrame !== \"undefined\") {\n cancelAnimationFrame(syncRaf);\n syncRaf = null;\n }\n // Annuler les timeouts committed en attente\n for (const timeoutId of committedTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n committedTimeouts.clear();\n knownSegmentIds.clear();\n },\n\n // ─── Session lifecycle ──────────────────────────────────────────────────\n beginSession(opts: BeginSessionOptions = {}): SessionAnchor {\n const replaceSelection = opts.replaceSelection ?? true;\n const isFocused = editor.isFocused;\n\n // Si l'éditeur n'a jamais reçu le focus, la sélection est par défaut au\n // début du doc (pos 1) — on retombe alors sur la fin du document pour\n // un comportement type Dragon Medical (insertion à la suite).\n let from = editor.state.selection.from;\n let to = editor.state.selection.to;\n if (!isFocused && from === to) {\n const docSize = editor.state.doc.content.size;\n const resumePos = lastDictationEnd !== null\n ? Math.min(lastDictationEnd, Math.max(1, docSize - 1))\n : Math.max(1, docSize - 1);\n from = resumePos;\n to = resumePos;\n }\n const hadSelection = from !== to;\n const selectionPendingDelete = hadSelection && replaceSelection;\n\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n\n // Ne pas supprimer la sélection ici — la suppression est différée\n // au premier insert (insertPartial / commitFinal) pour que l'user\n // voie encore le texte sélectionné pendant la connexion au serveur.\n\n const anchorValue = {\n initialStart: from,\n initialEnd: to, // conserver la vraie fin de sélection\n hadSelection,\n end: from, // point d'insertion = début de sélection\n selectionPendingDelete,\n };\n\n // Place le curseur PM au début de la sélection (sans la supprimer).\n try {\n const resolved = tr.doc.resolve(from);\n tr.setSelection(TextSelection.near(resolved));\n } catch {\n // Position hors bornes : on laisse PM gérer\n }\n\n setAnchorMeta(tr, { kind: \"set\", value: anchorValue });\n editor.view.dispatch(tr);\n\n // Re-focus l'éditeur pour que la barre clignotante soit visible\n if (!isFocused) {\n try {\n editor.view.focus();\n } catch {\n /* noop */\n }\n }\n\n return toPublicAnchor(anchorValue);\n },\n\n endSession(): void {\n cancelAllPendingPartials();\n const anchor = getPluginState(editor).anchor;\n if (anchor) lastDictationEnd = anchor.end;\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n setAnchorMeta(tr, { kind: \"clear\" });\n // Nettoyer aussi les marks committed en cours d'animation\n const committedMark = editor.schema.marks.ephiaCommitted;\n if (committedMark) {\n tr.removeMark(0, editor.state.doc.content.size, committedMark);\n }\n editor.view.dispatch(tr);\n syncToStore();\n },\n\n getSessionAnchor(): SessionAnchor | null {\n const anchor = getPluginState(editor).anchor;\n return anchor ? toPublicAnchor(anchor) : null;\n },\n\n getCursorRect(): DOMRect | null {\n const anchor = getPluginState(editor).anchor;\n const pos = anchor ? anchor.end : editor.state.selection.from;\n try {\n const coords = editor.view.coordsAtPos(pos);\n // coords = { top, bottom, left, right }\n return new DOMRect(\n coords.left,\n coords.top,\n coords.right - coords.left,\n coords.bottom - coords.top\n );\n } catch {\n const dom = editor.view.dom as HTMLElement;\n return dom.getBoundingClientRect();\n }\n },\n\n getRangeRect(start: number, end: number): DOMRect | null {\n if (end <= start) return null;\n try {\n const a = editor.view.coordsAtPos(start);\n const b = editor.view.coordsAtPos(end);\n const top = Math.min(a.top, b.top);\n const bottom = Math.max(a.bottom, b.bottom);\n const left = Math.min(a.left, b.left);\n const right = Math.max(a.right, b.right);\n return new DOMRect(left, top, Math.max(2, right - left), bottom - top);\n } catch {\n return null;\n }\n },\n\n // ─── Streaming insertion ────────────────────────────────────────────────\n insertPartial(segmentId: string, text: string, revision: number): void {\n schedulePartial(segmentId, text, revision);\n },\n\n commitFinal(segmentId: string, text: string, options?: CommitFinalOptions): void {\n clearCommittedTimeout(segmentId);\n flushPendingPartial(segmentId);\n const state = getPluginState(editor);\n const existing = state.segments[segmentId];\n if (!existing && knownSegmentIds.has(segmentId)) {\n console.warn(\"[ephia:tiptap] known segmentId lost range; refusing append\", {\n segmentId,\n text,\n });\n return;\n }\n\n if (existing) {\n // Commit final = entrée undo : ⌘Z doit pouvoir revenir avant le commit\n const tr = editor.state.tr;\n\n const oldText = textBetweenWithHardBreaks(editor.state.doc, existing.from, existing.to);\n let newEnd = existing.from + measureTextAsOperationsSize(editor, text);\n\n if (oldText !== text) {\n const prefixLen = getCommonPrefix(oldText, text);\n const suffixLen = getCommonSuffix(oldText, text, prefixLen);\n\n if (prefixLen > 0 || suffixLen > 0) {\n const deleteFrom = existing.from + prefixLen;\n const deleteTo = existing.to - suffixLen;\n const insertText = text.slice(prefixLen, text.length - suffixLen);\n if (deleteFrom < deleteTo) tr.delete(deleteFrom, deleteTo);\n if (insertText) {\n insertOperationsAt(\n editor,\n tr,\n deleteFrom,\n textToDocumentOperations(insertText)\n );\n }\n newEnd = existing.from + measureTextAsOperationsSize(editor, text);\n } else {\n tr.delete(existing.from, existing.to);\n const inserted = insertOperationsAt(\n editor,\n tr,\n existing.from,\n textToDocumentOperations(text)\n );\n newEnd = inserted.to;\n }\n }\n\n tr.removeMark(existing.from, newEnd, editor.schema.marks.ephiaPreview);\n\n // Transition visuelle : mark committed pour le fade-out lavande → vert\n const committedMark = editor.schema.marks.ephiaCommitted?.create();\n if (committedMark) {\n tr.addMark(existing.from, newEnd, committedMark);\n }\n\n setSegmentMeta(tr, segmentId, {\n from: existing.from,\n to: newEnd,\n revision: Number.MAX_SAFE_INTEGER,\n joinerLength: existing.joinerLength,\n });\n if (state.anchor) {\n setAnchorMeta(tr, { kind: \"patch\", end: newEnd });\n }\n lastDictationEnd = newEnd;\n trimOverlappingPreviewSegments(tr, state, segmentId, text);\n\n for (const absorbedId of options?.absorbedSegmentIds ?? []) {\n if (absorbedId === segmentId) continue;\n cancelPendingPartial(absorbedId);\n const absorbedSeg = state.segments[absorbedId];\n if (!absorbedSeg) continue;\n const aFrom = tr.mapping.map(absorbedSeg.from - (absorbedSeg.joinerLength ?? 0));\n const aTo = tr.mapping.map(absorbedSeg.to);\n if (aFrom < aTo) tr.delete(aFrom, aTo);\n setSegmentMeta(tr, absorbedId, null);\n }\n\n maybeScrollIntoView(tr, editor, newEnd);\n editor.view.dispatch(tr);\n knownSegmentIds.add(segmentId);\n\n // Retirer la mark committed après l'animation (1.2s + marge)\n if (committedMark) {\n const timeoutId = window.setTimeout(() => {\n committedTimeouts.delete(segmentId);\n const freshState = getPluginState(editor);\n const seg = freshState.segments[segmentId];\n if (seg && editor.schema.marks.ephiaCommitted) {\n const tr2 = editor.state.tr;\n tr2.setMeta(\"addToHistory\", false);\n tr2.setMeta(SKIP_STORE_SYNC_META, true);\n tr2.removeMark(seg.from, seg.to, editor.schema.marks.ephiaCommitted);\n editor.view.dispatch(tr2);\n }\n }, 1300);\n committedTimeouts.set(segmentId, timeoutId);\n }\n } else {\n // Pas de partial préalable → insérer à l'anchor (avec joiner) ou au curseur\n const tr = editor.state.tr;\n\n // Suppression lazy de la sélection (même logique que dans _applyPartial)\n let pos = state.anchor ? state.anchor.end : editor.state.selection.from;\n if (\n state.anchor?.selectionPendingDelete &&\n state.anchor.initialEnd > state.anchor.initialStart\n ) {\n tr.delete(state.anchor.initialStart, state.anchor.initialEnd);\n pos = state.anchor.initialStart;\n setAnchorMeta(tr, {\n kind: \"set\",\n value: {\n ...state.anchor,\n selectionPendingDelete: false,\n initialEnd: state.anchor.initialStart,\n end: state.anchor.initialStart,\n },\n });\n }\n\n let joiner = \"\";\n if (state.anchor) {\n const before = textBetweenWithHardBreaks(editor.state.doc, Math.max(0, pos - 500), pos);\n const overlap = stripLeadingOverlapFromTextWithInfo(before, text);\n text = overlap.text;\n if (!text) return;\n joiner = overlap.partialWord ? \"\" : pickJoiner(before, text);\n }\n\n const fullText = joiner + text;\n const inserted = insertOperationsAt(\n editor,\n tr,\n pos,\n textToDocumentOperations(fullText)\n );\n\n const segFrom = pos + measureTextAsOperationsSize(editor, joiner);\n const segTo = inserted.to;\n setSegmentMeta(tr, segmentId, {\n from: segFrom,\n to: segTo,\n revision: Number.MAX_SAFE_INTEGER,\n joinerLength: joiner.length,\n });\n\n if (state.anchor) {\n setAnchorMeta(tr, { kind: \"patch\", end: segTo });\n }\n lastDictationEnd = segTo;\n\n trimOverlappingPreviewSegments(tr, state, segmentId, text);\n\n for (const absorbedId of options?.absorbedSegmentIds ?? []) {\n if (absorbedId === segmentId) continue;\n cancelPendingPartial(absorbedId);\n const absorbedSeg = state.segments[absorbedId];\n if (!absorbedSeg) continue;\n const aFrom = tr.mapping.map(absorbedSeg.from - (absorbedSeg.joinerLength ?? 0));\n const aTo = tr.mapping.map(absorbedSeg.to);\n if (aFrom < aTo) tr.delete(aFrom, aTo);\n setSegmentMeta(tr, absorbedId, null);\n }\n\n maybeScrollIntoView(tr, editor, segTo);\n editor.view.dispatch(tr);\n knownSegmentIds.add(segmentId);\n }\n },\n\n clearPartial(segmentId: string): void {\n cancelPendingPartial(segmentId);\n const state = getPluginState(editor);\n const existing = state.segments[segmentId];\n\n if (existing) {\n if (existing.revision === Number.MAX_SAFE_INTEGER) {\n return;\n }\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n tr.delete(existing.from - (existing.joinerLength ?? 0), existing.to);\n setSegmentMeta(tr, segmentId, null);\n editor.view.dispatch(tr);\n }\n },\n\n clearAll(): void {\n cancelAllPendingPartials();\n const state = getPluginState(editor);\n const previewEntries = Object.entries(state.segments).filter(\n ([, seg]) => seg.revision !== Number.MAX_SAFE_INTEGER\n );\n if (previewEntries.length === 0) return;\n\n const tr = editor.state.tr;\n tr.setMeta(\"addToHistory\", false);\n tr.setMeta(SKIP_STORE_SYNC_META, true);\n for (const [segmentId, seg] of previewEntries.sort(\n (a, b) => b[1].from - a[1].from\n )) {\n const from = tr.mapping.map(seg.from - (seg.joinerLength ?? 0));\n const to = tr.mapping.map(seg.to);\n if (from < to) tr.delete(from, to);\n setSegmentMeta(tr, segmentId, null);\n }\n editor.view.dispatch(tr);\n },\n\n getText(): string {\n return getTextFromDoc(editor.state.doc);\n },\n\n getSelection() {\n const { from, to } = editor.state.selection;\n if (from === to) return null;\n return {\n text: textBetweenWithHardBreaks(editor.state.doc, from, to),\n range: { start: from, end: to },\n };\n },\n\n getCursorOffset(): number | null {\n return editor.state.selection.from;\n },\n\n applyOperation(operation: DocumentOperation): void {\n switch (operation.type) {\n case \"replace\": {\n let start: number;\n let end: number;\n\n if (operation.range) {\n start = operation.range.start;\n end = operation.range.end;\n } else if (operation.targetText) {\n const text = editor.getText();\n const idx = text.indexOf(operation.targetText);\n if (idx === -1) {\n console.warn(\n \"[tiptapBinding] replace ignored: targetText not found\"\n );\n return;\n }\n start = idx;\n end = idx + operation.targetText.length;\n } else {\n console.warn(\n \"[tiptapBinding] replace ignored: missing range or targetText\"\n );\n return;\n }\n\n editor\n .chain()\n .deleteRange({ from: start, to: end })\n .insertContentAt(start, [{ type: \"text\", text: operation.replacement }])\n .run();\n break;\n }\n\n case \"insert\":\n case \"insert_text\":\n case \"line_break\":\n case \"paragraph_break\": {\n const pos = resolveInsertPosition(editor, operation.position);\n const tr = editor.state.tr;\n insertOperationsAt(editor, tr, pos, [operation]);\n editor.view.dispatch(tr);\n break;\n }\n\n case \"delete\": {\n let start: number;\n let end: number;\n\n if (operation.range) {\n start = operation.range.start;\n end = operation.range.end;\n } else if (operation.targetText) {\n const text = editor.getText();\n const idx = text.indexOf(operation.targetText);\n if (idx === -1) {\n console.warn(\n \"[tiptapBinding] delete ignored: targetText not found\"\n );\n return;\n }\n start = idx;\n end = idx + operation.targetText.length;\n } else {\n console.warn(\n \"[tiptapBinding] delete ignored: missing range or targetText\"\n );\n return;\n }\n\n editor.chain().deleteRange({ from: start, to: end }).run();\n break;\n }\n\n case \"replace_all\": {\n editor.chain().setContent([{ type: \"paragraph\", content: [{ type: \"text\", text: operation.replacement }] }]).run();\n break;\n }\n\n default:\n console.warn(\n \"[tiptapBinding] Operation not supported:\",\n operation.type\n );\n }\n },\n\n applyOperations(operations: DocumentOperation[]): void {\n if (operations.length === 0) return;\n const firstInsertOperation = operations.find(\n (operation) =>\n operation.type === \"insert\" ||\n operation.type === \"insert_text\" ||\n operation.type === \"line_break\" ||\n operation.type === \"paragraph_break\"\n );\n if (!firstInsertOperation) {\n for (const operation of operations) {\n console.warn(\n \"[tiptapBinding] Operation not supported in applyOperations:\",\n operation.type\n );\n }\n return;\n }\n\n const tr = editor.state.tr;\n const pos = resolveInsertPosition(editor, firstInsertOperation.position);\n insertOperationsAt(editor, tr, pos, operations);\n editor.view.dispatch(tr);\n },\n };\n}\n","import { create } from \"zustand\";\nimport { subscribeWithSelector } from \"zustand/middleware\";\n\nexport interface DocumentSection {\n id: string;\n title: string;\n text: string;\n level: number;\n start: number;\n end: number;\n}\n\nexport interface DocumentStoreState {\n content: string;\n segments: DocumentSection[];\n selectedText: string | null;\n cursorSection: string | null;\n\n syncFromEditor: (content: string, segments: DocumentSection[]) => void;\n setSelection: (text: string | null, section: string | null) => void;\n}\n\nexport const documentStore = create<DocumentStoreState>()(\n subscribeWithSelector((set) => ({\n content: \"\",\n segments: [],\n selectedText: null,\n cursorSection: null,\n syncFromEditor: (content, segments) => set({ content, segments }),\n setSelection: (text, section) => set({ selectedText: text, cursorSection: section }),\n }))\n);\n","const TOKEN_RE = /\\S+/g;\nconst PUNCT_EDGE_RE = /^[.,;:!?\"'«»“”‘’()[\\]{}]+|[.,;:!?\"'«»“”‘’()[\\]{}]+$/g;\nconst ELISION_RE = /^(?:qu['’]|[dlsnmtcj]['’])/i;\n\ninterface Token {\n raw: string;\n norm: string;\n start: number;\n end: number;\n}\n\nfunction normalizeToken(token: string): string {\n return token\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(PUNCT_EDGE_RE, \"\")\n .replace(ELISION_RE, \"\");\n}\n\nfunction tokenize(text: string): Token[] {\n const out: Token[] = [];\n for (const match of text.matchAll(TOKEN_RE)) {\n const raw = match[0];\n const start = match.index ?? 0;\n const norm = normalizeToken(raw);\n if (!norm) continue;\n out.push({ raw, norm, start, end: start + raw.length });\n }\n return out;\n}\n\nfunction prefixCharsForNormalizedPrefix(raw: string, normalizedPrefix: string): number {\n if (!normalizedPrefix) return 0;\n for (let i = 1; i <= raw.length; i++) {\n const norm = normalizeToken(raw.slice(0, i));\n if (norm.length >= normalizedPrefix.length && norm.startsWith(normalizedPrefix)) {\n return i;\n }\n }\n return 0;\n}\n\n/**\n * Removes the prefix of `incoming` already covered by the suffix of `before`.\n * Handles case/punctuation/diacritics and the common streaming case where the\n * previous text ends with a partial word.\n */\nexport function stripLeadingOverlapFromText(\n before: string,\n incoming: string,\n maxTokens = 24\n): string {\n return stripLeadingOverlapFromTextWithInfo(before, incoming, maxTokens).text;\n}\n\nexport function stripLeadingOverlapFromTextWithInfo(\n before: string,\n incoming: string,\n maxTokens = 24\n): { text: string; partialWord: boolean; overlapTokens: number } {\n const left = tokenize(before);\n const right = tokenize(incoming);\n if (!left.length || !right.length) {\n return { text: incoming, partialWord: false, overlapTokens: 0 };\n }\n\n const limit = Math.min(left.length, right.length, maxTokens);\n for (let size = limit; size > 0; size--) {\n const leftSlice = left.slice(left.length - size);\n const rightSlice = right.slice(0, size);\n let partialLast = false;\n let matches = true;\n\n for (let i = 0; i < size; i++) {\n const l = leftSlice[i].norm;\n const r = rightSlice[i].norm;\n if (l === r) continue;\n if (i === size - 1 && l.length >= 3 && r.startsWith(l)) {\n partialLast = true;\n continue;\n }\n matches = false;\n break;\n }\n if (!matches) continue;\n\n const lastRight = rightSlice[rightSlice.length - 1];\n let cut = lastRight.end;\n if (partialLast) {\n cut = lastRight.start + prefixCharsForNormalizedPrefix(lastRight.raw, leftSlice[size - 1].norm);\n }\n return {\n text: incoming.slice(cut).replace(/^[ \\t]+/, \"\"),\n partialWord: partialLast,\n overlapTokens: size,\n };\n }\n\n return { text: incoming, partialWord: false, overlapTokens: 0 };\n}\n"],"mappings":"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","names":["node"]}
@@ -0,0 +1,383 @@
1
+ import { T as Transport, b as TransportConnectParams, a as TransportState } from '../Transport-zdeA4Pou.js';
2
+ export { b as EphiaAudioClientOptions, c as EphiaClientState, d as EphiaClientStatus, e as EphiaSessionStatus, a as EphiaStartOptions, f as canTransition, i as initialClientState, t as transitionSessionStatus } from '../client-options-Uo6jXO8k.js';
3
+ import { LocalTrackPublication } from 'livekit-client';
4
+ import { EphiaClientMessage, EphiaAudioEvent, EphiaServerEvent, TranscriptSafetyMeta, TranscriptLayoutMeta, ResetReason, EditorContext, DocumentOperation } from 'ephia-protocol';
5
+ import { b as EphiaSdkError } from '../audio-state-kZ3KSvux.js';
6
+ import { E as EphiaBinding } from '../EphiaBinding-BvRmlqqC.js';
7
+ import { T as TargetBinding } from '../TargetBinding-BKGQwUMc.js';
8
+ export { a as EphiaConnectionQuality, E as EphiaConnectionState, b as EphiaConnectionStatus, i as initialConnectionState, m as mapTransportStateToConnectionState } from '../connection-state-Bk33YprE.js';
9
+ import '../session-APaXR48R.js';
10
+
11
+ /**
12
+ * Transport LiveKit — implémentation du Transport Ephia pour l'agent ephia-transcribe-agent.
13
+ *
14
+ * Wire V2 :
15
+ * • SDK → backend : ephia.client.control
16
+ * • backend → SDK : ephia.server.event
17
+ */
18
+
19
+ declare class LiveKitTransport implements Transport {
20
+ private room;
21
+ private _eventCallbacks;
22
+ private _serverEventCallbacks;
23
+ private _stateCallbacks;
24
+ private _errorCallbacks;
25
+ private _state;
26
+ private _localAudioTrack;
27
+ private _krispProcessor;
28
+ private _sessionId;
29
+ private _clientSeq;
30
+ connect(params: TransportConnectParams): Promise<void>;
31
+ prepareConnection(url: string, token?: string): Promise<void>;
32
+ disconnect(_reason?: string): Promise<void>;
33
+ sendMessage(message: EphiaClientMessage): Promise<void>;
34
+ onEvent(callback: (event: EphiaAudioEvent) => void): () => void;
35
+ onServerEvent(callback: (event: EphiaServerEvent) => void): () => void;
36
+ onTransportState(callback: (state: TransportState) => void): () => void;
37
+ onError(callback: (error: EphiaSdkError) => void): () => void;
38
+ getState(): TransportState;
39
+ getLocalAudioPublication(): LocalTrackPublication | null;
40
+ publishAudio(track: MediaStreamTrack, options?: {
41
+ enableNoiseFilter?: boolean;
42
+ }): Promise<void>;
43
+ unpublishAudio(): Promise<void>;
44
+ performRpc(method: string, payload: unknown, timeout?: number): Promise<string>;
45
+ switchActiveDevice(kind: MediaDeviceKind, deviceId: string): Promise<boolean>;
46
+ private _toWireClientEvent;
47
+ private _toWireClientPayload;
48
+ private _attachRoomListeners;
49
+ private _handleWireServerEvent;
50
+ private _roomOptions;
51
+ private _setState;
52
+ private _emitError;
53
+ }
54
+
55
+ /**
56
+ * Transport mock pour tests, storybook et sandbox sans backend.
57
+ */
58
+
59
+ type MockScriptStep = {
60
+ type: string;
61
+ delayMs: number;
62
+ payload?: Record<string, unknown>;
63
+ };
64
+ type MockTransportOptions = {
65
+ script?: MockScriptStep[];
66
+ autoEmitReady?: boolean;
67
+ latencyMs?: number;
68
+ };
69
+ declare class MockTransport implements Transport {
70
+ private _state;
71
+ private _eventCallbacks;
72
+ private _serverEventCallbacks;
73
+ private _stateCallbacks;
74
+ private _errorCallbacks;
75
+ private _running;
76
+ private _scriptTimer;
77
+ private _options;
78
+ constructor(options?: MockTransportOptions);
79
+ connect(params: TransportConnectParams): Promise<void>;
80
+ disconnect(_reason?: string): Promise<void>;
81
+ publishAudio(_track: MediaStreamTrack): Promise<void>;
82
+ unpublishAudio(): Promise<void>;
83
+ sendMessage(message: EphiaClientMessage): Promise<void>;
84
+ onEvent(callback: (event: EphiaAudioEvent) => void): () => void;
85
+ onServerEvent(callback: (event: EphiaServerEvent) => void): () => void;
86
+ onTransportState(callback: (state: TransportState) => void): () => void;
87
+ onError(callback: (error: EphiaSdkError) => void): () => void;
88
+ getState(): TransportState;
89
+ prepareConnection(_url: string, _token?: string): Promise<void>;
90
+ performRpc(method: string, payload: unknown, _timeout?: number): Promise<string>;
91
+ /** Émet manuellement un event legacy (utile pour les tests). */
92
+ simulateEvent(event: EphiaAudioEvent): void;
93
+ /** Émet manuellement un event serveur V2 (utile pour les tests de TranscriptApplier). */
94
+ simulateServerEvent(event: EphiaServerEvent): void;
95
+ /** Force manuellement un état transport (utile pour les tests). */
96
+ simulateState(state: Partial<TransportState>): void;
97
+ private _setState;
98
+ private _emit;
99
+ private _delay;
100
+ private _runScript;
101
+ }
102
+
103
+ interface TranscriptSegment {
104
+ segmentId: string;
105
+ segmentSeq: number;
106
+ partialText: string | null;
107
+ finalText: string | null;
108
+ committedText: string | null;
109
+ isFinalized: boolean;
110
+ isCommitted: boolean;
111
+ revision: number;
112
+ stable: boolean;
113
+ source: string | null;
114
+ mergedWith: string | string[] | null;
115
+ }
116
+ interface TranscriptState {
117
+ segments: TranscriptSegment[];
118
+ currentSegmentId: string | null;
119
+ documentText: string;
120
+ }
121
+ declare const initialTranscriptState: TranscriptState;
122
+
123
+ declare function eventReducer(state: TranscriptState, event: EphiaAudioEvent): TranscriptState;
124
+
125
+ type LiveTranscriptStatus = "idle" | "connecting" | "recording" | "finalizing" | "ended" | "error";
126
+ type LiveTranscriptChunkStatus = "opened" | "processing" | "preview" | "done" | "error";
127
+ type LiveTranscriptChunk = {
128
+ segmentId: string;
129
+ segmentSeq: number;
130
+ status: LiveTranscriptChunkStatus;
131
+ text: string;
132
+ previewText?: string;
133
+ previewContinuesPreviousWord?: boolean;
134
+ stablePrefix?: string;
135
+ previewDelta?: string;
136
+ finalizedText?: string;
137
+ committedText?: string;
138
+ revision: number;
139
+ startedAtMs?: number;
140
+ audioClosedAtMs?: number;
141
+ endedAtMs?: number;
142
+ durationMs?: number;
143
+ provider?: string;
144
+ error?: string;
145
+ uncertain?: boolean;
146
+ reason?: string;
147
+ reasonCodes?: string[];
148
+ parseFallback?: boolean;
149
+ source?: string;
150
+ mergedWith?: string | string[];
151
+ effectiveSource?: string;
152
+ correctionPending?: boolean;
153
+ correctionAccepted?: boolean;
154
+ correctionRejectedReason?: string | null;
155
+ commandsApplied?: string[];
156
+ normalizationsApplied?: string[];
157
+ safety?: TranscriptSafetyMeta;
158
+ layout?: TranscriptLayoutMeta;
159
+ rawStt?: string;
160
+ realtimeText?: string;
161
+ hintSentToSmall?: string | null;
162
+ fallbackText?: string | null;
163
+ smallRawText?: string;
164
+ correctionOutcome?: string;
165
+ debug?: Record<string, unknown>;
166
+ };
167
+ type LiveTranscriptDiagnostics = {
168
+ mode?: "time" | "vad";
169
+ eventsCount: number;
170
+ chunksCount: number;
171
+ processingChunks: number;
172
+ doneChunks: number;
173
+ errorChunks: number;
174
+ seqValid: boolean;
175
+ lastProvider?: string;
176
+ lastLatencyMs?: number;
177
+ averageLatencyMs?: number;
178
+ finalTextLength: number;
179
+ previewActive: boolean;
180
+ uncertainChunks?: number;
181
+ parseFallbackChunks?: number;
182
+ contextChars?: number;
183
+ };
184
+ type LiveTranscriptState = {
185
+ sessionId: string | null;
186
+ status: LiveTranscriptStatus;
187
+ events: EphiaAudioEvent[];
188
+ chunks: Record<string, LiveTranscriptChunk>;
189
+ orderedSegmentIds: string[];
190
+ finalText: string;
191
+ previewText: string;
192
+ displayText: string;
193
+ documentText: string;
194
+ lastEvent: EphiaAudioEvent | null;
195
+ diagnostics: LiveTranscriptDiagnostics;
196
+ error: {
197
+ code: string;
198
+ message: string;
199
+ } | null;
200
+ spinnerTick: number;
201
+ finalizedBeforeReset?: Record<string, true>;
202
+ };
203
+ declare const initialLiveTranscriptState: LiveTranscriptState;
204
+
205
+ type LiveTranscriptAction = {
206
+ type: "event";
207
+ event: EphiaAudioEvent;
208
+ } | {
209
+ type: "reset";
210
+ } | {
211
+ type: "remove_segments";
212
+ segmentIds: string[];
213
+ } | {
214
+ type: "client_error";
215
+ error: {
216
+ code: string;
217
+ message: string;
218
+ };
219
+ } | {
220
+ type: "tick";
221
+ };
222
+ declare function liveTranscriptReducer(state: LiveTranscriptState, action: LiveTranscriptAction): LiveTranscriptState;
223
+
224
+ /**
225
+ * Assemble les chunks en texte cohérent.
226
+ *
227
+ * Règles :
228
+ * - tri par segmentSeq
229
+ * - pas de double espace
230
+ * - espace après ponctuation si manquant
231
+ * - ignorer les chunks error sans texte
232
+ */
233
+ declare function assembleTranscript(chunks: LiveTranscriptChunk[]): string;
234
+ /**
235
+ * Assemble les chunks commités en document texte.
236
+ *
237
+ * Règles :
238
+ * - tri par segmentSeq
239
+ * - utilise committedText ou text si disponible
240
+ * - ignore les chunks fusionnés (mergedWith présent)
241
+ */
242
+ declare function assembleCommittedDocument(chunks: Record<string, LiveTranscriptChunk>, orderedSegmentIds: string[]): string;
243
+ declare function buildDisplayText(finalText: string, previewText: string, previewContinuesPreviousWord?: boolean): string;
244
+
245
+ type WireClientMessage = EphiaClientMessage | {
246
+ type: string;
247
+ payload?: Record<string, unknown>;
248
+ };
249
+ type ManagedTarget = {
250
+ id: string;
251
+ binding: EphiaBinding | null;
252
+ element?: HTMLElement;
253
+ mode?: string;
254
+ };
255
+ type TargetManagerOptions = {
256
+ sendMessage: (message: WireClientMessage) => Promise<void>;
257
+ flushDelayMs?: number;
258
+ onActiveTargetChange?: (targetId: string | null) => void;
259
+ onTargetRegistered?: (targetId: string) => void;
260
+ };
261
+ declare class TargetManager {
262
+ private readonly targets;
263
+ private readonly sendMessage;
264
+ private readonly flushDelayMs;
265
+ private readonly onActiveTargetChange?;
266
+ private readonly onTargetRegistered?;
267
+ private activeTargetId;
268
+ private flushTimers;
269
+ constructor(options: TargetManagerOptions);
270
+ /**
271
+ * Register a target and return its registration token.
272
+ * Pass the token to unregister() to prevent stale cleanups from accidentally
273
+ * removing a newer registration with the same id (React StrictMode / remounts).
274
+ */
275
+ register(target: ManagedTarget): symbol;
276
+ /**
277
+ * Unregister a target.
278
+ * When `token` is provided, only unregisters if the stored token matches
279
+ * (prevents a stale cleanup from removing a newer registration).
280
+ * Returns true if the target was removed, false if skipped.
281
+ */
282
+ unregister(targetId: string, token?: symbol): boolean;
283
+ get(targetId: string): ManagedTarget | undefined;
284
+ getActiveTargetId(): string | null;
285
+ getActiveTarget(): ManagedTarget | undefined;
286
+ setActive(targetId: string): void;
287
+ syncActiveTarget(): void;
288
+ /** Resync native bindings from current DOM before dictation (manual edits between sessions). */
289
+ prepareForDictation(targetId?: string | null): void;
290
+ flushEditorContext(targetId?: string | null): void;
291
+ scheduleEditorContextFlush(targetId?: string | null): void;
292
+ reset(scope: "global" | "target", targetId?: string | null, reason?: ResetReason): void;
293
+ dispose(): void;
294
+ private firstWritableTargetId;
295
+ private clearFlush;
296
+ private send;
297
+ }
298
+
299
+ type TranscriptApplierOptions = {
300
+ targetManager: Pick<TargetManager, "get" | "getActiveTarget" | "getActiveTargetId">;
301
+ warn?: (message: string, details?: Record<string, unknown>) => void;
302
+ debug?: (message: string, details?: Record<string, unknown>) => void;
303
+ };
304
+ /**
305
+ * Applique les events serveur V2 aux bindings éditeurs enregistrés.
306
+ *
307
+ * Règles de stricte V2 :
308
+ * - targetId obligatoire sur segment.operation
309
+ * - eventId déjà vu → ignore
310
+ * - segmentRevision inférieure ou égale à la dernière appliquée → ignore
311
+ * - upsert/absorb atomique (upsert puis remove absorbed)
312
+ * - segment.preview ignoré si un canonical a déjà été appliqué pour ce segment
313
+ * - event/revision non marqués comme vus si le binding n'est pas encore disponible
314
+ */
315
+ declare class TranscriptApplier {
316
+ private readonly targetManager;
317
+ private readonly warn;
318
+ private readonly debug;
319
+ /** eventId → déjà traité (idempotence). */
320
+ private readonly seenEventIds;
321
+ private readonly seenEventQueue;
322
+ /** segmentId → dernière segmentRevision appliquée. */
323
+ private readonly latestRevisionBySegment;
324
+ /** segmentId → targetId (cache défensif). */
325
+ private readonly segmentTargetMap;
326
+ /** segmentIds pour lesquels un canonical a été appliqué avec succès. */
327
+ private readonly canonicalSegments;
328
+ /** Events en attente de montage de leur cible (targetId → events[]). */
329
+ private readonly pendingEvents;
330
+ constructor(options: TranscriptApplierOptions);
331
+ handleEvent(event: EphiaServerEvent): void;
332
+ /**
333
+ * Rejoue les events en attente pour une cible donnée.
334
+ * À appeler après l'enregistrement d'une nouvelle target.
335
+ */
336
+ flushPendingEvents(targetId: string): void;
337
+ private applyOperation;
338
+ private shouldApplySegmentRevision;
339
+ private resolveBinding;
340
+ private _markEventSeen;
341
+ private asEphiaBinding;
342
+ private _enqueuePendingEvent;
343
+ }
344
+
345
+ type EditorContextCollectorOptions = {
346
+ maxContextChars?: number;
347
+ };
348
+ type EditorContextTarget = {
349
+ id: string;
350
+ binding: Pick<TargetBinding, "getText" | "getSelection" | "getCursorOffset"> | null;
351
+ };
352
+ declare class EditorContextCollector {
353
+ private readonly maxContextChars;
354
+ constructor(options?: EditorContextCollectorOptions);
355
+ collect(target: EditorContextTarget): EditorContext;
356
+ private resolveCursorOffset;
357
+ private resolveInsertionMode;
358
+ }
359
+
360
+ /**
361
+ * P3 (post-P0 dictée) — conversion texte ↔ DocumentOperation[].
362
+ *
363
+ * Découpe un texte contenant des \n / \n\n structurels (déjà décidés par le
364
+ * backend — cf. merge_internal_layout côté ephia_transcribe_agent) en une
365
+ * séquence d'opérations neutres, consommable aussi bien par un binding riche
366
+ * (TipTap, cf. P4) qu'un consommateur plain-text/headless (documentOperationsToPlainText).
367
+ */
368
+
369
+ interface TextToDocumentOperationsOptions {
370
+ position?: number | "cursor" | "start" | "end";
371
+ }
372
+ /**
373
+ * \n → LineBreakOperation
374
+ * \n\n (ou plus) → ParagraphBreakOperation
375
+ * reste → InsertTextOperation
376
+ */
377
+ declare function textToDocumentOperations(text: string, options?: TextToDocumentOperationsOptions): DocumentOperation[];
378
+ /** Fonction inverse — round-trip sans perte avec textToDocumentOperations(),
379
+ * indispensable pour les consommateurs plain-text/headless qui n'ont pas de
380
+ * binding riche capable d'interpréter line_break/paragraph_break nativement. */
381
+ declare function documentOperationsToPlainText(operations: DocumentOperation[]): string;
382
+
383
+ export { EditorContextCollector, type EditorContextCollectorOptions, type EditorContextTarget, LiveKitTransport, type LiveTranscriptAction, type LiveTranscriptChunk, type LiveTranscriptDiagnostics, type LiveTranscriptState, type ManagedTarget, MockTransport, TargetManager, type TargetManagerOptions, type TextToDocumentOperationsOptions, TranscriptApplier, type TranscriptApplierOptions, type TranscriptSegment, type TranscriptState, Transport, TransportConnectParams, TransportState, assembleCommittedDocument, assembleTranscript, buildDisplayText, documentOperationsToPlainText, eventReducer, initialLiveTranscriptState, initialTranscriptState, liveTranscriptReducer, textToDocumentOperations };