@ecsplico/editor 3.1.0 → 3.2.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 (270) hide show
  1. package/CHANGELOG.md +41 -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 +2 -2
  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 +2 -2
  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/CompositeFieldEditor.js +1 -0
  96. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  97. package/dist/features/composites/components/CompositeView.js +17 -2
  98. package/dist/features/composites/components/CompositeView.js.map +1 -1
  99. package/dist/features/composites/components/DerivationBadge.js +12 -3
  100. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  101. package/dist/features/composites/components/FocusChip.js +53 -0
  102. package/dist/features/composites/components/FocusChip.js.map +1 -0
  103. package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
  104. package/dist/features/composites/components/SettingsEditingTab.js +40 -4
  105. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  106. package/dist/features/composites/derivation.d.ts +2 -1
  107. package/dist/features/composites/derivation.js +5 -1
  108. package/dist/features/composites/derivation.js.map +1 -1
  109. package/dist/features/composites/derived-chrome.js +13 -3
  110. package/dist/features/composites/derived-chrome.js.map +1 -1
  111. package/dist/features/composites/feature.d.ts +7 -1
  112. package/dist/features/composites/feature.js +96 -23
  113. package/dist/features/composites/feature.js.map +1 -1
  114. package/dist/features/composites/focus.d.ts +38 -0
  115. package/dist/features/composites/focus.js +86 -0
  116. package/dist/features/composites/focus.js.map +1 -0
  117. package/dist/features/composites/index.d.ts +6 -3
  118. package/dist/features/composites/index.js +6 -3
  119. package/dist/features/composites/meta-keys.d.ts +4 -0
  120. package/dist/features/composites/meta-keys.js +8 -4
  121. package/dist/features/composites/meta-keys.js.map +1 -1
  122. package/dist/features/composites/nodes.d.ts +14 -2
  123. package/dist/features/composites/nodes.js +61 -5
  124. package/dist/features/composites/nodes.js.map +1 -1
  125. package/dist/features/composites/outline.d.ts +4 -0
  126. package/dist/features/composites/outline.js +3 -0
  127. package/dist/features/composites/outline.js.map +1 -1
  128. package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
  129. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
  130. package/dist/features/composites/react/CompositeChrome.js +52 -4
  131. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  132. package/dist/features/composites/react/CompositeView.js +12 -2
  133. package/dist/features/composites/react/CompositeView.js.map +1 -1
  134. package/dist/features/composites/react/DerivationBadge.js +10 -1
  135. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  136. package/dist/features/composites/react/FocusChip.d.ts +11 -0
  137. package/dist/features/composites/react/FocusChip.js +50 -0
  138. package/dist/features/composites/react/FocusChip.js.map +1 -0
  139. package/dist/features/composites/react/SettingsEditingTab.js +36 -1
  140. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  141. package/dist/features/composites/react.d.ts +2 -1
  142. package/dist/features/composites/react.js +3 -1
  143. package/dist/features/composites/react.js.map +1 -1
  144. package/dist/features/composites/schema.d.ts +5 -3
  145. package/dist/features/composites/schema.js +8 -4
  146. package/dist/features/composites/schema.js.map +1 -1
  147. package/dist/features/composites/split.d.ts +62 -0
  148. package/dist/features/composites/split.js +172 -0
  149. package/dist/features/composites/split.js.map +1 -0
  150. package/dist/features/composites/vue.d.ts +4 -3
  151. package/dist/features/composites/vue.js +7 -5
  152. package/dist/features/composites/vue.js.map +1 -1
  153. package/dist/features/media/index.js +1 -1
  154. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  155. package/dist/features/media/react.js +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 +20 -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 +6 -0
  185. package/dist/i18n/de/tree.js.map +1 -1
  186. package/dist/i18n/de.d.ts +55 -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 +20 -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 +6 -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 +6 -6
  223. package/dist/nuxt/module.d.ts +110 -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 +6 -2
  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 +7 -78
  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 +32 -0
  245. package/dist/tree/DocumentTree.js +229 -78
  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 +152 -31
  250. package/dist/tree/react/DocumentTree.js.map +1 -1
  251. package/dist/tree/tree-host.js +30 -1
  252. package/dist/tree/tree-host.js.map +1 -1
  253. package/dist/tree/tree-items.js +52 -5
  254. package/dist/tree/tree-items.js.map +1 -1
  255. package/dist/tree/tree-model.js +113 -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 +13 -5
  265. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  266. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  267. package/dist/vue/compose.js +6 -2
  268. package/dist/vue/compose.js.map +1 -1
  269. package/dist/vue/kit/types.d.ts +2 -0
  270. package/package.json +21 -1
