@ecsplico/editor 3.1.1 → 3.3.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 (267) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/adapters/direct-ai.d.ts +36 -0
  3. package/dist/adapters/direct-ai.js +169 -0
  4. package/dist/adapters/direct-ai.js.map +1 -0
  5. package/dist/adapters/memory.js.map +1 -1
  6. package/dist/code/CodeSurface.vue.d.ts +1 -1
  7. package/dist/core/block-drag.js +3 -0
  8. package/dist/core/block-drag.js.map +1 -1
  9. package/dist/core/block-transfer.d.ts +44 -0
  10. package/dist/core/block-transfer.js +68 -0
  11. package/dist/core/block-transfer.js.map +1 -0
  12. package/dist/core/commands.js +5 -0
  13. package/dist/core/commands.js.map +1 -1
  14. package/dist/core/core-feature.d.ts +4 -1
  15. package/dist/core/core-feature.js +8 -3
  16. package/dist/core/core-feature.js.map +1 -1
  17. package/dist/core/editor-ui.d.ts +3 -1
  18. package/dist/core/editor-ui.js.map +1 -1
  19. package/dist/core/extension-points.d.ts +17 -1
  20. package/dist/core/feature.d.ts +6 -1
  21. package/dist/core/feature.js.map +1 -1
  22. package/dist/core/index.d.ts +4 -2
  23. package/dist/core/index.js +3 -2
  24. package/dist/core/schema/index.d.ts +24 -3
  25. package/dist/core/schema/index.js +21 -4
  26. package/dist/core/schema/index.js.map +1 -1
  27. package/dist/core/surface-session.js +3 -1
  28. package/dist/core/surface-session.js.map +1 -1
  29. package/dist/core/toolbar-items.js +1 -1
  30. package/dist/core/toolbar-items.js.map +1 -1
  31. package/dist/document/DocumentSurface.vue.d.ts +1 -1
  32. package/dist/features/ai/agent-op-review.js +1 -1
  33. package/dist/features/ai/agent-ops.js +2 -2
  34. package/dist/features/ai/ai-apply.js +1 -1
  35. package/dist/features/ai/assistant.js +1 -1
  36. package/dist/features/ai/components/AiSelectionMenu.js +2 -2
  37. package/dist/features/ai/components/AiSidebar.js +2 -2
  38. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  39. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  40. package/dist/features/ai/document-chat-model.js +1 -1
  41. package/dist/features/ai/document-chat.js +2 -2
  42. package/dist/features/ai/ghost-autocomplete.js +1 -1
  43. package/dist/features/ai/index.d.ts +4 -3
  44. package/dist/features/ai/index.js +6 -5
  45. package/dist/features/ai/protocol/complete.d.ts +19 -0
  46. package/dist/features/ai/protocol/complete.js +27 -1
  47. package/dist/features/ai/protocol/complete.js.map +1 -1
  48. package/dist/features/ai/protocol/generate.d.ts +95 -0
  49. package/dist/features/ai/protocol/generate.js +58 -0
  50. package/dist/features/ai/protocol/generate.js.map +1 -0
  51. package/dist/features/ai/protocol/index.d.ts +4 -3
  52. package/dist/features/ai/protocol/index.js +6 -5
  53. package/dist/features/ai/protocol/tasks.d.ts +3 -1
  54. package/dist/features/ai/protocol/tasks.js +74 -2
  55. package/dist/features/ai/protocol/tasks.js.map +1 -1
  56. package/dist/features/ai/react/AiSelectionMenu.js +2 -2
  57. package/dist/features/ai/react/AiSidebar.js +2 -2
  58. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  59. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  60. package/dist/features/ai/selection-flow.js +1 -1
  61. package/dist/features/ai/sidebar-flow.js +1 -1
  62. package/dist/features/block-ui/block-actions.d.ts +19 -3
  63. package/dist/features/block-ui/block-actions.js +49 -3
  64. package/dist/features/block-ui/block-actions.js.map +1 -1
  65. package/dist/features/block-ui/components/BlockActionsOverlay.js +24 -13
  66. package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
  67. package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +12 -5
  68. package/dist/features/block-ui/components/BlockUi.js +52 -20
  69. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  70. package/dist/features/block-ui/components/BlockUi.vue.d.ts +6 -4
  71. package/dist/features/block-ui/feature.d.ts +8 -2
  72. package/dist/features/block-ui/feature.js +7 -2
  73. package/dist/features/block-ui/feature.js.map +1 -1
  74. package/dist/features/block-ui/index.d.ts +3 -2
  75. package/dist/features/block-ui/index.js +2 -2
  76. package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +14 -7
  77. package/dist/features/block-ui/react/BlockActionsOverlay.js +25 -13
  78. package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
  79. package/dist/features/block-ui/react/BlockUi.d.ts +6 -4
  80. package/dist/features/block-ui/react/BlockUi.js +43 -15
  81. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  82. package/dist/features/block-ui/react.d.ts +4 -2
  83. package/dist/features/block-ui/react.js +3 -3
  84. package/dist/features/block-ui/react.js.map +1 -1
  85. package/dist/features/block-ui/vue.d.ts +4 -2
  86. package/dist/features/block-ui/vue.js +3 -3
  87. package/dist/features/block-ui/vue.js.map +1 -1
  88. package/dist/features/composites/baskets.d.ts +27 -0
  89. package/dist/features/composites/baskets.js +46 -0
  90. package/dist/features/composites/baskets.js.map +1 -0
  91. package/dist/features/composites/components/CompositeBasketMenu.js +97 -0
  92. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -0
  93. package/dist/features/composites/components/CompositeChrome.js +79 -23
  94. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  95. package/dist/features/composites/components/CompositeView.js +21 -2
  96. package/dist/features/composites/components/CompositeView.js.map +1 -1
  97. package/dist/features/composites/components/DerivationBadge.js +12 -3
  98. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  99. package/dist/features/composites/components/FocusChip.js +53 -0
  100. package/dist/features/composites/components/FocusChip.js.map +1 -0
  101. package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
  102. package/dist/features/composites/components/SettingsEditingTab.js +40 -4
  103. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  104. package/dist/features/composites/derivation.d.ts +2 -1
  105. package/dist/features/composites/derivation.js +5 -1
  106. package/dist/features/composites/derivation.js.map +1 -1
  107. package/dist/features/composites/derived-chrome.js +13 -3
  108. package/dist/features/composites/derived-chrome.js.map +1 -1
  109. package/dist/features/composites/feature.d.ts +7 -1
  110. package/dist/features/composites/feature.js +107 -23
  111. package/dist/features/composites/feature.js.map +1 -1
  112. package/dist/features/composites/focus.d.ts +38 -0
  113. package/dist/features/composites/focus.js +86 -0
  114. package/dist/features/composites/focus.js.map +1 -0
  115. package/dist/features/composites/index.d.ts +7 -3
  116. package/dist/features/composites/index.js +7 -3
  117. package/dist/features/composites/kinds.d.ts +6 -1
  118. package/dist/features/composites/kinds.js.map +1 -1
  119. package/dist/features/composites/merge.d.ts +24 -0
  120. package/dist/features/composites/merge.js +47 -0
  121. package/dist/features/composites/merge.js.map +1 -0
  122. package/dist/features/composites/meta-keys.d.ts +4 -0
  123. package/dist/features/composites/meta-keys.js +8 -4
  124. package/dist/features/composites/meta-keys.js.map +1 -1
  125. package/dist/features/composites/nodes.d.ts +15 -2
  126. package/dist/features/composites/nodes.js +61 -5
  127. package/dist/features/composites/nodes.js.map +1 -1
  128. package/dist/features/composites/outline.d.ts +4 -0
  129. package/dist/features/composites/outline.js +3 -0
  130. package/dist/features/composites/outline.js.map +1 -1
  131. package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
  132. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
  133. package/dist/features/composites/react/CompositeChrome.js +52 -4
  134. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  135. package/dist/features/composites/react/CompositeView.js +15 -2
  136. package/dist/features/composites/react/CompositeView.js.map +1 -1
  137. package/dist/features/composites/react/DerivationBadge.js +10 -1
  138. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  139. package/dist/features/composites/react/FocusChip.d.ts +11 -0
  140. package/dist/features/composites/react/FocusChip.js +50 -0
  141. package/dist/features/composites/react/FocusChip.js.map +1 -0
  142. package/dist/features/composites/react/SettingsEditingTab.js +36 -1
  143. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  144. package/dist/features/composites/react.d.ts +2 -1
  145. package/dist/features/composites/react.js +3 -1
  146. package/dist/features/composites/react.js.map +1 -1
  147. package/dist/features/composites/schema.d.ts +5 -3
  148. package/dist/features/composites/schema.js +8 -4
  149. package/dist/features/composites/schema.js.map +1 -1
  150. package/dist/features/composites/split.d.ts +62 -0
  151. package/dist/features/composites/split.js +172 -0
  152. package/dist/features/composites/split.js.map +1 -0
  153. package/dist/features/composites/vue.d.ts +4 -3
  154. package/dist/features/composites/vue.js +7 -5
  155. package/dist/features/composites/vue.js.map +1 -1
  156. package/dist/features/transfer/components/TransferMenu.js +93 -0
  157. package/dist/features/transfer/components/TransferMenu.js.map +1 -0
  158. package/dist/features/transfer/components/TransferMenu.vue.d.ts +15 -0
  159. package/dist/features/transfer/feature.d.ts +76 -0
  160. package/dist/features/transfer/feature.js +169 -0
  161. package/dist/features/transfer/feature.js.map +1 -0
  162. package/dist/features/transfer/index.d.ts +3 -0
  163. package/dist/features/transfer/index.js +3 -0
  164. package/dist/features/transfer/react/TransferMenu.d.ts +12 -0
  165. package/dist/features/transfer/react/TransferMenu.js +71 -0
  166. package/dist/features/transfer/react/TransferMenu.js.map +1 -0
  167. package/dist/features/transfer/react.d.ts +10 -0
  168. package/dist/features/transfer/react.js +11 -0
  169. package/dist/features/transfer/react.js.map +1 -0
  170. package/dist/features/transfer/transforms.d.ts +83 -0
  171. package/dist/features/transfer/transforms.js +137 -0
  172. package/dist/features/transfer/transforms.js.map +1 -0
  173. package/dist/features/transfer/vue.d.ts +9 -0
  174. package/dist/features/transfer/vue.js +11 -0
  175. package/dist/features/transfer/vue.js.map +1 -0
  176. package/dist/i18n/de/ai.js +4 -0
  177. package/dist/i18n/de/ai.js.map +1 -1
  178. package/dist/i18n/de/composites.js +21 -1
  179. package/dist/i18n/de/composites.js.map +1 -1
  180. package/dist/i18n/de/integrations.js +16 -0
  181. package/dist/i18n/de/integrations.js.map +1 -1
  182. package/dist/i18n/de/transfer.js +17 -0
  183. package/dist/i18n/de/transfer.js.map +1 -0
  184. package/dist/i18n/de/tree.js +7 -0
  185. package/dist/i18n/de/tree.js.map +1 -1
  186. package/dist/i18n/de.d.ts +57 -0
  187. package/dist/i18n/de.js +3 -1
  188. package/dist/i18n/de.js.map +1 -1
  189. package/dist/i18n/en/ai.js +4 -0
  190. package/dist/i18n/en/ai.js.map +1 -1
  191. package/dist/i18n/en/composites.js +21 -1
  192. package/dist/i18n/en/composites.js.map +1 -1
  193. package/dist/i18n/en/integrations.js +16 -0
  194. package/dist/i18n/en/integrations.js.map +1 -1
  195. package/dist/i18n/en/transfer.js +17 -0
  196. package/dist/i18n/en/transfer.js.map +1 -0
  197. package/dist/i18n/en/tree.js +7 -0
  198. package/dist/i18n/en/tree.js.map +1 -1
  199. package/dist/i18n/en.js +3 -1
  200. package/dist/i18n/en.js.map +1 -1
  201. package/dist/integrations/composite-ai/composite-fill.js +1 -1
  202. package/dist/integrations/composite-ai/composite-summary.js +1 -1
  203. package/dist/integrations/composite-ai/derive.d.ts +21 -0
  204. package/dist/integrations/composite-ai/derive.js +69 -47
  205. package/dist/integrations/composite-ai/derive.js.map +1 -1
  206. package/dist/integrations/composite-ai/index.d.ts +8 -3
  207. package/dist/integrations/composite-ai/index.js +50 -4
  208. package/dist/integrations/composite-ai/index.js.map +1 -1
  209. package/dist/integrations/composite-ai/outline-sanitize.d.ts +48 -0
  210. package/dist/integrations/composite-ai/outline-sanitize.js +175 -0
  211. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -0
  212. package/dist/integrations/composite-ai/split.d.ts +44 -0
  213. package/dist/integrations/composite-ai/split.js +187 -0
  214. package/dist/integrations/composite-ai/split.js.map +1 -0
  215. package/dist/integrations/composite-ai/transfer-transforms.d.ts +27 -0
  216. package/dist/integrations/composite-ai/transfer-transforms.js +153 -0
  217. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -0
  218. package/dist/integrations/transcript-ai/protocol.d.ts +13 -0
  219. package/dist/integrations/transcript-ai/protocol.js +49 -1
  220. package/dist/integrations/transcript-ai/protocol.js.map +1 -1
  221. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  222. package/dist/markdown/MarkdownSurface.vue.d.ts +1 -1
  223. package/dist/nuxt/module.d.ts +114 -0
  224. package/dist/presets/compose.d.ts +24 -8
  225. package/dist/presets/compose.js +117 -58
  226. package/dist/presets/compose.js.map +1 -1
  227. package/dist/react/compose.js +5 -1
  228. package/dist/react/compose.js.map +1 -1
  229. package/dist/react/composed-surface.d.ts +9 -0
  230. package/dist/react/composed-surface.js +4 -1
  231. package/dist/react/composed-surface.js.map +1 -1
  232. package/dist/server/ai.d.ts +2 -5
  233. package/dist/server/ai.js.map +1 -1
  234. package/dist/server/handlers/ai.d.ts +4 -75
  235. package/dist/server/handlers/ai.js +30 -94
  236. package/dist/server/handlers/ai.js.map +1 -1
  237. package/dist/server/handlers/editor.d.ts +1 -0
  238. package/dist/server/handlers/index.d.ts +3 -1
  239. package/dist/server/handlers/index.js +3 -1
  240. package/dist/server/index.d.ts +2 -1
  241. package/dist/settings/index.d.ts +8 -0
  242. package/dist/settings/index.js +16 -4
  243. package/dist/settings/index.js.map +1 -1
  244. package/dist/styles/composites.css +41 -0
  245. package/dist/tree/DocumentTree.js +246 -80
  246. package/dist/tree/DocumentTree.js.map +1 -1
  247. package/dist/tree/drop-rules.js +8 -0
  248. package/dist/tree/drop-rules.js.map +1 -1
  249. package/dist/tree/react/DocumentTree.js +164 -32
  250. package/dist/tree/react/DocumentTree.js.map +1 -1
  251. package/dist/tree/tree-host.js +41 -2
  252. package/dist/tree/tree-host.js.map +1 -1
  253. package/dist/tree/tree-items.js +53 -5
  254. package/dist/tree/tree-items.js.map +1 -1
  255. package/dist/tree/tree-model.js +122 -10
  256. package/dist/tree/tree-model.js.map +1 -1
  257. package/dist/tree/tree-rows.js +20 -7
  258. package/dist/tree/tree-rows.js.map +1 -1
  259. package/dist/tree/tree-view-model.d.ts +5 -0
  260. package/dist/tree/tree-view-model.js +13 -0
  261. package/dist/tree/tree-view-model.js.map +1 -1
  262. package/dist/vue/AdvancedEditor.js +6 -0
  263. package/dist/vue/AdvancedEditor.js.map +1 -1
  264. package/dist/vue/AdvancedEditor.vue.d.ts +9 -1
  265. package/dist/vue/compose.js +5 -1
  266. package/dist/vue/compose.js.map +1 -1
  267. package/package.json +21 -1
