@ecsplico/editor 2.1.2 → 3.1.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 (706) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/README.md +39 -156
  3. package/dist/adapters/comments/drizzle.d.ts +1 -1
  4. package/dist/adapters/comments/record.d.ts +15 -0
  5. package/dist/adapters/comments/{client.js → rest-model.js} +3 -3
  6. package/dist/adapters/comments/rest-model.js.map +1 -0
  7. package/dist/adapters/comments/server.d.ts +2 -1
  8. package/dist/adapters/comments/server.js +2 -1
  9. package/dist/adapters/comments/server.js.map +1 -1
  10. package/dist/adapters/comments/types.d.ts +2 -2
  11. package/dist/adapters/document-state.d.ts +23 -0
  12. package/dist/adapters/document-state.js +66 -0
  13. package/dist/adapters/document-state.js.map +1 -0
  14. package/dist/adapters/documents.js +14 -11
  15. package/dist/adapters/documents.js.map +1 -1
  16. package/dist/adapters/drizzle.d.ts +2 -2
  17. package/dist/adapters/drizzle.js +97 -88
  18. package/dist/adapters/drizzle.js.map +1 -1
  19. package/dist/adapters/forge.d.ts +12 -7
  20. package/dist/adapters/forge.js +38 -45
  21. package/dist/adapters/forge.js.map +1 -1
  22. package/dist/adapters/http.d.ts +20 -0
  23. package/dist/adapters/http.js +68 -0
  24. package/dist/adapters/http.js.map +1 -0
  25. package/dist/adapters/index.d.ts +5 -5
  26. package/dist/adapters/json.d.ts +3 -3
  27. package/dist/adapters/json.js +58 -49
  28. package/dist/adapters/json.js.map +1 -1
  29. package/dist/adapters/memory.d.ts +21 -0
  30. package/dist/adapters/memory.js +53 -0
  31. package/dist/adapters/memory.js.map +1 -0
  32. package/dist/adapters/nitro.d.ts +27 -20
  33. package/dist/adapters/nitro.js +59 -163
  34. package/dist/adapters/nitro.js.map +1 -1
  35. package/dist/adapters/react.d.ts +55 -0
  36. package/dist/adapters/react.js +209 -0
  37. package/dist/adapters/react.js.map +1 -0
  38. package/dist/adapters/shared.d.ts +15 -21
  39. package/dist/adapters/shared.js +10 -8
  40. package/dist/adapters/shared.js.map +1 -1
  41. package/dist/adapters/vue.d.ts +15 -30
  42. package/dist/adapters/vue.js +36 -87
  43. package/dist/adapters/vue.js.map +1 -1
  44. package/dist/code/CodeSurface.vue.d.ts +2 -2
  45. package/dist/config/index.d.ts +105 -0
  46. package/dist/config/index.js +95 -0
  47. package/dist/config/index.js.map +1 -0
  48. package/dist/core/abilities.js +1 -0
  49. package/dist/core/abilities.js.map +1 -1
  50. package/dist/{adapters/routes.js → core/api-routes.js} +5 -3
  51. package/dist/core/api-routes.js.map +1 -0
  52. package/dist/core/backend-capability.d.ts +7 -0
  53. package/dist/core/backend-capability.js +17 -0
  54. package/dist/core/backend-capability.js.map +1 -0
  55. package/dist/core/backend-types.d.ts +245 -0
  56. package/dist/core/backend-types.js +17 -0
  57. package/dist/core/backend-types.js.map +1 -0
  58. package/dist/core/backend.d.ts +108 -0
  59. package/dist/core/backend.js +14 -0
  60. package/dist/core/backend.js.map +1 -0
  61. package/dist/core/block-convert.d.ts +7 -5
  62. package/dist/core/block-convert.js +28 -5
  63. package/dist/core/block-convert.js.map +1 -1
  64. package/dist/core/command-types.d.ts +96 -0
  65. package/dist/core/commands.d.ts +40 -0
  66. package/dist/core/commands.js +151 -0
  67. package/dist/core/commands.js.map +1 -0
  68. package/dist/core/context.d.ts +17 -20
  69. package/dist/core/context.js +24 -21
  70. package/dist/core/context.js.map +1 -1
  71. package/dist/core/core-feature.d.ts +22 -7
  72. package/dist/core/core-feature.js +51 -18
  73. package/dist/core/core-feature.js.map +1 -1
  74. package/dist/core/define-editor.d.ts +4 -3
  75. package/dist/core/define-editor.js +14 -7
  76. package/dist/core/define-editor.js.map +1 -1
  77. package/dist/core/document-markdown.js +12 -3
  78. package/dist/core/document-markdown.js.map +1 -1
  79. package/dist/core/editor-ui.d.ts +8 -1
  80. package/dist/core/editor-ui.js +9 -8
  81. package/dist/core/editor-ui.js.map +1 -1
  82. package/dist/core/extension-points.d.ts +3 -15
  83. package/dist/core/feature-context.d.ts +8 -7
  84. package/dist/core/feature-context.js +12 -11
  85. package/dist/core/feature-context.js.map +1 -1
  86. package/dist/core/feature.d.ts +41 -19
  87. package/dist/core/feature.js.map +1 -1
  88. package/dist/core/fences.d.ts +2 -1
  89. package/dist/core/fences.js +3 -2
  90. package/dist/core/fences.js.map +1 -1
  91. package/dist/core/fetch-error.d.ts +1 -1
  92. package/dist/core/fetch-error.js +3 -1
  93. package/dist/core/fetch-error.js.map +1 -1
  94. package/dist/core/hash.d.ts +5 -0
  95. package/dist/core/hash.js +11 -0
  96. package/dist/core/hash.js.map +1 -0
  97. package/dist/core/http-backend.d.ts +11 -0
  98. package/dist/core/http-backend.js +195 -0
  99. package/dist/core/http-backend.js.map +1 -0
  100. package/dist/core/index.d.ts +15 -9
  101. package/dist/core/index.js +13 -9
  102. package/dist/core/palette.d.ts +48 -0
  103. package/dist/core/palette.js +95 -0
  104. package/dist/core/palette.js.map +1 -0
  105. package/dist/core/schema/abilities.d.ts +2 -1
  106. package/dist/core/schema/abilities.js +5 -1
  107. package/dist/core/schema/abilities.js.map +1 -1
  108. package/dist/core/schema/index.d.ts +11 -5
  109. package/dist/core/schema/index.js +13 -8
  110. package/dist/core/schema/index.js.map +1 -1
  111. package/dist/core/schema/mark-flags.d.ts +8 -1
  112. package/dist/core/schema/of-features.d.ts +13 -0
  113. package/dist/core/schema/of-features.js +14 -0
  114. package/dist/core/schema/of-features.js.map +1 -0
  115. package/dist/core/slash.d.ts +8 -6
  116. package/dist/core/slash.js +24 -16
  117. package/dist/core/slash.js.map +1 -1
  118. package/dist/core/surface-session.js +15 -5
  119. package/dist/core/surface-session.js.map +1 -1
  120. package/dist/core/toolbar-items.d.ts +10 -4
  121. package/dist/core/toolbar-items.js +269 -195
  122. package/dist/core/toolbar-items.js.map +1 -1
  123. package/dist/core/transaction-guards.d.ts +21 -0
  124. package/dist/core/transaction-guards.js +49 -0
  125. package/dist/core/transaction-guards.js.map +1 -0
  126. package/dist/core/util.js +11 -0
  127. package/dist/core/util.js.map +1 -0
  128. package/dist/document/DocumentSurface.d.ts +1 -1
  129. package/dist/document/DocumentSurface.vue.d.ts +2 -2
  130. package/dist/features/ai/agent-op-review.d.ts +1 -1
  131. package/dist/features/ai/agent-op-review.js +1 -1
  132. package/dist/features/ai/agent-ops.js +1 -1
  133. package/dist/features/ai/ai-apply.d.ts +2 -2
  134. package/dist/features/ai/ai-apply.js +1 -1
  135. package/dist/features/ai/ai-review.js +1 -1
  136. package/dist/features/ai/ai-sse.d.ts +6 -6
  137. package/dist/features/ai/ai-sse.js +20 -63
  138. package/dist/features/ai/ai-sse.js.map +1 -1
  139. package/dist/features/ai/assistant.d.ts +8 -7
  140. package/dist/features/ai/assistant.js +3 -3
  141. package/dist/features/ai/assistant.js.map +1 -1
  142. package/dist/features/ai/block-actions.d.ts +3 -2
  143. package/dist/features/ai/block-actions.js +3 -2
  144. package/dist/features/ai/block-actions.js.map +1 -1
  145. package/dist/features/ai/components/AiSelectionMenu.js +1 -1
  146. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  147. package/dist/features/ai/components/DocumentChatOps.vue.d.ts +1 -1
  148. package/dist/features/ai/components/SettingsModelProfiles.js +8 -8
  149. package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
  150. package/dist/features/ai/document-chat-model.d.ts +8 -7
  151. package/dist/features/ai/document-chat-model.js +5 -5
  152. package/dist/features/ai/document-chat-model.js.map +1 -1
  153. package/dist/features/ai/document-chat.js +1 -1
  154. package/dist/features/ai/feature.js +27 -14
  155. package/dist/features/ai/feature.js.map +1 -1
  156. package/dist/features/ai/ghost-autocomplete.d.ts +7 -6
  157. package/dist/features/ai/ghost-autocomplete.js +6 -9
  158. package/dist/features/ai/ghost-autocomplete.js.map +1 -1
  159. package/dist/features/ai/index.d.ts +4 -3
  160. package/dist/features/ai/index.js +10 -10
  161. package/dist/features/ai/model-profiles.d.ts +5 -4
  162. package/dist/features/ai/model-profiles.js +2 -2
  163. package/dist/features/ai/model-profiles.js.map +1 -1
  164. package/dist/features/ai/protocol/block-lines.js +1 -1
  165. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  166. package/dist/features/ai/protocol/chat.js +6 -2
  167. package/dist/features/ai/protocol/chat.js.map +1 -1
  168. package/dist/features/ai/protocol/index.d.ts +3 -2
  169. package/dist/features/ai/protocol/models.d.ts +2 -16
  170. package/dist/features/ai/protocol/models.js.map +1 -1
  171. package/dist/features/ai/rate-limit.d.ts +1 -1
  172. package/dist/features/ai/rate-limit.js +3 -1
  173. package/dist/features/ai/rate-limit.js.map +1 -1
  174. package/dist/features/ai/react/AiSelectionMenu.js +2 -2
  175. package/dist/features/ai/react/AiSidebar.js +4 -4
  176. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  177. package/dist/features/ai/react/DocumentChatOps.d.ts +1 -1
  178. package/dist/features/ai/react/DocumentChatOps.js +1 -1
  179. package/dist/features/ai/react/SettingsModelProfiles.js +8 -10
  180. package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
  181. package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
  182. package/dist/features/ai/react/use-ai-assistant.js +3 -3
  183. package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
  184. package/dist/features/ai/react/use-document-chat.js +2 -2
  185. package/dist/features/ai/react/use-document-chat.js.map +1 -1
  186. package/dist/features/ai/react/use-model-profiles.d.ts +2 -1
  187. package/dist/features/ai/selection-flow.d.ts +1 -1
  188. package/dist/features/ai/selection-flow.js +1 -1
  189. package/dist/features/ai/sidebar-flow.d.ts +1 -1
  190. package/dist/features/ai/sidebar-flow.js +1 -1
  191. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  192. package/dist/features/ai/use-ai-assistant.js +3 -3
  193. package/dist/features/ai/use-ai-assistant.js.map +1 -1
  194. package/dist/features/ai/use-document-chat.js +2 -2
  195. package/dist/features/ai/use-document-chat.js.map +1 -1
  196. package/dist/features/ai/use-model-profiles.d.ts +2 -1
  197. package/dist/features/authors/attribution.d.ts +1 -1
  198. package/dist/features/authors/feature.js +12 -11
  199. package/dist/features/authors/feature.js.map +1 -1
  200. package/dist/features/block-ui/block-actions.d.ts +1 -1
  201. package/dist/features/block-ui/block-actions.js +1 -1
  202. package/dist/features/block-ui/block-actions.js.map +1 -1
  203. package/dist/features/block-ui/components/BlockUi.js +1 -1
  204. package/dist/features/block-ui/feature.js +1 -0
  205. package/dist/features/block-ui/feature.js.map +1 -1
  206. package/dist/features/block-ui/react/BlockUi.js +1 -1
  207. package/dist/features/collab/anchor-resolver.js +1 -1
  208. package/dist/features/collab/feature.js +2 -0
  209. package/dist/features/collab/feature.js.map +1 -1
  210. package/dist/features/comments/components/CommentThreadView.vue.d.ts +2 -1
  211. package/dist/features/comments/feature.js +12 -11
  212. package/dist/features/comments/feature.js.map +1 -1
  213. package/dist/features/comments/index.d.ts +3 -2
  214. package/dist/features/comments/index.js +1 -1
  215. package/dist/features/comments/margin-layout.d.ts +3 -2
  216. package/dist/features/comments/model.d.ts +3 -67
  217. package/dist/features/comments/model.js +2 -2
  218. package/dist/features/comments/model.js.map +1 -1
  219. package/dist/features/comments/react/CommentThreadView.d.ts +3 -2
  220. package/dist/features/comments/react/use-comments.d.ts +2 -1
  221. package/dist/features/comments/react.d.ts +1 -1
  222. package/dist/features/comments/schema.d.ts +2 -52
  223. package/dist/features/comments/schema.js.map +1 -1
  224. package/dist/features/comments/snapshot-equality.js.map +1 -1
  225. package/dist/features/comments/use-comments.d.ts +3 -2
  226. package/dist/features/comments/vue.d.ts +1 -1
  227. package/dist/features/composites/accepted-children.d.ts +1 -0
  228. package/dist/features/composites/accepted-children.js +16 -1
  229. package/dist/features/composites/accepted-children.js.map +1 -1
  230. package/dist/features/composites/ai-brief.js +39 -0
  231. package/dist/features/composites/ai-brief.js.map +1 -1
  232. package/dist/features/composites/bibliography-command.d.ts +11 -0
  233. package/dist/features/composites/bibliography-form.js +90 -0
  234. package/dist/features/composites/bibliography-form.js.map +1 -0
  235. package/dist/features/composites/bibliography.d.ts +33 -0
  236. package/dist/features/composites/bibliography.js +120 -0
  237. package/dist/features/composites/bibliography.js.map +1 -0
  238. package/dist/features/composites/components/BibliographyEditor.js +261 -0
  239. package/dist/features/composites/components/BibliographyEditor.js.map +1 -0
  240. package/dist/features/composites/components/CompositeChrome.js +78 -29
  241. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  242. package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
  243. package/dist/features/composites/components/CompositeDetails.js +46 -11
  244. package/dist/features/composites/components/CompositeDetails.js.map +1 -1
  245. package/dist/features/composites/components/CompositeDetails.vue.d.ts +2 -1
  246. package/dist/features/composites/components/CompositeFieldEditor.js +64 -5
  247. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  248. package/dist/features/composites/components/CompositeInspector.js +20 -7
  249. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  250. package/dist/features/composites/components/CompositeTable.js +13 -4
  251. package/dist/features/composites/components/CompositeTable.js.map +1 -1
  252. package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
  253. package/dist/features/composites/components/CompositeView.js +11 -1
  254. package/dist/features/composites/components/CompositeView.js.map +1 -1
  255. package/dist/features/composites/components/DerivationBadge.js +103 -0
  256. package/dist/features/composites/components/DerivationBadge.js.map +1 -0
  257. package/dist/features/composites/components/ReferenceFieldSelect.js +60 -0
  258. package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -0
  259. package/dist/features/composites/components/ReferenceView.js +122 -0
  260. package/dist/features/composites/components/ReferenceView.js.map +1 -0
  261. package/dist/features/composites/components/ReferenceView.vue.d.ts +96 -0
  262. package/dist/features/composites/components/SettingsEditingTab.js +1 -1
  263. package/dist/features/composites/convert.js +2 -2
  264. package/dist/features/composites/convert.js.map +1 -1
  265. package/dist/features/composites/derivation.d.ts +61 -0
  266. package/dist/features/composites/derivation.js +98 -0
  267. package/dist/features/composites/derivation.js.map +1 -0
  268. package/dist/features/composites/derived-chrome.js +69 -0
  269. package/dist/features/composites/derived-chrome.js.map +1 -0
  270. package/dist/features/composites/feature.js +159 -109
  271. package/dist/features/composites/feature.js.map +1 -1
  272. package/dist/features/composites/fences.d.ts +4 -3
  273. package/dist/features/composites/fences.js +51 -7
  274. package/dist/features/composites/fences.js.map +1 -1
  275. package/dist/features/composites/index.d.ts +12 -3
  276. package/dist/features/composites/index.js +15 -6
  277. package/dist/features/composites/inspector-model.d.ts +4 -1
  278. package/dist/features/composites/inspector-model.js +8 -4
  279. package/dist/features/composites/inspector-model.js.map +1 -1
  280. package/dist/features/composites/kinds.d.ts +16 -2
  281. package/dist/features/composites/kinds.js +145 -6
  282. package/dist/features/composites/kinds.js.map +1 -1
  283. package/dist/features/composites/legacy.d.ts +4 -1
  284. package/dist/features/composites/legacy.js +56 -1
  285. package/dist/features/composites/legacy.js.map +1 -1
  286. package/dist/features/composites/meta-display.js +45 -2
  287. package/dist/features/composites/meta-display.js.map +1 -1
  288. package/dist/features/composites/meta-keys.d.ts +7 -0
  289. package/dist/features/composites/meta-keys.js +21 -0
  290. package/dist/features/composites/meta-keys.js.map +1 -0
  291. package/dist/features/composites/node-views.d.ts +6 -3
  292. package/dist/features/composites/node-views.js +7 -3
  293. package/dist/features/composites/node-views.js.map +1 -1
  294. package/dist/features/composites/nodes.d.ts +6 -1
  295. package/dist/features/composites/nodes.js +33 -7
  296. package/dist/features/composites/nodes.js.map +1 -1
  297. package/dist/features/composites/react/BibliographyEditor.js +229 -0
  298. package/dist/features/composites/react/BibliographyEditor.js.map +1 -0
  299. package/dist/features/composites/react/CompositeChrome.js +67 -19
  300. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  301. package/dist/features/composites/react/CompositeDetails.d.ts +2 -1
  302. package/dist/features/composites/react/CompositeDetails.js +45 -6
  303. package/dist/features/composites/react/CompositeDetails.js.map +1 -1
  304. package/dist/features/composites/react/CompositeFieldEditor.d.ts +1 -1
  305. package/dist/features/composites/react/CompositeFieldEditor.js +43 -1
  306. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  307. package/dist/features/composites/react/CompositeInspector.js +12 -6
  308. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  309. package/dist/features/composites/react/CompositeStatusMenu.js +1 -1
  310. package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
  311. package/dist/features/composites/react/CompositeTable.js +16 -2
  312. package/dist/features/composites/react/CompositeTable.js.map +1 -1
  313. package/dist/features/composites/react/CompositeTagsEditor.js +2 -2
  314. package/dist/features/composites/react/CompositeView.js +6 -1
  315. package/dist/features/composites/react/CompositeView.js.map +1 -1
  316. package/dist/features/composites/react/DerivationBadge.js +116 -0
  317. package/dist/features/composites/react/DerivationBadge.js.map +1 -0
  318. package/dist/features/composites/react/ReferenceFieldSelect.js +52 -0
  319. package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -0
  320. package/dist/features/composites/react/ReferenceView.d.ts +11 -0
  321. package/dist/features/composites/react/ReferenceView.js +115 -0
  322. package/dist/features/composites/react/ReferenceView.js.map +1 -0
  323. package/dist/features/composites/react/SettingsEditingTab.js +2 -2
  324. package/dist/features/composites/react.d.ts +2 -1
  325. package/dist/features/composites/react.js +7 -3
  326. package/dist/features/composites/react.js.map +1 -1
  327. package/dist/features/composites/reference-ai.js +16 -0
  328. package/dist/features/composites/reference-ai.js.map +1 -0
  329. package/dist/features/composites/reference-label.d.ts +19 -0
  330. package/dist/features/composites/reference-label.js +58 -0
  331. package/dist/features/composites/reference-label.js.map +1 -0
  332. package/dist/features/composites/reference-suggestion.d.ts +10 -0
  333. package/dist/features/composites/reference-suggestion.js +41 -0
  334. package/dist/features/composites/reference-suggestion.js.map +1 -0
  335. package/dist/features/composites/reference-text.d.ts +18 -0
  336. package/dist/features/composites/reference-text.js +89 -0
  337. package/dist/features/composites/reference-text.js.map +1 -0
  338. package/dist/features/composites/reference.d.ts +44 -0
  339. package/dist/features/composites/reference.js +148 -0
  340. package/dist/features/composites/reference.js.map +1 -0
  341. package/dist/features/composites/reveal-source.d.ts +32 -0
  342. package/dist/features/composites/reveal-source.js +89 -0
  343. package/dist/features/composites/reveal-source.js.map +1 -0
  344. package/dist/features/composites/rows.d.ts +25 -3
  345. package/dist/features/composites/rows.js +72 -10
  346. package/dist/features/composites/rows.js.map +1 -1
  347. package/dist/features/composites/schema.d.ts +9 -3
  348. package/dist/features/composites/schema.js +11 -4
  349. package/dist/features/composites/schema.js.map +1 -1
  350. package/dist/features/composites/table-model.js +12 -21
  351. package/dist/features/composites/table-model.js.map +1 -1
  352. package/dist/features/composites/vue.d.ts +3 -2
  353. package/dist/features/composites/vue.js +8 -4
  354. package/dist/features/composites/vue.js.map +1 -1
  355. package/dist/features/media/assets.d.ts +2 -0
  356. package/dist/features/media/assets.js +3 -2
  357. package/dist/features/media/assets.js.map +1 -1
  358. package/dist/features/media/components/FileView.js +13 -4
  359. package/dist/features/media/components/FileView.js.map +1 -1
  360. package/dist/features/media/feature.d.ts +3 -3
  361. package/dist/features/media/feature.js +21 -28
  362. package/dist/features/media/feature.js.map +1 -1
  363. package/dist/features/media/file-import.js +2 -2
  364. package/dist/features/media/file-import.js.map +1 -1
  365. package/dist/features/media/index.d.ts +2 -2
  366. package/dist/features/media/index.js +3 -3
  367. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  368. package/dist/features/media/react/FileView.js +19 -2
  369. package/dist/features/media/react/FileView.js.map +1 -1
  370. package/dist/features/media/react.js +1 -1
  371. package/dist/features/media/schema.d.ts +8 -0
  372. package/dist/features/media/schema.js +126 -11
  373. package/dist/features/media/schema.js.map +1 -1
  374. package/dist/features/media/upload.d.ts +16 -14
  375. package/dist/features/media/upload.js +13 -19
  376. package/dist/features/media/upload.js.map +1 -1
  377. package/dist/features/mentions/index.d.ts +4 -3
  378. package/dist/features/mentions/index.js +4 -1
  379. package/dist/features/mentions/index.js.map +1 -1
  380. package/dist/features/mentions/mention-text.d.ts +1 -1
  381. package/dist/features/mentions/mention-text.js.map +1 -1
  382. package/dist/features/mentions/mention.d.ts +0 -6
  383. package/dist/features/mentions/mention.js.map +1 -1
  384. package/dist/features/mentions/metadata-defaults.d.ts +2 -2
  385. package/dist/features/mentions/metadata-defaults.js +3 -9
  386. package/dist/features/mentions/metadata-defaults.js.map +1 -1
  387. package/dist/features/mentions/schema.d.ts +2 -1
  388. package/dist/features/mentions/schema.js.map +1 -1
  389. package/dist/features/tables/feature.d.ts +2 -1
  390. package/dist/features/tables/feature.js +90 -70
  391. package/dist/features/tables/feature.js.map +1 -1
  392. package/dist/features/themes/components/SettingsAppearanceTab.js +6 -9
  393. package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
  394. package/dist/features/themes/feature.js +2 -0
  395. package/dist/features/themes/feature.js.map +1 -1
  396. package/dist/features/themes/index.d.ts +2 -1
  397. package/dist/features/themes/react/SettingsAppearanceTab.js +7 -10
  398. package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
  399. package/dist/features/themes/schema.d.ts +2 -6
  400. package/dist/features/themes/schema.js.map +1 -1
  401. package/dist/features/themes/theme-runtime.d.ts +1 -1
  402. package/dist/features/themes/theme-runtime.js +4 -2
  403. package/dist/features/themes/theme-runtime.js.map +1 -1
  404. package/dist/features/transcript/TranscriptSegmentView.js +48 -28
  405. package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
  406. package/dist/features/transcript/feature.js +21 -19
  407. package/dist/features/transcript/feature.js.map +1 -1
  408. package/dist/features/transcript/react/TranscriptSegmentView.js +55 -36
  409. package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
  410. package/dist/features/transcript/segment-node.d.ts +2 -0
  411. package/dist/features/transcript/segment-node.js +35 -7
  412. package/dist/features/transcript/segment-node.js.map +1 -1
  413. package/dist/i18n/de/composites.js +91 -1
  414. package/dist/i18n/de/composites.js.map +1 -1
  415. package/dist/i18n/de/integrations.js +49 -8
  416. package/dist/i18n/de/integrations.js.map +1 -1
  417. package/dist/i18n/de/media.js +0 -1
  418. package/dist/i18n/de/media.js.map +1 -1
  419. package/dist/i18n/de/palette.js +22 -0
  420. package/dist/i18n/de/palette.js.map +1 -0
  421. package/dist/i18n/de/tables.js +0 -1
  422. package/dist/i18n/de/tables.js.map +1 -1
  423. package/dist/i18n/de/transcript.js +2 -1
  424. package/dist/i18n/de/transcript.js.map +1 -1
  425. package/dist/i18n/de/tree.js +3 -0
  426. package/dist/i18n/de/tree.js.map +1 -1
  427. package/dist/i18n/de.d.ts +157 -11
  428. package/dist/i18n/de.js +3 -1
  429. package/dist/i18n/de.js.map +1 -1
  430. package/dist/i18n/en/composites.js +89 -1
  431. package/dist/i18n/en/composites.js.map +1 -1
  432. package/dist/i18n/en/integrations.js +49 -8
  433. package/dist/i18n/en/integrations.js.map +1 -1
  434. package/dist/i18n/en/media.js +0 -1
  435. package/dist/i18n/en/media.js.map +1 -1
  436. package/dist/i18n/en/palette.js +22 -0
  437. package/dist/i18n/en/palette.js.map +1 -0
  438. package/dist/i18n/en/tables.js +0 -1
  439. package/dist/i18n/en/tables.js.map +1 -1
  440. package/dist/i18n/en/transcript.js +2 -1
  441. package/dist/i18n/en/transcript.js.map +1 -1
  442. package/dist/i18n/en/tree.js +3 -0
  443. package/dist/i18n/en/tree.js.map +1 -1
  444. package/dist/i18n/en.js +3 -1
  445. package/dist/i18n/en.js.map +1 -1
  446. package/dist/index.d.ts +4 -3
  447. package/dist/integrations/composite-ai/bibliography-fetch.d.ts +31 -0
  448. package/dist/integrations/composite-ai/bibliography-fetch.js +159 -0
  449. package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -0
  450. package/dist/integrations/composite-ai/composite-fill.d.ts +30 -8
  451. package/dist/integrations/composite-ai/composite-fill.js +97 -56
  452. package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
  453. package/dist/integrations/composite-ai/composite-summary.d.ts +4 -12
  454. package/dist/integrations/composite-ai/composite-summary.js +7 -26
  455. package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
  456. package/dist/integrations/composite-ai/derive.d.ts +29 -0
  457. package/dist/integrations/composite-ai/derive.js +173 -0
  458. package/dist/integrations/composite-ai/derive.js.map +1 -0
  459. package/dist/integrations/composite-ai/index.d.ts +11 -7
  460. package/dist/integrations/composite-ai/index.js +72 -14
  461. package/dist/integrations/composite-ai/index.js.map +1 -1
  462. package/dist/integrations/composite-ai/rendition.d.ts +45 -0
  463. package/dist/integrations/composite-ai/rendition.js +314 -0
  464. package/dist/integrations/composite-ai/rendition.js.map +1 -0
  465. package/dist/integrations/composite-media/index.d.ts +7 -5
  466. package/dist/integrations/composite-media/index.js +86 -13
  467. package/dist/integrations/composite-media/index.js.map +1 -1
  468. package/dist/integrations/transcript-ai/index.d.ts +1 -1
  469. package/dist/integrations/transcript-ai/index.js +5 -2
  470. package/dist/integrations/transcript-ai/index.js.map +1 -1
  471. package/dist/integrations/transcript-ai/protocol.d.ts +2 -12
  472. package/dist/integrations/transcript-ai/protocol.js +0 -2
  473. package/dist/integrations/transcript-ai/protocol.js.map +1 -1
  474. package/dist/integrations/transcript-ai/transcript-cleanup.d.ts +5 -5
  475. package/dist/integrations/transcript-ai/transcript-cleanup.js +7 -10
  476. package/dist/integrations/transcript-ai/transcript-cleanup.js.map +1 -1
  477. package/dist/markdown/MarkdownSurface.vue.d.ts +12 -11
  478. package/dist/markdown/MarkdownSurface2.js +3 -6
  479. package/dist/markdown/MarkdownSurface2.js.map +1 -1
  480. package/dist/markdown/document.js +1 -1
  481. package/dist/markdown/source-mode.d.ts +1 -1
  482. package/dist/nuxt/module.d.ts +1402 -54
  483. package/dist/nuxt/module.js +6 -49
  484. package/dist/nuxt/module.js.map +1 -1
  485. package/dist/nuxt/runtime/install-context.d.ts +7 -3
  486. package/dist/nuxt/runtime/install-context.js +14 -8
  487. package/dist/nuxt/runtime/install-context.js.map +1 -1
  488. package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +2 -2
  489. package/dist/nuxt/runtime/server/plugins/editor-server.js +20 -12
  490. package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
  491. package/dist/presets/compose.d.ts +94 -0
  492. package/dist/presets/compose.js +164 -0
  493. package/dist/presets/compose.js.map +1 -0
  494. package/dist/react/AdvancedEditor.d.ts +15 -15
  495. package/dist/react/AdvancedEditor.js +15 -13
  496. package/dist/react/AdvancedEditor.js.map +1 -1
  497. package/dist/react/EditorCommandPalette.d.ts +13 -0
  498. package/dist/react/EditorCommandPalette.js +118 -0
  499. package/dist/react/EditorCommandPalette.js.map +1 -0
  500. package/dist/react/EditorSettingsModal.d.ts +5 -5
  501. package/dist/react/EditorSettingsModal.js +7 -4
  502. package/dist/react/EditorSettingsModal.js.map +1 -1
  503. package/dist/react/EditorSettingsPanel.d.ts +7 -7
  504. package/dist/react/EditorSettingsPanel.js +8 -5
  505. package/dist/react/EditorSettingsPanel.js.map +1 -1
  506. package/dist/react/EditorSurface.d.ts +7 -7
  507. package/dist/react/EditorSurface.js +10 -6
  508. package/dist/react/EditorSurface.js.map +1 -1
  509. package/dist/react/EditorToolbar.d.ts +4 -6
  510. package/dist/react/EditorToolbar.js +29 -37
  511. package/dist/react/EditorToolbar.js.map +1 -1
  512. package/dist/react/SimpleEditor.d.ts +22 -23
  513. package/dist/react/SimpleEditor.js +21 -7
  514. package/dist/react/SimpleEditor.js.map +1 -1
  515. package/dist/react/advanced.d.ts +4 -3
  516. package/dist/react/advanced.js +2 -2
  517. package/dist/react/compose.d.ts +20 -0
  518. package/dist/react/compose.js +49 -0
  519. package/dist/react/compose.js.map +1 -0
  520. package/dist/react/composed-surface.d.ts +39 -0
  521. package/dist/react/composed-surface.js +71 -0
  522. package/dist/react/composed-surface.js.map +1 -0
  523. package/dist/react/context.js +6 -2
  524. package/dist/react/context.js.map +1 -1
  525. package/dist/react/index.d.ts +2 -1
  526. package/dist/react/index.js +2 -1
  527. package/dist/react/key-labels.js +15 -0
  528. package/dist/react/key-labels.js.map +1 -0
  529. package/dist/react/settings.d.ts +1 -1
  530. package/dist/react/simple.d.ts +2 -2
  531. package/dist/react/ui/command.js +36 -1
  532. package/dist/react/ui/command.js.map +1 -1
  533. package/dist/schema/advanced.d.ts +6 -6
  534. package/dist/schema/advanced.js +26 -29
  535. package/dist/schema/advanced.js.map +1 -1
  536. package/dist/server/ai.d.ts +5 -16
  537. package/dist/server/ai.js.map +1 -1
  538. package/dist/server/auth.d.ts +9 -0
  539. package/dist/server/collab-state.d.ts +1 -1
  540. package/dist/server/collab-state.js.map +1 -1
  541. package/dist/server/collab.d.ts +13 -13
  542. package/dist/server/collab.js +34 -18
  543. package/dist/server/collab.js.map +1 -1
  544. package/dist/server/crossref.js +78 -0
  545. package/dist/server/crossref.js.map +1 -0
  546. package/dist/server/errors.d.ts +1 -1
  547. package/dist/server/errors.js +1 -1
  548. package/dist/server/errors.js.map +1 -1
  549. package/dist/server/extract.js +308 -0
  550. package/dist/server/extract.js.map +1 -0
  551. package/dist/server/h3.d.ts +8 -0
  552. package/dist/{nuxt/runtime/server/utils/editor-route.js → server/h3.js} +8 -16
  553. package/dist/server/h3.js.map +1 -0
  554. package/dist/server/handlers/ai.d.ts +32 -10
  555. package/dist/server/handlers/ai.js +172 -37
  556. package/dist/server/handlers/ai.js.map +1 -1
  557. package/dist/server/handlers/context.d.ts +12 -5
  558. package/dist/server/handlers/context.js +10 -4
  559. package/dist/server/handlers/context.js.map +1 -1
  560. package/dist/server/handlers/editor.d.ts +19 -13
  561. package/dist/server/handlers/editor.js +49 -38
  562. package/dist/server/handlers/editor.js.map +1 -1
  563. package/dist/server/handlers/index.d.ts +4 -4
  564. package/dist/server/handlers/index.js +4 -4
  565. package/dist/server/host.d.ts +16 -25
  566. package/dist/server/host.js +58 -41
  567. package/dist/server/host.js.map +1 -1
  568. package/dist/server/index.d.ts +6 -4
  569. package/dist/server/index.js +3 -2
  570. package/dist/server/pdf.js +97 -0
  571. package/dist/server/pdf.js.map +1 -0
  572. package/dist/server/{adapter.d.ts → ports.d.ts} +81 -30
  573. package/dist/server/ports.js +29 -0
  574. package/dist/server/ports.js.map +1 -0
  575. package/dist/server/routes.d.ts +22 -0
  576. package/dist/server/routes.js +144 -0
  577. package/dist/server/routes.js.map +1 -0
  578. package/dist/settings/index.d.ts +1 -1
  579. package/dist/settings/index.js.map +1 -1
  580. package/dist/styles/composites.css +25 -0
  581. package/dist/tree/DocumentTree.js +56 -29
  582. package/dist/tree/DocumentTree.js.map +1 -1
  583. package/dist/tree/react/DocumentTree.js +23 -0
  584. package/dist/tree/react/DocumentTree.js.map +1 -1
  585. package/dist/tree/tree-host.js +2 -2
  586. package/dist/tree/tree-items.js +1 -1
  587. package/dist/vue/AdvancedEditor.js +30 -9
  588. package/dist/vue/AdvancedEditor.js.map +1 -1
  589. package/dist/vue/AdvancedEditor.vue.d.ts +32 -12
  590. package/dist/vue/SimpleEditor.js +62 -16
  591. package/dist/vue/SimpleEditor.js.map +1 -1
  592. package/dist/vue/SimpleEditor.vue.d.ts +39 -9
  593. package/dist/vue/advanced.d.ts +3 -2
  594. package/dist/vue/advanced.js +2 -2
  595. package/dist/vue/components/EditorCommandPalette.js +91 -0
  596. package/dist/vue/components/EditorCommandPalette.js.map +1 -0
  597. package/dist/vue/components/EditorCommandPalette.vue.d.ts +15 -0
  598. package/dist/vue/components/EditorSettingsModal.js +10 -6
  599. package/dist/vue/components/EditorSettingsModal.js.map +1 -1
  600. package/dist/vue/components/EditorSettingsModal.vue.d.ts +4 -4
  601. package/dist/vue/components/EditorSettingsPanel.js +10 -6
  602. package/dist/vue/components/EditorSettingsPanel.js.map +1 -1
  603. package/dist/vue/components/EditorSettingsPanel.vue.d.ts +6 -6
  604. package/dist/vue/components/EditorSurface.js +11 -6
  605. package/dist/vue/components/EditorSurface.js.map +1 -1
  606. package/dist/vue/components/EditorSurface.vue.d.ts +11 -11
  607. package/dist/vue/components/EditorToolbar.js +41 -49
  608. package/dist/vue/components/EditorToolbar.js.map +1 -1
  609. package/dist/vue/components/EditorToolbar.vue.d.ts +2 -4
  610. package/dist/vue/compose.d.ts +17 -0
  611. package/dist/vue/compose.js +51 -0
  612. package/dist/vue/compose.js.map +1 -0
  613. package/dist/vue/context-key.d.ts +6 -0
  614. package/dist/vue/context-key.js +6 -0
  615. package/dist/vue/context-key.js.map +1 -0
  616. package/dist/vue/context.d.ts +8 -6
  617. package/dist/vue/context.js +6 -5
  618. package/dist/vue/context.js.map +1 -1
  619. package/dist/vue/index.d.ts +10 -8
  620. package/dist/vue/index.js +10 -8
  621. package/dist/vue/kit/index.d.ts +2 -2
  622. package/dist/vue/kit/nuxt-ui.js +23 -0
  623. package/dist/vue/kit/nuxt-ui.js.map +1 -1
  624. package/dist/vue/kit/types.d.ts +40 -1
  625. package/package.json +30 -16
  626. package/dist/adapters/comments/client.d.ts +0 -64
  627. package/dist/adapters/comments/client.js.map +0 -1
  628. package/dist/adapters/routes.js.map +0 -1
  629. package/dist/core/toolbar.d.ts +0 -74
  630. package/dist/core/toolbar.js +0 -45
  631. package/dist/core/toolbar.js.map +0 -1
  632. package/dist/integrations/composite-ai/document-transcribe.d.ts +0 -14
  633. package/dist/integrations/composite-ai/document-transcribe.js +0 -89
  634. package/dist/integrations/composite-ai/document-transcribe.js.map +0 -1
  635. package/dist/nuxt/runtime/server/api/ai/chat.post.d.ts +0 -5
  636. package/dist/nuxt/runtime/server/api/ai/chat.post.js +0 -8
  637. package/dist/nuxt/runtime/server/api/ai/chat.post.js.map +0 -1
  638. package/dist/nuxt/runtime/server/api/ai/complete.post.d.ts +0 -5
  639. package/dist/nuxt/runtime/server/api/ai/complete.post.js +0 -8
  640. package/dist/nuxt/runtime/server/api/ai/complete.post.js.map +0 -1
  641. package/dist/nuxt/runtime/server/api/ai/generate.post.d.ts +0 -5
  642. package/dist/nuxt/runtime/server/api/ai/generate.post.js +0 -8
  643. package/dist/nuxt/runtime/server/api/ai/generate.post.js.map +0 -1
  644. package/dist/nuxt/runtime/server/api/ai/models.get.d.ts +0 -5
  645. package/dist/nuxt/runtime/server/api/ai/models.get.js +0 -8
  646. package/dist/nuxt/runtime/server/api/ai/models.get.js.map +0 -1
  647. package/dist/nuxt/runtime/server/api/ai/transcribe.post.d.ts +0 -5
  648. package/dist/nuxt/runtime/server/api/ai/transcribe.post.js +0 -8
  649. package/dist/nuxt/runtime/server/api/ai/transcribe.post.js.map +0 -1
  650. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.d.ts +0 -5
  651. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js +0 -8
  652. package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js.map +0 -1
  653. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.d.ts +0 -5
  654. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js +0 -8
  655. package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js.map +0 -1
  656. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.d.ts +0 -5
  657. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js +0 -8
  658. package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js.map +0 -1
  659. package/dist/nuxt/runtime/server/api/editor/settings.get.d.ts +0 -5
  660. package/dist/nuxt/runtime/server/api/editor/settings.get.js +0 -8
  661. package/dist/nuxt/runtime/server/api/editor/settings.get.js.map +0 -1
  662. package/dist/nuxt/runtime/server/api/editor/settings.put.d.ts +0 -5
  663. package/dist/nuxt/runtime/server/api/editor/settings.put.js +0 -8
  664. package/dist/nuxt/runtime/server/api/editor/settings.put.js.map +0 -1
  665. package/dist/nuxt/runtime/server/api/editor/tags.get.d.ts +0 -5
  666. package/dist/nuxt/runtime/server/api/editor/tags.get.js +0 -8
  667. package/dist/nuxt/runtime/server/api/editor/tags.get.js.map +0 -1
  668. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.d.ts +0 -5
  669. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js +0 -8
  670. package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js.map +0 -1
  671. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.d.ts +0 -5
  672. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js +0 -8
  673. package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js.map +0 -1
  674. package/dist/nuxt/runtime/server/api/editor/themes/index.get.d.ts +0 -5
  675. package/dist/nuxt/runtime/server/api/editor/themes/index.get.js +0 -8
  676. package/dist/nuxt/runtime/server/api/editor/themes/index.get.js.map +0 -1
  677. package/dist/nuxt/runtime/server/api/editor/themes/index.post.d.ts +0 -5
  678. package/dist/nuxt/runtime/server/api/editor/themes/index.post.js +0 -8
  679. package/dist/nuxt/runtime/server/api/editor/themes/index.post.js.map +0 -1
  680. package/dist/nuxt/runtime/server/api/editor/users.get.d.ts +0 -5
  681. package/dist/nuxt/runtime/server/api/editor/users.get.js +0 -8
  682. package/dist/nuxt/runtime/server/api/editor/users.get.js.map +0 -1
  683. package/dist/nuxt/runtime/server/plugins/collab.d.ts +0 -5
  684. package/dist/nuxt/runtime/server/plugins/collab.js +0 -10
  685. package/dist/nuxt/runtime/server/plugins/collab.js.map +0 -1
  686. package/dist/nuxt/runtime/server/routes/collab/[id].d.ts +0 -5
  687. package/dist/nuxt/runtime/server/routes/collab/[id].js +0 -35
  688. package/dist/nuxt/runtime/server/routes/collab/[id].js.map +0 -1
  689. package/dist/nuxt/runtime/server/utils/editor-route.d.ts +0 -11
  690. package/dist/nuxt/runtime/server/utils/editor-route.js.map +0 -1
  691. package/dist/presets/advanced.d.ts +0 -33
  692. package/dist/presets/advanced.js +0 -50
  693. package/dist/presets/advanced.js.map +0 -1
  694. package/dist/presets/simple.d.ts +0 -9
  695. package/dist/presets/simple.js +0 -12
  696. package/dist/presets/simple.js.map +0 -1
  697. package/dist/react/advanced-preset.d.ts +0 -11
  698. package/dist/react/advanced-preset.js +0 -31
  699. package/dist/react/advanced-preset.js.map +0 -1
  700. package/dist/vue/advanced-preset.d.ts +0 -13
  701. package/dist/vue/advanced-preset.js +0 -33
  702. package/dist/vue/advanced-preset.js.map +0 -1
  703. package/docs/extending.md +0 -538
  704. package/docs/integration.md +0 -632
  705. package/docs/llms.md +0 -383
  706. package/docs/usage.md +0 -1076