@@ -4,6 +4,8 @@ import { Notifier } from "../../core/notify.js";
4
4
  import { ModelRef } from "../../core/backend-types.js";
5
5
  import "../../features/ai/index.js";
6
6
  import { CompositeKind } from "../../features/composites/kinds.js";
7
+ import { Derivation } from "../../features/composites/derivation.js";
8
+ import "../../features/composites/schema.js";
7
9
  import "../../features/composites/index.js";
8
10
  import { Editor } from "@tiptap/core";
9
11
  import { Node as Node$1 } from "@tiptap/pm/model";
@@ -14,6 +16,25 @@ export declare const derivableKindsOf: (editor: Editor) => CompositeKind[];
14
16
  export declare const isDerivableSource: (editor: Editor, node: Node$1) => boolean;
15
17
  /** Whether a Baustein of `kindId` may be inserted right after the Baustein at `pos` (the same guards as creating it there). */
16
18
  export declare function canDeriveAfter(editor: Editor, pos: number, kindId: string): boolean;
19
+ /** Drops the derived Baustein `id` again, unless its body got content meanwhile (the user may have started writing in it). */
20
+ export declare function discardDerived(editor: Editor, id: string): void;
21
+ /**
22
+ * The write-back half of every derivation: parses the streamed `markdown` through the block pipeline and replaces the
23
+ * body of the (still empty) derived Baustein `id` with it, in one transaction together with the hash of exactly that
24
+ * body as `outputHash` (kept next to the `derivation` the Baustein already carries, else `options.derivation`; a Baustein
25
+ * with neither only gets its body), so one
26
+ * undo step removes both. An empty result calls `options.discard` and shows the „Leeres Ergebnis“ notice; a Baustein
27
+ * that vanished or no longer takes the body shows „nicht mehr gefunden“; on success the Baustein is revealed and
28
+ * `options.done` shown as success notice. The Baustein is found again by its block id, so edits while the model ran do
29
+ * not misplace the result. Returns whether the body was written.
30
+ */
31
+ export declare function writeDerivedBody(editor: Editor, id: string, markdown: string, options: {
32
+ derivation?: Derivation;
33
+ discard: () => void;
34
+ notify: Notifier;
35
+ t: Translate;
36
+ done: string;
37
+ }): boolean;
17
38
  /**
18
39
  * „Ableiten (KI) → <Art>“: inserts a sibling Baustein of kind `kindId` right after the Baustein at `pos`, titled
19
40
  * „<Art>: <Quelle>“, whose `meta.derivation` points back at the source block (`from.block`, method `ai:<kindId>`, the
@@ -24,6 +24,67 @@ function canDeriveAfter(editor, pos, kindId) {
24
24
  attrs: { kind: kindId }
25
25
  });
26
26
  }
27
+ /** Drops the derived Baustein `id` again, unless its body got content meanwhile (the user may have started writing in it). */
28
+ function discardDerived(editor, id) {
29
+ const found = editor.isDestroyed ? null : findBlockById(editor.state.doc, id);
30
+ if (!found) return;
31
+ if (Array.from({ length: found.node.childCount - 3 }, (_, index) => found.node.child(3 + index)).some((child) => child.textContent.trim() || child.type.name !== "paragraph")) return;
32
+ editor.view.dispatch(editor.state.tr.delete(found.pos, found.pos + found.node.nodeSize));
33
+ }
34
+ /**
35
+ * The write-back half of every derivation: parses the streamed `markdown` through the block pipeline and replaces the
36
+ * body of the (still empty) derived Baustein `id` with it, in one transaction together with the hash of exactly that
37
+ * body as `outputHash` (kept next to the `derivation` the Baustein already carries, else `options.derivation`; a Baustein
38
+ * with neither only gets its body), so one
39
+ * undo step removes both. An empty result calls `options.discard` and shows the „Leeres Ergebnis“ notice; a Baustein
40
+ * that vanished or no longer takes the body shows „nicht mehr gefunden“; on success the Baustein is revealed and
41
+ * `options.done` shown as success notice. The Baustein is found again by its block id, so edits while the model ran do
42
+ * not misplace the result. Returns whether the body was written.
43
+ */
44
+ function writeDerivedBody(editor, id, markdown, options) {
45
+ const { derivation, discard, notify, t, done } = options;
46
+ const blocks = markdown.trim() ? parseBlocks(editor, markdown).blocks.map((block) => editor.schema.nodeFromJSON(block)) : [];
47
+ if (!blocks.length) {
48
+ discard();
49
+ notify({
50
+ title: t("integrations.compositeDerive.empty.title"),
51
+ description: t("integrations.compositeDerive.empty.description"),
52
+ kind: "error"
53
+ });
54
+ return false;
55
+ }
56
+ const found = findBlockById(editor.state.doc, id);
57
+ if (!found || !found.node.canReplace(3, found.node.childCount, Fragment.from(blocks))) {
58
+ notify({
59
+ title: t("integrations.compositeDerive.gone.title"),
60
+ description: t("integrations.compositeDerive.gone.description"),
61
+ kind: "error"
62
+ });
63
+ return false;
64
+ }
65
+ const chromeSize = Array.from({ length: 3 }, (_, index) => found.node.child(index).nodeSize).reduce((sum, size) => sum + size, 0);
66
+ const tr = editor.state.tr.replaceWith(found.pos + 1 + chromeSize, found.pos + found.node.nodeSize - 1, blocks);
67
+ const written = findBlockById(tr.doc, id);
68
+ const stored = derivationOf(written.node) ?? derivation;
69
+ if (stored) tr.setNodeMarkup(written.pos, void 0, {
70
+ ...written.node.attrs,
71
+ meta: {
72
+ ...written.node.attrs.meta,
73
+ [DERIVATION_KEY]: {
74
+ ...stored,
75
+ outputHash: sha256Hex(bodyMarkdown(editor, written.node))
76
+ }
77
+ }
78
+ });
79
+ editor.view.dispatch(tr);
80
+ editor.commands.revealBlock(id);
81
+ notify({
82
+ title: done,
83
+ kind: "success",
84
+ icon: "i-lucide-git-branch"
85
+ });
86
+ return true;
87
+ }
27
88
  /**
28
89
  * „Ableiten (KI) → <Art>“: inserts a sibling Baustein of kind `kindId` right after the Baustein at `pos`, titled
29
90
  * „<Art>: <Quelle>“, whose `meta.derivation` points back at the source block (`from.block`, method `ai:<kindId>`, the
@@ -85,13 +146,7 @@ async function deriveComposite(editor, pos, kindId, ai, modelRef, notify, t) {
85
146
  icon: "i-lucide-loader-circle",
86
147
  duration: 0
87
148
  });
88
- /** Drops the new Baustein again, unless its body got content meanwhile (the user may have started writing in it). */
89
- const discard = () => {
90
- const found = editor.isDestroyed ? null : findBlockById(editor.state.doc, id);
91
- if (!found) return;
92
- if (Array.from({ length: found.node.childCount - 3 }, (_, index) => found.node.child(3 + index)).some((child) => child.textContent.trim() || child.type.name !== "paragraph")) return;
93
- editor.view.dispatch(editor.state.tr.delete(found.pos, found.pos + found.node.nodeSize));
94
- };
149
+ const discard = () => discardDerived(editor, id);
95
150
  try {
96
151
  const target = findBlockById(editor.state.doc, id);
97
152
  const result = target ? await generateCompositeBody(editor, target.pos, ai, modelRef, t, controller.signal) : null;
@@ -122,45 +177,12 @@ async function deriveComposite(editor, pos, kindId, ai, modelRef, notify, t) {
122
177
  });