@@ -1,15 +1,18 @@
1
+ import { withHeading } from "../../features/ai/protocol/tasks.js";
1
2
  import { ABILITY } from "../../core/schema/abilities.js";
2
3
  import { compositeKind } from "../../features/composites/kinds.js";
3
4
  import { compositeFence } from "../../features/composites/fences.js";
4
5
  import { compositeKindsOf } from "../../features/composites/nodes.js";
5
- import { withHeading } from "../../features/ai/protocol/tasks.js";
6
6
  import { modelProfiles } from "../../features/ai/model-profiles.js";
7
7
  import { structureOf } from "../../features/composites/structure.js";
8
8
  import { fillComposite, generateCompositeBody } from "./composite-fill.js";
9
- import { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource } from "./derive.js";
9
+ import { canDeriveAfter, derivableKindsOf, deriveComposite, discardDerived, isDerivableSource, writeDerivedBody } from "./derive.js";
10
10
  import { canFetchBibliography, doiOf, fetchBibliography } from "./bibliography-fetch.js";
11
11
  import { COMPOSITE_BODY_START, compositeSourceText, summarizeComposite } from "./composite-summary.js";
12
12
  import { canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition } from "./rendition.js";
13
+ import { applyOutline, collectHeadings, parseOutlineResponse, sanitizeOutline } from "./outline-sanitize.js";
14
+ import { bodyUnchanged, splitByInsertedHeadings, splitCompositeWithAi } from "./split.js";
15
+ import { bulletsTransform, summaryTransform, transferRequest } from "./transfer-transforms.js";
13
16
  import { Extension } from "@tiptap/core";