@@ -1,13 +1,13 @@
1
- import { CORE_TARGET_IDS, convertTargetId } from "../../core/convert.js";
2
1
  import { aiContextOf, conversionsOf } from "../../core/pipeline.js";
2
+ import { CORE_TARGET_IDS, convertTargetId } from "../../core/convert.js";
3
3
  import { parseConversionTarget } from "../../core/block-convert.js";
4
4
  import { silentNotifier } from "../../core/notify.js";
5
5
  import { createStore } from "../../core/store.js";
6
6
  import { CHAT_LIMITS } from "./protocol/chat.js";
7
- import { NO_AGENT_OPS, createAgentOpReview } from "./agent-op-review.js";
8
7
  import { notifyAiRateLimited } from "./rate-limit.js";
9
8
  import { postAiSse } from "./ai-sse.js";
10
9
  import { documentChatText } from "./document-chat.js";
10
+ import { NO_AGENT_OPS, createAgentOpReview } from "./agent-op-review.js";
11
11
  //#region src/features/ai/document-chat-model.ts
12
12
  const EMPTY = {
13
13
  messages: [],
@@ -16,11 +16,11 @@ const EMPTY = {
16
16
  error: null
17
17
  };
18
18
  /**
19
- * Document chat state: the conversation lives only here (client side) and goes to `/api/ai/chat` in full with every
19
+ * Document chat state: the conversation lives only here (client side) and goes to the backend's `chat` stream in full with every
20
20
  * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an
21
21
  * in-document review; their decisions go to the model with the next message.
22
22
  */
23
- function createDocumentChat({ fetch, modelRef, notify, t }) {
23
+ function createDocumentChat({ ai, modelRef, notify, t }) {
24
24
  const store = createStore(EMPTY);
25
25
  const review = createAgentOpReview({
26
26
  notify: silentNotifier,
@@ -110,7 +110,7 @@ function createDocumentChat({ fetch, modelRef, notify, t }) {
110
110
  targets,
111
111
  structure
112
112
  };
113
- const outcome = await postAiSse(fetch, "/api/ai/chat", body, {
113
+ const outcome = await postAiSse(ai, "chat", body, {
114
114
  signal: own.signal,
115
115
  t,
116
116
  onDelta: (text) => {
@@ -1 +1 @@
1
- {"version":3,"file":"document-chat-model.js","names":[],"sources":["../../../src/features/ai/document-chat-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { $Fetch } from 'ofetch'\nimport { aiContextOf, CORE_TARGET_IDS, convertTargetId, conversionsOf, parseConversionTarget } from '../../core'\nimport { type Notifier, silentNotifier } from '../../core/notify'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport type { Translate } from '../../i18n'\nimport type { ModelSlot } from '../../settings'\nimport { type AgentOpReviewState, createAgentOpReview, NO_AGENT_OPS } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport type { AiTarget } from './ai-apply'\nimport { postAiSse } from './ai-sse'\nimport { documentChatText } from './document-chat'\nimport { type AgentOp, type AgentOpProblem, CHAT_LIMITS, type AiTask, type DocumentChatMessage, type ModelRef } from './protocol'\nimport { notifyAiRateLimited } from './rate-limit'\n\n/**\n * A block generation run from the AI sidebar's quick actions, shown as an assistant turn. Client only: it never goes\n * to `/api/ai/chat`. `markdown` is the streamed result without the task heading (shown with `withTaskHeading`, applied\n * as `taskResultMarkdown`), `target` the apply target captured when it started; only a result that streamed to the end\n * (`done`) can be applied.\n */\nexport interface DocumentChatGeneration {\n task: AiTask\n /** The instruction of a task with `needsPrompt` (empty otherwise). */\n prompt: string\n markdown: string\n target: AiTarget\n done: boolean\n}\n\n/**\n * A chat turn as shown: an assistant turn also keeps the problems of the operations the server dropped, or is a block\n * generation of the sidebar (`generation`).\n */\nexport type DocumentChatTurn =\n | Extract<DocumentChatMessage, { role: 'user' }>\n | (Extract<DocumentChatMessage, { role: 'assistant' }> & { problems?: AgentOpProblem[], generation?: DocumentChatGeneration })\n\n/** Snapshot of a conversation; every change replaces `messages`/`ops` (never mutated in place). */\nexport interface DocumentChatState {\n messages: readonly DocumentChatTurn[]\n /** Operation states by operation id. */\n ops: AgentOpReviewState['ops']\n streaming: boolean\n error: string | null\n}\n\nexport interface DocumentChatOptions {\n fetch: $Fetch\n modelRef: (slot: ModelSlot) => ModelRef\n /** Receives the 429 notice. */\n notify: Notifier\n /** The app's translator (`EditorContext.t`): error messages, the 429 notice and the snippets of proposed operations. */\n t: Translate\n}\n\nexport interface DocumentChat {\n state: ReadonlyStore<DocumentChatState>\n /**\n * Sends `message` about the editor's current document; `scope` limits proposed edits to the selected blocks (null:\n * whole document). Returns false when it was not answered (error, rate limit, cancelled before any answer): it is\n * then removed again so the caller can restore the input.\n */\n ask: (message: string, editor: Editor, scope: AgentScope | null) => Promise<boolean>\n abort: () => void\n reset: () => void\n /** Accepts or rejects the open hunks of operation `id`. */\n decide: (editor: Editor, id: string, accept: boolean) => void\n /** Scrolls to operation `op`: its first open hunk (selected for accept/reject), else its target block. */\n reveal: (editor: Editor, op: AgentOp) => boolean\n /** Appends the assistant turn of a block generation. `generation` keeps its identity in `state`. */\n addGeneration: (generation: DocumentChatGeneration) => void\n /** Updates `generation` in place (same object) and publishes a new `messages` array. */\n updateGeneration: (generation: DocumentChatGeneration, patch: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) => void\n /** Removes the turn of `generation`. */\n removeGeneration: (generation: DocumentChatGeneration) => void\n clearError: () => void\n}\n\nconst EMPTY: DocumentChatState = { messages: [], ops: NO_AGENT_OPS, streaming: false, error: null }\n\n/**\n * Document chat state: the conversation lives only here (client side) and goes to `/api/ai/chat` in full with every\n * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an\n * in-document review; their decisions go to the model with the next message.\n */\nexport function createDocumentChat({ fetch, modelRef, notify, t }: DocumentChatOptions): DocumentChat {\n const store = createStore<DocumentChatState>(EMPTY)\n const review = createAgentOpReview({ notify: silentNotifier, t })\n let controller: AbortController | null = null\n\n const patch = (changes: Partial<DocumentChatState>) => store.set({ ...store.get(), ...changes })\n\n function abort() {\n controller?.abort()\n }\n\n function reset() {\n abort()\n controller = null\n store.set(EMPTY)\n review.reset()\n }\n\n /** Replaces the turn `turn` (by identity) with `next`; returns `next`. */\n function replaceTurn<T extends DocumentChatTurn>(turn: DocumentChatTurn, next: T): T {\n const { messages } = store.get()\n const index = messages.indexOf(turn)\n if (index !== -1) patch({ messages: messages.map((m, i) => (i === index ? next : m)) })\n return next\n }\n\n // The proposed operations live in the review (the chat shows their outcomes inline, so it needs no extra notices); its state is the chat's `ops`.\n review.state.subscribe(({ ops }) => {\n if (ops !== store.get().ops) patch({ ops })\n })\n\n async function ask(message: string, editor: Editor, scope: AgentScope | null): Promise<boolean> {\n if (store.get().streaming || !message.trim()) return false\n if (scope && !scope.ids.length) {\n patch({ error: t('ai.chat.emptyScope') })\n return false\n }\n // The block model of the system prompt: the editor's `aiContext` kinds and host blocks, and what `convert` may turn blocks into.\n // Read before any state changes: a merge error in the contributions throws here and must not leave the chat streaming.\n const { kinds, blocks, structure } = aiContextOf(editor)\n const providers = conversionsOf(editor)\n const targets = [\n ...CORE_TARGET_IDS,\n ...kinds.map(kind => convertTargetId({ type: 'composite', kind: kind.id })),\n ...blocks.filter(block => parseConversionTarget(providers, block.id) !== null).map(block => block.id),\n ]\n const own = new AbortController()\n controller = own\n const feedback = review.unreported()\n const userTurn: DocumentChatTurn = { role: 'user', content: message.trim(), ...(feedback.length ? { feedback } : {}) }\n let answer: Extract<DocumentChatTurn, { role: 'assistant' }> = { role: 'assistant', content: '' }\n // The newest chat turns (block generations are the sidebar's own); the request must start with a user message.\n const sent: DocumentChatMessage[] = [...store.get().messages, userTurn].filter(turn => turn.role === 'user' || !turn.generation).slice(-CHAT_LIMITS.messages).map(turn => turn.role === 'user'\n ? { role: 'user', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.feedback && { feedback: turn.feedback }) }\n : { role: 'assistant', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.ops?.length ? { ops: turn.ops } : {}), ...(turn.dropped ? { dropped: turn.dropped } : {}) })\n if (sent[0]?.role === 'assistant') sent.shift()\n patch({ error: null, streaming: true, messages: [...store.get().messages, userTurn, answer] })\n const received: { proposal?: { ops: AgentOp[], dropped: AgentOpProblem[] } } = {}\n const body = { messages: sent, document: documentChatText(editor), scope: scope?.ids ?? null, nextOp: review.nextOp(), model: modelRef('generate'), kinds, blocks, targets, structure }\n const outcome = await postAiSse(fetch, '/api/ai/chat', body, {\n signal: own.signal,\n t,\n onDelta: (text) => {\n if (controller === own) answer = replaceTurn(answer, { ...answer, content: answer.content + text })\n },\n onEvent: (event, data) => {\n if (event === 'ops' && controller === own) received.proposal = data as typeof received.proposal\n },\n })\n if (controller !== own) return false // superseded by reset()\n controller = null\n patch({ streaming: false })\n const { ops: proposed = [], dropped = [] } = outcome.status === 'done' ? received.proposal ?? {} : {}\n if (outcome.status === 'done' || (outcome.status === 'aborted' && answer.content)) {\n review.markReported(feedback.filter(({ outcome: sentOutcome }) => sentOutcome !== 'pending').map(({ op }) => op))\n if (proposed.length || dropped.length) {\n answer = replaceTurn(answer, { ...answer, ops: proposed, dropped: dropped.length, problems: dropped })\n review.advanceOp(proposed.length)\n if (!editor.isDestroyed) review.propose(editor, proposed, scope)\n }\n return true\n }\n // Drop the unanswered turn: a later message should not carry a dangling one in its history.\n patch({ messages: store.get().messages.slice(0, -2) })\n if (outcome.status === 'rate-limited') notifyAiRateLimited(notify, t)\n else if (outcome.status === 'error') patch({ error: outcome.message })\n return false\n }\n\n const decide = review.decide\n const reveal = review.reveal\n\n function addGeneration(generation: DocumentChatGeneration) {\n patch({ error: null, messages: [...store.get().messages, { role: 'assistant', content: '', generation }] })\n }\n\n function updateGeneration(generation: DocumentChatGeneration, changes: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) {\n Object.assign(generation, changes)\n const { messages } = store.get()\n patch({ messages: messages.map(turn => turn.role === 'assistant' && turn.generation === generation ? { ...turn } : turn) })\n }\n\n function removeGeneration(generation: DocumentChatGeneration) {\n const { messages } = store.get()\n const index = messages.findIndex(turn => turn.role === 'assistant' && turn.generation === generation)\n if (index !== -1) patch({ messages: messages.filter((_, i) => i !== index) })\n }\n\n function clearError() {\n if (store.get().error !== null) patch({ error: null })\n }\n\n return { state: store, ask, abort, reset, decide, reveal, addGeneration, updateGeneration, removeGeneration, clearError }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"document-chat-model.js","names":[],"sources":["../../../src/features/ai/document-chat-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport { aiContextOf, CORE_TARGET_IDS, convertTargetId, conversionsOf, parseConversionTarget } from '../../core'\nimport { type Notifier, silentNotifier } from '../../core/notify'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport type { Translate } from '../../i18n'\nimport type { ModelSlot } from '../../settings'\nimport { type AgentOpReviewState, createAgentOpReview, NO_AGENT_OPS } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport type { AiTarget } from './ai-apply'\nimport { postAiSse } from './ai-sse'\nimport { documentChatText } from './document-chat'\nimport { type AgentOp, type AgentOpProblem, CHAT_LIMITS, type AiTask, type DocumentChatMessage, type ModelRef } from './protocol'\nimport { notifyAiRateLimited } from './rate-limit'\n\n/**\n * A block generation run from the AI sidebar's quick actions, shown as an assistant turn. Client only: it never goes\n * to the `chat` stream. `markdown` is the streamed result without the task heading (shown with `withTaskHeading`, applied\n * as `taskResultMarkdown`), `target` the apply target captured when it started; only a result that streamed to the end\n * (`done`) can be applied.\n */\nexport interface DocumentChatGeneration {\n task: AiTask\n /** The instruction of a task with `needsPrompt` (empty otherwise). */\n prompt: string\n markdown: string\n target: AiTarget\n done: boolean\n}\n\n/**\n * A chat turn as shown: an assistant turn also keeps the problems of the operations the server dropped, or is a block\n * generation of the sidebar (`generation`).\n */\nexport type DocumentChatTurn =\n | Extract<DocumentChatMessage, { role: 'user' }>\n | (Extract<DocumentChatMessage, { role: 'assistant' }> & { problems?: AgentOpProblem[], generation?: DocumentChatGeneration })\n\n/** Snapshot of a conversation; every change replaces `messages`/`ops` (never mutated in place). */\nexport interface DocumentChatState {\n messages: readonly DocumentChatTurn[]\n /** Operation states by operation id. */\n ops: AgentOpReviewState['ops']\n streaming: boolean\n error: string | null\n}\n\nexport interface DocumentChatOptions {\n /** `EditorContext.backend.ai`; without it every question ends in the error state (501), like a server without AI. */\n ai: EditorBackend['ai']\n modelRef: (slot: ModelSlot) => ModelRef\n /** Receives the 429 notice. */\n notify: Notifier\n /** The app's translator (`EditorContext.t`): error messages, the 429 notice and the snippets of proposed operations. */\n t: Translate\n}\n\nexport interface DocumentChat {\n state: ReadonlyStore<DocumentChatState>\n /**\n * Sends `message` about the editor's current document; `scope` limits proposed edits to the selected blocks (null:\n * whole document). Returns false when it was not answered (error, rate limit, cancelled before any answer): it is\n * then removed again so the caller can restore the input.\n */\n ask: (message: string, editor: Editor, scope: AgentScope | null) => Promise<boolean>\n abort: () => void\n reset: () => void\n /** Accepts or rejects the open hunks of operation `id`. */\n decide: (editor: Editor, id: string, accept: boolean) => void\n /** Scrolls to operation `op`: its first open hunk (selected for accept/reject), else its target block. */\n reveal: (editor: Editor, op: AgentOp) => boolean\n /** Appends the assistant turn of a block generation. `generation` keeps its identity in `state`. */\n addGeneration: (generation: DocumentChatGeneration) => void\n /** Updates `generation` in place (same object) and publishes a new `messages` array. */\n updateGeneration: (generation: DocumentChatGeneration, patch: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) => void\n /** Removes the turn of `generation`. */\n removeGeneration: (generation: DocumentChatGeneration) => void\n clearError: () => void\n}\n\nconst EMPTY: DocumentChatState = { messages: [], ops: NO_AGENT_OPS, streaming: false, error: null }\n\n/**\n * Document chat state: the conversation lives only here (client side) and goes to the backend's `chat` stream in full with every\n * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an\n * in-document review; their decisions go to the model with the next message.\n */\nexport function createDocumentChat({ ai, modelRef, notify, t }: DocumentChatOptions): DocumentChat {\n const store = createStore<DocumentChatState>(EMPTY)\n const review = createAgentOpReview({ notify: silentNotifier, t })\n let controller: AbortController | null = null\n\n const patch = (changes: Partial<DocumentChatState>) => store.set({ ...store.get(), ...changes })\n\n function abort() {\n controller?.abort()\n }\n\n function reset() {\n abort()\n controller = null\n store.set(EMPTY)\n review.reset()\n }\n\n /** Replaces the turn `turn` (by identity) with `next`; returns `next`. */\n function replaceTurn<T extends DocumentChatTurn>(turn: DocumentChatTurn, next: T): T {\n const { messages } = store.get()\n const index = messages.indexOf(turn)\n if (index !== -1) patch({ messages: messages.map((m, i) => (i === index ? next : m)) })\n return next\n }\n\n // The proposed operations live in the review (the chat shows their outcomes inline, so it needs no extra notices); its state is the chat's `ops`.\n review.state.subscribe(({ ops }) => {\n if (ops !== store.get().ops) patch({ ops })\n })\n\n async function ask(message: string, editor: Editor, scope: AgentScope | null): Promise<boolean> {\n if (store.get().streaming || !message.trim()) return false\n if (scope && !scope.ids.length) {\n patch({ error: t('ai.chat.emptyScope') })\n return false\n }\n // The block model of the system prompt: the editor's `aiContext` kinds and host blocks, and what `convert` may turn blocks into.\n // Read before any state changes: a merge error in the contributions throws here and must not leave the chat streaming.\n const { kinds, blocks, structure } = aiContextOf(editor)\n const providers = conversionsOf(editor)\n const targets = [\n ...CORE_TARGET_IDS,\n ...kinds.map(kind => convertTargetId({ type: 'composite', kind: kind.id })),\n ...blocks.filter(block => parseConversionTarget(providers, block.id) !== null).map(block => block.id),\n ]\n const own = new AbortController()\n controller = own\n const feedback = review.unreported()\n const userTurn: DocumentChatTurn = { role: 'user', content: message.trim(), ...(feedback.length ? { feedback } : {}) }\n let answer: Extract<DocumentChatTurn, { role: 'assistant' }> = { role: 'assistant', content: '' }\n // The newest chat turns (block generations are the sidebar's own); the request must start with a user message.\n const sent: DocumentChatMessage[] = [...store.get().messages, userTurn].filter(turn => turn.role === 'user' || !turn.generation).slice(-CHAT_LIMITS.messages).map(turn => turn.role === 'user'\n ? { role: 'user', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.feedback && { feedback: turn.feedback }) }\n : { role: 'assistant', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.ops?.length ? { ops: turn.ops } : {}), ...(turn.dropped ? { dropped: turn.dropped } : {}) })\n if (sent[0]?.role === 'assistant') sent.shift()\n patch({ error: null, streaming: true, messages: [...store.get().messages, userTurn, answer] })\n const received: { proposal?: { ops: AgentOp[], dropped: AgentOpProblem[] } } = {}\n const body = { messages: sent, document: documentChatText(editor), scope: scope?.ids ?? null, nextOp: review.nextOp(), model: modelRef('generate'), kinds, blocks, targets, structure }\n const outcome = await postAiSse(ai, 'chat', body, {\n signal: own.signal,\n t,\n onDelta: (text) => {\n if (controller === own) answer = replaceTurn(answer, { ...answer, content: answer.content + text })\n },\n onEvent: (event, data) => {\n if (event === 'ops' && controller === own) received.proposal = data as typeof received.proposal\n },\n })\n if (controller !== own) return false // superseded by reset()\n controller = null\n patch({ streaming: false })\n const { ops: proposed = [], dropped = [] } = outcome.status === 'done' ? received.proposal ?? {} : {}\n if (outcome.status === 'done' || (outcome.status === 'aborted' && answer.content)) {\n review.markReported(feedback.filter(({ outcome: sentOutcome }) => sentOutcome !== 'pending').map(({ op }) => op))\n if (proposed.length || dropped.length) {\n answer = replaceTurn(answer, { ...answer, ops: proposed, dropped: dropped.length, problems: dropped })\n review.advanceOp(proposed.length)\n if (!editor.isDestroyed) review.propose(editor, proposed, scope)\n }\n return true\n }\n // Drop the unanswered turn: a later message should not carry a dangling one in its history.\n patch({ messages: store.get().messages.slice(0, -2) })\n if (outcome.status === 'rate-limited') notifyAiRateLimited(notify, t)\n else if (outcome.status === 'error') patch({ error: outcome.message })\n return false\n }\n\n const decide = review.decide\n const reveal = review.reveal\n\n function addGeneration(generation: DocumentChatGeneration) {\n patch({ error: null, messages: [...store.get().messages, { role: 'assistant', content: '', generation }] })\n }\n\n function updateGeneration(generation: DocumentChatGeneration, changes: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) {\n Object.assign(generation, changes)\n const { messages } = store.get()\n patch({ messages: messages.map(turn => turn.role === 'assistant' && turn.generation === generation ? { ...turn } : turn) })\n }\n\n function removeGeneration(generation: DocumentChatGeneration) {\n const { messages } = store.get()\n const index = messages.findIndex(turn => turn.role === 'assistant' && turn.generation === generation)\n if (index !== -1) patch({ messages: messages.filter((_, i) => i !== index) })\n }\n\n function clearError() {\n if (store.get().error !== null) patch({ error: null })\n }\n\n return { state: store, ask, abort, reset, decide, reveal, addGeneration, updateGeneration, removeGeneration, clearError }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
+ import { parseBlocks } from "../../core/pipeline.js";
1
2
  import { blockIdOf, findBlockById } from "../../core/schema/block-ids.js";
2
3
  import { isBlockContainer, isChromeNode } from "../../core/schema/node-flags.js";
3
4
  import { isWrapperBlock } from "../../core/convert.js";
4
- import { parseBlocks } from "../../core/pipeline.js";
5
5
  import { TRUNCATED_LINE, blockLine, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, listLinePrefix, nestedLinePrefix, quoteLinePrefix, singleBlockLine } from "./protocol/block-lines.js";
6
6
  import { CHAT_LIMITS, CITATION_HREF_PREFIX, citationBlockId } from "./protocol/chat.js";
7
7
  import { Fragment } from "@tiptap/pm/model";
@@ -1,6 +1,6 @@
1
1
  import { AiReview } from "./ai-review.js";
2
2
  import { AiSelectionHighlight } from "./ai-selection-highlight.js";
3
- import { aiBlockAction, aiSlashCommand } from "./block-actions.js";
3
+ import { aiBlockAction, aiCommand } from "./block-actions.js";
4
4
  import { completionFetcher, ghostAutocomplete } from "./ghost-autocomplete.js";
5
5
  import { modelProfiles } from "./model-profiles.js";
6
6
  import { Extension } from "@tiptap/core";
@@ -16,13 +16,19 @@ function sidebarShortcut(ctx) {
16
16
  } })
17
17
  });
18
18
  }
19
- function aiToolbar(ctx) {
20
- return [{
21
- id: "ai",
22
- order: 1e3,
23
- items: [{
19
+ const aiToolbarGroups = [{
20
+ id: "ai",
21
+ order: 1e3,
22
+ labelKey: "palette.group.ai"
23
+ }];
24
+ function aiCommands(ctx) {
25
+ return [
26
+ {
24
27
  id: "sidebar",
25
- order: 10,
28
+ toolbar: {
29
+ group: "ai",
30
+ order: 10
31
+ },
26
32
  icon: "i-lucide-panel-right",
27
33
  label: ctx.t("ai.toolbar.sidebar"),
28
34
  kbds: [
@@ -31,9 +37,13 @@ function aiToolbar(ctx) {
31
37
  "k"
32
38
  ],
33
39
  run: () => ctx.ui.toggleSidebar()
34
- }, {
40
+ },
41
+ {
35
42
  id: "autocomplete",
36
- order: 20,
43
+ toolbar: {
44
+ group: "ai",
45
+ order: 20
46
+ },
37
47
  icon: "i-lucide-wand-sparkles",
38
48
  label: ctx.t("ai.toolbar.autocomplete"),
39
49
  active: () => ctx.settings.get().ai.autocomplete,
@@ -42,8 +52,9 @@ function aiToolbar(ctx) {
42
52
  s.ai.autocomplete = !s.ai.autocomplete;
43
53
  });
44
54
  }
45
- }]
46
- }];
55
+ },
56
+ aiCommand(ctx)
57
+ ];
47
58
  }
48
59
  /**
49
60
  * AI on a core editor: the selection menu and `/ai` (rewrite and generate at the caret), in-document review of
@@ -58,6 +69,8 @@ function aiToolbar(ctx) {
58
69
  function aiFeatureCore(ui) {
59
70
  return {
60
71
  id: "ai",
72
+ dimension: "ai",
73
+ backend: ["ai"],
61
74
  extensions: (ctx) => [
62
75
  AiSelectionHighlight,
63
76
  AiReview,
@@ -65,15 +78,15 @@ function aiFeatureCore(ui) {
65
78
  settings: ctx.settings,
66
79
  debounce: GHOST_DEBOUNCE,
67
80
  fetchSuggestion: completionFetcher({
68
- fetch: ctx.fetch,
81
+ ai: ctx.backend.ai,
69
82
  modelRef: modelProfiles(ctx.app).modelRef
70
83
  })
71
84
  }),
72
85
  sidebarShortcut(ctx)
73
86
  ],
74
- toolbar: aiToolbar,
87
+ toolbarGroups: aiToolbarGroups,
88
+ commands: aiCommands,
75
89
  blockActions: (ctx) => [aiBlockAction(ctx)],
76
- slashCommands: (ctx) => [aiSlashCommand(ctx)],
77
90
  overlays: [ui.selectionMenu, ui.reviewBar],
78
91
  settingsTabs: (ctx) => [{
79
92
  id: "ai",
@@ -1 +1 @@
1
- {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/ai/feature.ts"],"sourcesContent":["import { type AnyExtension, Extension } from '@tiptap/core'\nimport type { EditorFeature, FeatureContext, ToolbarGroup } from '../../core'\nimport { AiReview } from './ai-review'\nimport { AiSelectionHighlight } from './ai-selection-highlight'\nimport { aiBlockAction, aiSlashCommand } from './block-actions'\nimport { completionFetcher, ghostAutocomplete } from './ghost-autocomplete'\nimport { modelProfiles } from './model-profiles'\n\n/** What a framework binding supplies to the AI feature: the components of its UI layer. */\nexport interface AiUi<C> {\n /** The selection bubble menu and the caret menu (rewrite, translate, generate; preview and apply). */\n selectionMenu: C\n /** The accept/reject bar of the in-document review. */\n reviewBar: C\n /** The „KI“ settings tab component of the framework. */\n settingsTab: C\n}\n\n/** Pause after the last keystroke before an autocomplete suggestion is requested (ms). */\nconst GHOST_DEBOUNCE = 500\n\n// Mod-Shift-k toggles this editor's AI sidebar while the editor has focus (shown on the toolbar's „KI-Seitenleiste“).\nfunction sidebarShortcut(ctx: FeatureContext): AnyExtension {\n return Extension.create({\n name: 'aiSidebarShortcut',\n addKeyboardShortcuts: () => ({\n 'Mod-Shift-k': () => {\n ctx.ui.toggleSidebar()\n return true\n },\n }),\n })\n}\n\nfunction aiToolbar<C>(ctx: FeatureContext): ToolbarGroup<C>[] {\n return [{\n id: 'ai',\n order: 1000,\n items: [\n {\n id: 'sidebar',\n order: 10,\n icon: 'i-lucide-panel-right',\n label: ctx.t('ai.toolbar.sidebar'),\n kbds: ['meta', 'shift', 'k'],\n run: () => ctx.ui.toggleSidebar(),\n },\n {\n id: 'autocomplete',\n order: 20,\n icon: 'i-lucide-wand-sparkles',\n label: ctx.t('ai.toolbar.autocomplete'),\n active: () => ctx.settings.get().ai.autocomplete,\n run: () => { ctx.settings.update((s) => { s.ai.autocomplete = !s.ai.autocomplete }) },\n },\n ],\n }]\n}\n\n/**\n * AI on a core editor: the selection menu and `/ai` (rewrite and generate at the caret), in-document review of\n * rewrites, ghost autocomplete, the model profiles and the `ai` settings tab, the „Mit KI bearbeiten“ block action, the\n * toolbar group (sidebar toggle, autocomplete switch) and Mod-Shift-k. The sidebar (`AiSidebar`) and the model switch\n * are components the host places. Reads the document model through the merged `aiContext` (kinds, host blocks,\n * structure) and the `formatAiResult` contribution of other\n * features (defaults: no kinds, no blocks, flat structure) and maps positions through the preset's anchor resolver, so it imports\n * no composites, transcript, media, mentions, collaboration or Yjs code. The model profiles come from the app context\n * (`modelProfiles(ctx.app)`), shared by all editors of the app.\n */\nexport function aiFeatureCore<C>(ui: AiUi<C>): EditorFeature<C> {\n return {\n id: 'ai',\n extensions: ctx => [\n AiSelectionHighlight,\n AiReview,\n ghostAutocomplete({\n settings: ctx.settings,\n debounce: GHOST_DEBOUNCE,\n fetchSuggestion: completionFetcher({ fetch: ctx.fetch, modelRef: modelProfiles(ctx.app).modelRef }),\n }),\n sidebarShortcut(ctx),\n ],\n toolbar: aiToolbar<C>,\n blockActions: ctx => [aiBlockAction(ctx)],\n slashCommands: ctx => [aiSlashCommand(ctx)],\n overlays: [ui.selectionMenu, ui.reviewBar],\n settingsTabs: ctx => [{ id: 'ai', label: ctx.t('ai.tab'), icon: 'i-lucide-sparkles', component: ui.settingsTab }],\n }\n}\n"],"mappings":";;;;;;;;AAmBA,MAAM,iBAAiB;AAGvB,SAAS,gBAAgB,KAAmC;CAC1D,OAAO,UAAU,OAAO;EACtB,MAAM;EACN,6BAA6B,EAC3B,qBAAqB;GACnB,IAAI,GAAG,cAAc;GACrB,OAAO;EACT,EACF;CACF,CAAC;AACH;AAEA,SAAS,UAAa,KAAwC;CAC5D,OAAO,CAAC;EACN,IAAI;EACJ,OAAO;EACP,OAAO,CACL;GACE,IAAI;GACJ,OAAO;GACP,MAAM;GACN,OAAO,IAAI,EAAE,oBAAoB;GACjC,MAAM;IAAC;IAAQ;IAAS;GAAG;GAC3B,WAAW,IAAI,GAAG,cAAc;EAClC,GACA;GACE,IAAI;GACJ,OAAO;GACP,MAAM;GACN,OAAO,IAAI,EAAE,yBAAyB;GACtC,cAAc,IAAI,SAAS,IAAI,CAAC,CAAC,GAAG;GACpC,WAAW;IAAE,IAAI,SAAS,QAAQ,MAAM;KAAE,EAAE,GAAG,eAAe,CAAC,EAAE,GAAG;IAAa,CAAC;GAAE;EACtF,CACF;CACF,CAAC;AACH;;;;;;;;;;;AAYA,SAAgB,cAAiB,IAA+B;CAC9D,OAAO;EACL,IAAI;EACJ,aAAY,QAAO;GACjB;GACA;GACA,kBAAkB;IAChB,UAAU,IAAI;IACd,UAAU;IACV,iBAAiB,kBAAkB;KAAE,OAAO,IAAI;KAAO,UAAU,cAAc,IAAI,GAAG,CAAC,CAAC;IAAS,CAAC;GACpG,CAAC;GACD,gBAAgB,GAAG;EACrB;EACA,SAAS;EACT,eAAc,QAAO,CAAC,cAAc,GAAG,CAAC;EACxC,gBAAe,QAAO,CAAC,eAAe,GAAG,CAAC;EAC1C,UAAU,CAAC,GAAG,eAAe,GAAG,SAAS;EACzC,eAAc,QAAO,CAAC;GAAE,IAAI;GAAM,OAAO,IAAI,EAAE,QAAQ;GAAG,MAAM;GAAqB,WAAW,GAAG;EAAY,CAAC;CAClH;AACF"}
1
+ {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/ai/feature.ts"],"sourcesContent":["import { type AnyExtension, Extension } from '@tiptap/core'\nimport type { CommandDef, EditorFeature, FeatureContext, ToolbarGroupDef } from '../../core'\nimport { AiReview } from './ai-review'\nimport { AiSelectionHighlight } from './ai-selection-highlight'\nimport { aiBlockAction, aiCommand } from './block-actions'\nimport { completionFetcher, ghostAutocomplete } from './ghost-autocomplete'\nimport { modelProfiles } from './model-profiles'\n\n/** What a framework binding supplies to the AI feature: the components of its UI layer. */\nexport interface AiUi<C> {\n /** The selection bubble menu and the caret menu (rewrite, translate, generate; preview and apply). */\n selectionMenu: C\n /** The accept/reject bar of the in-document review. */\n reviewBar: C\n /** The „KI“ settings tab component of the framework. */\n settingsTab: C\n}\n\n/** Pause after the last keystroke before an autocomplete suggestion is requested (ms). */\nconst GHOST_DEBOUNCE = 500\n\n// Mod-Shift-k toggles this editor's AI sidebar while the editor has focus (shown on the toolbar's „KI-Seitenleiste“).\nfunction sidebarShortcut(ctx: FeatureContext): AnyExtension {\n return Extension.create({\n name: 'aiSidebarShortcut',\n addKeyboardShortcuts: () => ({\n 'Mod-Shift-k': () => {\n ctx.ui.toggleSidebar()\n return true\n },\n }),\n })\n}\n\nconst aiToolbarGroups: ToolbarGroupDef[] = [{ id: 'ai', order: 1000, labelKey: 'palette.group.ai' }]\n\nfunction aiCommands<C>(ctx: FeatureContext): CommandDef<C>[] {\n return [\n {\n id: 'sidebar',\n toolbar: { group: 'ai', order: 10 },\n icon: 'i-lucide-panel-right',\n label: ctx.t('ai.toolbar.sidebar'),\n kbds: ['meta', 'shift', 'k'],\n run: () => ctx.ui.toggleSidebar(),\n },\n {\n id: 'autocomplete',\n toolbar: { group: 'ai', order: 20 },\n icon: 'i-lucide-wand-sparkles',\n label: ctx.t('ai.toolbar.autocomplete'),\n active: () => ctx.settings.get().ai.autocomplete,\n run: () => { ctx.settings.update((s) => { s.ai.autocomplete = !s.ai.autocomplete }) },\n },\n aiCommand(ctx),\n ]\n}\n\n/**\n * AI on a core editor: the selection menu and `/ai` (rewrite and generate at the caret), in-document review of\n * rewrites, ghost autocomplete, the model profiles and the `ai` settings tab, the „Mit KI bearbeiten“ block action, the\n * toolbar group (sidebar toggle, autocomplete switch) and Mod-Shift-k. The sidebar (`AiSidebar`) and the model switch\n * are components the host places. Reads the document model through the merged `aiContext` (kinds, host blocks,\n * structure) and the `formatAiResult` contribution of other\n * features (defaults: no kinds, no blocks, flat structure) and maps positions through the preset's anchor resolver, so it imports\n * no composites, transcript, media, mentions, collaboration or Yjs code. The model profiles come from the app context\n * (`modelProfiles(ctx.app)`), shared by all editors of the app.\n */\nexport function aiFeatureCore<C>(ui: AiUi<C>): EditorFeature<C> {\n return {\n id: 'ai',\n dimension: 'ai',\n backend: ['ai'],\n extensions: ctx => [\n AiSelectionHighlight,\n AiReview,\n ghostAutocomplete({\n settings: ctx.settings,\n debounce: GHOST_DEBOUNCE,\n fetchSuggestion: completionFetcher({ ai: ctx.backend.ai, modelRef: modelProfiles(ctx.app).modelRef }),\n }),\n sidebarShortcut(ctx),\n ],\n toolbarGroups: aiToolbarGroups,\n commands: aiCommands<C>,\n blockActions: ctx => [aiBlockAction(ctx)],\n overlays: [ui.selectionMenu, ui.reviewBar],\n settingsTabs: ctx => [{ id: 'ai', label: ctx.t('ai.tab'), icon: 'i-lucide-sparkles', component: ui.settingsTab }],\n }\n}\n"],"mappings":";;;;;;;;AAmBA,MAAM,iBAAiB;AAGvB,SAAS,gBAAgB,KAAmC;CAC1D,OAAO,UAAU,OAAO;EACtB,MAAM;EACN,6BAA6B,EAC3B,qBAAqB;GACnB,IAAI,GAAG,cAAc;GACrB,OAAO;EACT,EACF;CACF,CAAC;AACH;AAEA,MAAM,kBAAqC,CAAC;CAAE,IAAI;CAAM,OAAO;CAAM,UAAU;AAAmB,CAAC;AAEnG,SAAS,WAAc,KAAsC;CAC3D,OAAO;EACL;GACE,IAAI;GACJ,SAAS;IAAE,OAAO;IAAM,OAAO;GAAG;GAClC,MAAM;GACN,OAAO,IAAI,EAAE,oBAAoB;GACjC,MAAM;IAAC;IAAQ;IAAS;GAAG;GAC3B,WAAW,IAAI,GAAG,cAAc;EAClC;EACA;GACE,IAAI;GACJ,SAAS;IAAE,OAAO;IAAM,OAAO;GAAG;GAClC,MAAM;GACN,OAAO,IAAI,EAAE,yBAAyB;GACtC,cAAc,IAAI,SAAS,IAAI,CAAC,CAAC,GAAG;GACpC,WAAW;IAAE,IAAI,SAAS,QAAQ,MAAM;KAAE,EAAE,GAAG,eAAe,CAAC,EAAE,GAAG;IAAa,CAAC;GAAE;EACtF;EACA,UAAU,GAAG;CACf;AACF;;;;;;;;;;;AAYA,SAAgB,cAAiB,IAA+B;CAC9D,OAAO;EACL,IAAI;EACJ,WAAW;EACX,SAAS,CAAC,IAAI;EACd,aAAY,QAAO;GACjB;GACA;GACA,kBAAkB;IAChB,UAAU,IAAI;IACd,UAAU;IACV,iBAAiB,kBAAkB;KAAE,IAAI,IAAI,QAAQ;KAAI,UAAU,cAAc,IAAI,GAAG,CAAC,CAAC;IAAS,CAAC;GACtG,CAAC;GACD,gBAAgB,GAAG;EACrB;EACA,eAAe;EACf,UAAU;EACV,eAAc,QAAO,CAAC,cAAc,GAAG,CAAC;EACxC,UAAU,CAAC,GAAG,eAAe,GAAG,SAAS;EACzC,eAAc,QAAO,CAAC;GAAE,IAAI;GAAM,OAAO,IAAI,EAAE,QAAQ;GAAG,MAAM;GAAqB,WAAW,GAAG;EAAY,CAAC;CAClH;AACF"}
@@ -1,11 +1,11 @@
1
+ import { Store } from "../../core/store.js";
1
2
  import { ModelSlot } from "../../settings/values.js";
2
3
  import { EditorSettings } from "../../settings/index.js";
3
- import { Store } from "../../core/store.js";
4
- import { ModelRef } from "./protocol/models.js";
4
+ import { EditorBackend } from "../../core/backend.js";
5
+ import { ModelRef } from "../../core/backend-types.js";
5
6
  import "./protocol/index.js";
6
7
  import { Extension } from "@tiptap/core";
7
8
  import { PluginKey } from "@tiptap/pm/state";
8
- import { $Fetch } from "ofetch";
9
9
  //#region src/features/ai/ghost-autocomplete.d.ts
10
10
  export interface GhostAutocompleteOptions {
11
11
  /** The app's shared settings: `ai.autocomplete` switches the suggestions on. */
@@ -27,12 +27,13 @@ interface GhostState {
27
27
  activity: number;
28
28
  }
29
29
  export declare const ghostAutocompleteKey: PluginKey<GhostState>;
30
- /** What the suggestion source needs of the app: its HTTP client and the model profile of a slot (`useModelProfiles`). */
30
+ /** What the suggestion source needs of the app: its AI port and the model profile of a slot (`useModelProfiles`). */
31
31
  export interface CompletionHost {
32
- fetch: $Fetch;
32
+ /** `EditorContext.backend.ai`; without it there are no suggestions. */
33
+ ai: EditorBackend['ai'];
33
34
  modelRef: (slot: ModelSlot) => ModelRef;
34
35
  }
35
- /** The suggestion source of the app behind `host`: `POST /api/ai/complete` on the profile of the `complete` slot. */
36
+ /** The suggestion source of the app behind `host`: `ai.complete` on the profile of the `complete` slot. */
36
37
  export declare function completionFetcher(host: CompletionHost): GhostAutocompleteOptions['fetchSuggestion'];
37
38
  /**
38
39
  * Inline AI autocomplete: after a typing pause at the end of a non-empty textblock, a short continuation is shown as grey
@@ -28,17 +28,14 @@ function mapSuggestion(suggestion, tr, oldState, newState) {
28
28
  pos: mapped.pos
29
29
  };
30
30
  }
31
- /** The suggestion source of the app behind `host`: `POST /api/ai/complete` on the profile of the `complete` slot. */
31
+ /** The suggestion source of the app behind `host`: `ai.complete` on the profile of the `complete` slot. */
32
32
  function completionFetcher(host) {
33
33
  return async (before, signal) => {
34
- const { text } = await host.fetch("/api/ai/complete", {
35
- method: "POST",
36
- body: {
37
- before,
38
- model: host.modelRef("complete")
39
- },
40
- signal
41
- });
34
+ if (!host.ai) return "";
35
+ const { text } = await host.ai.complete({
36
+ before,
37
+ model: host.modelRef("complete")
38
+ }, signal);
42
39
  return text;
43
40
  };
44
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ghost-autocomplete.js","names":[],"sources":["../../../src/features/ai/ghost-autocomplete.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { type EditorState, Plugin, PluginKey, TextSelection, type Transaction } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'\nimport type { Store } from '../../core/store'\nimport { anchorResolverOf, hasAbility } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport type { EditorSettings, ModelSlot } from '../../settings'\nimport type { $Fetch } from 'ofetch'\nimport { AI_COMPLETE_LIMITS, type ModelRef } from './protocol'\n\nexport interface GhostAutocompleteOptions {\n /** The app's shared settings: `ai.autocomplete` switches the suggestions on. */\n settings: Store<EditorSettings>\n /** Pause after the last keystroke before a suggestion is requested (ms). */\n debounce: number\n /** Fetches a continuation of `before` (document text up to the caret); '' for none. Must honour `signal`. */\n fetchSuggestion: (before: string, signal: AbortSignal) => Promise<string>\n}\n\ninterface Suggestion { text: string, pos: number }\ninterface GhostState {\n suggestion: Suggestion | null\n /** Bumped by local edits: each bump starts a new pause, so at most one request follows it. */\n edits: number\n /** Bumped by local edits and caret moves: an in-flight request is stale once this changed. */\n activity: number\n}\ntype GhostMeta = { type: 'show', suggestion: Suggestion } | { type: 'clear' }\n\nexport const ghostAutocompleteKey = new PluginKey<GhostState>('ghostAutocomplete')\n\n/** Text of the caret's textblock, when the caret is an empty selection at the end of a non-empty, non-code textblock. */\nfunction blockTextAtEnd(state: EditorState): string | null {\n const { selection } = state\n if (!(selection instanceof TextSelection) || !selection.empty) return null\n const { $head } = selection\n const block = $head.parent\n if (!block.isTextblock || block.type.spec.code || $head.parentOffset !== block.content.size) return null\n const text = block.textContent\n return text.trim() ? text : null\n}\n\n/** Keeps a suggestion across a collaborator's change only if the caret block is untouched and the caret stayed with it. */\nfunction mapSuggestion(suggestion: Suggestion, tr: Transaction, oldState: EditorState, newState: EditorState): Suggestion | null {\n const mapped = tr.mapping.mapResult(suggestion.pos)\n if (mapped.deleted || newState.selection.head !== mapped.pos) return null\n const text = blockTextAtEnd(newState)\n if (text === null || text !== oldState.doc.resolve(suggestion.pos).parent.textContent) return null\n return { text: suggestion.text, pos: mapped.pos }\n}\n\n/** What the suggestion source needs of the app: its HTTP client and the model profile of a slot (`useModelProfiles`). */\nexport interface CompletionHost {\n fetch: $Fetch\n modelRef: (slot: ModelSlot) => ModelRef\n}\n\n/** The suggestion source of the app behind `host`: `POST /api/ai/complete` on the profile of the `complete` slot. */\nexport function completionFetcher(host: CompletionHost): GhostAutocompleteOptions['fetchSuggestion'] {\n return async (before, signal) => {\n const { text } = await host.fetch<{ text: string }>('/api/ai/complete', { method: 'POST', body: { before, model: host.modelRef('complete') }, signal })\n return text\n }\n}\n\n/**\n * Inline AI autocomplete: after a typing pause at the end of a non-empty textblock, a short continuation is shown as grey\n * ghost text after the caret. Tab accepts it as normal text, Esc dismisses it; typing, caret moves and blur clear it.\n * The suggestion is a local widget decoration — never part of the document, so collaborators never see it.\n * On while the editor setting `ai.autocomplete` is (see `useEditorSettings`); turning it off clears the ghost.\n * Client-only: not part of `schemaExtensions()`.\n */\nexport const ghostAutocomplete = (options: GhostAutocompleteOptions) => Extension.create({\n name: 'ghostAutocomplete',\n // Runs its Tab/Escape bindings before ListItem's Tab (sinkListItem); both return false without a suggestion.\n priority: 1000,\n\n addKeyboardShortcuts() {\n return {\n Tab: ({ editor }) => {\n const suggestion = ghostAutocompleteKey.getState(editor.state)?.suggestion\n const { selection } = editor.state\n if (!suggestion || !selection.empty || selection.head !== suggestion.pos) return false\n const end = suggestion.pos + suggestion.text.length\n const tr = editor.state.tr.insertText(suggestion.text, suggestion.pos)\n editor.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, end)).scrollIntoView())\n return true\n },\n Escape: ({ editor }) => {\n if (!ghostAutocompleteKey.getState(editor.state)?.suggestion) return false\n editor.view.dispatch(editor.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n return true\n },\n }\n },\n\n addProseMirrorPlugins() {\n const { editor } = this\n const { settings } = options\n let timer: number | undefined\n let inFlight: AbortController | null = null\n\n function cancel() {\n clearTimeout(timer)\n timer = undefined\n inFlight?.abort()\n inFlight = null\n }\n\n function eligible(view: EditorView): boolean {\n const { state } = view\n if (!settings.get().ai.autocomplete || !editor.isEditable || !view.hasFocus() || view.composing || blockTextAtEnd(state) === null) return false\n if (!hasAbility(editor, ABILITY.aiAutocomplete)) return false\n // No ghost while an @tiptap/suggestion popup (e.g. `@` mentions) is open: its state is `{ active, query, … }`.\n return !state.plugins.some((plugin) => {\n const pluginState: unknown = plugin.getState(state)\n return typeof pluginState === 'object' && pluginState !== null && 'query' in pluginState && 'active' in pluginState && pluginState.active === true\n })\n }\n\n async function request(view: EditorView, activity: number) {\n timer = undefined\n const { state } = view\n if (ghostAutocompleteKey.getState(state)?.activity !== activity || !eligible(view)) return\n const pos = state.selection.head\n const blockText = blockTextAtEnd(state)\n // Positions count at least one per character, so twice the limit in positions covers the character limit.\n const before = state.doc.textBetween(Math.max(0, pos - AI_COMPLETE_LIMITS.before * 2), pos, '\\n\\n').slice(-AI_COMPLETE_LIMITS.before)\n const controller = new AbortController()\n inFlight = controller\n let text: string\n try {\n text = await options.fetchSuggestion(before, controller.signal)\n } catch {\n // Aborted, rate-limited (429) or failed: autocomplete is a silent nicety, so no ghost and no toast.\n return\n } finally {\n if (inFlight === controller) inFlight = null\n }\n if (controller.signal.aborted || !text || view.isDestroyed) return\n // Stale-response guard: no local edit or caret move since the request, and the caret block text is unchanged\n // (a collaborator's change elsewhere may have shifted the caret position).\n const now = view.state\n if (ghostAutocompleteKey.getState(now)?.activity !== activity || !eligible(view) || blockTextAtEnd(now) !== blockText) return\n view.dispatch(now.tr.setMeta(ghostAutocompleteKey, { type: 'show', suggestion: { text, pos: now.selection.head } } satisfies GhostMeta))\n }\n\n return [\n new Plugin<GhostState>({\n key: ghostAutocompleteKey,\n state: {\n init: () => ({ suggestion: null, edits: 0, activity: 0 }),\n apply(tr, prev, oldState, newState) {\n const meta = tr.getMeta(ghostAutocompleteKey) as GhostMeta | undefined\n // A collaborator's edit (the collaboration feature's resolver tells); the user's own undo/redo is not remote.\n const remote = anchorResolverOf(editor)?.isRemoteChange(tr) ?? false\n const edited = tr.docChanged && !remote\n const moved = tr.selectionSet && !remote\n let suggestion = prev.suggestion\n if (meta?.type === 'show') suggestion = meta.suggestion\n else if (meta?.type === 'clear' || edited || moved) suggestion = null\n else if (suggestion && tr.docChanged) suggestion = mapSuggestion(suggestion, tr, oldState, newState)\n if (suggestion === prev.suggestion && !edited && !moved) return prev\n return { suggestion, edits: prev.edits + (edited ? 1 : 0), activity: prev.activity + (edited || moved ? 1 : 0) }\n },\n },\n props: {\n decorations(state) {\n const suggestion = ghostAutocompleteKey.getState(state)?.suggestion\n if (!suggestion) return null\n const widget = Decoration.widget(suggestion.pos, () => {\n const span = document.createElement('span')\n span.className = 'ghost-autocomplete'\n span.contentEditable = 'false'\n span.setAttribute('aria-hidden', 'true')\n span.textContent = suggestion.text\n return span\n }, { side: 1, key: `ghost:${suggestion.text}`, ignoreSelection: true })\n return DecorationSet.create(state.doc, [widget])\n },\n handleDOMEvents: {\n blur(view) {\n cancel()\n if (ghostAutocompleteKey.getState(view.state)?.suggestion) {\n view.dispatch(view.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n }\n return false\n },\n },\n },\n view: (view) => {\n // Turned off (toolbar or settings): drop the pending request and the shown ghost right away.\n let last = settings.get().ai.autocomplete\n const stop = settings.subscribe((value) => {\n const enabled = value.ai.autocomplete\n if (enabled === last) return\n last = enabled\n if (enabled || view.isDestroyed) return\n cancel()\n if (ghostAutocompleteKey.getState(view.state)?.suggestion) {\n view.dispatch(view.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n }\n })\n return {\n update(view, prevState) {\n const next = ghostAutocompleteKey.getState(view.state)\n const prev = ghostAutocompleteKey.getState(prevState)\n if (!next || !prev) return\n if (!settings.get().ai.autocomplete) return cancel()\n if (next.activity === prev.activity) return\n // Typing or a caret move: drop the pending pause and abort the in-flight request.\n cancel()\n // Only a local edit starts a new pause; caret moves alone never request.\n if (next.edits === prev.edits || !eligible(view)) return\n const activity = next.activity\n timer = window.setTimeout(() => void request(view, activity), options.debounce)\n },\n destroy() {\n stop()\n cancel()\n },\n }\n },\n }),\n ]\n },\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"ghost-autocomplete.js","names":[],"sources":["../../../src/features/ai/ghost-autocomplete.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { type EditorState, Plugin, PluginKey, TextSelection, type Transaction } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'\nimport type { Store } from '../../core/store'\nimport { anchorResolverOf, hasAbility } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport type { EditorSettings, ModelSlot } from '../../settings'\nimport type { EditorBackend } from '../../core/backend'\nimport { AI_COMPLETE_LIMITS, type ModelRef } from './protocol'\n\nexport interface GhostAutocompleteOptions {\n /** The app's shared settings: `ai.autocomplete` switches the suggestions on. */\n settings: Store<EditorSettings>\n /** Pause after the last keystroke before a suggestion is requested (ms). */\n debounce: number\n /** Fetches a continuation of `before` (document text up to the caret); '' for none. Must honour `signal`. */\n fetchSuggestion: (before: string, signal: AbortSignal) => Promise<string>\n}\n\ninterface Suggestion { text: string, pos: number }\ninterface GhostState {\n suggestion: Suggestion | null\n /** Bumped by local edits: each bump starts a new pause, so at most one request follows it. */\n edits: number\n /** Bumped by local edits and caret moves: an in-flight request is stale once this changed. */\n activity: number\n}\ntype GhostMeta = { type: 'show', suggestion: Suggestion } | { type: 'clear' }\n\nexport const ghostAutocompleteKey = new PluginKey<GhostState>('ghostAutocomplete')\n\n/** Text of the caret's textblock, when the caret is an empty selection at the end of a non-empty, non-code textblock. */\nfunction blockTextAtEnd(state: EditorState): string | null {\n const { selection } = state\n if (!(selection instanceof TextSelection) || !selection.empty) return null\n const { $head } = selection\n const block = $head.parent\n if (!block.isTextblock || block.type.spec.code || $head.parentOffset !== block.content.size) return null\n const text = block.textContent\n return text.trim() ? text : null\n}\n\n/** Keeps a suggestion across a collaborator's change only if the caret block is untouched and the caret stayed with it. */\nfunction mapSuggestion(suggestion: Suggestion, tr: Transaction, oldState: EditorState, newState: EditorState): Suggestion | null {\n const mapped = tr.mapping.mapResult(suggestion.pos)\n if (mapped.deleted || newState.selection.head !== mapped.pos) return null\n const text = blockTextAtEnd(newState)\n if (text === null || text !== oldState.doc.resolve(suggestion.pos).parent.textContent) return null\n return { text: suggestion.text, pos: mapped.pos }\n}\n\n/** What the suggestion source needs of the app: its AI port and the model profile of a slot (`useModelProfiles`). */\nexport interface CompletionHost {\n /** `EditorContext.backend.ai`; without it there are no suggestions. */\n ai: EditorBackend['ai']\n modelRef: (slot: ModelSlot) => ModelRef\n}\n\n/** The suggestion source of the app behind `host`: `ai.complete` on the profile of the `complete` slot. */\nexport function completionFetcher(host: CompletionHost): GhostAutocompleteOptions['fetchSuggestion'] {\n return async (before, signal) => {\n if (!host.ai) return ''\n const { text } = await host.ai.complete({ before, model: host.modelRef('complete') }, signal)\n return text\n }\n}\n\n/**\n * Inline AI autocomplete: after a typing pause at the end of a non-empty textblock, a short continuation is shown as grey\n * ghost text after the caret. Tab accepts it as normal text, Esc dismisses it; typing, caret moves and blur clear it.\n * The suggestion is a local widget decoration — never part of the document, so collaborators never see it.\n * On while the editor setting `ai.autocomplete` is (see `useEditorSettings`); turning it off clears the ghost.\n * Client-only: not part of `schemaExtensions()`.\n */\nexport const ghostAutocomplete = (options: GhostAutocompleteOptions) => Extension.create({\n name: 'ghostAutocomplete',\n // Runs its Tab/Escape bindings before ListItem's Tab (sinkListItem); both return false without a suggestion.\n priority: 1000,\n\n addKeyboardShortcuts() {\n return {\n Tab: ({ editor }) => {\n const suggestion = ghostAutocompleteKey.getState(editor.state)?.suggestion\n const { selection } = editor.state\n if (!suggestion || !selection.empty || selection.head !== suggestion.pos) return false\n const end = suggestion.pos + suggestion.text.length\n const tr = editor.state.tr.insertText(suggestion.text, suggestion.pos)\n editor.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, end)).scrollIntoView())\n return true\n },\n Escape: ({ editor }) => {\n if (!ghostAutocompleteKey.getState(editor.state)?.suggestion) return false\n editor.view.dispatch(editor.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n return true\n },\n }\n },\n\n addProseMirrorPlugins() {\n const { editor } = this\n const { settings } = options\n let timer: number | undefined\n let inFlight: AbortController | null = null\n\n function cancel() {\n clearTimeout(timer)\n timer = undefined\n inFlight?.abort()\n inFlight = null\n }\n\n function eligible(view: EditorView): boolean {\n const { state } = view\n if (!settings.get().ai.autocomplete || !editor.isEditable || !view.hasFocus() || view.composing || blockTextAtEnd(state) === null) return false\n if (!hasAbility(editor, ABILITY.aiAutocomplete)) return false\n // No ghost while an @tiptap/suggestion popup (e.g. `@` mentions) is open: its state is `{ active, query, … }`.\n return !state.plugins.some((plugin) => {\n const pluginState: unknown = plugin.getState(state)\n return typeof pluginState === 'object' && pluginState !== null && 'query' in pluginState && 'active' in pluginState && pluginState.active === true\n })\n }\n\n async function request(view: EditorView, activity: number) {\n timer = undefined\n const { state } = view\n if (ghostAutocompleteKey.getState(state)?.activity !== activity || !eligible(view)) return\n const pos = state.selection.head\n const blockText = blockTextAtEnd(state)\n // Positions count at least one per character, so twice the limit in positions covers the character limit.\n const before = state.doc.textBetween(Math.max(0, pos - AI_COMPLETE_LIMITS.before * 2), pos, '\\n\\n').slice(-AI_COMPLETE_LIMITS.before)\n const controller = new AbortController()\n inFlight = controller\n let text: string\n try {\n text = await options.fetchSuggestion(before, controller.signal)\n } catch {\n // Aborted, rate-limited (429) or failed: autocomplete is a silent nicety, so no ghost and no toast.\n return\n } finally {\n if (inFlight === controller) inFlight = null\n }\n if (controller.signal.aborted || !text || view.isDestroyed) return\n // Stale-response guard: no local edit or caret move since the request, and the caret block text is unchanged\n // (a collaborator's change elsewhere may have shifted the caret position).\n const now = view.state\n if (ghostAutocompleteKey.getState(now)?.activity !== activity || !eligible(view) || blockTextAtEnd(now) !== blockText) return\n view.dispatch(now.tr.setMeta(ghostAutocompleteKey, { type: 'show', suggestion: { text, pos: now.selection.head } } satisfies GhostMeta))\n }\n\n return [\n new Plugin<GhostState>({\n key: ghostAutocompleteKey,\n state: {\n init: () => ({ suggestion: null, edits: 0, activity: 0 }),\n apply(tr, prev, oldState, newState) {\n const meta = tr.getMeta(ghostAutocompleteKey) as GhostMeta | undefined\n // A collaborator's edit (the collaboration feature's resolver tells); the user's own undo/redo is not remote.\n const remote = anchorResolverOf(editor)?.isRemoteChange(tr) ?? false\n const edited = tr.docChanged && !remote\n const moved = tr.selectionSet && !remote\n let suggestion = prev.suggestion\n if (meta?.type === 'show') suggestion = meta.suggestion\n else if (meta?.type === 'clear' || edited || moved) suggestion = null\n else if (suggestion && tr.docChanged) suggestion = mapSuggestion(suggestion, tr, oldState, newState)\n if (suggestion === prev.suggestion && !edited && !moved) return prev\n return { suggestion, edits: prev.edits + (edited ? 1 : 0), activity: prev.activity + (edited || moved ? 1 : 0) }\n },\n },\n props: {\n decorations(state) {\n const suggestion = ghostAutocompleteKey.getState(state)?.suggestion\n if (!suggestion) return null\n const widget = Decoration.widget(suggestion.pos, () => {\n const span = document.createElement('span')\n span.className = 'ghost-autocomplete'\n span.contentEditable = 'false'\n span.setAttribute('aria-hidden', 'true')\n span.textContent = suggestion.text\n return span\n }, { side: 1, key: `ghost:${suggestion.text}`, ignoreSelection: true })\n return DecorationSet.create(state.doc, [widget])\n },\n handleDOMEvents: {\n blur(view) {\n cancel()\n if (ghostAutocompleteKey.getState(view.state)?.suggestion) {\n view.dispatch(view.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n }\n return false\n },\n },\n },\n view: (view) => {\n // Turned off (toolbar or settings): drop the pending request and the shown ghost right away.\n let last = settings.get().ai.autocomplete\n const stop = settings.subscribe((value) => {\n const enabled = value.ai.autocomplete\n if (enabled === last) return\n last = enabled\n if (enabled || view.isDestroyed) return\n cancel()\n if (ghostAutocompleteKey.getState(view.state)?.suggestion) {\n view.dispatch(view.state.tr.setMeta(ghostAutocompleteKey, { type: 'clear' } satisfies GhostMeta))\n }\n })\n return {\n update(view, prevState) {\n const next = ghostAutocompleteKey.getState(view.state)\n const prev = ghostAutocompleteKey.getState(prevState)\n if (!next || !prev) return\n if (!settings.get().ai.autocomplete) return cancel()\n if (next.activity === prev.activity) return\n // Typing or a caret move: drop the pending pause and abort the in-flight request.\n cancel()\n // Only a local edit starts a new pause; caret moves alone never request.\n if (next.edits === prev.edits || !eligible(view)) return\n const activity = next.activity\n timer = window.setTimeout(() => void request(view, activity), options.debounce)\n },\n destroy() {\n stop()\n cancel()\n },\n }\n },\n }),\n ]\n },\n})\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
+ import { ModelProfile, ModelRef } from "../../core/backend-types.js";
1
2
  import { BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, BlockLines, TRUNCATED_LINE, blockLine, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, listLinePrefix, nestedLinePrefix, quoteLinePrefix, readBlockLines, singleBlockLine, stripBlockLineEcho, stripListMarkerEcho } from "./protocol/block-lines.js";
2
3
  import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AgentOp, AgentOpContext, AgentOpFeedback, AgentOpOutcome, AgentOpProblem, AgentOpProblemReason, AgentOpType, AgentTextOptions, ModelAgentOp, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, checkAgentOps } from "./protocol/agent-ops.js";
3
4
  import { CHAT_LIMITS, CITATION_HREF_PREFIX, ChatBlock, ChatBlockModel, ChatCompositeKind, DocumentChatMessage, citationBlockId, documentChatMessages } from "./protocol/chat.js";
4
5
  import { AI_COMPLETE_LIMITS, AiCompleteMessage, cleanCompletion, completeMessages, completionReady } from "./protocol/complete.js";
5
- import { AI_LANGUAGE_KEYS, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, ModelProfile, ModelRef, UserModelProfile, modelRefSchema } from "./protocol/models.js";
6
+ import { AI_LANGUAGE_KEYS, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, UserModelProfile, modelRefSchema } from "./protocol/models.js";
6
7
  import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "./protocol/schemas.js";
7
8
  import { AI_LIMITS, AI_TASKS, AI_TASK_IDS, AiApplyMode, AiBlockInput, AiInput, AiMessage, AiTask, AiTaskId, blockTasks, inBlockTasks, withHeading, withTaskHeading } from "./protocol/tasks.js";
8
9
  import "./protocol/index.js";
@@ -14,7 +15,7 @@ import { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AiMenuAction, aiMenuActionsOf,
14
15
  import { AiSelectionHighlight, aiSelectionHighlightKey, hideAiSelectionHighlight, showAiSelectionHighlight } from "./ai-selection-highlight.js";
15
16
  import { AiSseOutcome, postAiSse } from "./ai-sse.js";
16
17
  import { AiAssistant, AiAssistantOptions, AiAssistantState, AiRunBody, AiStatus, createAiAssistant } from "./assistant.js";
17
- import { aiBlockAction, aiSlashCommand, blockTextRange } from "./block-actions.js";
18
+ import { aiBlockAction, aiCommand, blockTextRange } from "./block-actions.js";
18
19
  import { documentChatText } from "./document-chat.js";
19
20
  import { DocumentChat, DocumentChatGeneration, DocumentChatOptions, DocumentChatState, DocumentChatTurn, createDocumentChat } from "./document-chat-model.js";
20
21
  import { AiUi, aiFeatureCore } from "./feature.js";
@@ -23,4 +24,4 @@ import { ModelProfilesModel, modelProfiles } from "./model-profiles.js";
23
24
  import { isRateLimited, notifyAiRateLimited } from "./rate-limit.js";
24
25
  import { AiSidebarFlow, AiSidebarFlowOptions, createAiSidebarFlow } from "./sidebar-flow.js";
25
26
  import { AI_SELECTION_MENU_KEY, AiSelectionFlow, AiSelectionFlowOptions, AiSelectionMode, AiSelectionRequest, AiSelectionState, createAiSelectionFlow } from "./selection-flow.js";
26
- export { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AI_COMPLETE_LIMITS, AI_ID_PATTERN, AI_LANGUAGE_KEYS, AI_LIMITS, AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AI_SELECTION_MENU_KEY, AI_TASKS, AI_TASK_IDS, AgentOp, AgentOpContext, AgentOpFeedback, AgentOpOutcome, AgentOpProblem, AgentOpProblemReason, type AgentOpReview, type AgentOpReviewOptions, type AgentOpReviewState, type AgentOpState, AgentOpType, type AgentScope, AgentTextOptions, AiApplyMode, type AiApplyOutcome, type AiAssistant, type AiAssistantOptions, type AiAssistantState, AiBlockInput, AiCompleteMessage, AiInput, type AiMenuAction, AiMessage, type AiResultAction, AiReview, type AiReviewChange, type AiReviewChangeSummary, type AiReviewDecision, type AiReviewHandle, type AiReviewHunk, type AiReviewOptions, type AiReviewOutcome, type AiReviewSkipReason, type AiReviewState, type AiRunBody, type AiSelectionFlow, type AiSelectionFlowOptions, AiSelectionHighlight, type AiSelectionMode, type AiSelectionRequest, type AiSelectionState, type AiSidebarFlow, type AiSidebarFlowOptions, type AiSseOutcome, type AiStatus, type AiTarget, AiTask, AiTaskId, type AiUi, BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, BlockLines, CHAT_LIMITS, CITATION_HREF_PREFIX, ChatBlock, ChatBlockModel, ChatCompositeKind, type DocumentChat, type DocumentChatGeneration, DocumentChatMessage, type DocumentChatOptions, type DocumentChatState, type DocumentChatTurn, type GhostAutocompleteOptions, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, ModelAgentOp, ModelProfile, type ModelProfilesModel, ModelRef, TRUNCATED_LINE, UserModelProfile, acceptAiReview, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, aiApplyFailure, aiBlockAction, aiBlockInputSchema, aiBlockSchema, aiFeatureCore, aiMenuActionsOf, aiReviewKey, aiSelectionHighlightKey, aiSlashCommand, applyAiResult, blockLine, blockTasks, blockTextRange, captureTarget, checkAgentOps, citationBlockId, cleanCompletion, completeMessages, completionFetcher, completionReady, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, createAgentOpReview, createAiAssistant, createAiSelectionFlow, createAiSidebarFlow, createDocumentChat, documentChatMessages, documentChatText, documentMarkdown, formatTaskResult, ghostAutocomplete, ghostAutocompleteKey, hideAiSelectionHighlight, inBlockTasks, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, isRateLimited, listLinePrefix, modelProfiles, modelRefSchema, nestedLinePrefix, notifyAiRateLimited, openAiMenuAtCaret, openAiMenuForSelection, postAiSse, quoteLinePrefix, rangeMarkdown, readBlockLines, rejectAiReview, resultBlocks, selectionMarkdown, selectionScope, setActiveAiReviewHunk, showAiSelectionHighlight, singleBlockLine, startAiReview, stripBlockLineEcho, stripListMarkerEcho, withHeading, withTaskHeading };
27
+ export { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AI_COMPLETE_LIMITS, AI_ID_PATTERN, AI_LANGUAGE_KEYS, AI_LIMITS, AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AI_SELECTION_MENU_KEY, AI_TASKS, AI_TASK_IDS, AgentOp, AgentOpContext, AgentOpFeedback, AgentOpOutcome, AgentOpProblem, AgentOpProblemReason, type AgentOpReview, type AgentOpReviewOptions, type AgentOpReviewState, type AgentOpState, AgentOpType, type AgentScope, AgentTextOptions, AiApplyMode, type AiApplyOutcome, type AiAssistant, type AiAssistantOptions, type AiAssistantState, AiBlockInput, AiCompleteMessage, AiInput, type AiMenuAction, AiMessage, type AiResultAction, AiReview, type AiReviewChange, type AiReviewChangeSummary, type AiReviewDecision, type AiReviewHandle, type AiReviewHunk, type AiReviewOptions, type AiReviewOutcome, type AiReviewSkipReason, type AiReviewState, type AiRunBody, type AiSelectionFlow, type AiSelectionFlowOptions, AiSelectionHighlight, type AiSelectionMode, type AiSelectionRequest, type AiSelectionState, type AiSidebarFlow, type AiSidebarFlowOptions, type AiSseOutcome, type AiStatus, type AiTarget, AiTask, AiTaskId, type AiUi, BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, BlockLines, CHAT_LIMITS, CITATION_HREF_PREFIX, ChatBlock, ChatBlockModel, ChatCompositeKind, type DocumentChat, type DocumentChatGeneration, DocumentChatMessage, type DocumentChatOptions, type DocumentChatState, type DocumentChatTurn, type GhostAutocompleteOptions, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, ModelAgentOp, type ModelProfile, type ModelProfilesModel, type ModelRef, TRUNCATED_LINE, UserModelProfile, acceptAiReview, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, aiApplyFailure, aiBlockAction, aiBlockInputSchema, aiBlockSchema, aiCommand, aiFeatureCore, aiMenuActionsOf, aiReviewKey, aiSelectionHighlightKey, applyAiResult, blockLine, blockTasks, blockTextRange, captureTarget, checkAgentOps, citationBlockId, cleanCompletion, completeMessages, completionFetcher, completionReady, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, createAgentOpReview, createAiAssistant, createAiSelectionFlow, createAiSidebarFlow, createDocumentChat, documentChatMessages, documentChatText, documentMarkdown, formatTaskResult, ghostAutocomplete, ghostAutocompleteKey, hideAiSelectionHighlight, inBlockTasks, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, isRateLimited, listLinePrefix, modelProfiles, modelRefSchema, nestedLinePrefix, notifyAiRateLimited, openAiMenuAtCaret, openAiMenuForSelection, postAiSse, quoteLinePrefix, rangeMarkdown, readBlockLines, rejectAiReview, resultBlocks, selectionMarkdown, selectionScope, setActiveAiReviewHunk, showAiSelectionHighlight, singleBlockLine, startAiReview, stripBlockLineEcho, stripListMarkerEcho, withHeading, withTaskHeading };
@@ -5,21 +5,21 @@ import { AI_COMPLETE_LIMITS, cleanCompletion, completeMessages, completionReady
5
5
  import { AI_LANGUAGE_KEYS, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, modelRefSchema } from "./protocol/models.js";
6
6
  import { AI_LIMITS, AI_TASKS, AI_TASK_IDS, blockTasks, inBlockTasks, withHeading, withTaskHeading } from "./protocol/tasks.js";
7
7
  import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "./protocol/schemas.js";
8
- import { selectionScope } from "./agent-ops.js";
9
8
  import { aiApplyFailure, applyAiResult, captureTarget, documentMarkdown, formatTaskResult, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, rangeMarkdown, resultBlocks, selectionMarkdown } from "./ai-apply.js";
10
9
  import { AiReview, acceptAiReview, aiReviewKey, rejectAiReview, setActiveAiReviewHunk, startAiReview } from "./ai-review.js";
11
- import { createAgentOpReview } from "./agent-op-review.js";
12
- import { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, aiMenuActionsOf, openAiMenuAtCaret, openAiMenuForSelection } from "./ai-menu.js";
13
10
  import { AiSelectionHighlight, aiSelectionHighlightKey, hideAiSelectionHighlight, showAiSelectionHighlight } from "./ai-selection-highlight.js";
11
+ import { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, aiMenuActionsOf, openAiMenuAtCaret, openAiMenuForSelection } from "./ai-menu.js";
12
+ import { aiBlockAction, aiCommand, blockTextRange } from "./block-actions.js";
13
+ import { completionFetcher, ghostAutocomplete, ghostAutocompleteKey } from "./ghost-autocomplete.js";
14
+ import { modelProfiles } from "./model-profiles.js";
15
+ import { aiFeatureCore } from "./feature.js";
16
+ import { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from "./selection-flow.js";
14
17
  import { isRateLimited, notifyAiRateLimited } from "./rate-limit.js";
15
18
  import { postAiSse } from "./ai-sse.js";
16
19
  import { createAiAssistant } from "./assistant.js";
17
- import { aiBlockAction, aiSlashCommand, blockTextRange } from "./block-actions.js";
20
+ import { selectionScope } from "./agent-ops.js";
21
+ import { createAiSidebarFlow } from "./sidebar-flow.js";
18
22
  import { documentChatText } from "./document-chat.js";
23
+ import { createAgentOpReview } from "./agent-op-review.js";
19
24
  import { createDocumentChat } from "./document-chat-model.js";
20
- import { completionFetcher, ghostAutocomplete, ghostAutocompleteKey } from "./ghost-autocomplete.js";
21
- import { modelProfiles } from "./model-profiles.js";
22
- import { aiFeatureCore } from "./feature.js";
23
- import { createAiSidebarFlow } from "./sidebar-flow.js";
24
- import { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from "./selection-flow.js";
25
- export { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AI_COMPLETE_LIMITS, AI_ID_PATTERN, AI_LANGUAGE_KEYS, AI_LIMITS, AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AI_SELECTION_MENU_KEY, AI_TASKS, AI_TASK_IDS, AiReview, AiSelectionHighlight, BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, CHAT_LIMITS, CITATION_HREF_PREFIX, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, TRUNCATED_LINE, acceptAiReview, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, aiApplyFailure, aiBlockAction, aiBlockInputSchema, aiBlockSchema, aiFeatureCore, aiMenuActionsOf, aiReviewKey, aiSelectionHighlightKey, aiSlashCommand, applyAiResult, blockLine, blockTasks, blockTextRange, captureTarget, checkAgentOps, citationBlockId, cleanCompletion, completeMessages, completionFetcher, completionReady, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, createAgentOpReview, createAiAssistant, createAiSelectionFlow, createAiSidebarFlow, createDocumentChat, documentChatMessages, documentChatText, documentMarkdown, formatTaskResult, ghostAutocomplete, ghostAutocompleteKey, hideAiSelectionHighlight, inBlockTasks, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, isRateLimited, listLinePrefix, modelProfiles, modelRefSchema, nestedLinePrefix, notifyAiRateLimited, openAiMenuAtCaret, openAiMenuForSelection, postAiSse, quoteLinePrefix, rangeMarkdown, readBlockLines, rejectAiReview, resultBlocks, selectionMarkdown, selectionScope, setActiveAiReviewHunk, showAiSelectionHighlight, singleBlockLine, startAiReview, stripBlockLineEcho, stripListMarkerEcho, withHeading, withTaskHeading };
25
+ export { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AI_COMPLETE_LIMITS, AI_ID_PATTERN, AI_LANGUAGE_KEYS, AI_LIMITS, AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AI_SELECTION_MENU_KEY, AI_TASKS, AI_TASK_IDS, AiReview, AiSelectionHighlight, BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, CHAT_LIMITS, CITATION_HREF_PREFIX, MODEL_REF_LIMITS, MODEL_SLOT_KEYS, TRUNCATED_LINE, acceptAiReview, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, aiApplyFailure, aiBlockAction, aiBlockInputSchema, aiBlockSchema, aiCommand, aiFeatureCore, aiMenuActionsOf, aiReviewKey, aiSelectionHighlightKey, applyAiResult, blockLine, blockTasks, blockTextRange, captureTarget, checkAgentOps, citationBlockId, cleanCompletion, completeMessages, completionFetcher, completionReady, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, createAgentOpReview, createAiAssistant, createAiSelectionFlow, createAiSidebarFlow, createDocumentChat, documentChatMessages, documentChatText, documentMarkdown, formatTaskResult, ghostAutocomplete, ghostAutocompleteKey, hideAiSelectionHighlight, inBlockTasks, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, isRateLimited, listLinePrefix, modelProfiles, modelRefSchema, nestedLinePrefix, notifyAiRateLimited, openAiMenuAtCaret, openAiMenuForSelection, postAiSse, quoteLinePrefix, rangeMarkdown, readBlockLines, rejectAiReview, resultBlocks, selectionMarkdown, selectionScope, setActiveAiReviewHunk, showAiSelectionHighlight, singleBlockLine, startAiReview, stripBlockLineEcho, stripListMarkerEcho, withHeading, withTaskHeading };
@@ -1,13 +1,14 @@
1
+ import { ReadonlyStore, Store } from "../../core/store.js";
1
2
  import { ModelSlot } from "../../settings/values.js";
2
3
  import "../../settings/index.js";
3
- import { ReadonlyStore, Store } from "../../core/store.js";
4
4
  import { EditorContext } from "../../core/context.js";
5
- import { ModelProfile, ModelRef, UserModelProfile } from "./protocol/models.js";
5
+ import { ModelProfile, ModelRef } from "../../core/backend-types.js";
6
+ import { UserModelProfile } from "./protocol/models.js";
6
7
  import "./protocol/index.js";
7
8
  //#region src/features/ai/model-profiles.d.ts
8
9
  /** The model profiles of one app: see `modelProfiles`. */
9
10
  export interface ModelProfilesModel {
10
- /** Server presets (`GET /api/ai/models`, without keys); empty until loaded. */
11
+ /** Server presets (`ai.models()`, without keys); empty until loaded. */
11
12
  presets: ReadonlyStore<ModelProfile[]>;
12
13
  /** This browser's own profiles (localStorage). Change them through `saveUserProfile`/`removeUserProfile`. */
13
14
  userProfiles: Store<UserModelProfile[]>;
@@ -26,7 +27,7 @@ export interface ModelProfilesModel {
26
27
  }
27
28
  /**
28
29
  * Model profiles the AI features run on, shared by all editors of the app (cached per `createEditorContext`, never
29
- * process-global): server presets (`GET /api/ai/models` through the context's HTTP client, requested once on first
30
+ * process-global): server presets (`backend.ai.models()`, requested once on first
30
31
  * use, without keys) plus user profiles that live only in this browser's localStorage (their key goes with each
31
32
  * request). Framework-free: `modelRef(slot)` is what AI requests send as `model`. Frameworks observe `presets`,
32
33
  * `userProfiles` and `app.settings` (the slot assignment is `settings.ai.slots`).
@@ -35,7 +35,7 @@ function createModelProfiles(app) {
35
35
  rewrite: "",
36
36
  generate: ""
37
37
  };
38
- app.fetch("/api/ai/models").then((response) => {
38
+ app.backend.ai?.models().then((response) => {
39
39
  serverSlots = response.slots;
40
40
  presets.set(response.presets);
41
41
  }).catch((error) => {
@@ -92,7 +92,7 @@ function createModelProfiles(app) {
92
92
  const models = /* @__PURE__ */ new WeakMap();
93
93
  /**
94
94
  * Model profiles the AI features run on, shared by all editors of the app (cached per `createEditorContext`, never
95
- * process-global): server presets (`GET /api/ai/models` through the context's HTTP client, requested once on first
95
+ * process-global): server presets (`backend.ai.models()`, requested once on first
96
96
  * use, without keys) plus user profiles that live only in this browser's localStorage (their key goes with each
97
97
  * request). Framework-free: `modelRef(slot)` is what AI requests send as `model`. Frameworks observe `presets`,
98
98
  * `userProfiles` and `app.settings` (the slot assignment is `settings.ai.slots`).
@@ -1 +1 @@
1
- {"version":3,"file":"model-profiles.js","names":[],"sources":["../../../src/features/ai/model-profiles.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { EditorContext } from '../../core/context'\nimport { createStore, type ReadonlyStore, type Store } from '../../core/store'\nimport { MODEL_SLOTS, type ModelSlot } from '../../settings'\nimport type { ModelProfile, ModelRef, UserModelProfile } from './protocol'\n\nconst STORAGE_KEY = 'tiptap-editor:model-profiles'\n\n/** The model profiles of one app: see `modelProfiles`. */\nexport interface ModelProfilesModel {\n /** Server presets (`GET /api/ai/models`, without keys); empty until loaded. */\n presets: ReadonlyStore<ModelProfile[]>\n /** This browser's own profiles (localStorage). Change them through `saveUserProfile`/`removeUserProfile`. */\n userProfiles: Store<UserModelProfile[]>\n /** Presets, then user profiles. Reads the stores: call it again after either changed. */\n profiles: () => ModelProfile[]\n /**\n * Profile a slot runs on: the one the settings assign to it, else the server's default preset for the slot, else the\n * first preset; none before the presets loaded (unless the settings point at a user profile).\n */\n slotProfile: (slot: ModelSlot) => ModelProfile | undefined\n /** What AI requests send as `model`; works before the presets loaded (the default slot preset of `createEditor({ ai })`). */\n modelRef: (slot: ModelSlot) => ModelRef\n saveUserProfile: (profile: UserModelProfile) => void\n /** Also resets every slot that ran on the profile to the server default. */\n removeUserProfile: (id: string) => void\n}\n\nconst storedProfileSchema = z.object({ id: z.string(), label: z.string(), endpoint: z.string(), model: z.string(), apiKey: z.string() })\n\nfunction storeUserProfiles(profiles: UserModelProfile[]) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(profiles))\n }\n catch {\n // Storage unavailable (e.g. privacy mode): the profiles last for this session only.\n }\n}\n\nfunction loadUserProfiles(): UserModelProfile[] {\n try {\n const stored: unknown = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]')\n if (Array.isArray(stored)) {\n return stored.flatMap((item) => {\n const parsed = storedProfileSchema.safeParse(item)\n return parsed.success ? [parsed.data] : []\n })\n }\n }\n catch {\n // Unreadable or corrupt storage: no user profiles.\n }\n return []\n}\n\nfunction createModelProfiles(app: EditorContext): ModelProfilesModel {\n const presets = createStore<ModelProfile[]>([])\n const userProfiles = createStore<UserModelProfile[]>(loadUserProfiles())\n /** Default preset id per slot as the server reports it; until then the app's public AI configuration. */\n let serverSlots: Record<ModelSlot, string> | null = null\n const defaultSlots: Record<ModelSlot, string> = app.ai?.slots ?? { complete: '', rewrite: '', generate: '' }\n\n app.fetch<{ presets: ModelProfile[], slots: Record<ModelSlot, string> }>('/api/ai/models')\n .then((response) => {\n serverSlots = response.slots\n presets.set(response.presets)\n })\n .catch((error) => {\n // Requests keep using the default slot presets by id.\n console.warn('tiptap-editor: Modell-Presets konnten nicht geladen werden', error)\n })\n\n const profiles = (): ModelProfile[] => [\n ...presets.get(),\n ...userProfiles.get().map(({ id, label, endpoint, model }) => ({ id, label, endpoint, model, source: 'user' as const })),\n ]\n\n const slotProfile = (slot: ModelSlot): ModelProfile | undefined => {\n const chosen = app.settings.get().ai.slots[slot]\n const defaultId = (serverSlots ?? defaultSlots)[slot]\n return profiles().find(p => p.id === chosen)\n ?? presets.get().find(p => p.id === defaultId)\n ?? presets.get()[0]\n }\n\n const modelRef = (slot: ModelSlot): ModelRef => {\n const profile = slotProfile(slot)\n if (!profile) return { preset: (serverSlots ?? defaultSlots)[slot] }\n const user = profile.source === 'user' ? userProfiles.get().find(p => p.id === profile.id) : undefined\n return user ? { endpoint: user.endpoint, model: user.model, apiKey: user.apiKey } : { preset: profile.id }\n }\n\n const saveUserProfile = (profile: UserModelProfile) => {\n const current = userProfiles.get()\n const index = current.findIndex(p => p.id === profile.id)\n const next = index < 0 ? [...current, profile] : current.map((p, i) => (i === index ? profile : p))\n userProfiles.set(next)\n storeUserProfiles(next)\n }\n\n const removeUserProfile = (id: string) => {\n const next = userProfiles.get().filter(p => p.id !== id)\n userProfiles.set(next)\n storeUserProfiles(next)\n const slots = app.settings.get().ai.slots\n if (MODEL_SLOTS.some(slot => slots[slot] === id)) {\n app.settings.update((s) => {\n for (const slot of MODEL_SLOTS) {\n if (s.ai.slots[slot] === id) delete s.ai.slots[slot]\n }\n })\n }\n }\n\n return { presets, userProfiles, profiles, slotProfile, modelRef, saveUserProfile, removeUserProfile }\n}\n\nconst models = new WeakMap<EditorContext, ModelProfilesModel>()\n\n/**\n * Model profiles the AI features run on, shared by all editors of the app (cached per `createEditorContext`, never\n * process-global): server presets (`GET /api/ai/models` through the context's HTTP client, requested once on first\n * use, without keys) plus user profiles that live only in this browser's localStorage (their key goes with each\n * request). Framework-free: `modelRef(slot)` is what AI requests send as `model`. Frameworks observe `presets`,\n * `userProfiles` and `app.settings` (the slot assignment is `settings.ai.slots`).\n */\nexport function modelProfiles(app: EditorContext): ModelProfilesModel {\n let model = models.get(app)\n if (!model) {\n model = createModelProfiles(app)\n models.set(app, model)\n }\n return model\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model-profiles.js","names":[],"sources":["../../../src/features/ai/model-profiles.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { EditorContext } from '../../core/context'\nimport { createStore, type ReadonlyStore, type Store } from '../../core/store'\nimport { MODEL_SLOTS, type ModelSlot } from '../../settings'\nimport type { ModelProfile, ModelRef, UserModelProfile } from './protocol'\n\nconst STORAGE_KEY = 'tiptap-editor:model-profiles'\n\n/** The model profiles of one app: see `modelProfiles`. */\nexport interface ModelProfilesModel {\n /** Server presets (`ai.models()`, without keys); empty until loaded. */\n presets: ReadonlyStore<ModelProfile[]>\n /** This browser's own profiles (localStorage). Change them through `saveUserProfile`/`removeUserProfile`. */\n userProfiles: Store<UserModelProfile[]>\n /** Presets, then user profiles. Reads the stores: call it again after either changed. */\n profiles: () => ModelProfile[]\n /**\n * Profile a slot runs on: the one the settings assign to it, else the server's default preset for the slot, else the\n * first preset; none before the presets loaded (unless the settings point at a user profile).\n */\n slotProfile: (slot: ModelSlot) => ModelProfile | undefined\n /** What AI requests send as `model`; works before the presets loaded (the default slot preset of `createEditor({ ai })`). */\n modelRef: (slot: ModelSlot) => ModelRef\n saveUserProfile: (profile: UserModelProfile) => void\n /** Also resets every slot that ran on the profile to the server default. */\n removeUserProfile: (id: string) => void\n}\n\nconst storedProfileSchema = z.object({ id: z.string(), label: z.string(), endpoint: z.string(), model: z.string(), apiKey: z.string() })\n\nfunction storeUserProfiles(profiles: UserModelProfile[]) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(profiles))\n }\n catch {\n // Storage unavailable (e.g. privacy mode): the profiles last for this session only.\n }\n}\n\nfunction loadUserProfiles(): UserModelProfile[] {\n try {\n const stored: unknown = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]')\n if (Array.isArray(stored)) {\n return stored.flatMap((item) => {\n const parsed = storedProfileSchema.safeParse(item)\n return parsed.success ? [parsed.data] : []\n })\n }\n }\n catch {\n // Unreadable or corrupt storage: no user profiles.\n }\n return []\n}\n\nfunction createModelProfiles(app: EditorContext): ModelProfilesModel {\n const presets = createStore<ModelProfile[]>([])\n const userProfiles = createStore<UserModelProfile[]>(loadUserProfiles())\n /** Default preset id per slot as the server reports it; until then the app's public AI configuration. */\n let serverSlots: Record<ModelSlot, string> | null = null\n const defaultSlots: Record<ModelSlot, string> = app.ai?.slots ?? { complete: '', rewrite: '', generate: '' }\n\n // A backend without `ai` has no server presets: requests keep using the default slot presets by id.\n app.backend.ai?.models()\n .then((response) => {\n serverSlots = response.slots\n presets.set(response.presets)\n })\n .catch((error) => {\n // Requests keep using the default slot presets by id.\n console.warn('tiptap-editor: Modell-Presets konnten nicht geladen werden', error)\n })\n\n const profiles = (): ModelProfile[] => [\n ...presets.get(),\n ...userProfiles.get().map(({ id, label, endpoint, model }) => ({ id, label, endpoint, model, source: 'user' as const })),\n ]\n\n const slotProfile = (slot: ModelSlot): ModelProfile | undefined => {\n const chosen = app.settings.get().ai.slots[slot]\n const defaultId = (serverSlots ?? defaultSlots)[slot]\n return profiles().find(p => p.id === chosen)\n ?? presets.get().find(p => p.id === defaultId)\n ?? presets.get()[0]\n }\n\n const modelRef = (slot: ModelSlot): ModelRef => {\n const profile = slotProfile(slot)\n if (!profile) return { preset: (serverSlots ?? defaultSlots)[slot] }\n const user = profile.source === 'user' ? userProfiles.get().find(p => p.id === profile.id) : undefined\n return user ? { endpoint: user.endpoint, model: user.model, apiKey: user.apiKey } : { preset: profile.id }\n }\n\n const saveUserProfile = (profile: UserModelProfile) => {\n const current = userProfiles.get()\n const index = current.findIndex(p => p.id === profile.id)\n const next = index < 0 ? [...current, profile] : current.map((p, i) => (i === index ? profile : p))\n userProfiles.set(next)\n storeUserProfiles(next)\n }\n\n const removeUserProfile = (id: string) => {\n const next = userProfiles.get().filter(p => p.id !== id)\n userProfiles.set(next)\n storeUserProfiles(next)\n const slots = app.settings.get().ai.slots\n if (MODEL_SLOTS.some(slot => slots[slot] === id)) {\n app.settings.update((s) => {\n for (const slot of MODEL_SLOTS) {\n if (s.ai.slots[slot] === id) delete s.ai.slots[slot]\n }\n })\n }\n }\n\n return { presets, userProfiles, profiles, slotProfile, modelRef, saveUserProfile, removeUserProfile }\n}\n\nconst models = new WeakMap<EditorContext, ModelProfilesModel>()\n\n/**\n * Model profiles the AI features run on, shared by all editors of the app (cached per `createEditorContext`, never\n * process-global): server presets (`backend.ai.models()`, requested once on first\n * use, without keys) plus user profiles that live only in this browser's localStorage (their key goes with each\n * request). Framework-free: `modelRef(slot)` is what AI requests send as `model`. Frameworks observe `presets`,\n * `userProfiles` and `app.settings` (the slot assignment is `settings.ai.slots`).\n */\nexport function modelProfiles(app: EditorContext): ModelProfilesModel {\n let model = models.get(app)\n if (!model) {\n model = createModelProfiles(app)\n models.set(app, model)\n }\n return model\n}\n"],"mappings":"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"}
@@ -166,7 +166,7 @@ const BLOCK_LINE_COMPOSITE_TEXT = `Im Dokument beginnt ein Baustein mit der Kopf
166
166
  "Titel",
167
167
  "Beschreibung",
168
168
  "Tags: #a #b"
169
- ]))}“ (die Felder als JSON nur, wenn welche gesetzt sind; leere Teile fehlen samt ihrem „|“). Seine Inhaltsblöcke folgen als eigene Zeilen mit ihren IDs, um zwei Leerzeichen eingerückt; eine Zeile „${compositeLineClose("")}“ ohne ID schließt ihn.`;
169
+ ]))}“ (die Felder als JSON nur, wenn welche gesetzt sind; leere Teile fehlen samt ihrem „|“). Seine Inhaltsblöcke folgen als eigene Zeilen mit ihren IDs, um zwei Leerzeichen eingerückt; eine Zeile „${compositeLineClose("")}“ ohne ID schließt ihn. Das Feld „bib“ ist eine Literaturangabe als CSL-JSON, „derivation“ beschreibt, woraus der Baustein erzeugt wurde. Ein Verweis auf einen Baustein des Dokuments steht im Text als „[@<Block-ID>]“, mit Fundstelle als „[@<Block-ID>, S. 12]“.`;
170
170
  /** What the prompt says about the Markdown of an operation: no echo of the line prefix (`stripBlockLineEcho` removes it when it comes anyway). */
171
171
  const BLOCK_LINE_ECHO_TEXT = "„markdown“ enthält nur den neuen Inhalt, ohne Block-ID und ohne das Listen- oder Zitatzeichen der Zeile.";
172
172
  /** What the retry message says about IDs. */