123
178
  return;
124
179
  }
125
- const blocks = result.output.trim() ? parseBlocks(editor, result.output).blocks.map((block) => editor.schema.nodeFromJSON(block)) : [];
126
- if (!blocks.length) {
127
- discard();
128
- notify({
129
- title: t("integrations.compositeDerive.empty.title"),
130
- description: t("integrations.compositeDerive.empty.description"),
131
- kind: "error"
132
- });
133
- return;
134
- }
135
- const found = findBlockById(editor.state.doc, id);
136
- if (!found || !found.node.canReplace(3, found.node.childCount, Fragment.from(blocks))) {
137
- notify({
138
- title: t("integrations.compositeDerive.gone.title"),
139
- description: t("integrations.compositeDerive.gone.description"),
140
- kind: "error"
141
- });
142
- return;
143
- }
144
- const chromeSize = Array.from({ length: 3 }, (_, index) => found.node.child(index).nodeSize).reduce((sum, size) => sum + size, 0);
145
- const tr = editor.state.tr.replaceWith(found.pos + 1 + chromeSize, found.pos + found.node.nodeSize - 1, blocks);
146
- const written = findBlockById(tr.doc, id);
147
- const stored = derivationOf(written.node) ?? derivation;
148
- tr.setNodeMarkup(written.pos, void 0, {
149
- ...written.node.attrs,
150
- meta: {
151
- ...written.node.attrs.meta,
152
- [DERIVATION_KEY]: {
153
- ...stored,
154
- outputHash: sha256Hex(bodyMarkdown(editor, written.node))
155
- }
156
- }
157
- });
158
- editor.view.dispatch(tr);
159
- editor.commands.revealBlock(id);
160
- notify({
161
- title: t("integrations.compositeDerive.done", { kind: kind.label }),
162
- kind: "success",
163
- icon: "i-lucide-git-branch"
180
+ writeDerivedBody(editor, id, result.output, {
181
+ derivation,
182
+ discard,
183
+ notify,
184
+ t,
185
+ done: t("integrations.compositeDerive.done", { kind: kind.label })
164
186
  });
165
187
  } finally {
166
188
  notify.dismiss?.(toastId);
@@ -168,6 +190,6 @@ async function deriveComposite(editor, pos, kindId, ai, modelRef, notify, t) {
168
190
  }
169
191
  }
170
192
  //#endregion
171
- export { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource };
193
+ export { canDeriveAfter, derivableKindsOf, deriveComposite, discardDerived, isDerivableSource, writeDerivedBody };
172
194
 
173
195
  //# sourceMappingURL=derive.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"derive.js","names":[],"sources":["../../../src/integrations/composite-ai/derive.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport { Fragment, type Node as PMNode } from '@tiptap/pm/model'\nimport type { EditorBackend } from '../../core/backend'\nimport { guardsAccept, parseBlocks, sha256Hex } from '../../core'\nimport type { Notifier } from '../../core/notify'\nimport { blockIdOf, findBlockById, newBlockId } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { type ModelRef, notifyAiRateLimited } from '../../features/ai'\nimport { bodyMarkdown, COMPOSITE_CHROME_COUNT, compositeJSON, compositeKind, compositeKindsOf, DERIVATION_KEY, type Derivation, derivationOf } from '../../features/composites/schema'\nimport { generateCompositeBody } from './composite-fill'\n\n/** Kinds a Baustein can be derived into: the ones with an `ai` configuration (their brief drives the generation). */\nexport const derivableKindsOf = (editor: Editor) => compositeKindsOf(editor).filter(kind => kind.ai !== undefined)\n\n/** Whether the Baustein `node` can be the source of a derivation: a composite with a block id (the derivation points at it by id). */\nexport const isDerivableSource = (editor: Editor, node: PMNode) => node.type.name === 'composite' && blockIdOf(node) !== null && derivableKindsOf(editor).length > 0\n\n/** Whether a Baustein of `kindId` may be inserted right after the Baustein at `pos` (the same guards as creating it there). */\nexport function canDeriveAfter(editor: Editor, pos: number, kindId: string): boolean {\n const source = editor.state.doc.nodeAt(pos)\n if (!source) return false\n const $at = editor.state.doc.resolve(pos + source.nodeSize)\n return guardsAccept(editor, $at, { type: 'composite', attrs: { kind: kindId } })\n}\n\n/**\n * „Ableiten (KI) → <Art>“: inserts a sibling Baustein of kind `kindId` right after the Baustein at `pos`, titled\n * „<Art>: <Quelle>“, whose `meta.derivation` points back at the source block (`from.block`, method `ai:<kindId>`, the\n * Markdown hash of the source's body as `inputHash`), generates its body from the source (`generateCompositeBody`; the\n * `aiBlockData` of composites hands the model the source's Markdown) and writes the result directly into the new\n * Baustein — no review, the new Baustein is the proposal. The hash of the written body becomes `outputHash`, so a later\n * edit shows as „bearbeitet“ and a later edit of the source as „Quelle geändert“. The new Baustein is found again by its\n * block id after every await, so edits while the model runs do not misplace the result. When the run fails or yields\n * nothing the still-empty Baustein is removed again. Notices go through `notify`, in the language of `t`.\n */\nexport async function deriveComposite(editor: Editor, pos: number, kindId: string, ai: NonNullable<EditorBackend['ai']>, modelRef: (slot: 'generate') => ModelRef, notify: Notifier, t: Translate): Promise<void> {\n const source = editor.state.doc.nodeAt(pos)\n const sourceId = source && blockIdOf(source)\n const kind = compositeKindsOf(editor).find(candidate => candidate.id === kindId)\n if (source?.type.name !== 'composite' || !sourceId || !kind?.ai) return\n if (!canDeriveAfter(editor, pos, kindId)) {\n notify({ title: t('integrations.compositeDerive.notAccepted'), kind: 'error' })\n return\n }\n\n const sourceKind = compositeKind(compositeKindsOf(editor), String(source.attrs.kind))\n const derivation: Derivation = {\n from: { block: sourceId },\n method: `ai:${kindId}`,\n createdAt: new Date().toISOString(),\n inputHash: sha256Hex(bodyMarkdown(editor, source)),\n outputHash: '',\n }\n const id = newBlockId()\n const json = compositeJSON(kindId)\n const [title, ...rest] = json.content as JSONContent[]\n const text = `${kind.label}: ${source.child(0).textContent.trim() || sourceKind.label}`\n const created: JSONContent = {\n ...json,\n attrs: { ...json.attrs, id, meta: { [DERIVATION_KEY]: derivation } },\n content: [{ ...title!, content: [{ type: 'text', text }] }, ...rest],\n }\n editor.view.dispatch(editor.state.tr.insert(pos + source.nodeSize, editor.schema.nodeFromJSON(created)))\n\n const toastId = `derive-${id}`\n const controller = new AbortController()\n const abort = () => controller.abort()\n editor.on('destroy', abort)\n notify({ id: toastId, title: t('integrations.compositeDerive.running', { kind: kind.label }), kind: 'info', icon: 'i-lucide-loader-circle', duration: 0 })\n\n /** Drops the new Baustein again, unless its body got content meanwhile (the user may have started writing in it). */\n const discard = () => {\n const found = editor.isDestroyed ? null : findBlockById(editor.state.doc, id)\n if (!found) return\n const body = Array.from({ length: found.node.childCount - COMPOSITE_CHROME_COUNT }, (_, index) => found.node.child(COMPOSITE_CHROME_COUNT + index))\n if (body.some(child => child.textContent.trim() || child.type.name !== 'paragraph')) return\n editor.view.dispatch(editor.state.tr.delete(found.pos, found.pos + found.node.nodeSize))\n }\n\n try {\n const target = findBlockById(editor.state.doc, id)\n const result = target ? await generateCompositeBody(editor, target.pos, ai, modelRef, t, controller.signal) : null\n if (!result || result.status === 'aborted' || editor.isDestroyed) {\n if (!editor.isDestroyed) discard()\n return\n }\n if (result.status === 'rate-limited') {\n discard()\n notifyAiRateLimited(notify, t)\n return\n }\n if (result.status === 'data-error') {\n discard()\n notify({ title: t('integrations.compositeFill.dataMissing'), description: result.message, kind: 'error' })\n return\n }\n if (result.status === 'error') {\n discard()\n notify({ title: t('integrations.compositeDerive.failed'), description: result.message, kind: 'error' })\n return\n }\n\n const blocks = result.output.trim() ? parseBlocks(editor, result.output).blocks.map(block => editor.schema.nodeFromJSON(block)) : []\n if (!blocks.length) {\n discard()\n notify({ title: t('integrations.compositeDerive.empty.title'), description: t('integrations.compositeDerive.empty.description'), kind: 'error' })\n return\n }\n const found = findBlockById(editor.state.doc, id)\n if (!found || !found.node.canReplace(COMPOSITE_CHROME_COUNT, found.node.childCount, Fragment.from(blocks))) {\n notify({ title: t('integrations.compositeDerive.gone.title'), description: t('integrations.compositeDerive.gone.description'), kind: 'error' })\n return\n }\n\n // One transaction: body and the hash of exactly that body, so one undo step removes both.\n const chromeSize = Array.from({ length: COMPOSITE_CHROME_COUNT }, (_, index) => found.node.child(index).nodeSize).reduce((sum, size) => sum + size, 0)\n const tr = editor.state.tr.replaceWith(found.pos + 1 + chromeSize, found.pos + found.node.nodeSize - 1, blocks)\n const written = findBlockById(tr.doc, id)!\n const stored = derivationOf(written.node) ?? derivation\n tr.setNodeMarkup(written.pos, undefined, {\n ...written.node.attrs,\n meta: { ...written.node.attrs.meta, [DERIVATION_KEY]: { ...stored, outputHash: sha256Hex(bodyMarkdown(editor, written.node)) } },\n })\n editor.view.dispatch(tr)\n editor.commands.revealBlock(id)\n notify({ title: t('integrations.compositeDerive.done', { kind: kind.label }), kind: 'success', icon: 'i-lucide-git-branch' })\n }\n finally {\n notify.dismiss?.(toastId)\n editor.off('destroy', abort)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derive.js","names":[],"sources":["../../../src/integrations/composite-ai/derive.ts"],"sourcesContent":["import type { Editor, JSONContent } from '@tiptap/core'\nimport { Fragment, type Node as PMNode } from '@tiptap/pm/model'\nimport type { EditorBackend } from '../../core/backend'\nimport { guardsAccept, parseBlocks, sha256Hex } from '../../core'\nimport type { Notifier } from '../../core/notify'\nimport { blockIdOf, findBlockById, newBlockId } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { type ModelRef, notifyAiRateLimited } from '../../features/ai'\nimport { bodyMarkdown, COMPOSITE_CHROME_COUNT, compositeJSON, compositeKind, compositeKindsOf, DERIVATION_KEY, type Derivation, derivationOf } from '../../features/composites/schema'\nimport { generateCompositeBody } from './composite-fill'\n\n/** Kinds a Baustein can be derived into: the ones with an `ai` configuration (their brief drives the generation). */\nexport const derivableKindsOf = (editor: Editor) => compositeKindsOf(editor).filter(kind => kind.ai !== undefined)\n\n/** Whether the Baustein `node` can be the source of a derivation: a composite with a block id (the derivation points at it by id). */\nexport const isDerivableSource = (editor: Editor, node: PMNode) => node.type.name === 'composite' && blockIdOf(node) !== null && derivableKindsOf(editor).length > 0\n\n/** Whether a Baustein of `kindId` may be inserted right after the Baustein at `pos` (the same guards as creating it there). */\nexport function canDeriveAfter(editor: Editor, pos: number, kindId: string): boolean {\n const source = editor.state.doc.nodeAt(pos)\n if (!source) return false\n const $at = editor.state.doc.resolve(pos + source.nodeSize)\n return guardsAccept(editor, $at, { type: 'composite', attrs: { kind: kindId } })\n}\n\n/** Drops the derived Baustein `id` again, unless its body got content meanwhile (the user may have started writing in it). */\nexport function discardDerived(editor: Editor, id: string): void {\n const found = editor.isDestroyed ? null : findBlockById(editor.state.doc, id)\n if (!found) return\n const body = Array.from({ length: found.node.childCount - COMPOSITE_CHROME_COUNT }, (_, index) => found.node.child(COMPOSITE_CHROME_COUNT + index))\n if (body.some(child => child.textContent.trim() || child.type.name !== 'paragraph')) return\n editor.view.dispatch(editor.state.tr.delete(found.pos, found.pos + found.node.nodeSize))\n}\n\n/**\n * The write-back half of every derivation: parses the streamed `markdown` through the block pipeline and replaces the\n * body of the (still empty) derived Baustein `id` with it, in one transaction together with the hash of exactly that\n * body as `outputHash` (kept next to the `derivation` the Baustein already carries, else `options.derivation`; a Baustein\n * with neither only gets its body), so one\n * undo step removes both. An empty result calls `options.discard` and shows the „Leeres Ergebnis“ notice; a Baustein\n * that vanished or no longer takes the body shows „nicht mehr gefunden“; on success the Baustein is revealed and\n * `options.done` shown as success notice. The Baustein is found again by its block id, so edits while the model ran do\n * not misplace the result. Returns whether the body was written.\n */\nexport function writeDerivedBody(editor: Editor, id: string, markdown: string, options: { derivation?: Derivation, discard: () => void, notify: Notifier, t: Translate, done: string }): boolean {\n const { derivation, discard, notify, t, done } = options\n const blocks = markdown.trim() ? parseBlocks(editor, markdown).blocks.map(block => editor.schema.nodeFromJSON(block)) : []\n if (!blocks.length) {\n discard()\n notify({ title: t('integrations.compositeDerive.empty.title'), description: t('integrations.compositeDerive.empty.description'), kind: 'error' })\n return false\n }\n const found = findBlockById(editor.state.doc, id)\n if (!found || !found.node.canReplace(COMPOSITE_CHROME_COUNT, found.node.childCount, Fragment.from(blocks))) {\n notify({ title: t('integrations.compositeDerive.gone.title'), description: t('integrations.compositeDerive.gone.description'), kind: 'error' })\n return false\n }\n\n // One transaction: body and the hash of exactly that body, so one undo step removes both.\n const chromeSize = Array.from({ length: COMPOSITE_CHROME_COUNT }, (_, index) => found.node.child(index).nodeSize).reduce((sum, size) => sum + size, 0)\n const tr = editor.state.tr.replaceWith(found.pos + 1 + chromeSize, found.pos + found.node.nodeSize - 1, blocks)\n const written = findBlockById(tr.doc, id)!\n const stored = derivationOf(written.node) ?? derivation\n if (stored) {\n tr.setNodeMarkup(written.pos, undefined, {\n ...written.node.attrs,\n meta: { ...written.node.attrs.meta, [DERIVATION_KEY]: { ...stored, outputHash: sha256Hex(bodyMarkdown(editor, written.node)) } },\n })\n }\n editor.view.dispatch(tr)\n editor.commands.revealBlock(id)\n notify({ title: done, kind: 'success', icon: 'i-lucide-git-branch' })\n return true\n}\n\n/**\n * „Ableiten (KI) → <Art>“: inserts a sibling Baustein of kind `kindId` right after the Baustein at `pos`, titled\n * „<Art>: <Quelle>“, whose `meta.derivation` points back at the source block (`from.block`, method `ai:<kindId>`, the\n * Markdown hash of the source's body as `inputHash`), generates its body from the source (`generateCompositeBody`; the\n * `aiBlockData` of composites hands the model the source's Markdown) and writes the result directly into the new\n * Baustein — no review, the new Baustein is the proposal. The hash of the written body becomes `outputHash`, so a later\n * edit shows as „bearbeitet“ and a later edit of the source as „Quelle geändert“. The new Baustein is found again by its\n * block id after every await, so edits while the model runs do not misplace the result. When the run fails or yields\n * nothing the still-empty Baustein is removed again. Notices go through `notify`, in the language of `t`.\n */\nexport async function deriveComposite(editor: Editor, pos: number, kindId: string, ai: NonNullable<EditorBackend['ai']>, modelRef: (slot: 'generate') => ModelRef, notify: Notifier, t: Translate): Promise<void> {\n const source = editor.state.doc.nodeAt(pos)\n const sourceId = source && blockIdOf(source)\n const kind = compositeKindsOf(editor).find(candidate => candidate.id === kindId)\n if (source?.type.name !== 'composite' || !sourceId || !kind?.ai) return\n if (!canDeriveAfter(editor, pos, kindId)) {\n notify({ title: t('integrations.compositeDerive.notAccepted'), kind: 'error' })\n return\n }\n\n const sourceKind = compositeKind(compositeKindsOf(editor), String(source.attrs.kind))\n const derivation: Derivation = {\n from: { block: sourceId },\n method: `ai:${kindId}`,\n createdAt: new Date().toISOString(),\n inputHash: sha256Hex(bodyMarkdown(editor, source)),\n outputHash: '',\n }\n const id = newBlockId()\n const json = compositeJSON(kindId)\n const [title, ...rest] = json.content as JSONContent[]\n const text = `${kind.label}: ${source.child(0).textContent.trim() || sourceKind.label}`\n const created: JSONContent = {\n ...json,\n attrs: { ...json.attrs, id, meta: { [DERIVATION_KEY]: derivation } },\n content: [{ ...title!, content: [{ type: 'text', text }] }, ...rest],\n }\n editor.view.dispatch(editor.state.tr.insert(pos + source.nodeSize, editor.schema.nodeFromJSON(created)))\n\n const toastId = `derive-${id}`\n const controller = new AbortController()\n const abort = () => controller.abort()\n editor.on('destroy', abort)\n notify({ id: toastId, title: t('integrations.compositeDerive.running', { kind: kind.label }), kind: 'info', icon: 'i-lucide-loader-circle', duration: 0 })\n\n const discard = () => discardDerived(editor, id)\n\n try {\n const target = findBlockById(editor.state.doc, id)\n const result = target ? await generateCompositeBody(editor, target.pos, ai, modelRef, t, controller.signal) : null\n if (!result || result.status === 'aborted' || editor.isDestroyed) {\n if (!editor.isDestroyed) discard()\n return\n }\n if (result.status === 'rate-limited') {\n discard()\n notifyAiRateLimited(notify, t)\n return\n }\n if (result.status === 'data-error') {\n discard()\n notify({ title: t('integrations.compositeFill.dataMissing'), description: result.message, kind: 'error' })\n return\n }\n if (result.status === 'error') {\n discard()\n notify({ title: t('integrations.compositeDerive.failed'), description: result.message, kind: 'error' })\n return\n }\n\n writeDerivedBody(editor, id, result.output, { derivation, discard, notify, t, done: t('integrations.compositeDerive.done', { kind: kind.label }) })\n }\n finally {\n notify.dismiss?.(toastId)\n editor.off('destroy', abort)\n }\n}\n"],"mappings":"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"}
@@ -1,11 +1,14 @@
1
1
  import { EditorFeature } from "../../core/feature.js";
2
2
  import "../../core/index.js";
3
3
  import "../../features/composites/bibliography-command.js";
4
+ import { bulletsTransform, summaryTransform, transferRequest } from "./transfer-transforms.js";
4
5
  import { CompositeBodyResult, fillComposite, generateCompositeBody } from "./composite-fill.js";
5
6
  import { COMPOSITE_BODY_START, compositeSourceText, summarizeComposite } from "./composite-summary.js";
6
7
  import { RenditionOptions, canCreateRendition, canRegenerateRendition, createRendition, regenerateRendition } from "./rendition.js";
7
- import { canDeriveAfter, derivableKindsOf, deriveComposite, isDerivableSource } from "./derive.js";
8
+ import { canDeriveAfter, derivableKindsOf, deriveComposite, discardDerived, isDerivableSource, writeDerivedBody } from "./derive.js";
8
9
  import { canFetchBibliography, doiOf, fetchBibliography } from "./bibliography-fetch.js";
10
+ import { OutlineEntry, OutlineHeading, applyOutline, collectHeadings, parseOutlineResponse, sanitizeOutline } from "./outline-sanitize.js";
11
+ import { bodyUnchanged, splitByInsertedHeadings, splitCompositeWithAi } from "./split.js";
9
12
  //#region src/integrations/composite-ai/index.d.ts
10
13
  /**
11
14
  * AI on composites (requires `composites`, `ai`, `media`): the „Baustein füllen (KI)“ block action (on composites of
@@ -18,9 +21,11 @@ import { canFetchBibliography, doiOf, fetchBibliography } from "./bibliography-f
18
21
  * has a heading is inserted as a `section` composite fence, otherwise (and in flat structure) the base AI heading
19
22
  * is used. It is the preset's single `formatAiResult` contributor. The base AI feature and the composites feature do
20
23
  * not know it. Framework-free: notices go through the surface's `ctx.notify`, the model through the app's
21
- * `modelProfiles(ctx.app)`.
24
+ * `modelProfiles(ctx.app)`. Also „Mit KI in (Unter-)Bausteine teilen“ (block actions on composites: the model inserts
25
+ * headings, the body is split at them) and „Gliederung bereinigen (KI)“ (⌘K command and block action on headings:
26
+ * repairs the heading outline of the whole document).
22
27
  */
23
28
  export declare function compositeAiFeature(): EditorFeature<never>;
24
29
  //#endregion
25
- export { COMPOSITE_BODY_START, type CompositeBodyResult, type RenditionOptions, canCreateRendition, canDeriveAfter, canFetchBibliography, canRegenerateRendition, compositeSourceText, createRendition, derivableKindsOf, deriveComposite, doiOf, fetchBibliography, fillComposite, generateCompositeBody, isDerivableSource, regenerateRendition, summarizeComposite };
30
+ export { COMPOSITE_BODY_START, type CompositeBodyResult, type OutlineEntry, type OutlineHeading, type RenditionOptions, applyOutline, bodyUnchanged, bulletsTransform, canCreateRendition, canDeriveAfter, canFetchBibliography, canRegenerateRendition, collectHeadings, compositeSourceText, createRendition, derivableKindsOf, deriveComposite, discardDerived, doiOf, fetchBibliography, fillComposite, generateCompositeBody, isDerivableSource, parseOutlineResponse, regenerateRendition, sanitizeOutline, splitByInsertedHeadings, splitCompositeWithAi, summarizeComposite, summaryTransform, transferRequest, writeDerivedBody };
26
31
  //# sourceMappingURL=index.d.ts.map
@@ -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":";;;;;;;;;;;;;;;AAoBA,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;CACF;AACF;;;;;;;;;;;;;;AAeA,SAAgB,qBAA2C;CACzD,OAAO;EACL,IAAI;EACJ,WAAW;EACX,UAAU;GAAC;GAAc;GAAM;EAAO;EACtC,cAAc;EACd,YAAY;EACZ,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"}
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":"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"}
@@ -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