14
17
  //#region src/integrations/composite-ai/index.ts
15
18
  /** The editor command `fetchCompositeBibliography` (declared by composites) behind the bibliography editor's „DOI abrufen“ button; absent without the AI capability. */
@@ -98,9 +101,49 @@ function compositeAiActions(ctx) {
98
101
  run: ({ editor, pos }) => {
99
102
  regenerateRendition(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t);
100
103
  }
104
+ },
105
+ ...["children", "siblings"].map((mode) => ({
106
+ id: `composite-split-ai-${mode}`,
107
+ label: ctx.t(`integrations.compositeSplit.ai.${mode}`),
108
+ icon: "i-lucide-sparkles",
109
+ ability: ABILITY.aiGenerate,
110
+ order: mode === "children" ? 454 : 455,
111
+ isVisible: ({ node }) => node.type.name === "composite" && compositeSourceText(node).trim().length > 0,
112
+ run: ({ editor, pos }) => {
113
+ const { blocks } = ctx.settings.get();
114
+ splitCompositeWithAi(editor, pos, mode, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, {
115
+ targetChars: blocks.splitTargetChars,
116
+ defaultKind: blocks.defaultKind
117
+ });
118
+ }
119
+ })),
120
+ {
121
+ id: "outline-sanitize",
122
+ label: ctx.t("integrations.outlineSanitize.label"),
123
+ icon: "i-lucide-list-tree",
124
+ ability: ABILITY.aiGenerate,
125
+ order: 456,
126
+ isVisible: ({ node }) => node.type.name === "heading",
127
+ run: ({ editor }) => {
128
+ sanitizeOutline(editor, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t);
129
+ }
101
130
  }
102
131
  ];
103
132
  }
133
+ /** The ⌘K entry „Gliederung bereinigen (KI)“ (no toolbar button); absent without the AI capability. */
134
+ function compositeAiCommands(ctx) {
135
+ const ai = ctx.backend.ai;
136
+ if (!ai) return [];
137
+ return [{
138
+ id: "outline-sanitize",
139
+ label: ctx.t("integrations.outlineSanitize.label"),
140
+ icon: "i-lucide-list-tree",
141
+ toolbar: false,
142
+ run: (editor) => {
143
+ sanitizeOutline(editor, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t);
144
+ }
145
+ }];
146
+ }
104
147
  /**
105
148
  * AI on composites (requires `composites`, `ai`, `media`): the „Baustein füllen (KI)“ block action (on composites of
106
149
  * kinds with an `ai` configuration: brief + document → task `fill-block`, the result opens the diff review over the
@@ -112,7 +155,9 @@ function compositeAiActions(ctx) {
112
155
  * has a heading is inserted as a `section` composite fence, otherwise (and in flat structure) the base AI heading
113
156
  * is used. It is the preset's single `formatAiResult` contributor. The base AI feature and the composites feature do
114
157
  * not know it. Framework-free: notices go through the surface's `ctx.notify`, the model through the app's
115
- * `modelProfiles(ctx.app)`.
158
+ * `modelProfiles(ctx.app)`. Also „Mit KI in (Unter-)Bausteine teilen“ (block actions on composites: the model inserts
159
+ * headings, the body is split at them) and „Gliederung bereinigen (KI)“ (⌘K command and block action on headings:
160
+ * repairs the heading outline of the whole document).
116
161
  */
117
162
  function compositeAiFeature() {
118
163
  return {
@@ -125,6 +170,7 @@ function compositeAiFeature() {
125
170
  ],
126
171
  blockActions: compositeAiActions,
127
172
  extensions: bibliographyFetchExtensions,
173
+ commands: compositeAiCommands,
128
174
  formatAiResult: (editor, markdown, heading) => {
129
175
  if (!heading) return markdown;
130
176
  const text = withHeading(heading, markdown);
@@ -133,6 +179,6 @@ function compositeAiFeature() {
133
179
  };
134
180
  }
135
181
  //#endregion
136
- export { COMPOSITE_BODY_START, canCreateRendition, canDeriveAfter, canFetchBibliography, canRegenerateRendition, compositeAiFeature, compositeSourceText, createRendition, derivableKindsOf, deriveComposite, doiOf, fetchBibliography, fillComposite, generateCompositeBody, isDerivableSource, regenerateRendition, summarizeComposite };
182
+ export { COMPOSITE_BODY_START, applyOutline, bodyUnchanged, bulletsTransform, canCreateRendition, canDeriveAfter, canFetchBibliography, canRegenerateRendition, collectHeadings, compositeAiFeature, compositeSourceText, createRendition, derivableKindsOf, deriveComposite, discardDerived, doiOf, fetchBibliography, fillComposite, generateCompositeBody, isDerivableSource, parseOutlineResponse, regenerateRendition, sanitizeOutline, splitByInsertedHeadings, splitCompositeWithAi, summarizeComposite, summaryTransform, transferRequest, writeDerivedBody };
137
183
 
138
184
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/integrations/composite-ai/index.ts"],"sourcesContent":["import { type AnyExtension, Extension } from '@tiptap/core'\nimport type { BlockAction, EditorFeature, FeatureContext } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { modelProfiles, withHeading } from '../../features/ai'\nimport { structureOf } from '../../features/composites'\nimport '../../features/composites/bibliography-command'\nimport { compositeFence, compositeKind, compositeKindsOf } from '../../features/composites/schema'\nimport { fillComposite } from './composite-fill'\nimport { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource } from './derive'\nimport { canFetchBibliography, fetchBibliography } from './bibliography-fetch'\nimport { compositeSourceText, summarizeComposite } from './composite-summary'\nimport { canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition } from './rendition'\n\nexport { type CompositeBodyResult, fillComposite, generateCompositeBody } from './composite-fill'\nexport { COMPOSITE_BODY_START, compositeSourceText, summarizeComposite } from './composite-summary'\nexport { canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition, type RenditionOptions } from './rendition'\nexport { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource } from './derive'\nexport { canFetchBibliography, doiOf, fetchBibliography } from './bibliography-fetch'\n\n/** The editor command `fetchCompositeBibliography` (declared by composites) behind the bibliography editor's „DOI abrufen“ button; absent without the AI capability. */\nfunction bibliographyFetchExtensions(ctx: FeatureContext): AnyExtension[] {\n const ai = ctx.backend.ai\n if (!ai) return []\n return [Extension.create({\n name: 'compositeBibliographyFetch',\n addCommands: () => ({\n fetchCompositeBibliography: (pos: number) => ({ editor }) => {\n void fetchBibliography(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, ctx.documentId)\n return true\n },\n }),\n })]\n}\n\nfunction compositeAiActions(ctx: FeatureContext): BlockAction[] {\n const ai = ctx.backend.ai\n // Without the AI capability there is nothing to run (`composeEditor` omits the integration in that case).\n if (!ai) return []\n return [\n {\n id: 'composite-fill',\n label: ctx.t('integrations.compositeFill.action'),\n icon: 'i-lucide-wand-sparkles',\n ability: ABILITY.aiGenerate,\n order: 25,\n isVisible: ({ editor, node }) => node.type.name === 'composite' && compositeKind(compositeKindsOf(editor), String(node.attrs.kind)).ai !== undefined,\n run: ({ editor, pos }) => {\n void fillComposite(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'composite-summarize',\n label: ctx.t('integrations.compositeSummary.action'),\n icon: 'i-lucide-text-quote',\n ability: ABILITY.compositeSummarize,\n order: 30,\n isVisible: ({ node }) => compositeSourceText(node).trim().length > 0,\n run: ({ editor, pos }) => {\n void summarizeComposite(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'composite-derive',\n label: ctx.t('integrations.compositeDerive.action'),\n icon: 'i-lucide-git-branch',\n ability: ABILITY.compositeDerive,\n order: 452,\n isVisible: ({ editor, node }) => isDerivableSource(editor, node),\n menu: ({ editor, pos }) => derivableKindsOf(editor).map(kind => ({\n label: kind.label,\n icon: kind.icon,\n disabled: !canDeriveAfter(editor, pos, kind.id),\n onSelect: () => {\n void deriveComposite(editor, pos, kind.id, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n })),\n },\n {\n id: 'bibliography-fetch',\n label: ctx.t('integrations.bibliographyFetch.action'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeDerive,\n order: 453,\n isVisible: ({ editor, node }) => canFetchBibliography(editor, node),\n run: ({ editor, pos }) => {\n void fetchBibliography(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, ctx.documentId)\n },\n },\n {\n id: 'rendition-create',\n label: ctx.t('integrations.rendition.action'),\n icon: 'i-lucide-captions',\n ability: ABILITY.compositeDerive,\n order: 450,\n isVisible: ({ editor, node }) => canCreateRendition(editor, node, !!ctx.ai?.transcription),\n run: ({ editor, pos }) => {\n void createRendition(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'rendition-regenerate',\n label: ctx.t('integrations.rendition.regenerate'),\n icon: 'i-lucide-refresh-cw',\n ability: ABILITY.compositeDerive,\n order: 451,\n isVisible: ({ editor, node, pos }) => canRegenerateRendition(editor, node, pos, !!ctx.ai?.transcription),\n run: ({ editor, pos }) => {\n regenerateRendition(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n ]\n}\n\n/**\n * AI on composites (requires `composites`, `ai`, `media`): the „Baustein füllen (KI)“ block action (on composites of\n * kinds with an `ai` configuration: brief + document → task `fill-block`, the result opens the diff review over the\n * body), the „Zusammenfassung erzeugen“ block action (streams a\n * summary of a composite's body into its description), the „Lesefassung erzeugen“ / „Neu erzeugen (ersetzen)“\n * actions of source-holding Bausteine (`ai.extract`: a rendition child with the transcript, full text or description of\n * the source; a recording needs the app's public AI configuration to enable transcription),\n * and the composite-structure form of AI results: with `composite` structure an AI result that\n * has a heading is inserted as a `section` composite fence, otherwise (and in flat structure) the base AI heading\n * is used. It is the preset's single `formatAiResult` contributor. The base AI feature and the composites feature do\n * not know it. Framework-free: notices go through the surface's `ctx.notify`, the model through the app's\n * `modelProfiles(ctx.app)`.\n */\nexport function compositeAiFeature(): EditorFeature<never> {\n return {\n id: 'composite-ai',\n dimension: 'ai',\n requires: ['composites', 'ai', 'media'],\n blockActions: compositeAiActions,\n extensions: bibliographyFetchExtensions,\n formatAiResult: (editor, markdown, heading) => {\n if (!heading) return markdown\n const text = withHeading(heading, markdown)\n return structureOf(editor) === 'composite' ? compositeFence('section', {}, text) : text\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/integrations/composite-ai/index.ts"],"sourcesContent":["import { type AnyExtension, Extension } from '@tiptap/core'\nimport type { BlockAction, CommandDef, EditorFeature, FeatureContext } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { modelProfiles, withHeading } from '../../features/ai'\nimport { structureOf } from '../../features/composites'\nimport '../../features/composites/bibliography-command'\nimport { compositeFence, compositeKind, compositeKindsOf } from '../../features/composites/schema'\nimport { fillComposite } from './composite-fill'\nimport { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource } from './derive'\nimport { canFetchBibliography, fetchBibliography } from './bibliography-fetch'\nimport { compositeSourceText, summarizeComposite } from './composite-summary'\nimport { canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition } from './rendition'\nimport { sanitizeOutline } from './outline-sanitize'\nimport { splitCompositeWithAi } from './split'\n\nexport { bulletsTransform, summaryTransform, transferRequest } from './transfer-transforms'\n\nexport { type CompositeBodyResult, fillComposite, generateCompositeBody } from './composite-fill'\nexport { COMPOSITE_BODY_START, compositeSourceText, summarizeComposite } from './composite-summary'\nexport { canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition, type RenditionOptions } from './rendition'\nexport { canDeriveAfter, derivableKindsOf, deriveComposite, discardDerived, isDerivableSource, writeDerivedBody } from './derive'\nexport { canFetchBibliography, doiOf, fetchBibliography } from './bibliography-fetch'\nexport { applyOutline, collectHeadings, type OutlineEntry, type OutlineHeading, parseOutlineResponse, sanitizeOutline } from './outline-sanitize'\nexport { bodyUnchanged, splitByInsertedHeadings, splitCompositeWithAi } from './split'\n\n/** The editor command `fetchCompositeBibliography` (declared by composites) behind the bibliography editor's „DOI abrufen“ button; absent without the AI capability. */\nfunction bibliographyFetchExtensions(ctx: FeatureContext): AnyExtension[] {\n const ai = ctx.backend.ai\n if (!ai) return []\n return [Extension.create({\n name: 'compositeBibliographyFetch',\n addCommands: () => ({\n fetchCompositeBibliography: (pos: number) => ({ editor }) => {\n void fetchBibliography(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, ctx.documentId)\n return true\n },\n }),\n })]\n}\n\nfunction compositeAiActions(ctx: FeatureContext): BlockAction[] {\n const ai = ctx.backend.ai\n // Without the AI capability there is nothing to run (`composeEditor` omits the integration in that case).\n if (!ai) return []\n return [\n {\n id: 'composite-fill',\n label: ctx.t('integrations.compositeFill.action'),\n icon: 'i-lucide-wand-sparkles',\n ability: ABILITY.aiGenerate,\n order: 25,\n isVisible: ({ editor, node }) => node.type.name === 'composite' && compositeKind(compositeKindsOf(editor), String(node.attrs.kind)).ai !== undefined,\n run: ({ editor, pos }) => {\n void fillComposite(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'composite-summarize',\n label: ctx.t('integrations.compositeSummary.action'),\n icon: 'i-lucide-text-quote',\n ability: ABILITY.compositeSummarize,\n order: 30,\n isVisible: ({ node }) => compositeSourceText(node).trim().length > 0,\n run: ({ editor, pos }) => {\n void summarizeComposite(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'composite-derive',\n label: ctx.t('integrations.compositeDerive.action'),\n icon: 'i-lucide-git-branch',\n ability: ABILITY.compositeDerive,\n order: 452,\n isVisible: ({ editor, node }) => isDerivableSource(editor, node),\n menu: ({ editor, pos }) => derivableKindsOf(editor).map(kind => ({\n label: kind.label,\n icon: kind.icon,\n disabled: !canDeriveAfter(editor, pos, kind.id),\n onSelect: () => {\n void deriveComposite(editor, pos, kind.id, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n })),\n },\n {\n id: 'bibliography-fetch',\n label: ctx.t('integrations.bibliographyFetch.action'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeDerive,\n order: 453,\n isVisible: ({ editor, node }) => canFetchBibliography(editor, node),\n run: ({ editor, pos }) => {\n void fetchBibliography(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, ctx.documentId)\n },\n },\n {\n id: 'rendition-create',\n label: ctx.t('integrations.rendition.action'),\n icon: 'i-lucide-captions',\n ability: ABILITY.compositeDerive,\n order: 450,\n isVisible: ({ editor, node }) => canCreateRendition(editor, node, !!ctx.ai?.transcription),\n run: ({ editor, pos }) => {\n void createRendition(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n {\n id: 'rendition-regenerate',\n label: ctx.t('integrations.rendition.regenerate'),\n icon: 'i-lucide-refresh-cw',\n ability: ABILITY.compositeDerive,\n order: 451,\n isVisible: ({ editor, node, pos }) => canRegenerateRendition(editor, node, pos, !!ctx.ai?.transcription),\n run: ({ editor, pos }) => {\n regenerateRendition(editor, pos, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n ...(['children', 'siblings'] as const).map((mode): BlockAction => ({\n id: `composite-split-ai-${mode}`,\n label: ctx.t(`integrations.compositeSplit.ai.${mode}`),\n icon: 'i-lucide-sparkles',\n ability: ABILITY.aiGenerate,\n order: mode === 'children' ? 454 : 455,\n isVisible: ({ node }) => node.type.name === 'composite' && compositeSourceText(node).trim().length > 0,\n run: ({ editor, pos }) => {\n const { blocks } = ctx.settings.get()\n void splitCompositeWithAi(editor, pos, mode, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t, { targetChars: blocks.splitTargetChars, defaultKind: blocks.defaultKind })\n },\n })),\n {\n id: 'outline-sanitize',\n label: ctx.t('integrations.outlineSanitize.label'),\n icon: 'i-lucide-list-tree',\n ability: ABILITY.aiGenerate,\n order: 456,\n isVisible: ({ node }) => node.type.name === 'heading',\n run: ({ editor }) => {\n void sanitizeOutline(editor, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n },\n ]\n}\n\n/** The ⌘K entry „Gliederung bereinigen (KI)“ (no toolbar button); absent without the AI capability. */\nfunction compositeAiCommands(ctx: FeatureContext): CommandDef<never>[] {\n const ai = ctx.backend.ai\n if (!ai) return []\n return [{\n id: 'outline-sanitize',\n label: ctx.t('integrations.outlineSanitize.label'),\n icon: 'i-lucide-list-tree',\n toolbar: false,\n run: (editor) => {\n void sanitizeOutline(editor, ai, modelProfiles(ctx.app).modelRef, ctx.notify, ctx.t)\n },\n }]\n}\n\n/**\n * AI on composites (requires `composites`, `ai`, `media`): the „Baustein füllen (KI)“ block action (on composites of\n * kinds with an `ai` configuration: brief + document → task `fill-block`, the result opens the diff review over the\n * body), the „Zusammenfassung erzeugen“ block action (streams a\n * summary of a composite's body into its description), the „Lesefassung erzeugen“ / „Neu erzeugen (ersetzen)“\n * actions of source-holding Bausteine (`ai.extract`: a rendition child with the transcript, full text or description of\n * the source; a recording needs the app's public AI configuration to enable transcription),\n * and the composite-structure form of AI results: with `composite` structure an AI result that\n * has a heading is inserted as a `section` composite fence, otherwise (and in flat structure) the base AI heading\n * is used. It is the preset's single `formatAiResult` contributor. The base AI feature and the composites feature do\n * not know it. Framework-free: notices go through the surface's `ctx.notify`, the model through the app's\n * `modelProfiles(ctx.app)`. Also „Mit KI in (Unter-)Bausteine teilen“ (block actions on composites: the model inserts\n * headings, the body is split at them) and „Gliederung bereinigen (KI)“ (⌘K command and block action on headings:\n * repairs the heading outline of the whole document).\n */\nexport function compositeAiFeature(): EditorFeature<never> {\n return {\n id: 'composite-ai',\n dimension: 'ai',\n requires: ['composites', 'ai', 'media'],\n blockActions: compositeAiActions,\n extensions: bibliographyFetchExtensions,\n commands: compositeAiCommands,\n formatAiResult: (editor, markdown, heading) => {\n if (!heading) return markdown\n const text = withHeading(heading, markdown)\n return structureOf(editor) === 'composite' ? compositeFence('section', {}, text) : text\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0BA,SAAS,4BAA4B,KAAqC;CACxE,MAAM,KAAK,IAAI,QAAQ;CACvB,IAAI,CAAC,IAAI,OAAO,CAAC;CACjB,OAAO,CAAC,UAAU,OAAO;EACvB,MAAM;EACN,oBAAoB,EAClB,6BAA6B,SAAiB,EAAE,aAAa;GAC3D,kBAAuB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,GAAG,IAAI,UAAU;GAC1G,OAAO;EACT,EACF;CACF,CAAC,CAAC;AACJ;AAEA,SAAS,mBAAmB,KAAoC;CAC9D,MAAM,KAAK,IAAI,QAAQ;CAEvB,IAAI,CAAC,IAAI,OAAO,CAAC;CACjB,OAAO;EACL;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,mCAAmC;GAChD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,QAAQ,WAAW,KAAK,KAAK,SAAS,eAAe,cAAc,iBAAiB,MAAM,GAAG,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,KAAA;GAC3I,MAAM,EAAE,QAAQ,UAAU;IACxB,cAAmB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;GACxF;EACF;EACA;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,sCAAsC;GACnD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,WAAW,oBAAoB,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS;GACnE,MAAM,EAAE,QAAQ,UAAU;IACxB,mBAAwB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;GAC7F;EACF;EACA;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,qCAAqC;GAClD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,QAAQ,WAAW,kBAAkB,QAAQ,IAAI;GAC/D,OAAO,EAAE,QAAQ,UAAU,iBAAiB,MAAM,CAAC,CAAC,KAAI,UAAS;IAC/D,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,UAAU,CAAC,eAAe,QAAQ,KAAK,KAAK,EAAE;IAC9C,gBAAgB;KACd,gBAAqB,QAAQ,KAAK,KAAK,IAAI,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;IACnG;GACF,EAAE;EACJ;EACA;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,uCAAuC;GACpD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,QAAQ,WAAW,qBAAqB,QAAQ,IAAI;GAClE,MAAM,EAAE,QAAQ,UAAU;IACxB,kBAAuB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,GAAG,IAAI,UAAU;GAC5G;EACF;EACA;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,+BAA+B;GAC5C,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,QAAQ,WAAW,mBAAmB,QAAQ,MAAM,CAAC,CAAC,IAAI,IAAI,aAAa;GACzF,MAAM,EAAE,QAAQ,UAAU;IACxB,gBAAqB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;GAC1F;EACF;EACA;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,mCAAmC;GAChD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,QAAQ,MAAM,UAAU,uBAAuB,QAAQ,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,aAAa;GACvG,MAAM,EAAE,QAAQ,UAAU;IACxB,oBAAoB,QAAQ,KAAK,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;GACzF;EACF;EACA,GAAI,CAAC,YAAY,UAAU,CAAC,CAAW,KAAK,UAAuB;GACjE,IAAI,sBAAsB;GAC1B,OAAO,IAAI,EAAE,kCAAkC,MAAM;GACrD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO,SAAS,aAAa,MAAM;GACnC,YAAY,EAAE,WAAW,KAAK,KAAK,SAAS,eAAe,oBAAoB,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS;GACrG,MAAM,EAAE,QAAQ,UAAU;IACxB,MAAM,EAAE,WAAW,IAAI,SAAS,IAAI;IACpC,qBAA0B,QAAQ,KAAK,MAAM,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,GAAG;KAAE,aAAa,OAAO;KAAkB,aAAa,OAAO;IAAY,CAAC;GAChL;EACF,EAAE;EACF;GACE,IAAI;GACJ,OAAO,IAAI,EAAE,oCAAoC;GACjD,MAAM;GACN,SAAS,QAAQ;GACjB,OAAO;GACP,YAAY,EAAE,WAAW,KAAK,KAAK,SAAS;GAC5C,MAAM,EAAE,aAAa;IACnB,gBAAqB,QAAQ,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;GACrF;EACF;CACF;AACF;;AAGA,SAAS,oBAAoB,KAA0C;CACrE,MAAM,KAAK,IAAI,QAAQ;CACvB,IAAI,CAAC,IAAI,OAAO,CAAC;CACjB,OAAO,CAAC;EACN,IAAI;EACJ,OAAO,IAAI,EAAE,oCAAoC;EACjD,MAAM;EACN,SAAS;EACT,MAAM,WAAW;GACf,gBAAqB,QAAQ,IAAI,cAAc,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC;EACrF;CACF,CAAC;AACH;;;;;;;;;;;;;;;;AAiBA,SAAgB,qBAA2C;CACzD,OAAO;EACL,IAAI;EACJ,WAAW;EACX,UAAU;GAAC;GAAc;GAAM;EAAO;EACtC,cAAc;EACd,YAAY;EACZ,UAAU;EACV,iBAAiB,QAAQ,UAAU,YAAY;GAC7C,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,OAAO,YAAY,SAAS,QAAQ;GAC1C,OAAO,YAAY,MAAM,MAAM,cAAc,eAAe,WAAW,CAAC,GAAG,IAAI,IAAI;EACrF;CACF;AACF"}
@@ -0,0 +1,48 @@
1
+ import { Translate } from "../../i18n/index.js";
2
+ import { EditorBackend } from "../../core/backend.js";
3
+ import { Notifier } from "../../core/notify.js";
4
+ import { ModelRef } from "../../core/backend-types.js";
5
+ import "../../features/ai/index.js";
6
+ import { Editor } from "@tiptap/core";
7
+ import { Transaction } from "@tiptap/pm/state";
8
+ import { Node as Node$1 } from "@tiptap/pm/model";
9
+ //#region src/integrations/composite-ai/outline-sanitize.d.ts
10
+ /** A heading of the document as the model sees it, plus where it is: `i` is its index in document order, `chars` the text length of the blocks under it up to the next heading of the same or a higher level. */
11
+ export interface OutlineHeading {
12
+ i: number;
13
+ level: number;
14
+ text: string;
15
+ chars: number;
16
+ pos: number;
17
+ }
18
+ /** The model's answer for one heading: its level and text (empty: the heading is removed). */
19
+ export interface OutlineEntry {
20
+ i: number;
21
+ level: number;
22
+ text: string;
23
+ }
24
+ /** Every `heading` node of `doc` (any depth, document order). */
25
+ export declare function collectHeadings(doc: Node$1): OutlineHeading[];
26
+ /**
27
+ * The model's answer as one entry per heading, or `null` when it is not exactly that: a JSON list (a Markdown code
28
+ * fence around it is tolerated) of the same length as `headings`, `i` equal to the position, `level` an integer 1..6,
29
+ * `text` a string.
30
+ */
31
+ export declare function parseOutlineResponse(text: string, headings: readonly {
32
+ i: number;
33
+ }[]): OutlineEntry[] | null;
34
+ /**
35
+ * Writes `outline` into `tr` for the `headings` collected from `tr.doc` (so far unchanged): an empty text removes the
36
+ * heading, otherwise its level and, when it differs, its text (as plain text) are set. Runs from the last heading to
37
+ * the first, so the positions of the earlier ones stay valid. Returns how many headings changed.
38
+ */
39
+ export declare function applyOutline(tr: Transaction, headings: readonly OutlineHeading[], outline: readonly OutlineEntry[]): number;
40
+ /**
41
+ * „Gliederung bereinigen (KI)“: sends the headings of the whole document (task `outline-sanitize`, `generate` slot) and
42
+ * applies the model's repaired outline (levels without gaps, numbering and trailing punctuation gone, empty headings
43
+ * merged into the next) in one transaction. A malformed answer, or a document whose headings changed while the model
44
+ * ran, writes nothing and shows a notice. Notices go through `notify`, in the language of `t`.
45
+ */
46
+ export declare function sanitizeOutline(editor: Editor, ai: NonNullable<EditorBackend['ai']>, modelRef: (slot: 'generate') => ModelRef, notify: Notifier, t: Translate): Promise<void>;
47
+ //#endregion
48
+ //# sourceMappingURL=outline-sanitize.d.ts.map
@@ -0,0 +1,175 @@
1
+ import { AI_LIMITS } from "../../features/ai/protocol/tasks.js";
2
+ import { headingGroupRange } from "../../features/composites/heading-composites.js";
3
+ import { notifyAiRateLimited } from "../../features/ai/rate-limit.js";
4
+ import { postAiSse } from "../../features/ai/ai-sse.js";
5
+ //#region src/integrations/composite-ai/outline-sanitize.ts
6
+ /** Every `heading` node of `doc` (any depth, document order). */
7
+ function collectHeadings(doc) {
8
+ const headings = [];
9
+ doc.descendants((node, pos, parent, index) => {
10
+ if (node.type.name !== "heading" || !parent) return;
11
+ const { to } = headingGroupRange(parent, index);
12
+ let chars = 0;
13
+ for (let sibling = index + 1; sibling < to; sibling++) chars += parent.child(sibling).textContent.length;
14
+ headings.push({
15
+ i: headings.length,
16
+ level: node.attrs.level,
17
+ text: node.textContent,
18
+ chars,
19
+ pos
20
+ });
21
+ });
22
+ return headings;
23
+ }
24
+ /**
25
+ * The model's answer as one entry per heading, or `null` when it is not exactly that: a JSON list (a Markdown code
26
+ * fence around it is tolerated) of the same length as `headings`, `i` equal to the position, `level` an integer 1..6,
27
+ * `text` a string.
28
+ */
29
+ function parseOutlineResponse(text, headings) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
33
+ } catch {
34
+ return null;
35
+ }
36
+ if (!Array.isArray(parsed) || parsed.length !== headings.length) return null;
37
+ const entries = [];
38
+ for (const [index, item] of parsed.entries()) {
39
+ const { i, level, text: value } = item ?? {};
40
+ if (i !== index || typeof level !== "number" || !Number.isInteger(level) || level < 1 || level > 6 || typeof value !== "string") return null;
41
+ entries.push({
42
+ i,
43
+ level,
44
+ text: value.trim()
45
+ });
46
+ }
47
+ return entries;
48
+ }
49
+ /**
50
+ * Writes `outline` into `tr` for the `headings` collected from `tr.doc` (so far unchanged): an empty text removes the
51
+ * heading, otherwise its level and, when it differs, its text (as plain text) are set. Runs from the last heading to
52
+ * the first, so the positions of the earlier ones stay valid. Returns how many headings changed.
53
+ */
54
+ function applyOutline(tr, headings, outline) {
55
+ let changed = 0;
56
+ for (let index = headings.length - 1; index >= 0; index--) {
57
+ const { pos } = headings[index];
58
+ const { level, text } = outline[index];
59
+ const node = tr.doc.nodeAt(pos);
60
+ if (node?.type.name !== "heading") continue;
61
+ if (!text) {
62
+ tr.delete(pos, pos + node.nodeSize);
63
+ changed++;
64
+ continue;
65
+ }
66
+ const retext = text !== node.textContent;
67
+ if (!retext && level === node.attrs.level) continue;
68
+ if (level !== node.attrs.level) tr.setNodeMarkup(pos, void 0, {
69
+ ...node.attrs,
70
+ level
71
+ });
72
+ if (retext) tr.replaceWith(pos + 1, pos + 1 + node.content.size, tr.doc.type.schema.text(text));
73
+ changed++;
74
+ }
75
+ return changed;
76
+ }
77
+ /**
78
+ * „Gliederung bereinigen (KI)“: sends the headings of the whole document (task `outline-sanitize`, `generate` slot) and
79
+ * applies the model's repaired outline (levels without gaps, numbering and trailing punctuation gone, empty headings
80
+ * merged into the next) in one transaction. A malformed answer, or a document whose headings changed while the model
81
+ * ran, writes nothing and shows a notice. Notices go through `notify`, in the language of `t`.
82
+ */
83
+ async function sanitizeOutline(editor, ai, modelRef, notify, t) {
84
+ const headings = collectHeadings(editor.state.doc);
85
+ if (!headings.length) {
86
+ notify({
87
+ title: t("integrations.outlineSanitize.none"),
88
+ kind: "info"
89
+ });
90
+ return;
91
+ }
92
+ const selection = JSON.stringify(headings.map(({ i, level, text, chars }) => ({
93
+ i,
94
+ level,
95
+ text,
96
+ chars
97
+ })));
98
+ if (selection.length > AI_LIMITS.selection) {
99
+ notify({
100
+ title: t("integrations.outlineSanitize.tooLong"),
101
+ kind: "error"
102
+ });
103
+ return;
104
+ }
105
+ const toastId = "outline-sanitize";
106
+ const controller = new AbortController();
107
+ const abort = () => controller.abort();
108
+ editor.on("destroy", abort);
109
+ notify({
110
+ id: toastId,
111
+ title: t("integrations.outlineSanitize.running"),
112
+ kind: "info",
113
+ icon: "i-lucide-loader-circle",
114
+ duration: 0
115
+ });
116
+ let output = "";
117
+ try {
118
+ const outcome = await postAiSse(ai, "generate", {
119
+ task: "outline-sanitize",
120
+ prompt: "",
121
+ selection,
122
+ context: "",
123
+ model: modelRef("generate")
124
+ }, {
125
+ signal: controller.signal,
126
+ t,
127
+ onDelta: (text) => {
128
+ output += text;
129
+ }
130
+ });
131
+ if (outcome.status === "aborted" || editor.isDestroyed) return;
132
+ if (outcome.status === "rate-limited") {
133
+ notifyAiRateLimited(notify, t);
134
+ return;
135
+ }
136
+ if (outcome.status === "error") {
137
+ notify({
138
+ title: t("integrations.outlineSanitize.failed"),
139
+ description: outcome.message,
140
+ kind: "error"
141
+ });
142
+ return;
143
+ }
144
+ const outline = parseOutlineResponse(output, headings);
145
+ if (!outline) {
146
+ notify({
147
+ title: t("integrations.outlineSanitize.invalid"),
148
+ kind: "warning"
149
+ });
150
+ return;
151
+ }
152
+ const current = collectHeadings(editor.state.doc);
153
+ if (current.length !== headings.length || current.some((heading, index) => heading.pos !== headings[index].pos || heading.level !== headings[index].level || heading.text !== headings[index].text)) {
154
+ notify({
155
+ title: t("integrations.outlineSanitize.gone"),
156
+ kind: "error"
157
+ });
158
+ return;
159
+ }
160
+ const tr = editor.state.tr;
161
+ if (applyOutline(tr, current, outline)) editor.view.dispatch(tr);
162
+ notify({
163
+ title: t("integrations.outlineSanitize.done"),
164
+ kind: "success",
165
+ icon: "i-lucide-list-tree"
166
+ });
167
+ } finally {
168
+ notify.dismiss?.(toastId);
169
+ editor.off("destroy", abort);
170
+ }
171
+ }
172
+ //#endregion
173
+ export { applyOutline, collectHeadings, parseOutlineResponse, sanitizeOutline };
174
+
175
+ //# sourceMappingURL=outline-sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outline-sanitize.js","names":[],"sources":["../../../src/integrations/composite-ai/outline-sanitize.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport type { Transaction } from '@tiptap/pm/state'\nimport type { EditorBackend } from '../../core/backend'\nimport type { Notifier } from '../../core/notify'\nimport type { Translate } from '../../i18n'\nimport { AI_LIMITS, type ModelRef, notifyAiRateLimited, postAiSse } from '../../features/ai'\nimport { headingGroupRange } from '../../features/composites/schema'\n\n/** A heading of the document as the model sees it, plus where it is: `i` is its index in document order, `chars` the text length of the blocks under it up to the next heading of the same or a higher level. */\nexport interface OutlineHeading { i: number, level: number, text: string, chars: number, pos: number }\n\n/** The model's answer for one heading: its level and text (empty: the heading is removed). */\nexport interface OutlineEntry { i: number, level: number, text: string }\n\n/** Every `heading` node of `doc` (any depth, document order). */\nexport function collectHeadings(doc: PMNode): OutlineHeading[] {\n const headings: OutlineHeading[] = []\n doc.descendants((node, pos, parent, index) => {\n if (node.type.name !== 'heading' || !parent) return\n const { to } = headingGroupRange(parent, index)\n let chars = 0\n for (let sibling = index + 1; sibling < to; sibling++) chars += parent.child(sibling).textContent.length\n headings.push({ i: headings.length, level: node.attrs.level as number, text: node.textContent, chars, pos })\n })\n return headings\n}\n\n/**\n * The model's answer as one entry per heading, or `null` when it is not exactly that: a JSON list (a Markdown code\n * fence around it is tolerated) of the same length as `headings`, `i` equal to the position, `level` an integer 1..6,\n * `text` a string.\n */\nexport function parseOutlineResponse(text: string, headings: readonly { i: number }[]): OutlineEntry[] | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(text.trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, ''))\n }\n catch {\n return null\n }\n if (!Array.isArray(parsed) || parsed.length !== headings.length) return null\n const entries: OutlineEntry[] = []\n for (const [index, item] of parsed.entries()) {\n const { i, level, text: value } = (item ?? {}) as Partial<Record<'i' | 'level' | 'text', unknown>>\n if (i !== index || typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6 || typeof value !== 'string') return null\n entries.push({ i, level, text: value.trim() })\n }\n return entries\n}\n\n/**\n * Writes `outline` into `tr` for the `headings` collected from `tr.doc` (so far unchanged): an empty text removes the\n * heading, otherwise its level and, when it differs, its text (as plain text) are set. Runs from the last heading to\n * the first, so the positions of the earlier ones stay valid. Returns how many headings changed.\n */\nexport function applyOutline(tr: Transaction, headings: readonly OutlineHeading[], outline: readonly OutlineEntry[]): number {\n let changed = 0\n for (let index = headings.length - 1; index >= 0; index--) {\n const { pos } = headings[index]!\n const { level, text } = outline[index]!\n const node = tr.doc.nodeAt(pos)\n if (node?.type.name !== 'heading') continue\n if (!text) {\n tr.delete(pos, pos + node.nodeSize)\n changed++\n continue\n }\n const retext = text !== node.textContent\n if (!retext && level === node.attrs.level) continue\n if (level !== node.attrs.level) tr.setNodeMarkup(pos, undefined, { ...node.attrs, level })\n if (retext) tr.replaceWith(pos + 1, pos + 1 + node.content.size, tr.doc.type.schema.text(text))\n changed++\n }\n return changed\n}\n\n/**\n * „Gliederung bereinigen (KI)“: sends the headings of the whole document (task `outline-sanitize`, `generate` slot) and\n * applies the model's repaired outline (levels without gaps, numbering and trailing punctuation gone, empty headings\n * merged into the next) in one transaction. A malformed answer, or a document whose headings changed while the model\n * ran, writes nothing and shows a notice. Notices go through `notify`, in the language of `t`.\n */\nexport async function sanitizeOutline(editor: Editor, ai: NonNullable<EditorBackend['ai']>, modelRef: (slot: 'generate') => ModelRef, notify: Notifier, t: Translate): Promise<void> {\n const headings = collectHeadings(editor.state.doc)\n if (!headings.length) {\n notify({ title: t('integrations.outlineSanitize.none'), kind: 'info' })\n return\n }\n const selection = JSON.stringify(headings.map(({ i, level, text, chars }) => ({ i, level, text, chars })))\n if (selection.length > AI_LIMITS.selection) {\n notify({ title: t('integrations.outlineSanitize.tooLong'), kind: 'error' })\n return\n }\n\n const toastId = 'outline-sanitize'\n const controller = new AbortController()\n const abort = () => controller.abort()\n editor.on('destroy', abort)\n notify({ id: toastId, title: t('integrations.outlineSanitize.running'), kind: 'info', icon: 'i-lucide-loader-circle', duration: 0 })\n let output = ''\n try {\n const outcome = await postAiSse(ai, 'generate', {\n task: 'outline-sanitize',\n prompt: '',\n selection,\n context: '',\n model: modelRef('generate'),\n }, { signal: controller.signal, t, onDelta: (text) => { output += text } })\n\n if (outcome.status === 'aborted' || editor.isDestroyed) return\n if (outcome.status === 'rate-limited') {\n notifyAiRateLimited(notify, t)\n return\n }\n if (outcome.status === 'error') {\n notify({ title: t('integrations.outlineSanitize.failed'), description: outcome.message, kind: 'error' })\n return\n }\n const outline = parseOutlineResponse(output, headings)\n if (!outline) {\n notify({ title: t('integrations.outlineSanitize.invalid'), kind: 'warning' })\n return\n }\n\n const current = collectHeadings(editor.state.doc)\n if (current.length !== headings.length || current.some((heading, index) => heading.pos !== headings[index]!.pos || heading.level !== headings[index]!.level || heading.text !== headings[index]!.text)) {\n notify({ title: t('integrations.outlineSanitize.gone'), kind: 'error' })\n return\n }\n const tr = editor.state.tr\n if (applyOutline(tr, current, outline)) editor.view.dispatch(tr)\n notify({ title: t('integrations.outlineSanitize.done'), kind: 'success', icon: 'i-lucide-list-tree' })\n }\n finally {\n notify.dismiss?.(toastId)\n editor.off('destroy', abort)\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { Translate } from "../../i18n/index.js";
2
+ import { EditorBackend } from "../../core/backend.js";
3
+ import { Notifier } from "../../core/notify.js";
4
+ import { ModelRef } from "../../core/backend-types.js";
5
+ import "../../features/ai/index.js";
6
+ import { SplitMode } from "../../features/composites/split.js";
7
+ import { Editor } from "@tiptap/core";
8
+ import { Node as Node$1, Schema } from "@tiptap/pm/model";
9
+ //#region src/integrations/composite-ai/split.d.ts
10
+ /**
11
+ * Whether the model only inserted headings: `result` without its heading lines equals `original` with whitespace
12
+ * collapsed (so line breaks and blank lines may differ, words may not). Heading lines the original already has stay
13
+ * part of the comparison, so the model cannot drop or rewrite them.
14
+ */
15
+ export declare function bodyUnchanged(original: string, result: string): boolean;
16
+ /**
17
+ * The nodes that replace `composite` when the model's result (`blocks`, the result Markdown as blocks, with headings
18
+ * inserted) is split at those headings, or `null` when it yields fewer than two parts. Every part gets the model's
19
+ * heading as a hidden title (`meta.titleHidden`), no block id.
20
+ *
21
+ * - `children`: one node, the composite with the blocks before the first heading followed by one sub-Baustein of
22
+ * `kind` per heading.
23
+ * - `siblings`: the composite keeps the blocks before the first heading (without any, the body of the first heading
24
+ * group, its heading is dropped); every further heading group becomes a following sibling of the composite's own kind.
25
+ *
26
+ * The headings of the result are level 2, so only the lowest level present is grouped (`groupHeadingsIntoComposites`,
27
+ * not recursive).
28
+ */
29
+ export declare function splitByInsertedHeadings(schema: Schema, composite: Node$1, blocks: readonly Node$1[], mode: SplitMode, kind: string): Node$1[] | null;
30
+ /**
31
+ * „Mit KI teilen“: asks the model to insert `##` headings into the body of the Baustein at `pos` (task `split-block`,
32
+ * `targetChars` as the wanted section size, `generate` slot) and splits the body at them, in `mode`:
33
+ * `children` makes sub-Bausteine, `siblings` makes following Bausteine of the same kind. The model may only insert
34
+ * headings: when the text minus those headings is not the original body (`bodyUnchanged`) nothing is written and a
35
+ * warning is shown. The Baustein is anchored while the model runs; edits to its body meanwhile discard the result.
36
+ * The write is one transaction. `defaultKind` is the setting `blocks.defaultKind` (see `childKindFor`). Notices go
37
+ * through `notify`, in the language of `t`.
38
+ */
39
+ export declare function splitCompositeWithAi(editor: Editor, pos: number, mode: SplitMode, ai: NonNullable<EditorBackend['ai']>, modelRef: (slot: 'generate') => ModelRef, notify: Notifier, t: Translate, options: {
40
+ targetChars: number;
41
+ defaultKind: string;
42
+ }): Promise<void>;
43
+ //#endregion
44
+ //# sourceMappingURL=split.d.ts.map
@@ -0,0 +1,187 @@
1
+ import { AI_LIMITS } from "../../features/ai/protocol/tasks.js";
2
+ import { parseBlocks } from "../../core/pipeline.js";
3
+ import { captureRange, releaseRanges, resolveRange } from "../../core/doc-anchors.js";
4
+ import { TITLE_HIDDEN_KEY } from "../../features/composites/meta-keys.js";
5
+ import { bodyMarkdown } from "../../features/composites/derivation.js";
6
+ import { groupHeadingsIntoComposites } from "../../features/composites/heading-composites.js";
7
+ import { compositeKindsOf } from "../../features/composites/nodes.js";
8
+ import { notifyAiRateLimited } from "../../features/ai/rate-limit.js";
9
+ import { postAiSse } from "../../features/ai/ai-sse.js";
10
+ import { childKindFor } from "../../features/composites/split.js";
11
+ import { Fragment } from "@tiptap/pm/model";
12
+ //#region src/integrations/composite-ai/split.ts
13
+ const HEADING_LINE = /^#{1,6}\s/;
14
+ const collapse = (text) => text.replace(/\s+/g, " ").trim();
15
+ /**
16
+ * Whether the model only inserted headings: `result` without its heading lines equals `original` with whitespace
17
+ * collapsed (so line breaks and blank lines may differ, words may not). Heading lines the original already has stay
18
+ * part of the comparison, so the model cannot drop or rewrite them.
19
+ */
20
+ function bodyUnchanged(original, result) {
21
+ const own = /* @__PURE__ */ new Map();
22
+ for (const line of original.split("\n")) {
23
+ if (!HEADING_LINE.test(line)) continue;
24
+ const key = collapse(line);
25
+ own.set(key, (own.get(key) ?? 0) + 1);
26
+ }
27
+ const kept = result.split("\n").filter((line) => {
28
+ if (!HEADING_LINE.test(line)) return true;
29
+ const key = collapse(line);
30
+ const left = own.get(key) ?? 0;
31
+ if (!left) return false;
32
+ own.set(key, left - 1);
33
+ return true;
34
+ });
35
+ return collapse(kept.join("\n")) === collapse(original);
36
+ }
37
+ /**
38
+ * The nodes that replace `composite` when the model's result (`blocks`, the result Markdown as blocks, with headings
39
+ * inserted) is split at those headings, or `null` when it yields fewer than two parts. Every part gets the model's
40
+ * heading as a hidden title (`meta.titleHidden`), no block id.
41
+ *
42
+ * - `children`: one node, the composite with the blocks before the first heading followed by one sub-Baustein of
43
+ * `kind` per heading.
44
+ * - `siblings`: the composite keeps the blocks before the first heading (without any, the body of the first heading
45
+ * group, its heading is dropped); every further heading group becomes a following sibling of the composite's own kind.
46
+ *
47
+ * The headings of the result are level 2, so only the lowest level present is grouped (`groupHeadingsIntoComposites`,
48
+ * not recursive).
49
+ */
50
+ function splitByInsertedHeadings(schema, composite, blocks, mode, kind) {
51
+ let minLevel = Infinity;
52
+ for (const block of blocks) if (block.type.name === "heading") minLevel = Math.min(minLevel, block.attrs.level);
53
+ if (minLevel === Infinity) return null;
54
+ const preambleLength = blocks.findIndex((block) => block.type.name === "heading" && block.attrs.level === minLevel);
55
+ const grouped = groupHeadingsIntoComposites(schema, blocks, false, kind);
56
+ if (!grouped) return null;
57
+ const preamble = grouped.slice(0, preambleLength);
58
+ const parts = grouped.slice(preambleLength).map((part) => part.type.create({
59
+ ...part.attrs,
60
+ meta: {
61
+ ...part.attrs.meta,
62
+ [TITLE_HIDDEN_KEY]: true
63
+ }
64
+ }, part.content));
65
+ if ((preamble.length ? 1 : 0) + parts.length < 2) return null;
66
+ const chrome = [];
67
+ composite.forEach((child, _offset, index) => {
68
+ if (index < 3) chrome.push(child);
69
+ });
70
+ const withBody = (body) => composite.copy(Fragment.from([...chrome, ...body]));
71
+ if (mode === "children") return [withBody([...preamble, ...parts])];
72
+ if (preamble.length) return [withBody(preamble), ...parts];
73
+ const [first, ...rest] = parts;
74
+ const firstBody = [];
75
+ first.forEach((child, _offset, index) => {
76
+ if (index >= 3) firstBody.push(child);
77
+ });
78
+ return [withBody(firstBody), ...rest];
79
+ }
80
+ /**
81
+ * „Mit KI teilen“: asks the model to insert `##` headings into the body of the Baustein at `pos` (task `split-block`,
82
+ * `targetChars` as the wanted section size, `generate` slot) and splits the body at them, in `mode`:
83
+ * `children` makes sub-Bausteine, `siblings` makes following Bausteine of the same kind. The model may only insert
84
+ * headings: when the text minus those headings is not the original body (`bodyUnchanged`) nothing is written and a
85
+ * warning is shown. The Baustein is anchored while the model runs; edits to its body meanwhile discard the result.
86
+ * The write is one transaction. `defaultKind` is the setting `blocks.defaultKind` (see `childKindFor`). Notices go
87
+ * through `notify`, in the language of `t`.
88
+ */
89
+ async function splitCompositeWithAi(editor, pos, mode, ai, modelRef, notify, t, options) {
90
+ const composite = editor.state.doc.nodeAt(pos);
91
+ if (composite?.type.name !== "composite") return;
92
+ const original = bodyMarkdown(editor, composite);
93
+ if (!original.trim()) return;
94
+ const kind = mode === "children" ? childKindFor(compositeKindsOf(editor), String(composite.attrs.kind), options.defaultKind) : String(composite.attrs.kind);
95
+ if (!kind) {
96
+ notify({
97
+ title: t("integrations.compositeSplit.notPossible"),
98
+ kind: "error"
99
+ });
100
+ return;
101
+ }
102
+ if (original.length > AI_LIMITS.selection) {
103
+ notify({
104
+ title: t("integrations.compositeSplit.tooLong"),
105
+ kind: "error"
106
+ });
107
+ return;
108
+ }
109
+ const anchor = captureRange(editor, {
110
+ from: pos,
111
+ to: pos + composite.nodeSize
112
+ }, { startInside: true });
113
+ const toastId = `split-${anchor.id}`;
114
+ const controller = new AbortController();
115
+ const abort = () => controller.abort();
116
+ editor.on("destroy", abort);
117
+ notify({
118
+ id: toastId,
119
+ title: t("integrations.compositeSplit.running"),
120
+ kind: "info",
121
+ icon: "i-lucide-loader-circle",
122
+ duration: 0
123
+ });
124
+ let output = "";
125
+ try {
126
+ const outcome = await postAiSse(ai, "generate", {
127
+ task: "split-block",
128
+ prompt: String(options.targetChars),
129
+ selection: original,
130
+ context: "",
131
+ model: modelRef("generate")
132
+ }, {
133
+ signal: controller.signal,
134
+ t,
135
+ onDelta: (text) => {
136
+ output += text;
137
+ }
138
+ });
139
+ if (outcome.status === "aborted" || editor.isDestroyed) return;
140
+ if (outcome.status === "rate-limited") {
141
+ notifyAiRateLimited(notify, t);
142
+ return;
143
+ }
144
+ if (outcome.status === "error") {
145
+ notify({
146
+ title: t("integrations.compositeSplit.failed"),
147
+ description: outcome.message,
148
+ kind: "error"
149
+ });
150
+ return;
151
+ }
152
+ if (!bodyUnchanged(original, output)) {
153
+ notify({
154
+ title: t("integrations.compositeSplit.changed"),
155
+ kind: "warning"
156
+ });
157
+ return;
158
+ }
159
+ const range = resolveRange(editor.state, anchor);
160
+ const current = range && editor.state.doc.nodeAt(range.from);
161
+ if (!range || current?.type.name !== "composite" || range.from + current.nodeSize !== range.to || bodyMarkdown(editor, current) !== original) {
162
+ notify({
163
+ title: t("integrations.compositeSplit.gone"),
164
+ kind: "error"
165
+ });
166
+ return;
167
+ }
168
+ const { schema } = editor;
169
+ const replacement = splitByInsertedHeadings(schema, current, parseBlocks(editor, output).blocks.map((block) => schema.nodeFromJSON(block)), mode, kind);
170
+ if (!replacement) {
171
+ notify({
172
+ title: t("integrations.compositeSplit.notPossible"),
173
+ kind: "error"
174
+ });
175
+ return;
176
+ }
177
+ editor.view.dispatch(editor.state.tr.replaceWith(range.from, range.to, replacement));
178
+ } finally {
179
+ notify.dismiss?.(toastId);
180
+ editor.off("destroy", abort);
181
+ releaseRanges(editor, [anchor]);
182
+ }
183
+ }
184
+ //#endregion
185
+ export { bodyUnchanged, splitByInsertedHeadings, splitCompositeWithAi };
186
+
187
+ //# sourceMappingURL=split.js.map